Add Linkfile and Copyfile support to Manifest FS.

Change-Id: Iee24760742ffb3b5546c9b400c9fc5d314528733
diff --git a/fs/gitilesfs.go b/fs/gitilesfs.go
index c89554e..37a3cc1 100644
--- a/fs/gitilesfs.go
+++ b/fs/gitilesfs.go
@@ -51,6 +51,28 @@
 	lazyRepo *cache.LazyRepo
 }
 
+type linkNode struct {
+	nodefs.Node
+	linkTarget []byte
+}
+
+func newLinkNode(target string) *linkNode {
+	return &linkNode{
+		Node:       nodefs.NewDefaultNode(),
+		linkTarget: []byte(target),
+	}
+}
+
+func (n *linkNode) GetAttr(out *fuse.Attr, file nodefs.File, context *fuse.Context) (code fuse.Status) {
+	out.Size = uint64(len(n.linkTarget))
+	out.Mode = fuse.S_IFLNK
+	return fuse.OK
+}
+
+func (n *linkNode) Readlink(c *fuse.Context) ([]byte, fuse.Status) {
+	return n.linkTarget, fuse.OK
+}
+
 // gitilesNode represents a read-only blob in the FUSE filesystem.
 type gitilesNode struct {
 	nodefs.Node
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go
index 4fe8124..ff22bd1 100644
--- a/fs/gitilesfs_test.go
+++ b/fs/gitilesfs_test.go
@@ -23,8 +23,10 @@
 	"net/http"
 	"os"
 	"path/filepath"
+	"reflect"
 	"regexp"
 	"strings"
+	"syscall"
 	"testing"
 
 	"github.com/google/gitfs/cache"
@@ -60,7 +62,10 @@
   <default revision="master"
            remote="aosp"
            sync-j="4" />
-  <project path="build/kati" name="platform/build/kati" groups="pdk,tradefed" revision="ce34badf691d36e8048b63f89d1a86ee5fa4325c" />
+  <project path="build/kati" name="platform/build/kati" groups="pdk,tradefed" revision="ce34badf691d36e8048b63f89d1a86ee5fa4325c">
+    <copyfile dest="build/copydest" src="AUTHORS" />
+    <linkfile dest="build/linkdest" src="AUTHORS" />
+  </project>
 </manifest>`
 
 var testGitiles = map[string]string{
@@ -319,6 +324,25 @@
 	if string(contents) != want {
 		t.Fatalf("got %q, want %q", contents, want)
 	}
+
+	copyPath := filepath.Join(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(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)
+	}
 }
 
 // TODO(hanwen): factor common setup into a testFixture struct.
@@ -358,7 +382,7 @@
 	}
 
 	opts := MultiFSOptions{}
-	fs := NewMultiFS(cache, service, opts)
+	fs := NewMultiFS(service, cache, opts)
 
 	mntDir := d + "/mnt"
 	if err := os.Mkdir(mntDir, 0755); err != nil {
diff --git a/fs/manifestfs.go b/fs/manifestfs.go
index 8c241d5..2e72a01 100644
--- a/fs/manifestfs.go
+++ b/fs/manifestfs.go
@@ -129,6 +129,48 @@
 		}
 	}
 
+	// Do Linkfile, Copyfile after setting up the repos, so we
+	// have directories to attach the copy/link nodes to.
+	for _, p := range fs.options.Manifest.Project {
+		for _, cp := range p.Copyfile {
+			srcNode, left := fsConn.Node(fs.Inode(), filepath.Join(p.Path, cp.Src))
+			if len(left) > 0 {
+				return fmt.Errorf("Copyfile(%s): source %s does not exist", p.Name, cp.Src)
+			}
+
+			dir, left := fsConn.Node(fs.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(fs.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.Path, 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)
+		}
+	}
+
 	return nil
 }
 
@@ -187,5 +229,3 @@
 	}
 	return resmap, nil
 }
-
-// TODO(hanwen): support LinkFile and CopyFile directives.