From 0b93b7668307d822891556804f5933239ba84428 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Tue, 21 Oct 2025 22:17:52 +0200 Subject: [PATCH] Add support for Chmod on billy.Filesystem The billy package contains a `Change` interface type, which seems to have gone unused for several years now, presumably due to the difficulty of implementing most of the required methods for all supported platforms. This commit reduces the `Change` interface to a `Chmod` interface, which is supported on all platforms. The interface is implemented in all applicable abstractions. Supporting `chmod` in billy would help with issues such as https://github.com/go-git/go-git/issues/588 (which was closed as stale, but is unfortunately still a real issue for some folks). Signed-off-by: Conrad Hoffmann --- fs.go | 17 ++--------------- helper/chroot/chroot.go | 9 +++++++++ memfs/memory.go | 4 ++++ memfs/storage.go | 12 ++++++++++++ osfs/os_bound.go | 8 ++++++++ osfs/os_chroot.go | 4 ++++ 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/fs.go b/fs.go index b822ff3..0269156 100644 --- a/fs.go +++ b/fs.go @@ -4,7 +4,6 @@ import ( "errors" "io" "io/fs" - "time" ) var ( @@ -132,24 +131,12 @@ type Symlink interface { Readlink(link string) (string, error) } -// Change abstract the FileInfo change related operations in a storage-agnostic +// Chmod abstract the FileInfo change related operations in a storage-agnostic // interface as an extension to the Basic interface -type Change interface { +type Chmod interface { // 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. Chmod(name string, mode fs.FileMode) error - // Lchown changes the numeric uid and gid of the named file. If the file is - // a symbolic link, it changes the uid and gid of the link itself. - Lchown(name string, uid, gid int) error - // Chown changes the numeric uid and gid of the named file. If the file is a - // symbolic link, it changes the uid and gid of the link's target. - Chown(name string, uid, gid int) error - // Chtimes changes the access and modification times of the named file, - // similar to the Unix utime() or utimes() functions. - // - // The underlying filesystem may truncate or round the values to a less - // precise time unit. - Chtimes(name string, atime time.Time, mtime time.Time) error } // Chroot abstract the chroot related operations in a storage-agnostic interface diff --git a/helper/chroot/chroot.go b/helper/chroot/chroot.go index c54cd73..6a00baa 100644 --- a/helper/chroot/chroot.go +++ b/helper/chroot/chroot.go @@ -201,6 +201,15 @@ func (fs *ChrootHelper) Readlink(link string) (string, error) { return string(os.PathSeparator) + target, nil } +func (fs *ChrootHelper) Chmod(path string, mode fs.FileMode) error { + fullpath, err := fs.underlyingPath(path) + if err != nil { + return err + } + + return fs.underlying.(billy.Chmod).Chmod(fullpath, mode) +} + func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) { fullpath, err := fs.underlyingPath(path) if err != nil { diff --git a/memfs/memory.go b/memfs/memory.go index 6120515..d5b1b65 100644 --- a/memfs/memory.go +++ b/memfs/memory.go @@ -177,6 +177,10 @@ func (fs *Memory) Remove(filename string) error { return fs.s.Remove(filename) } +func (fs *Memory) Chmod(path string, mode gofs.FileMode) error { + return fs.s.Chmod(path, mode) +} + // Falls back to Go's filepath.Join, which works differently depending on the // OS where the code is being executed. func (fs *Memory) Join(elem ...string) string { diff --git a/memfs/storage.go b/memfs/storage.go index f7c0d1e..be0649a 100644 --- a/memfs/storage.go +++ b/memfs/storage.go @@ -222,6 +222,18 @@ func (s *storage) Remove(path string) error { return nil } +func (s *storage) Chmod(path string, mode fs.FileMode) error { + path = clean(path) + + f, has := s.Get(path) + if !has { + return os.ErrNotExist + } + + f.mode = mode + return nil +} + func clean(path string) string { return filepath.Clean(filepath.FromSlash(path)) } diff --git a/osfs/os_bound.go b/osfs/os_bound.go index 1ddf42c..41e1e42 100644 --- a/osfs/os_bound.go +++ b/osfs/os_bound.go @@ -226,6 +226,14 @@ func (fs *BoundOS) Readlink(link string) (string, error) { return os.Readlink(link) } +func (fs *BoundOS) Chmod(path string, mode fs.FileMode) error { + abspath, err := fs.abs(path) + if err != nil { + return err + } + return os.Chmod(abspath, mode) +} + // Chroot returns a new BoundOS filesystem, with the base dir set to the // result of joining the provided path with the underlying base dir. func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) { diff --git a/osfs/os_chroot.go b/osfs/os_chroot.go index c257e5e..efd28a0 100644 --- a/osfs/os_chroot.go +++ b/osfs/os_chroot.go @@ -80,6 +80,10 @@ func (fs *ChrootOS) Remove(filename string) error { return os.Remove(filename) } +func (fs *ChrootOS) Chmod(path string, mode fs.FileMode) error { + return os.Chmod(path, mode) +} + func (fs *ChrootOS) TempFile(dir, prefix string) (billy.File, error) { if err := fs.createDir(dir + string(os.PathSeparator)); err != nil { return nil, err