aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2015-09-04 20:31:06 +0200
committerJakob Unterwurzacher2015-09-04 20:37:37 +0200
commit6f90ec716a0e486628297a8d74e9f4d8d895e744 (patch)
tree66261a242910310edd4db12455de3970ed23e2fb
parent779ad6dda3d7926c1f68e72600a5db3764e6039d (diff)
Rebase to cluefs
https://github.com/airnandez/cluefs
-rw-r--r--.gitignore2
-rw-r--r--cryptfs/file.go12
-rw-r--r--cryptfs/fs.go26
-rw-r--r--frontend/dir.go10
-rw-r--r--frontend/file.go4
-rw-r--r--frontend/fs.go29
-rw-r--r--frontend/node.go108
-rw-r--r--main.go394
8 files changed, 64 insertions, 521 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8d65432
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# Binary
+/gocryptfs
diff --git a/cryptfs/file.go b/cryptfs/file.go
index 77262d4..5645f3c 100644
--- a/cryptfs/file.go
+++ b/cryptfs/file.go
@@ -8,7 +8,7 @@ import (
"crypto/cipher"
)
-type File struct {
+type CryptFile struct {
file *os.File
gcm cipher.AEAD
plainBS int64
@@ -17,7 +17,7 @@ type File struct {
// readCipherBlock - Read ciphertext block number "blockNo", decrypt,
// return plaintext
-func (be *File) readCipherBlock(blockNo int64) ([]byte, error) {
+func (be *CryptFile) readCipherBlock(blockNo int64) ([]byte, error) {
off := blockNo * int64(be.cipherBS)
buf := make([]byte, be.cipherBS)
@@ -64,7 +64,7 @@ type intraBlock struct {
}
// Split a plaintext byte range into (possible partial) blocks
-func (be *File) splitRange(offset int64, length int64) []intraBlock {
+func (be *CryptFile) splitRange(offset int64, length int64) []intraBlock {
var b intraBlock
var parts []intraBlock
@@ -79,7 +79,7 @@ func (be *File) splitRange(offset int64, length int64) []intraBlock {
return parts
}
-func (be *File) min64(x int64, y int64) int64 {
+func (be *CryptFile) min64(x int64, y int64) int64 {
if x < y {
return x
}
@@ -87,7 +87,7 @@ func (be *File) min64(x int64, y int64) int64 {
}
// writeCipherBlock - Encrypt plaintext and write it to file block "blockNo"
-func (be *File) writeCipherBlock(blockNo int64, plain []byte) error {
+func (be *CryptFile) writeCipherBlock(blockNo int64, plain []byte) error {
if int64(len(plain)) > be.plainBS {
panic("writeCipherBlock: Cannot write block that is larger than plainBS")
@@ -109,7 +109,7 @@ func (be *File) writeCipherBlock(blockNo int64, plain []byte) error {
// Perform RMW cycle on block
// Write "data" into file location specified in "b"
-func (be *File) rmwWrite(b intraBlock, data []byte, f *os.File) error {
+func (be *CryptFile) rmwWrite(b intraBlock, data []byte, f *os.File) error {
if b.length != int64(len(data)) {
panic("Length mismatch")
}
diff --git a/cryptfs/fs.go b/cryptfs/fs.go
index 9050b30..f5781e2 100644
--- a/cryptfs/fs.go
+++ b/cryptfs/fs.go
@@ -19,14 +19,14 @@ const (
DECRYPT = false
)
-type FS struct {
+type CryptFS struct {
blockCipher cipher.Block
gcm cipher.AEAD
plainBS int64
cipherBS int64
}
-func NewFS(key [16]byte) *FS {
+func NewCryptFS(key [16]byte) *CryptFS {
b, err := aes.NewCipher(key[:])
if err != nil {
@@ -38,7 +38,7 @@ func NewFS(key [16]byte) *FS {
panic(err)
}
- return &FS{
+ return &CryptFS{
blockCipher: b,
gcm: g,
plainBS: DEFAULT_PLAINBS,
@@ -46,8 +46,8 @@ func NewFS(key [16]byte) *FS {
}
}
-func (fs *FS) NewFile(f *os.File) *File {
- return &File {
+func (fs *CryptFS) NewFile(f *os.File) *CryptFile {
+ return &CryptFile {
file: f,
gcm: fs.gcm,
plainBS: fs.plainBS,
@@ -56,7 +56,7 @@ func (fs *FS) NewFile(f *os.File) *File {
}
// DecryptName - decrypt filename
-func (be *FS) decryptName(cipherName string) (string, error) {
+func (be *CryptFS) decryptName(cipherName string) (string, error) {
bin, err := base64.URLEncoding.DecodeString(cipherName)
if err != nil {
@@ -81,7 +81,7 @@ func (be *FS) decryptName(cipherName string) (string, error) {
}
// EncryptName - encrypt filename
-func (be *FS) encryptName(plainName string) string {
+func (be *CryptFS) encryptName(plainName string) string {
bin := []byte(plainName)
bin = be.pad16(bin)
@@ -97,7 +97,7 @@ func (be *FS) encryptName(plainName string) string {
// TranslatePath - encrypt or decrypt path. Just splits the string on "/"
// and hands the parts to EncryptName() / DecryptName()
-func (be *FS) translatePath(path string, op bool) (string, error) {
+func (be *CryptFS) translatePath(path string, op bool) (string, error) {
var err error
// Empty string means root directory
@@ -125,18 +125,18 @@ func (be *FS) translatePath(path string, op bool) (string, error) {
}
// EncryptPath - encrypt filename or path. Just hands it to TranslatePath().
-func (be *FS) EncryptPath(path string) string {
+func (be *CryptFS) EncryptPath(path string) string {
newPath, _ := be.translatePath(path, ENCRYPT)
return newPath
}
// DecryptPath - decrypt filename or path. Just hands it to TranslatePath().
-func (be *FS) DecryptPath(path string) (string, error) {
+func (be *CryptFS) DecryptPath(path string) (string, error) {
return be.translatePath(path, DECRYPT)
}
// plainSize - calculate plaintext size from ciphertext size
-func (be *FS) PlainSize(s int64) int64 {
+func (be *CryptFS) PlainSize(s int64) int64 {
// Zero sized files stay zero-sized
if s > 0 {
// Number of blocks
@@ -149,7 +149,7 @@ func (be *FS) PlainSize(s int64) int64 {
// pad16 - pad filename to 16 byte blocks using standard PKCS#7 padding
// https://tools.ietf.org/html/rfc5652#section-6.3
-func (be *FS) pad16(orig []byte) (padded []byte) {
+func (be *CryptFS) pad16(orig []byte) (padded []byte) {
oldLen := len(orig)
if oldLen == 0 {
panic("Padding zero-length string makes no sense")
@@ -169,7 +169,7 @@ func (be *FS) pad16(orig []byte) (padded []byte) {
}
// unPad16 - remove padding
-func (be *FS) unPad16(orig []byte) ([]byte, error) {
+func (be *CryptFS) unPad16(orig []byte) ([]byte, error) {
oldLen := len(orig)
if oldLen % aes.BlockSize != 0 {
return nil, errors.New("Unaligned size")
diff --git a/frontend/dir.go b/frontend/dir.go
new file mode 100644
index 0000000..4703df9
--- /dev/null
+++ b/frontend/dir.go
@@ -0,0 +1,10 @@
+package frontend
+
+import (
+ //"github.com/rfjakob/gocryptfs/cryptfs"
+ "github.com/rfjakob/cluefs/lib/cluefs"
+)
+
+type Dir struct {
+ *cluefs.Dir
+}
diff --git a/frontend/file.go b/frontend/file.go
index 7b292bd..81590e8 100644
--- a/frontend/file.go
+++ b/frontend/file.go
@@ -2,8 +2,10 @@ package frontend
import (
"github.com/rfjakob/gocryptfs/cryptfs"
+ "github.com/rfjakob/cluefs/lib/cluefs"
)
type File struct {
- cryptfs.File
+ *cryptfs.CryptFile
+ *cluefs.File
}
diff --git a/frontend/fs.go b/frontend/fs.go
index 637d134..ba6ad09 100644
--- a/frontend/fs.go
+++ b/frontend/fs.go
@@ -2,25 +2,26 @@ package frontend
import (
"github.com/rfjakob/gocryptfs/cryptfs"
- "bazil.org/fuse/fs"
+ "github.com/rfjakob/cluefs/lib/cluefs"
)
type FS struct {
- *cryptfs.FS
- backing string
+ *cryptfs.CryptFS
+ *cluefs.ClueFS
}
-func New(key [16]byte, b string) *FS {
- return &FS {
- FS: cryptfs.NewFS(key),
- backing: b,
- }
-}
+type nullTracer struct {}
-func (fs *FS) Root() (fs.Node, error) {
- n := Node{
- backing: "",
- parentFS: fs,
+func (nullTracer) Trace(op cluefs.FsOperTracer) {}
+
+func NewFS(key [16]byte, backing string) *FS {
+ var nt nullTracer
+ clfs, err := cluefs.NewClueFS(backing, nt)
+ if err != nil {
+ panic(err)
+ }
+ return &FS {
+ CryptFS: cryptfs.NewCryptFS(key),
+ ClueFS: clfs,
}
- return n, nil
}
diff --git a/frontend/node.go b/frontend/node.go
index 833be2e..7218d54 100644
--- a/frontend/node.go
+++ b/frontend/node.go
@@ -1,113 +1,9 @@
package frontend
import (
- "fmt"
- "os"
- "time"
- "syscall"
- "io/ioutil"
- "path"
-
- "golang.org/x/net/context"
-
- //"github.com/rfjakob/gocryptfs/cryptfs"
- "bazil.org/fuse"
- "bazil.org/fuse/fs"
+ "github.com/rfjakob/cluefs/lib/cluefs"
)
-
type Node struct {
- fs.NodeRef
- backing string
- parentFS *FS
-}
-
-// FileModeFromStat - create os.FileMode from stat value
-// For some reason, they use different constants.
-// Adapted from https://golang.org/src/os/stat_linux.go
-func FileModeFromStat(st *syscall.Stat_t) os.FileMode {
- fileMode := os.FileMode(st.Mode & 0777)
- switch st.Mode & syscall.S_IFMT {
- case syscall.S_IFBLK:
- fileMode |= os.ModeDevice
- case syscall.S_IFCHR:
- fileMode |= os.ModeDevice | os.ModeCharDevice
- case syscall.S_IFDIR:
- fileMode |= os.ModeDir
- case syscall.S_IFIFO:
- fileMode |= os.ModeNamedPipe
- case syscall.S_IFLNK:
- fileMode |= os.ModeSymlink
- case syscall.S_IFREG:
- // nothing to do
- case syscall.S_IFSOCK:
- fileMode |= os.ModeSocket
- }
- if st.Mode & syscall.S_ISGID != 0 {
- fileMode |= os.ModeSetgid
- }
- if st.Mode & syscall.S_ISUID != 0 {
- fileMode |= os.ModeSetuid
- }
- if st.Mode & syscall.S_ISVTX != 0 {
- fileMode |= os.ModeSticky
- }
- return fileMode
-}
-
-
-func StatToAttr(s *syscall.Stat_t, a *fuse.Attr) {
- a.Inode = s.Ino
- a.Size = uint64(s.Size)
- a.Blocks = uint64(s.Blocks)
- a.Atime = time.Unix(s.Atim.Sec, s.Atim.Nsec)
- a.Mtime = time.Unix(s.Mtim.Sec, s.Mtim.Nsec)
- a.Ctime = time.Unix(s.Ctim.Sec, s.Ctim.Nsec)
- a.Mode = FileModeFromStat(s)
- a.Nlink = uint32(s.Nlink)
- a.Uid = uint32(s.Uid)
- a.Gid = uint32(s.Gid)
- a.Rdev = uint32(s.Rdev)
-}
-
-func (n Node) Attr(ctx context.Context, attr *fuse.Attr) error {
- var err error
- var st syscall.Stat_t
- if n.backing == "" {
- // When GetAttr is called for the toplevel directory, we always want
- // to look through symlinks.
- fmt.Printf("Attr %s\n", n.parentFS.backing)
- //err = syscall.Stat(n.parentFS.backing, &st)
- err = syscall.Stat("/", &st)
- } else {
- fmt.Printf("Attr %s\n", path.Join(n.parentFS.backing, n.backing))
- p := path.Join(n.parentFS.backing, n.backing)
- err = syscall.Lstat(p, &st)
- }
- if err != nil {
- return err
- }
- StatToAttr(&st, attr)
- return nil
-}
-
-func (n *Node) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
- entries, err := ioutil.ReadDir(n.backing)
- if err != nil {
- return nil, err
- }
- var fuseEntries []fuse.Dirent
- for _, e := range entries {
- var d fuse.Dirent
- d.Name = e.Name()
- fuseEntries = append(fuseEntries, d)
- }
- return fuseEntries, err
-}
-
-func (n *Node) Lookup(ctx context.Context, name string) (fs.Node, error) {
- if name == "hello" {
- return Node{}, nil
- }
- return nil, fuse.ENOENT
+ *cluefs.Node
}
diff --git a/main.go b/main.go
index a3159f3..af7bd21 100644
--- a/main.go
+++ b/main.go
@@ -1,396 +1,28 @@
-// Memfs implements an in-memory file system.
package main
import (
- "flag"
- "fmt"
- "log"
- "os"
- "sync"
- "sync/atomic"
- "time"
-
- "bazil.org/fuse"
- "bazil.org/fuse/fs"
- "golang.org/x/net/context"
-
+ "github.com/rfjakob/cluefs/lib/cluefs"
"github.com/rfjakob/gocryptfs/frontend"
+ "os"
)
-// debug flag enables logging of debug messages to stderr.
-var debug = flag.Bool("debug", true, "enable debug log messages to stderr")
-
-func usage() {
- fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
- fmt.Fprintf(os.Stderr, " %s BACKING MOUNTPOINT\n", os.Args[0])
- flag.PrintDefaults()
-}
-
-func debugLog(msg interface{}) {
- fmt.Fprintf(os.Stderr, "%v\n", msg)
-}
-
func main() {
- flag.Usage = usage
- flag.Parse()
-
- if flag.NArg() != 2 {
- usage()
- os.Exit(2)
- }
-
- backing := flag.Arg(0)
- mountpoint := flag.Arg(1)
- c, err := fuse.Mount(
- mountpoint,
- fuse.FSName("gocryptfs"),
- fuse.Subtype("gocryptfs"),
- fuse.LocalVolume(),
- fuse.VolumeName("gocryptfs"),
- )
+ // Parse command line arguments
+ conf, err := cluefs.ParseArguments()
if err != nil {
- log.Fatal(err)
- }
- defer c.Close()
-
- cfg := &fs.Config{}
- if *debug {
- cfg.Debug = debugLog
+ os.Exit(1)
}
- srv := fs.New(c, cfg)
+ // Create the file system object
var key [16]byte
- filesys := frontend.New(key, backing)
-
- if err := srv.Serve(filesys); err != nil {
- log.Fatal(err)
- }
-
- // Check if the mount process has an error to report.
- <-c.Ready
- if err := c.MountError; err != nil {
- log.Fatal(err)
- }
-}
-
-type MemFS struct {
- root *Dir
- nodeId uint64
-
- nodeCount uint64
- size int64
-}
-
-// Compile-time interface checks.
-var _ fs.FS = (*MemFS)(nil)
-var _ fs.FSStatfser = (*MemFS)(nil)
-
-var _ fs.Node = (*Dir)(nil)
-var _ fs.NodeCreater = (*Dir)(nil)
-var _ fs.NodeMkdirer = (*Dir)(nil)
-var _ fs.NodeRemover = (*Dir)(nil)
-var _ fs.NodeRenamer = (*Dir)(nil)
-var _ fs.NodeStringLookuper = (*Dir)(nil)
-
-var _ fs.HandleReadAller = (*File)(nil)
-var _ fs.HandleWriter = (*File)(nil)
-var _ fs.Node = (*File)(nil)
-var _ fs.NodeOpener = (*File)(nil)
-var _ fs.NodeSetattrer = (*File)(nil)
-
-func NewMemFS() *MemFS {
- fs := &MemFS{
- nodeCount: 1,
- }
- fs.root = fs.newDir(os.ModeDir | 0777)
- if fs.root.attr.Inode != 1 {
- panic("Root node should have been assigned id 1")
- }
- return fs
-}
-
-func (m *MemFS) nextId() uint64 {
- return atomic.AddUint64(&m.nodeId, 1)
-}
-
-func (m *MemFS) newDir(mode os.FileMode) *Dir {
- n := time.Now()
- return &Dir{
- attr: fuse.Attr{
- Inode: m.nextId(),
- Atime: n,
- Mtime: n,
- Ctime: n,
- Crtime: n,
- Mode: os.ModeDir | mode,
- },
- fs: m,
- nodes: make(map[string]fs.Node),
- }
-}
-
-func (m *MemFS) newFile(mode os.FileMode) *File {
- n := time.Now()
- return &File{
- attr: fuse.Attr{
- Inode: m.nextId(),
- Atime: n,
- Mtime: n,
- Ctime: n,
- Crtime: n,
- Mode: mode,
- },
- data: make([]byte, 0),
- }
-}
-
-type Dir struct {
- sync.RWMutex
- attr fuse.Attr
-
- fs *MemFS
- parent *Dir
- nodes map[string]fs.Node
-}
-
-type File struct {
- sync.RWMutex
- attr fuse.Attr
-
- fs *MemFS
- data []byte
-}
-
-func (f *MemFS) Root() (fs.Node, error) {
- return f.root, nil
-}
-
-func (f *MemFS) Statfs(ctx context.Context, req *fuse.StatfsRequest,
- resp *fuse.StatfsResponse) error {
- resp.Blocks = uint64((atomic.LoadInt64(&f.size) + 511) / 512)
- resp.Bsize = 512
- resp.Files = atomic.LoadUint64(&f.nodeCount)
- return nil
-}
-
-func (f *File) Attr(ctx context.Context, o *fuse.Attr) error {
- f.RLock()
- *o = f.attr
- f.RUnlock()
- return nil
-}
-
-func (d *Dir) Attr(ctx context.Context, o *fuse.Attr) error {
- d.RLock()
- *o = d.attr
- d.RUnlock()
- return nil
-}
-
-func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
- d.RLock()
- n, exist := d.nodes[name]
- d.RUnlock()
-
- if !exist {
- return nil, fuse.ENOENT
- }
- return n, nil
-}
-
-func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
- d.RLock()
- dirs := make([]fuse.Dirent, len(d.nodes)+2)
+ cfs := frontend.NewFS(key, conf.GetShadowDir())
- // Add special references.
- dirs[0] = fuse.Dirent{
- Name: ".",
- Inode: d.attr.Inode,
- Type: fuse.DT_Dir,
+ // Mount and serve file system requests
+ if err = cfs.MountAndServe(conf.GetMountPoint(), conf.GetReadOnly()); err != nil {
+ cluefs.ErrlogMain.Printf("could not mount file system [%s]", err)
+ os.Exit(3)
}
- dirs[1] = fuse.Dirent{
- Name: "..",
- Type: fuse.DT_Dir,
- }
- if d.parent != nil {
- dirs[1].Inode = d.parent.attr.Inode
- } else {
- dirs[1].Inode = d.attr.Inode
- }
-
- // Add remaining files.
- idx := 2
- for name, node := range d.nodes {
- ent := fuse.Dirent{
- Name: name,
- }
- switch n := node.(type) {
- case *File:
- ent.Inode = n.attr.Inode
- ent.Type = fuse.DT_File
- case *Dir:
- ent.Inode = n.attr.Inode
- ent.Type = fuse.DT_Dir
- }
- dirs[idx] = ent
- idx++
- }
- d.RUnlock()
- return dirs, nil
-}
-
-func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
- d.Lock()
- defer d.Unlock()
-
- if _, exists := d.nodes[req.Name]; exists {
- return nil, fuse.EEXIST
- }
-
- n := d.fs.newDir(req.Mode)
- d.nodes[req.Name] = n
- atomic.AddUint64(&d.fs.nodeCount, 1)
-
- return n, nil
-}
-
-func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest,
- resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
- d.Lock()
- defer d.Unlock()
-
- if _, exists := d.nodes[req.Name]; exists {
- return nil, nil, fuse.EEXIST
- }
-
- n := d.fs.newFile(req.Mode)
- n.fs = d.fs
- d.nodes[req.Name] = n
- atomic.AddUint64(&d.fs.nodeCount, 1)
-
- resp.Attr = n.attr
-
- return n, n, nil
-}
-
-func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
- nd := newDir.(*Dir)
- if d.attr.Inode == nd.attr.Inode {
- d.Lock()
- defer d.Unlock()
- } else if d.attr.Inode < nd.attr.Inode {
- d.Lock()
- defer d.Unlock()
- nd.Lock()
- defer nd.Unlock()
- } else {
- nd.Lock()
- defer nd.Unlock()
- d.Lock()
- defer d.Unlock()
- }
-
- if _, exists := d.nodes[req.OldName]; !exists {
- return fuse.ENOENT
- }
-
- // Rename can be used as an atomic replace, override an existing file.
- if old, exists := nd.nodes[req.NewName]; exists {
- atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one
- if oldFile, ok := old.(*File); !ok {
- atomic.AddInt64(&d.fs.size, -int64(oldFile.attr.Size))
- }
- }
-
- nd.nodes[req.NewName] = d.nodes[req.OldName]
- delete(d.nodes, req.OldName)
- return nil
-}
-
-func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
- d.Lock()
- defer d.Unlock()
-
- if n, exists := d.nodes[req.Name]; !exists {
- return fuse.ENOENT
- } else if req.Dir && len(n.(*Dir).nodes) > 0 {
- return fuse.ENOTEMPTY
- }
-
- delete(d.nodes, req.Name)
- atomic.AddUint64(&d.fs.nodeCount, ^uint64(0)) // decrement by one
- return nil
-}
-
-func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle,
- error) {
- return f, nil
-}
-
-func (f *File) ReadAll(ctx context.Context) ([]byte, error) {
- f.RLock()
- out := make([]byte, len(f.data))
- copy(out, f.data)
- f.RUnlock()
-
- return out, nil
-}
-
-func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
- f.Lock()
-
- l := len(req.Data)
- end := int(req.Offset) + l
- if end > len(f.data) {
- delta := end - len(f.data)
- f.data = append(f.data, make([]byte, delta)...)
- f.attr.Size = uint64(len(f.data))
- atomic.AddInt64(&f.fs.size, int64(delta))
- }
- copy(f.data[req.Offset:end], req.Data)
- resp.Size = l
-
- f.Unlock()
- return nil
-}
-
-func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest,
- resp *fuse.SetattrResponse) error {
- f.Lock()
-
- if req.Valid.Size() {
- delta := int(req.Size) - len(f.data)
- if delta > 0 {
- f.data = append(f.data, make([]byte, delta)...)
- } else {
- f.data = f.data[0:req.Size]
- }
- f.attr.Size = req.Size
- atomic.AddInt64(&f.fs.size, int64(delta))
- }
-
- if req.Valid.Mode() {
- f.attr.Mode = req.Mode
- }
-
- if req.Valid.Atime() {
- f.attr.Atime = req.Atime
- }
-
- if req.Valid.AtimeNow() {
- f.attr.Atime = time.Now()
- }
-
- if req.Valid.Mtime() {
- f.attr.Mtime = req.Mtime
- }
-
- if req.Valid.MtimeNow() {
- f.attr.Mtime = time.Now()
- }
-
- resp.Attr = f.attr
- f.Unlock()
- return nil
+ // We are done
+ os.Exit(0)
}