Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jacobsa-fuse
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
jacobsa-fuse
Commits
d307babe
Commit
d307babe
authored
Mar 06, 2015
by
Aaron Jacobs
Browse files
Options
Browse Files
Download
Plain Diff
Implemented file creation.
parents
8fc315cf
a612b8b8
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
641 additions
and
15 deletions
+641
-15
file_system.go
file_system.go
+145
-12
fuseutil/not_implemented_file_system.go
fuseutil/not_implemented_file_system.go
+12
-0
samples/memfs/fs.go
samples/memfs/fs.go
+86
-1
samples/memfs/inode.go
samples/memfs/inode.go
+38
-0
samples/memfs/memfs_test.go
samples/memfs/memfs_test.go
+52
-2
samples/memfs/posix_test.go
samples/memfs/posix_test.go
+253
-0
server.go
server.go
+55
-0
No files found.
file_system.go
View file @
d307babe
...
...
@@ -69,10 +69,32 @@ type FileSystem interface {
// Create a directory inode as a child of an existing directory inode. The
// 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
(
ctx
context
.
Context
,
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
///////////////////////////////////
...
...
@@ -128,11 +150,45 @@ type FileSystem interface {
ctx
context
.
Context
,
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
(
ctx
context
.
Context
,
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
// are no more references to an open file: all file descriptors are closed
// 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
// responses for LookUpInode, MkDir,
etc. Consumed by the kernel in order to
// set up a dcache entry.
// responses for LookUpInode, MkDir,
CreateFile, etc. Consumed by the kernel in
//
order to
set up a dcache entry.
type
ChildInodeEntry
struct
{
// The ID of the child inode. The file system must ensure that the returned
// inode ID remains valid until a later call to ForgetInode.
...
...
@@ -247,6 +303,16 @@ type ChildInodeEntry struct {
Generation
GenerationNumber
// 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
// The FUSE VFS layer in the kernel maintains a cache of file attributes,
...
...
@@ -377,16 +443,40 @@ type MkDirRequest struct {
type
MkDirResponse
struct
{
// Information about the inode that was created.
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 file system is responsible for initializing and recording (where
// supported) attributes like time information, ownership information, etc.
// The handle may be supplied to the following methods:
//
// 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
// * ReadFile
// * WriteFile
// * ReleaseFileHandle
//
// The file system must ensure this ID remains valid until a later call to
// ReleaseFileHandle.
Handle
HandleID
}
type
RmDirRequest
struct
{
...
...
@@ -547,6 +637,7 @@ type OpenFileResponse struct {
// 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
...
...
@@ -558,7 +649,7 @@ type ReadFileRequest struct {
Header
RequestHeader
// 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
Handle
HandleID
...
...
@@ -579,6 +670,48 @@ type ReadFileResponse struct {
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
{
Header
RequestHeader
...
...
fuseutil/not_implemented_file_system.go
View file @
d307babe
...
...
@@ -57,6 +57,12 @@ func (fs *NotImplementedFileSystem) MkDir(
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
(
ctx
context
.
Context
,
req
*
fuse
.
RmDirRequest
)
(
*
fuse
.
RmDirResponse
,
error
)
{
...
...
@@ -93,6 +99,12 @@ func (fs *NotImplementedFileSystem) ReadFile(
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
(
ctx
context
.
Context
,
req
*
fuse
.
ReleaseFileHandleRequest
)
(
*
fuse
.
ReleaseFileHandleResponse
,
error
)
{
...
...
samples/memfs/fs.go
View file @
d307babe
...
...
@@ -279,7 +279,7 @@ func (fs *memFS) MkDir(
parent
:=
fs
.
getInodeForModifyingOrDie
(
req
.
Parent
)
defer
parent
.
mu
.
Unlock
()
// Set up attributes from the child, using the cred
i
entials 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).
now
:=
fs
.
clock
.
Now
()
childAttrs
:=
fuse
.
InodeAttributes
{
...
...
@@ -311,6 +311,52 @@ func (fs *memFS) MkDir(
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
(
ctx
context
.
Context
,
req
*
fuse
.
RmDirRequest
)
(
resp
*
fuse
.
RmDirResponse
,
err
error
)
{
...
...
@@ -391,3 +437,42 @@ func (fs *memFS) ReadDir(
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
}
samples/memfs/inode.go
View file @
d307babe
...
...
@@ -54,6 +54,7 @@ type inode struct {
// 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 not set
// INVARIANT: attributes.Size == len(contents)
attributes
fuse
.
InodeAttributes
// GUARDED_BY(mu)
// 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."
)
}
}
// 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.
...
...
@@ -279,3 +289,31 @@ func (inode *inode) ReadDir(offset int, size int) (data []byte, err error) {
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
}
samples/memfs/memfs_test.go
View file @
d307babe
...
...
@@ -280,7 +280,7 @@ func (t *MemFSTest) Mkdir_IntermediateIsFile() {
err
=
os
.
Mkdir
(
dirName
,
0754
)
AssertNe
(
nil
,
err
)
ExpectThat
(
err
,
Error
(
HasSubstr
(
"
TODO
"
)))
ExpectThat
(
err
,
Error
(
HasSubstr
(
"
not a directory
"
)))
}
func
(
t
*
MemFSTest
)
Mkdir_IntermediateIsNonExistent
()
{
...
...
@@ -309,7 +309,45 @@ func (t *MemFSTest) Mkdir_PermissionDenied() {
}
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
\x00
world"
// 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
()
{
...
...
@@ -336,6 +374,10 @@ func (t *MemFSTest) UnlinkFile_NonExistent() {
AssertTrue
(
false
,
"TODO"
)
}
func
(
t
*
MemFSTest
)
UnlinkFile_StillOpen
()
{
AssertTrue
(
false
,
"TODO"
)
}
func
(
t
*
MemFSTest
)
Rmdir_NonEmpty
()
{
var
err
error
...
...
@@ -468,3 +510,11 @@ func (t *MemFSTest) CaseSensitive() {
AssertThat
(
err
,
Error
(
HasSubstr
(
"no such file or directory"
)))
}
}
func
(
t
*
MemFSTest
)
FileReadsAndWrites
()
{
AssertTrue
(
false
,
"TODO"
)
}
func
(
t
*
MemFSTest
)
FileReadsAndWrites_BeyondEOF
()
{
AssertTrue
(
false
,
"TODO"
)
}
samples/memfs/posix_test.go
0 → 100644
View file @
d307babe
// 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\x00
taco"
,
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\x00
taco"
,
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\x00
taco"
,
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
]))
}
server.go
View file @
d307babe
...
...
@@ -195,6 +195,35 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
s
.
logger
.
Println
(
"Responding:"
,
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
:
// We don't yet support files.
if
!
typed
.
Dir
{
...
...
@@ -326,6 +355,32 @@ func (s *server) handleFuseRequest(fuseReq bazilfuse.Request) {
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
:
s
.
logger
.
Println
(
"Unhandled type. Returning ENOSYS."
)
typed
.
RespondError
(
ENOSYS
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment