storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/mountinfo/mountinfo_linux.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5   * MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
     6   *
     7   * Licensed under the Apache License, Version 2.0 (the "License");
     8   * you may not use this file except in compliance with the License.
     9   * You may obtain a copy of the License at
    10   *
    11   *     http://www.apache.org/licenses/LICENSE-2.0
    12   *
    13   * Unless required by applicable law or agreed to in writing, software
    14   * distributed under the License is distributed on an "AS IS" BASIS,
    15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16   * See the License for the specific language governing permissions and
    17   * limitations under the License.
    18   */
    19  
    20  package mountinfo
    21  
    22  import (
    23  	"bufio"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"path/filepath"
    28  	"strconv"
    29  	"strings"
    30  	"syscall"
    31  )
    32  
    33  const (
    34  	// Number of fields per line in /proc/mounts as per the fstab man page.
    35  	expectedNumFieldsPerLine = 6
    36  	// Location of the mount file to use
    37  	procMountsPath = "/proc/mounts"
    38  )
    39  
    40  // IsLikelyMountPoint determines if a directory is a mountpoint.
    41  func IsLikelyMountPoint(path string) bool {
    42  	s1, err := os.Lstat(path)
    43  	if err != nil {
    44  		return false
    45  	}
    46  
    47  	// A symlink can never be a mount point
    48  	if s1.Mode()&os.ModeSymlink != 0 {
    49  		return false
    50  	}
    51  
    52  	s2, err := os.Lstat(filepath.Dir(strings.TrimSuffix(path, "/")))
    53  	if err != nil {
    54  		return false
    55  	}
    56  
    57  	// If the directory has a different device as parent, then it is a mountpoint.
    58  	if s1.Sys().(*syscall.Stat_t).Dev != s2.Sys().(*syscall.Stat_t).Dev {
    59  		//  path/.. on a different device as path
    60  		return true
    61  	}
    62  
    63  	// path/.. is the same i-node as path - this check is for bind mounts.
    64  	return s1.Sys().(*syscall.Stat_t).Ino == s2.Sys().(*syscall.Stat_t).Ino
    65  }
    66  
    67  // CheckCrossDevice - check if any list of paths has any sub-mounts at /proc/mounts.
    68  func CheckCrossDevice(absPaths []string) error {
    69  	return checkCrossDevice(absPaths, procMountsPath)
    70  }
    71  
    72  // Check cross device is an internal function.
    73  func checkCrossDevice(absPaths []string, mountsPath string) error {
    74  	mounts, err := readProcMounts(mountsPath)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	for _, path := range absPaths {
    79  		if err := mounts.checkCrossMounts(path); err != nil {
    80  			return err
    81  		}
    82  	}
    83  	return nil
    84  }
    85  
    86  // CheckCrossDevice - check if given path has any sub-mounts in the input mounts list.
    87  func (mts mountInfos) checkCrossMounts(path string) error {
    88  	if !filepath.IsAbs(path) {
    89  		return fmt.Errorf("Invalid argument, path (%s) is expected to be absolute", path)
    90  	}
    91  	var crossMounts mountInfos
    92  	for _, mount := range mts {
    93  		// Add a separator to indicate that this is a proper mount-point.
    94  		// This is to avoid a situation where prefix is '/tmp/fsmount'
    95  		// and mount path is /tmp/fs. In such a scenario we need to check for
    96  		// `/tmp/fs/` to be a common prefix amount other mounts.
    97  		mpath := strings.TrimSuffix(mount.Path, "/") + "/"
    98  		ppath := strings.TrimSuffix(path, "/") + "/"
    99  		if strings.HasPrefix(mpath, ppath) {
   100  			// At this point if the mount point has a common prefix two conditions can happen.
   101  			// - mount.Path matches exact with `path` means we can proceed no error here.
   102  			// - mount.Path doesn't match (means cross-device mount), should error out.
   103  			if mount.Path != path {
   104  				crossMounts = append(crossMounts, mount)
   105  			}
   106  		}
   107  	}
   108  	msg := `Cross-device mounts detected on path (%s) at following locations %s. Export path should not have any sub-mounts, refusing to start.`
   109  	if len(crossMounts) > 0 {
   110  		// if paths didn't match then we do have cross-device mount.
   111  		return fmt.Errorf(msg, path, crossMounts)
   112  	}
   113  	return nil
   114  }
   115  
   116  // readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash
   117  // of the contents.  If the out argument is not nil, this fills it with MountPoint structs.
   118  func readProcMounts(mountFilePath string) (mountInfos, error) {
   119  	file, err := os.Open(mountFilePath)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	defer file.Close()
   124  	return parseMountFrom(file)
   125  }
   126  
   127  func parseMountFrom(file io.Reader) (mountInfos, error) {
   128  	var mounts = mountInfos{}
   129  	scanner := bufio.NewReader(file)
   130  	for {
   131  		line, err := scanner.ReadString('\n')
   132  		if err == io.EOF {
   133  			break
   134  		}
   135  
   136  		fields := strings.Fields(line)
   137  		if len(fields) != expectedNumFieldsPerLine {
   138  			// ignore incorrect lines.
   139  			continue
   140  		}
   141  
   142  		// Freq should be an integer.
   143  		if _, err := strconv.Atoi(fields[4]); err != nil {
   144  			return nil, err
   145  		}
   146  
   147  		// Pass should be an integer.
   148  		if _, err := strconv.Atoi(fields[5]); err != nil {
   149  			return nil, err
   150  		}
   151  
   152  		mounts = append(mounts, mountInfo{
   153  			Device:  fields[0],
   154  			Path:    fields[1],
   155  			FSType:  fields[2],
   156  			Options: strings.Split(fields[3], ","),
   157  			Freq:    fields[4],
   158  			Pass:    fields[5],
   159  		})
   160  	}
   161  	return mounts, nil
   162  }