Remove obsolete manifestfs Change-Id: Ifa460f8773b5d55080e00dca337a5d4e93bf63e9
diff --git a/cmd/slothfs-deref-manifest/main.go b/cmd/slothfs-deref-manifest/main.go deleted file mode 100644 index 00965f4..0000000 --- a/cmd/slothfs-deref-manifest/main.go +++ /dev/null
@@ -1,54 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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 ( - "flag" - "log" - "os" - - "github.com/google/slothfs/gitiles" - "github.com/google/slothfs/populate" -) - -func main() { - gitilesOptions := gitiles.DefineFlags() - branch := flag.String("branch", "master", "Specify branch of the manifest repository to use.") - repo := flag.String("repo", "platform/manifest", "Set repository name holding manifest file.") - flag.Parse() - - service, err := gitiles.NewService(*gitilesOptions) - if err != nil { - log.Fatalf("NewService: %v", err) - } - - mf, err := populate.FetchManifest(service, *repo, *branch) - if err != nil { - log.Fatalf("FetchManifest: %v", err) - } - - mf.Filter() - - if err := populate.DerefManifest(service, mf); err != nil { - log.Fatalf("DerefManifest: %v", err) - } - - xml, err := mf.MarshalXML() - if err != nil { - log.Fatalf("MarshalXML: %v", err) - } - - os.Stdout.Write(xml) -}
diff --git a/cmd/slothfs-deref-repo/main.go b/cmd/slothfs-deref-repo/main.go deleted file mode 100644 index 74140d2..0000000 --- a/cmd/slothfs-deref-repo/main.go +++ /dev/null
@@ -1,69 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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. - -// This program creates a manifest file with revisions filled in from -// a normal repo checkout. This can be used for comparing slothfs and -// actual repo checkouts. -package main - -import ( - "flag" - "log" - "os" - "path/filepath" - - "github.com/google/slothfs/manifest" - git "github.com/libgit2/git2go" -) - -func main() { - flag.Parse() - if len(flag.Args()) == 0 { - log.Fatal("must state the repo toplevel directory.") - } - - top := flag.Arg(0) - mf, err := manifest.ParseFile(filepath.Join(top, ".repo", "manifest.xml")) - if err != nil { - log.Fatal(err) - } - - mf.Filter() - for i, p := range mf.Project { - repoPath := filepath.Join(top, p.GetPath(), ".git") - repo, err := git.OpenRepository(repoPath) - if err != nil { - continue - } - - h, err := repo.Head() - if err != nil { - log.Println("head", p.Name, err) - } - - obj, err := h.Peel(git.ObjectCommit) - if err != nil { - log.Println("peel", p.Name, err) - } - - mf.Project[i].Revision = obj.Id().String() - } - - xml, err := mf.MarshalXML() - if err != nil { - log.Fatal("MarshalXML", err) - } - - os.Stdout.Write(xml) -}
diff --git a/cmd/slothfs-manifestfs/main.go b/cmd/slothfs-manifestfs/main.go deleted file mode 100644 index 549dfd7..0000000 --- a/cmd/slothfs-manifestfs/main.go +++ /dev/null
@@ -1,99 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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 ( - "flag" - "io/ioutil" - "log" - "os" - "path/filepath" - "time" - - "github.com/google/slothfs/cache" - "github.com/google/slothfs/fs" - "github.com/google/slothfs/gitiles" - "github.com/google/slothfs/manifest" - "github.com/hanwen/go-fuse/fuse/nodefs" -) - -func main() { - manifestPath := flag.String("manifest", "", "Set path to the expanded manifest file.") - cacheDir := flag.String("cache", filepath.Join(os.Getenv("HOME"), ".cache", "slothfs"), - "Set the directory holding the file system cache.") - debug := flag.Bool("debug", false, "Print FUSE debug info.") - config := flag.String("config", "", "Set path to clone configuration JSON file.") - gitilesOptions := gitiles.DefineFlags() - flag.Parse() - - if *manifestPath == "" { - log.Fatal("must set --manifest") - } - if *cacheDir == "" { - log.Fatal("must set --cache") - } - - if len(flag.Args()) < 1 { - log.Fatal("mountpoint argument missing.") - } - mntDir := flag.Arg(0) - - cache, err := cache.NewCache(*cacheDir, cache.Options{}) - if err != nil { - log.Printf("NewCache: %v", err) - } - - service, err := gitiles.NewService(*gitilesOptions) - if err != nil { - log.Printf("NewService: %v", err) - } - - mf, err := manifest.ParseFile(*manifestPath) - if err != nil { - log.Fatal(err) - } - - opts := fs.ManifestOptions{ - Manifest: mf, - } - - if *config != "" { - configContents, err := ioutil.ReadFile(*config) - if err != nil { - log.Fatal(err) - } - opts.RepoCloneOption, opts.FileCloneOption, err = fs.ReadConfig(configContents) - if err != nil { - log.Fatal(err) - } - } - - root, err := fs.NewManifestFS(service, cache, opts) - if err != nil { - log.Fatalf("NewManifestFS: %v", err) - } - - server, _, err := nodefs.MountRoot(mntDir, root, &nodefs.Options{ - EntryTimeout: time.Hour, - NegativeTimeout: time.Hour, - AttrTimeout: time.Hour, - Debug: *debug, - }) - if err != nil { - log.Fatalf("MountFileSystem: %v", err) - } - log.Printf("Started gitiles fs FUSE on %s", mntDir) - server.Serve() -}
diff --git a/fs/fixture_test.go b/fs/fixture_test.go new file mode 100644 index 0000000..92d41f7 --- /dev/null +++ b/fs/fixture_test.go
@@ -0,0 +1,103 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// 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 fs + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/google/slothfs/cache" + "github.com/google/slothfs/gitiles" + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" +) + +type testFixture struct { + dir string + mntDir string + server *fuse.Server + cache *cache.Cache + testServer *testServer + service *gitiles.Service + root nodefs.Node +} + +func (f *testFixture) cleanup() { + if f.testServer != nil { + f.testServer.listener.Close() + } + if f.server != nil { + f.server.Unmount() + } + os.RemoveAll(f.dir) +} + +func newTestFixture() (*testFixture, error) { + d, err := ioutil.TempDir("", "slothfs") + if err != nil { + return nil, err + } + + fixture := &testFixture{dir: d} + + fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{}) + if err != nil { + return nil, err + } + + fixture.testServer, err = newTestServer() + if err != nil { + return nil, err + } + + fixture.service, err = gitiles.NewService(gitiles.Options{ + Address: fmt.Sprintf("http://%s", fixture.testServer.addr), + }) + if err != nil { + return nil, err + } + + return fixture, nil +} + +func (f *testFixture) mount(root nodefs.Node) error { + f.mntDir = filepath.Join(f.dir, "mnt") + if err := os.Mkdir(f.mntDir, 0755); err != nil { + return err + } + + fuseOpts := &nodefs.Options{ + EntryTimeout: time.Hour, + NegativeTimeout: time.Hour, + AttrTimeout: time.Hour, + } + + var err error + f.server, _, err = nodefs.MountRoot(f.mntDir, root, fuseOpts) + if err != nil { + return err + } + + if fuseDebug { + f.server.SetDebug(true) + } + go f.server.Serve() + + f.root = root + return nil +}
diff --git a/fs/manifestfs.go b/fs/manifestfs.go deleted file mode 100644 index 018efcf..0000000 --- a/fs/manifestfs.go +++ /dev/null
@@ -1,274 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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 fs - -import ( - "encoding/json" - "fmt" - "log" - "path/filepath" - "strings" - - "github.com/google/slothfs/cache" - "github.com/google/slothfs/gitiles" - "github.com/google/slothfs/manifest" - "github.com/hanwen/go-fuse/fuse" - "github.com/hanwen/go-fuse/fuse/nodefs" -) - -type manifestFSRoot struct { - nodefs.Node - - service *gitiles.Service - - cache *cache.Cache - nodeCache *nodeCache - - // trees is Path => Tree map. - trees map[string]*gitiles.Tree - - options ManifestOptions - - // XML data for the manifest. - manifestXML []byte -} - -func (r *manifestFSRoot) Deletable() bool { return false } - -func (r *manifestFSRoot) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) { - return nil, fuse.ENODATA -} - -// NewManifestFS creates a Manifest FS root node. -func NewManifestFS(service *gitiles.Service, cache *cache.Cache, opts ManifestOptions) (nodefs.Node, error) { - xml, err := opts.Manifest.MarshalXML() - if err != nil { - return nil, err - } - root := &manifestFSRoot{ - Node: newDirNode(), - nodeCache: newNodeCache(), - cache: cache, - service: service, - options: opts, - manifestXML: xml, - } - - for _, p := range opts.Manifest.Project { - if _, err := parseID(p.Revision); err != nil { - return nil, fmt.Errorf("project %s revision %q does not parse: %v", p.Name, p.Revision, err) - } - } - - root.trees, err = fetchTreeMap(cache, service, opts.Manifest) - if err != nil { - return nil, err - } - return root, nil -} - -func (r *manifestFSRoot) OnMount(fsConn *nodefs.FileSystemConnector) { - if err := r.onMount(fsConn); err != nil { - log.Printf("onMount: %v", err) - for k := range r.Inode().Children() { - r.Inode().RmChild(k) - } - - r.Inode().NewChild("ERROR", false, newDataNode([]byte(err.Error()))) - } - - // Don't need the trees anymore. - r.trees = nil -} - -func (r *manifestFSRoot) onMount(fsConn *nodefs.FileSystemConnector) error { - var byDepth [][]string - for p := range r.trees { - d := len(strings.Split(p, "/")) - for len(byDepth) <= d { - byDepth = append(byDepth, nil) - } - - byDepth[d] = append(byDepth[d], p) - } - - clonablePaths := map[string]bool{} - revmap := map[string]*manifest.Project{} - for i, p := range r.options.Manifest.Project { - revmap[p.GetPath()] = &r.options.Manifest.Project[i] - - if p.CloneDepth == "" { - clonablePaths[p.GetPath()] = true - } - } - - // TODO(hanwen): use parallelism here. - - for _, ps := range byDepth { - for _, p := range ps { - dir, base := filepath.Split(p) - parent, left := fsConn.Node(r.Inode(), dir) - for _, c := range left { - ch := parent.NewChild(c, true, newDirNode()) - parent = ch - } - - clone, ok := clonablePaths[p] - if !ok { - for _, e := range r.options.RepoCloneOption { - if e.RE.FindString(p) != "" { - clone = e.Clone - break - } - } - } - - cloneURL := revmap[p].CloneURL - if !clone { - cloneURL = "" - } - - repoService := r.service.NewRepoService(revmap[p].Name) - - opts := GitilesRevisionOptions{ - Revision: revmap[p].Revision, - GitilesOptions: GitilesOptions{ - CloneURL: cloneURL, - CloneOption: r.options.FileCloneOption, - }, - } - - subRoot := NewGitilesRoot(r.cache, r.trees[p], repoService, opts) - subRoot.(*gitilesRoot).nodeCache = r.nodeCache - parent.NewChild(base, true, subRoot) - if err := subRoot.(*gitilesRoot).onMount(fsConn); err != nil { - return fmt.Errorf("onMount(%s): %v", p, err) - } - } - } - - // Do Linkfile, Copyfile after setting up the repos, so we - // have directories to attach the copy/link nodes to. - for _, p := range r.options.Manifest.Project { - for _, cp := range p.Copyfile { - srcNode, left := fsConn.Node(r.Inode(), filepath.Join(p.GetPath(), cp.Src)) - if len(left) > 0 { - return fmt.Errorf("Copyfile(%s): source %s does not exist", p.Name, cp.Src) - } - - dir, left := fsConn.Node(r.Inode(), cp.Dest) - switch len(left) { - case 0: - return fmt.Errorf("Copyfile(%s): dest %s already exists.", p.Name, cp.Dest) - case 1: - default: - return fmt.Errorf("Copyfile(%s): directory for dest %s does not exist.", p.Name, cp.Dest) - } - - dir.AddChild(left[0], srcNode) - } - - for _, lf := range p.Linkfile { - dir, left := fsConn.Node(r.Inode(), lf.Dest) - switch len(left) { - case 0: - return fmt.Errorf("Linkfile(%s): dest %s already exists.", p.Name, lf.Dest) - case 1: - default: - return fmt.Errorf("Linkfile(%s): directory for dest %s does not exist.", p.Name, lf.Dest) - } - - src := filepath.Join(p.GetPath(), lf.Src) - rel, err := filepath.Rel(filepath.Dir(lf.Dest), src) - if err != nil { - return err - } - - node := newLinkNode(filepath.Join(rel)) - dir.NewChild(left[0], false, node) - } - } - - metaNode := r.Inode().NewChild(".slothfs", true, newDirNode()) - metaNode.NewChild("manifest.xml", false, newDataNode(r.manifestXML)) - - var tree gitiles.Tree - treeContent, err := json.Marshal(tree) - if err != nil { - log.Panicf("json.Marshal: %v", err) - } - metaNode.NewChild("tree.json", false, newDataNode(treeContent)) - - return nil -} - -func fetchTreeMap(c *cache.Cache, service *gitiles.Service, mf *manifest.Manifest) (map[string]*gitiles.Tree, error) { - type resultT struct { - path string - resp *gitiles.Tree - err error - } - - // Fetch all the trees in parallel. - out := make(chan resultT, len(mf.Project)) - for _, p := range mf.Project { - go func(p manifest.Project) { - revID, err := parseID(p.Revision) - if err != nil { - out <- resultT{p.GetPath(), nil, err} - return - } - - tree, err := c.Tree.Get(revID) - cached := (err == nil && tree != nil) - if err != nil { - if repo := c.Git.OpenLocal(p.CloneURL); repo != nil { - tree, err = cache.GetTree(repo, revID) - } - } - - if err != nil { - repoService := service.NewRepoService(p.Name) - - tree, err = repoService.GetTree(p.Revision, "", true) - } - - if !cached && tree != nil && err == nil { - if err := c.Tree.Add(revID, tree); err != nil { - log.Printf("treeCache.Add: %v", err) - } - } - - out <- resultT{p.GetPath(), tree, err} - }(p) - } - - // drain goroutines - var result []resultT - for range mf.Project { - r := <-out - result = append(result, r) - } - - resmap := map[string]*gitiles.Tree{} - for _, r := range result { - if r.err != nil { - return nil, fmt.Errorf("Tree(%s): %v", r.path, r.err) - } - - resmap[r.path] = r.resp - } - return resmap, nil -}
diff --git a/fs/manifestfs_test.go b/fs/manifestfs_test.go deleted file mode 100644 index b17ddf9..0000000 --- a/fs/manifestfs_test.go +++ /dev/null
@@ -1,468 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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 fs - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "reflect" - "sort" - "strings" - "syscall" - "testing" - "time" - - "github.com/google/slothfs/cache" - "github.com/google/slothfs/gitiles" - "github.com/google/slothfs/manifest" - "github.com/hanwen/go-fuse/fuse" - "github.com/hanwen/go-fuse/fuse/nodefs" -) - -func newManifestTestFixture(mf *manifest.Manifest) (*testFixture, error) { - fix, err := newTestFixture() - if err != nil { - return nil, err - } - - opts := ManifestOptions{ - Manifest: mf, - } - - fs, err := NewManifestFS(fix.service, fix.cache, opts) - if err != nil { - return nil, err - } - if err := fix.mount(fs); err != nil { - return nil, err - } - - return fix, nil -} - -func TestManifestFSGitRepoSeedsTreeCache(t *testing.T) { - fix, err := newTestFixture() - if err != nil { - t.Fatal("newTestFixture", err) - } - defer fix.cleanup() - - // Add a git repo. - cmd := exec.Command("/bin/sh", "-c", - strings.Join([]string{ - "mkdir -p localhost/platform/build/kati.git", - "cd localhost/platform/build/kati.git", - "git init", - "touch file", - "git add file", - "git commit -m msg -a", - "git show --no-patch --pretty=format:'%H' HEAD | head", - }, " && ")) - cmd.Dir = filepath.Join(fix.dir, "cache", "git") - - headSHA1 := "" - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("create repo: %v, out: %s", err, string(out)) - } else { - lines := strings.Split(string(out), "\n") - headSHA1 = lines[len(lines)-1] - } - - p := testManifest.Project[0] - p.CloneURL = "file:///localhost/platform/build/kati" - p.Revision = headSHA1 - - mf := *testManifest - mf.Project = nil - mf.Project = append(mf.Project, p) - - opts := ManifestOptions{ - Manifest: &mf, - } - - fs, err := NewManifestFS(fix.service, fix.cache, opts) - if err != nil { - t.Fatal("NewManifestFS", err) - } - if err := fix.mount(fs); err != nil { - t.Fatal("mount", err) - } - - headID, err := parseID(headSHA1) - if err != nil { - t.Fatalf("parseID(%s): %v", headSHA1, err) - } - - tree, err := fix.cache.Tree.Get(headID) - if err != nil { - t.Fatalf("treeCache.Get(%s): %v", headSHA1, err) - } - - newInt := func(n int) *int { return &n } - tree.ID = "" - want := &gitiles.Tree{ - Entries: []gitiles.TreeEntry{ - { - Name: "file", - Mode: 0100644, - Type: "blob", - ID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - Size: newInt(0), - }, - }, - } - if !reflect.DeepEqual(tree, want) { - t.Errorf("got cached tree %v, want %v", tree, want) - } -} - -func TestManifestFSCloneOption(t *testing.T) { - mf := *testManifest - for i := range mf.Project { - mf.Project[i].CloneDepth = "1" - } - - fix, err := newManifestTestFixture(&mf) - if err != nil { - t.Fatalf("newManifestTestFixture: %v", err) - } - defer fix.cleanup() - - fs := fix.root.(*manifestFSRoot) - ch := fs.Inode() - for _, n := range []string{"build", "kati", "AUTHORS"} { - newCh := ch.GetChild(n) - if ch == nil { - t.Fatalf("node for %q not found. Have %s", n, ch.Children()) - } - ch = newCh - } - - giNode, ok := ch.Node().(*gitilesNode) - if !ok { - t.Fatalf("got node type %T, want *gitilesNode", ch.Node()) - } - - if giNode.clone { - t.Errorf("file had clone set.") - } -} - -func TestManifestFSTimestamps(t *testing.T) { - fix, err := newManifestTestFixture(testManifest) - if err != nil { - t.Fatal("newTestFixture", err) - } - defer fix.cleanup() - - var zeroFiles []string - if err := filepath.Walk(fix.mntDir, func(n string, fi os.FileInfo, err error) error { - if fi != nil && fi.ModTime().UnixNano() == 0 { - r, _ := filepath.Rel(fix.mntDir, n) - zeroFiles = append(zeroFiles, r) - } - return nil - }); err != nil { - t.Fatalf("Walk: %v", err) - } - if len(zeroFiles) > 0 { - sort.Strings(zeroFiles) - t.Errorf("found files with zero timestamps: %v", zeroFiles) - } -} - -func TestManifestFSBasic(t *testing.T) { - fix, err := newManifestTestFixture(testManifest) - if err != nil { - t.Fatal("newTestFixture", err) - } - defer fix.cleanup() - - fn := filepath.Join(fix.mntDir, "build", "kati", "AUTHORS") - fi, err := os.Lstat(fn) - if err != nil { - t.Fatalf("Lstat(%s): %v", fn, err) - } - if fi.Size() != 373 { - t.Errorf("got size %d want %d", fi.Size(), 373) - } - - contents, err := ioutil.ReadFile(fn) - if err != nil { - t.Fatalf("ReadFile(%s): %v", fn, err) - } - - want := testGitiles["/platform/build/kati/+show/ce34badf691d36e8048b63f89d1a86ee5fa4325c/AUTHORS?format=TEXT"] - if string(contents) != want { - t.Fatalf("got %q, want %q", contents, want) - } - - copyPath := filepath.Join(fix.mntDir, "build", "copydest") - if copyFI, err := os.Lstat(copyPath); err != nil { - t.Errorf("Lstat(%s): %v", copyPath, err) - } else { - copyStat := copyFI.Sys().(*syscall.Stat_t) - origStat := fi.Sys().(*syscall.Stat_t) - - if !reflect.DeepEqual(copyStat, origStat) { - t.Errorf("got stat %v, want %v", copyStat, origStat) - } - } - - linkPath := filepath.Join(fix.mntDir, "build", "linkdest") - if got, err := os.Readlink(linkPath); err != nil { - t.Errorf("Readlink(%s): %v", linkPath, err) - } else if want := "kati/AUTHORS"; got != want { - t.Errorf("Readlink(%s) = %q, want %q", linkPath, got, want) - } -} - -func TestManifestFSXMLFile(t *testing.T) { - fix, err := newManifestTestFixture(testManifest) - if err != nil { - t.Fatal("newTestFixture", err) - } - defer fix.cleanup() - - xmlPath := filepath.Join(fix.mntDir, ".slothfs", "manifest.xml") - fuseMF, err := manifest.ParseFile(xmlPath) - if err != nil { - t.Fatalf("ParseFile(%s): %v", xmlPath, err) - } - - if !reflect.DeepEqual(fuseMF, testManifest) { - t.Errorf("read back manifest %v, want %v", fuseMF, testManifest) - } -} - -type testFixture struct { - dir string - mntDir string - server *fuse.Server - cache *cache.Cache - testServer *testServer - service *gitiles.Service - root nodefs.Node -} - -func (f *testFixture) cleanup() { - if f.testServer != nil { - f.testServer.listener.Close() - } - if f.server != nil { - f.server.Unmount() - } - os.RemoveAll(f.dir) -} - -func newTestFixture() (*testFixture, error) { - d, err := ioutil.TempDir("", "slothfs") - if err != nil { - return nil, err - } - - fixture := &testFixture{dir: d} - - fixture.cache, err = cache.NewCache(filepath.Join(d, "/cache"), cache.Options{}) - if err != nil { - return nil, err - } - - fixture.testServer, err = newTestServer() - if err != nil { - return nil, err - } - - fixture.service, err = gitiles.NewService(gitiles.Options{ - Address: fmt.Sprintf("http://%s", fixture.testServer.addr), - }) - if err != nil { - return nil, err - } - - return fixture, nil -} - -func (f *testFixture) mount(root nodefs.Node) error { - f.mntDir = filepath.Join(f.dir, "mnt") - if err := os.Mkdir(f.mntDir, 0755); err != nil { - return err - } - - fuseOpts := &nodefs.Options{ - EntryTimeout: time.Hour, - NegativeTimeout: time.Hour, - AttrTimeout: time.Hour, - } - - var err error - f.server, _, err = nodefs.MountRoot(f.mntDir, root, fuseOpts) - if err != nil { - return err - } - - if fuseDebug { - f.server.SetDebug(true) - } - go f.server.Serve() - - f.root = root - return nil -} - -func TestMultiManifestFSBrokenXML(t *testing.T) { - fix, err := newTestFixture() - if err != nil { - t.Fatalf("newTestFixture: %v", err) - } - defer fix.cleanup() - - brokenXMLFile := filepath.Join(fix.dir, "broken.xml") - if err := ioutil.WriteFile(brokenXMLFile, []byte("I'm not XML."), 0644); err != nil { - t.Errorf("WriteFile(%s): %v", brokenXMLFile, err) - } - - opts := MultiManifestFSOptions{} - fs := NewMultiManifestFS(fix.service, fix.cache, opts) - - if err := fix.mount(fs); err != nil { - t.Fatalf("mount: %v", err) - } - - if err := os.Symlink(brokenXMLFile, filepath.Join(fix.mntDir, "config", "ws")); err == nil { - t.Fatalf("want error for broken XML file") - } -} - -func TestMultiManifestFSBasic(t *testing.T) { - fix, err := newTestFixture() - if err != nil { - t.Fatalf("newTestFixture: %v", err) - } - defer fix.cleanup() - - xmlFile := filepath.Join(fix.dir, "manifest.xml") - if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil { - t.Errorf("WriteFile(%s): %v", xmlFile, err) - } - - opts := MultiManifestFSOptions{} - fs := NewMultiManifestFS(fix.service, fix.cache, opts) - - if err := fix.mount(fs); err != nil { - t.Fatalf("mount: %v", err) - } - - wsDir := filepath.Join(fix.mntDir, "ws") - if fi, err := os.Lstat(wsDir); err == nil { - t.Fatalf("got %v, want non-existent workspace dir", fi) - } - - configName := filepath.Join(fix.mntDir, "config", "ws") - if err := os.Symlink(xmlFile, configName); err != nil { - t.Fatalf("Symlink(%s): %v", xmlFile, err) - } - - if _, err := os.Lstat(wsDir); err != nil { - t.Fatalf("Lstat(%s): %v", wsDir, err) - } - - if got, err := os.Readlink(configName); err != nil { - t.Fatalf("Readlink(%s): %v", configName, err) - } else if want := "../ws/.slothfs/manifest.xml"; got != want { - t.Errorf("got link %s, want %s", got, want) - } - - if _, err := manifest.ParseFile(configName); err != nil { - t.Fatalf("ParseFile(%s): %v", configName, err) - } - - fn := filepath.Join(wsDir, "build", "kati", "AUTHORS") - if fi, err := os.Lstat(fn); err != nil { - t.Fatalf("Lstat(%s): %v", fn, err) - } else if fi.Size() != 373 { - t.Errorf("got %d, want size 373", fi.Size()) - } - - if err := os.Remove(configName); err != nil { - t.Fatalf("Delete(%s): %v", configName, err) - } - - if fi, err := os.Lstat(wsDir); err == nil { - t.Errorf("Lstat(%s): got %v, want error", wsDir, fi) - } -} - -func TestMultiManifestFSManifestDir(t *testing.T) { - fix, err := newTestFixture() - if err != nil { - t.Fatalf("newTestFixture: %v", err) - } - defer fix.cleanup() - - mfDir := filepath.Join(fix.dir, "manifests") - if err := os.MkdirAll(mfDir, 0755); err != nil { - t.Fatalf("MkdirAll: %v", err) - } - - xmlFile := filepath.Join(mfDir, "ws") - if err := ioutil.WriteFile(xmlFile, []byte(testManifestXML), 0644); err != nil { - t.Errorf("WriteFile(%s): %v", xmlFile, err) - } - - opts := MultiManifestFSOptions{ - ManifestDir: mfDir, - } - fs := NewMultiManifestFS(fix.service, fix.cache, opts) - - if err := fix.mount(fs); err != nil { - t.Fatalf("mount: %v", err) - } - - wsDir := filepath.Join(fix.mntDir, "ws") - if _, err := os.Lstat(wsDir); err != nil { - t.Fatalf("Lstat(%s): %v", wsDir, err) - } - - if err := os.Remove(filepath.Join(fix.mntDir, "config", "ws")); err != nil { - t.Fatalf("Remove(config link): %v", err) - } - - if fi, err := os.Lstat(filepath.Join(mfDir, "ws")); err == nil { - t.Errorf("'ws' still in manifest dir: %v", fi) - } - - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile: %v", err) - } - if err := ioutil.WriteFile(f.Name(), []byte(testManifestXML), 0644); err != nil { - t.Errorf("WriteFile(%s): %v", xmlFile, err) - } - - configName := filepath.Join(fix.mntDir, "config", "ws2") - if err := os.Symlink(f.Name(), configName); err != nil { - t.Fatalf("Symlink(%s): %v", xmlFile, err) - } - - // XML file appears again. - xmlFile = filepath.Join(mfDir, "ws2") - if _, err := os.Stat(xmlFile); err != nil { - t.Errorf("Stat(%s): %v", xmlFile, err) - } -}
diff --git a/fs/multimanifestfs.go b/fs/multimanifestfs.go deleted file mode 100644 index f9fa084..0000000 --- a/fs/multimanifestfs.go +++ /dev/null
@@ -1,222 +0,0 @@ -// Copyright 2016 Google Inc. All rights reserved. -// -// 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 fs - -import ( - "io/ioutil" - "log" - "os" - "path/filepath" - "sync" - "syscall" - - "github.com/google/slothfs/cache" - "github.com/google/slothfs/gitiles" - "github.com/google/slothfs/manifest" - "github.com/hanwen/go-fuse/fuse" - "github.com/hanwen/go-fuse/fuse/nodefs" -) - -type multiManifestFSRoot struct { - nodefs.Node - nodeCache *nodeCache - cache *cache.Cache - fsConn *nodefs.FileSystemConnector - options MultiManifestFSOptions - gitiles *gitiles.Service -} - -func (r *multiManifestFSRoot) StatFs() *fuse.StatfsOut { - var s syscall.Statfs_t - err := syscall.Statfs(r.cache.Root(), &s) - if err == nil { - out := &fuse.StatfsOut{} - out.FromStatfsT(&s) - return out - } - return nil -} - -func (c *configNode) configureWorkspaces() error { - if c.root.options.ManifestDir == "" { - return nil - } - fs, err := filepath.Glob(filepath.Join(c.root.options.ManifestDir, "*")) - if err != nil || len(fs) == 0 { - return err - } - - log.Println("configuring workspaces...") - var wg sync.WaitGroup - wg.Add(len(fs)) - for _, f := range fs { - go func(n string) { - _, code := c.Symlink(filepath.Base(n), n, nil) - log.Printf("manifest %s: %v", n, code) - wg.Done() - }(f) - } - wg.Wait() - - return nil -} - -func (r *multiManifestFSRoot) OnMount(fsConn *nodefs.FileSystemConnector) { - r.fsConn = fsConn - - cfg := &configNode{ - Node: nodefs.NewDefaultNode(), - root: r, - } - r.Inode().NewChild("config", true, cfg) - - if err := cfg.configureWorkspaces(); err != nil { - log.Printf("configureWorkspaces: %v", err) - } -} - -func (c *configNode) Deletable() bool { return false } - -func NewMultiManifestFS(service *gitiles.Service, c *cache.Cache, options MultiManifestFSOptions) *multiManifestFSRoot { - r := &multiManifestFSRoot{ - Node: nodefs.NewDefaultNode(), - nodeCache: newNodeCache(), - cache: c, - options: options, - gitiles: service, - } - return r -} - -func (r *multiManifestFSRoot) Deletable() bool { return false } - -func (r *multiManifestFSRoot) GetXAttr(attribute string, context *fuse.Context) (data []byte, code fuse.Status) { - return nil, fuse.ENODATA -} - -type configNode struct { - nodefs.Node - root *multiManifestFSRoot -} - -type configEntryNode struct { - nodefs.Node - link []byte -} - -func (c *configEntryNode) GetAttr(out *fuse.Attr, f nodefs.File, ctx *fuse.Context) fuse.Status { - out.Mode = fuse.S_IFLNK - return fuse.OK -} - -func (c *configEntryNode) Readlink(ctx *fuse.Context) ([]byte, fuse.Status) { - return c.link, fuse.OK -} - -func (c *configEntryNode) Deletable() bool { return false } - -func (c *configNode) Unlink(name string, ctx *fuse.Context) fuse.Status { - child := c.root.Inode().RmChild(name) - if child == nil { - return fuse.ENOENT - } - - // Notify the kernel this part of the tree disappeared. - c.root.fsConn.DeleteNotify(c.root.Inode(), child, name) - - c.Inode().RmChild(name) - - // No need to notify for the removed symlink. Since we're in - // the Unlink method, will VFS already knows about the - // deletion once we return OK. - - if dir := c.root.options.ManifestDir; dir != "" { - os.Remove(filepath.Join(dir, name)) - } - - return fuse.OK -} - -func (c *configNode) Symlink(name, content string, ctx *fuse.Context) (*nodefs.Inode, fuse.Status) { - mfBytes, err := ioutil.ReadFile(content) - if err != nil { - return nil, fuse.ToStatus(err) - } - - mf, err := manifest.Parse(mfBytes) - if err != nil { - log.Printf("Parse(%s): %v", content, err) - return nil, fuse.EINVAL - } - - options := ManifestOptions{ - Manifest: mf, - RepoCloneOption: c.root.options.RepoCloneOption, - FileCloneOption: c.root.options.FileCloneOption, - } - - fs, err := NewManifestFS(c.root.gitiles, c.root.cache, options) - if err != nil { - log.Printf("NewManifestFS(%s): %v", string(content), err) - return nil, fuse.EIO - } - fs.(*manifestFSRoot).nodeCache = c.root.nodeCache - - child := c.root.Inode().NewChild(name, true, fs) - if child == nil { - // TODO(hanwen): can this ever happen? - return nil, fuse.EINVAL - } - - config := c.Inode().NewChild(name, false, &configEntryNode{ - Node: nodefs.NewDefaultNode(), - // This is sneaky, but it appears to work. - link: []byte(filepath.Join("..", name, ".slothfs", "manifest.xml")), - }) - - if err := fs.(*manifestFSRoot).onMount(c.root.fsConn); err != nil { - log.Printf("onMount(%s): %v", name, err) - for k := range child.Children() { - child.RmChild(k) - } - - child.NewChild("ERROR", false, &dataNode{nodefs.NewDefaultNode(), []byte(err.Error())}) - } else { - if dir := c.root.options.ManifestDir; dir != "" { - for { - f, err := ioutil.TempFile(dir, "") - if err != nil { - break - } - - _, err = f.Write(mfBytes) - if err != nil { - break - } - - if err := f.Close(); err != nil { - break - } - - os.Rename(f.Name(), filepath.Join(dir, name)) - break - } - } - } - - c.root.fsConn.EntryNotify(c.root.Inode(), name) - - return config, fuse.OK -}