github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/volume/local/local_unix.go (about) 1 //go:build linux || freebsd 2 // +build linux freebsd 3 4 // Package local provides the default implementation for volumes. It 5 // is used to mount data volume containers and directories local to 6 // the host server. 7 package local // import "github.com/docker/docker/volume/local" 8 9 import ( 10 "fmt" 11 "net" 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 val, ok := opts["size"]; ok { 60 size, err := units.RAMInBytes(val) 61 if err != nil { 62 return errdefs.InvalidParameter(err) 63 } 64 if size > 0 && r.quotaCtl == nil { 65 return errdefs.InvalidParameter(errors.New("quota size requested but no quota support")) 66 } 67 } 68 for opt, reqopts := range mandatoryOpts { 69 if _, ok := opts[opt]; ok { 70 for _, reqopt := range reqopts { 71 if _, ok := opts[reqopt]; !ok { 72 return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt)) 73 } 74 } 75 } 76 } 77 return nil 78 } 79 80 func (v *localVolume) setOpts(opts map[string]string) error { 81 if len(opts) == 0 { 82 return nil 83 } 84 v.opts = &optsConfig{ 85 MountType: opts["type"], 86 MountOpts: opts["o"], 87 MountDevice: opts["device"], 88 } 89 if val, ok := opts["size"]; ok { 90 size, err := units.RAMInBytes(val) 91 if err != nil { 92 return errdefs.InvalidParameter(err) 93 } 94 if size > 0 && v.quotaCtl == nil { 95 return errdefs.InvalidParameter(errors.New("quota size requested but no quota support")) 96 } 97 v.opts.Quota.Size = uint64(size) 98 } 99 return v.saveOpts() 100 } 101 102 func unmount(path string) { 103 _ = mount.Unmount(path) 104 } 105 106 func (v *localVolume) needsMount() bool { 107 if v.opts == nil { 108 return false 109 } 110 if v.opts.MountDevice != "" || v.opts.MountType != "" { 111 return true 112 } 113 return false 114 } 115 116 func (v *localVolume) mount() error { 117 if v.opts.MountDevice == "" { 118 return fmt.Errorf("missing device in volume options") 119 } 120 mountOpts := v.opts.MountOpts 121 switch v.opts.MountType { 122 case "nfs", "cifs": 123 if addrValue := getAddress(v.opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil { 124 ipAddr, err := net.ResolveIPAddr("ip", addrValue) 125 if err != nil { 126 return errors.Wrapf(err, "error resolving passed in network volume address") 127 } 128 mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1) 129 } 130 } 131 if err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts); err != nil { 132 if password := getPassword(v.opts.MountOpts); password != "" { 133 err = errors.New(strings.Replace(err.Error(), "password="+password, "password=********", 1)) 134 } 135 return errors.Wrap(err, "failed to mount local volume") 136 } 137 return nil 138 } 139 140 func (v *localVolume) postMount() error { 141 if v.opts == nil { 142 return nil 143 } 144 if v.opts.Quota.Size > 0 { 145 if v.quotaCtl != nil { 146 return v.quotaCtl.SetQuota(v.path, v.opts.Quota) 147 } else { 148 return errors.New("size quota requested for volume but no quota support") 149 } 150 } 151 return nil 152 } 153 154 func (v *localVolume) unmount() error { 155 if v.needsMount() { 156 if err := mount.Unmount(v.path); err != nil { 157 if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil { 158 return errdefs.System(err) 159 } 160 } 161 v.active.mounted = false 162 } 163 return nil 164 } 165 166 func (v *localVolume) CreatedAt() (time.Time, error) { 167 fileInfo, err := os.Stat(v.path) 168 if err != nil { 169 return time.Time{}, err 170 } 171 sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix() 172 return time.Unix(sec, nsec), nil 173 }