diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index 77f214a0..a60a6f78 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -40,6 +40,14 @@ func Chdir(dir string) error { return nil } +// Rename renames (moves) oldpath to newpath. +// If newpath already exists and is not a directory, Rename replaces it. +// OS-specific restrictions may apply when oldpath and newpath are in different directories. +// If there is an error, it will be of type *LinkError. +func Rename(oldpath, newpath string) error { + return rename(oldpath, newpath) +} + // unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations // are relative to the current working directory. type unixFilesystem struct { diff --git a/src/os/file_unix.go b/src/os/file_unix.go index d10b910c..4ce72d7f 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -18,6 +18,15 @@ func fixLongPath(path string) string { return path } +func rename(oldname, newname string) error { + // TODO: import rest of upstream tests, handle fancy cases + err := syscall.Rename(oldname, newname) + if err != nil { + return &LinkError{"rename", oldname, newname, err} + } + return nil +} + func Pipe() (r *File, w *File, err error) { var p [2]int err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC)) diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 6d992187..47e9f535 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -8,12 +8,21 @@ package os import ( + "internal/syscall/windows" "syscall" "unicode/utf16" ) type syscallFd = syscall.Handle +func rename(oldname, newname string) error { + e := windows.Rename(fixLongPath(oldname), fixLongPath(newname)) + if e != nil { + return &LinkError{"rename", oldname, newname, e} + } + return nil +} + func Pipe() (r *File, w *File, err error) { var p [2]syscall.Handle e := handleSyscallError(syscall.Pipe(p[:])) diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 135e540e..2892c926 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -3,6 +3,7 @@ package os_test import ( + "io/ioutil" . "os" "path/filepath" "runtime" @@ -35,7 +36,7 @@ func TestMkdir(t *testing.T) { func TestStatBadDir(t *testing.T) { if runtime.GOOS == "windows" { - t.Log("TODO: TestStatBadDir fails on Windows, skipping") + t.Log("TODO: TestStatBadDir: IsNotExist fails on Windows, skipping") return } dir := TempDir() @@ -94,3 +95,88 @@ func TestRemove(t *testing.T) { t.Fatalf("Remove: %v", err) } } + +func TestRename(t *testing.T) { + // TODO: use t.TempDir() + from, to := TempDir()+"/"+"TestRename-from", TempDir()+"/"+"TestRename-to" + + file, err := Create(from) + defer Remove(from) // TODO: switch to t.Tempdir, remove this line + if err != nil { + t.Fatalf("open %q failed: %v", from, err) + } + defer Remove(to) // TODO: switch to t.Tempdir, remove this line + if err = file.Close(); err != nil { + t.Errorf("close %q failed: %v", from, err) + } + err = Rename(from, to) + if err != nil { + t.Fatalf("rename %q, %q failed: %v", to, from, err) + } + _, err = Stat(to) + if err != nil { + t.Errorf("stat %q failed: %v", to, err) + } +} + +func TestRenameOverwriteDest(t *testing.T) { + from, to := TempDir()+"/"+"TestRenameOverwrite-from", TempDir()+"/"+"TestRenameOverwrite-to" + + toData := []byte("to") + fromData := []byte("from") + + err := ioutil.WriteFile(to, toData, 0777) + defer Remove(to) // TODO: switch to t.Tempdir, remove this line + if err != nil { + t.Fatalf("write file %q failed: %v", to, err) + } + + err = ioutil.WriteFile(from, fromData, 0777) + defer Remove(from) // TODO: switch to t.Tempdir, remove this line + if err != nil { + t.Fatalf("write file %q failed: %v", from, err) + } + err = Rename(from, to) + if err != nil { + t.Fatalf("rename %q, %q failed: %v", to, from, err) + } + + _, err = Stat(from) + if err == nil { + t.Errorf("from file %q still exists", from) + } + if runtime.GOOS == "windows" { + t.Log("TODO: TestRenameOverwriteDest: IsNotExist fails on Windows, skipping") + } else if err != nil && !IsNotExist(err) { + t.Fatalf("stat from: %v", err) + } + toFi, err := Stat(to) + if err != nil { + t.Fatalf("stat %q failed: %v", to, err) + } + if toFi.Size() != int64(len(fromData)) { + t.Errorf(`"to" size = %d; want %d (old "from" size)`, toFi.Size(), len(fromData)) + } +} + +func TestRenameFailed(t *testing.T) { + from, to := TempDir()+"/"+"RenameFailed-from", TempDir()+"/"+"RenameFailed-to" + + err := Rename(from, to) + switch err := err.(type) { + case *LinkError: + if err.Op != "rename" { + t.Errorf("rename %q, %q: err.Op: want %q, got %q", from, to, "rename", err.Op) + } + if err.Old != from { + t.Errorf("rename %q, %q: err.Old: want %q, got %q", from, to, from, err.Old) + } + if err.New != to { + t.Errorf("rename %q, %q: err.New: want %q, got %q", from, to, to, err.New) + } + case nil: + t.Errorf("rename %q, %q: expected error, got nil", from, to) + default: + t.Errorf("rename %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err) + } +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 25c77865..af8a2810 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -95,6 +95,16 @@ func Rmdir(path string) (err error) { return } +func Rename(from, to string) (err error) { + fromdata := cstring(from) + todata := cstring(to) + fail := int(libc_rename(&fromdata[0], &todata[0])) + if fail < 0 { + err = getErrno() + } + return +} + func Unlink(path string) (err error) { data := cstring(path) fail := int(libc_unlink(&data[0])) @@ -270,6 +280,10 @@ func libc_mkdir(pathname *byte, mode uint32) int32 //export rmdir func libc_rmdir(pathname *byte) int32 +// int rename(const char *from, *to); +//export rename +func libc_rename(from, too *byte) int32 + // int unlink(const char *pathname); //export unlink func libc_unlink(pathname *byte) int32