Return open file as FOPEN_KEEP_CACHE.

This avoid having to read contents for each file through the FUSE
connection over and over again.

Change-Id: I5f8606516a1fda6ca66e5baf6723b964b500082b
diff --git a/fs/gitilesfs.go b/fs/gitilesfs.go
index 8400832..1c0ee46 100644
--- a/fs/gitilesfs.go
+++ b/fs/gitilesfs.go
@@ -22,6 +22,7 @@
 	"os"
 	"path/filepath"
 	"sync"
+	"sync/atomic"
 	"syscall"
 	"time"
 
@@ -101,6 +102,9 @@
 	// The timestamp is writable; protect it with a mutex.
 	mtimeMu sync.Mutex
 	mtime   time.Time
+
+	// This is to verify that FOPEN_KEEP_CACHE is working as expected.
+	readCount uint32
 }
 
 func (n *gitilesNode) Deletable() bool {
@@ -155,10 +159,18 @@
 	if err != nil {
 		return nil, fuse.ToStatus(err)
 	}
-	return nodefs.NewLoopbackFile(f), fuse.OK
+
+	return &nodefs.WithFlags{
+		File:      nodefs.NewLoopbackFile(f),
+		FuseFlags: fuse.FOPEN_KEEP_CACHE,
+	}, fuse.OK
 }
 
 func (n *gitilesNode) Read(file nodefs.File, dest []byte, off int64, context *fuse.Context) (fuse.ReadResult, fuse.Status) {
+	if off == 0 {
+		atomic.AddUint32(&n.readCount, 1)
+	}
+
 	if n.root.handleLessIO {
 		return n.handleLessRead(file, dest, off, context)
 	}
diff --git a/fs/gitilesfs_test.go b/fs/gitilesfs_test.go
index ffb0961..f29c5e9 100644
--- a/fs/gitilesfs_test.go
+++ b/fs/gitilesfs_test.go
@@ -27,6 +27,7 @@
 	"regexp"
 	"strings"
 	"sync"
+	"sync/atomic"
 	"syscall"
 	"testing"
 	"time"
@@ -428,6 +429,49 @@
 	}
 }
 
+func TestGitilesFSCachedRead(t *testing.T) {
+	fix, err := newTestFixture()
+	if err != nil {
+		t.Fatal("newTestFixture", err)
+	}
+	defer fix.cleanup()
+
+	repoService := fix.service.NewRepoService("platform/build/kati")
+	treeResp, err := repoService.GetTree("ce34badf691d36e8048b63f89d1a86ee5fa4325c", "", true)
+	if err != nil {
+		t.Fatal("Tree:", err)
+	}
+
+	options := GitilesOptions{
+		Revision: "ce34badf691d36e8048b63f89d1a86ee5fa4325c",
+	}
+
+	fs := NewGitilesRoot(fix.cache, treeResp, repoService, options)
+	if err := fix.mount(fs); err != nil {
+		t.Fatal("mount", err)
+	}
+
+	for i := 0; i < 2; i++ {
+		if _, err := ioutil.ReadFile(filepath.Join(fix.mntDir, "AUTHORS")); err != nil {
+			t.Fatalf("ReadFile %d: %v", i, err)
+		}
+	}
+
+	ch := fs.Inode().GetChild("AUTHORS")
+	if ch == nil {
+		t.Fatalf("node for AUTHORS not found")
+	}
+
+	giNode, ok := ch.Node().(*gitilesNode)
+	if !ok {
+		t.Fatalf("got node type %T, want *gitilesNode", ch.Node())
+	}
+
+	if c := atomic.LoadUint32(&giNode.readCount); c != 1 {
+		t.Errorf("inode was read %d times, want 1.", c)
+	}
+}
+
 func TestGitilesFSTimeStamps(t *testing.T) {
 	fix, err := newTestFixture()
 	if err != nil {