1 // A UnixVolume is a Volume backed by a locally mounted disk.
20 // A UnixVolume stores and retrieves blocks in a local directory.
21 type UnixVolume struct {
22 root string // path to the volume's root directory
28 func (v *UnixVolume) Touch(loc string) error {
30 return MethodDisabledError
33 f, err := os.OpenFile(p, os.O_RDWR|os.O_APPEND, 0644)
40 defer v.mutex.Unlock()
42 if e := lockfile(f); e != nil {
46 now := time.Now().Unix()
47 utime := syscall.Utimbuf{now, now}
48 return syscall.Utime(p, &utime)
51 func (v *UnixVolume) Mtime(loc string) (time.Time, error) {
53 if fi, err := os.Stat(p); err != nil {
54 return time.Time{}, err
56 return fi.ModTime(), nil
60 // Get retrieves a block identified by the locator string "loc", and
61 // returns its contents as a byte slice.
63 // If the block could not be found, opened, or read, Get returns a nil
64 // slice and whatever non-nil error was returned by Stat or ReadFile.
65 func (v *UnixVolume) Get(loc string) ([]byte, error) {
66 path := v.blockPath(loc)
67 stat, err := os.Stat(path)
72 return nil, os.ErrInvalid
73 } else if stat.Size() == 0 {
74 return bufs.Get(0), nil
75 } else if stat.Size() > BLOCKSIZE {
76 return nil, TooLongError
78 f, err := os.Open(path)
83 buf := bufs.Get(int(stat.Size()))
86 defer v.mutex.Unlock()
88 _, err = io.ReadFull(f, buf)
96 // Put stores a block of data identified by the locator string
97 // "loc". It returns nil on success. If the volume is full, it
98 // returns a FullError. If the write fails due to some other error,
99 // that error is returned.
100 func (v *UnixVolume) Put(loc string, block []byte) error {
102 return MethodDisabledError
107 bdir := v.blockDir(loc)
108 if err := os.MkdirAll(bdir, 0755); err != nil {
109 log.Printf("%s: could not create directory %s: %s",
114 tmpfile, tmperr := ioutil.TempFile(bdir, "tmp"+loc)
116 log.Printf("ioutil.TempFile(%s, tmp%s): %s", bdir, loc, tmperr)
119 bpath := v.blockPath(loc)
123 defer v.mutex.Unlock()
125 if _, err := tmpfile.Write(block); err != nil {
126 log.Printf("%s: writing to %s: %s\n", v, bpath, err)
128 os.Remove(tmpfile.Name())
131 if err := tmpfile.Close(); err != nil {
132 log.Printf("closing %s: %s\n", tmpfile.Name(), err)
133 os.Remove(tmpfile.Name())
136 if err := os.Rename(tmpfile.Name(), bpath); err != nil {
137 log.Printf("rename %s %s: %s\n", tmpfile.Name(), bpath, err)
138 os.Remove(tmpfile.Name())
144 // Status returns a VolumeStatus struct describing the volume's
145 // current state, or nil if an error occurs.
147 func (v *UnixVolume) Status() *VolumeStatus {
148 var fs syscall.Statfs_t
151 if fi, err := os.Stat(v.root); err == nil {
152 devnum = fi.Sys().(*syscall.Stat_t).Dev
154 log.Printf("%s: os.Stat: %s\n", v, err)
158 err := syscall.Statfs(v.root, &fs)
160 log.Printf("%s: statfs: %s\n", v, err)
163 // These calculations match the way df calculates disk usage:
164 // "free" space is measured by fs.Bavail, but "used" space
165 // uses fs.Blocks - fs.Bfree.
166 free := fs.Bavail * uint64(fs.Bsize)
167 used := (fs.Blocks - fs.Bfree) * uint64(fs.Bsize)
168 return &VolumeStatus{v.root, devnum, free, used}
171 var blockDirRe = regexp.MustCompile(`^[0-9a-f]+$`)
173 // IndexTo writes (to the given Writer) a list of blocks found on this
174 // volume which begin with the specified prefix. If the prefix is an
175 // empty string, IndexTo writes a complete list of blocks.
177 // Each block is given in the format
179 // locator+size modification-time {newline}
183 // e4df392f86be161ca6ed3773a962b8f3+67108864 1388894303
184 // e4d41e6fd68460e0e3fc18cc746959d2+67108864 1377796043
185 // e4de7a2810f5554cd39b36d8ddb132ff+67108864 1388701136
187 func (v *UnixVolume) IndexTo(prefix string, w io.Writer) error {
188 var lastErr error = nil
189 rootdir, err := os.Open(v.root)
193 defer rootdir.Close()
195 names, err := rootdir.Readdirnames(1)
198 } else if err != nil {
201 if !strings.HasPrefix(names[0], prefix) && !strings.HasPrefix(prefix, names[0]) {
202 // prefix excludes all blocks stored in this dir
205 if !blockDirRe.MatchString(names[0]) {
208 blockdirpath := filepath.Join(v.root, names[0])
209 blockdir, err := os.Open(blockdirpath)
211 log.Print("Error reading ", blockdirpath, ": ", err)
216 fileInfo, err := blockdir.Readdir(1)
219 } else if err != nil {
220 log.Print("Error reading ", blockdirpath, ": ", err)
224 name := fileInfo[0].Name()
225 if !strings.HasPrefix(name, prefix) {
228 _, err = fmt.Fprint(w,
230 "+", fileInfo[0].Size(),
231 " ", fileInfo[0].ModTime().Unix(),
238 func (v *UnixVolume) Delete(loc string) error {
239 // Touch() must be called before calling Write() on a block. Touch()
240 // also uses lockfile(). This avoids a race condition between Write()
241 // and Delete() because either (a) the file will be deleted and Touch()
242 // will signal to the caller that the file is not present (and needs to
243 // be re-written), or (b) Touch() will update the file's timestamp and
244 // Delete() will read the correct up-to-date timestamp and choose not to
248 return MethodDisabledError
252 defer v.mutex.Unlock()
254 p := v.blockPath(loc)
255 f, err := os.OpenFile(p, os.O_RDWR|os.O_APPEND, 0644)
260 if e := lockfile(f); e != nil {
265 // If the block has been PUT in the last blob_signature_ttl
266 // seconds, return success without removing the block. This
267 // protects data from garbage collection until it is no longer
268 // possible for clients to retrieve the unreferenced blocks
269 // anyway (because the permission signatures have expired).
270 if fi, err := os.Stat(p); err != nil {
273 if time.Since(fi.ModTime()) < blob_signature_ttl {
280 // blockDir returns the fully qualified directory name for the directory
281 // where loc is (or would be) stored on this volume.
282 func (v *UnixVolume) blockDir(loc string) string {
283 return filepath.Join(v.root, loc[0:3])
286 // blockPath returns the fully qualified pathname for the path to loc
288 func (v *UnixVolume) blockPath(loc string) string {
289 return filepath.Join(v.blockDir(loc), loc)
292 // IsFull returns true if the free space on the volume is less than
293 // MIN_FREE_KILOBYTES.
295 func (v *UnixVolume) IsFull() (isFull bool) {
296 fullSymlink := v.root + "/full"
298 // Check if the volume has been marked as full in the last hour.
299 if link, err := os.Readlink(fullSymlink); err == nil {
300 if ts, err := strconv.Atoi(link); err == nil {
301 fulltime := time.Unix(int64(ts), 0)
302 if time.Since(fulltime).Hours() < 1.0 {
308 if avail, err := v.FreeDiskSpace(); err == nil {
309 isFull = avail < MIN_FREE_KILOBYTES
311 log.Printf("%s: FreeDiskSpace: %s\n", v, err)
315 // If the volume is full, timestamp it.
317 now := fmt.Sprintf("%d", time.Now().Unix())
318 os.Symlink(now, fullSymlink)
323 // FreeDiskSpace returns the number of unused 1k blocks available on
326 func (v *UnixVolume) FreeDiskSpace() (free uint64, err error) {
327 var fs syscall.Statfs_t
328 err = syscall.Statfs(v.root, &fs)
330 // Statfs output is not guaranteed to measure free
331 // space in terms of 1K blocks.
332 free = fs.Bavail * uint64(fs.Bsize) / 1024
337 func (v *UnixVolume) String() string {
338 return fmt.Sprintf("[UnixVolume %s]", v.root)
341 func (v *UnixVolume) Writable() bool {
345 // lockfile and unlockfile use flock(2) to manage kernel file locks.
346 func lockfile(f *os.File) error {
347 return syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
350 func unlockfile(f *os.File) error {
351 return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)