Skip to content

Instantly share code, notes, and snippets.

@perillo
Created March 14, 2016 17:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save perillo/7934af67544117ce75cf to your computer and use it in GitHub Desktop.
Save perillo/7934af67544117ce75cf to your computer and use it in GitHub Desktop.
// 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