rclone/cmd/serve/nfs/filesystem.go
Nick Craig-Wood 6ba3e24853 serve nfs: fix incorrect user id and group id exported to NFS #7973
Before this change all exports were exported as root and the --uid and
--gid flags of the VFS were ignored.

This fixes the issue by exporting the UID and GID correctly which
default to the current user and group unless set explicitly.
2024-09-04 10:50:21 +01:00

230 lines
6.4 KiB
Go

//go:build unix
package nfs
import (
"math"
"os"
"path"
"strings"
"time"
billy "github.com/go-git/go-billy/v5"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/log"
"github.com/rclone/rclone/vfs"
"github.com/rclone/rclone/vfs/vfscommon"
"github.com/willscott/go-nfs/file"
)
// setSys sets the Sys() call up for the vfs.Node passed in
//
// The billy abstraction layer does not extend to exposing `uid` and `gid`
// ownership of files. If ownership is important to your file system, you
// will need to ensure that the `os.FileInfo` meets additional constraints.
// In particular, the `Sys()` escape hatch is queried by this library, and
// if your file system populates a [`syscall.Stat_t`](https://golang.org/pkg/syscall/#Stat_t)
// concrete struct, the ownership specified in that object will be used.
// It can also return a file.FileInfo which is easier to manage cross platform
func setSys(fi os.FileInfo) {
node, ok := fi.(vfs.Node)
if !ok {
fs.Errorf(fi, "internal error: %T is not a vfs.Node", fi)
}
vfs := node.VFS()
// Set the UID and GID for the node passed in from the VFS defaults.
stat := file.FileInfo{
Nlink: 1,
UID: vfs.Opt.UID,
GID: vfs.Opt.GID,
Fileid: math.MaxUint64, // without this mounting doesn't work on Linux
}
node.SetSys(&stat)
}
// FS is our wrapper around the VFS to properly support billy.Filesystem interface
type FS struct {
vfs *vfs.VFS
}
// ReadDir implements read dir
func (f *FS) ReadDir(path string) (dir []os.FileInfo, err error) {
defer log.Trace(path, "")("items=%d, err=%v", &dir, &err)
dir, err = f.vfs.ReadDir(path)
if err != nil {
return nil, err
}
for _, fi := range dir {
setSys(fi)
}
return dir, nil
}
// Create implements creating new files
func (f *FS) Create(filename string) (node billy.File, err error) {
defer log.Trace(filename, "")("%v, err=%v", &node, &err)
return f.vfs.Create(filename)
}
// Open opens a file
func (f *FS) Open(filename string) (node billy.File, err error) {
defer log.Trace(filename, "")("%v, err=%v", &node, &err)
return f.vfs.Open(filename)
}
// OpenFile opens a file
func (f *FS) OpenFile(filename string, flag int, perm os.FileMode) (node billy.File, err error) {
defer log.Trace(filename, "flag=0x%X, perm=%v", flag, perm)("%v, err=%v", &node, &err)
return f.vfs.OpenFile(filename, flag, perm)
}
// Stat gets the file stat
func (f *FS) Stat(filename string) (fi os.FileInfo, err error) {
defer log.Trace(filename, "")("fi=%v, err=%v", &fi, &err)
fi, err = f.vfs.Stat(filename)
if err != nil {
return nil, err
}
setSys(fi)
return fi, nil
}
// Rename renames a file
func (f *FS) Rename(oldpath, newpath string) (err error) {
defer log.Trace(oldpath, "newpath=%q", newpath)("err=%v", &err)
return f.vfs.Rename(oldpath, newpath)
}
// Remove deletes a file
func (f *FS) Remove(filename string) (err error) {
defer log.Trace(filename, "")("err=%v", &err)
return f.vfs.Remove(filename)
}
// Join joins path elements
func (f *FS) Join(elem ...string) string {
return path.Join(elem...)
}
// TempFile is not implemented
func (f *FS) TempFile(dir, prefix string) (node billy.File, err error) {
defer log.Trace(dir, "prefix=%q", prefix)("node=%v, err=%v", &node, &err)
return nil, os.ErrInvalid
}
// MkdirAll creates a directory and all the ones above it
// it does not redirect to VFS.MkDirAll because that one doesn't
// honor the permissions
func (f *FS) MkdirAll(filename string, perm os.FileMode) (err error) {
defer log.Trace(filename, "perm=%v", perm)("err=%v", &err)
parts := strings.Split(filename, "/")
for i := range parts {
current := strings.Join(parts[:i+1], "/")
_, err := f.Stat(current)
if err == vfs.ENOENT {
err = f.vfs.Mkdir(current, perm)
if err != nil {
return err
}
}
}
return nil
}
// Lstat gets the stats for symlink
func (f *FS) Lstat(filename string) (fi os.FileInfo, err error) {
defer log.Trace(filename, "")("fi=%v, err=%v", &fi, &err)
fi, err = f.vfs.Stat(filename)
if err != nil {
return nil, err
}
setSys(fi)
return fi, nil
}
// Symlink is not supported over NFS
func (f *FS) Symlink(target, link string) (err error) {
defer log.Trace(target, "link=%q", link)("err=%v", &err)
return os.ErrInvalid
}
// Readlink is not supported
func (f *FS) Readlink(link string) (result string, err error) {
defer log.Trace(link, "")("result=%q, err=%v", &result, &err)
return "", os.ErrInvalid
}
// Chmod changes the file modes
func (f *FS) Chmod(name string, mode os.FileMode) (err error) {
defer log.Trace(name, "mode=%v", mode)("err=%v", &err)
file, err := f.vfs.Open(name)
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil {
fs.Logf(f, "Error while closing file: %e", err)
}
}()
err = file.Chmod(mode)
// Mask Chmod not implemented
if err == vfs.ENOSYS {
err = nil
}
return err
}
// Lchown changes the owner of symlink
func (f *FS) Lchown(name string, uid, gid int) (err error) {
defer log.Trace(name, "uid=%d, gid=%d", uid, gid)("err=%v", &err)
return f.Chown(name, uid, gid)
}
// Chown changes owner of the file
func (f *FS) Chown(name string, uid, gid int) (err error) {
defer log.Trace(name, "uid=%d, gid=%d", uid, gid)("err=%v", &err)
file, err := f.vfs.Open(name)
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil {
fs.Logf(f, "Error while closing file: %e", err)
}
}()
return file.Chown(uid, gid)
}
// Chtimes changes the acces time and modified time
func (f *FS) Chtimes(name string, atime time.Time, mtime time.Time) (err error) {
defer log.Trace(name, "atime=%v, mtime=%v", atime, mtime)("err=%v", &err)
return f.vfs.Chtimes(name, atime, mtime)
}
// Chroot is not supported in VFS
func (f *FS) Chroot(path string) (FS billy.Filesystem, err error) {
defer log.Trace(path, "")("FS=%v, err=%v", &FS, &err)
return nil, os.ErrInvalid
}
// Root returns the root of a VFS
func (f *FS) Root() (root string) {
defer log.Trace(nil, "")("root=%q", &root)
return f.vfs.Fs().Root()
}
// Capabilities exports the filesystem capabilities
func (f *FS) Capabilities() (caps billy.Capability) {
defer log.Trace(nil, "")("caps=%v", &caps)
if f.vfs.Opt.CacheMode == vfscommon.CacheModeOff {
return billy.ReadCapability | billy.SeekCapability
}
return billy.WriteCapability | billy.ReadCapability |
billy.ReadAndWriteCapability | billy.SeekCapability | billy.TruncateCapability
}
// Interface check
var (
_ billy.Filesystem = (*FS)(nil)
_ billy.Change = (*FS)(nil)
)