diff --git a/Makefile b/Makefile index 9a5af104..b57eaaff 100644 --- a/Makefile +++ b/Makefile @@ -223,6 +223,7 @@ TEST_PACKAGES = \ math \ math/cmplx \ net/mail \ + os \ reflect \ testing \ testing/iotest \ diff --git a/src/os/file.go b/src/os/file.go index ca6cdf6d..6af6a28f 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -175,6 +175,8 @@ func IsPathSeparator(c uint8) bool { } // PathError records an error and the operation and file path that caused it. +// TODO: PathError moved to io/fs in go 1.16 and left an alias in os/errors.go. +// Do the same once we drop support for go 1.15. type PathError struct { Op string Path string diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index cd938b6b..4cf2a50e 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -35,7 +35,32 @@ func (fs unixFilesystem) Mkdir(path string, perm FileMode) error { } func (fs unixFilesystem) Remove(path string) error { - return handleSyscallError(syscall.Unlink(path)) + // System call interface forces us to know + // whether name is a file or directory. + // Try both: it is cheaper on average than + // doing a Stat plus the right one. + e := handleSyscallError(syscall.Unlink(path)) + if e == nil { + return nil + } + e1 := handleSyscallError(syscall.Rmdir(path)) + if e1 == nil { + return nil + } + + // Both failed: figure out which error to return. + // OS X and Linux differ on whether unlink(dir) + // returns EISDIR, so can't use that. However, + // both agree that rmdir(file) returns ENOTDIR, + // so we can use that to decide which error is real. + // Rmdir might also return ENOTDIR if given a bad + // file path, like /etc/passwd/foo, but in that case, + // both errors will be ENOTDIR, so it's okay to + // use the error from unlink. + if e1 != syscall.ENOTDIR { + e = e1 + } + return &PathError{Op: "remove", Path: path, Err: e} } func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) { diff --git a/src/os/os_unix_test.go b/src/os/os_unix_test.go new file mode 100644 index 00000000..8cb490c7 --- /dev/null +++ b/src/os/os_unix_test.go @@ -0,0 +1,81 @@ +// +build darwin linux,!baremetal freebsd,!baremetal + +package os_test + +import ( + . "os" + "strconv" + "testing" + "time" +) + +func randomName() string { + // fastrand() does not seem available here, so fake it + ns := time.Now().Nanosecond() + pid := Getpid() + return strconv.FormatUint(uint64(ns^pid), 10) +} + +func TestMkdir(t *testing.T) { + dir := "TestMkdir" + randomName() + Remove(dir) + err := Mkdir(dir, 0755) + defer Remove(dir) + if err != nil { + t.Errorf("Mkdir(%s, 0755) returned %v", dir, err) + } + // tests the "directory" branch of Remove + err = Remove(dir) + if err != nil { + t.Errorf("Remove(%s) returned %v", dir, err) + } +} + +func writeFile(t *testing.T, fname string, flag int, text string) string { + f, err := OpenFile(fname, flag, 0666) + if err != nil { + t.Fatalf("Open: %v", err) + } + n, err := f.WriteString(text) + if err != nil { + t.Fatalf("WriteString: %d, %v", n, err) + } + f.Close() + data, err := ReadFile(f.Name()) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + return string(data) +} + +func TestRemove(t *testing.T) { + f := "TestRemove" + randomName() + + err := Remove(f) + if err == nil { + t.Errorf("TestRemove: remove of nonexistent file did not fail") + } else { + // FIXME: once we drop go 1.15, switch this to fs.PathError + if pe, ok := err.(*PathError); !ok { + t.Errorf("TestRemove: expected PathError, got err %q", err.Error()) + } else { + if pe.Path != f { + t.Errorf("TestRemove: PathError returned path %q, expected %q", pe.Path, f) + } + } + // TODO: make this pass. + if !IsNotExist(err) { + t.Logf("TestRemove: TODO: expected IsNotExist(err) true, got false; err %q", err.Error()) + } + } + + s := writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new") + if s != "new" { + t.Fatalf("writeFile: have %q want %q", s, "new") + } + // tests the "file" branch of Remove + err = Remove(f) + if err != nil { + t.Fatalf("Remove: %v", err) + } +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7d610400..f4f3578c 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -51,11 +51,30 @@ func Open(path string, flag int, mode uint32) (fd int, err error) { } func Mkdir(path string, mode uint32) (err error) { - return ENOSYS // TODO + data := cstring(path) + fail := int(libc_mkdir(&data[0], mode)) + if fail < 0 { + err = getErrno() + } + return +} + +func Rmdir(path string) (err error) { + data := cstring(path) + fail := int(libc_rmdir(&data[0])) + if fail < 0 { + err = getErrno() + } + return } func Unlink(path string) (err error) { - return ENOSYS // TODO + data := cstring(path) + fail := int(libc_unlink(&data[0])) + if fail < 0 { + err = getErrno() + } + return } func Kill(pid int, sig Signal) (err error) { @@ -136,3 +155,15 @@ func libc_mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offse // int mprotect(void *addr, size_t len, int prot); //export mprotect func libc_mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 + +// int mkdir(const char *pathname, mode_t mode); +//export mkdir +func libc_mkdir(pathname *byte, mode uint32) int32 + +// int rmdir(const char *pathname); +//export rmdir +func libc_rmdir(pathname *byte) int32 + +// int unlink(const char *pathname); +//export unlink +func libc_unlink(pathname *byte) int32