Created
March 14, 2016 17:31
-
-
Save perillo/7934af67544117ce75cf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2016 Manlio Perillo. All rights reserved. | |
// Use of this source code is governed by a BSD-style | |
// license that can be found in the LICENSE file. | |
package store | |
import ( | |
"os" | |
"path/filepath" | |
) | |
// Store represents an immutable objects storage implemented atop a POSIX | |
// filesystem. | |
// | |
// Currently only Linux is supported. | |
// | |
// Files and directory will be created using an umask of 077. | |
type Store struct { | |
dir *os.File | |
dirpath string | |
} | |
// Open opens a new storage at the specified directory. dirpath must be the | |
// name of an existing directory. | |
func Open(dirpath string) (*Store, error) { | |
dir, err := opendir(dirpath) | |
if err != nil { | |
return nil, err | |
} | |
s := &Store{ | |
dir: dir, | |
dirpath: dirpath, | |
} | |
return s, nil | |
} | |
func (s *Store) Close() error { | |
return s.dir.Close() | |
} | |
// bucket represents an object storage bucket. | |
type bucket struct { | |
store *store | |
dir *os.File | |
name string | |
} | |
// Open opens a bucket with specified name. If the bucket does not exists, it | |
// will be created. | |
// | |
// name must only contain characters from the POSIX Portable Filename Character | |
// Set. | |
func (s *Store) Open(name string) (Bucket, error) { | |
if !isValidBucketName(name) { | |
return nil, ErrInvalidBucketName | |
} | |
path := filepath.Join(s.dirpath, name) | |
if err := os.Mkdir(path, 0700); err != nil { | |
if !os.IsExist(err) { | |
return nil, err | |
} | |
} | |
dir, err := opendir(path) | |
if err != nil { | |
return nil, err | |
} | |
b := &bucket{ | |
store: s, | |
dir: dir, | |
name: name, | |
} | |
return b, nil | |
} | |
func (b *bucket) Name() string { | |
return b.name | |
} | |
func (b *bucket) Close() error { | |
return b.dir.Close() | |
} | |
type objectReader struct { | |
bucket *bucket | |
name string // public name, as specified by user | |
file *os.File | |
} | |
func (b *bucket) Open(name string) (ObjectReader, error) { | |
file, err := openat(s.dir, encode(name), os.O_RDONLY, 0) | |
if err != nil { | |
return nil, err | |
} | |
r := &objectReader{ | |
bucket: s, | |
name: name, | |
file: file, | |
} | |
return r, nil | |
} | |
func (r *objectReader) Name() string { | |
return r.name | |
} | |
func (r *objectReader) Read(p []byte) (n int, err error) { | |
return r.file.Read(p) | |
} | |
func (r *objectReader) Close() error { | |
return r.file.Close() | |
} | |
type objectWriter struct { | |
bucket *bucket | |
name string // public name, as specified by user | |
pname string // internal name | |
file *os.File | |
} | |
func (b *bucket) Create(name string) (ObjectWriter, error) { | |
pname := encode(name) | |
// Use a temporary file to ensure atomicity and consistency. | |
file, err := tempFileat(s.dir, "."+pname) | |
if err != nil { | |
return nil, err | |
} | |
r := &objectWriter{ | |
bucket: s, | |
name: name, | |
pname: pname, | |
file: file, | |
} | |
return r, nil | |
} | |
func (w *objectWriter) Name() string { | |
return w.name | |
} | |
func (w *objectWriter) Write(p []byte) (n int, err error) { | |
return w.file.Write(p) | |
} | |
func (w *objectWriter) Sync() error { | |
defer w.Close() | |
// The protocoll is: | |
// 1) Sync the temp file to ensure consistency | |
// 2) Rename tempname → name to ensure atomicity | |
// 3) Sync the directory to ensure durability | |
if err := w.file.Sync(); err != nil { | |
return err | |
} | |
err := renameat(w.bucket.dir, w.file.Name(), w.bucket.dir, w.pname) | |
if err != nil { | |
return err | |
} | |
return w.bucket.dir.Sync() | |
} | |
func (w *objectWriter) Close() error { | |
err := w.file.Close() | |
unlinkat(w.bucket.dir, w.file.Name()) | |
return err | |
} | |
// isValidBucketName returns true if name is a valid bucket name. | |
func isValidBucketName(name string) bool { | |
for i := 0; i < len(name); i++ { | |
if !isPortableFilename(name[i]) { | |
return false | |
} | |
} | |
return true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment