Add Chmod() method to File

`os.File` offers a `Chmod()` method. This is often safer and more direct
to use than `os.Chmod()` because it operates on an open file descriptor
rather than having to lookup the file by name. Without this, it's
possible for the target file to be renamed, in which case an
`os.Chmod()` would either fail or apply to any file that's taken its
place.

Therefore, add the `Chmod()` method to the `File` interface, and
implement it for all `File` implementations. The bulk of this change is
in `MemMapFs`, which required moving the chmod functionality down into
the `mem` package so that it can be shared between both `mem.File` and
`MemMapFs`.
This commit is contained in:
Oliver Bone 2022-05-20 12:19:26 +01:00 committed by Oliver Bone
parent 100c9a6d7b
commit 428eac8624
No known key found for this signature in database
GPG Key ID: 383B71DB6EEC1D18
13 changed files with 108 additions and 54 deletions

View File

@ -116,6 +116,7 @@ io.Seeker
io.Writer io.Writer
io.WriterAt io.WriterAt
Chmod(mode os.FileMode) : error
Name() : string Name() : string
Readdir(count int) : []os.FileInfo, error Readdir(count int) : []os.FileInfo, error
Readdirnames(n int) : []string, error Readdirnames(n int) : []string, error

View File

@ -42,6 +42,7 @@ type File interface {
io.Writer io.Writer
io.WriterAt io.WriterAt
Chmod(mode os.FileMode) error
Name() string Name() string
Readdir(count int) ([]os.FileInfo, error) Readdir(count int) ([]os.FileInfo, error)
Readdirnames(n int) ([]string, error) Readdirnames(n int) ([]string, error)

View File

@ -18,6 +18,7 @@ package gcsfs
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -175,6 +176,10 @@ func (o *GcsFile) WriteAt(b []byte, off int64) (n int, err error) {
return written, err return written, err
} }
func (o *GcsFile) Chmod(mode os.FileMode) error {
return errors.New("method Chmod is not implemented in GCS")
}
func (o *GcsFile) Name() string { func (o *GcsFile) Name() string {
return filepath.FromSlash(o.resource.name) return filepath.FromSlash(o.resource.name)
} }

View File

@ -203,6 +203,10 @@ type fromIOFSFile struct {
name string name string
} }
func (f fromIOFSFile) Chmod(mode os.FileMode) error {
return notImplemented("chmod", f.name)
}
func (f fromIOFSFile) ReadAt(p []byte, off int64) (n int, err error) { func (f fromIOFSFile) ReadAt(p []byte, off int64) (n int, err error) {
readerAt, ok := f.File.(io.ReaderAt) readerAt, ok := f.File.(io.ReaderAt)
if !ok { if !ok {

View File

@ -25,7 +25,11 @@ import (
"time" "time"
) )
const FilePathSeparator = string(filepath.Separator) const (
FilePathSeparator = string(filepath.Separator)
chmodBits = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky // Only a subset of bits are allowed to be changed. Documented under os.Chmod()
)
type File struct { type File struct {
// atomic requires 64-bit alignment for struct field access // atomic requires 64-bit alignment for struct field access
@ -67,11 +71,20 @@ func (d *FileData) Name() string {
} }
func CreateFile(name string) *FileData { func CreateFile(name string) *FileData {
return &FileData{name: name, mode: os.ModeTemporary, modtime: time.Now()} return &FileData{
name: name,
modtime: time.Now(),
}
} }
func CreateDir(name string) *FileData { func CreateDir(name string) *FileData {
return &FileData{name: name, memDir: &DirMap{}, dir: true, modtime: time.Now()} return &FileData{
name: name,
mode: os.ModeDir,
memDir: &DirMap{},
dir: true,
modtime: time.Now(),
}
} }
func ChangeFileName(f *FileData, newname string) { func ChangeFileName(f *FileData, newname string) {
@ -80,9 +93,9 @@ func ChangeFileName(f *FileData, newname string) {
f.Unlock() f.Unlock()
} }
func SetMode(f *FileData, mode os.FileMode) { func Chmod(f *FileData, mode os.FileMode) {
f.Lock() f.Lock()
f.mode = mode f.mode = (f.mode & ^chmodBits) | (mode & chmodBits)
f.Unlock() f.Unlock()
} }
@ -131,6 +144,11 @@ func (f *File) Close() error {
return nil return nil
} }
func (f *File) Chmod(mode os.FileMode) error {
Chmod(f.fileData, mode)
return nil
}
func (f *File) Name() string { func (f *File) Name() string {
return f.fileData.Name() return f.fileData.Name()
} }

View File

@ -81,13 +81,13 @@ func TestFileDataModeRace(t *testing.T) {
t.Errorf("Failed to read correct value, was %v", s.Mode()) t.Errorf("Failed to read correct value, was %v", s.Mode())
} }
SetMode(&d, someOtherMode) Chmod(&d, someOtherMode)
if s.Mode() != someOtherMode { if s.Mode() != someOtherMode {
t.Errorf("Failed to set Mode, was %v", s.Mode()) t.Errorf("Failed to set Mode, was %v", s.Mode())
} }
go func() { go func() {
SetMode(&d, someMode) Chmod(&d, someMode)
}() }()
if s.Mode() != someMode && s.Mode() != someOtherMode { if s.Mode() != someMode && s.Mode() != someOtherMode {

View File

@ -25,8 +25,6 @@ import (
"github.com/spf13/afero/mem" "github.com/spf13/afero/mem"
) )
const chmodBits = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky // Only a subset of bits are allowed to be changed. Documented under os.Chmod()
type MemMapFs struct { type MemMapFs struct {
mu sync.RWMutex mu sync.RWMutex
data map[string]*mem.FileData data map[string]*mem.FileData
@ -43,7 +41,7 @@ func (m *MemMapFs) getData() map[string]*mem.FileData {
// Root should always exist, right? // Root should always exist, right?
// TODO: what about windows? // TODO: what about windows?
root := mem.CreateDir(FilePathSeparator) root := mem.CreateDir(FilePathSeparator)
mem.SetMode(root, os.ModeDir|0755) mem.Chmod(root, 0755)
m.data[FilePathSeparator] = root m.data[FilePathSeparator] = root
}) })
return m.data return m.data
@ -123,7 +121,7 @@ func (m *MemMapFs) lockfreeMkdir(name string, perm os.FileMode) error {
} }
} else { } else {
item := mem.CreateDir(name) item := mem.CreateDir(name)
mem.SetMode(item, os.ModeDir|perm) mem.Chmod(item, perm)
m.getData()[name] = item m.getData()[name] = item
m.registerWithParent(item, perm) m.registerWithParent(item, perm)
} }
@ -131,7 +129,6 @@ func (m *MemMapFs) lockfreeMkdir(name string, perm os.FileMode) error {
} }
func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error { func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
perm &= chmodBits
name = normalizePath(name) name = normalizePath(name)
m.mu.RLock() m.mu.RLock()
@ -143,12 +140,12 @@ func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
m.mu.Lock() m.mu.Lock()
item := mem.CreateDir(name) item := mem.CreateDir(name)
mem.SetMode(item, os.ModeDir|perm) mem.Chmod(item, perm)
m.getData()[name] = item m.getData()[name] = item
m.registerWithParent(item, perm) m.registerWithParent(item, perm)
m.mu.Unlock() m.mu.Unlock()
return m.setFileMode(name, perm|os.ModeDir) return nil
} }
func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error { func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error {
@ -215,7 +212,6 @@ func (m *MemMapFs) lockfreeOpen(name string) (*mem.FileData, error) {
} }
func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
perm &= chmodBits
chmod := false chmod := false
file, err := m.openWrite(name) file, err := m.openWrite(name)
if err == nil && (flag&os.O_EXCL > 0) { if err == nil && (flag&os.O_EXCL > 0) {
@ -246,7 +242,7 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro
} }
} }
if chmod { if chmod {
return file, m.setFileMode(name, perm) m.Chmod(name, perm)
} }
return file, nil return file, nil
} }
@ -331,45 +327,30 @@ func (m *MemMapFs) Stat(name string) (os.FileInfo, error) {
return fi, nil return fi, nil
} }
func (m *MemMapFs) Chmod(name string, mode os.FileMode) error { func (m *MemMapFs) getFileData(name string) *mem.FileData {
mode &= chmodBits
m.mu.RLock()
f, ok := m.getData()[name]
m.mu.RUnlock()
if !ok {
return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound}
}
prevOtherBits := mem.GetFileInfo(f).Mode() & ^chmodBits
mode = prevOtherBits | mode
return m.setFileMode(name, mode)
}
func (m *MemMapFs) setFileMode(name string, mode os.FileMode) error {
name = normalizePath(name) name = normalizePath(name)
m.mu.RLock() m.mu.RLock()
f, ok := m.getData()[name] f := m.getData()[name]
m.mu.RUnlock() m.mu.RUnlock()
if !ok {
return f
}
func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
f := m.getFileData(name)
if f == nil {
return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound} return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound}
} }
m.mu.Lock() mem.Chmod(f, mode)
mem.SetMode(f, mode)
m.mu.Unlock()
return nil return nil
} }
func (m *MemMapFs) Chown(name string, uid, gid int) error { func (m *MemMapFs) Chown(name string, uid, gid int) error {
name = normalizePath(name) f := m.getFileData(name)
if f == nil {
m.mu.RLock()
f, ok := m.getData()[name]
m.mu.RUnlock()
if !ok {
return &os.PathError{Op: "chown", Path: name, Err: ErrFileNotFound} return &os.PathError{Op: "chown", Path: name, Err: ErrFileNotFound}
} }
@ -380,18 +361,12 @@ func (m *MemMapFs) Chown(name string, uid, gid int) error {
} }
func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error { func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
name = normalizePath(name) f := m.getFileData(name)
if f == nil {
m.mu.RLock()
f, ok := m.getData()[name]
m.mu.RUnlock()
if !ok {
return &os.PathError{Op: "chtimes", Path: name, Err: ErrFileNotFound} return &os.PathError{Op: "chtimes", Path: name, Err: ErrFileNotFound}
} }
m.mu.Lock()
mem.SetModTime(f, mtime) mem.SetModTime(f, mtime)
m.mu.Unlock()
return nil return nil
} }

View File

@ -614,7 +614,7 @@ func TestMemFsChmod(t *testing.T) {
t.Fatal("mkdir failed to create a directory: mode =", info.Mode()) t.Fatal("mkdir failed to create a directory: mode =", info.Mode())
} }
err = fs.Chmod(file, 0) err = fs.Chmod(file, os.ModeTemporary|0355)
if err != nil { if err != nil {
t.Error("Failed to run chmod:", err) t.Error("Failed to run chmod:", err)
} }
@ -623,9 +623,29 @@ func TestMemFsChmod(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if info.Mode().String() != "d---------" { if info.Mode().String() != "d-wxr-xr-x" {
t.Error("chmod should not change file type. New mode =", info.Mode()) t.Error("incorrect file mode after chmod:", info.Mode())
} }
f, err := fs.Open(file)
if err != nil {
t.Error("failed to open file:", err)
}
err = f.Chmod(os.ModeNamedPipe | 0744)
if err != nil {
t.Error("Failed to run chmod:", err)
}
info, err = fs.Stat(file)
if err != nil {
t.Fatal(err)
}
if info.Mode().String() != "drwxr--r--" {
t.Error("incorrect file mode after chmod:", info.Mode())
}
f.Close()
} }
// can't use Mkdir to get around which permissions we're allowed to set // can't use Mkdir to get around which permissions we're allowed to set

View File

@ -178,6 +178,10 @@ func (f *RegexpFile) WriteAt(s []byte, o int64) (int, error) {
return f.f.WriteAt(s, o) return f.f.WriteAt(s, o)
} }
func (f *RegexpFile) Chmod(mode os.FileMode) error {
return f.f.Chmod(mode)
}
func (f *RegexpFile) Name() string { func (f *RegexpFile) Name() string {
return f.f.Name() return f.f.Name()
} }

View File

@ -44,6 +44,10 @@ func (f *File) Close() error {
return f.fd.Close() return f.fd.Close()
} }
func (f *File) Chmod(mode os.FileMode) error {
return f.fd.Chmod(mode)
}
func (f *File) Name() string { func (f *File) Name() string {
return f.fd.Name() return f.fd.Name()
} }

View File

@ -71,6 +71,10 @@ func (f *File) Write(p []byte) (n int, err error) { return 0, syscall.EROFS }
func (f *File) WriteAt(p []byte, off int64) (n int, err error) { return 0, syscall.EROFS } func (f *File) WriteAt(p []byte, off int64) (n int, err error) { return 0, syscall.EROFS }
func (f *File) Chmod(mode os.FileMode) error {
return syscall.EROFS
}
func (f *File) Name() string { func (f *File) Name() string {
return filepath.Join(splitpath(f.h.Name)) return filepath.Join(splitpath(f.h.Name))
} }

View File

@ -41,6 +41,20 @@ func (f *UnionFile) Close() error {
return BADFD return BADFD
} }
func (f *UnionFile) Chmod(mode os.FileMode) error {
if f.Layer != nil {
err := f.Layer.Chmod(mode)
if err == nil && f.Base != nil {
err = f.Base.Chmod(mode)
}
return err
}
if f.Base != nil {
return f.Base.Chmod(mode)
}
return BADFD
}
func (f *UnionFile) Read(s []byte) (int, error) { func (f *UnionFile) Read(s []byte) (int, error) {
if f.Layer != nil { if f.Layer != nil {
n, err := f.Layer.Read(s) n, err := f.Layer.Read(s)

View File

@ -104,6 +104,10 @@ func (f *File) Write(p []byte) (n int, err error) { return 0, syscall.EPERM }
func (f *File) WriteAt(p []byte, off int64) (n int, err error) { return 0, syscall.EPERM } func (f *File) WriteAt(p []byte, off int64) (n int, err error) { return 0, syscall.EPERM }
func (f *File) Chmod(mode os.FileMode) error {
return syscall.EPERM
}
func (f *File) Name() string { func (f *File) Name() string {
if f.zipfile == nil { if f.zipfile == nil {
return string(filepath.Separator) return string(filepath.Separator)