Unverified Commit d5fa746e authored by Fazlul Shahriar's avatar Fazlul Shahriar Committed by GitHub

Add os.Rename wrapper for Plan 9 (#87)

os.Rename documentation says: "OS-specific restrictions may apply when
oldpath and newpath are in different directories." On Unix, this means
we can't rename across devices. On Plan 9 however, the functionality is
even more limited: cross-directory renames are not allowed at all.

Add a wrapper around os.Rename for Plan 9, which will copy the file if
we're renaming across directory. All tests seems to pass.

(Aside: I also had to write this wrapper to get go-git working on Plan 9:
https://github.com/go-git/go-billy/blob/v5.0.0/osfs/os_plan9.go#L27
but I notice few issues with that one.)

Fixes #86
parent 5aadd5ca
...@@ -156,7 +156,7 @@ func Move(oldPath string, newPath string, out io.Writer) error { ...@@ -156,7 +156,7 @@ func Move(oldPath string, newPath string, out io.Writer) error {
// else we found something unexpected, so to be safe just move it // else we found something unexpected, so to be safe just move it
log.Warnw("found unexpected file in datastore directory, moving anyways", "file", fn) log.Warnw("found unexpected file in datastore directory, moving anyways", "file", fn)
newPath := filepath.Join(newDS.path, fn) newPath := filepath.Join(newDS.path, fn)
err := os.Rename(oldPath, newPath) err := rename(oldPath, newPath)
if err != nil { if err != nil {
return err return err
} }
...@@ -177,7 +177,7 @@ func moveKey(oldDS *Datastore, newDS *Datastore, key datastore.Key) error { ...@@ -177,7 +177,7 @@ func moveKey(oldDS *Datastore, newDS *Datastore, key datastore.Key) error {
if err != nil && !os.IsExist(err) { if err != nil && !os.IsExist(err) {
return err return err
} }
err = os.Rename(oldPath, newPath) err = rename(oldPath, newPath)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -369,7 +369,7 @@ func (fs *Datastore) renameAndUpdateDiskUsage(tmpPath, path string) error { ...@@ -369,7 +369,7 @@ func (fs *Datastore) renameAndUpdateDiskUsage(tmpPath, path string) error {
// it will either a) Re-add the size of an existing file, which // it will either a) Re-add the size of an existing file, which
// was sustracted before b) Add 0 if there is no existing file. // was sustracted before b) Add 0 if there is no existing file.
for i := 0; i < RetryAttempts; i++ { for i := 0; i < RetryAttempts; i++ {
err = os.Rename(tmpPath, path) err = rename(tmpPath, path)
// if there's no error, or the source file doesn't exist, abort. // if there's no error, or the source file doesn't exist, abort.
if err == nil || os.IsNotExist(err) { if err == nil || os.IsNotExist(err) {
break break
...@@ -1060,7 +1060,7 @@ func (fs *Datastore) writeDiskUsageFile(du int64, doSync bool) { ...@@ -1060,7 +1060,7 @@ func (fs *Datastore) writeDiskUsageFile(du int64, doSync bool) {
} }
closed = true closed = true
if err := os.Rename(tmp.Name(), filepath.Join(fs.path, DiskUsageFile)); err != nil { if err := rename(tmp.Name(), filepath.Join(fs.path, DiskUsageFile)); err != nil {
log.Warnw("cound not write disk usage", "error", err) log.Warnw("cound not write disk usage", "error", err)
return return
} }
......
// +build !plan9
package flatfs
import "os"
var rename = os.Rename
package flatfs
import (
"io"
"os"
"path/filepath"
"syscall"
)
// rename behaves like os.Rename but can rename files across directories.
func rename(oldpath, newpath string) error {
err := os.Rename(oldpath, newpath)
if le, ok := err.(*os.LinkError); !ok || le.Err != os.ErrInvalid {
return err
}
if filepath.Dir(oldpath) == filepath.Dir(newpath) {
// We should not get here, but just in case
// os.ErrInvalid is used for something else in the future.
return err
}
src, err := os.Open(oldpath)
if err != nil {
return &os.LinkError{"rename", oldpath, newpath, err}
}
defer src.Close()
fi, err := src.Stat()
if err != nil {
return &os.LinkError{"rename", oldpath, newpath, err}
}
if fi.Mode().IsDir() {
return &os.LinkError{"rename", oldpath, newpath, syscall.EISDIR}
}
dst, err := os.OpenFile(newpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
if err != nil {
return &os.LinkError{"rename", oldpath, newpath, err}
}
if _, err := io.Copy(dst, src); err != nil {
dst.Close()
os.Remove(newpath)
return &os.LinkError{"rename", oldpath, newpath, err}
}
if err := dst.Close(); err != nil {
os.Remove(newpath)
return &os.LinkError{"rename", oldpath, newpath, err}
}
// Copy mtime and mode from original file.
// We need only one syscall if we avoid os.Chmod and os.Chtimes.
dir := fi.Sys().(*syscall.Dir)
var d syscall.Dir
d.Null()
d.Mtime = dir.Mtime
d.Mode = dir.Mode
_ = dirwstat(newpath, &d) // ignore error, as per mv(1)
if err := os.Remove(oldpath); err != nil {
return &os.LinkError{"rename", oldpath, newpath, err}
}
return nil
}
func dirwstat(name string, d *syscall.Dir) error {
var buf [syscall.STATFIXLEN]byte
n, err := d.Marshal(buf[:])
if err != nil {
return &os.PathError{"dirwstat", name, err}
}
if err = syscall.Wstat(name, buf[:n]); err != nil {
return &os.PathError{"dirwstat", name, err}
}
return nil
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment