add setstat_mode 2

in this mode chmod/chtimes/chown can be silently ignored only for cloud
based file systems

Fixes #223
This commit is contained in:
Nicola Murino 2020-11-12 10:39:46 +01:00
parent 38e0cba675
commit 5720d40fee
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
13 changed files with 117 additions and 56 deletions

View file

@ -223,6 +223,8 @@ type Configuration struct {
Actions ProtocolActions `json:"actions" mapstructure:"actions"` Actions ProtocolActions `json:"actions" mapstructure:"actions"`
// SetstatMode 0 means "normal mode": requests for changing permissions and owner/group are executed. // SetstatMode 0 means "normal mode": requests for changing permissions and owner/group are executed.
// 1 means "ignore mode": requests for changing permissions and owner/group are silently ignored. // 1 means "ignore mode": requests for changing permissions and owner/group are silently ignored.
// 2 means "ignore mode for cloud fs": requests for changing permissions and owner/group/time are
// silently ignored for cloud based filesystem such as S3, GCS, Azure Blob
SetstatMode int `json:"setstat_mode" mapstructure:"setstat_mode"` SetstatMode int `json:"setstat_mode" mapstructure:"setstat_mode"`
// Support for HAProxy PROXY protocol. // Support for HAProxy PROXY protocol.
// If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable // If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable

View file

@ -448,17 +448,28 @@ func (c *BaseConnection) DoStat(fsPath string, mode int) (os.FileInfo, error) {
return c.Fs.Stat(c.getRealFsPath(fsPath)) return c.Fs.Stat(c.getRealFsPath(fsPath))
} }
// SetStat set StatAttributes for the specified fsPath func (c *BaseConnection) ignoreSetStat() bool {
func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAttributes) error {
if Config.SetstatMode == 1 { if Config.SetstatMode == 1 {
return nil return true
} }
if Config.SetstatMode == 2 && !vfs.IsLocalOsFs(c.Fs) {
return true
}
return false
}
// SetStat set StatAttributes for the specified fsPath
// nolint:gocyclo
func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAttributes) error {
pathForPerms := c.getPathForSetStatPerms(fsPath, virtualPath) pathForPerms := c.getPathForSetStatPerms(fsPath, virtualPath)
if attributes.Flags&StatAttrPerms != 0 { if attributes.Flags&StatAttrPerms != 0 {
if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) { if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
return c.GetPermissionDeniedError() return c.GetPermissionDeniedError()
} }
if c.ignoreSetStat() {
return nil
}
if err := c.Fs.Chmod(c.getRealFsPath(fsPath), attributes.Mode); err != nil { if err := c.Fs.Chmod(c.getRealFsPath(fsPath), attributes.Mode); err != nil {
c.Log(logger.LevelWarn, "failed to chmod path %#v, mode: %v, err: %+v", fsPath, attributes.Mode.String(), err) c.Log(logger.LevelWarn, "failed to chmod path %#v, mode: %v, err: %+v", fsPath, attributes.Mode.String(), err)
return c.GetFsError(err) return c.GetFsError(err)
@ -471,6 +482,9 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
if !c.User.HasPerm(dataprovider.PermChown, pathForPerms) { if !c.User.HasPerm(dataprovider.PermChown, pathForPerms) {
return c.GetPermissionDeniedError() return c.GetPermissionDeniedError()
} }
if c.ignoreSetStat() {
return nil
}
if err := c.Fs.Chown(c.getRealFsPath(fsPath), attributes.UID, attributes.GID); err != nil { if err := c.Fs.Chown(c.getRealFsPath(fsPath), attributes.UID, attributes.GID); err != nil {
c.Log(logger.LevelWarn, "failed to chown path %#v, uid: %v, gid: %v, err: %+v", fsPath, attributes.UID, c.Log(logger.LevelWarn, "failed to chown path %#v, uid: %v, gid: %v, err: %+v", fsPath, attributes.UID,
attributes.GID, err) attributes.GID, err)
@ -484,7 +498,9 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
if !c.User.HasPerm(dataprovider.PermChtimes, pathForPerms) { if !c.User.HasPerm(dataprovider.PermChtimes, pathForPerms) {
return c.GetPermissionDeniedError() return c.GetPermissionDeniedError()
} }
if c.ignoreSetStat() {
return nil
}
if err := c.Fs.Chtimes(c.getRealFsPath(fsPath), attributes.Atime, attributes.Mtime); err != nil { if err := c.Fs.Chtimes(c.getRealFsPath(fsPath), attributes.Atime, attributes.Mtime); err != nil {
c.Log(logger.LevelWarn, "failed to chtimes for path %#v, access time: %v, modification time: %v, err: %+v", c.Log(logger.LevelWarn, "failed to chtimes for path %#v, access time: %v, modification time: %v, err: %+v",
fsPath, attributes.Atime, attributes.Mtime, err) fsPath, attributes.Atime, attributes.Mtime, err)
@ -950,6 +966,8 @@ func (c *BaseConnection) GetFsError(err error) error {
return c.GetNotExistError() return c.GetNotExistError()
} else if c.Fs.IsPermission(err) { } else if c.Fs.IsPermission(err) {
return c.GetPermissionDeniedError() return c.GetPermissionDeniedError()
} else if c.Fs.IsNotSupported(err) {
return c.GetOpUnsupportedError()
} else if err != nil { } else if err != nil {
return c.GetGenericError(err) return c.GetGenericError(err)
} }

View file

@ -496,6 +496,29 @@ func TestSetStat(t *testing.T) {
err = c.SetStat(user.GetHomeDir(), "/", &StatAttributes{}) err = c.SetStat(user.GetHomeDir(), "/", &StatAttributes{})
assert.NoError(t, err) assert.NoError(t, err)
err = c.SetStat(dir2, "/dir1/file", &StatAttributes{
Mode: os.ModePerm,
Flags: StatAttrPerms,
})
assert.NoError(t, err)
err = c.SetStat(dir1, "/dir2/file", &StatAttributes{
UID: os.Getuid(),
GID: os.Getgid(),
Flags: StatAttrUIDGID,
})
assert.NoError(t, err)
err = c.SetStat(dir1, "/dir3/file", &StatAttributes{
Atime: time.Now(),
Mtime: time.Now(),
Flags: StatAttrTimes,
})
assert.NoError(t, err)
Config.SetstatMode = 2
assert.False(t, c.ignoreSetStat())
c1 := NewBaseConnection("", ProtocolSFTP, user, newMockOsFs(false, fs.ConnectionID(), user.GetHomeDir()))
assert.True(t, c1.ignoreSetStat())
Config.SetstatMode = oldSetStatMode Config.SetstatMode = oldSetStatMode
// chmod // chmod
err = c.SetStat(dir1, "/dir1/file", &StatAttributes{ err = c.SetStat(dir1, "/dir1/file", &StatAttributes{
@ -1146,6 +1169,12 @@ func TestErrorsMapping(t *testing.T) {
} else { } else {
assert.EqualError(t, err, ErrPermissionDenied.Error()) assert.EqualError(t, err, ErrPermissionDenied.Error())
} }
err = conn.GetFsError(vfs.ErrVfsUnsupported)
if protocol == ProtocolSFTP {
assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
} else {
assert.EqualError(t, err, ErrOpUnsupported.Error())
}
err = conn.GetFsError(nil) err = conn.GetFsError(nil)
assert.NoError(t, err) assert.NoError(t, err)
err = conn.GetOpUnsupportedError() err = conn.GetOpUnsupportedError()

View file

@ -10,7 +10,7 @@ if [ "$1" = 'sftpgo' ]; then
DIR_UID=$(stat -c %u ${DIR}) DIR_UID=$(stat -c %u ${DIR})
DIR_GID=$(stat -c %g ${DIR}) DIR_GID=$(stat -c %g ${DIR})
if [ ${DIR_UID} != ${SFTPGO_PUID} ] || [ ${DIR_GID} != ${SFTPGO_PGID} ]; then if [ ${DIR_UID} != ${SFTPGO_PUID} ] || [ ${DIR_GID} != ${SFTPGO_PGID} ]; then
echo `date +%Y-%m-%dT%H:%M:%S` - "entrypoint, change owner for ${DIR} uid: ${SFTPGO_PUID} gid: ${SFTPGO_PGID}" echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.000`'","sender":"entrypoint","message":"change owner for \"'${DIR}'\" UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
if [ ${DIR} = "/etc/sftpgo" ]; then if [ ${DIR} = "/etc/sftpgo" ]; then
chown -R ${SFTPGO_PUID}:${SFTPGO_PGID} ${DIR} chown -R ${SFTPGO_PUID}:${SFTPGO_PGID} ${DIR}
else else
@ -18,7 +18,7 @@ if [ "$1" = 'sftpgo' ]; then
fi fi
fi fi
done done
echo `date +%Y-%m-%dT%H:%M:%S` - "entrypoint, run as uid: ${SFTPGO_PUID} gid: ${SFTPGO_PGID}" echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.000`'","sender":"entrypoint","message":"run as UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
exec su-exec ${SFTPGO_PUID}:${SFTPGO_PGID} "$@" exec su-exec ${SFTPGO_PUID}:${SFTPGO_PGID} "$@"
fi fi

View file

@ -10,19 +10,19 @@ if [ "$1" = 'sftpgo' ]; then
getent group ${SFTPGO_PGID} > /dev/null getent group ${SFTPGO_PGID} > /dev/null
HAS_PGID=$? HAS_PGID=$?
if [ ${HAS_PUID} -ne 0 ] || [ ${HAS_PGID} -ne 0 ]; then if [ ${HAS_PUID} -ne 0 ] || [ ${HAS_PGID} -ne 0 ]; then
echo `date +%Y-%m-%dT%H:%M:%S.%3N` - "entrypoint, prepare to run as uid: ${SFTPGO_PUID} gid: ${SFTPGO_PGID}" echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"prepare to run as UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
if [ ${HAS_PGID} -ne 0 ]; then if [ ${HAS_PGID} -ne 0 ]; then
echo `date +%Y-%m-%dT%H:%M:%S.%3N` - "entrypoint, set GID to: ${SFTPGO_PGID}" echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"set GID to: '${SFTPGO_PGID}'"}'
groupmod -g ${SFTPGO_PGID} sftpgo groupmod -g ${SFTPGO_PGID} sftpgo
fi fi
if [ ${HAS_PUID} -ne 0 ]; then if [ ${HAS_PUID} -ne 0 ]; then
echo `date +%Y-%m-%dT%H:%M:%S.%3N` - "entrypoint, set UID to: ${SFTPGO_PUID}" echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"set UID to: '${SFTPGO_PUID}'"}'
usermod -u ${SFTPGO_PUID} sftpgo usermod -u ${SFTPGO_PUID} sftpgo
fi fi
chown -R ${SFTPGO_PUID}:${SFTPGO_PGID} /etc/sftpgo chown -R ${SFTPGO_PUID}:${SFTPGO_PGID} /etc/sftpgo
chown ${SFTPGO_PUID}:${SFTPGO_PGID} /var/lib/sftpgo /srv/sftpgo chown ${SFTPGO_PUID}:${SFTPGO_PGID} /var/lib/sftpgo /srv/sftpgo
fi fi
echo `date +%Y-%m-%dT%H:%M:%S.%3N` - "entrypoint, run as uid: ${SFTPGO_PUID} gid: ${SFTPGO_PGID}" echo '{"level":"info","time":"'`date +%Y-%m-%dT%H:%M:%S.%3N`'","sender":"entrypoint","message":"run as UID: '${SFTPGO_PUID}' GID: '${SFTPGO_PGID}'"}'
exec gosu ${SFTPGO_PUID}:${SFTPGO_PGID} "$@" exec gosu ${SFTPGO_PUID}:${SFTPGO_PGID} "$@"
fi fi

View file

@ -54,7 +54,7 @@ The configuration file contains the following sections:
- `actions`, struct. It contains the command to execute and/or the HTTP URL to notify and the trigger conditions. See [Custom Actions](./custom-actions.md) for more details - `actions`, struct. It contains the command to execute and/or the HTTP URL to notify and the trigger conditions. See [Custom Actions](./custom-actions.md) for more details
- `execute_on`, list of strings. Valid values are `download`, `upload`, `pre-delete`, `delete`, `rename`, `ssh_cmd`. Leave empty to disable actions. - `execute_on`, list of strings. Valid values are `download`, `upload`, `pre-delete`, `delete`, `rename`, `ssh_cmd`. Leave empty to disable actions.
- `hook`, string. Absolute path to the command to execute or HTTP URL to notify. - `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
- `setstat_mode`, integer. 0 means "normal mode": requests for changing permissions, owner/group and access/modification times are executed. 1 means "ignore mode": requests for changing permissions, owner/group and access/modification times are silently ignored. - `setstat_mode`, integer. 0 means "normal mode": requests for changing permissions, owner/group and access/modification times are executed. 1 means "ignore mode": requests for changing permissions, owner/group and access/modification times are silently ignored. 2 means "ignore mode for cloud based filesystems": requests for changing permissions, owner/group and access/modification times are silently ignored for cloud filesystems and executed for local filesystem.
- `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported. If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too. For example, for HAProxy, add `send-proxy` or `send-proxy-v2` to each server configuration line. The following modes are supported: - `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Both protocol versions 1 and 2 are supported. If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too. For example, for HAProxy, add `send-proxy` or `send-proxy-v2` to each server configuration line. The following modes are supported:
- 0, disabled - 0, disabled
- 1, enabled. Proxy header will be used and requests without proxy header will be accepted - 1, enabled. Proxy header will be used and requests without proxy header will be accepted

View file

@ -20,9 +20,8 @@ The configured bucket must exist.
Some SFTP commands don't work over S3: Some SFTP commands don't work over S3:
- `symlink` and `chtimes` will fail - `chtimes`, `chown` and `chmod` will fail. If you want to silently ignore these method set `setstat_mode` to `1` or `2` in your configuration file
- `chown` and `chmod` are silently ignored - `truncate`, `symlink`, `readlink` are not supported
- `truncate` is not supported
- opening a file for both reading and writing at the same time is not supported - opening a file for both reading and writing at the same time is not supported
- upload resume is not supported - upload resume is not supported
- upload mode `atomic` is ignored since S3 uploads are already atomic - upload mode `atomic` is ignored since S3 uploads are already atomic

View file

@ -397,17 +397,6 @@ func TestSFTPCmdTargetPath(t *testing.T) {
assert.True(t, os.IsNotExist(err)) assert.True(t, os.IsNotExist(err))
} }
func TestSetstatModeIgnore(t *testing.T) {
originalMode := common.Config.SetstatMode
common.Config.SetstatMode = 1
connection := Connection{}
request := sftp.NewRequest("Setstat", "invalid")
request.Flags = 0
err := connection.handleSFTPSetstat("invalid", request)
assert.NoError(t, err)
common.Config.SetstatMode = originalMode
}
func TestSFTPGetUsedQuota(t *testing.T) { func TestSFTPGetUsedQuota(t *testing.T) {
u := dataprovider.User{} u := dataprovider.User{}
u.HomeDir = "home_rel_path" u.HomeDir = "home_rel_path"

View file

@ -361,37 +361,34 @@ func (fs *AzureBlobFs) Mkdir(name string) error {
// Symlink creates source as a symbolic link to target. // Symlink creates source as a symbolic link to target.
func (*AzureBlobFs) Symlink(source, target string) error { func (*AzureBlobFs) Symlink(source, target string) error {
return errors.New("403 symlinks are not supported") return ErrVfsUnsupported
} }
// Readlink returns the destination of the named symbolic link // Readlink returns the destination of the named symbolic link
func (*AzureBlobFs) Readlink(name string) (string, error) { func (*AzureBlobFs) Readlink(name string) (string, error) {
return "", errors.New("403 readlink is not supported") return "", ErrVfsUnsupported
} }
// Chown changes the numeric uid and gid of the named file. // Chown changes the numeric uid and gid of the named file.
// Silently ignored.
func (*AzureBlobFs) Chown(name string, uid int, gid int) error { func (*AzureBlobFs) Chown(name string, uid int, gid int) error {
return nil return ErrVfsUnsupported
} }
// Chmod changes the mode of the named file to mode. // Chmod changes the mode of the named file to mode.
// Silently ignored.
func (*AzureBlobFs) Chmod(name string, mode os.FileMode) error { func (*AzureBlobFs) Chmod(name string, mode os.FileMode) error {
return nil return ErrVfsUnsupported
} }
// Chtimes changes the access and modification times of the named file. // Chtimes changes the access and modification times of the named file.
// Silently ignored.
func (*AzureBlobFs) Chtimes(name string, atime, mtime time.Time) error { func (*AzureBlobFs) Chtimes(name string, atime, mtime time.Time) error {
return errors.New("403 chtimes is not supported") return ErrVfsUnsupported
} }
// Truncate changes the size of the named file. // Truncate changes the size of the named file.
// Truncate by path is not supported, while truncating an opened // Truncate by path is not supported, while truncating an opened
// file is handled inside base transfer // file is handled inside base transfer
func (*AzureBlobFs) Truncate(name string, size int64) error { func (*AzureBlobFs) Truncate(name string, size int64) error {
return errors.New("403 truncate is not supported") return ErrVfsUnsupported
} }
// ReadDir reads the directory named by dirname and returns // ReadDir reads the directory named by dirname and returns
@ -519,6 +516,14 @@ func (*AzureBlobFs) IsPermission(err error) bool {
return strings.Contains(err.Error(), "403") return strings.Contains(err.Error(), "403")
} }
// IsNotSupported returns true if the error indicate an unsupported operation
func (*AzureBlobFs) IsNotSupported(err error) bool {
if err == nil {
return false
}
return err == ErrVfsUnsupported
}
// CheckRootPath creates the specified local root directory if it does not exists // CheckRootPath creates the specified local root directory if it does not exists
func (fs *AzureBlobFs) CheckRootPath(username string, uid int, gid int) bool { func (fs *AzureBlobFs) CheckRootPath(username string, uid int, gid int) bool {
// we need a local directory for temporary files // we need a local directory for temporary files
@ -575,7 +580,7 @@ func (fs *AzureBlobFs) ScanRootDirContents() (int, int64, error) {
// GetDirSize returns the number of files and the size for a folder // GetDirSize returns the number of files and the size for a folder
// including any subfolders // including any subfolders
func (*AzureBlobFs) GetDirSize(dirname string) (int, int64, error) { func (*AzureBlobFs) GetDirSize(dirname string) (int, int64, error) {
return 0, 0, errUnsupported return 0, 0, ErrVfsUnsupported
} }
// GetAtomicUploadPath returns the path to use for an atomic upload. // GetAtomicUploadPath returns the path to use for an atomic upload.

View file

@ -314,37 +314,34 @@ func (fs *GCSFs) Mkdir(name string) error {
// Symlink creates source as a symbolic link to target. // Symlink creates source as a symbolic link to target.
func (*GCSFs) Symlink(source, target string) error { func (*GCSFs) Symlink(source, target string) error {
return errors.New("403 symlinks are not supported") return ErrVfsUnsupported
} }
// Readlink returns the destination of the named symbolic link // Readlink returns the destination of the named symbolic link
func (*GCSFs) Readlink(name string) (string, error) { func (*GCSFs) Readlink(name string) (string, error) {
return "", errors.New("403 readlink is not supported") return "", ErrVfsUnsupported
} }
// Chown changes the numeric uid and gid of the named file. // Chown changes the numeric uid and gid of the named file.
// Silently ignored.
func (*GCSFs) Chown(name string, uid int, gid int) error { func (*GCSFs) Chown(name string, uid int, gid int) error {
return nil return ErrVfsUnsupported
} }
// Chmod changes the mode of the named file to mode. // Chmod changes the mode of the named file to mode.
// Silently ignored.
func (*GCSFs) Chmod(name string, mode os.FileMode) error { func (*GCSFs) Chmod(name string, mode os.FileMode) error {
return nil return ErrVfsUnsupported
} }
// Chtimes changes the access and modification times of the named file. // Chtimes changes the access and modification times of the named file.
// Silently ignored.
func (*GCSFs) Chtimes(name string, atime, mtime time.Time) error { func (*GCSFs) Chtimes(name string, atime, mtime time.Time) error {
return errors.New("403 chtimes is not supported") return ErrVfsUnsupported
} }
// Truncate changes the size of the named file. // Truncate changes the size of the named file.
// Truncate by path is not supported, while truncating an opened // Truncate by path is not supported, while truncating an opened
// file is handled inside base transfer // file is handled inside base transfer
func (*GCSFs) Truncate(name string, size int64) error { func (*GCSFs) Truncate(name string, size int64) error {
return errors.New("403 truncate is not supported") return ErrVfsUnsupported
} }
// ReadDir reads the directory named by dirname and returns // ReadDir reads the directory named by dirname and returns
@ -455,6 +452,14 @@ func (*GCSFs) IsPermission(err error) bool {
return strings.Contains(err.Error(), "403") return strings.Contains(err.Error(), "403")
} }
// IsNotSupported returns true if the error indicate an unsupported operation
func (*GCSFs) IsNotSupported(err error) bool {
if err == nil {
return false
}
return err == ErrVfsUnsupported
}
// CheckRootPath creates the specified local root directory if it does not exists // CheckRootPath creates the specified local root directory if it does not exists
func (fs *GCSFs) CheckRootPath(username string, uid int, gid int) bool { func (fs *GCSFs) CheckRootPath(username string, uid int, gid int) bool {
// we need a local directory for temporary files // we need a local directory for temporary files
@ -502,7 +507,7 @@ func (fs *GCSFs) ScanRootDirContents() (int, int64, error) {
// GetDirSize returns the number of files and the size for a folder // GetDirSize returns the number of files and the size for a folder
// including any subfolders // including any subfolders
func (*GCSFs) GetDirSize(dirname string) (int, int64, error) { func (*GCSFs) GetDirSize(dirname string) (int, int64, error) {
return 0, 0, errUnsupported return 0, 0, ErrVfsUnsupported
} }
// GetAtomicUploadPath returns the path to use for an atomic upload. // GetAtomicUploadPath returns the path to use for an atomic upload.

View file

@ -185,6 +185,14 @@ func (*OsFs) IsPermission(err error) bool {
return os.IsPermission(err) return os.IsPermission(err)
} }
// IsNotSupported returns true if the error indicate an unsupported operation
func (*OsFs) IsNotSupported(err error) bool {
if err == nil {
return false
}
return err == ErrVfsUnsupported
}
// CheckRootPath creates the root directory if it does not exists // CheckRootPath creates the root directory if it does not exists
func (fs *OsFs) CheckRootPath(username string, uid int, gid int) bool { func (fs *OsFs) CheckRootPath(username string, uid int, gid int) bool {
var err error var err error

View file

@ -350,37 +350,34 @@ func (fs *S3Fs) Mkdir(name string) error {
// Symlink creates source as a symbolic link to target. // Symlink creates source as a symbolic link to target.
func (*S3Fs) Symlink(source, target string) error { func (*S3Fs) Symlink(source, target string) error {
return errors.New("403 symlinks are not supported") return ErrVfsUnsupported
} }
// Readlink returns the destination of the named symbolic link // Readlink returns the destination of the named symbolic link
func (*S3Fs) Readlink(name string) (string, error) { func (*S3Fs) Readlink(name string) (string, error) {
return "", errors.New("403 readlink is not supported") return "", ErrVfsUnsupported
} }
// Chown changes the numeric uid and gid of the named file. // Chown changes the numeric uid and gid of the named file.
// Silently ignored.
func (*S3Fs) Chown(name string, uid int, gid int) error { func (*S3Fs) Chown(name string, uid int, gid int) error {
return nil return ErrVfsUnsupported
} }
// Chmod changes the mode of the named file to mode. // Chmod changes the mode of the named file to mode.
// Silently ignored.
func (*S3Fs) Chmod(name string, mode os.FileMode) error { func (*S3Fs) Chmod(name string, mode os.FileMode) error {
return nil return ErrVfsUnsupported
} }
// Chtimes changes the access and modification times of the named file. // Chtimes changes the access and modification times of the named file.
// Silently ignored.
func (*S3Fs) Chtimes(name string, atime, mtime time.Time) error { func (*S3Fs) Chtimes(name string, atime, mtime time.Time) error {
return errors.New("403 chtimes is not supported") return ErrVfsUnsupported
} }
// Truncate changes the size of the named file. // Truncate changes the size of the named file.
// Truncate by path is not supported, while truncating an opened // Truncate by path is not supported, while truncating an opened
// file is handled inside base transfer // file is handled inside base transfer
func (*S3Fs) Truncate(name string, size int64) error { func (*S3Fs) Truncate(name string, size int64) error {
return errors.New("403 truncate is not supported") return ErrVfsUnsupported
} }
// ReadDir reads the directory named by dirname and returns // ReadDir reads the directory named by dirname and returns
@ -485,6 +482,14 @@ func (*S3Fs) IsPermission(err error) bool {
return strings.Contains(err.Error(), "403") return strings.Contains(err.Error(), "403")
} }
// IsNotSupported returns true if the error indicate an unsupported operation
func (*S3Fs) IsNotSupported(err error) bool {
if err == nil {
return false
}
return err == ErrVfsUnsupported
}
// CheckRootPath creates the specified local root directory if it does not exists // CheckRootPath creates the specified local root directory if it does not exists
func (fs *S3Fs) CheckRootPath(username string, uid int, gid int) bool { func (fs *S3Fs) CheckRootPath(username string, uid int, gid int) bool {
// we need a local directory for temporary files // we need a local directory for temporary files
@ -520,7 +525,7 @@ func (fs *S3Fs) ScanRootDirContents() (int, int64, error) {
// GetDirSize returns the number of files and the size for a folder // GetDirSize returns the number of files and the size for a folder
// including any subfolders // including any subfolders
func (*S3Fs) GetDirSize(dirname string) (int, int64, error) { func (*S3Fs) GetDirSize(dirname string) (int, int64, error) {
return 0, 0, errUnsupported return 0, 0, ErrVfsUnsupported
} }
// GetAtomicUploadPath returns the path to use for an atomic upload. // GetAtomicUploadPath returns the path to use for an atomic upload.

View file

@ -46,6 +46,7 @@ type Fs interface {
ResolvePath(sftpPath string) (string, error) ResolvePath(sftpPath string) (string, error)
IsNotExist(err error) bool IsNotExist(err error) bool
IsPermission(err error) bool IsPermission(err error) bool
IsNotSupported(err error) bool
ScanRootDirContents() (int, int64, error) ScanRootDirContents() (int, int64, error)
GetDirSize(dirname string) (int, int64, error) GetDirSize(dirname string) (int, int64, error)
GetAtomicUploadPath(name string) string GetAtomicUploadPath(name string) string
@ -56,7 +57,7 @@ type Fs interface {
GetMimeType(name string) (string, error) GetMimeType(name string) (string, error)
} }
var errUnsupported = errors.New("Not supported") var ErrVfsUnsupported = errors.New("Not supported")
// QuotaCheckResult defines the result for a quota check // QuotaCheckResult defines the result for a quota check
type QuotaCheckResult struct { type QuotaCheckResult struct {