mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
sftpd: add statvfs@openssh.com support
This commit is contained in:
parent
4ddfe41f23
commit
51f110bc7b
31 changed files with 428 additions and 77 deletions
|
@ -325,7 +325,7 @@ func parsePatternsFilesFilters() []dataprovider.PatternsFilter {
|
|||
var patterns []dataprovider.PatternsFilter
|
||||
for _, val := range portableAllowedPatterns {
|
||||
p, exts := getPatternsFilterValues(strings.TrimSpace(val))
|
||||
if len(p) > 0 {
|
||||
if p != "" {
|
||||
patterns = append(patterns, dataprovider.PatternsFilter{
|
||||
Path: path.Clean(p),
|
||||
AllowedPatterns: exts,
|
||||
|
@ -335,7 +335,7 @@ func parsePatternsFilesFilters() []dataprovider.PatternsFilter {
|
|||
}
|
||||
for _, val := range portableDeniedPatterns {
|
||||
p, exts := getPatternsFilterValues(strings.TrimSpace(val))
|
||||
if len(p) > 0 {
|
||||
if p != "" {
|
||||
found := false
|
||||
for index, e := range patterns {
|
||||
if path.Clean(e.Path) == path.Clean(p) {
|
||||
|
@ -364,7 +364,7 @@ func getPatternsFilterValues(value string) (string, []string) {
|
|||
exts := []string{}
|
||||
for _, e := range strings.Split(dirExts[1], ",") {
|
||||
cleanedExt := strings.TrimSpace(e)
|
||||
if len(cleanedExt) > 0 {
|
||||
if cleanedExt != "" {
|
||||
exts = append(exts, cleanedExt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -705,7 +705,7 @@ func (c ConnectionStatus) GetConnectionInfo() string {
|
|||
func (c ConnectionStatus) GetTransfersAsString() string {
|
||||
result := ""
|
||||
for _, t := range c.Transfers {
|
||||
if len(result) > 0 {
|
||||
if result != "" {
|
||||
result += ". "
|
||||
}
|
||||
result += t.getConnectionTransferAsString()
|
||||
|
|
|
@ -683,7 +683,7 @@ func (c *BaseConnection) hasSpaceForRename(virtualSourcePath, virtualTargetPath
|
|||
// rename between user root dir and a virtual folder included in user quota
|
||||
return true
|
||||
}
|
||||
quotaResult := c.HasSpace(true, virtualTargetPath)
|
||||
quotaResult := c.HasSpace(true, false, virtualTargetPath)
|
||||
return c.hasSpaceForCrossRename(quotaResult, initialSize, fsSourcePath)
|
||||
}
|
||||
|
||||
|
@ -774,7 +774,7 @@ func (c *BaseConnection) GetMaxWriteSize(quotaResult vfs.QuotaCheckResult, isRes
|
|||
}
|
||||
|
||||
// HasSpace checks user's quota usage
|
||||
func (c *BaseConnection) HasSpace(checkFiles bool, requestPath string) vfs.QuotaCheckResult {
|
||||
func (c *BaseConnection) HasSpace(checkFiles, getUsage bool, requestPath string) vfs.QuotaCheckResult {
|
||||
result := vfs.QuotaCheckResult{
|
||||
HasSpace: true,
|
||||
AllowedSize: 0,
|
||||
|
@ -792,14 +792,14 @@ func (c *BaseConnection) HasSpace(checkFiles bool, requestPath string) vfs.Quota
|
|||
var vfolder vfs.VirtualFolder
|
||||
vfolder, err = c.User.GetVirtualFolderForPath(path.Dir(requestPath))
|
||||
if err == nil && !vfolder.IsIncludedInUserQuota() {
|
||||
if vfolder.HasNoQuotaRestrictions(checkFiles) {
|
||||
if vfolder.HasNoQuotaRestrictions(checkFiles) && !getUsage {
|
||||
return result
|
||||
}
|
||||
result.QuotaSize = vfolder.QuotaSize
|
||||
result.QuotaFiles = vfolder.QuotaFiles
|
||||
result.UsedFiles, result.UsedSize, err = dataprovider.GetUsedVirtualFolderQuota(vfolder.Name)
|
||||
} else {
|
||||
if c.User.HasNoQuotaRestrictions(checkFiles) {
|
||||
if c.User.HasNoQuotaRestrictions(checkFiles) && !getUsage {
|
||||
return result
|
||||
}
|
||||
result.QuotaSize = c.User.QuotaSize
|
||||
|
@ -981,9 +981,13 @@ func (c *BaseConnection) GetOpUnsupportedError() error {
|
|||
func (c *BaseConnection) GetGenericError(err error) error {
|
||||
switch c.protocol {
|
||||
case ProtocolSFTP:
|
||||
if err == vfs.ErrStorageSizeUnavailable {
|
||||
return sftp.ErrSSHFxOpUnsupported
|
||||
}
|
||||
return sftp.ErrSSHFxFailure
|
||||
default:
|
||||
if err == ErrPermissionDenied || err == ErrNotExist || err == ErrOpUnsupported || err == ErrQuotaExceeded {
|
||||
if err == ErrPermissionDenied || err == ErrNotExist || err == ErrOpUnsupported ||
|
||||
err == ErrQuotaExceeded || err == vfs.ErrStorageSizeUnavailable {
|
||||
return err
|
||||
}
|
||||
return ErrGenericFailure
|
||||
|
|
|
@ -917,7 +917,7 @@ func TestHasSpaceForRename(t *testing.T) {
|
|||
c := NewBaseConnection("", ProtocolSFTP, user, fs)
|
||||
// with quota tracking disabled hasSpaceForRename will always return true
|
||||
assert.True(t, c.hasSpaceForRename("", "", 0, ""))
|
||||
quotaResult := c.HasSpace(true, "")
|
||||
quotaResult := c.HasSpace(true, false, "")
|
||||
assert.True(t, quotaResult.HasSpace)
|
||||
|
||||
err = closeDataprovider()
|
||||
|
@ -1028,7 +1028,7 @@ func TestHasSpace(t *testing.T) {
|
|||
fs, err := user.GetFilesystem("id")
|
||||
assert.NoError(t, err)
|
||||
c := NewBaseConnection("", ProtocolSFTP, user, fs)
|
||||
quotaResult := c.HasSpace(true, "/")
|
||||
quotaResult := c.HasSpace(true, false, "/")
|
||||
assert.True(t, quotaResult.HasSpace)
|
||||
|
||||
user.VirtualFolders[0].QuotaFiles = 0
|
||||
|
@ -1038,7 +1038,7 @@ func TestHasSpace(t *testing.T) {
|
|||
user, err = dataprovider.UserExists(user.Username)
|
||||
assert.NoError(t, err)
|
||||
c.User = user
|
||||
quotaResult = c.HasSpace(true, "/vdir/file")
|
||||
quotaResult = c.HasSpace(true, false, "/vdir/file")
|
||||
assert.True(t, quotaResult.HasSpace)
|
||||
|
||||
user.VirtualFolders[0].QuotaFiles = 10
|
||||
|
@ -1046,17 +1046,17 @@ func TestHasSpace(t *testing.T) {
|
|||
err = dataprovider.UpdateUser(&user)
|
||||
assert.NoError(t, err)
|
||||
c.User = user
|
||||
quotaResult = c.HasSpace(true, "/vdir/file1")
|
||||
quotaResult = c.HasSpace(true, false, "/vdir/file1")
|
||||
assert.True(t, quotaResult.HasSpace)
|
||||
|
||||
quotaResult = c.HasSpace(true, "/file")
|
||||
quotaResult = c.HasSpace(true, false, "/file")
|
||||
assert.True(t, quotaResult.HasSpace)
|
||||
|
||||
folder, err := dataprovider.GetFolderByName(folderName)
|
||||
assert.NoError(t, err)
|
||||
err = dataprovider.UpdateVirtualFolderQuota(folder, 10, 1048576, true)
|
||||
assert.NoError(t, err)
|
||||
quotaResult = c.HasSpace(true, "/vdir/file1")
|
||||
quotaResult = c.HasSpace(true, false, "/vdir/file1")
|
||||
assert.False(t, quotaResult.HasSpace)
|
||||
|
||||
err = dataprovider.DeleteUser(user.Username)
|
||||
|
@ -1199,6 +1199,12 @@ func TestErrorsMapping(t *testing.T) {
|
|||
} else {
|
||||
assert.EqualError(t, err, ErrOpUnsupported.Error())
|
||||
}
|
||||
err = conn.GetFsError(vfs.ErrStorageSizeUnavailable)
|
||||
if protocol == ProtocolSFTP {
|
||||
assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
|
||||
} else {
|
||||
assert.EqualError(t, err, vfs.ErrStorageSizeUnavailable.Error())
|
||||
}
|
||||
err = conn.GetFsError(nil)
|
||||
assert.NoError(t, err)
|
||||
err = conn.GetOpUnsupportedError()
|
||||
|
|
|
@ -400,7 +400,7 @@ func LoadConfig(configDir, configFile string) error {
|
|||
if strings.TrimSpace(globalConf.FTPD.Banner) == "" {
|
||||
globalConf.FTPD.Banner = defaultFTPDBanner
|
||||
}
|
||||
if len(globalConf.ProviderConf.UsersBaseDir) > 0 && !utils.IsFileInputValid(globalConf.ProviderConf.UsersBaseDir) {
|
||||
if globalConf.ProviderConf.UsersBaseDir != "" && !utils.IsFileInputValid(globalConf.ProviderConf.UsersBaseDir) {
|
||||
err = fmt.Errorf("invalid users base dir %#v will be ignored", globalConf.ProviderConf.UsersBaseDir)
|
||||
globalConf.ProviderConf.UsersBaseDir = ""
|
||||
logger.Warn(logSender, "", "Configuration error: %v", err)
|
||||
|
@ -455,7 +455,7 @@ func checkCommonParamsCompatibility() {
|
|||
logger.WarnToConsole("sftpd.idle_timeout is deprecated, please use common.idle_timeout")
|
||||
globalConf.Common.IdleTimeout = globalConf.SFTPD.IdleTimeout //nolint:staticcheck
|
||||
}
|
||||
if len(globalConf.SFTPD.Actions.Hook) > 0 && len(globalConf.Common.Actions.Hook) == 0 { //nolint:staticcheck
|
||||
if globalConf.SFTPD.Actions.Hook != "" && len(globalConf.Common.Actions.Hook) == 0 { //nolint:staticcheck
|
||||
logger.Warn(logSender, "", "sftpd.actions is deprecated, please use common.actions")
|
||||
logger.WarnToConsole("sftpd.actions is deprecated, please use common.actions")
|
||||
globalConf.Common.Actions.ExecuteOn = globalConf.SFTPD.Actions.ExecuteOn //nolint:staticcheck
|
||||
|
|
|
@ -2015,7 +2015,7 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
|
|||
if err != nil {
|
||||
return u, fmt.Errorf("Pre-login hook error: %v", err)
|
||||
}
|
||||
if len(strings.TrimSpace(string(out))) == 0 {
|
||||
if strings.TrimSpace(string(out)) == "" {
|
||||
providerLog(logger.LevelDebug, "empty response from pre-login hook, no modification requested for user %#v id: %v",
|
||||
username, u.ID)
|
||||
if u.ID == 0 {
|
||||
|
@ -2182,13 +2182,13 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
|
|||
if err != nil {
|
||||
return user, fmt.Errorf("Invalid external auth response: %v", err)
|
||||
}
|
||||
if len(user.Username) == 0 {
|
||||
if user.Username == "" {
|
||||
return user, ErrInvalidCredentials
|
||||
}
|
||||
if len(password) > 0 {
|
||||
if password != "" {
|
||||
user.Password = password
|
||||
}
|
||||
if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
|
||||
if pkey != "" && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
|
||||
user.PublicKeys = append(user.PublicKeys, pkey)
|
||||
}
|
||||
// some users want to map multiple login usernames with a single SFTPGo account
|
||||
|
|
|
@ -718,22 +718,16 @@ func (u *User) GetQuotaSummary() string {
|
|||
func (u *User) GetPermissionsAsString() string {
|
||||
result := ""
|
||||
for dir, perms := range u.Permissions {
|
||||
var dirPerms string
|
||||
for _, p := range perms {
|
||||
if len(dirPerms) > 0 {
|
||||
dirPerms += ", "
|
||||
}
|
||||
dirPerms += p
|
||||
}
|
||||
dirPerms := strings.Join(perms, ", ")
|
||||
dp := fmt.Sprintf("%#v: %#v", dir, dirPerms)
|
||||
if dir == "/" {
|
||||
if len(result) > 0 {
|
||||
if result != "" {
|
||||
result = dp + ", " + result
|
||||
} else {
|
||||
result = dp
|
||||
}
|
||||
} else {
|
||||
if len(result) > 0 {
|
||||
if result != "" {
|
||||
result += ", "
|
||||
}
|
||||
result += dp
|
||||
|
|
|
@ -62,7 +62,7 @@ docker run --name some-sftpgo \
|
|||
-p 2022:2022 \
|
||||
--mount type=bind,source=/my/own/sftpgodata,target=/srv/sftpgo \
|
||||
--mount type=bind,source=/my/own/sftpgohome,target=/var/lib/sftpgo \
|
||||
-e SFTPGO_HTTPD__BIND_PORT=8090 \
|
||||
-e SFTPGO_HTTPD__BINDINGS__0__PORT=8090 \
|
||||
-d "drakkan/sftpgo:tag"
|
||||
```
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -1585,7 +1586,7 @@ func TestAllocateAvailable(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAvailableUnsupportedFs(t *testing.T) {
|
||||
func TestAvailableSFTPFs(t *testing.T) {
|
||||
u := getTestUser()
|
||||
localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1593,10 +1594,12 @@ func TestAvailableUnsupportedFs(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
client, err := getFTPClient(sftpUser, false)
|
||||
if assert.NoError(t, err) {
|
||||
code, response, err := client.SendCustomCommand("AVBL")
|
||||
code, response, err := client.SendCustomCommand("AVBL /")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ftp.StatusFileUnavailable, code)
|
||||
assert.Contains(t, response, "unable to get available size for this storage backend")
|
||||
assert.Equal(t, ftp.StatusFile, code)
|
||||
avblSize, err := strconv.ParseInt(response, 10, 64)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, avblSize, int64(0))
|
||||
|
||||
err = client.Quit()
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -213,8 +213,7 @@ func (c *Connection) Chtimes(name string, atime time.Time, mtime time.Time) erro
|
|||
func (c *Connection) GetAvailableSpace(dirName string) (int64, error) {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
quotaResult := c.HasSpace(false, path.Join(dirName, "fakefile.txt"))
|
||||
|
||||
quotaResult := c.HasSpace(false, false, path.Join(dirName, "fakefile.txt"))
|
||||
if !quotaResult.HasSpace {
|
||||
return 0, nil
|
||||
}
|
||||
|
@ -230,7 +229,11 @@ func (c *Connection) GetAvailableSpace(dirName string) (int64, error) {
|
|||
return 0, c.GetFsError(err)
|
||||
}
|
||||
|
||||
return c.Fs.GetAvailableDiskSize(p)
|
||||
statVFS, err := c.Fs.GetAvailableDiskSize(p)
|
||||
if err != nil {
|
||||
return 0, c.GetFsError(err)
|
||||
}
|
||||
return int64(statVFS.FreeSpace()), nil
|
||||
}
|
||||
|
||||
// the available space is the minimum between MaxUploadFileSize, if setted,
|
||||
|
@ -260,7 +263,7 @@ func (c *Connection) AllocateSpace(size int) error {
|
|||
folders = append(folders, path.Join(v.VirtualPath, "fakefile.txt"))
|
||||
}
|
||||
for _, f := range folders {
|
||||
quotaResult := c.HasSpace(false, f)
|
||||
quotaResult := c.HasSpace(false, false, f)
|
||||
if quotaResult.HasSpace {
|
||||
if quotaResult.QuotaSize == 0 {
|
||||
// unlimited size is allowed
|
||||
|
@ -393,7 +396,7 @@ func (c *Connection) uploadFile(fsPath, ftpPath string, flags int) (ftpserver.Fi
|
|||
}
|
||||
|
||||
func (c *Connection) handleFTPUploadToNewFile(resolvedPath, filePath, requestPath string) (ftpserver.FileTransfer, error) {
|
||||
quotaResult := c.HasSpace(true, requestPath)
|
||||
quotaResult := c.HasSpace(true, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
|
@ -419,7 +422,7 @@ func (c *Connection) handleFTPUploadToNewFile(resolvedPath, filePath, requestPat
|
|||
func (c *Connection) handleFTPUploadToExistingFile(flags int, resolvedPath, filePath string, fileSize int64,
|
||||
requestPath string) (ftpserver.FileTransfer, error) {
|
||||
var err error
|
||||
quotaResult := c.HasSpace(false, requestPath)
|
||||
quotaResult := c.HasSpace(false, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
|
|
|
@ -616,6 +616,27 @@ func TestUploadFileStatError(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAVBLErrors(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
_, err := connection.GetAvailableSpace("/")
|
||||
assert.NoError(t, err)
|
||||
_, err = connection.GetAvailableSpace("/missing-path")
|
||||
assert.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestUploadOverwriteErrors(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
|
|
2
go.mod
2
go.mod
|
@ -71,7 +71,7 @@ require (
|
|||
|
||||
replace (
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9
|
||||
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20201211115031-0b6bbc64f191
|
||||
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20210210202350-a2b46fc9c0d5
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20201217113543-470e61ed2598
|
||||
golang.org/x/net => github.com/drakkan/net v0.0.0-20210201075003-5fb2b186574d
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -173,8 +173,8 @@ github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9 h1:LPH1dEblAOO/LoG7yHP
|
|||
github.com/drakkan/ftp v0.0.0-20201114075148-9b9adce499a9/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||
github.com/drakkan/net v0.0.0-20210201075003-5fb2b186574d h1:h2rU/lTUkEYB3y4k6+FgQNMajf4uE+sbMRn85kT+VTQ=
|
||||
github.com/drakkan/net v0.0.0-20210201075003-5fb2b186574d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
github.com/drakkan/sftp v0.0.0-20201211115031-0b6bbc64f191 h1:c+RLqMs6Aqc8IDc5MWTf+zqNlO4+5WfiJqZzHFlr4a8=
|
||||
github.com/drakkan/sftp v0.0.0-20201211115031-0b6bbc64f191/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
|
||||
github.com/drakkan/sftp v0.0.0-20210210202350-a2b46fc9c0d5 h1:jVxjoPrGY9Ypw65tTHRdDvumOE3ys2fLZfvFT6+gFPU=
|
||||
github.com/drakkan/sftp v0.0.0-20210210202350-a2b46fc9c0d5/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
NFPM_VERSION=2.2.3
|
||||
NFPM_VERSION=2.2.4
|
||||
NFPM_ARCH=${NFPM_ARCH:-amd64}
|
||||
if [ -z ${SFTPGO_VERSION} ]
|
||||
then
|
||||
|
|
|
@ -272,7 +272,7 @@ func (s *Service) loadInitialData() error {
|
|||
func (s *Service) restoreDump(dump dataprovider.BackupData) error {
|
||||
err := httpd.RestoreAdmins(dump.Admins, s.LoadDataFrom, s.LoadDataMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to restore folders from file %#v: %v", s.LoadDataFrom, err)
|
||||
return fmt.Errorf("unable to restore admins from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
err = httpd.RestoreFolders(dump.Folders, s.LoadDataFrom, s.LoadDataMode, s.LoadDataQuotaScan)
|
||||
if err != nil {
|
||||
|
|
|
@ -253,11 +253,43 @@ func (c *Connection) Lstat(request *sftp.Request) (sftp.ListerAt, error) {
|
|||
return listerAt([]os.FileInfo{s}), nil
|
||||
}
|
||||
|
||||
// StatVFS implements StatVFSFileCmder interface
|
||||
func (c *Connection) StatVFS(r *sftp.Request) (*sftp.StatVFS, error) {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
// we are assuming that r.Filepath is a dir, this could be wrong but should
|
||||
// not produce any side effect here.
|
||||
// we don't consider c.User.Filters.MaxUploadFileSize, we return disk stats here
|
||||
// not the limit for a single file upload
|
||||
quotaResult := c.HasSpace(true, true, path.Join(r.Filepath, "fakefile.txt"))
|
||||
|
||||
p, err := c.Fs.ResolvePath(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
|
||||
if !quotaResult.HasSpace {
|
||||
return c.getStatVFSFromQuotaResult(p, quotaResult), nil
|
||||
}
|
||||
|
||||
if quotaResult.QuotaSize == 0 && quotaResult.QuotaFiles == 0 {
|
||||
// no quota restrictions
|
||||
statvfs, err := c.Fs.GetAvailableDiskSize(p)
|
||||
if err == vfs.ErrStorageSizeUnavailable {
|
||||
return c.getStatVFSFromQuotaResult(p, quotaResult), nil
|
||||
}
|
||||
return statvfs, err
|
||||
}
|
||||
|
||||
// there is free space but some limits are configured
|
||||
return c.getStatVFSFromQuotaResult(p, quotaResult), nil
|
||||
}
|
||||
|
||||
func (c *Connection) getSFTPCmdTargetPath(requestTarget string) (string, error) {
|
||||
var target string
|
||||
// If a target is provided in this request validate that it is going to the correct
|
||||
// location for the server. If it is not, return an error
|
||||
if len(requestTarget) > 0 {
|
||||
if requestTarget != "" {
|
||||
var err error
|
||||
target, err = c.Fs.ResolvePath(requestTarget)
|
||||
if err != nil {
|
||||
|
@ -309,7 +341,7 @@ func (c *Connection) handleSFTPRemove(filePath string, request *sftp.Request) er
|
|||
}
|
||||
|
||||
func (c *Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPath string, errForRead error) (sftp.WriterAtReaderAt, error) {
|
||||
quotaResult := c.HasSpace(true, requestPath)
|
||||
quotaResult := c.HasSpace(true, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, sftp.ErrSSHFxFailure
|
||||
|
@ -336,7 +368,7 @@ func (c *Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPa
|
|||
func (c *Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, resolvedPath, filePath string,
|
||||
fileSize int64, requestPath string, errForRead error) (sftp.WriterAtReaderAt, error) {
|
||||
var err error
|
||||
quotaResult := c.HasSpace(false, requestPath)
|
||||
quotaResult := c.HasSpace(false, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, sftp.ErrSSHFxFailure
|
||||
|
@ -406,6 +438,52 @@ func (c *Connection) Disconnect() error {
|
|||
return c.channel.Close()
|
||||
}
|
||||
|
||||
func (c *Connection) getStatVFSFromQuotaResult(name string, quotaResult vfs.QuotaCheckResult) *sftp.StatVFS {
|
||||
if quotaResult.QuotaSize == 0 || quotaResult.QuotaFiles == 0 {
|
||||
s, err := c.Fs.GetAvailableDiskSize(name)
|
||||
if err == nil {
|
||||
if quotaResult.QuotaSize == 0 {
|
||||
quotaResult.QuotaSize = int64(s.TotalSpace())
|
||||
}
|
||||
if quotaResult.QuotaFiles == 0 {
|
||||
quotaResult.QuotaFiles = int(s.Files)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we are unable to get quota size or quota files we add some arbitrary values
|
||||
if quotaResult.QuotaSize == 0 {
|
||||
quotaResult.QuotaSize = quotaResult.UsedSize + 8*1024*1024*1024*1024 // 8TB
|
||||
}
|
||||
if quotaResult.QuotaFiles == 0 {
|
||||
quotaResult.QuotaFiles = quotaResult.UsedFiles + 1000000 // 1 million
|
||||
}
|
||||
|
||||
bsize := uint64(4096)
|
||||
for bsize > uint64(quotaResult.QuotaSize) {
|
||||
bsize = bsize / 4
|
||||
}
|
||||
blocks := uint64(quotaResult.QuotaSize) / bsize
|
||||
bfree := uint64(quotaResult.QuotaSize-quotaResult.UsedSize) / bsize
|
||||
files := uint64(quotaResult.QuotaFiles)
|
||||
ffree := uint64(quotaResult.QuotaFiles - quotaResult.UsedFiles)
|
||||
if !quotaResult.HasSpace {
|
||||
bfree = 0
|
||||
ffree = 0
|
||||
}
|
||||
|
||||
return &sftp.StatVFS{
|
||||
Bsize: bsize,
|
||||
Frsize: bsize,
|
||||
Blocks: blocks,
|
||||
Bfree: bfree,
|
||||
Bavail: bfree,
|
||||
Files: files,
|
||||
Ffree: ffree,
|
||||
Favail: ffree,
|
||||
Namemax: 255,
|
||||
}
|
||||
}
|
||||
|
||||
func getOSOpenFlags(requestFlags sftp.FileOpenFlags) (flags int) {
|
||||
var osFlags int
|
||||
if requestFlags.Read && requestFlags.Write {
|
||||
|
|
|
@ -376,6 +376,11 @@ func TestWithInvalidHome(t *testing.T) {
|
|||
}
|
||||
_, err = c.Fs.ResolvePath("../upper_path")
|
||||
assert.Error(t, err, "tested path is not a home subdir")
|
||||
_, err = c.StatVFS(&sftp.Request{
|
||||
Method: "StatVFS",
|
||||
Filepath: "../unresolvable-path",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSFTPCmdTargetPath(t *testing.T) {
|
||||
|
@ -408,7 +413,7 @@ func TestSFTPGetUsedQuota(t *testing.T) {
|
|||
connection := Connection{
|
||||
BaseConnection: common.NewBaseConnection("", common.ProtocolSFTP, u, nil),
|
||||
}
|
||||
quotaResult := connection.HasSpace(false, "/")
|
||||
quotaResult := connection.HasSpace(false, false, "/")
|
||||
assert.False(t, quotaResult.HasSpace)
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ func (c *scpCommand) getUploadFileData(sizeToRead int64, transfer *transfer) err
|
|||
}
|
||||
|
||||
func (c *scpCommand) handleUploadFile(resolvedPath, filePath string, sizeToRead int64, isNewFile bool, fileSize int64, requestPath string) error {
|
||||
quotaResult := c.connection.HasSpace(isNewFile, requestPath)
|
||||
quotaResult := c.connection.HasSpace(isNewFile, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
err := fmt.Errorf("denying file write due to quota limits")
|
||||
c.connection.Log(logger.LevelWarn, "error uploading file: %#v, err: %v", filePath, err)
|
||||
|
|
|
@ -33,7 +33,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
sftpExtensions = []string{"posix-rename@openssh.com"}
|
||||
sftpExtensions = []string{"statvfs@openssh.com"}
|
||||
)
|
||||
|
||||
// Binding defines the configuration for a network listener
|
||||
|
|
|
@ -456,6 +456,12 @@ func TestBasicSFTPFsHandling(t *testing.T) {
|
|||
assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
|
||||
assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
|
||||
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uint64(u.QuotaSize/4096), stat.Blocks)
|
||||
assert.Equal(t, uint64((u.QuotaSize-testFileSize)/4096), stat.Bfree)
|
||||
assert.Equal(t, uint64(1), stat.Files-stat.Ffree)
|
||||
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
err = os.Remove(localDownloadPath)
|
||||
|
@ -6371,6 +6377,136 @@ func TestGetVirtualFolderForPath(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStatVFS(t *testing.T) {
|
||||
usePubKey := false
|
||||
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
testFileSize := int64(65535)
|
||||
client, err := getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, stat.ID, uint32(0))
|
||||
assert.Greater(t, stat.Blocks, uint64(0))
|
||||
assert.Greater(t, stat.Bsize, uint64(0))
|
||||
|
||||
_, err = client.StatVFS("missing-path")
|
||||
assert.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
user.QuotaFiles = 100
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
|
||||
testFilePath := filepath.Join(homeBasePath, testFileName)
|
||||
err = createTestFile(testFilePath, testFileSize)
|
||||
assert.NoError(t, err)
|
||||
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, stat.ID, uint32(0))
|
||||
assert.Greater(t, stat.Blocks, uint64(0))
|
||||
assert.Greater(t, stat.Bsize, uint64(0))
|
||||
assert.Equal(t, uint64(100), stat.Files)
|
||||
assert.Equal(t, uint64(99), stat.Ffree)
|
||||
}
|
||||
|
||||
user.QuotaSize = 8192
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, stat.ID, uint32(0))
|
||||
assert.Greater(t, stat.Blocks, uint64(0))
|
||||
assert.Greater(t, stat.Bsize, uint64(0))
|
||||
assert.Equal(t, uint64(100), stat.Files)
|
||||
assert.Equal(t, uint64(0), stat.Ffree)
|
||||
assert.Equal(t, uint64(2), stat.Blocks)
|
||||
assert.Equal(t, uint64(0), stat.Bfree)
|
||||
}
|
||||
user.QuotaFiles = 0
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, stat.ID, uint32(0))
|
||||
assert.Greater(t, stat.Blocks, uint64(0))
|
||||
assert.Greater(t, stat.Bsize, uint64(0))
|
||||
assert.Greater(t, stat.Files, uint64(0))
|
||||
assert.Equal(t, uint64(0), stat.Ffree)
|
||||
assert.Equal(t, uint64(2), stat.Blocks)
|
||||
assert.Equal(t, uint64(0), stat.Bfree)
|
||||
}
|
||||
|
||||
user.QuotaSize = 1
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
client, err = getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, stat.ID, uint32(0))
|
||||
assert.Equal(t, uint64(1), stat.Blocks)
|
||||
assert.Equal(t, uint64(1), stat.Bsize)
|
||||
assert.Greater(t, stat.Files, uint64(0))
|
||||
assert.Equal(t, uint64(0), stat.Ffree)
|
||||
assert.Equal(t, uint64(1), stat.Blocks)
|
||||
assert.Equal(t, uint64(0), stat.Bfree)
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStatVFSCloudBackend(t *testing.T) {
|
||||
usePubKey := true
|
||||
u := getTestUser(usePubKey)
|
||||
u.FsConfig.Provider = dataprovider.AzureBlobFilesystemProvider
|
||||
u.FsConfig.AzBlobConfig.SASURL = "https://myaccount.blob.core.windows.net/sasurl"
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
client, err := getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err) {
|
||||
defer client.Close()
|
||||
|
||||
err = dataprovider.UpdateUserQuota(user, 100, 8192, true)
|
||||
assert.NoError(t, err)
|
||||
stat, err := client.StatVFS("/")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, stat.ID, uint32(0))
|
||||
assert.Greater(t, stat.Blocks, uint64(0))
|
||||
assert.Greater(t, stat.Bsize, uint64(0))
|
||||
assert.Equal(t, uint64(1000000+100), stat.Files)
|
||||
assert.Equal(t, uint64(2147483648+2), stat.Blocks)
|
||||
assert.Equal(t, uint64(1000000), stat.Ffree)
|
||||
assert.Equal(t, uint64(2147483648), stat.Bfree)
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSSHCommands(t *testing.T) {
|
||||
usePubKey := false
|
||||
user, _, err := httpdtest.AddUser(getTestUser(usePubKey), http.StatusCreated)
|
||||
|
|
|
@ -309,7 +309,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error {
|
|||
return c.sendErrorResponse(errUnsupportedConfig)
|
||||
}
|
||||
sshDestPath := c.getDestPath()
|
||||
quotaResult := c.connection.HasSpace(true, command.quotaCheckPath)
|
||||
quotaResult := c.connection.HasSpace(true, false, command.quotaCheckPath)
|
||||
if !quotaResult.HasSpace {
|
||||
return c.sendErrorResponse(common.ErrQuotaExceeded)
|
||||
}
|
||||
|
@ -640,7 +640,7 @@ func (c *sshCommand) checkCopyDestination(fsDestPath string) error {
|
|||
}
|
||||
|
||||
func (c *sshCommand) checkCopyQuota(numFiles int, filesSize int64, requestPath string) error {
|
||||
quotaResult := c.connection.HasSpace(true, requestPath)
|
||||
quotaResult := c.connection.HasSpace(true, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
return common.ErrQuotaExceeded
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/pkg/sftp"
|
||||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/metrics"
|
||||
|
@ -702,8 +703,8 @@ func (*AzureBlobFs) Close() error {
|
|||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*AzureBlobFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
func (*AzureBlobFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
|
||||
return nil, ErrStorageSizeUnavailable
|
||||
}
|
||||
|
||||
func (fs *AzureBlobFs) isEqual(key string, virtualName string) bool {
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/pkg/sftp"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
|
@ -696,6 +697,6 @@ func (fs *GCSFs) Close() error {
|
|||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*GCSFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
func (*GCSFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
|
||||
return nil, ErrStorageSizeUnavailable
|
||||
}
|
||||
|
|
10
vfs/osfs.go
10
vfs/osfs.go
|
@ -11,8 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/rs/xid"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
|
@ -480,10 +480,6 @@ func (*OsFs) Close() error {
|
|||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*OsFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
usage, err := disk.Usage(dirName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(usage.Free), nil
|
||||
func (*OsFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
|
||||
return getStatFS(dirName)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/pkg/sftp"
|
||||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/metrics"
|
||||
|
@ -661,6 +662,6 @@ func (*S3Fs) Close() error {
|
|||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*S3Fs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
func (*S3Fs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
|
||||
return nil, ErrStorageSizeUnavailable
|
||||
}
|
||||
|
|
|
@ -503,6 +503,17 @@ func (fs *SFTPFs) GetMimeType(name string) (string, error) {
|
|||
return ctype, err
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (fs *SFTPFs) GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error) {
|
||||
if err := fs.checkConnection(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := fs.sftpClient.HasExtension("statvfs@openssh.com"); !ok {
|
||||
return nil, ErrStorageSizeUnavailable
|
||||
}
|
||||
return fs.sftpClient.StatVFS(dirName)
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
func (fs *SFTPFs) Close() error {
|
||||
fs.Lock()
|
||||
|
@ -521,11 +532,6 @@ func (fs *SFTPFs) Close() error {
|
|||
return sshErr
|
||||
}
|
||||
|
||||
// GetAvailableDiskSize return the available size for the specified path
|
||||
func (*SFTPFs) GetAvailableDiskSize(dirName string) (int64, error) {
|
||||
return 0, errStorageSizeUnavailable
|
||||
}
|
||||
|
||||
func (fs *SFTPFs) checkConnection() error {
|
||||
err := fs.closed()
|
||||
if err == nil {
|
||||
|
|
38
vfs/statvfs_fallback.go
Normal file
38
vfs/statvfs_fallback.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// +build !darwin,!linux,!freebsd
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"github.com/pkg/sftp"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
)
|
||||
|
||||
const bsize = uint64(4096)
|
||||
|
||||
func getStatFS(path string) (*sftp.StatVFS, error) {
|
||||
usage, err := disk.Usage(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we assume block size = 4096
|
||||
blocks := usage.Total / bsize
|
||||
bfree := usage.Free / bsize
|
||||
files := usage.InodesTotal
|
||||
ffree := usage.InodesFree
|
||||
if files == 0 {
|
||||
// these assumptions are wrong but still better than returning 0
|
||||
files = blocks / 4
|
||||
ffree = bfree / 4
|
||||
}
|
||||
return &sftp.StatVFS{
|
||||
Bsize: bsize,
|
||||
Frsize: bsize,
|
||||
Blocks: blocks,
|
||||
Bfree: bfree,
|
||||
Bavail: bfree,
|
||||
Files: files,
|
||||
Ffree: ffree,
|
||||
Favail: ffree,
|
||||
Namemax: 255,
|
||||
}, nil
|
||||
}
|
28
vfs/statvfs_linux.go
Normal file
28
vfs/statvfs_linux.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// +build linux
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getStatFS(path string) (*sftp.StatVFS, error) {
|
||||
stat := unix.Statfs_t{}
|
||||
err := unix.Statfs(path, &stat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sftp.StatVFS{
|
||||
Bsize: uint64(stat.Bsize),
|
||||
Frsize: uint64(stat.Frsize),
|
||||
Blocks: stat.Blocks,
|
||||
Bfree: stat.Bfree,
|
||||
Bavail: stat.Bavail,
|
||||
Files: stat.Files,
|
||||
Ffree: stat.Ffree,
|
||||
Favail: stat.Ffree, // not sure how to calculate Favail
|
||||
Flag: uint64(stat.Flags),
|
||||
Namemax: uint64(stat.Namelen),
|
||||
}, nil
|
||||
}
|
28
vfs/statvfs_unix.go
Normal file
28
vfs/statvfs_unix.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// +build freebsd darwin
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getStatFS(path string) (*sftp.StatVFS, error) {
|
||||
stat := unix.Statfs_t{}
|
||||
err := unix.Statfs(path, &stat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sftp.StatVFS{
|
||||
Bsize: uint64(stat.Bsize),
|
||||
Frsize: uint64(stat.Bsize),
|
||||
Blocks: stat.Blocks,
|
||||
Bfree: stat.Bfree,
|
||||
Bavail: uint64(stat.Bavail),
|
||||
Files: stat.Files,
|
||||
Ffree: uint64(stat.Ffree),
|
||||
Favail: uint64(stat.Ffree), // not sure how to calculate Favail
|
||||
Flag: uint64(stat.Flags),
|
||||
Namemax: 255, // we use a conservative value here
|
||||
}, nil
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/pkg/sftp"
|
||||
|
||||
"github.com/drakkan/sftpgo/kms"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
|
@ -23,8 +24,9 @@ import (
|
|||
const dirMimeType = "inode/directory"
|
||||
|
||||
var (
|
||||
validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
|
||||
errStorageSizeUnavailable = errors.New("unable to get available size for this storage backend")
|
||||
validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
|
||||
// ErrStorageSizeUnavailable is returned if the storage backend does not support getting the size
|
||||
ErrStorageSizeUnavailable = errors.New("unable to get available size for this storage backend")
|
||||
)
|
||||
|
||||
// Fs defines the interface for filesystem backends
|
||||
|
@ -60,7 +62,7 @@ type Fs interface {
|
|||
Join(elem ...string) string
|
||||
HasVirtualFolders() bool
|
||||
GetMimeType(name string) (string, error)
|
||||
GetAvailableDiskSize(dirName string) (int64, error)
|
||||
GetAvailableDiskSize(dirName string) (*sftp.StatVFS, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ func (c *Connection) putFile(fsPath, virtualPath string) (webdav.File, error) {
|
|||
}
|
||||
|
||||
func (c *Connection) handleUploadToNewFile(resolvedPath, filePath, requestPath string) (webdav.File, error) {
|
||||
quotaResult := c.HasSpace(true, requestPath)
|
||||
quotaResult := c.HasSpace(true, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
|
@ -236,7 +236,7 @@ func (c *Connection) handleUploadToNewFile(resolvedPath, filePath, requestPath s
|
|||
func (c *Connection) handleUploadToExistingFile(resolvedPath, filePath string, fileSize int64,
|
||||
requestPath string) (webdav.File, error) {
|
||||
var err error
|
||||
quotaResult := c.HasSpace(false, requestPath)
|
||||
quotaResult := c.HasSpace(false, false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
|
|
Loading…
Reference in a new issue