@@ -99,6 +99,13 @@ func (client *LinuxClient) IsMounted(ctx context.Context, sourceDevice, mountpoi
9999 Logc (ctx ).WithFields (logFields ).Debug (">>>> mount_linux.IsMounted" )
100100 defer Logc (ctx ).WithFields (logFields ).Debug ("<<<< mount_linux.IsMounted" )
101101
102+ sourceDevice = strings .TrimPrefix (sourceDevice , "/dev/" )
103+
104+ // Ensure at least one arg was specified
105+ if sourceDevice == "" && mountpoint == "" {
106+ return false , errors .New ("no device or mountpoint specified" )
107+ }
108+
102109 // Get device path if source is already linked
103110 devicePath , err := client .filepath .EvalSymlinks (sourceDevice )
104111 if err != nil {
@@ -108,67 +115,23 @@ func (client *LinuxClient) IsMounted(ctx context.Context, sourceDevice, mountpoi
108115 }
109116 devicePath = strings .TrimPrefix (devicePath , "/dev/" )
110117
111- sourceDevice = strings .TrimPrefix (sourceDevice , "/dev/" )
112-
113- // Ensure at least one arg was specified
114- if sourceDevice == "" && mountpoint == "" {
115- return false , errors .New ("no device or mountpoint specified" )
116- }
117-
118- // Read the system mounts
119- procSelfMountinfo , err := client .ListProcMountinfo ()
118+ mountInfo , err := client .ReadMountProcInfo (ctx , mountpoint , sourceDevice , devicePath )
120119 if err != nil {
121- Logc (ctx ).WithFields (logFields ).Errorf ( "checking mounts failed; %s" , err )
122- return false , fmt . Errorf ( "checking mounts failed; %s" , err )
120+ Logc (ctx ).WithFields (logFields ).WithError ( err ). Debug ( "Checking mounts failed." )
121+ return false , err
123122 }
124123
125- // Check each mount for a match of source device and/or mountpoint
126- for _ , procMount := range procSelfMountinfo {
127-
128- // If mountpoint was specified and doesn't match proc mount, move on
129- if mountpoint != "" {
130- if ! strings .Contains (procMount .MountPoint , mountpoint ) {
131- continue
132- }
133- Logc (ctx ).WithFields (logFields ).Debugf ("Mountpoint found: %v" , procMount )
134- }
135-
136- // If sourceDevice was specified and doesn't match proc mount, move on
137- if sourceDevice != "" {
138-
139- procSourceDevice := strings .TrimPrefix (procMount .Root , "/" )
140-
141- if strings .HasPrefix (procMount .MountSource , "/dev/" ) {
142- procSourceDevice = strings .TrimPrefix (procMount .MountSource , "/dev/" )
143- if sourceDevice != procSourceDevice && devicePath != procSourceDevice {
144- // Resolve any symlinks to get the real device, if device path has not already been found
145- procSourceDevice , err = client .filepath .EvalSymlinks (procMount .MountSource )
146- if err != nil {
147- Logc (ctx ).WithFields (logFields ).WithError (err ).Debug ("Could not resolve device symlink" )
148- continue
149- }
150- procSourceDevice = strings .TrimPrefix (procSourceDevice , "/dev/" )
151- }
152- }
153-
154- if sourceDevice != procSourceDevice && devicePath != procSourceDevice {
155- continue
156- }
157-
158- Logc (ctx ).WithFields (logFields ).Debugf ("Device found: %v" , sourceDevice )
159-
160- if err = checkMountOptions (ctx , procMount , mountOptions ); err != nil {
161- Logc (ctx ).WithFields (logFields ).WithError (err ).Warning ("Checking mount options failed." )
162- }
163-
164- }
124+ if mountInfo == nil {
125+ Logc (ctx ).WithFields (logFields ).Debug ("Mount information not found." )
126+ return false , nil
127+ }
165128
166- Logc (ctx ). WithFields ( logFields ). Debug ( "Mount information found." )
167- return true , nil
129+ if err = checkMountOptions (ctx , mountInfo , mountOptions ); err != nil {
130+ Logc ( ctx ). WithFields ( logFields ). WithError ( err ). Warning ( "Checking mount options failed." )
168131 }
169132
170- Logc (ctx ).WithFields (logFields ).Debug ("Mount information not found." )
171- return false , nil
133+ Logc (ctx ).WithFields (logFields ).Debug ("Mount information found." )
134+ return true , nil
172135}
173136
174137// PVMountpointMappings identifies devices corresponding to published paths
@@ -349,8 +312,9 @@ func (client *LinuxClient) MountDevice(ctx context.Context, device, mountpoint,
349312 }
350313
351314 if ! mounted {
352- if _ , err := client .command .Execute (ctx , "mount" , args ... ); err != nil {
353- Logc (ctx ).WithField ("error" , err ).Error ("Mount failed." )
315+ if out , err := client .command .Execute (ctx , "mount" , args ... ); err != nil {
316+ Logc (ctx ).WithError (fmt .Errorf ("exit error: %v, mount command output: %s" , err , string (out ))).Error ("Mount failed." )
317+ return fmt .Errorf ("mount failed: %v, output: %s" , err , out )
354318 }
355319 }
356320
@@ -406,6 +370,9 @@ func (client *LinuxClient) EnsureFileExists(ctx context.Context, path string) er
406370func (client * LinuxClient ) EnsureDirExists (ctx context.Context , path string ) error {
407371 fields := LogFields {"path" : path }
408372
373+ Logc (ctx ).WithFields (fields ).Debug (">>>> EnsureDirExists" )
374+ defer Logc (ctx ).WithFields (fields ).Debug ("<<<< EnsureDirExists" )
375+
409376 if info , err := client .os .Stat (path ); err == nil {
410377 if ! info .IsDir () {
411378 Logc (ctx ).WithFields (fields ).Error ("Path exists but is not a directory" )
@@ -590,48 +557,18 @@ func parseProcMountInfo(content []byte) ([]models.MountInfo, error) {
590557 // The last split() item is empty string following the last \n
591558 continue
592559 }
593- fields := strings .Fields (line )
594- numFields := len (fields )
595- if numFields < minNumProcSelfMntInfoFieldsPerLine {
596- return nil , fmt .Errorf ("wrong number of fields (expected at least %d, got %d): %s" ,
597- minNumProcSelfMntInfoFieldsPerLine , numFields , line )
598- }
599-
600- // separator must be in the 4th position from the end for the line to contain fsType, mountSource, and
601- // superOptions
602- if fields [numFields - 4 ] != "-" {
603- return nil , fmt .Errorf ("malformed mountinfo (could not find separator): %s" , line )
604- }
605-
606- // If root value is marked deleted, skip the entry
607- if strings .Contains (fields [3 ], "deleted" ) {
608- continue
609- }
610-
611- mp := models.MountInfo {
612- DeviceId : fields [2 ],
613- Root : fields [3 ],
614- MountPoint : fields [4 ],
615- MountOptions : strings .Split (fields [5 ], "," ),
616- }
617560
618- mountId , err := strconv . Atoi ( fields [ 0 ] )
561+ mp , err := parseProcMount ( line )
619562 if err != nil {
620563 return nil , err
621564 }
622- mp .MountId = mountId
623565
624- parentId , err := strconv . Atoi ( fields [ 1 ])
625- if err != nil {
626- return nil , err
566+ if mp == nil {
567+ // For the case, where err == nil && mp == nil, this can happen when root is marked as deleted.
568+ continue
627569 }
628- mp .ParentId = parentId
629570
630- mp .FsType = fields [numFields - 3 ]
631- mp .MountSource = fields [numFields - 2 ]
632- mp .SuperOptions = strings .Split (fields [numFields - 1 ], "," )
633-
634- out = append (out , mp )
571+ out = append (out , * mp )
635572 }
636573 return out , nil
637574}
@@ -681,3 +618,194 @@ func parseProcMounts(content []byte) ([]models.MountPoint, error) {
681618 }
682619 return out , nil
683620}
621+
622+ // ReadMountProcInfo tries the consistent read for the given mount information from the "/proc/self/mountinfo"
623+ func (client * LinuxClient ) ReadMountProcInfo (ctx context.Context , mountpoint , sourceDevice , devicePath string ) (* models.MountInfo , error ) {
624+ return client .ConsistentReadMount (ctx , "/proc/self/mountinfo" , mountpoint , sourceDevice , devicePath , maxListTries )
625+ }
626+
627+ // ConsistentReadMount verifies whether a specific mountpoint and source device consistently appear in the mount file.
628+ // It reads the file up to maxAttempts times, returning parsedEntry if the same matching entry is found in two consecutive reads.
629+ // Returns nil and an error if either the entry was present but not consistently found within the allowed attempts or there was an error.
630+ // Returns nil and nil if the entry was never found.
631+ func (client * LinuxClient ) ConsistentReadMount (ctx context.Context , filename , mountpoint , sourceDevice , devicePath string , maxAttempts int ) (* models.MountInfo , error ) {
632+ logFields := LogFields {
633+ "filename" : filename ,
634+ "mountpoint" : mountpoint ,
635+ "maxAttempts" : maxAttempts ,
636+ }
637+
638+ Logc (ctx ).WithFields (logFields ).Debug (">>>> ConsistentReadMount" )
639+ defer Logc (ctx ).WithFields (logFields ).Debug ("<<<< ConsistentReadMount" )
640+
641+ if maxAttempts < 1 {
642+ Logc (ctx ).WithFields (logFields ).Errorf ("maxAttempts has to be equal or greater than 1, currently set to :%d" , maxAttempts )
643+ return nil , fmt .Errorf ("maxAttempts has to be equal or greater than 1, currently set to :%d" , maxAttempts )
644+ }
645+
646+ var (
647+ previousMountEntry string
648+ entryFound = false
649+ )
650+
651+ // First iteration is to actually read the file for the first time.
652+ // And maxAttempts are made for the comparison thereafter.
653+ // Hence, +1
654+ for i := 0 ; i < (maxAttempts + 1 ); i ++ {
655+ select {
656+ case <- ctx .Done ():
657+ Logc (ctx ).WithError (ctx .Err ()).Error ("Exiting early as the context has been cancelled or timed out." )
658+ return nil , ctx .Err ()
659+ default :
660+ }
661+
662+ var (
663+ mountFound bool
664+ currentMount string
665+ parsedEntry * models.MountInfo
666+ )
667+
668+ content , err := client .os .ReadFile (filename )
669+ if err != nil {
670+ Logc (ctx ).WithError (err ).Error ("Failed to read mount file." )
671+ return nil , err
672+ }
673+
674+ // Ranging over each entry.
675+ lines := strings .Split (string (content ), "\n " )
676+ for _ , currentMount = range lines {
677+ if currentMount == "" {
678+ // The last split() item is empty string following the last \n
679+ continue
680+ }
681+
682+ // Parsing the line that we retrieved to models.MountInfo struct
683+ parsedEntry , err = parseProcMount (currentMount )
684+ if err != nil || parsedEntry == nil {
685+ // We'll keep continuing ranging over remaining lines.
686+ Logc (ctx ).WithField ("line" , currentMount ).WithError (err ).Warn ("Unable to parse the entry." )
687+ continue
688+ }
689+
690+ // Comparing the parsedEntry with the information that we have.
691+ mountFound , err = client .compareMount (ctx , mountpoint , sourceDevice , devicePath , parsedEntry )
692+ if err != nil {
693+ Logc (ctx ).WithField ("line" , currentMount ).WithError (err ).Warn ("Unable to compare the mount." )
694+ continue
695+ }
696+
697+ if mountFound {
698+ break
699+ }
700+ }
701+
702+ if mountFound {
703+ entryFound = true
704+ if previousMountEntry == currentMount {
705+ return parsedEntry , nil
706+ }
707+ }
708+
709+ // Changing previousMountEntry every iteration ensures, that when we're comparing,
710+ // it is between consecutive reads.
711+ previousMountEntry = currentMount
712+ }
713+
714+ if entryFound {
715+ return nil , fmt .Errorf ("could not find consistent mount entry in %s for mountpoint %s after %d attempts" , filename , mountpoint , maxAttempts )
716+ }
717+
718+ return nil , nil
719+ }
720+
721+ // parseProcMount parses single line/entry of the /proc/self/mountinfo file.
722+ func parseProcMount (line string ) (* models.MountInfo , error ) {
723+ // Ex: 26 29 0:5 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=4027860k,nr_inodes=1006965,mode=755,inode64
724+
725+ fields := strings .Fields (line )
726+ numFields := len (fields )
727+ if numFields < minNumProcSelfMntInfoFieldsPerLine {
728+ return nil , fmt .Errorf ("wrong number of fields (expected at least %d, got %d): %s" ,
729+ minNumProcSelfMntInfoFieldsPerLine , numFields , line )
730+ }
731+
732+ // separator must be in the 4th position from the end for the line to contain fsType, mountSource, and
733+ // superOptions
734+ if fields [numFields - 4 ] != "-" {
735+ return nil , fmt .Errorf ("malformed mountinfo (could not find separator): %s" , line )
736+ }
737+
738+ // If root value is marked deleted, skip the entry
739+ if strings .Contains (fields [3 ], "deleted" ) {
740+ return nil , nil
741+ }
742+
743+ mp := models.MountInfo {
744+ DeviceId : fields [2 ],
745+ Root : fields [3 ],
746+ MountPoint : fields [4 ],
747+ MountOptions : strings .Split (fields [5 ], "," ),
748+ }
749+
750+ mountId , err := strconv .Atoi (fields [0 ])
751+ if err != nil {
752+ return nil , err
753+ }
754+ mp .MountId = mountId
755+
756+ parentId , err := strconv .Atoi (fields [1 ])
757+ if err != nil {
758+ return nil , err
759+ }
760+ mp .ParentId = parentId
761+
762+ mp .FsType = fields [numFields - 3 ]
763+ mp .MountSource = fields [numFields - 2 ]
764+ mp .SuperOptions = strings .Split (fields [numFields - 1 ], "," )
765+
766+ return & mp , nil
767+ }
768+
769+ // compareMount compares the given mountpoint and sourceDevice with the retrieved entry from /proc/self/mountinfo
770+ func (client * LinuxClient ) compareMount (ctx context.Context , mountpoint , sourceDevice , devicePath string , procMount * models.MountInfo ) (bool , error ) {
771+ logFields := LogFields {
772+ "mountPoint" : mountpoint ,
773+ "procMount" : procMount ,
774+ }
775+
776+ var err error
777+
778+ if mountpoint != "" {
779+ if ! strings .Contains (procMount .MountPoint , mountpoint ) {
780+ return false , nil
781+ }
782+ Logc (ctx ).WithFields (logFields ).Debugf ("Mountpoint found: %v" , procMount )
783+ }
784+
785+ // If sourceDevice was specified and doesn't match proc mount, move on
786+ if sourceDevice != "" {
787+
788+ procSourceDevice := strings .TrimPrefix (procMount .Root , "/" )
789+
790+ if strings .HasPrefix (procMount .MountSource , "/dev/" ) {
791+ procSourceDevice = strings .TrimPrefix (procMount .MountSource , "/dev/" )
792+ if sourceDevice != procSourceDevice && devicePath != procSourceDevice {
793+ // Resolve any symlinks to get the real device, if device path has not already been found
794+ procSourceDevice , err = client .filepath .EvalSymlinks (procMount .MountSource )
795+ if err != nil {
796+ Logc (ctx ).WithFields (logFields ).WithError (err ).Debug ("Could not resolve device symlink" )
797+ return false , err
798+ }
799+ procSourceDevice = strings .TrimPrefix (procSourceDevice , "/dev/" )
800+ }
801+ }
802+
803+ if sourceDevice != procSourceDevice && devicePath != procSourceDevice {
804+ return false , nil
805+ }
806+
807+ Logc (ctx ).WithFields (logFields ).Debugf ("Device found: %v" , sourceDevice )
808+ }
809+
810+ return true , nil
811+ }
0 commit comments