github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/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/Prakhar-Agarwal-byte/moby/volume/local" 7 8 import ( 9 "fmt" 10 "net" 11 "os" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/Prakhar-Agarwal-byte/moby/errdefs" 17 "github.com/Prakhar-Agarwal-byte/moby/quota" 18 units "github.com/docker/go-units" 19 "github.com/moby/sys/mount" 20 "github.com/moby/sys/mountinfo" 21 "github.com/pkg/errors" 22 ) 23 24 var ( 25 validOpts = map[string]struct{}{ 26 "type": {}, // specify the filesystem type for mount, e.g. nfs 27 "o": {}, // generic mount options 28 "device": {}, // device to mount from 29 "size": {}, // quota size limit 30 } 31 mandatoryOpts = map[string][]string{ 32 "device": {"type"}, 33 "type": {"device"}, 34 "o": {"device", "type"}, 35 } 36 ) 37 38 type optsConfig struct { 39 MountType string 40 MountOpts string 41 MountDevice string 42 Quota quota.Quota 43 } 44 45 func (o *optsConfig) String() string { 46 return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size) 47 } 48 49 func (r *Root) validateOpts(opts map[string]string) error { 50 if len(opts) == 0 { 51 return nil 52 } 53 for opt := range opts { 54 if _, ok := validOpts[opt]; !ok { 55 return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt)) 56 } 57 } 58 if val, ok := opts["size"]; ok { 59 size, err := units.RAMInBytes(val) 60 if err != nil { 61 return errdefs.InvalidParameter(err) 62 } 63 if size > 0 && r.quotaCtl == nil { 64 return errdefs.InvalidParameter(errors.New("quota size requested but no quota support")) 65 } 66 } 67 for opt, reqopts := range mandatoryOpts { 68 if _, ok := opts[opt]; ok { 69 for _, reqopt := range reqopts { 70 if _, ok := opts[reqopt]; !ok { 71 return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt)) 72 } 73 } 74 } 75 } 76 return nil 77 } 78 79 func (v *localVolume) setOpts(opts map[string]string) error { 80 if len(opts) == 0 { 81 return nil 82 } 83 v.opts = &optsConfig{ 84 MountType: opts["type"], 85 MountOpts: opts["o"], 86 MountDevice: opts["device"], 87 } 88 if val, ok := opts["size"]; ok { 89 size, err := units.RAMInBytes(val) 90 if err != nil { 91 return errdefs.InvalidParameter(err) 92 } 93 if size > 0 && v.quotaCtl == nil { 94 return errdefs.InvalidParameter(errors.New("quota size requested but no quota support")) 95 } 96 v.opts.Quota.Size = uint64(size) 97 } 98 return v.saveOpts() 99 } 100 101 func (v *localVolume) needsMount() bool { 102 if v.opts == nil { 103 return false 104 } 105 if v.opts.MountDevice != "" || v.opts.MountType != "" { 106 return true 107 } 108 return false 109 } 110 111 func (v *localVolume) mount() error { 112 if v.opts.MountDevice == "" { 113 return fmt.Errorf("missing device in volume options") 114 } 115 mountOpts := v.opts.MountOpts 116 switch v.opts.MountType { 117 case "nfs", "cifs": 118 if addrValue := getAddress(v.opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil { 119 ipAddr, err := net.ResolveIPAddr("ip", addrValue) 120 if err != nil { 121 return errors.Wrapf(err, "error resolving passed in network volume address") 122 } 123 mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1) 124 } 125 } 126 if err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts); err != nil { 127 if password := getPassword(v.opts.MountOpts); password != "" { 128 err = errors.New(strings.Replace(err.Error(), "password="+password, "password=********", 1)) 129 } 130 return errors.Wrap(err, "failed to mount local volume") 131 } 132 return nil 133 } 134 135 func (v *localVolume) postMount() error { 136 if v.opts == nil { 137 return nil 138 } 139 if v.opts.Quota.Size > 0 { 140 if v.quotaCtl != nil { 141 return v.quotaCtl.SetQuota(v.path, v.opts.Quota) 142 } else { 143 return errors.New("size quota requested for volume but no quota support") 144 } 145 } 146 return nil 147 } 148 149 func (v *localVolume) unmount() error { 150 if v.needsMount() { 151 if err := mount.Unmount(v.path); err != nil { 152 if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil { 153 return errdefs.System(err) 154 } 155 } 156 v.active.mounted = false 157 } 158 return nil 159 } 160 161 // restoreIfMounted restores the mounted status if the _data directory is already mounted. 162 func (v *localVolume) restoreIfMounted() error { 163 if v.needsMount() { 164 // Check if the _data directory is already mounted. 165 mounted, err := mountinfo.Mounted(v.path) 166 if err != nil { 167 return fmt.Errorf("failed to determine if volume _data path is already mounted: %w", err) 168 } 169 170 if mounted { 171 // Mark volume as mounted, but don't increment active count. If 172 // any container needs this, the refcount will be incremented 173 // by the live-restore (if enabled). 174 // In other case, refcount will be zero but the volume will 175 // already be considered as mounted when Mount is called, and 176 // only the refcount will be incremented. 177 v.active.mounted = true 178 } 179 } 180 181 return nil 182 } 183 184 func (v *localVolume) CreatedAt() (time.Time, error) { 185 fileInfo, err := os.Stat(v.rootPath) 186 if err != nil { 187 return time.Time{}, err 188 } 189 sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix() 190 return time.Unix(sec, nsec), nil 191 }