github.com/rish1988/moby@v25.0.2+incompatible/volume/local/local_unix.go (about) 1 //go: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 "net/url" 12 "os" 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 validOpts = map[string]struct{}{ 27 "type": {}, // specify the filesystem type for mount, e.g. nfs 28 "o": {}, // generic mount options 29 "device": {}, // device to mount from 30 "size": {}, // quota size limit 31 } 32 mandatoryOpts = map[string][]string{ 33 "device": {"type"}, 34 "type": {"device"}, 35 "o": {"device", "type"}, 36 } 37 ) 38 39 type optsConfig struct { 40 MountType string 41 MountOpts string 42 MountDevice string 43 Quota quota.Quota 44 } 45 46 func (o *optsConfig) String() string { 47 return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size) 48 } 49 50 func (r *Root) validateOpts(opts map[string]string) error { 51 if len(opts) == 0 { 52 return nil 53 } 54 for opt := range opts { 55 if _, ok := validOpts[opt]; !ok { 56 return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt)) 57 } 58 } 59 if typeOpt, deviceOpt := opts["type"], opts["device"]; typeOpt == "cifs" && deviceOpt != "" { 60 deviceURL, err := url.Parse(deviceOpt) 61 if err != nil { 62 return errdefs.InvalidParameter(errors.Wrapf(err, "error parsing mount device url")) 63 } 64 if deviceURL.Port() != "" { 65 return errdefs.InvalidParameter(errors.New("port not allowed in CIFS device URL, include 'port' in 'o='")) 66 } 67 } 68 if val, ok := opts["size"]; ok { 69 size, err := units.RAMInBytes(val) 70 if err != nil { 71 return errdefs.InvalidParameter(err) 72 } 73 if size > 0 && r.quotaCtl == nil { 74 return errdefs.InvalidParameter(errors.New("quota size requested but no quota support")) 75 } 76 } 77 for opt, reqopts := range mandatoryOpts { 78 if _, ok := opts[opt]; ok { 79 for _, reqopt := range reqopts { 80 if _, ok := opts[reqopt]; !ok { 81 return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt)) 82 } 83 } 84 } 85 } 86 return nil 87 } 88 89 func (v *localVolume) setOpts(opts map[string]string) error { 90 if len(opts) == 0 { 91 return nil 92 } 93 v.opts = &optsConfig{ 94 MountType: opts["type"], 95 MountOpts: opts["o"], 96 MountDevice: opts["device"], 97 } 98 if val, ok := opts["size"]; ok { 99 size, err := units.RAMInBytes(val) 100 if err != nil { 101 return errdefs.InvalidParameter(err) 102 } 103 if size > 0 && v.quotaCtl == nil { 104 return errdefs.InvalidParameter(errors.New("quota size requested but no quota support")) 105 } 106 v.opts.Quota.Size = uint64(size) 107 } 108 return v.saveOpts() 109 } 110 111 func (v *localVolume) needsMount() bool { 112 if v.opts == nil { 113 return false 114 } 115 if v.opts.MountDevice != "" || v.opts.MountType != "" { 116 return true 117 } 118 return false 119 } 120 121 func getMountOptions(opts *optsConfig, resolveIP func(string, string) (*net.IPAddr, error)) (mountDevice string, mountOpts string, _ error) { 122 if opts.MountDevice == "" { 123 return "", "", fmt.Errorf("missing device in volume options") 124 } 125 126 mountOpts = opts.MountOpts 127 mountDevice = opts.MountDevice 128 129 switch opts.MountType { 130 case "nfs", "cifs": 131 if addrValue := getAddress(opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil { 132 ipAddr, err := resolveIP("ip", addrValue) 133 if err != nil { 134 return "", "", errors.Wrap(err, "error resolving passed in network volume address") 135 } 136 mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1) 137 break 138 } 139 140 if opts.MountType != "cifs" { 141 break 142 } 143 144 deviceURL, err := url.Parse(mountDevice) 145 if err != nil { 146 return "", "", errors.Wrap(err, "error parsing mount device url") 147 } 148 if deviceURL.Host != "" && net.ParseIP(deviceURL.Host) == nil { 149 ipAddr, err := resolveIP("ip", deviceURL.Host) 150 if err != nil { 151 return "", "", errors.Wrap(err, "error resolving passed in network volume address") 152 } 153 deviceURL.Host = ipAddr.String() 154 dev, err := url.QueryUnescape(deviceURL.String()) 155 if err != nil { 156 return "", "", fmt.Errorf("failed to unescape device URL: %q", deviceURL) 157 } 158 mountDevice = dev 159 } 160 } 161 162 return mountDevice, mountOpts, nil 163 } 164 165 func (v *localVolume) mount() error { 166 mountDevice, mountOpts, err := getMountOptions(v.opts, net.ResolveIPAddr) 167 if err != nil { 168 return err 169 } 170 171 if err := mount.Mount(mountDevice, v.path, v.opts.MountType, mountOpts); err != nil { 172 if password := getPassword(v.opts.MountOpts); password != "" { 173 err = errors.New(strings.Replace(err.Error(), "password="+password, "password=********", 1)) 174 } 175 return errors.Wrap(err, "failed to mount local volume") 176 } 177 return nil 178 } 179 180 func (v *localVolume) postMount() error { 181 if v.opts == nil { 182 return nil 183 } 184 if v.opts.Quota.Size > 0 { 185 if v.quotaCtl != nil { 186 return v.quotaCtl.SetQuota(v.path, v.opts.Quota) 187 } else { 188 return errors.New("size quota requested for volume but no quota support") 189 } 190 } 191 return nil 192 } 193 194 func (v *localVolume) unmount() error { 195 if v.needsMount() { 196 if err := mount.Unmount(v.path); err != nil { 197 if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil { 198 return errdefs.System(err) 199 } 200 } 201 v.active.mounted = false 202 } 203 return nil 204 } 205 206 // restoreIfMounted restores the mounted status if the _data directory is already mounted. 207 func (v *localVolume) restoreIfMounted() error { 208 if v.needsMount() { 209 // Check if the _data directory is already mounted. 210 mounted, err := mountinfo.Mounted(v.path) 211 if err != nil { 212 return fmt.Errorf("failed to determine if volume _data path is already mounted: %w", err) 213 } 214 215 if mounted { 216 // Mark volume as mounted, but don't increment active count. If 217 // any container needs this, the refcount will be incremented 218 // by the live-restore (if enabled). 219 // In other case, refcount will be zero but the volume will 220 // already be considered as mounted when Mount is called, and 221 // only the refcount will be incremented. 222 v.active.mounted = true 223 } 224 } 225 226 return nil 227 } 228 229 func (v *localVolume) CreatedAt() (time.Time, error) { 230 fileInfo, err := os.Stat(v.rootPath) 231 if err != nil { 232 return time.Time{}, err 233 } 234 sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix() 235 return time.Unix(sec, nsec), nil 236 }