2021-08-23 13:14:53 +00:00
|
|
|
//go:build linux || freebsd
|
2015-09-16 21:18:24 +00:00
|
|
|
|
|
|
|
// Package local provides the default implementation for volumes. It
|
|
|
|
// is used to mount data volume containers and directories local to
|
|
|
|
// the host server.
|
2018-02-05 21:05:59 +00:00
|
|
|
package local // import "github.com/docker/docker/volume/local"
|
2015-09-16 21:18:24 +00:00
|
|
|
|
|
|
|
import (
|
2016-02-12 02:48:16 +00:00
|
|
|
"fmt"
|
2016-10-12 20:11:20 +00:00
|
|
|
"net"
|
2023-11-29 08:18:39 +00:00
|
|
|
"net/url"
|
2017-05-17 21:19:13 +00:00
|
|
|
"os"
|
2015-09-16 21:18:24 +00:00
|
|
|
"strings"
|
2017-05-17 21:19:13 +00:00
|
|
|
"syscall"
|
|
|
|
"time"
|
2016-02-12 02:48:16 +00:00
|
|
|
|
2018-12-22 02:02:28 +00:00
|
|
|
"github.com/docker/docker/errdefs"
|
2020-08-09 19:53:09 +00:00
|
|
|
"github.com/docker/docker/quota"
|
|
|
|
units "github.com/docker/go-units"
|
2020-03-13 23:38:24 +00:00
|
|
|
"github.com/moby/sys/mount"
|
2020-09-19 16:45:41 +00:00
|
|
|
"github.com/moby/sys/mountinfo"
|
2018-12-22 01:28:30 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-02-12 02:48:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-12-22 01:23:17 +00:00
|
|
|
validOpts = map[string]struct{}{
|
|
|
|
"type": {}, // specify the filesystem type for mount, e.g. nfs
|
|
|
|
"o": {}, // generic mount options
|
|
|
|
"device": {}, // device to mount from
|
2020-08-09 19:53:09 +00:00
|
|
|
"size": {}, // quota size limit
|
2016-02-12 02:48:16 +00:00
|
|
|
}
|
2020-08-09 19:53:09 +00:00
|
|
|
mandatoryOpts = map[string][]string{
|
2021-01-14 13:54:37 +00:00
|
|
|
"device": {"type"},
|
|
|
|
"type": {"device"},
|
|
|
|
"o": {"device", "type"},
|
2018-04-09 08:09:30 +00:00
|
|
|
}
|
2015-09-16 21:18:24 +00:00
|
|
|
)
|
|
|
|
|
2016-02-12 02:48:16 +00:00
|
|
|
type optsConfig struct {
|
|
|
|
MountType string
|
|
|
|
MountOpts string
|
|
|
|
MountDevice string
|
2020-08-09 19:53:09 +00:00
|
|
|
Quota quota.Quota
|
2016-02-12 02:48:16 +00:00
|
|
|
}
|
2015-09-16 21:18:24 +00:00
|
|
|
|
2016-09-23 14:38:19 +00:00
|
|
|
func (o *optsConfig) String() string {
|
2020-08-09 19:53:09 +00:00
|
|
|
return fmt.Sprintf("type='%s' device='%s' o='%s' size='%d'", o.MountType, o.MountDevice, o.MountOpts, o.Quota.Size)
|
2016-09-23 14:38:19 +00:00
|
|
|
}
|
|
|
|
|
2022-05-17 13:54:17 +00:00
|
|
|
func (r *Root) validateOpts(opts map[string]string) error {
|
2016-02-12 02:48:16 +00:00
|
|
|
if len(opts) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2022-05-17 13:54:17 +00:00
|
|
|
for opt := range opts {
|
|
|
|
if _, ok := validOpts[opt]; !ok {
|
|
|
|
return errdefs.InvalidParameter(errors.Errorf("invalid option: %q", opt))
|
|
|
|
}
|
2016-02-12 02:48:16 +00:00
|
|
|
}
|
2023-12-01 15:37:10 +00:00
|
|
|
if typeOpt, deviceOpt := opts["type"], opts["device"]; typeOpt == "cifs" && deviceOpt != "" {
|
|
|
|
deviceURL, err := url.Parse(deviceOpt)
|
|
|
|
if err != nil {
|
|
|
|
return errdefs.InvalidParameter(errors.Wrapf(err, "error parsing mount device url"))
|
|
|
|
}
|
|
|
|
if deviceURL.Port() != "" {
|
|
|
|
return errdefs.InvalidParameter(errors.New("port not allowed in CIFS device URL, include 'port' in 'o='"))
|
|
|
|
}
|
|
|
|
}
|
2020-08-09 19:53:09 +00:00
|
|
|
if val, ok := opts["size"]; ok {
|
|
|
|
size, err := units.RAMInBytes(val)
|
|
|
|
if err != nil {
|
2022-05-14 11:16:06 +00:00
|
|
|
return errdefs.InvalidParameter(err)
|
2020-08-09 19:53:09 +00:00
|
|
|
}
|
2022-05-17 13:54:17 +00:00
|
|
|
if size > 0 && r.quotaCtl == nil {
|
2022-05-14 11:16:06 +00:00
|
|
|
return errdefs.InvalidParameter(errors.New("quota size requested but no quota support"))
|
2020-08-09 19:53:09 +00:00
|
|
|
}
|
2022-05-17 13:54:17 +00:00
|
|
|
}
|
|
|
|
for opt, reqopts := range mandatoryOpts {
|
|
|
|
if _, ok := opts[opt]; ok {
|
|
|
|
for _, reqopt := range reqopts {
|
|
|
|
if _, ok := opts[reqopt]; !ok {
|
|
|
|
return errdefs.InvalidParameter(errors.Errorf("missing required option: %q", reqopt))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-09 19:53:09 +00:00
|
|
|
}
|
2016-02-12 02:48:16 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-17 13:54:17 +00:00
|
|
|
func (v *localVolume) setOpts(opts map[string]string) error {
|
2018-12-22 02:02:28 +00:00
|
|
|
if len(opts) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2022-05-17 13:54:17 +00:00
|
|
|
v.opts = &optsConfig{
|
|
|
|
MountType: opts["type"],
|
|
|
|
MountOpts: opts["o"],
|
|
|
|
MountDevice: opts["device"],
|
2018-12-22 02:02:28 +00:00
|
|
|
}
|
2022-05-14 11:16:06 +00:00
|
|
|
if val, ok := opts["size"]; ok {
|
2022-05-17 13:54:17 +00:00
|
|
|
size, err := units.RAMInBytes(val)
|
2022-05-14 11:16:06 +00:00
|
|
|
if err != nil {
|
|
|
|
return errdefs.InvalidParameter(err)
|
|
|
|
}
|
2022-05-17 13:54:17 +00:00
|
|
|
if size > 0 && v.quotaCtl == nil {
|
|
|
|
return errdefs.InvalidParameter(errors.New("quota size requested but no quota support"))
|
2018-12-22 02:02:28 +00:00
|
|
|
}
|
2022-05-17 13:54:17 +00:00
|
|
|
v.opts.Quota.Size = uint64(size)
|
2018-12-22 02:02:28 +00:00
|
|
|
}
|
2020-10-07 22:25:49 +00:00
|
|
|
return v.saveOpts()
|
2018-12-22 02:02:28 +00:00
|
|
|
}
|
|
|
|
|
2020-08-09 19:53:09 +00:00
|
|
|
func (v *localVolume) needsMount() bool {
|
|
|
|
if v.opts == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if v.opts.MountDevice != "" || v.opts.MountType != "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-01-23 16:16:44 +00:00
|
|
|
func getMountOptions(opts *optsConfig, resolveIP func(string, string) (*net.IPAddr, error)) (mountDevice string, mountOpts string, _ error) {
|
|
|
|
if opts.MountDevice == "" {
|
|
|
|
return "", "", fmt.Errorf("missing device in volume options")
|
2016-02-12 02:48:16 +00:00
|
|
|
}
|
2023-11-29 08:18:39 +00:00
|
|
|
|
2024-01-23 16:16:44 +00:00
|
|
|
mountOpts = opts.MountOpts
|
|
|
|
mountDevice = opts.MountDevice
|
2023-11-29 08:18:39 +00:00
|
|
|
|
2024-01-23 16:16:44 +00:00
|
|
|
switch opts.MountType {
|
2024-01-23 11:32:09 +00:00
|
|
|
case "nfs", "cifs":
|
2024-01-23 16:16:44 +00:00
|
|
|
if addrValue := getAddress(opts.MountOpts); addrValue != "" && net.ParseIP(addrValue).To4() == nil {
|
|
|
|
ipAddr, err := resolveIP("ip", addrValue)
|
2016-10-12 20:11:20 +00:00
|
|
|
if err != nil {
|
2024-01-23 16:16:44 +00:00
|
|
|
return "", "", errors.Wrap(err, "error resolving passed in network volume address")
|
2016-10-12 20:11:20 +00:00
|
|
|
}
|
|
|
|
mountOpts = strings.Replace(mountOpts, "addr="+addrValue, "addr="+ipAddr.String(), 1)
|
2024-01-23 16:17:53 +00:00
|
|
|
break
|
2016-10-12 20:11:20 +00:00
|
|
|
}
|
2024-01-23 11:32:09 +00:00
|
|
|
|
2024-01-23 16:16:44 +00:00
|
|
|
if opts.MountType != "cifs" {
|
2024-01-23 11:32:09 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
deviceURL, err := url.Parse(mountDevice)
|
2023-11-29 08:18:39 +00:00
|
|
|
if err != nil {
|
2024-01-23 16:16:44 +00:00
|
|
|
return "", "", errors.Wrap(err, "error parsing mount device url")
|
2023-11-29 08:18:39 +00:00
|
|
|
}
|
2023-12-01 15:37:10 +00:00
|
|
|
if deviceURL.Host != "" && net.ParseIP(deviceURL.Host) == nil {
|
2024-01-23 16:16:44 +00:00
|
|
|
ipAddr, err := resolveIP("ip", deviceURL.Host)
|
2023-11-29 08:18:39 +00:00
|
|
|
if err != nil {
|
2024-01-23 16:16:44 +00:00
|
|
|
return "", "", errors.Wrap(err, "error resolving passed in network volume address")
|
2023-11-29 08:18:39 +00:00
|
|
|
}
|
2023-12-01 15:37:10 +00:00
|
|
|
deviceURL.Host = ipAddr.String()
|
2024-01-23 16:34:49 +00:00
|
|
|
dev, err := url.QueryUnescape(deviceURL.String())
|
|
|
|
if err != nil {
|
|
|
|
return "", "", fmt.Errorf("failed to unescape device URL: %q", deviceURL)
|
|
|
|
}
|
|
|
|
mountDevice = dev
|
2023-11-29 08:18:39 +00:00
|
|
|
}
|
2016-10-12 20:11:20 +00:00
|
|
|
}
|
2024-01-23 16:16:44 +00:00
|
|
|
|
|
|
|
return mountDevice, mountOpts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *localVolume) mount() error {
|
|
|
|
mountDevice, mountOpts, err := getMountOptions(v.opts, net.ResolveIPAddr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-11-29 08:18:39 +00:00
|
|
|
if err := mount.Mount(mountDevice, v.path, v.opts.MountType, mountOpts); err != nil {
|
2022-05-12 14:53:02 +00:00
|
|
|
if password := getPassword(v.opts.MountOpts); password != "" {
|
|
|
|
err = errors.New(strings.Replace(err.Error(), "password="+password, "password=********", 1))
|
|
|
|
}
|
2022-05-23 13:48:45 +00:00
|
|
|
return errors.Wrap(err, "failed to mount local volume")
|
2022-05-12 14:53:02 +00:00
|
|
|
}
|
2022-05-23 13:48:45 +00:00
|
|
|
return nil
|
2016-02-12 02:48:16 +00:00
|
|
|
}
|
2017-05-17 21:19:13 +00:00
|
|
|
|
2020-08-09 19:53:09 +00:00
|
|
|
func (v *localVolume) postMount() error {
|
|
|
|
if v.opts == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if v.opts.Quota.Size > 0 {
|
|
|
|
if v.quotaCtl != nil {
|
2022-09-23 20:28:03 +00:00
|
|
|
return v.quotaCtl.SetQuota(v.path, v.opts.Quota)
|
2020-08-09 19:53:09 +00:00
|
|
|
} else {
|
2022-05-14 11:16:06 +00:00
|
|
|
return errors.New("size quota requested for volume but no quota support")
|
2020-08-09 19:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-19 16:45:41 +00:00
|
|
|
func (v *localVolume) unmount() error {
|
|
|
|
if v.needsMount() {
|
|
|
|
if err := mount.Unmount(v.path); err != nil {
|
|
|
|
if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
|
|
|
|
return errdefs.System(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
v.active.mounted = false
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-17 14:44:28 +00:00
|
|
|
// restoreIfMounted restores the mounted status if the _data directory is already mounted.
|
|
|
|
func (v *localVolume) restoreIfMounted() error {
|
|
|
|
if v.needsMount() {
|
|
|
|
// Check if the _data directory is already mounted.
|
|
|
|
mounted, err := mountinfo.Mounted(v.path)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to determine if volume _data path is already mounted: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if mounted {
|
|
|
|
// Mark volume as mounted, but don't increment active count. If
|
|
|
|
// any container needs this, the refcount will be incremented
|
|
|
|
// by the live-restore (if enabled).
|
|
|
|
// In other case, refcount will be zero but the volume will
|
|
|
|
// already be considered as mounted when Mount is called, and
|
|
|
|
// only the refcount will be incremented.
|
|
|
|
v.active.mounted = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-05-17 21:19:13 +00:00
|
|
|
func (v *localVolume) CreatedAt() (time.Time, error) {
|
2018-11-26 15:23:23 +00:00
|
|
|
fileInfo, err := os.Stat(v.rootPath)
|
2017-05-17 21:19:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return time.Time{}, err
|
|
|
|
}
|
|
|
|
sec, nsec := fileInfo.Sys().(*syscall.Stat_t).Ctim.Unix()
|
|
|
|
return time.Unix(sec, nsec), nil
|
|
|
|
}
|