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 {
// should inherit. If nil, context.Background() will be used.
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.
//
// Normally on OS X we mount with the novncache option
......@@ -89,6 +94,11 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
// InodeAttributes.Mode.
opts = append(opts, bazilfuse.SetOption("default_permissions", ""))
// Read only?
if c.ReadOnly {
opts = append(opts, bazilfuse.ReadOnly())
}
// OS X: set novncache when appropriate.
if isDarwin && !c.EnableVnodeCaching {
opts = append(opts, bazilfuse.SetOption("novncache", ""))
......
......@@ -259,8 +259,75 @@ func (fs *flushFS) OpenDir(
defer fs.mu.Unlock()
// Sanity check.
if op.Inode != barID {
err = fuse.ENOSYS
switch op.Inode {
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
}
......
......@@ -19,17 +19,20 @@ import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"runtime"
"syscall"
"testing"
"time"
"unsafe"
"golang.org/x/sys/unix"
"github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse/fsutil"
"github.com/jacobsa/fuse/fusetesting"
"github.com/jacobsa/fuse/samples"
. "github.com/jacobsa/oglematchers"
. "github.com/jacobsa/ogletest"
......@@ -56,7 +59,8 @@ type flushFSTest struct {
func (t *flushFSTest) setUp(
ti *TestInfo,
flushErr bazilfuse.Errno,
fsyncErr bazilfuse.Errno) {
fsyncErr bazilfuse.Errno,
readOnly bool) {
var err error
// Set up files to receive flush and fsync reports.
......@@ -76,6 +80,10 @@ func (t *flushFSTest) setUp(
fmt.Sprintf("%d", int(fsyncErr)),
}
if readOnly {
t.MountFlags = append(t.MountFlags, "--read_only")
}
t.MountFiles = map[string]*os.File{
"flushfs.flushes_file": t.flushes,
"flushfs.fsyncs_file": t.fsyncs,
......@@ -207,7 +215,7 @@ func init() { RegisterTestSuite(&NoErrorsTest{}) }
func (t *NoErrorsTest) SetUp(ti *TestInfo) {
const noErr = 0
t.flushFSTest.setUp(ti, noErr, noErr)
t.flushFSTest.setUp(ti, noErr, noErr, false)
}
func (t *NoErrorsTest) Close_ReadWrite() {
......@@ -802,7 +810,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) }
func (t *FlushErrorTest) SetUp(ti *TestInfo) {
const noErr = 0
t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr)
t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr, false)
}
func (t *FlushErrorTest) Close() {
......@@ -871,7 +879,7 @@ func (t *FlushErrorTest) Dup2() {
}
////////////////////////////////////////////////////////////////////////
// Fsync error
// Fsync error
////////////////////////////////////////////////////////////////////////
type FsyncErrorTest struct {
......@@ -882,7 +890,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) }
func (t *FsyncErrorTest) SetUp(ti *TestInfo) {
const noErr = 0
t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT)
t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT, false)
}
func (t *FsyncErrorTest) Fsync() {
......@@ -943,3 +951,99 @@ func (t *FsyncErrorTest) Msync() {
err = syscall.Munmap(data)
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, "")
var fFlushError = flag.Int("flushfs.flush_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) {
// Check the flags.
if *fFlushesFile == 0 || *fFsyncsFile == 0 {
......@@ -134,7 +136,11 @@ func main() {
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 {
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