package local import ( "errors" "fmt" "io/ioutil" "os" "path/filepath" "strings" "sync" "github.com/docker/docker/volume" ) // VolumeDataPathName is the name of the directory where the volume data is stored. // It uses a very distintive name to avoid colissions migrating data between // Docker versions. const ( VolumeDataPathName = "_data" volumesPathName = "volumes" ) var oldVfsDir = filepath.Join("vfs", "dir") func New(scope string) (*Root, error) { rootDirectory := filepath.Join(scope, volumesPathName) if err := os.MkdirAll(rootDirectory, 0700); err != nil { return nil, err } r := &Root{ scope: scope, path: rootDirectory, volumes: make(map[string]*Volume), } dirs, err := ioutil.ReadDir(rootDirectory) if err != nil { return nil, err } for _, d := range dirs { name := filepath.Base(d.Name()) r.volumes[name] = &Volume{ driverName: r.Name(), name: name, path: r.DataPath(name), } } return r, nil } type Root struct { m sync.Mutex scope string path string volumes map[string]*Volume } func (r *Root) DataPath(volumeName string) string { return filepath.Join(r.path, volumeName, VolumeDataPathName) } func (r *Root) Name() string { return "local" } func (r *Root) Create(name string) (volume.Volume, error) { r.m.Lock() defer r.m.Unlock() v, exists := r.volumes[name] if !exists { path := r.DataPath(name) if err := os.MkdirAll(path, 0755); err != nil { if os.IsExist(err) { return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path)) } return nil, err } v = &Volume{ driverName: r.Name(), name: name, path: path, } r.volumes[name] = v } v.use() return v, nil } func (r *Root) Remove(v volume.Volume) error { r.m.Lock() defer r.m.Unlock() lv, ok := v.(*Volume) if !ok { return errors.New("unknown volume type") } lv.release() if lv.usedCount == 0 { realPath, err := filepath.EvalSymlinks(lv.path) if err != nil { return err } if !r.scopedPath(realPath) { return fmt.Errorf("Unable to remove a directory of out the Docker root: %s", realPath) } if err := os.RemoveAll(realPath); err != nil { return err } delete(r.volumes, lv.name) return os.RemoveAll(filepath.Dir(lv.path)) } return nil } // scopedPath verifies that the path where the volume is located // is under Docker's root and the valid local paths. func (r *Root) scopedPath(realPath string) bool { // Volumes path for Docker version >= 1.7 if strings.HasPrefix(realPath, filepath.Join(r.scope, volumesPathName)) { return true } // Volumes path for Docker version < 1.7 if strings.HasPrefix(realPath, filepath.Join(r.scope, oldVfsDir)) { return true } return false } type Volume struct { m sync.Mutex usedCount int // unique name of the volume name string // path is the path on the host where the data lives path string // driverName is the name of the driver that created the volume. driverName string } func (v *Volume) Name() string { return v.name } func (v *Volume) DriverName() string { return v.driverName } func (v *Volume) Path() string { return v.path } func (v *Volume) Mount() (string, error) { return v.path, nil } func (v *Volume) Unmount() error { return nil } func (v *Volume) use() { v.m.Lock() v.usedCount++ v.m.Unlock() } func (v *Volume) release() { v.m.Lock() v.usedCount-- v.m.Unlock() }