Commit 54cb69c5 authored by Aaron Jacobs's avatar Aaron Jacobs

Added support for mounting a file system in read-only mode.

For GoogleCloudPlatform/gcsfuse#48.
parents cb273d01 abdc5138
...@@ -65,6 +65,11 @@ type MountConfig struct { ...@@ -65,6 +65,11 @@ type MountConfig struct {
// should inherit. If nil, context.Background() will be used. // should inherit. If nil, context.Background() will be used.
OpContext context.Context OpContext context.Context
// Mount the file system in read-only mode. File modes will appear as normal,
// but opening a file for writing and metadata operations like chmod,
// chtimes, etc. will fail.
ReadOnly bool
// OS X only. // OS X only.
// //
// Normally on OS X we mount with the novncache option // Normally on OS X we mount with the novncache option
...@@ -89,6 +94,11 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) { ...@@ -89,6 +94,11 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
// InodeAttributes.Mode. // InodeAttributes.Mode.
opts = append(opts, bazilfuse.SetOption("default_permissions", "")) opts = append(opts, bazilfuse.SetOption("default_permissions", ""))
// Read only?
if c.ReadOnly {
opts = append(opts, bazilfuse.ReadOnly())
}
// OS X: set novncache when appropriate. // OS X: set novncache when appropriate.
if isDarwin && !c.EnableVnodeCaching { if isDarwin && !c.EnableVnodeCaching {
opts = append(opts, bazilfuse.SetOption("novncache", "")) opts = append(opts, bazilfuse.SetOption("novncache", ""))
......
...@@ -259,8 +259,75 @@ func (fs *flushFS) OpenDir( ...@@ -259,8 +259,75 @@ func (fs *flushFS) OpenDir(
defer fs.mu.Unlock() defer fs.mu.Unlock()
// Sanity check. // Sanity check.
if op.Inode != barID { switch op.Inode {
err = fuse.ENOSYS case fuseops.RootInodeID:
case barID:
default:
err = fuse.ENOENT
return
}
return
}
func (fs *flushFS) ReadDir(
op *fuseops.ReadDirOp) {
var err error
defer fuseutil.RespondToOp(op, &err)
fs.mu.Lock()
defer fs.mu.Unlock()
// Create the appropriate listing.
var dirents []fuseutil.Dirent
switch op.Inode {
case fuseops.RootInodeID:
dirents = []fuseutil.Dirent{
fuseutil.Dirent{
Offset: 1,
Inode: fooID,
Name: "foo",
Type: fuseutil.DT_File,
},
fuseutil.Dirent{
Offset: 2,
Inode: barID,
Name: "bar",
Type: fuseutil.DT_Directory,
},
}
case barID:
default:
err = fmt.Errorf("Unexpected inode: %v", op.Inode)
return
}
// If the offset is for the end of the listing, we're done. Otherwise we
// expect it to be for the start.
switch op.Offset {
case fuseops.DirOffset(len(dirents)):
return
case 0:
default:
err = fmt.Errorf("Unexpected offset: %v", op.Offset)
return
}
// Fill in the listing.
for _, de := range dirents {
op.Data = fuseutil.AppendDirent(op.Data, de)
}
// We don't support doing this in anything more than one shot.
if len(op.Data) > op.Size {
err = fmt.Errorf("Couldn't fit listing in %v bytes", op.Size)
return return
} }
......
...@@ -19,17 +19,20 @@ import ( ...@@ -19,17 +19,20 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path" "path"
"runtime" "runtime"
"syscall" "syscall"
"testing" "testing"
"time"
"unsafe" "unsafe"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/jacobsa/bazilfuse" "github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse/fsutil" "github.com/jacobsa/fuse/fsutil"
"github.com/jacobsa/fuse/fusetesting"
"github.com/jacobsa/fuse/samples" "github.com/jacobsa/fuse/samples"
. "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglematchers"
. "github.com/jacobsa/ogletest" . "github.com/jacobsa/ogletest"
...@@ -56,7 +59,8 @@ type flushFSTest struct { ...@@ -56,7 +59,8 @@ type flushFSTest struct {
func (t *flushFSTest) setUp( func (t *flushFSTest) setUp(
ti *TestInfo, ti *TestInfo,
flushErr bazilfuse.Errno, flushErr bazilfuse.Errno,
fsyncErr bazilfuse.Errno) { fsyncErr bazilfuse.Errno,
readOnly bool) {
var err error var err error
// Set up files to receive flush and fsync reports. // Set up files to receive flush and fsync reports.
...@@ -76,6 +80,10 @@ func (t *flushFSTest) setUp( ...@@ -76,6 +80,10 @@ func (t *flushFSTest) setUp(
fmt.Sprintf("%d", int(fsyncErr)), fmt.Sprintf("%d", int(fsyncErr)),
} }
if readOnly {
t.MountFlags = append(t.MountFlags, "--read_only")
}
t.MountFiles = map[string]*os.File{ t.MountFiles = map[string]*os.File{
"flushfs.flushes_file": t.flushes, "flushfs.flushes_file": t.flushes,
"flushfs.fsyncs_file": t.fsyncs, "flushfs.fsyncs_file": t.fsyncs,
...@@ -207,7 +215,7 @@ func init() { RegisterTestSuite(&NoErrorsTest{}) } ...@@ -207,7 +215,7 @@ func init() { RegisterTestSuite(&NoErrorsTest{}) }
func (t *NoErrorsTest) SetUp(ti *TestInfo) { func (t *NoErrorsTest) SetUp(ti *TestInfo) {
const noErr = 0 const noErr = 0
t.flushFSTest.setUp(ti, noErr, noErr) t.flushFSTest.setUp(ti, noErr, noErr, false)
} }
func (t *NoErrorsTest) Close_ReadWrite() { func (t *NoErrorsTest) Close_ReadWrite() {
...@@ -802,7 +810,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) } ...@@ -802,7 +810,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) }
func (t *FlushErrorTest) SetUp(ti *TestInfo) { func (t *FlushErrorTest) SetUp(ti *TestInfo) {
const noErr = 0 const noErr = 0
t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr) t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr, false)
} }
func (t *FlushErrorTest) Close() { func (t *FlushErrorTest) Close() {
...@@ -871,7 +879,7 @@ func (t *FlushErrorTest) Dup2() { ...@@ -871,7 +879,7 @@ func (t *FlushErrorTest) Dup2() {
} }
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Fsync error // Fsync error
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
type FsyncErrorTest struct { type FsyncErrorTest struct {
...@@ -882,7 +890,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) } ...@@ -882,7 +890,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) }
func (t *FsyncErrorTest) SetUp(ti *TestInfo) { func (t *FsyncErrorTest) SetUp(ti *TestInfo) {
const noErr = 0 const noErr = 0
t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT) t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT, false)
} }
func (t *FsyncErrorTest) Fsync() { func (t *FsyncErrorTest) Fsync() {
...@@ -943,3 +951,99 @@ func (t *FsyncErrorTest) Msync() { ...@@ -943,3 +951,99 @@ func (t *FsyncErrorTest) Msync() {
err = syscall.Munmap(data) err = syscall.Munmap(data)
AssertEq(nil, err) AssertEq(nil, err)
} }
////////////////////////////////////////////////////////////////////////
// Read-only mount
////////////////////////////////////////////////////////////////////////
type ReadOnlyTest struct {
flushFSTest
}
func init() { RegisterTestSuite(&ReadOnlyTest{}) }
func (t *ReadOnlyTest) SetUp(ti *TestInfo) {
const noErr = 0
t.flushFSTest.setUp(ti, noErr, noErr, true)
}
func (t *ReadOnlyTest) ReadRoot() {
var fi os.FileInfo
// Read.
entries, err := fusetesting.ReadDirPicky(t.Dir)
AssertEq(nil, err)
AssertEq(2, len(entries))
// bar
fi = entries[0]
ExpectEq("bar", fi.Name())
ExpectEq(os.FileMode(0777)|os.ModeDir, fi.Mode())
// foo
fi = entries[1]
ExpectEq("foo", fi.Name())
ExpectEq(os.FileMode(0777), fi.Mode())
}
func (t *ReadOnlyTest) StatFiles() {
var fi os.FileInfo
var err error
// bar
fi, err = os.Stat(path.Join(t.Dir, "bar"))
AssertEq(nil, err)
ExpectEq("bar", fi.Name())
ExpectEq(os.FileMode(0777)|os.ModeDir, fi.Mode())
// foo
fi, err = os.Stat(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
ExpectEq("foo", fi.Name())
ExpectEq(os.FileMode(0777), fi.Mode())
}
func (t *ReadOnlyTest) ReadFile() {
_, err := ioutil.ReadFile(path.Join(t.Dir, "foo"))
ExpectEq(nil, err)
}
func (t *ReadOnlyTest) ReadDir() {
_, err := fusetesting.ReadDirPicky(path.Join(t.Dir, "bar"))
ExpectEq(nil, err)
}
func (t *ReadOnlyTest) CreateFile() {
err := ioutil.WriteFile(path.Join(t.Dir, "blah"), []byte{}, 0400)
ExpectThat(err, Error(HasSubstr("read-only")))
}
func (t *ReadOnlyTest) Mkdir() {
err := os.Mkdir(path.Join(t.Dir, "blah"), 0700)
ExpectThat(err, Error(HasSubstr("read-only")))
}
func (t *ReadOnlyTest) OpenForWrite() {
modes := []int{
os.O_WRONLY,
os.O_RDWR,
}
for _, mode := range modes {
f, err := os.OpenFile(path.Join(t.Dir, "foo"), mode, 0700)
f.Close()
ExpectThat(err, Error(HasSubstr("read-only")), "mode: %v", mode)
}
}
func (t *ReadOnlyTest) Chtimes() {
err := os.Chtimes(path.Join(t.Dir, "foo"), time.Now(), time.Now())
ExpectThat(err, Error(MatchesRegexp("read-only|not permitted")))
}
func (t *ReadOnlyTest) Chmod() {
err := os.Chmod(path.Join(t.Dir, "foo"), 0700)
ExpectThat(err, Error(HasSubstr("read-only")))
}
...@@ -39,6 +39,8 @@ var fFsyncsFile = flag.Uint64("flushfs.fsyncs_file", 0, "") ...@@ -39,6 +39,8 @@ var fFsyncsFile = flag.Uint64("flushfs.fsyncs_file", 0, "")
var fFlushError = flag.Int("flushfs.flush_error", 0, "") var fFlushError = flag.Int("flushfs.flush_error", 0, "")
var fFsyncError = flag.Int("flushfs.fsync_error", 0, "") var fFsyncError = flag.Int("flushfs.fsync_error", 0, "")
var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.")
func makeFlushFS() (server fuse.Server, err error) { func makeFlushFS() (server fuse.Server, err error) {
// Check the flags. // Check the flags.
if *fFlushesFile == 0 || *fFsyncsFile == 0 { if *fFlushesFile == 0 || *fFsyncsFile == 0 {
...@@ -134,7 +136,11 @@ func main() { ...@@ -134,7 +136,11 @@ func main() {
log.Fatalf("You must set --mount_point.") log.Fatalf("You must set --mount_point.")
} }
mfs, err := fuse.Mount(*fMountPoint, server, &fuse.MountConfig{}) cfg := &fuse.MountConfig{
ReadOnly: *fReadOnly,
}
mfs, err := fuse.Mount(*fMountPoint, server, cfg)
if err != nil { if err != nil {
log.Fatalf("Mount: %v", err) log.Fatalf("Mount: %v", err)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment