From be7bbba4ca54d0a393ee72b175c4ba050e8f8bd1 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Sat, 4 Dec 2021 13:50:32 -0800 Subject: [PATCH] os: implement and smoketest os.Chmod --- src/os/file_anyos.go | 62 +++++++++++++++++++++++++++++++++++++ src/os/file_unix.go | 21 +++---------- src/os/os_test.go | 26 ++++++++++++++++ src/syscall/syscall_libc.go | 13 ++++++++ 4 files changed, 106 insertions(+), 16 deletions(-) diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index f2c4acd0..0b070a5f 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -1,5 +1,9 @@ // +build !baremetal,!js +// Portions 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 import ( @@ -133,6 +137,48 @@ func (f unixFileHandle) Close() error { return handleSyscallError(syscall.Close(syscallFd(f))) } +// Chmod changes the mode of the named file to mode. +// If the file is a symbolic link, it changes the mode of the link's target. +// If there is an error, it will be of type *PathError. +// +// A different subset of the mode bits are used, depending on the +// operating system. +// +// On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and +// ModeSticky are used. +// +// On Windows, only the 0200 bit (owner writable) of mode is used; it +// controls whether the file's read-only attribute is set or cleared. +// The other bits are currently unused. For compatibility with Go 1.12 +// and earlier, use a non-zero mode. Use mode 0400 for a read-only +// file and 0600 for a readable+writable file. +func Chmod(name string, mode FileMode) error { + longName := fixLongPath(name) + e := ignoringEINTR(func() error { + return syscall.Chmod(longName, syscallMode(mode)) + }) + if e != nil { + return &PathError{Op: "chmod", Path: name, Err: e} + } + return nil +} + +// ignoringEINTR makes a function call and repeats it if it returns an +// EINTR error. This appears to be required even though we install all +// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. +// Also #20400 and #36644 are issues in which a signal handler is +// installed without setting SA_RESTART. None of these are the common case, +// but there are enough of them that it seems that we can't avoid +// an EINTR loop. +func ignoringEINTR(fn func() error) error { + for { + err := fn() + if err != syscall.EINTR { + return err + } + } +} + // handleSyscallError converts syscall errors into regular os package errors. // The err parameter must be either nil or of type syscall.Errno. func handleSyscallError(err error) error { @@ -148,3 +194,19 @@ func handleSyscallError(err error) error { return err } } + +// syscallMode returns the syscall-specific mode bits from Go's portable mode bits. +func syscallMode(i FileMode) (o uint32) { + o |= uint32(i.Perm()) + if i&ModeSetuid != 0 { + o |= syscall.S_ISUID + } + if i&ModeSetgid != 0 { + o |= syscall.S_ISGID + } + if i&ModeSticky != 0 { + o |= syscall.S_ISVTX + } + // No mapping for Go's ModeTemporary (plan9 only). + return +} diff --git a/src/os/file_unix.go b/src/os/file_unix.go index d797893c..51e1fffd 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -13,6 +13,11 @@ import ( type syscallFd = int +// fixLongPath is a noop on non-Windows platforms. +func fixLongPath(path string) string { + return path +} + func Pipe() (r *File, w *File, err error) { var p [2]int err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC)) @@ -50,19 +55,3 @@ func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) { } return } - -// ignoringEINTR makes a function call and repeats it if it returns an -// EINTR error. This appears to be required even though we install all -// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. -// Also #20400 and #36644 are issues in which a signal handler is -// installed without setting SA_RESTART. None of these are the common case, -// but there are enough of them that it seems that we can't avoid -// an EINTR loop. -func ignoringEINTR(fn func() error) error { - for { - err := fn() - if err != syscall.EINTR { - return err - } - } -} diff --git a/src/os/os_test.go b/src/os/os_test.go index 25c48778..9f3f6a1a 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -25,6 +25,32 @@ func newFile(testName string, t *testing.T) (f *File) { return } +func checkMode(t *testing.T, path string, mode FileMode) { + dir, err := Stat(path) + if err != nil { + t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err) + } + if dir.Mode()&ModePerm != mode { + t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode) + } +} + +func TestChmod(t *testing.T) { + f := newFile("TestChmod", t) + defer Remove(f.Name()) + defer f.Close() + // Creation mode is read write + + fm := FileMode(0456) + if runtime.GOOS == "windows" { + fm = FileMode(0444) // read-only file + } + if err := Chmod(f.Name(), fm); err != nil { + t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err) + } + checkMode(t, f.Name(), fm) +} + func TestReadAt(t *testing.T) { if runtime.GOOS == "windows" { t.Log("TODO: implement Pread for Windows") diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index bfa8a72e..4451cf76 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -68,6 +68,15 @@ func Chdir(path string) (err error) { return } +func Chmod(path string, mode uint32) (err error) { + data := cstring(path) + fail := int(libc_chmod(&data[0], mode)) + if fail < 0 { + err = getErrno() + } + return +} + func Mkdir(path string, mode uint32) (err error) { data := cstring(path) fail := int(libc_mkdir(&data[0], mode)) @@ -211,6 +220,10 @@ func libc_mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 //export chdir func libc_chdir(pathname *byte) int32 +// int chmod(const char *pathname, mode_t mode); +//export chmod +func libc_chmod(pathname *byte, mode uint32) int32 + // int mkdir(const char *pathname, mode_t mode); //export mkdir func libc_mkdir(pathname *byte, mode uint32) int32