diff --git a/src/os/file.go b/src/os/file.go index f1314d81..21892778 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -38,6 +38,15 @@ func Mkdir(path string, perm FileMode) error { return nil } +// Many functions in package syscall return a count of -1 instead of 0. +// Using fixCount(call()) instead of call() corrects the count. +func fixCount(n int, err error) (int, error) { + if n < 0 { + n = 0 + } + return n, err +} + // Remove removes a file or (empty) directory. If the operation fails, it will // return an error of type *PathError. func Remove(path string) error { @@ -52,11 +61,6 @@ func Remove(path string) error { return nil } -// Symlink is a stub, it is not implemented. -func Symlink(oldname, newname string) error { - return ErrNotImplemented -} - // RemoveAll is a stub, it is not implemented. func RemoveAll(path string) error { return ErrNotImplemented @@ -257,11 +261,6 @@ func Getwd() (string, error) { return syscall.Getwd() } -// Readlink is a stub (for now), always returning the string it was given -func Readlink(name string) (string, error) { - return name, nil -} - // TempDir returns the default directory to use for temporary files. // // On Unix systems, it returns $TMPDIR if non-empty, else /tmp. diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 5d9ab5d2..90c0c7b2 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -52,6 +52,44 @@ func tempDir() string { return dir } +// Symlink creates newname as a symbolic link to oldname. +// On Windows, a symlink to a non-existent oldname creates a file symlink; +// if oldname is later created as a directory the symlink will not work. +// If there is an error, it will be of type *LinkError. +func Symlink(oldname, newname string) error { + e := ignoringEINTR(func() error { + return syscall.Symlink(oldname, newname) + }) + if e != nil { + return &LinkError{"symlink", oldname, newname, e} + } + return nil +} + +// Readlink returns the destination of the named symbolic link. +// If there is an error, it will be of type *PathError. +func Readlink(name string) (string, error) { + for len := 128; ; len *= 2 { + b := make([]byte, len) + var ( + n int + e error + ) + for { + n, e = fixCount(syscall.Readlink(name, b)) + if e != syscall.EINTR { + break + } + } + if e != nil { + return "", &PathError{Op: "readlink", Path: name, Err: e} + } + if n < len { + return string(b[0:n]), nil + } + } +} + // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 43d6bbe6..3e1c63b9 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -15,6 +15,16 @@ import ( type syscallFd = syscall.Handle +// Symlink is a stub, it is not implemented. +func Symlink(oldname, newname string) error { + return ErrNotImplemented +} + +// Readlink is a stub (for now), always returning the string it was given +func Readlink(name string) (string, error) { + return name, nil +} + func rename(oldname, newname string) error { e := windows.Rename(fixLongPath(oldname), fixLongPath(newname)) if e != nil { diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 2892c926..b1d748a7 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -96,6 +96,28 @@ func TestRemove(t *testing.T) { } } +// chtmpdir changes the working directory to a new temporary directory and +// provides a cleanup function. +func chtmpdir(t *testing.T) func() { + oldwd, err := Getwd() + if err != nil { + t.Fatalf("chtmpdir: %v", err) + } + d, err := MkdirTemp("", "test") + if err != nil { + t.Fatalf("chtmpdir: %v", err) + } + if err := Chdir(d); err != nil { + t.Fatalf("chtmpdir: %v", err) + } + return func() { + if err := Chdir(oldwd); err != nil { + t.Fatalf("chtmpdir: %v", err) + } + RemoveAll(d) + } +} + func TestRename(t *testing.T) { // TODO: use t.TempDir() from, to := TempDir()+"/"+"TestRename-from", TempDir()+"/"+"TestRename-to" diff --git a/src/os/os_symlink_test.go b/src/os/os_symlink_test.go new file mode 100644 index 00000000..ad74e5dd --- /dev/null +++ b/src/os/os_symlink_test.go @@ -0,0 +1,75 @@ +// +build !windows,!baremetal,!js,!wasi + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "testing" +) + +// TODO: Move this back into os_anyos_test.go when wasi supports symlink + +func TestSymlink(t *testing.T) { + //testenv.MustHaveSymlink(t) + + defer chtmpdir(t)() + from, to := "symlinktestfrom", "symlinktestto" + file, err := Create(to) + if err != nil { + t.Fatalf("Create(%q) failed: %v", to, err) + } + if err = file.Close(); err != nil { + t.Errorf("Close(%q) failed: %v", to, err) + } + err = Symlink(to, from) + if err != nil { + t.Fatalf("Symlink(%q, %q) failed: %v", to, from, err) + } + tostat, err := Lstat(to) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", to, err) + } + if tostat.Mode()&ModeSymlink != 0 { + t.Fatalf("Lstat(%q).Mode()&ModeSymlink = %v, want 0", to, tostat.Mode()&ModeSymlink) + } + fromstat, err := Stat(from) + if err != nil { + t.Fatalf("Stat(%q) failed: %v", from, err) + } + if !SameFile(tostat, fromstat) { + t.Errorf("Symlink(%q, %q) did not create symlink", to, from) + } + fromstat, err = Lstat(from) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", from, err) + } + if fromstat.Mode()&ModeSymlink == 0 { + t.Fatalf("Lstat(%q).Mode()&ModeSymlink = 0, want %v", from, ModeSymlink) + } + fromstat, err = Stat(from) + if err != nil { + t.Fatalf("Stat(%q) failed: %v", from, err) + } + if fromstat.Name() != from { + t.Errorf("Stat(%q).Name() = %q, want %q", from, fromstat.Name(), from) + } + if fromstat.Mode()&ModeSymlink != 0 { + t.Fatalf("Stat(%q).Mode()&ModeSymlink = %v, want 0", from, fromstat.Mode()&ModeSymlink) + } + s, err := Readlink(from) + if err != nil { + t.Fatalf("Readlink(%q) failed: %v", from, err) + } + if s != to { + t.Fatalf("Readlink(%q) = %q, want %q", from, s, to) + } + file, err = Open(from) + if err != nil { + t.Fatalf("Open(%q) failed: %v", from, err) + } + file.Close() +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index d8d68bca..27c95886 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -63,6 +63,16 @@ func Open(path string, flag int, mode uint32) (fd int, err error) { return } +func Readlink(path string, p []byte) (n int, err error) { + data := cstring(path) + buf, count := splitSlice(p) + n = libc_readlink(&data[0], buf, uint(count)) + if n < 0 { + err = getErrno() + } + return +} + func Chdir(path string) (err error) { data := cstring(path) fail := int(libc_chdir(&data[0])) @@ -109,6 +119,16 @@ func Rename(from, to string) (err error) { return } +func Symlink(from, to string) (err error) { + fromdata := cstring(from) + todata := cstring(to) + fail := int(libc_symlink(&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])) @@ -303,7 +323,15 @@ func libc_rmdir(pathname *byte) int32 // int rename(const char *from, *to); //export rename -func libc_rename(from, too *byte) int32 +func libc_rename(from, to *byte) int32 + +// int symlink(const char *from, *to); +//export symlink +func libc_symlink(from, to *byte) int32 + +// ssize_t readlink(const char *path, void *buf, size_t count); +//export readlink +func libc_readlink(path *byte, buf *byte, count uint) int // int unlink(const char *pathname); //export unlink