syscallcompat: OSX: add Renamat wrapper

Adds a poor man's renameat implementation for OSX.
This commit is contained in:
Jakob Unterwurzacher 2016-07-03 20:05:32 +02:00
parent 9b725c15cf
commit 79851bf6cc
4 changed files with 90 additions and 37 deletions

View File

@ -4,6 +4,7 @@ package fusefrontend
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@ -17,6 +18,7 @@ import (
"github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog" "github.com/rfjakob/gocryptfs/internal/tlog"
) )
@ -385,6 +387,7 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
} }
defer oldDirFd.Close() defer oldDirFd.Close()
finalOldDirFd = int(oldDirFd.Fd()) finalOldDirFd = int(oldDirFd.Fd())
// Use relative path
finalOldPath = cOldName finalOldPath = cOldName
} }
// Handle long destination file name // Handle long destination file name
@ -399,6 +402,7 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
} }
defer newDirFd.Close() defer newDirFd.Close()
finalNewDirFd = int(newDirFd.Fd()) finalNewDirFd = int(newDirFd.Fd())
// Use relative path
finalNewPath = cNewName finalNewPath = cNewName
// Create destination .name file // Create destination .name file
err = fs.nameTransform.WriteLongName(newDirFd, cNewName, newPath) err = fs.nameTransform.WriteLongName(newDirFd, cNewName, newPath)
@ -407,7 +411,8 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
} }
} }
// Actual rename // Actual rename
err = syscall.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath) fmt.Printf("Renameat oldfd=%d oldpath=%s newfd=%d newpath=%s\n", finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
err = syscallcompat.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
if err == syscall.ENOTEMPTY || err == syscall.EEXIST { if err == syscall.ENOTEMPTY || err == syscall.EEXIST {
// If an empty directory is overwritten we will always get an error as // If an empty directory is overwritten we will always get an error as
// the "empty" directory will still contain gocryptfs.diriv. // the "empty" directory will still contain gocryptfs.diriv.
@ -416,7 +421,7 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
// again. // again.
tlog.Debug.Printf("Rename: Handling ENOTEMPTY") tlog.Debug.Printf("Rename: Handling ENOTEMPTY")
if fs.Rmdir(newPath, context) == fuse.OK { if fs.Rmdir(newPath, context) == fuse.OK {
err = syscall.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath) err = syscallcompat.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
} }
} }
if err != nil { if err != nil {

View File

@ -14,6 +14,7 @@ import (
"github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/nametransform" "github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog" "github.com/rfjakob/gocryptfs/internal/tlog"
) )
@ -168,7 +169,7 @@ func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) {
// Protect against concurrent readers. // Protect against concurrent readers.
fs.dirIVLock.Lock() fs.dirIVLock.Lock()
defer fs.dirIVLock.Unlock() defer fs.dirIVLock.Unlock()
err = syscall.Renameat(int(dirfd.Fd()), nametransform.DirIVFilename, err = syscallcompat.Renameat(int(dirfd.Fd()), nametransform.DirIVFilename,
int(parentDirFd.Fd()), tmpName) int(parentDirFd.Fd()), tmpName)
if err != nil { if err != nil {
tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v",
@ -182,7 +183,7 @@ func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) {
if err != nil { if err != nil {
// This can happen if another file in the directory was created in the // This can happen if another file in the directory was created in the
// meantime, undo the rename // meantime, undo the rename
err2 := syscall.Renameat(int(parentDirFd.Fd()), tmpName, err2 := syscallcompat.Renameat(int(parentDirFd.Fd()), tmpName,
int(dirfd.Fd()), nametransform.DirIVFilename) int(dirfd.Fd()), nametransform.DirIVFilename)
if err != nil { if err != nil {
tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2)

View File

@ -2,44 +2,87 @@ package syscallcompat
import ( import (
"os" "os"
"path/filepath"
"sync" "sync"
"syscall" "syscall"
) )
// prealloc - preallocate space without changing the file size. This prevents // Sorry, fallocate is not available on OSX at all and
// us from running out of space in the middle of an operation. // fcntl F_PREALLOCATE is not accessible from Go.
func Prealloc(fd int, off int64, len int64) (err error) { // See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help.
// func EnospcPrealloc(fd int, off int64, len int64) (err error) {
// Sorry, fallocate is not available on OSX at all and
// fcntl F_PREALLOCATE is not accessible from Go.
//
// See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help.
return nil return nil
} }
var openatLock sync.Mutex // See above.
// Poor man's Openat:
// 1) fchdir to the dirfd
// 2) open the file
// 3) chdir back.
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
openatLock.Lock()
defer openatLock.Unlock()
oldWd, err := os.Getwd()
if err != nil {
return -1, err
}
err = syscall.Fchdir(dirfd)
if err != nil {
return -1, err
}
defer os.Chdir(oldWd)
return syscall.Open(path, flags, mode)
}
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) { func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return syscall.EOPNOTSUPP return syscall.EOPNOTSUPP
} }
var chdirMutex sync.Mutex
// Poor man's Openat
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
chdirMutex.Lock()
defer chdirMutex.Unlock()
if !filepath.IsAbs(path) {
// Save the old working directory
oldWd, err := os.Getwd()
if err != nil {
return -1, err
}
// Chdir to target directory
err = syscall.Fchdir(dirfd)
if err != nil {
return -1, err
}
// Chdir back at the end
defer os.Chdir(oldWd)
}
return syscall.Open(path, flags, mode)
}
// Poor man's Renameat
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error) {
chdirMutex.Lock()
defer chdirMutex.Unlock()
// Unless both paths are absolute we have to save the old working dir and
// Chdir(oldWd) back to it in the end. If we error out before the first
// chdir, Chdir(oldWd) is unneccassary but does no harm.
if !filepath.IsAbs(oldpath) || !filepath.IsAbs(newpath) {
oldWd, err := os.Getwd()
if err != nil {
return err
}
defer os.Chdir(oldWd)
}
// Make oldpath absolute
oldpath, err = dirfdAbs(olddirfd, oldpath)
if err != nil {
return err
}
// Make newpath absolute
newpath, err = dirfdAbs(newdirfd, newpath)
if err != nil {
return err
}
return syscall.Rename(oldpath, newpath)
}
// dirfdAbs transforms the dirfd-relative "path" to an absolute one. If the
// path is not already absolute, this function will change the working
// directory. The caller has to chdir back.
func dirfdAbs(dirfd int, path string) (absPath string, err error) {
if filepath.IsAbs(path) {
return path, nil
}
err = syscall.Fchdir(dirfd)
if err != nil {
return "", err
}
wd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(wd, path), nil
}

View File

@ -37,10 +37,14 @@ func EnospcPrealloc(fd int, off int64, len int64) (err error) {
} }
} }
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return syscall.Fallocate(fd, mode, off, len)
}
func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) { func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
return syscall.Openat(dirfd, path, flags, mode) return syscall.Openat(dirfd, path, flags, mode)
} }
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) { func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) (err error) {
return syscall.Fallocate(fd, mode, off, len) return syscall.Renameat(olddirfd, oldpath, newdirfd, newpath)
} }