Implemented file creation.
commit
d307babe25
159
file_system.go
159
file_system.go
|
@ -69,10 +69,32 @@ type FileSystem interface {
|
||||||
|
|
||||||
// Create a directory inode as a child of an existing directory inode. The
|
// Create a directory inode as a child of an existing directory inode. The
|
||||||
// kernel sends this in response to a mkdir(2) call.
|
// kernel sends this in response to a mkdir(2) call.
|
||||||
|
//
|
||||||
|
// The kernel appears to verify the name doesn't already exist (mkdir calls
|
||||||
|
// mkdirat calls user_path_create calls filename_create, which verifies:
|
||||||
|
// http://goo.gl/FZpLu5). But volatile file systems and paranoid non-volatile
|
||||||
|
// file systems should check for the reasons described below on CreateFile.
|
||||||
MkDir(
|
MkDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *MkDirRequest) (*MkDirResponse, error)
|
req *MkDirRequest) (*MkDirResponse, error)
|
||||||
|
|
||||||
|
// Create a file inode and open it.
|
||||||
|
//
|
||||||
|
// The kernel calls this method when the user asks to open a file with the
|
||||||
|
// O_CREAT flag and the kernel has observed that the file doesn't exist. (See
|
||||||
|
// for example lookup_open, http://goo.gl/PlqE9d).
|
||||||
|
//
|
||||||
|
// However it's impossible to tell for sure that all kernels make this check
|
||||||
|
// in all cases and the official fuse documentation is less than encouraging
|
||||||
|
// (" the file does not exist, first create it with the specified mode, and
|
||||||
|
// then open it"). Therefore file systems would be smart to be paranoid and
|
||||||
|
// check themselves, returning EEXIST when the file already exists. This of
|
||||||
|
// course particularly applies to file systems that are volatile from the
|
||||||
|
// kernel's point of view.
|
||||||
|
CreateFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *CreateFileRequest) (*CreateFileResponse, error)
|
||||||
|
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// Inode destruction
|
// Inode destruction
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
|
@ -128,11 +150,45 @@ type FileSystem interface {
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *OpenFileRequest) (*OpenFileResponse, error)
|
req *OpenFileRequest) (*OpenFileResponse, error)
|
||||||
|
|
||||||
// Read data from a file previously opened with OpenFile.
|
// Read data from a file previously opened with CreateFile or OpenFile.
|
||||||
|
//
|
||||||
|
// Note that this method is not called for every call to read(2) by the end
|
||||||
|
// user; some reads may be served by the page cache. See notes on Write for
|
||||||
|
// more.
|
||||||
ReadFile(
|
ReadFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *ReadFileRequest) (*ReadFileResponse, error)
|
req *ReadFileRequest) (*ReadFileResponse, error)
|
||||||
|
|
||||||
|
// Write data to a file previously opened with CreateFile or OpenFile.
|
||||||
|
//
|
||||||
|
// When the user writes data using write(2), the write goes into the page
|
||||||
|
// cache and the page is marked dirty. Later the kernel may write back the
|
||||||
|
// page via the FUSE VFS layer, causing this method to be called:
|
||||||
|
//
|
||||||
|
// * The kernel calls address_space_operations::writepage when a dirty page
|
||||||
|
// needs to be written to backing store (see vfs.txt). Fuse sets this to
|
||||||
|
// fuse_writepage (see file.c).
|
||||||
|
//
|
||||||
|
// * fuse_writepage calls fuse_writepage_locked.
|
||||||
|
//
|
||||||
|
// * fuse_writepage_locked makes a write request to the userspace server.
|
||||||
|
//
|
||||||
|
// Note that writes *will* be received before a call to Flush when closing
|
||||||
|
// the file descriptor to which they were written:
|
||||||
|
//
|
||||||
|
// * fuse_flush calls write_inode_now, which appears to start a writeback
|
||||||
|
// in the background (it talks about a "flusher thread").
|
||||||
|
//
|
||||||
|
// * fuse_flush then calls fuse_sync_writes, which "[waits] for all pending
|
||||||
|
// writepages on the inode to finish".
|
||||||
|
//
|
||||||
|
// * Only then does fuse_flush finally send the flush request.
|
||||||
|
//
|
||||||
|
// TODO(jacobsa): Add links for all of the references above.
|
||||||
|
WriteFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *WriteFileRequest) (*WriteFileResponse, error)
|
||||||
|
|
||||||
// Release a previously-minted file handle. The kernel calls this when there
|
// Release a previously-minted file handle. The kernel calls this when there
|
||||||
// are no more references to an open file: all file descriptors are closed
|
// are no more references to an open file: all file descriptors are closed
|
||||||
// and all memory mappings are unmapped.
|
// and all memory mappings are unmapped.
|
||||||
|
@ -235,8 +291,8 @@ type RequestHeader struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Information about a child inode within its parent directory. Shared by the
|
// Information about a child inode within its parent directory. Shared by the
|
||||||
// responses for LookUpInode, MkDir, etc. Consumed by the kernel in order to
|
// responses for LookUpInode, MkDir, CreateFile, etc. Consumed by the kernel in
|
||||||
// set up a dcache entry.
|
// order to set up a dcache entry.
|
||||||
type ChildInodeEntry struct {
|
type ChildInodeEntry struct {
|
||||||
// The ID of the child inode. The file system must ensure that the returned
|
// The ID of the child inode. The file system must ensure that the returned
|
||||||
// inode ID remains valid until a later call to ForgetInode.
|
// inode ID remains valid until a later call to ForgetInode.
|
||||||
|
@ -247,6 +303,16 @@ type ChildInodeEntry struct {
|
||||||
Generation GenerationNumber
|
Generation GenerationNumber
|
||||||
|
|
||||||
// Current attributes for the child inode.
|
// Current attributes for the child inode.
|
||||||
|
//
|
||||||
|
// When creating a new inode, the file system is responsible for initializing
|
||||||
|
// and recording (where supported) attributes like time information,
|
||||||
|
// ownership information, etc.
|
||||||
|
//
|
||||||
|
// Ownership information in particular must be set to something reasonable or
|
||||||
|
// by default root will own everything and unprivileged users won't be able
|
||||||
|
// to do anything useful. In traditional file systems in the kernel, the
|
||||||
|
// function inode_init_owner (http://goo.gl/5qavg8) contains the
|
||||||
|
// standards-compliant logic for this.
|
||||||
Attributes InodeAttributes
|
Attributes InodeAttributes
|
||||||
|
|
||||||
// The FUSE VFS layer in the kernel maintains a cache of file attributes,
|
// The FUSE VFS layer in the kernel maintains a cache of file attributes,
|
||||||
|
@ -377,18 +443,42 @@ type MkDirRequest struct {
|
||||||
|
|
||||||
type MkDirResponse struct {
|
type MkDirResponse struct {
|
||||||
// Information about the inode that was created.
|
// Information about the inode that was created.
|
||||||
//
|
|
||||||
// The file system is responsible for initializing and recording (where
|
|
||||||
// supported) attributes like time information, ownership information, etc.
|
|
||||||
//
|
|
||||||
// Ownership information in particular must be set to something reasonable or
|
|
||||||
// by default root will own everything and unprivileged users won't be able
|
|
||||||
// to do anything useful. In traditional file systems in the kernel, the
|
|
||||||
// function inode_init_owner (http://goo.gl/5qavg8) contains the
|
|
||||||
// standards-compliant logic for this.
|
|
||||||
Entry ChildInodeEntry
|
Entry ChildInodeEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateFileRequest struct {
|
||||||
|
Header RequestHeader
|
||||||
|
|
||||||
|
// The ID of parent directory inode within which to create the child file.
|
||||||
|
Parent InodeID
|
||||||
|
|
||||||
|
// The name of the child to create, and the mode with which to create it.
|
||||||
|
Name string
|
||||||
|
Mode os.FileMode
|
||||||
|
|
||||||
|
// Flags for the open operation.
|
||||||
|
Flags bazilfuse.OpenFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateFileResponse struct {
|
||||||
|
// Information about the inode that was created.
|
||||||
|
Entry ChildInodeEntry
|
||||||
|
|
||||||
|
// An opaque ID that will be echoed in follow-up calls for this file using
|
||||||
|
// the same struct file in the kernel. In practice this usually means
|
||||||
|
// follow-up calls using the file descriptor returned by open(2).
|
||||||
|
//
|
||||||
|
// The handle may be supplied to the following methods:
|
||||||
|
//
|
||||||
|
// * ReadFile
|
||||||
|
// * WriteFile
|
||||||
|
// * ReleaseFileHandle
|
||||||
|
//
|
||||||
|
// The file system must ensure this ID remains valid until a later call to
|
||||||
|
// ReleaseFileHandle.
|
||||||
|
Handle HandleID
|
||||||
|
}
|
||||||
|
|
||||||
type RmDirRequest struct {
|
type RmDirRequest struct {
|
||||||
Header RequestHeader
|
Header RequestHeader
|
||||||
|
|
||||||
|
@ -547,6 +637,7 @@ type OpenFileResponse struct {
|
||||||
// The handle may be supplied to the following methods:
|
// The handle may be supplied to the following methods:
|
||||||
//
|
//
|
||||||
// * ReadFile
|
// * ReadFile
|
||||||
|
// * WriteFile
|
||||||
// * ReleaseFileHandle
|
// * ReleaseFileHandle
|
||||||
//
|
//
|
||||||
// The file system must ensure this ID remains valid until a later call to
|
// The file system must ensure this ID remains valid until a later call to
|
||||||
|
@ -558,7 +649,7 @@ type ReadFileRequest struct {
|
||||||
Header RequestHeader
|
Header RequestHeader
|
||||||
|
|
||||||
// The file inode that we are reading, and the handle previously returned by
|
// The file inode that we are reading, and the handle previously returned by
|
||||||
// OpenFile when opening that inode.
|
// CreateFile or OpenFile when opening that inode.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
|
|
||||||
|
@ -579,6 +670,48 @@ type ReadFileResponse struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WriteFileRequest struct {
|
||||||
|
Header RequestHeader
|
||||||
|
|
||||||
|
// The file inode that we are modifying, and the handle previously returned
|
||||||
|
// by CreateFile or OpenFile when opening that inode.
|
||||||
|
Inode InodeID
|
||||||
|
Handle HandleID
|
||||||
|
|
||||||
|
// The offset at which to write the data below.
|
||||||
|
//
|
||||||
|
// The man page for pwrite(2) implies that aside from changing the file
|
||||||
|
// handle's offset, using pwrite is equivalent to using lseek(2) and then
|
||||||
|
// write(2). The man page for lseek(2) says the following:
|
||||||
|
//
|
||||||
|
// "The lseek() function allows the file offset to be set beyond the end of
|
||||||
|
// the file (but this does not change the size of the file). If data is later
|
||||||
|
// written at this point, subsequent reads of the data in the gap (a "hole")
|
||||||
|
// return null bytes (aq\0aq) until data is actually written into the gap."
|
||||||
|
//
|
||||||
|
// It is therefore reasonable to assume that the kernel is looking for
|
||||||
|
// the following semantics:
|
||||||
|
//
|
||||||
|
// * If the offset is less than or equal to the current size, extend the
|
||||||
|
// file as necessary to fit any data that goes past the end of the file.
|
||||||
|
//
|
||||||
|
// * If the offset is greater than the current size, extend the file
|
||||||
|
// with null bytes until it is not, then do the above.
|
||||||
|
//
|
||||||
|
Offset int64
|
||||||
|
|
||||||
|
// The data to write.
|
||||||
|
//
|
||||||
|
// The FUSE documentation requires that exactly the number of bytes supplied
|
||||||
|
// be written, except on error (http://goo.gl/KUpwwn). This appears to be
|
||||||
|
// because it uses file mmapping machinery (http://goo.gl/SGxnaN) to write a
|
||||||
|
// page at a time.
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type WriteFileResponse struct {
|
||||||
|
}
|
||||||
|
|
||||||
type ReleaseFileHandleRequest struct {
|
type ReleaseFileHandleRequest struct {
|
||||||
Header RequestHeader
|
Header RequestHeader
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,12 @@ func (fs *NotImplementedFileSystem) MkDir(
|
||||||
return nil, fuse.ENOSYS
|
return nil, fuse.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) CreateFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.CreateFileRequest) (*fuse.CreateFileResponse, error) {
|
||||||
|
return nil, fuse.ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) RmDir(
|
func (fs *NotImplementedFileSystem) RmDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *fuse.RmDirRequest) (*fuse.RmDirResponse, error) {
|
req *fuse.RmDirRequest) (*fuse.RmDirResponse, error) {
|
||||||
|
@ -93,6 +99,12 @@ func (fs *NotImplementedFileSystem) ReadFile(
|
||||||
return nil, fuse.ENOSYS
|
return nil, fuse.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) WriteFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.WriteFileRequest) (*fuse.WriteFileResponse, error) {
|
||||||
|
return nil, fuse.ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) ReleaseFileHandle(
|
func (fs *NotImplementedFileSystem) ReleaseFileHandle(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *fuse.ReleaseFileHandleRequest) (*fuse.ReleaseFileHandleResponse, error) {
|
req *fuse.ReleaseFileHandleRequest) (*fuse.ReleaseFileHandleResponse, error) {
|
||||||
|
|
|
@ -279,7 +279,7 @@ func (fs *memFS) MkDir(
|
||||||
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||||
defer parent.mu.Unlock()
|
defer parent.mu.Unlock()
|
||||||
|
|
||||||
// Set up attributes from the child, using the credientials of the calling
|
// Set up attributes from the child, using the credentials of the calling
|
||||||
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
||||||
now := fs.clock.Now()
|
now := fs.clock.Now()
|
||||||
childAttrs := fuse.InodeAttributes{
|
childAttrs := fuse.InodeAttributes{
|
||||||
|
@ -311,6 +311,52 @@ func (fs *memFS) MkDir(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) CreateFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.CreateFileRequest) (resp *fuse.CreateFileResponse, err error) {
|
||||||
|
resp = &fuse.CreateFileResponse{}
|
||||||
|
|
||||||
|
fs.mu.Lock()
|
||||||
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
// Grab the parent, which we will update shortly.
|
||||||
|
parent := fs.getInodeForModifyingOrDie(req.Parent)
|
||||||
|
defer parent.mu.Unlock()
|
||||||
|
|
||||||
|
// Set up attributes from the child, using the credentials of the calling
|
||||||
|
// process as owner (matching inode_init_owner, cf. http://goo.gl/5qavg8).
|
||||||
|
now := fs.clock.Now()
|
||||||
|
childAttrs := fuse.InodeAttributes{
|
||||||
|
Mode: req.Mode,
|
||||||
|
Atime: now,
|
||||||
|
Mtime: now,
|
||||||
|
Ctime: now,
|
||||||
|
Crtime: now,
|
||||||
|
Uid: req.Header.Uid,
|
||||||
|
Gid: req.Header.Gid,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a child.
|
||||||
|
childID, child := fs.allocateInode(childAttrs)
|
||||||
|
defer child.mu.Unlock()
|
||||||
|
|
||||||
|
// Add an entry in the parent.
|
||||||
|
parent.AddChild(childID, req.Name, fuseutil.DT_File)
|
||||||
|
|
||||||
|
// Fill in the response entry.
|
||||||
|
resp.Entry.Child = childID
|
||||||
|
resp.Entry.Attributes = child.attributes
|
||||||
|
|
||||||
|
// We don't spontaneously mutate, so the kernel can cache as long as it wants
|
||||||
|
// (since it also handles invalidation).
|
||||||
|
resp.Entry.AttributesExpiration = fs.clock.Now().Add(365 * 24 * time.Hour)
|
||||||
|
resp.Entry.EntryExpiration = resp.Entry.EntryExpiration
|
||||||
|
|
||||||
|
// We have nothing interesting to put in the Handle field.
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *memFS) RmDir(
|
func (fs *memFS) RmDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *fuse.RmDirRequest) (resp *fuse.RmDirResponse, err error) {
|
req *fuse.RmDirRequest) (resp *fuse.RmDirResponse, err error) {
|
||||||
|
@ -391,3 +437,42 @@ func (fs *memFS) ReadDir(
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) OpenFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.OpenFileRequest) (resp *fuse.OpenFileResponse, err error) {
|
||||||
|
resp = &fuse.OpenFileResponse{}
|
||||||
|
|
||||||
|
fs.mu.RLock()
|
||||||
|
defer fs.mu.RUnlock()
|
||||||
|
|
||||||
|
// We don't mutate spontaneosuly, so if the VFS layer has asked for an
|
||||||
|
// inode that doesn't exist, something screwed up earlier (a lookup, a
|
||||||
|
// cache invalidation, etc.).
|
||||||
|
inode := fs.getInodeForReadingOrDie(req.Inode)
|
||||||
|
defer inode.mu.RUnlock()
|
||||||
|
|
||||||
|
if inode.dir {
|
||||||
|
panic("Found directory.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *memFS) WriteFile(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuse.WriteFileRequest) (resp *fuse.WriteFileResponse, err error) {
|
||||||
|
resp = &fuse.WriteFileResponse{}
|
||||||
|
|
||||||
|
fs.mu.RLock()
|
||||||
|
defer fs.mu.RUnlock()
|
||||||
|
|
||||||
|
// Find the inode in question.
|
||||||
|
inode := fs.getInodeForModifyingOrDie(req.Inode)
|
||||||
|
defer inode.mu.Unlock()
|
||||||
|
|
||||||
|
// Serve the request.
|
||||||
|
_, err = inode.WriteAt(req.Data, req.Offset)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ type inode struct {
|
||||||
// INVARIANT: No non-permission mode bits are set besides os.ModeDir
|
// INVARIANT: No non-permission mode bits are set besides os.ModeDir
|
||||||
// INVARIANT: If dir, then os.ModeDir is set
|
// INVARIANT: If dir, then os.ModeDir is set
|
||||||
// INVARIANT: If !dir, then os.ModeDir is not set
|
// INVARIANT: If !dir, then os.ModeDir is not set
|
||||||
|
// INVARIANT: attributes.Size == len(contents)
|
||||||
attributes fuse.InodeAttributes // GUARDED_BY(mu)
|
attributes fuse.InodeAttributes // GUARDED_BY(mu)
|
||||||
|
|
||||||
// For directories, entries describing the children of the directory. Unused
|
// For directories, entries describing the children of the directory. Unused
|
||||||
|
@ -142,6 +143,15 @@ func (inode *inode) checkInvariants() {
|
||||||
panic("Non-nil entries in a file.")
|
panic("Non-nil entries in a file.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check the size.
|
||||||
|
if inode.attributes.Size != uint64(len(inode.contents)) {
|
||||||
|
panic(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Unexpected size: %v vs. %v",
|
||||||
|
inode.attributes.Size,
|
||||||
|
len(inode.contents)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the index of the child within inode.entries, if it exists.
|
// Return the index of the child within inode.entries, if it exists.
|
||||||
|
@ -279,3 +289,31 @@ func (inode *inode) ReadDir(offset int, size int) (data []byte, err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write to the file's contents. See documentation for ioutil.WriterAt.
|
||||||
|
//
|
||||||
|
// REQUIRES: !inode.dir
|
||||||
|
// EXCLUSIVE_LOCKS_REQUIRED(inode.mu)
|
||||||
|
func (inode *inode) WriteAt(p []byte, off int64) (n int, err error) {
|
||||||
|
if inode.dir {
|
||||||
|
panic("WriteAt called on directory.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the contents slice is long enough.
|
||||||
|
newLen := int(off) + len(p)
|
||||||
|
if len(inode.contents) < newLen {
|
||||||
|
padding := make([]byte, newLen-len(inode.contents))
|
||||||
|
inode.contents = append(inode.contents, padding...)
|
||||||
|
inode.attributes.Size = uint64(newLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy in the data.
|
||||||
|
n = copy(inode.contents[off:], p)
|
||||||
|
|
||||||
|
// Sanity check.
|
||||||
|
if n != len(p) {
|
||||||
|
panic(fmt.Sprintf("Unexpected short copy: %v", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -280,7 +280,7 @@ func (t *MemFSTest) Mkdir_IntermediateIsFile() {
|
||||||
err = os.Mkdir(dirName, 0754)
|
err = os.Mkdir(dirName, 0754)
|
||||||
|
|
||||||
AssertNe(nil, err)
|
AssertNe(nil, err)
|
||||||
ExpectThat(err, Error(HasSubstr("TODO")))
|
ExpectThat(err, Error(HasSubstr("not a directory")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) Mkdir_IntermediateIsNonExistent() {
|
func (t *MemFSTest) Mkdir_IntermediateIsNonExistent() {
|
||||||
|
@ -309,7 +309,45 @@ func (t *MemFSTest) Mkdir_PermissionDenied() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) CreateNewFile_InRoot() {
|
func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||||
AssertTrue(false, "TODO")
|
var err error
|
||||||
|
var fi os.FileInfo
|
||||||
|
var stat *syscall.Stat_t
|
||||||
|
|
||||||
|
fileName := path.Join(t.mfs.Dir(), "foo")
|
||||||
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
|
// Write a file.
|
||||||
|
createTime := t.clock.Now()
|
||||||
|
err = ioutil.WriteFile(fileName, []byte(contents), 0400)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Simulate time advancing.
|
||||||
|
t.clock.AdvanceTime(time.Second)
|
||||||
|
|
||||||
|
// Stat it.
|
||||||
|
fi, err = os.Stat(fileName)
|
||||||
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq("foo", fi.Name())
|
||||||
|
ExpectEq(len(contents), fi.Size())
|
||||||
|
ExpectEq(0400, fi.Mode())
|
||||||
|
ExpectEq(0, fi.ModTime().Sub(createTime))
|
||||||
|
ExpectFalse(fi.IsDir())
|
||||||
|
|
||||||
|
ExpectNe(0, stat.Ino)
|
||||||
|
ExpectEq(1, stat.Nlink)
|
||||||
|
ExpectEq(currentUid(), stat.Uid)
|
||||||
|
ExpectEq(currentGid(), stat.Gid)
|
||||||
|
ExpectEq(len(contents), stat.Size)
|
||||||
|
ExpectEq(0, timespecToTime(stat.Atimespec).Sub(createTime))
|
||||||
|
ExpectEq(0, timespecToTime(stat.Mtimespec).Sub(createTime))
|
||||||
|
ExpectEq(0, timespecToTime(stat.Ctimespec).Sub(createTime))
|
||||||
|
|
||||||
|
// Read it back.
|
||||||
|
slice, err := ioutil.ReadFile(fileName)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq(contents, string(slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) CreateNewFile_InSubDir() {
|
func (t *MemFSTest) CreateNewFile_InSubDir() {
|
||||||
|
@ -336,6 +374,10 @@ func (t *MemFSTest) UnlinkFile_NonExistent() {
|
||||||
AssertTrue(false, "TODO")
|
AssertTrue(false, "TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) UnlinkFile_StillOpen() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) Rmdir_NonEmpty() {
|
func (t *MemFSTest) Rmdir_NonEmpty() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -468,3 +510,11 @@ func (t *MemFSTest) CaseSensitive() {
|
||||||
AssertThat(err, Error(HasSubstr("no such file or directory")))
|
AssertThat(err, Error(HasSubstr("no such file or directory")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) FileReadsAndWrites() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *MemFSTest) FileReadsAndWrites_BeyondEOF() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright 2015 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.
|
||||||
|
|
||||||
|
// Tests for the behavior of os.File objects on plain old posix file systems,
|
||||||
|
// for use in verifying the intended behavior of memfs.
|
||||||
|
|
||||||
|
package memfs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/jacobsa/ogletest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPosix(t *testing.T) { RunTests(t) }
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Helpers
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func getFileOffset(f *os.File) (offset int64, err error) {
|
||||||
|
const relativeToCurrent = 1
|
||||||
|
offset, err = f.Seek(0, relativeToCurrent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Boilerplate
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type PosixTest struct {
|
||||||
|
// A temporary directory.
|
||||||
|
dir string
|
||||||
|
|
||||||
|
// Files to close when tearing down. Nil entries are skipped.
|
||||||
|
toClose []io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ SetUpInterface = &PosixTest{}
|
||||||
|
var _ TearDownInterface = &PosixTest{}
|
||||||
|
|
||||||
|
func init() { RegisterTestSuite(&PosixTest{}) }
|
||||||
|
|
||||||
|
func (t *PosixTest) SetUp(ti *TestInfo) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create a temporary directory.
|
||||||
|
t.dir, err = ioutil.TempDir("", "posix_test")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) TearDown() {
|
||||||
|
// Close any files we opened.
|
||||||
|
for _, c := range t.toClose {
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the temporary directory.
|
||||||
|
err := os.RemoveAll(t.dir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test functions
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func (t *PosixTest) WriteOverlapsEndOfFile() {
|
||||||
|
var err error
|
||||||
|
var n int
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
f, err := os.Create(path.Join(t.dir, "foo"))
|
||||||
|
t.toClose = append(t.toClose, f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Make it 4 bytes long.
|
||||||
|
err = f.Truncate(4)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write the range [2, 6).
|
||||||
|
n, err = f.WriteAt([]byte("taco"), 2)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// Read the full contents of the file.
|
||||||
|
contents, err := ioutil.ReadAll(f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq("\x00\x00taco", string(contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) WriteStartsAtEndOfFile() {
|
||||||
|
var err error
|
||||||
|
var n int
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
f, err := os.Create(path.Join(t.dir, "foo"))
|
||||||
|
t.toClose = append(t.toClose, f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Make it 2 bytes long.
|
||||||
|
err = f.Truncate(2)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write the range [2, 6).
|
||||||
|
n, err = f.WriteAt([]byte("taco"), 2)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// Read the full contents of the file.
|
||||||
|
contents, err := ioutil.ReadAll(f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq("\x00\x00taco", string(contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) WriteStartsPastEndOfFile() {
|
||||||
|
var err error
|
||||||
|
var n int
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
f, err := os.Create(path.Join(t.dir, "foo"))
|
||||||
|
t.toClose = append(t.toClose, f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write the range [2, 6).
|
||||||
|
n, err = f.WriteAt([]byte("taco"), 2)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// Read the full contents of the file.
|
||||||
|
contents, err := ioutil.ReadAll(f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq("\x00\x00taco", string(contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) WriteAtDoesntChangeOffset_NotAppendMode() {
|
||||||
|
var err error
|
||||||
|
var n int
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
f, err := os.Create(path.Join(t.dir, "foo"))
|
||||||
|
t.toClose = append(t.toClose, f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Make it 16 bytes long.
|
||||||
|
err = f.Truncate(16)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Seek to offset 4.
|
||||||
|
_, err = f.Seek(4, 0)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write the range [10, 14).
|
||||||
|
n, err = f.WriteAt([]byte("taco"), 2)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// We should still be at offset 4.
|
||||||
|
offset, err := getFileOffset(f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq(4, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) WriteAtDoesntChangeOffset_AppendMode() {
|
||||||
|
var err error
|
||||||
|
var n int
|
||||||
|
|
||||||
|
// Create a file in append mode.
|
||||||
|
f, err := os.OpenFile(
|
||||||
|
path.Join(t.dir, "foo"),
|
||||||
|
os.O_RDWR|os.O_APPEND|os.O_CREATE,
|
||||||
|
0600)
|
||||||
|
|
||||||
|
t.toClose = append(t.toClose, f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Make it 16 bytes long.
|
||||||
|
err = f.Truncate(16)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Seek to offset 4.
|
||||||
|
_, err = f.Seek(4, 0)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Write the range [10, 14).
|
||||||
|
n, err = f.WriteAt([]byte("taco"), 2)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// We should still be at offset 4.
|
||||||
|
offset, err := getFileOffset(f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
ExpectEq(4, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PosixTest) ReadsPastEndOfFile() {
|
||||||
|
var err error
|
||||||
|
var n int
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
|
||||||
|
// Create a file.
|
||||||
|
f, err := os.Create(path.Join(t.dir, "foo"))
|
||||||
|
t.toClose = append(t.toClose, f)
|
||||||
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
// Give it some contents.
|
||||||
|
n, err = f.Write([]byte("taco"))
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(4, n)
|
||||||
|
|
||||||
|
// Read a range overlapping EOF.
|
||||||
|
n, err = f.ReadAt(buf[:4], 2)
|
||||||
|
AssertEq(io.EOF, err)
|
||||||
|
ExpectEq(2, n)
|
||||||
|
ExpectEq("co", string(buf[:n]))
|
||||||
|
|
||||||
|
// Read a range starting at EOF.
|
||||||
|
n, err = f.ReadAt(buf[:4], 4)
|
||||||
|
AssertEq(io.EOF, err)
|
||||||
|
ExpectEq(0, n)
|
||||||
|
ExpectEq("", string(buf[:n]))
|
||||||
|
|
||||||
|
// Read a range starting past EOF.
|
||||||
|
n, err = f.ReadAt(buf[:4], 100)
|
||||||
|
AssertEq(io.EOF, err)
|
||||||
|
ExpectEq(0, n)
|
||||||
|
ExpectEq("", string(buf[:n]))
|
||||||
|
}
|
55
server.go
55
server.go
|
@ -195,6 +195,35 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
||||||
s.logger.Println("Responding:", fuseResp)
|
s.logger.Println("Responding:", fuseResp)
|
||||||
typed.Respond(fuseResp)
|
typed.Respond(fuseResp)
|
||||||
|
|
||||||
|
case *bazilfuse.CreateRequest:
|
||||||
|
// Convert the request.
|
||||||
|
req := &CreateFileRequest{
|
||||||
|
Header: convertHeader(typed.Header),
|
||||||
|
Parent: InodeID(typed.Header.Node),
|
||||||
|
Name: typed.Name,
|
||||||
|
Mode: typed.Mode,
|
||||||
|
Flags: typed.Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the file system.
|
||||||
|
resp, err := s.fs.CreateFile(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Println("Responding:", err)
|
||||||
|
typed.RespondError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the response.
|
||||||
|
fuseResp := &bazilfuse.CreateResponse{
|
||||||
|
OpenResponse: bazilfuse.OpenResponse{
|
||||||
|
Handle: bazilfuse.HandleID(resp.Handle),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
convertChildInodeEntry(s.clock, &resp.Entry, &fuseResp.LookupResponse)
|
||||||
|
|
||||||
|
s.logger.Println("Responding:", fuseResp)
|
||||||
|
typed.Respond(fuseResp)
|
||||||
|
|
||||||
case *bazilfuse.RemoveRequest:
|
case *bazilfuse.RemoveRequest:
|
||||||
// We don't yet support files.
|
// We don't yet support files.
|
||||||
if !typed.Dir {
|
if !typed.Dir {
|
||||||
|
@ -326,6 +355,32 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
|
||||||
typed.Respond(fuseResp)
|
typed.Respond(fuseResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *bazilfuse.WriteRequest:
|
||||||
|
// Convert the request.
|
||||||
|
req := &WriteFileRequest{
|
||||||
|
Header: convertHeader(typed.Header),
|
||||||
|
Inode: InodeID(typed.Header.Node),
|
||||||
|
Handle: HandleID(typed.Handle),
|
||||||
|
Data: typed.Data,
|
||||||
|
Offset: typed.Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the file system.
|
||||||
|
_, err := s.fs.WriteFile(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Println("Responding:", err)
|
||||||
|
typed.RespondError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the response.
|
||||||
|
fuseResp := &bazilfuse.WriteResponse{
|
||||||
|
Size: len(typed.Data),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Println("Responding:", fuseResp)
|
||||||
|
typed.Respond(fuseResp)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
s.logger.Println("Unhandled type. Returning ENOSYS.")
|
s.logger.Println("Unhandled type. Returning ENOSYS.")
|
||||||
typed.RespondError(ENOSYS)
|
typed.RespondError(ENOSYS)
|
||||||
|
|
Loading…
Reference in New Issue