1// Package local provides the default implementation for volumes. It
2// is used to mount data volume containers and directories local to
3// the host server.
4package local
5
6import (
7	"encoding/json"
8	"fmt"
9	"io/ioutil"
10	"os"
11	"path/filepath"
12	"reflect"
13	"strings"
14	"sync"
15
16	"github.com/pkg/errors"
17
18	"github.com/sirupsen/logrus"
19	"github.com/docker/docker/pkg/idtools"
20	"github.com/docker/docker/pkg/mount"
21	"github.com/docker/docker/utils"
22	"github.com/docker/docker/volume"
23)
24
25// VolumeDataPathName is the name of the directory where the volume data is stored.
26// It uses a very distinctive name to avoid collisions migrating data between
27// Docker versions.
28const (
29	VolumeDataPathName = "_data"
30	volumesPathName    = "volumes"
31)
32
33var (
34	// ErrNotFound is the typed error returned when the requested volume name can't be found
35	ErrNotFound = fmt.Errorf("volume not found")
36	// volumeNameRegex ensures the name assigned for the volume is valid.
37	// This name is used to create the bind directory, so we need to avoid characters that
38	// would make the path to escape the root directory.
39	volumeNameRegex = utils.RestrictedNamePattern
40)
41
42type validationError struct {
43	error
44}
45
46func (validationError) IsValidationError() bool {
47	return true
48}
49
50type activeMount struct {
51	count   uint64
52	mounted bool
53}
54
55// New instantiates a new Root instance with the provided scope. Scope
56// is the base path that the Root instance uses to store its
57// volumes. The base path is created here if it does not exist.
58func New(scope string, rootUID, rootGID int) (*Root, error) {
59	rootDirectory := filepath.Join(scope, volumesPathName)
60
61	if err := idtools.MkdirAllAs(rootDirectory, 0700, rootUID, rootGID); err != nil {
62		return nil, err
63	}
64
65	r := &Root{
66		scope:   scope,
67		path:    rootDirectory,
68		volumes: make(map[string]*localVolume),
69		rootUID: rootUID,
70		rootGID: rootGID,
71	}
72
73	dirs, err := ioutil.ReadDir(rootDirectory)
74	if err != nil {
75		return nil, err
76	}
77
78	mountInfos, err := mount.GetMounts()
79	if err != nil {
80		logrus.Debugf("error looking up mounts for local volume cleanup: %v", err)
81	}
82
83	for _, d := range dirs {
84		if !d.IsDir() {
85			continue
86		}
87
88		name := filepath.Base(d.Name())
89		v := &localVolume{
90			driverName: r.Name(),
91			name:       name,
92			path:       r.DataPath(name),
93		}
94		r.volumes[name] = v
95		optsFilePath := filepath.Join(rootDirectory, name, "opts.json")
96		if b, err := ioutil.ReadFile(optsFilePath); err == nil {
97			opts := optsConfig{}
98			if err := json.Unmarshal(b, &opts); err != nil {
99				return nil, errors.Wrapf(err, "error while unmarshaling volume options for volume: %s", name)
100			}
101			// Make sure this isn't an empty optsConfig.
102			// This could be empty due to buggy behavior in older versions of Docker.
103			if !reflect.DeepEqual(opts, optsConfig{}) {
104				v.opts = &opts
105			}
106
107			// unmount anything that may still be mounted (for example, from an unclean shutdown)
108			for _, info := range mountInfos {
109				if info.Mountpoint == v.path {
110					mount.Unmount(v.path)
111					break
112				}
113			}
114		}
115	}
116
117	return r, nil
118}
119
120// Root implements the Driver interface for the volume package and
121// manages the creation/removal of volumes. It uses only standard vfs
122// commands to create/remove dirs within its provided scope.
123type Root struct {
124	m       sync.Mutex
125	scope   string
126	path    string
127	volumes map[string]*localVolume
128	rootUID int
129	rootGID int
130}
131
132// List lists all the volumes
133func (r *Root) List() ([]volume.Volume, error) {
134	var ls []volume.Volume
135	r.m.Lock()
136	for _, v := range r.volumes {
137		ls = append(ls, v)
138	}
139	r.m.Unlock()
140	return ls, nil
141}
142
143// DataPath returns the constructed path of this volume.
144func (r *Root) DataPath(volumeName string) string {
145	return filepath.Join(r.path, volumeName, VolumeDataPathName)
146}
147
148// Name returns the name of Root, defined in the volume package in the DefaultDriverName constant.
149func (r *Root) Name() string {
150	return volume.DefaultDriverName
151}
152
153// Create creates a new volume.Volume with the provided name, creating
154// the underlying directory tree required for this volume in the
155// process.
156func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error) {
157	if err := r.validateName(name); err != nil {
158		return nil, err
159	}
160
161	r.m.Lock()
162	defer r.m.Unlock()
163
164	v, exists := r.volumes[name]
165	if exists {
166		return v, nil
167	}
168
169	path := r.DataPath(name)
170	if err := idtools.MkdirAllAs(path, 0755, r.rootUID, r.rootGID); err != nil {
171		if os.IsExist(err) {
172			return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
173		}
174		return nil, errors.Wrapf(err, "error while creating volume path '%s'", path)
175	}
176
177	var err error
178	defer func() {
179		if err != nil {
180			os.RemoveAll(filepath.Dir(path))
181		}
182	}()
183
184	v = &localVolume{
185		driverName: r.Name(),
186		name:       name,
187		path:       path,
188	}
189
190	if len(opts) != 0 {
191		if err = setOpts(v, opts); err != nil {
192			return nil, err
193		}
194		var b []byte
195		b, err = json.Marshal(v.opts)
196		if err != nil {
197			return nil, err
198		}
199		if err = ioutil.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 600); err != nil {
200			return nil, errors.Wrap(err, "error while persisting volume options")
201		}
202	}
203
204	r.volumes[name] = v
205	return v, nil
206}
207
208// Remove removes the specified volume and all underlying data. If the
209// given volume does not belong to this driver and an error is
210// returned. The volume is reference counted, if all references are
211// not released then the volume is not removed.
212func (r *Root) Remove(v volume.Volume) error {
213	r.m.Lock()
214	defer r.m.Unlock()
215
216	lv, ok := v.(*localVolume)
217	if !ok {
218		return fmt.Errorf("unknown volume type %T", v)
219	}
220
221	realPath, err := filepath.EvalSymlinks(lv.path)
222	if err != nil {
223		if !os.IsNotExist(err) {
224			return err
225		}
226		realPath = filepath.Dir(lv.path)
227	}
228
229	if !r.scopedPath(realPath) {
230		return fmt.Errorf("Unable to remove a directory of out the Docker root %s: %s", r.scope, realPath)
231	}
232
233	if err := removePath(realPath); err != nil {
234		return err
235	}
236
237	delete(r.volumes, lv.name)
238	return removePath(filepath.Dir(lv.path))
239}
240
241func removePath(path string) error {
242	if err := os.RemoveAll(path); err != nil {
243		if os.IsNotExist(err) {
244			return nil
245		}
246		return errors.Wrapf(err, "error removing volume path '%s'", path)
247	}
248	return nil
249}
250
251// Get looks up the volume for the given name and returns it if found
252func (r *Root) Get(name string) (volume.Volume, error) {
253	r.m.Lock()
254	v, exists := r.volumes[name]
255	r.m.Unlock()
256	if !exists {
257		return nil, ErrNotFound
258	}
259	return v, nil
260}
261
262// Scope returns the local volume scope
263func (r *Root) Scope() string {
264	return volume.LocalScope
265}
266
267func (r *Root) validateName(name string) error {
268	if len(name) == 1 {
269		return validationError{fmt.Errorf("volume name is too short, names should be at least two alphanumeric characters")}
270	}
271	if !volumeNameRegex.MatchString(name) {
272		return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed. If you intented to pass a host directory, use absolute path", name, utils.RestrictedNameChars)}
273	}
274	return nil
275}
276
277// localVolume implements the Volume interface from the volume package and
278// represents the volumes created by Root.
279type localVolume struct {
280	m sync.Mutex
281	// unique name of the volume
282	name string
283	// path is the path on the host where the data lives
284	path string
285	// driverName is the name of the driver that created the volume.
286	driverName string
287	// opts is the parsed list of options used to create the volume
288	opts *optsConfig
289	// active refcounts the active mounts
290	active activeMount
291}
292
293// Name returns the name of the given Volume.
294func (v *localVolume) Name() string {
295	return v.name
296}
297
298// DriverName returns the driver that created the given Volume.
299func (v *localVolume) DriverName() string {
300	return v.driverName
301}
302
303// Path returns the data location.
304func (v *localVolume) Path() string {
305	return v.path
306}
307
308// Mount implements the localVolume interface, returning the data location.
309func (v *localVolume) Mount(id string) (string, error) {
310	v.m.Lock()
311	defer v.m.Unlock()
312	if v.opts != nil {
313		if !v.active.mounted {
314			if err := v.mount(); err != nil {
315				return "", err
316			}
317			v.active.mounted = true
318		}
319		v.active.count++
320	}
321	return v.path, nil
322}
323
324// Umount is for satisfying the localVolume interface and does not do anything in this driver.
325func (v *localVolume) Unmount(id string) error {
326	v.m.Lock()
327	defer v.m.Unlock()
328	if v.opts != nil {
329		v.active.count--
330		if v.active.count == 0 {
331			if err := mount.Unmount(v.path); err != nil {
332				v.active.count++
333				return errors.Wrapf(err, "error while unmounting volume path '%s'", v.path)
334			}
335			v.active.mounted = false
336		}
337	}
338	return nil
339}
340
341func validateOpts(opts map[string]string) error {
342	for opt := range opts {
343		if !validOpts[opt] {
344			return validationError{fmt.Errorf("invalid option key: %q", opt)}
345		}
346	}
347	return nil
348}
349
350func (v *localVolume) Status() map[string]interface{} {
351	return nil
352}
353
354// getAddress finds out address/hostname from options
355func getAddress(opts string) string {
356	optsList := strings.Split(opts, ",")
357	for i := 0; i < len(optsList); i++ {
358		if strings.HasPrefix(optsList[i], "addr=") {
359			addr := (strings.SplitN(optsList[i], "=", 2)[1])
360			return addr
361		}
362	}
363	return ""
364}
365