aboutsummaryrefslogtreecommitdiff
path: root/libpod/volume_internal_linux.go
blob: 4c033201840d94b3a9c8a46ec5649b6e4481abd9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// +build linux

package libpod

import (
	"io/ioutil"
	"os/exec"

	"github.com/containers/libpod/libpod/define"
	"github.com/containers/libpod/pkg/rootless"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

// mount mounts the volume if necessary.
// A mount is necessary if a volume has any options set.
// If a mount is necessary, v.state.MountCount will be incremented.
// If it was 0 when the increment occurred, the volume will be mounted on the
// host. Otherwise, we assume it is already mounted.
// Must be done while the volume is locked.
// Is a no-op on volumes that do not require a mount (as defined by
// volumeNeedsMount())
func (v *Volume) mount() error {
	if !v.needsMount() {
		return nil
	}

	// We cannot mount volumes as rootless.
	if rootless.IsRootless() {
		return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges")
	}

	// Update the volume from the DB to get an accurate mount counter.
	if err := v.update(); err != nil {
		return err
	}

	// If the count is non-zero, the volume is already mounted.
	// Nothing to do.
	if v.state.MountCount > 0 {
		v.state.MountCount = v.state.MountCount + 1
		logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount)
		return v.save()
	}

	volDevice := v.config.Options["device"]
	volType := v.config.Options["type"]
	volOptions := v.config.Options["o"]

	// Some filesystems (tmpfs) don't have a device, but we still need to
	// give the kernel something.
	if volDevice == "" && volType != "" {
		volDevice = volType
	}

	// We need to use the actual mount command.
	// Convincing unix.Mount to use the same semantics as the mount command
	// itself seems prohibitively difficult.
	// TODO: might want to cache this path in the runtime?
	mountPath, err := exec.LookPath("mount")
	if err != nil {
		return errors.Wrapf(err, "error locating 'mount' binary")
	}
	mountArgs := []string{}
	if volOptions != "" {
		mountArgs = append(mountArgs, "-o", volOptions)
	}
	if volType != "" {
		mountArgs = append(mountArgs, "-t", volType)
	}
	mountArgs = append(mountArgs, volDevice, v.config.MountPoint)
	mountCmd := exec.Command(mountPath, mountArgs...)

	errPipe, err := mountCmd.StderrPipe()
	if err != nil {
		return errors.Wrapf(err, "error getting stderr pipe for mount")
	}
	if err := mountCmd.Start(); err != nil {
		out, err2 := ioutil.ReadAll(errPipe)
		if err2 != nil {
			return errors.Wrapf(err2, "error reading mount STDERR")
		}
		return errors.Wrapf(errors.New(string(out)), "error mounting volume %s", v.Name())
	}

	logrus.Debugf("Mounted volume %s", v.Name())

	// Increment the mount counter
	v.state.MountCount = v.state.MountCount + 1
	logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount)
	return v.save()
}

// unmount unmounts the volume if necessary.
// Unmounting a volume that is not mounted is a no-op.
// Unmounting a volume that does not require a mount is a no-op.
// The volume must be locked for this to occur.
// The mount counter will be decremented if non-zero. If the counter reaches 0,
// the volume will really be unmounted, as no further containers are using the
// volume.
// If force is set, the volume will be unmounted regardless of mount counter.
func (v *Volume) unmount(force bool) error {
	if !v.needsMount() {
		return nil
	}

	// Update the volume from the DB to get an accurate mount counter.
	if err := v.update(); err != nil {
		return err
	}

	if v.state.MountCount == 0 {
		logrus.Debugf("Volume %s already unmounted", v.Name())
		return nil
	}

	// We cannot unmount volumes as rootless.
	if rootless.IsRootless() {
		// If force is set, just clear the counter and bail without
		// error, so we can remove volumes from the state if they are in
		// an awkward configuration.
		if force {
			logrus.Errorf("Volume %s is mounted despite being rootless - state is not sane", v.Name())
			v.state.MountCount = 0
			return v.save()
		}

		return errors.Wrapf(define.ErrRootless, "cannot mount or unmount volumes without root privileges")
	}

	if !force {
		v.state.MountCount = v.state.MountCount - 1
	} else {
		v.state.MountCount = 0
	}

	logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount)

	if v.state.MountCount == 0 {
		// Unmount the volume
		if err := unix.Unmount(v.config.MountPoint, unix.MNT_DETACH); err != nil {
			if err == unix.EINVAL {
				// Ignore EINVAL - the mount no longer exists.
				return nil
			}
			return errors.Wrapf(err, "error unmounting volume %s", v.Name())
		}
		logrus.Debugf("Unmounted volume %s", v.Name())
	}

	return v.save()
}