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 }