FTP: add support for AVBL command

This commit is contained in:
Nicola Murino 2020-12-25 11:14:08 +01:00
parent 1dce1eff48
commit 2a95d031ea
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
12 changed files with 153 additions and 6 deletions

View file

@ -87,8 +87,8 @@ var (
ErrGenericFailure = errors.New("failure")
ErrQuotaExceeded = errors.New("denying write due to space limit")
ErrSkipPermissionsCheck = errors.New("permission check skipped")
ErrConnectionDenied = errors.New("You are not allowed to connect")
ErrNoBinding = errors.New("No binding configured")
ErrConnectionDenied = errors.New("you are not allowed to connect")
ErrNoBinding = errors.New("no binding configured")
errNoTransfer = errors.New("requested transfer not found")
errTransferMismatch = errors.New("transfer mismatch")
)

View file

@ -1395,7 +1395,7 @@ func TestUploadOverwriteVfolder(t *testing.T) {
assert.NoError(t, err)
}
func TestAllocate(t *testing.T) {
func TestAllocateAvailable(t *testing.T) {
u := getTestUser()
mappedPath := filepath.Join(os.TempDir(), "vdir")
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
@ -1415,6 +1415,16 @@ func TestAllocate(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, ftp.StatusCommandOK, code)
assert.Equal(t, "Done !", response)
code, response, err = client.SendCustomCommand("AVBL /vdir")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
assert.Equal(t, "110", response)
code, _, err = client.SendCustomCommand("AVBL")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
err = client.Quit()
assert.NoError(t, err)
}
@ -1442,6 +1452,12 @@ func TestAllocate(t *testing.T) {
err = ftpUploadFile(testFilePath, testFileName, testFileSize, client, 0)
assert.NoError(t, err)
code, response, err = client.SendCustomCommand("AVBL")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
assert.Equal(t, "1", response)
// we still have space in vdir
code, response, err = client.SendCustomCommand("allo 50")
assert.NoError(t, err)
@ -1475,10 +1491,38 @@ func TestAllocate(t *testing.T) {
assert.Equal(t, ftp.StatusFileUnavailable, code)
assert.Contains(t, response, common.ErrQuotaExceeded.Error())
code, response, err = client.SendCustomCommand("AVBL")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
assert.Equal(t, "100", response)
err = client.Quit()
assert.NoError(t, err)
}
user.QuotaSize = 50
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
code, response, err := client.SendCustomCommand("AVBL")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
assert.Equal(t, "0", response)
}
user.QuotaSize = 1000
user.Filters.MaxUploadFileSize = 1
user, _, err = httpd.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
client, err = getFTPClient(user, false)
if assert.NoError(t, err) {
code, response, err := client.SendCustomCommand("AVBL")
assert.NoError(t, err)
assert.Equal(t, ftp.StatusFile, code)
assert.Equal(t, "1", response)
}
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveFolder(vfs.BaseVirtualFolder{MappedPath: mappedPath}, http.StatusOK)
@ -1489,6 +1533,30 @@ func TestAllocate(t *testing.T) {
assert.NoError(t, err)
}
func TestAvailableUnsupportedFs(t *testing.T) {
u := getTestUser()
localUser, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
sftpUser, _, err := httpd.AddUser(getTestSFTPUser(), http.StatusOK)
assert.NoError(t, err)
client, err := getFTPClient(sftpUser, false)
if assert.NoError(t, err) {
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")
err = client.Quit()
assert.NoError(t, err)
}
_, err = httpd.RemoveUser(sftpUser, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveUser(localUser, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(localUser.GetHomeDir())
assert.NoError(t, err)
}
func TestChtimes(t *testing.T) {
u := getTestUser()
localUser, _, err := httpd.AddUser(u, http.StatusOK)

View file

@ -49,7 +49,7 @@ func (c *Connection) Disconnect() error {
return c.clientContext.Close()
}
// GetCommand returns an empty string
// GetCommand returns the last received FTP command
func (c *Connection) GetCommand() string {
return c.clientContext.GetLastCommand()
}
@ -209,7 +209,42 @@ func (c *Connection) Chtimes(name string, atime time.Time, mtime time.Time) erro
return c.SetStat(p, name, &attrs)
}
// AllocateSpace implements ClientDriverExtensionAllocate
// GetAvailableSpace implements ClientDriverExtensionAvailableSpace interface
func (c *Connection) GetAvailableSpace(dirName string) (int64, error) {
c.UpdateLastActivity()
quotaResult := c.HasSpace(false, path.Join(dirName, "fakefile.txt"))
if !quotaResult.HasSpace {
return 0, nil
}
if quotaResult.AllowedSize == 0 {
// no quota restrictions
if c.User.Filters.MaxUploadFileSize > 0 {
return c.User.Filters.MaxUploadFileSize, nil
}
p, err := c.Fs.ResolvePath(dirName)
if err != nil {
return 0, c.GetFsError(err)
}
return c.Fs.GetAvailableDiskSize(p)
}
// the available space is the minimum between MaxUploadFileSize, if setted,
// and quota allowed size
if c.User.Filters.MaxUploadFileSize > 0 {
if c.User.Filters.MaxUploadFileSize < quotaResult.AllowedSize {
return c.User.Filters.MaxUploadFileSize, nil
}
}
return quotaResult.AllowedSize, nil
}
// AllocateSpace implements ClientDriverExtensionAllocate interface
func (c *Connection) AllocateSpace(size int) error {
c.UpdateLastActivity()
// check the max allowed file size first

View file

@ -342,6 +342,10 @@ func TestResolvePathErrors(t *testing.T) {
if assert.Error(t, err) {
assert.EqualError(t, err, common.ErrGenericFailure.Error())
}
_, err = connection.GetAvailableSpace("")
if assert.Error(t, err) {
assert.EqualError(t, err, common.ErrGenericFailure.Error())
}
}
func TestUploadFileStatError(t *testing.T) {

1
go.mod
View file

@ -34,6 +34,7 @@ require (
github.com/rs/xid v1.2.1
github.com/rs/zerolog v1.20.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil/v3 v3.20.11
github.com/spf13/afero v1.5.1
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.1

5
go.sum
View file

@ -85,6 +85,7 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -208,6 +209,7 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@ -559,6 +561,8 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
github.com/shirou/gopsutil/v3 v3.20.11 h1:NeVf1K0cgxsWz+N3671ojRptdgzvp7BXL3KV21R0JnA=
github.com/shirou/gopsutil/v3 v3.20.11/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -742,6 +746,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -701,6 +701,11 @@ func (*AzureBlobFs) Close() error {
return nil
}
// GetAvailableDiskSize return the available size for the specified path
func (*AzureBlobFs) GetAvailableDiskSize(dirName string) (int64, error) {
return 0, errStorageSizeUnavailable
}
func (fs *AzureBlobFs) isEqual(key string, virtualName string) bool {
if key == virtualName {
return true

View file

@ -754,3 +754,8 @@ func (fs *GCSFs) GetMimeType(name string) (string, error) {
func (fs *GCSFs) Close() error {
return nil
}
// GetAvailableDiskSize return the available size for the specified path
func (*GCSFs) GetAvailableDiskSize(dirName string) (int64, error) {
return 0, errStorageSizeUnavailable
}

View file

@ -12,6 +12,7 @@ import (
"github.com/eikenb/pipeat"
"github.com/rs/xid"
"github.com/shirou/gopsutil/v3/disk"
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils"
@ -477,3 +478,12 @@ func (fs *OsFs) GetMimeType(name string) (string, error) {
func (*OsFs) Close() error {
return nil
}
// 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
}

View file

@ -703,3 +703,8 @@ func (fs *S3Fs) GetMimeType(name string) (string, error) {
func (*S3Fs) Close() error {
return nil
}
// GetAvailableDiskSize return the available size for the specified path
func (*S3Fs) GetAvailableDiskSize(dirName string) (int64, error) {
return 0, errStorageSizeUnavailable
}

View file

@ -494,6 +494,11 @@ 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 {

View file

@ -22,7 +22,10 @@ import (
const dirMimeType = "inode/directory"
var validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
var (
validAzAccessTier = []string{"", "Archive", "Hot", "Cool"}
errStorageSizeUnavailable = errors.New("unable to get available size for this storage backend")
)
// Fs defines the interface for filesystem backends
type Fs interface {
@ -57,6 +60,7 @@ type Fs interface {
Join(elem ...string) string
HasVirtualFolders() bool
GetMimeType(name string) (string, error)
GetAvailableDiskSize(dirName string) (int64, error)
Close() error
}