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  }