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  }