+ return fmt.Sprintf("[UnixVolume %s]", v.Root)
+}
+
+// Writable returns false if all future Put, Mtime, and Delete calls
+// are expected to fail.
+func (v *UnixVolume) Writable() bool {
+ return !v.ReadOnly
+}
+
+// Replication returns the number of replicas promised by the
+// underlying device (as specified in configuration).
+func (v *UnixVolume) Replication() int {
+ return v.DirectoryReplication
+}
+
+// GetStorageClasses implements Volume
+func (v *UnixVolume) GetStorageClasses() []string {
+ return v.StorageClasses
+}
+
+// InternalStats returns I/O and filesystem ops counters.
+func (v *UnixVolume) InternalStats() interface{} {
+ return &v.os.stats
+}
+
+// lock acquires the serialize lock, if one is in use. If ctx is done
+// before the lock is acquired, lock returns ctx.Err() instead of
+// acquiring the lock.
+func (v *UnixVolume) lock(ctx context.Context) error {
+ if v.locker == nil {
+ return nil
+ }
+ locked := make(chan struct{})
+ go func() {
+ v.locker.Lock()
+ close(locked)
+ }()
+ select {
+ case <-ctx.Done():
+ go func() {
+ <-locked
+ v.locker.Unlock()
+ }()
+ return ctx.Err()
+ case <-locked:
+ return nil
+ }
+}
+
+// unlock releases the serialize lock, if one is in use.
+func (v *UnixVolume) unlock() {
+ if v.locker == nil {
+ return
+ }
+ v.locker.Unlock()
+}
+
+// lockfile and unlockfile use flock(2) to manage kernel file locks.
+func (v *UnixVolume) lockfile(f *os.File) error {
+ v.os.stats.Tick(&v.os.stats.FlockOps)
+ err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
+ v.os.stats.TickErr(err)
+ return err
+}
+
+func (v *UnixVolume) unlockfile(f *os.File) error {
+ err := syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
+ v.os.stats.TickErr(err)
+ return err
+}
+
+// Where appropriate, translate a more specific filesystem error to an
+// error recognized by handlers, like os.ErrNotExist.
+func (v *UnixVolume) translateError(err error) error {
+ switch err.(type) {
+ case *os.PathError:
+ // stat() returns a PathError if the parent directory
+ // (not just the file itself) is missing
+ return os.ErrNotExist
+ default:
+ return err
+ }
+}
+
+var unixTrashLocRegexp = regexp.MustCompile(`/([0-9a-f]{32})\.trash\.(\d+)$`)
+
+// EmptyTrash walks hierarchy looking for {hash}.trash.*
+// and deletes those with deadline < now.
+func (v *UnixVolume) EmptyTrash() {
+ var bytesDeleted, bytesInTrash int64
+ var blocksDeleted, blocksInTrash int64
+
+ doFile := func(path string, info os.FileInfo) {
+ if info.Mode().IsDir() {
+ return
+ }
+ matches := unixTrashLocRegexp.FindStringSubmatch(path)
+ if len(matches) != 3 {
+ return
+ }
+ deadline, err := strconv.ParseInt(matches[2], 10, 64)
+ if err != nil {
+ log.Printf("EmptyTrash: %v: ParseInt(%v): %v", path, matches[2], err)
+ return
+ }
+ atomic.AddInt64(&bytesInTrash, info.Size())
+ atomic.AddInt64(&blocksInTrash, 1)
+ if deadline > time.Now().Unix() {
+ return
+ }
+ err = v.os.Remove(path)
+ if err != nil {
+ log.Printf("EmptyTrash: Remove %v: %v", path, err)
+ return
+ }
+ atomic.AddInt64(&bytesDeleted, info.Size())
+ atomic.AddInt64(&blocksDeleted, 1)
+ }
+
+ type dirent struct {
+ path string
+ info os.FileInfo
+ }
+ var wg sync.WaitGroup
+ todo := make(chan dirent, theConfig.EmptyTrashWorkers)
+ for i := 0; i < 1 || i < theConfig.EmptyTrashWorkers; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for e := range todo {
+ doFile(e.path, e.info)
+ }
+ }()
+ }
+
+ err := filepath.Walk(v.Root, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ log.Printf("EmptyTrash: filepath.Walk: %v: %v", path, err)
+ return nil
+ }
+ todo <- dirent{path, info}
+ return nil
+ })
+ close(todo)
+ wg.Wait()
+
+ if err != nil {
+ log.Printf("EmptyTrash error for %v: %v", v.String(), err)
+ }
+
+ log.Printf("EmptyTrash stats for %v: Deleted %v bytes in %v blocks. Remaining in trash: %v bytes in %v blocks.", v.String(), bytesDeleted, blocksDeleted, bytesInTrash-bytesDeleted, blocksInTrash-blocksDeleted)
+}
+
+type unixStats struct {
+ statsTicker
+ OpenOps uint64
+ StatOps uint64
+ FlockOps uint64
+ UtimesOps uint64
+ CreateOps uint64
+ RenameOps uint64
+ UnlinkOps uint64
+ ReaddirOps uint64
+}
+
+func (s *unixStats) TickErr(err error) {
+ if err == nil {
+ return
+ }
+ s.statsTicker.TickErr(err, fmt.Sprintf("%T", err))
+}
+
+type osWithStats struct {
+ stats unixStats
+}
+
+func (o *osWithStats) Open(name string) (*os.File, error) {
+ o.stats.Tick(&o.stats.OpenOps)
+ f, err := os.Open(name)
+ o.stats.TickErr(err)
+ return f, err
+}
+
+func (o *osWithStats) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
+ o.stats.Tick(&o.stats.OpenOps)
+ f, err := os.OpenFile(name, flag, perm)
+ o.stats.TickErr(err)
+ return f, err
+}
+
+func (o *osWithStats) Remove(path string) error {
+ o.stats.Tick(&o.stats.UnlinkOps)
+ err := os.Remove(path)
+ o.stats.TickErr(err)
+ return err
+}
+
+func (o *osWithStats) Rename(a, b string) error {
+ o.stats.Tick(&o.stats.RenameOps)
+ err := os.Rename(a, b)
+ o.stats.TickErr(err)
+ return err
+}
+
+func (o *osWithStats) Stat(path string) (os.FileInfo, error) {
+ o.stats.Tick(&o.stats.StatOps)
+ fi, err := os.Stat(path)
+ o.stats.TickErr(err)
+ return fi, err
+}
+
+func (o *osWithStats) TempFile(dir, base string) (*os.File, error) {
+ o.stats.Tick(&o.stats.CreateOps)
+ f, err := ioutil.TempFile(dir, base)
+ o.stats.TickErr(err)
+ return f, err