mirror of https://github.com/perkeep/perkeep.git
Merge branch 'master' of camlistore.org:camlistore
This commit is contained in:
commit
f6a9c55cd8
6
build.pl
6
build.pl
|
@ -280,7 +280,7 @@ sub find_go_camli_deps {
|
|||
my $t = $targets{$target} or die "Bogus or undeclared build target: $target\n";
|
||||
|
||||
opendir(my $dh, $target) or die;
|
||||
my @go_files = grep { !/_testmain\.go$/ } grep { /\.go$/ } readdir($dh);
|
||||
my @go_files = grep { !m!^\.\#! } grep { !/_testmain\.go$/ } grep { /\.go$/ } readdir($dh);
|
||||
closedir($dh);
|
||||
|
||||
# TODO: just stat the files first and keep a cache file of the
|
||||
|
@ -292,7 +292,7 @@ sub find_go_camli_deps {
|
|||
my @deps;
|
||||
my %seen; # $dep -> 1
|
||||
for my $f (@go_files) {
|
||||
open(my $fh, "$target/$f") or die;
|
||||
open(my $fh, "$target/$f") or die "Failed to open $target/$f: $!";
|
||||
my $src = do { local $/; <$fh>; };
|
||||
unless ($src =~ m!\bimport\s*\((.+?)\)!s) {
|
||||
die "Failed to parse imports from $target/$f.\n".
|
||||
|
@ -327,7 +327,7 @@ sub gen_target_makefile {
|
|||
my @deps = @{$t->{deps}};
|
||||
|
||||
opendir(my $dh, $target) or die;
|
||||
my @go_files = grep { !/_testmain\.go$/ } grep { /\.go$/ } readdir($dh);
|
||||
my @go_files = grep { !m!^\.\#! } grep { !/_testmain\.go$/ } grep { /\.go$/ } readdir($dh);
|
||||
closedir($dh);
|
||||
|
||||
open(my $mf, ">$target/Makefile") or die;
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
Copyright 2011 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"camli/blobref"
|
||||
"camli/client"
|
||||
"camli/schema"
|
||||
"camli/third_party/github.com/hanwen/go-fuse/fuse"
|
||||
)
|
||||
|
||||
var _ = fmt.Println
|
||||
var _ = log.Println
|
||||
|
||||
type CamliFileSystem struct {
|
||||
fetcher blobref.Fetcher
|
||||
root *blobref.BlobRef
|
||||
|
||||
lk sync.Mutex
|
||||
nameToBlob map[string]*blobref.BlobRef
|
||||
}
|
||||
|
||||
func NewCamliFileSystem(client *client.Client, root *blobref.BlobRef) *CamliFileSystem {
|
||||
return &CamliFileSystem{
|
||||
fetcher: client,
|
||||
root: root,
|
||||
nameToBlob: make(map[string]*blobref.BlobRef),
|
||||
}
|
||||
}
|
||||
|
||||
// Where name == "" for root,
|
||||
// Returns nil on failure
|
||||
func (fs *CamliFileSystem) blobRefFromNameCached(name string) *blobref.BlobRef {
|
||||
fs.lk.Lock()
|
||||
defer fs.lk.Unlock()
|
||||
return fs.nameToBlob[name]
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) fetchSchemaSuperset(br *blobref.BlobRef) (*schema.Superset, os.Error) {
|
||||
rsc, _, err := fs.fetcher.Fetch(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rsc.Close()
|
||||
jd := json.NewDecoder(rsc)
|
||||
ss := new(schema.Superset)
|
||||
err = jd.Decode(ss)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing %s as schema blob: %v", br, err)
|
||||
return nil, os.EINVAL
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// Where name == "" for root,
|
||||
// Returns fuse.Status == fuse.OK on success or anything else on failure.
|
||||
func (fs *CamliFileSystem) blobRefFromName(name string) (*blobref.BlobRef, fuse.Status) {
|
||||
if name == "" {
|
||||
return fs.root, fuse.OK
|
||||
}
|
||||
if br := fs.blobRefFromNameCached(name); br != nil {
|
||||
return br, fuse.OK
|
||||
}
|
||||
dir, fileName := filepath.Split(name)
|
||||
dirBlob, fuseStatus := fs.blobRefFromName(dir)
|
||||
if fuseStatus != fuse.OK {
|
||||
return nil, fuseStatus
|
||||
}
|
||||
|
||||
dirss, err := fs.fetchSchemaSuperset(dirBlob)
|
||||
switch {
|
||||
case err == os.ENOENT:
|
||||
log.Printf("Failed to find directory %s", dirBlob)
|
||||
return nil, fuse.ENOENT
|
||||
case err == os.EINVAL:
|
||||
log.Printf("Failed to parse directory %s", dirBlob)
|
||||
return nil, fuse.ENOTDIR
|
||||
case err != nil:
|
||||
panic(fmt.Sprintf("Invalid fetcher error: %v", err))
|
||||
case dirss == nil:
|
||||
panic("nil dirss")
|
||||
case dirss.Type != "directory":
|
||||
log.Printf("Expected %s to be a directory; actually a %s",
|
||||
dirBlob, dirss.Type)
|
||||
return nil, fuse.ENOTDIR
|
||||
}
|
||||
|
||||
if dirss.Entries == "" {
|
||||
log.Printf("Expected %s to have 'entries'", dirBlob)
|
||||
return nil, fuse.ENOTDIR
|
||||
}
|
||||
entriesBlob := blobref.Parse(dirss.Entries)
|
||||
if entriesBlob == nil {
|
||||
log.Printf("Blob %s had invalid blobref %q for its 'entries'", dirBlob, dirss.Entries)
|
||||
return nil, fuse.ENOTDIR
|
||||
}
|
||||
|
||||
entss, err := fs.fetchSchemaSuperset(entriesBlob)
|
||||
switch {
|
||||
case err == os.ENOENT:
|
||||
log.Printf("Failed to find entries %s via directory %s", entriesBlob, dirBlob)
|
||||
return nil, fuse.ENOENT
|
||||
case err == os.EINVAL:
|
||||
log.Printf("Failed to parse entries %s via directory %s", entriesBlob, dirBlob)
|
||||
return nil, fuse.ENOTDIR
|
||||
case err != nil:
|
||||
panic(fmt.Sprintf("Invalid fetcher error: %v", err))
|
||||
case entss == nil:
|
||||
panic("nil entss")
|
||||
case entss.Type != "static-set":
|
||||
log.Printf("Expected %s to be a directory; actually a %s",
|
||||
dirBlob, dirss.Type)
|
||||
return nil, fuse.ENOTDIR
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
foundCh := make(chan *blobref.BlobRef)
|
||||
for _, m := range entss.Members {
|
||||
wg.Add(1)
|
||||
go func(memberBlobstr string) {
|
||||
childss, err := fs.fetchSchemaSuperset(entriesBlob)
|
||||
if err == nil && childss.HasFilename(fileName) {
|
||||
foundCh <- entriesBlob
|
||||
}
|
||||
wg.Done()
|
||||
}(m)
|
||||
}
|
||||
failCh := make(chan string)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
failCh <- "ENOENT"
|
||||
}()
|
||||
select {
|
||||
case found := <-foundCh:
|
||||
fs.lk.Lock()
|
||||
defer fs.lk.Unlock()
|
||||
fs.nameToBlob[name] = found
|
||||
return found, fuse.OK
|
||||
case <-failCh:
|
||||
}
|
||||
// TODO: negative cache
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Mount(connector *fuse.PathFileSystemConnector) fuse.Status {
|
||||
log.Printf("cammount: Mount")
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Unmount() {
|
||||
log.Printf("cammount: Unmount.")
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) GetAttr(name string) (*fuse.Attr, fuse.Status) {
|
||||
log.Printf("cammount: GetAttr(%q)", name)
|
||||
blobref, errStatus := fs.blobRefFromName(name)
|
||||
log.Printf("cammount: GetAttr(%q), blobRefFromName err=%v", name, errStatus)
|
||||
if errStatus != fuse.OK {
|
||||
return nil, errStatus
|
||||
}
|
||||
log.Printf("cammount: got blob %s", blobref)
|
||||
|
||||
// TODO: this is redundant with what blobRefFromName already
|
||||
// did. we should at least keep this in RAM (pre-de-JSON'd)
|
||||
// so we don't have to fetch + unmarshal it again.
|
||||
ss, err := fs.fetchSchemaSuperset(blobref)
|
||||
if err != nil {
|
||||
log.Printf("cammount: GetAttr(%q, %s): fetch schema error: %v", name, blobref, err)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
out := new(fuse.Attr)
|
||||
var fi os.FileInfo
|
||||
|
||||
if ss.UnixPermission != "" {
|
||||
// Convert from octal
|
||||
mode, err := strconv.Btoui64(ss.UnixPermission, 8)
|
||||
if err != nil {
|
||||
fi.Mode = uint32(mode)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: have a mode to set permissions equal to mounting user?
|
||||
fi.Uid = ss.UnixOwnerId
|
||||
fi.Gid = ss.UnixGroupId
|
||||
|
||||
// TODO: other types
|
||||
switch ss.Type {
|
||||
case "directory":
|
||||
fi.Mode = fi.Mode | syscall.S_IFDIR
|
||||
case "file":
|
||||
fi.Mode = fi.Mode | syscall.S_IFREG
|
||||
fi.Size = ss.Size
|
||||
case "symlink":
|
||||
fi.Mode = fi.Mode | syscall.S_IFLNK
|
||||
}
|
||||
|
||||
// TODO: mtime and such
|
||||
|
||||
fuse.CopyFileInfo(&fi, out)
|
||||
return out, fuse.OK
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Access(name string, mode uint32) fuse.Status {
|
||||
log.Printf("cammount: Access(%q, %d)", name, mode)
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Open(name string, flags uint32) (file fuse.RawFuseFile, code fuse.Status) {
|
||||
log.Printf("cammount: Open(%q, %d)", name, flags)
|
||||
// TODO
|
||||
return nil, fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) OpenDir(name string) (stream chan fuse.DirEntry, code fuse.Status) {
|
||||
log.Printf("cammount: OpenDir(%q)", name)
|
||||
// TODO
|
||||
return nil, fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Readlink(name string) (string, fuse.Status) {
|
||||
log.Printf("cammount: Readlink(%q)", name)
|
||||
// TODO
|
||||
return "", fuse.EACCES
|
||||
}
|
||||
|
||||
// *************************************************************************
|
||||
// EACCESS stuff
|
||||
|
||||
func (fs *CamliFileSystem) Chmod(name string, mode uint32) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Chown(name string, uid uint32, gid uint32) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Create(name string, flags uint32, mode uint32) (file fuse.RawFuseFile, code fuse.Status) {
|
||||
code = fuse.EACCES
|
||||
return
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Link(oldName string, newName string) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Mkdir(name string, mode uint32) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Mknod(name string, mode uint32, dev uint32) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Rename(oldName string, newName string) (code fuse.Status) {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Rmdir(name string) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Symlink(value string, linkName string) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Truncate(name string, offset uint64) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Unlink(name string) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
||||
|
||||
func (fs *CamliFileSystem) Utimens(name string, AtimeNs uint64, CtimeNs uint64) fuse.Status {
|
||||
return fuse.EACCES
|
||||
}
|
|
@ -22,6 +22,8 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
|
||||
"camli/blobref"
|
||||
"camli/client"
|
||||
"camli/third_party/github.com/hanwen/go-fuse/fuse"
|
||||
)
|
||||
|
||||
|
@ -45,23 +47,19 @@ func main() {
|
|||
threaded := flag.Bool("threaded", true, "switch off threading; print debugging messages.")
|
||||
flag.Parse()
|
||||
if flag.NArg() < 2 {
|
||||
// TODO - where to get program name?
|
||||
fmt.Println("usage: main ORIGINAL MOUNTPOINT")
|
||||
fmt.Println("usage: cammount <blobref> <mountpoint>")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
orig := flag.Arg(0)
|
||||
fs := fuse.NewLoopbackFileSystem(orig)
|
||||
root := blobref.Parse(flag.Arg(0))
|
||||
if root == nil {
|
||||
fmt.Printf("Error parsing root blobref: %q\n", root)
|
||||
os.Exit(2)
|
||||
}
|
||||
client := client.NewOrFail() // automatic from flags
|
||||
fs := NewCamliFileSystem(client, root)
|
||||
timing := fuse.NewTimingPathFilesystem(fs)
|
||||
|
||||
var opts fuse.PathFileSystemConnectorOptions
|
||||
|
||||
opts.AttrTimeout = 1.0
|
||||
opts.EntryTimeout = 1.0
|
||||
opts.NegativeTimeout = 1.0
|
||||
|
||||
fs.SetOptions(&opts)
|
||||
|
||||
conn := fuse.NewPathFileSystemConnector(timing)
|
||||
rawTiming := fuse.NewTimingRawFilesystem(conn)
|
||||
|
||||
|
@ -75,7 +73,7 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Mounted %s on %s (threaded=%v, debug=%v)\n", orig, mountPoint, *threaded, *debug)
|
||||
fmt.Printf("Mounted %s on %s (threaded=%v, debug=%v)\n", root.String(), mountPoint, *threaded, *debug)
|
||||
state.Loop(*threaded)
|
||||
fmt.Println("Finished", state.Stats())
|
||||
|
||||
|
|
|
@ -55,6 +55,28 @@ type Superset struct {
|
|||
Permanode string "permaNode"
|
||||
Attribute string "attribute"
|
||||
Value string "value"
|
||||
|
||||
FileName string "fileName"
|
||||
FileNameBytes []byte "fileNameBytes" // TODO: needs custom UnmarshalJSON?
|
||||
UnixPermission string "unixPermission"
|
||||
UnixOwnerId int "unixOwnerId"
|
||||
UnixOwner string "unixOwner"
|
||||
UnixGroupId int "unixGroupId"
|
||||
UnixGroup string "unixGroup"
|
||||
UnixMtime string "unixMtime"
|
||||
UnixCtime string "unixCtime"
|
||||
UnixAtime string "unixAtime"
|
||||
|
||||
Size int64 "size" // for files
|
||||
|
||||
Entries string "entries" // for directories, a blobref to a static-set
|
||||
Members []string "members" // for static sets (for directory static-sets:
|
||||
// blobrefs to child dirs/files)
|
||||
}
|
||||
|
||||
func (ss *Superset) HasFilename(name string) bool {
|
||||
// TODO: use filename bytes too
|
||||
return ss.FileName == name
|
||||
}
|
||||
|
||||
var DefaultStatHasher = &defaultStatHasher{}
|
||||
|
|
Loading…
Reference in New Issue