mirror of https://github.com/perkeep/perkeep.git
fs: bunch of read-write FUSE work, debugging, and integration tests.
Change-Id: I74807f693720effb7ae8405259797331f79f59fd
This commit is contained in:
parent
62b0f66f05
commit
98eb69b5e1
|
@ -172,7 +172,7 @@ func main() {
|
|||
err = fs.Unmount(mountPoint)
|
||||
log.Printf("Unmount = %v", err)
|
||||
|
||||
log.Printf("cammount FUSE processending.")
|
||||
log.Printf("cammount FUSE process ending.")
|
||||
}
|
||||
|
||||
func awaitQuitKey(done chan<- bool) {
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"camlistore.org/pkg/test"
|
||||
)
|
||||
|
||||
var (
|
||||
errmu sync.Mutex
|
||||
lasterr error
|
||||
)
|
||||
|
||||
func condSkip(t *testing.T) {
|
||||
errmu.Lock()
|
||||
defer errmu.Unlock()
|
||||
if lasterr != nil {
|
||||
t.Skipf("Skipping test; some other test already failed.")
|
||||
}
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skipf("Skipping test on OS %q", runtime.GOOS)
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
_, err := os.Stat("/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs")
|
||||
if os.IsNotExist(err) {
|
||||
test.DependencyErrorOrSkip(t)
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cammountTest(t *testing.T, fn func(mountPoint string)) {
|
||||
w := test.GetWorld(t)
|
||||
mountPoint, err := ioutil.TempDir("", "fs-test-mount")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verbose := "false"
|
||||
if os.Getenv("VERBOSE_FUSE") != "" {
|
||||
verbose = "true"
|
||||
}
|
||||
mount := w.Cmd("cammount", "--debug="+verbose, mountPoint)
|
||||
mount.Stderr = os.Stderr
|
||||
stdin, err := mount.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := mount.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waitc := make(chan error, 1)
|
||||
go func() { waitc <- mount.Wait() }()
|
||||
defer func() {
|
||||
log.Printf("Sending quit")
|
||||
stdin.Write([]byte("q\n"))
|
||||
select {
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Printf("timeout waiting for cammount to finish")
|
||||
mount.Process.Kill()
|
||||
Unmount(mountPoint)
|
||||
case err := <-waitc:
|
||||
log.Printf("cammount exited: %v", err)
|
||||
}
|
||||
if !waitFor(not(dirToBeFUSE(mountPoint)), 5*time.Second, 1*time.Second) {
|
||||
// It didn't unmount. Try again.
|
||||
Unmount(mountPoint)
|
||||
}
|
||||
}()
|
||||
|
||||
if !waitFor(dirToBeFUSE(mountPoint), 5*time.Second, 100*time.Millisecond) {
|
||||
t.Fatalf("error waiting for %s to be mounted", mountPoint)
|
||||
}
|
||||
fn(mountPoint)
|
||||
}
|
||||
|
||||
func TestRoot(t *testing.T) {
|
||||
condSkip(t)
|
||||
cammountTest(t, func(mountPoint string) {
|
||||
f, err := os.Open(mountPoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sort.Strings(names)
|
||||
want := []string{"WELCOME.txt", "date", "recent", "roots", "sha1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "tag"}
|
||||
if !reflect.DeepEqual(names, want) {
|
||||
t.Errorf("root directory = %q; want %q", names, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMutable(t *testing.T) {
|
||||
condSkip(t)
|
||||
cammountTest(t, func(mountPoint string) {
|
||||
rootDir := filepath.Join(mountPoint, "roots", "r")
|
||||
if err := os.Mkdir(rootDir, 0700); err != nil {
|
||||
t.Fatalf("Failed to make roots/r dir: %v", err)
|
||||
}
|
||||
fi, err := os.Stat(rootDir)
|
||||
if err != nil || !fi.IsDir() {
|
||||
t.Fatalf("Stat of roots/r dir = %v, %v; want a directory", fi, err)
|
||||
}
|
||||
|
||||
filename := filepath.Join(rootDir, "x")
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
fi, err = os.Stat(filename)
|
||||
if err != nil || !fi.Mode().IsRegular() || fi.Size() != 0 {
|
||||
t.Fatalf("Stat of roots/r/x = %v, %v; want a 0-byte regular file", fi, err)
|
||||
}
|
||||
|
||||
if false { // broken
|
||||
for _, str := range []string{"foo, ", "bar\n", "another line.\n"} {
|
||||
f, err = os.OpenFile(filename, os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenFile: %v", err)
|
||||
}
|
||||
if _, err := f.Write([]byte(str)); err != nil {
|
||||
t.Fatalf("Error appending %q to %s: %v", str, filename, err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
slurp, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const want = "foo, bar\nanother line.\n"
|
||||
fi, err = os.Stat(filename)
|
||||
if err != nil || !fi.Mode().IsRegular() || fi.Size() != int64(len(want)) {
|
||||
t.Errorf("Stat of roots/r/x = %v, %v; want a %d byte regular file", fi, len(want), err)
|
||||
}
|
||||
if got := string(slurp); got != want {
|
||||
t.Fatalf("contents = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete it.
|
||||
if err := os.Remove(filename); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Gone?
|
||||
if _, err := os.Stat(filename); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected file to be gone; got stat err = %v instead", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func waitFor(condition func() bool, maxWait, checkInterval time.Duration) bool {
|
||||
t0 := time.Now()
|
||||
tmax := t0.Add(maxWait)
|
||||
for time.Now().Before(tmax) {
|
||||
if condition() {
|
||||
return true
|
||||
}
|
||||
time.Sleep(checkInterval)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func not(cond func() bool) func() bool {
|
||||
return func() bool {
|
||||
return !cond()
|
||||
}
|
||||
}
|
||||
|
||||
func dirToBeFUSE(dir string) func() bool {
|
||||
return func() bool {
|
||||
out, err := exec.Command("df", dir).CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
if strings.Contains(string(out), "mount_osxfusefs@") {
|
||||
log.Printf("fs %s is mounted on fuse.", dir)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
207
pkg/fs/mut.go
207
pkg/fs/mut.go
|
@ -24,6 +24,7 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -44,14 +45,22 @@ const populateInterval = 30 * time.Second
|
|||
type mutDir struct {
|
||||
fs *CamliFileSystem
|
||||
permanode *blobref.BlobRef
|
||||
parent *mutDir
|
||||
name string // ent name (base name within parent)
|
||||
parent *mutDir // or nil, if the root within its roots.go root.
|
||||
name string // ent name (base name within parent)
|
||||
|
||||
mu sync.Mutex
|
||||
lastPop time.Time
|
||||
children map[string]fuse.Node
|
||||
}
|
||||
|
||||
// for debugging
|
||||
func (n *mutDir) fullPath() string {
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(n.parent.fullPath(), n.name)
|
||||
}
|
||||
|
||||
func (n *mutDir) Attr() fuse.Attr {
|
||||
return fuse.Attr{
|
||||
Mode: os.ModeDir | 0700,
|
||||
|
@ -141,15 +150,33 @@ func (n *mutDir) ReadDir(intr fuse.Intr) ([]fuse.Dirent, fuse.Error) {
|
|||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
var ents []fuse.Dirent
|
||||
for name := range n.children {
|
||||
ents = append(ents, fuse.Dirent{
|
||||
for name, childNode := range n.children {
|
||||
var ino uint64
|
||||
switch v := childNode.(type) {
|
||||
case *mutDir:
|
||||
ino = v.permanode.AsUint64()
|
||||
case *mutFile:
|
||||
ino = v.permanode.AsUint64()
|
||||
default:
|
||||
log.Printf("mutDir.ReadDir: unknown child type %T", childNode)
|
||||
}
|
||||
|
||||
// TODO: figure out what Dirent.Type means.
|
||||
// fuse.go says "Type uint32 // ?"
|
||||
dirent := fuse.Dirent{
|
||||
Name: name,
|
||||
})
|
||||
Inode: ino,
|
||||
}
|
||||
log.Printf("mutDir(%q) appending inode %x, %+v", n.fullPath(), dirent.Inode, dirent)
|
||||
ents = append(ents, dirent)
|
||||
}
|
||||
return ents, nil
|
||||
}
|
||||
|
||||
func (n *mutDir) Lookup(name string, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
||||
func (n *mutDir) Lookup(name string, intr fuse.Intr) (ret fuse.Node, err fuse.Error) {
|
||||
defer func() {
|
||||
log.Printf("mutDir(%q).Lookup(%q) = %#v, %v", n.fullPath(), name, ret, err)
|
||||
}()
|
||||
if err := n.populate(); err != nil {
|
||||
log.Println("populate:", err)
|
||||
return nil, fuse.EIO
|
||||
|
@ -162,10 +189,16 @@ func (n *mutDir) Lookup(name string, intr fuse.Intr) (fuse.Node, fuse.Error) {
|
|||
return nil, fuse.ENOENT
|
||||
}
|
||||
|
||||
// Create of regular file. (not a dir)
|
||||
//
|
||||
// TOOD(bradfitz): what are the two bits in fl=514? what are CreateRequest.Flags?
|
||||
//
|
||||
// 2013/07/21 05:26:35 <- &{Create [ID=0x3 Node=0x8 Uid=61652 Gid=5000 Pid=13115] "x" fl=514 mode=-rw-r--r-- fuse.Intr}
|
||||
// 2013/07/21 05:26:36 -> 0x3 Create {LookupResponse:{Node:23 Generation:0 EntryValid:1m0s AttrValid:1m0s Attr:{Inode:15976986887557313215 Size:0 Blocks:0 Atime:2013-07-21 05:23:51.537251251 +1200 NZST Mtime:2013-07-21 05:23:51.537251251 +1200 NZST Ctime:2013-07-21 05:23:51.537251251 +1200 NZST Crtime:2013-07-21 05:23:51.537251251 +1200 NZST Mode:-rw------- Nlink:1 Uid:61652 Gid:5000 Rdev:0 Flags:0}} OpenResponse:{Handle:1 Flags:OpenDirectIO}}
|
||||
func (n *mutDir) Create(req *fuse.CreateRequest, res *fuse.CreateResponse, intr fuse.Intr) (fuse.Node, fuse.Handle, fuse.Error) {
|
||||
child, err := n.creat(req.Name, false)
|
||||
if err != nil {
|
||||
log.Printf("mutDir.Mkdir(%q): %v", req.Name, err)
|
||||
log.Printf("mutDir.Create(%q): %v", req.Name, err)
|
||||
return nil, nil, fuse.EIO
|
||||
}
|
||||
|
||||
|
@ -251,24 +284,65 @@ type mutFile struct {
|
|||
parent *mutDir
|
||||
name string // ent name (base name within parent)
|
||||
|
||||
mu sync.Mutex
|
||||
content *blobref.BlobRef
|
||||
size int64
|
||||
mu sync.Mutex
|
||||
content *blobref.BlobRef
|
||||
size int64
|
||||
mtime, atime time.Time // if zero, use serverStart
|
||||
}
|
||||
|
||||
// for debugging
|
||||
func (n *mutFile) fullPath() string {
|
||||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(n.parent.fullPath(), n.name)
|
||||
}
|
||||
|
||||
func (n *mutFile) Attr() fuse.Attr {
|
||||
// TODO: don't grab n.mu three+ times in here.
|
||||
|
||||
n.mu.Lock()
|
||||
size := n.size
|
||||
var blocks uint64
|
||||
if size > 0 {
|
||||
blocks = uint64(size)/512 + 1
|
||||
}
|
||||
inode := n.permanode.AsUint64()
|
||||
n.mu.Unlock()
|
||||
|
||||
return fuse.Attr{
|
||||
Mode: 0600, // writable!
|
||||
Uid: uint32(os.Getuid()),
|
||||
Gid: uint32(os.Getgid()),
|
||||
Size: uint64(n.size),
|
||||
// TODO(adg): use the real stuff here
|
||||
Mtime: serverStart,
|
||||
Inode: inode,
|
||||
Mode: 0600, // writable!
|
||||
Uid: uint32(os.Getuid()),
|
||||
Gid: uint32(os.Getgid()),
|
||||
Size: uint64(size),
|
||||
Blocks: blocks,
|
||||
Mtime: n.modTime(),
|
||||
Atime: n.accessTime(),
|
||||
Ctime: serverStart,
|
||||
Crtime: serverStart,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *mutFile) accessTime() time.Time {
|
||||
n.mu.Lock()
|
||||
if !n.atime.IsZero() {
|
||||
defer n.mu.Unlock()
|
||||
return n.atime
|
||||
}
|
||||
n.mu.Unlock()
|
||||
return n.modTime()
|
||||
}
|
||||
|
||||
func (n *mutFile) modTime() time.Time {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if !n.mtime.IsZero() {
|
||||
return n.mtime
|
||||
}
|
||||
return serverStart
|
||||
}
|
||||
|
||||
func (n *mutFile) setContent(br *blobref.BlobRef, size int64) error {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
@ -279,13 +353,37 @@ func (n *mutFile) setContent(br *blobref.BlobRef, size int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Empirically:
|
||||
// open for read: req.Flags == 0
|
||||
// open for append: req.Flags == 1
|
||||
// open for write: req.Flags == 1
|
||||
// open for read/write (+<) == 2 (bitmask? of?)
|
||||
// TODO(bradfitz): look this up, once I have connectivity.
|
||||
func (n *mutFile) Open(req *fuse.OpenRequest, res *fuse.OpenResponse, intr fuse.Intr) (fuse.Handle, fuse.Error) {
|
||||
log.Printf("mutFile.Open: %v: content: %v", n.permanode, n.content)
|
||||
log.Printf("mutFile.Open: %v: content: %v dir=%v flags=%v mode=%v", n.permanode, n.content, req.Dir, req.Flags, req.Mode)
|
||||
r, err := schema.NewFileReader(n.fs.fetcher, n.content)
|
||||
if err != nil {
|
||||
log.Printf("mutFile.Open: %v", err)
|
||||
return nil, fuse.EIO
|
||||
}
|
||||
|
||||
// Read-only.
|
||||
if res.Flags == 0 {
|
||||
log.Printf("mutFile.Open returning read-only file")
|
||||
n := &node{
|
||||
fs: n.fs,
|
||||
blobref: n.content,
|
||||
}
|
||||
return &nodeReader{n: n, fr: r}, nil
|
||||
}
|
||||
|
||||
log.Printf("mutFile.Open returning read-write filehandle")
|
||||
|
||||
// Turn off the OpenDirectIO bit (on by default in rsc fuse server.go),
|
||||
// else append operations don't work for some reason.
|
||||
// TODO(bradfitz): also do tihs in Create? CreateResponse.OpenResponse.Flags.
|
||||
res.Flags &= ^fuse.OpenDirectIO
|
||||
|
||||
defer r.Close()
|
||||
return n.newHandle(r)
|
||||
}
|
||||
|
@ -293,6 +391,29 @@ func (n *mutFile) Open(req *fuse.OpenRequest, res *fuse.OpenResponse, intr fuse.
|
|||
func (n *mutFile) Fsync(r *fuse.FsyncRequest, intr fuse.Intr) fuse.Error {
|
||||
// TODO(adg): in the fuse package, plumb through fsync to mutFileHandle
|
||||
// in the same way we did Truncate.
|
||||
log.Printf("mutFile.Fsync: TODO")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *mutFile) Setattr(req *fuse.SetattrRequest, res *fuse.SetattrResponse, intr fuse.Intr) fuse.Error {
|
||||
log.Printf("mutFile.Setattr on %q: %#v", n.fullPath(), req)
|
||||
// 2013/07/17 19:43:41 mutFile.Setattr on "foo": &fuse.SetattrRequest{Header:fuse.Header{Conn:(*fuse.Conn)(0xc210047180), ID:0x3, Node:0x3d, Uid:0xf0d4, Gid:0x1388, Pid:0x75e8}, Valid:0x30, Handle:0x0, Size:0x0, Atime:time.Time{sec:63509651021, nsec:0x4aec6b8, loc:(*time.Location)(0x47f7600)}, Mtime:time.Time{sec:63509651021, nsec:0x4aec6b8, loc:(*time.Location)(0x47f7600)}, Mode:0x4000000, Uid:0x0, Gid:0x0, Bkuptime:time.Time{sec:62135596800, nsec:0x0, loc:(*time.Location)(0x47f7600)}, Chgtime:time.Time{sec:62135596800, nsec:0x0, loc:(*time.Location)(0x47f7600)}, Crtime:time.Time{sec:0, nsec:0x0, loc:(*time.Location)(nil)}, Flags:0x0}
|
||||
|
||||
n.mu.Lock()
|
||||
if req.Valid&fuse.SetattrMtime != 0 {
|
||||
n.mtime = req.Mtime
|
||||
}
|
||||
if req.Valid&fuse.SetattrAtime != 0 {
|
||||
n.atime = req.Atime
|
||||
}
|
||||
if req.Valid&fuse.SetattrSize != 0 {
|
||||
// TODO(bradfitz): truncate?
|
||||
n.size = int64(req.Size)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
res.AttrValid = 1 * time.Minute
|
||||
res.Attr = n.Attr()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -321,10 +442,14 @@ func (n *mutFile) newHandle(body io.Reader) (fuse.Handle, fuse.Error) {
|
|||
type mutFileHandle struct {
|
||||
f *mutFile
|
||||
tmp *os.File
|
||||
written bool
|
||||
}
|
||||
|
||||
func (h *mutFileHandle) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr fuse.Intr) fuse.Error {
|
||||
if h.tmp == nil {
|
||||
log.Printf("Read called on camli mutFileHandle without a tempfile set")
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
buf := make([]byte, req.Size)
|
||||
n, err := h.tmp.ReadAt(buf, req.Offset)
|
||||
if err == io.EOF {
|
||||
|
@ -339,8 +464,13 @@ func (h *mutFileHandle) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr
|
|||
}
|
||||
|
||||
func (h *mutFileHandle) Write(req *fuse.WriteRequest, res *fuse.WriteResponse, intr fuse.Intr) fuse.Error {
|
||||
h.written = true
|
||||
if h.tmp == nil {
|
||||
log.Printf("Write called on camli mutFileHandle without a tempfile set")
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
n, err := h.tmp.WriteAt(req.Data, req.Offset)
|
||||
log.Printf("mutFileHandle.Write(%q, at %d, flags %v, %q) = %d, %v", h.f.fullPath(), req.Offset, req.Flags, req.Data, n, err)
|
||||
if err != nil {
|
||||
log.Println("mutFileHandle.Write:", err)
|
||||
return fuse.EIO
|
||||
|
@ -350,27 +480,38 @@ func (h *mutFileHandle) Write(req *fuse.WriteRequest, res *fuse.WriteResponse, i
|
|||
}
|
||||
|
||||
func (h *mutFileHandle) Release(req *fuse.ReleaseRequest, intr fuse.Intr) fuse.Error {
|
||||
if h.written {
|
||||
_, err := h.tmp.Seek(0, 0)
|
||||
if err != nil {
|
||||
log.Println("mutFileHandle.Release:", err)
|
||||
return fuse.EIO
|
||||
}
|
||||
var n int64
|
||||
br, err := schema.WriteFileFromReader(h.f.fs.client, h.f.name, readerutil.CountingReader{Reader: h.tmp, N: &n})
|
||||
if err != nil {
|
||||
log.Println("mutFileHandle.Release:", err)
|
||||
return fuse.EIO
|
||||
}
|
||||
h.f.setContent(br, n)
|
||||
if h.tmp == nil {
|
||||
log.Printf("Release called on camli mutFileHandle without a tempfile set")
|
||||
return fuse.EIO
|
||||
}
|
||||
log.Printf("mutFileHandle release.")
|
||||
_, err := h.tmp.Seek(0, 0)
|
||||
if err != nil {
|
||||
log.Println("mutFileHandle.Release:", err)
|
||||
return fuse.EIO
|
||||
}
|
||||
var n int64
|
||||
br, err := schema.WriteFileFromReader(h.f.fs.client, h.f.name, readerutil.CountingReader{Reader: h.tmp, N: &n})
|
||||
if err != nil {
|
||||
log.Println("mutFileHandle.Release:", err)
|
||||
return fuse.EIO
|
||||
}
|
||||
h.f.setContent(br, n)
|
||||
|
||||
h.tmp.Close()
|
||||
os.Remove(h.tmp.Name())
|
||||
h.tmp = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *mutFileHandle) Truncate(size uint64, intr fuse.Intr) fuse.Error {
|
||||
h.written = true
|
||||
if h.tmp == nil {
|
||||
log.Printf("Truncate called on camli mutFileHandle without a tempfile set")
|
||||
return fuse.EIO
|
||||
}
|
||||
|
||||
log.Printf("mutFileHandle.Truncate(%q) to size %d", h.f.fullPath(), size)
|
||||
if err := h.tmp.Truncate(int64(size)); err != nil {
|
||||
log.Println("mutFileHandle.Truncate:", err)
|
||||
return fuse.EIO
|
||||
|
|
Binary file not shown.
|
@ -106,6 +106,7 @@ func (w *World) Start() error {
|
|||
w.server.Dir = w.tempDir
|
||||
w.server.Env = append(os.Environ(),
|
||||
"CAMLI_ROOT="+w.tempDir,
|
||||
"CAMLI_SECRET_RING="+filepath.Join(w.camRoot, filepath.FromSlash("pkg/jsonsign/testdata/test-secring.gpg")),
|
||||
"CAMLI_BASE_URL=http://127.0.0.1:"+strconv.Itoa(w.port),
|
||||
)
|
||||
listenerFD, err := w.listener.(*net.TCPListener).File()
|
||||
|
|
Loading…
Reference in New Issue