github.com/demonoid81/containerd@v1.3.4/mount/mount_linux.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package mount
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/containerd/containerd/sys"
    27  	"github.com/pkg/errors"
    28  	"golang.org/x/sys/unix"
    29  )
    30  
    31  var pagesize = 4096
    32  
    33  func init() {
    34  	pagesize = os.Getpagesize()
    35  }
    36  
    37  // Mount to the provided target path
    38  func (m *Mount) Mount(target string) error {
    39  	var (
    40  		chdir   string
    41  		options = m.Options
    42  	)
    43  
    44  	// avoid hitting one page limit of mount argument buffer
    45  	//
    46  	// NOTE: 512 is a buffer during pagesize check.
    47  	if m.Type == "overlay" && optionsSize(options) >= pagesize-512 {
    48  		chdir, options = compactLowerdirOption(options)
    49  	}
    50  
    51  	flags, data := parseMountOptions(options)
    52  	if len(data) > pagesize {
    53  		return errors.Errorf("mount options is too long")
    54  	}
    55  
    56  	// propagation types.
    57  	const ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE
    58  
    59  	// Ensure propagation type change flags aren't included in other calls.
    60  	oflags := flags &^ ptypes
    61  
    62  	// In the case of remounting with changed data (data != ""), need to call mount (moby/moby#34077).
    63  	if flags&unix.MS_REMOUNT == 0 || data != "" {
    64  		// Initial call applying all non-propagation flags for mount
    65  		// or remount with changed data
    66  		if err := mountAt(chdir, m.Source, target, m.Type, uintptr(oflags), data); err != nil {
    67  			return err
    68  		}
    69  	}
    70  
    71  	if flags&ptypes != 0 {
    72  		// Change the propagation type.
    73  		const pflags = ptypes | unix.MS_REC | unix.MS_SILENT
    74  		if err := unix.Mount("", target, "", uintptr(flags&pflags), ""); err != nil {
    75  			return err
    76  		}
    77  	}
    78  
    79  	const broflags = unix.MS_BIND | unix.MS_RDONLY
    80  	if oflags&broflags == broflags {
    81  		// Remount the bind to apply read only.
    82  		return unix.Mount("", target, "", uintptr(oflags|unix.MS_REMOUNT), "")
    83  	}
    84  	return nil
    85  }
    86  
    87  // Unmount the provided mount path with the flags
    88  func Unmount(target string, flags int) error {
    89  	if err := unmount(target, flags); err != nil && err != unix.EINVAL {
    90  		return err
    91  	}
    92  	return nil
    93  }
    94  
    95  func unmount(target string, flags int) error {
    96  	for i := 0; i < 50; i++ {
    97  		if err := unix.Unmount(target, flags); err != nil {
    98  			switch err {
    99  			case unix.EBUSY:
   100  				time.Sleep(50 * time.Millisecond)
   101  				continue
   102  			default:
   103  				return err
   104  			}
   105  		}
   106  		return nil
   107  	}
   108  	return errors.Wrapf(unix.EBUSY, "failed to unmount target %s", target)
   109  }
   110  
   111  // UnmountAll repeatedly unmounts the given mount point until there
   112  // are no mounts remaining (EINVAL is returned by mount), which is
   113  // useful for undoing a stack of mounts on the same mount point.
   114  // UnmountAll all is noop when the first argument is an empty string.
   115  // This is done when the containerd client did not specify any rootfs
   116  // mounts (e.g. because the rootfs is managed outside containerd)
   117  // UnmountAll is noop when the mount path does not exist.
   118  func UnmountAll(mount string, flags int) error {
   119  	if mount == "" {
   120  		return nil
   121  	}
   122  	if _, err := os.Stat(mount); os.IsNotExist(err) {
   123  		return nil
   124  	}
   125  
   126  	for {
   127  		if err := unmount(mount, flags); err != nil {
   128  			// EINVAL is returned if the target is not a
   129  			// mount point, indicating that we are
   130  			// done. It can also indicate a few other
   131  			// things (such as invalid flags) which we
   132  			// unfortunately end up squelching here too.
   133  			if err == unix.EINVAL {
   134  				return nil
   135  			}
   136  			return err
   137  		}
   138  	}
   139  }
   140  
   141  // parseMountOptions takes fstab style mount options and parses them for
   142  // use with a standard mount() syscall
   143  func parseMountOptions(options []string) (int, string) {
   144  	var (
   145  		flag int
   146  		data []string
   147  	)
   148  	flags := map[string]struct {
   149  		clear bool
   150  		flag  int
   151  	}{
   152  		"async":         {true, unix.MS_SYNCHRONOUS},
   153  		"atime":         {true, unix.MS_NOATIME},
   154  		"bind":          {false, unix.MS_BIND},
   155  		"defaults":      {false, 0},
   156  		"dev":           {true, unix.MS_NODEV},
   157  		"diratime":      {true, unix.MS_NODIRATIME},
   158  		"dirsync":       {false, unix.MS_DIRSYNC},
   159  		"exec":          {true, unix.MS_NOEXEC},
   160  		"mand":          {false, unix.MS_MANDLOCK},
   161  		"noatime":       {false, unix.MS_NOATIME},
   162  		"nodev":         {false, unix.MS_NODEV},
   163  		"nodiratime":    {false, unix.MS_NODIRATIME},
   164  		"noexec":        {false, unix.MS_NOEXEC},
   165  		"nomand":        {true, unix.MS_MANDLOCK},
   166  		"norelatime":    {true, unix.MS_RELATIME},
   167  		"nostrictatime": {true, unix.MS_STRICTATIME},
   168  		"nosuid":        {false, unix.MS_NOSUID},
   169  		"rbind":         {false, unix.MS_BIND | unix.MS_REC},
   170  		"relatime":      {false, unix.MS_RELATIME},
   171  		"remount":       {false, unix.MS_REMOUNT},
   172  		"ro":            {false, unix.MS_RDONLY},
   173  		"rw":            {true, unix.MS_RDONLY},
   174  		"strictatime":   {false, unix.MS_STRICTATIME},
   175  		"suid":          {true, unix.MS_NOSUID},
   176  		"sync":          {false, unix.MS_SYNCHRONOUS},
   177  	}
   178  	for _, o := range options {
   179  		// If the option does not exist in the flags table or the flag
   180  		// is not supported on the platform,
   181  		// then it is a data value for a specific fs type
   182  		if f, exists := flags[o]; exists && f.flag != 0 {
   183  			if f.clear {
   184  				flag &^= f.flag
   185  			} else {
   186  				flag |= f.flag
   187  			}
   188  		} else {
   189  			data = append(data, o)
   190  		}
   191  	}
   192  	return flag, strings.Join(data, ",")
   193  }
   194  
   195  // compactLowerdirOption updates overlay lowdir option and returns the common
   196  // dir among all the lowdirs.
   197  func compactLowerdirOption(opts []string) (string, []string) {
   198  	idx, dirs := findOverlayLowerdirs(opts)
   199  	if idx == -1 || len(dirs) == 1 {
   200  		// no need to compact if there is only one lowerdir
   201  		return "", opts
   202  	}
   203  
   204  	// find out common dir
   205  	commondir := longestCommonPrefix(dirs)
   206  	if commondir == "" {
   207  		return "", opts
   208  	}
   209  
   210  	// NOTE: the snapshot id is based on digits.
   211  	// in order to avoid to get snapshots/x, should be back to parent dir.
   212  	// however, there is assumption that the common dir is ${root}/io.containerd.v1.overlayfs/snapshots.
   213  	commondir = path.Dir(commondir)
   214  	if commondir == "/" {
   215  		return "", opts
   216  	}
   217  	commondir = commondir + "/"
   218  
   219  	newdirs := make([]string, 0, len(dirs))
   220  	for _, dir := range dirs {
   221  		newdirs = append(newdirs, dir[len(commondir):])
   222  	}
   223  
   224  	newopts := copyOptions(opts)
   225  	newopts = append(newopts[:idx], newopts[idx+1:]...)
   226  	newopts = append(newopts, fmt.Sprintf("lowerdir=%s", strings.Join(newdirs, ":")))
   227  	return commondir, newopts
   228  }
   229  
   230  // findOverlayLowerdirs returns the index of lowerdir in mount's options and
   231  // all the lowerdir target.
   232  func findOverlayLowerdirs(opts []string) (int, []string) {
   233  	var (
   234  		idx    = -1
   235  		prefix = "lowerdir="
   236  	)
   237  
   238  	for i, opt := range opts {
   239  		if strings.HasPrefix(opt, prefix) {
   240  			idx = i
   241  			break
   242  		}
   243  	}
   244  
   245  	if idx == -1 {
   246  		return -1, nil
   247  	}
   248  	return idx, strings.Split(opts[idx][len(prefix):], ":")
   249  }
   250  
   251  // longestCommonPrefix finds the longest common prefix in the string slice.
   252  func longestCommonPrefix(strs []string) string {
   253  	if len(strs) == 0 {
   254  		return ""
   255  	} else if len(strs) == 1 {
   256  		return strs[0]
   257  	}
   258  
   259  	// find out the min/max value by alphabetical order
   260  	min, max := strs[0], strs[0]
   261  	for _, str := range strs[1:] {
   262  		if min > str {
   263  			min = str
   264  		}
   265  		if max < str {
   266  			max = str
   267  		}
   268  	}
   269  
   270  	// find out the common part between min and max
   271  	for i := 0; i < len(min) && i < len(max); i++ {
   272  		if min[i] != max[i] {
   273  			return min[:i]
   274  		}
   275  	}
   276  	return min
   277  }
   278  
   279  // copyOptions copies the options.
   280  func copyOptions(opts []string) []string {
   281  	if len(opts) == 0 {
   282  		return nil
   283  	}
   284  
   285  	acopy := make([]string, len(opts))
   286  	copy(acopy, opts)
   287  	return acopy
   288  }
   289  
   290  // optionsSize returns the byte size of options of mount.
   291  func optionsSize(opts []string) int {
   292  	size := 0
   293  	for _, opt := range opts {
   294  		size += len(opt)
   295  	}
   296  	return size
   297  }
   298  
   299  func mountAt(chdir string, source, target, fstype string, flags uintptr, data string) error {
   300  	if chdir == "" {
   301  		return unix.Mount(source, target, fstype, flags, data)
   302  	}
   303  
   304  	f, err := os.Open(chdir)
   305  	if err != nil {
   306  		return errors.Wrap(err, "failed to mountat")
   307  	}
   308  	defer f.Close()
   309  
   310  	fs, err := f.Stat()
   311  	if err != nil {
   312  		return errors.Wrap(err, "failed to mountat")
   313  	}
   314  
   315  	if !fs.IsDir() {
   316  		return errors.Wrap(errors.Errorf("%s is not dir", chdir), "failed to mountat")
   317  	}
   318  	return errors.Wrap(sys.FMountat(f.Fd(), source, target, fstype, flags, data), "failed to mountat")
   319  }