github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/volume/local/local_unix.go (about) 1 // +build linux freebsd 2 3 // Package local provides the default implementation for volumes. It 4 // is used to mount data volume containers and directories local to 5 // the host server. 6 package local // import "github.com/docker/docker/volume/local" 7 8 import ( 9 "fmt" 10 "net" 11 "os" 12 "path/filepath" 13 "strings" 14 "syscall" 15 "time" 16 17 "github.com/docker/docker/errdefs" 18 "github.com/docker/docker/quota" 19 units "github.com/docker/go-units" 20 "github.com/moby/sys/mount" 21 "github.com/moby/sys/mountinfo" 22 "github.com/pkg/errors" 23 ) 24 25 var ( 26 oldVfsDir = filepath.Join("vfs", "dir") 27 28 validOpts = map[string]struct{}{ 29 "type": {}, // specify the filesystem type for mount, e.g. nfs 30 "o": {}, // generic mount options 31 "device": {}, // device to mount from 32 "size": {}, // quota size limit 33 } 34 mandatoryOpts = map[string][]string{ 35 "device": []string{"type"}, 36 "type": []string{"device"}, 37 "o": []string{"device", "type"}, 38 } 39 ) 40 41 type optsConfig struct { 42 MountType string 43 MountOpts string 44 MountDevice string 45 Quota quota.Quota 46 } 47 48 func (o *optsConfig) String() string { 49 return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size) 50 } 51 52 // scopedPath verifies that the path where the volume is located 53 // is under Docker's root and the valid local paths. 54 func (r *Root) scopedPath(realPath string) bool { 55 // Volumes path for Docker version >= 1.7 56 if strings.HasPrefix(realPath, filepath.Join(r.scope, volumesPathName)) && realPath != filepath.Join(r.scope, volumesPathName) { 57 return true 58 } 59 60 // Volumes path for Docker version < 1.7 61 if strings.HasPrefix(realPath, filepath.Join(r.scope, oldVfsDir)) { 62 return true 63 } 64 65 return false 66 } 67 68 func setOpts(v *localVolume, opts map[string]string) error { 69 if len(opts) == 0 { 70 return nil 71 } 72 err := validateOpts(opts) 73 if err != nil { 74 return err 75 } 76 v.opts = &optsConfig{ 77 MountType: opts["type"], 78 MountOpts: opts["o"], 79 MountDevice: opts["device"], 80 } 81 if val, ok := opts["size"]; ok { 82 size, err := units.RAMInBytes(val) 83 if err != nil { 84 return err 85 } 86 if size > 0 && v.quotaCtl == nil { 87 return errdefs.InvalidParameter(errors.Errorf("quota size requested but no quota support")) 88 } 89 v.opts.Quota.Size = uint64(size) 90 } 91 return nil 92 } 93 94 func validateOpts(opts map[string]string) error { 95 if len(opts) == 0 { 96 return nil 97 } 98 for opt := range opts { 99 if _, ok := validOpts[opt]; !ok { 100 return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt)) 101 } 102 } 103 for opt, reqopts := range mandatoryOpts { 104 if _, ok := opts[opt]; ok { 105 for _, reqopt := range reqopts { 106 if _, ok := opts[reqopt]; !ok { 107 return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt)) 108 } 109 } 110 } 111 } 112 return nil 113 } 114 115 func unmount(path string) { 116 _ = mount.Unmount(path) 117 } 118 119 func (v *localVolume) needsMount() bool { 120 if v.opts == nil { 121 return false 122 } 123 if v.opts.MountDevice != "" || v.opts.MountType != "" { 124 return true 125 } 126 return false 127 } 128 129 func (v *localVolume) mount() error { 130 if v.opts.MountDevice == "" { 131 return fmt.Errorf("missing device in volume options") 132 } 133 mountOpts := v.opts.MountOpts 134 switch v.opts.MountType { 135 case "nfs", "cifs": 136 if addrValue := getAddress(v.opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil { 137 ipAddr, err := net.ResolveIPAddr("ip", addrValue) 138 if err != nil { 139 return errors.Wrapf(err, "error resolving passed in network volume address") 140 } 141 mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1) 142 } 143 } 144 err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts) 145 return errors.Wrap(err, "failed to mount local volume") 146 } 147 148 func (v *localVolume) postMount() error { 149 if v.opts == nil { 150 return nil 151 } 152 if v.opts.Quota.Size > 0 { 153 if v.quotaCtl != nil { 154 err := v.quotaCtl.SetQuota(v.path, v.opts.Quota) 155 if err != nil { 156 return err 157 } 158 } else { 159 return fmt.Errorf("size quota requested for volume but no quota support") 160 } 161 } 162 return nil 163 } 164 165 func (v *localVolume) unmount() error { 166 if v.needsMount() { 167 if err := mount.Unmount(v.path); err != nil { 168 if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil { 169 return errdefs.System(err) 170 } 171 } 172 v.active.mounted = false 173 } 174 return nil 175 } 176 177 func (v *localVolume) CreatedAt() (time.Time, error) { 178 fileInfo, err := os.Stat(v.path) 179 if err != nil { 180 return time.Time{}, err 181 } 182 sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix() 183 return time.Unix(sec, nsec), nil 184 }