diff --git a/build.bash b/build.bash index 427edfd..627a31d 100755 --- a/build.bash +++ b/build.bash @@ -94,7 +94,7 @@ fi # Actual "go build" call for gocryptfs go build "-ldflags=$GO_LDFLAGS" "$@" # Additional binaries -for d in gocryptfs-xray contrib/statfs contrib/findholes ; do +for d in gocryptfs-xray contrib/statfs contrib/findholes contrib/atomicrename ; do (cd "$d"; go build "-ldflags=$GO_LDFLAGS" "$@") done diff --git a/contrib/atomicrename/.gitignore b/contrib/atomicrename/.gitignore new file mode 100644 index 0000000..b91a212 --- /dev/null +++ b/contrib/atomicrename/.gitignore @@ -0,0 +1 @@ +/atomicrename diff --git a/contrib/atomicrename/main.go b/contrib/atomicrename/main.go new file mode 100644 index 0000000..67088b0 --- /dev/null +++ b/contrib/atomicrename/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io/ioutil" + "os" + "strings" + "sync/atomic" + "syscall" +) + +const fileCount = 100 + +type stats struct { + renameOk int + renameError int + readOk int + readError int + readContentMismatch int +} + +func usage() { + fmt.Printf(`atomicrename creates %d "src" files in the current directory, renames +them in random order over a single "dst" file while reading the "dst" +file concurrently in a loop. + +Progress and errors are reported as they occour in addition to a summary +printed at the end. cifs and fuse filesystems are known to fail, local +filesystems and nfs seem ok. + +See https://github.com/hanwen/go-fuse/issues/398 for background info. +`, fileCount) + os.Exit(1) +} + +func main() { + flag.Usage = usage + flag.Parse() + + hello := []byte("hello world") + srcFiles := make(map[string]struct{}) + + // prepare source files + fmt.Print("creating files") + for i := 0; i < fileCount; i++ { + srcName := fmt.Sprintf("src.atomicrename.%d", i) + srcFiles[srcName] = struct{}{} + buf := bytes.Repeat([]byte("_"), i) + buf = append(buf, hello...) + if err := ioutil.WriteFile(srcName, buf, 0600); err != nil { + panic(err) + } + fmt.Print(".") + } + fmt.Print("\n") + + // prepare destination file + const dstName = "dst.atomicrename" + if err := ioutil.WriteFile(dstName, hello, 0600); err != nil { + panic(err) + } + + var running int32 = 1 + + stats := stats{} + + // read thread + go func() { + for atomic.LoadInt32(&running) == 1 { + have, err := ioutil.ReadFile(dstName) + if err != nil { + fmt.Println(err) + stats.readError++ + continue + } + if !strings.HasSuffix(string(have), string(hello)) { + fmt.Printf("content mismatch: have %q\n", have) + stats.readContentMismatch++ + continue + } + fmt.Printf("content ok len=%d\n", len(have)) + stats.readOk++ + } + }() + + // rename thread = main thread + for srcName := range srcFiles { + if err := os.Rename(srcName, dstName); err != nil { + fmt.Println(err) + stats.renameError++ + } + stats.renameOk++ + } + // Signal the Read goroutine to stop when loop is done + atomic.StoreInt32(&running, 0) + + syscall.Unlink(dstName) + fmt.Printf("stats: %#v\n", stats) +}