sftpd: add Readlink support
This commit is contained in:
parent
5208e4a4ca
commit
02e35ee002
16 changed files with 183 additions and 38 deletions
|
@ -146,6 +146,7 @@ type ActiveTransfer interface {
|
||||||
GetStartTime() time.Time
|
GetStartTime() time.Time
|
||||||
SignalClose()
|
SignalClose()
|
||||||
Truncate(fsPath string, size int64) (int64, error)
|
Truncate(fsPath string, size int64) (int64, error)
|
||||||
|
GetRealFsPath(fsPath string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveConnection defines the interface for the current active connections
|
// ActiveConnection defines the interface for the current active connections
|
||||||
|
|
|
@ -172,6 +172,18 @@ func (c *BaseConnection) SignalTransfersAbort() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BaseConnection) getRealFsPath(fsPath string) string {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
for _, t := range c.activeTransfers {
|
||||||
|
if p := t.GetRealFsPath(fsPath); len(p) > 0 {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fsPath
|
||||||
|
}
|
||||||
|
|
||||||
func (c *BaseConnection) truncateOpenHandle(fsPath string, size int64) (int64, error) {
|
func (c *BaseConnection) truncateOpenHandle(fsPath string, size int64) (int64, error) {
|
||||||
c.RLock()
|
c.RLock()
|
||||||
defer c.RUnlock()
|
defer c.RUnlock()
|
||||||
|
@ -439,7 +451,7 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
|
||||||
if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
|
if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
|
||||||
return c.GetPermissionDeniedError()
|
return c.GetPermissionDeniedError()
|
||||||
}
|
}
|
||||||
if err := c.Fs.Chmod(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)
|
||||||
}
|
}
|
||||||
|
@ -451,7 +463,7 @@ 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 err := c.Fs.Chown(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)
|
||||||
return c.GetFsError(err)
|
return c.GetFsError(err)
|
||||||
|
@ -465,7 +477,7 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
|
||||||
return c.GetPermissionDeniedError()
|
return c.GetPermissionDeniedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Fs.Chtimes(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)
|
||||||
return c.GetFsError(err)
|
return c.GetFsError(err)
|
||||||
|
|
|
@ -605,7 +605,7 @@ func TestSpaceForCrossRename(t *testing.T) {
|
||||||
testDir := filepath.Join(os.TempDir(), "dir")
|
testDir := filepath.Join(os.TempDir(), "dir")
|
||||||
err = os.MkdirAll(testDir, os.ModePerm)
|
err = os.MkdirAll(testDir, os.ModePerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = ioutil.WriteFile(filepath.Join(testDir, "afile"), []byte("content"), os.ModePerm)
|
err = ioutil.WriteFile(filepath.Join(testDir, "afile.txt"), []byte("content"), os.ModePerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = os.Chmod(testDir, 0001)
|
err = os.Chmod(testDir, 0001)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -616,7 +616,7 @@ func TestSpaceForCrossRename(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testFile := filepath.Join(os.TempDir(), "afile")
|
testFile := filepath.Join(os.TempDir(), "afile.txt")
|
||||||
err = ioutil.WriteFile(testFile, []byte("test data"), os.ModePerm)
|
err = ioutil.WriteFile(testFile, []byte("test data"), os.ModePerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
quotaResult = vfs.QuotaCheckResult{
|
quotaResult = vfs.QuotaCheckResult{
|
||||||
|
|
|
@ -108,6 +108,18 @@ func (t *BaseTransfer) GetFsPath() string {
|
||||||
return t.fsPath
|
return t.fsPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRealFsPath returns the real transfer filesystem path.
|
||||||
|
// If atomic uploads are enabled this differ from fsPath
|
||||||
|
func (t *BaseTransfer) GetRealFsPath(fsPath string) string {
|
||||||
|
if fsPath == t.GetFsPath() {
|
||||||
|
if t.File != nil {
|
||||||
|
return t.File.Name()
|
||||||
|
}
|
||||||
|
return t.fsPath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// SetCancelFn sets the cancel function for the transfer
|
// SetCancelFn sets the cancel function for the transfer
|
||||||
func (t *BaseTransfer) SetCancelFn(cancelFn func()) {
|
func (t *BaseTransfer) SetCancelFn(cancelFn func()) {
|
||||||
t.cancelFn = cancelFn
|
t.cancelFn = cancelFn
|
||||||
|
|
|
@ -83,6 +83,36 @@ func TestTransferThrottling(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRealPath(t *testing.T) {
|
||||||
|
testFile := filepath.Join(os.TempDir(), "afile.txt")
|
||||||
|
fs := vfs.NewOsFs("123", os.TempDir(), nil)
|
||||||
|
u := dataprovider.User{
|
||||||
|
Username: "user",
|
||||||
|
HomeDir: os.TempDir(),
|
||||||
|
}
|
||||||
|
u.Permissions = make(map[string][]string)
|
||||||
|
u.Permissions["/"] = []string{dataprovider.PermAny}
|
||||||
|
file, err := os.Create(testFile)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
assert.FailNow(t, "unable to open test file")
|
||||||
|
}
|
||||||
|
conn := NewBaseConnection(fs.ConnectionID(), ProtocolSFTP, u, fs)
|
||||||
|
transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 0, 0, true, fs)
|
||||||
|
rPath := transfer.GetRealFsPath(testFile)
|
||||||
|
assert.Equal(t, testFile, rPath)
|
||||||
|
rPath = conn.getRealFsPath(testFile)
|
||||||
|
assert.Equal(t, testFile, rPath)
|
||||||
|
err = transfer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = file.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
transfer.File = nil
|
||||||
|
rPath = transfer.GetRealFsPath(testFile)
|
||||||
|
assert.Equal(t, testFile, rPath)
|
||||||
|
rPath = transfer.GetRealFsPath("")
|
||||||
|
assert.Empty(t, rPath)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTruncate(t *testing.T) {
|
func TestTruncate(t *testing.T) {
|
||||||
testFile := filepath.Join(os.TempDir(), "transfer_test_file")
|
testFile := filepath.Join(os.TempDir(), "transfer_test_file")
|
||||||
fs := vfs.NewOsFs("123", os.TempDir(), nil)
|
fs := vfs.NewOsFs("123", os.TempDir(), nil)
|
||||||
|
@ -99,14 +129,14 @@ func TestTruncate(t *testing.T) {
|
||||||
_, err = file.Write([]byte("hello"))
|
_, err = file.Write([]byte("hello"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
conn := NewBaseConnection(fs.ConnectionID(), ProtocolSFTP, u, fs)
|
conn := NewBaseConnection(fs.ConnectionID(), ProtocolSFTP, u, fs)
|
||||||
transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 0, 100, true, fs)
|
transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 5, 100, false, fs)
|
||||||
|
|
||||||
err = conn.SetStat(testFile, "/transfer_test_file", &StatAttributes{
|
err = conn.SetStat(testFile, "/transfer_test_file", &StatAttributes{
|
||||||
Size: 2,
|
Size: 2,
|
||||||
Flags: StatAttrSize,
|
Flags: StatAttrSize,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(98), transfer.MaxWriteSize)
|
assert.Equal(t, int64(103), transfer.MaxWriteSize)
|
||||||
err = transfer.Close()
|
err = transfer.Close()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = file.Close()
|
err = file.Close()
|
||||||
|
|
|
@ -227,7 +227,7 @@ func (u *User) AddVirtualDirs(list []os.FileInfo, sftpPath string) []os.FileInfo
|
||||||
}
|
}
|
||||||
for _, v := range u.VirtualFolders {
|
for _, v := range u.VirtualFolders {
|
||||||
if path.Dir(v.VirtualPath) == sftpPath {
|
if path.Dir(v.VirtualPath) == sftpPath {
|
||||||
fi := vfs.NewFileInfo(v.VirtualPath, true, 0, time.Now())
|
fi := vfs.NewFileInfo(v.VirtualPath, true, 0, time.Now(), false)
|
||||||
found := false
|
found := false
|
||||||
for index, f := range list {
|
for index, f := range list {
|
||||||
if f.Name() == fi.Name() {
|
if f.Name() == fi.Name() {
|
||||||
|
|
|
@ -195,6 +195,23 @@ func (c *Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return listerAt([]os.FileInfo{s}), nil
|
return listerAt([]os.FileInfo{s}), nil
|
||||||
|
case "Readlink":
|
||||||
|
if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(request.Filepath)) {
|
||||||
|
return nil, sftp.ErrSSHFxPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := c.Fs.Readlink(p)
|
||||||
|
if err != nil {
|
||||||
|
c.Log(logger.LevelWarn, "error running readlink on path %#v: %+v", p, err)
|
||||||
|
return nil, c.GetFsError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(s)) {
|
||||||
|
return nil, sftp.ErrSSHFxPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
return listerAt([]os.FileInfo{vfs.NewFileInfo(s, false, 0, time.Now(), true)}), nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, sftp.ErrSSHFxOpUnsupported
|
return nil, sftp.ErrSSHFxOpUnsupported
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,6 +229,17 @@ func TestReadWriteErrors(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnsupportedListOP(t *testing.T) {
|
||||||
|
fs := vfs.NewOsFs("", os.TempDir(), nil)
|
||||||
|
conn := common.NewBaseConnection("", common.ProtocolSFTP, dataprovider.User{}, fs)
|
||||||
|
sftpConn := Connection{
|
||||||
|
BaseConnection: conn,
|
||||||
|
}
|
||||||
|
request := sftp.NewRequest("Unsupported", "")
|
||||||
|
_, err := sftpConn.Filelist(request)
|
||||||
|
assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
|
||||||
|
}
|
||||||
|
|
||||||
func TestTransferCancelFn(t *testing.T) {
|
func TestTransferCancelFn(t *testing.T) {
|
||||||
testfile := "testfile"
|
testfile := "testfile"
|
||||||
file, err := os.Create(testfile)
|
file, err := os.Create(testfile)
|
||||||
|
|
|
@ -608,8 +608,9 @@ func TestLink(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = client.Symlink(testFileName, testFileName+".link")
|
err = client.Symlink(testFileName, testFileName+".link")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, err = client.ReadLink(testFileName + ".link")
|
linkName, err := client.ReadLink(testFileName + ".link")
|
||||||
assert.Error(t, err, "readlink is currently not implemented so must fail")
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, path.Join("/", testFileName), linkName)
|
||||||
err = client.Symlink(testFileName, testFileName+".link")
|
err = client.Symlink(testFileName, testFileName+".link")
|
||||||
assert.Error(t, err, "creating a symlink to an existing one must fail")
|
assert.Error(t, err, "creating a symlink to an existing one must fail")
|
||||||
err = client.Link(testFileName, testFileName+".hlink")
|
err = client.Link(testFileName, testFileName+".hlink")
|
||||||
|
@ -657,7 +658,12 @@ func TestStat(t *testing.T) {
|
||||||
assert.Equal(t, newPerm, newFi.Mode().Perm())
|
assert.Equal(t, newPerm, newFi.Mode().Perm())
|
||||||
}
|
}
|
||||||
_, err = client.ReadLink(testFileName)
|
_, err = client.ReadLink(testFileName)
|
||||||
assert.Error(t, err, "readlink is not supported and must fail")
|
assert.Error(t, err, "readlink on a file must fail")
|
||||||
|
err = client.Symlink(testFileName, testFileName+".sym")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
linkName, err := client.ReadLink(testFileName + ".sym")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, path.Join("/", testFileName), linkName)
|
||||||
newPerm = os.FileMode(0666)
|
newPerm = os.FileMode(0666)
|
||||||
err = client.Chmod(testFileName, newPerm)
|
err = client.Chmod(testFileName, newPerm)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -674,6 +680,21 @@ func TestStat(t *testing.T) {
|
||||||
err = f.Close()
|
err = f.Close()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
f, err = client.OpenFile(testFileName, os.O_WRONLY)
|
||||||
|
newPerm = os.FileMode(0444)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
err = f.Chmod(newPerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = f.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
newFi, err = client.Lstat(testFileName)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
assert.Equal(t, newPerm, newFi.Mode().Perm())
|
||||||
|
}
|
||||||
|
newPerm = os.FileMode(0666)
|
||||||
|
err = client.Chmod(testFileName, newPerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
err = os.Remove(testFilePath)
|
err = os.Remove(testFilePath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
@ -4650,6 +4671,7 @@ func TestPermList(t *testing.T) {
|
||||||
u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
|
u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
|
||||||
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
|
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite, dataprovider.PermChmod,
|
||||||
dataprovider.PermChown, dataprovider.PermChtimes}
|
dataprovider.PermChown, dataprovider.PermChtimes}
|
||||||
|
u.Permissions["/sub"] = []string{dataprovider.PermCreateSymlinks, dataprovider.PermListItems}
|
||||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
client, err := getSftpClient(user, usePubKey)
|
client, err := getSftpClient(user, usePubKey)
|
||||||
|
@ -4659,6 +4681,21 @@ func TestPermList(t *testing.T) {
|
||||||
assert.Error(t, err, "read remote dir without permission should not succeed")
|
assert.Error(t, err, "read remote dir without permission should not succeed")
|
||||||
_, err = client.Stat("test_file")
|
_, err = client.Stat("test_file")
|
||||||
assert.Error(t, err, "stat remote file without permission should not succeed")
|
assert.Error(t, err, "stat remote file without permission should not succeed")
|
||||||
|
_, err = client.ReadLink("test_link")
|
||||||
|
assert.Error(t, err, "read remote link without permission on source dir should not succeed")
|
||||||
|
f, err := client.Create(testFileName)
|
||||||
|
if assert.NoError(t, err) {
|
||||||
|
_, err = f.Write([]byte("content"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = f.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
err = client.Mkdir("sub")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = client.Symlink(testFileName, path.Join("/sub", testFileName))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = client.ReadLink(path.Join("/sub", testFileName))
|
||||||
|
assert.Error(t, err, "read remote link without permission on targe dir should not succeed")
|
||||||
}
|
}
|
||||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -21,16 +21,20 @@ type FileInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileInfo creates file info.
|
// NewFileInfo creates file info.
|
||||||
func NewFileInfo(name string, isDirectory bool, sizeInBytes int64, modTime time.Time) FileInfo {
|
func NewFileInfo(name string, isDirectory bool, sizeInBytes int64, modTime time.Time, fullName bool) FileInfo {
|
||||||
mode := os.FileMode(0644)
|
mode := os.FileMode(0644)
|
||||||
contentType := ""
|
contentType := ""
|
||||||
if isDirectory {
|
if isDirectory {
|
||||||
mode = os.FileMode(0755) | os.ModeDir
|
mode = os.FileMode(0755) | os.ModeDir
|
||||||
contentType = "inode/directory"
|
contentType = "inode/directory"
|
||||||
}
|
}
|
||||||
|
if !fullName {
|
||||||
|
// we have always Unix style paths here
|
||||||
|
name = path.Base(name)
|
||||||
|
}
|
||||||
|
|
||||||
return FileInfo{
|
return FileInfo{
|
||||||
name: path.Base(name), // we have always Unix style paths here
|
name: name,
|
||||||
sizeInBytes: sizeInBytes,
|
sizeInBytes: sizeInBytes,
|
||||||
modTime: modTime,
|
modTime: modTime,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
|
|
21
vfs/gcsfs.go
21
vfs/gcsfs.go
|
@ -86,10 +86,10 @@ func (fs GCSFs) Stat(name string) (os.FileInfo, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
return NewFileInfo(name, true, 0, time.Now()), nil
|
return NewFileInfo(name, true, 0, time.Now(), false), nil
|
||||||
}
|
}
|
||||||
if fs.config.KeyPrefix == name+"/" {
|
if fs.config.KeyPrefix == name+"/" {
|
||||||
return NewFileInfo(name, true, 0, time.Now()), nil
|
return NewFileInfo(name, true, 0, time.Now(), false), nil
|
||||||
}
|
}
|
||||||
prefix := fs.getPrefixForStat(name)
|
prefix := fs.getPrefixForStat(name)
|
||||||
query := &storage.Query{Prefix: prefix, Delimiter: "/"}
|
query := &storage.Query{Prefix: prefix, Delimiter: "/"}
|
||||||
|
@ -108,7 +108,7 @@ func (fs GCSFs) Stat(name string) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
if len(attrs.Prefix) > 0 {
|
if len(attrs.Prefix) > 0 {
|
||||||
if fs.isEqual(attrs.Prefix, name) {
|
if fs.isEqual(attrs.Prefix, name) {
|
||||||
result = NewFileInfo(name, true, 0, time.Now())
|
result = NewFileInfo(name, true, 0, time.Now(), false)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,7 +117,7 @@ func (fs GCSFs) Stat(name string) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
if fs.isEqual(attrs.Name, name) {
|
if fs.isEqual(attrs.Name, name) {
|
||||||
isDir := strings.HasSuffix(attrs.Name, "/")
|
isDir := strings.HasSuffix(attrs.Name, "/")
|
||||||
result = NewFileInfo(name, isDir, attrs.Size, attrs.Updated)
|
result = NewFileInfo(name, isDir, attrs.Size, attrs.Updated, false)
|
||||||
if !isDir {
|
if !isDir {
|
||||||
result.setContentType(attrs.ContentType)
|
result.setContentType(attrs.ContentType)
|
||||||
}
|
}
|
||||||
|
@ -280,6 +280,11 @@ func (GCSFs) Symlink(source, target string) error {
|
||||||
return errors.New("403 symlinks are not supported")
|
return errors.New("403 symlinks are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link
|
||||||
|
func (GCSFs) Readlink(name string) (string, error) {
|
||||||
|
return "", errors.New("403 readlink is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Chown changes the numeric uid and gid of the named file.
|
// Chown changes the numeric uid and gid of the named file.
|
||||||
// Silently ignored.
|
// Silently ignored.
|
||||||
func (GCSFs) Chown(name string, uid int, gid int) error {
|
func (GCSFs) Chown(name string, uid int, gid int) error {
|
||||||
|
@ -333,7 +338,7 @@ func (fs GCSFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
if len(attrs.Prefix) > 0 {
|
if len(attrs.Prefix) > 0 {
|
||||||
name, _ := fs.resolve(attrs.Prefix, prefix)
|
name, _ := fs.resolve(attrs.Prefix, prefix)
|
||||||
result = append(result, NewFileInfo(name, true, 0, time.Now()))
|
result = append(result, NewFileInfo(name, true, 0, time.Now(), false))
|
||||||
} else {
|
} else {
|
||||||
name, isDir := fs.resolve(attrs.Name, prefix)
|
name, isDir := fs.resolve(attrs.Name, prefix)
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
|
@ -342,7 +347,7 @@ func (fs GCSFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
if !attrs.Deleted.IsZero() {
|
if !attrs.Deleted.IsZero() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fi := NewFileInfo(name, isDir, attrs.Size, attrs.Updated)
|
fi := NewFileInfo(name, isDir, attrs.Size, attrs.Updated, false)
|
||||||
if !isDir {
|
if !isDir {
|
||||||
fi.setContentType(attrs.ContentType)
|
fi.setContentType(attrs.ContentType)
|
||||||
}
|
}
|
||||||
|
@ -508,13 +513,13 @@ func (fs GCSFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = walkFn(attrs.Name, NewFileInfo(name, isDir, attrs.Size, attrs.Updated), nil)
|
err = walkFn(attrs.Name, NewFileInfo(name, isDir, attrs.Size, attrs.Updated, false), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
walkFn(root, NewFileInfo(root, true, 0, time.Now()), err) //nolint:errcheck
|
walkFn(root, NewFileInfo(root, true, 0, time.Now(), false), err) //nolint:errcheck
|
||||||
metrics.GCSListObjectsCompleted(err)
|
metrics.GCSListObjectsCompleted(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
14
vfs/osfs.go
14
vfs/osfs.go
|
@ -56,7 +56,7 @@ func (fs OsFs) Stat(name string) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
for _, v := range fs.virtualFolders {
|
for _, v := range fs.virtualFolders {
|
||||||
if v.MappedPath == name {
|
if v.MappedPath == name {
|
||||||
info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime())
|
info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ func (fs OsFs) Lstat(name string) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
for _, v := range fs.virtualFolders {
|
for _, v := range fs.virtualFolders {
|
||||||
if v.MappedPath == name {
|
if v.MappedPath == name {
|
||||||
info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime())
|
info := NewFileInfo(v.VirtualPath, true, fi.Size(), fi.ModTime(), false)
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,6 +116,16 @@ func (OsFs) Symlink(source, target string) error {
|
||||||
return os.Symlink(source, target)
|
return os.Symlink(source, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link
|
||||||
|
// as absolute virtual path
|
||||||
|
func (fs OsFs) Readlink(name string) (string, error) {
|
||||||
|
p, err := os.Readlink(name)
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
return fs.GetRelativePath(p), err
|
||||||
|
}
|
||||||
|
|
||||||
// Chown changes the numeric uid and gid of the named file.
|
// Chown changes the numeric uid and gid of the named file.
|
||||||
func (OsFs) Chown(name string, uid int, gid int) error {
|
func (OsFs) Chown(name string, uid int, gid int) error {
|
||||||
return os.Chown(name, uid, gid)
|
return os.Chown(name, uid, gid)
|
||||||
|
|
21
vfs/s3fs.go
21
vfs/s3fs.go
|
@ -112,10 +112,10 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
return NewFileInfo(name, true, 0, time.Now()), nil
|
return NewFileInfo(name, true, 0, time.Now(), false), nil
|
||||||
}
|
}
|
||||||
if "/"+fs.config.KeyPrefix == name+"/" {
|
if "/"+fs.config.KeyPrefix == name+"/" {
|
||||||
return NewFileInfo(name, true, 0, time.Now()), nil
|
return NewFileInfo(name, true, 0, time.Now(), false), nil
|
||||||
}
|
}
|
||||||
prefix := path.Dir(name)
|
prefix := path.Dir(name)
|
||||||
if prefix == "/" || prefix == "." {
|
if prefix == "/" || prefix == "." {
|
||||||
|
@ -135,7 +135,7 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
|
||||||
}, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
|
}, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
|
||||||
for _, p := range page.CommonPrefixes {
|
for _, p := range page.CommonPrefixes {
|
||||||
if fs.isEqual(p.Prefix, name) {
|
if fs.isEqual(p.Prefix, name) {
|
||||||
result = NewFileInfo(name, true, 0, time.Now())
|
result = NewFileInfo(name, true, 0, time.Now(), false)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ func (fs S3Fs) Stat(name string) (os.FileInfo, error) {
|
||||||
objectSize := *fileObject.Size
|
objectSize := *fileObject.Size
|
||||||
objectModTime := *fileObject.LastModified
|
objectModTime := *fileObject.LastModified
|
||||||
isDir := strings.HasSuffix(*fileObject.Key, "/")
|
isDir := strings.HasSuffix(*fileObject.Key, "/")
|
||||||
result = NewFileInfo(name, isDir, objectSize, objectModTime)
|
result = NewFileInfo(name, isDir, objectSize, objectModTime, false)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -312,6 +312,11 @@ func (S3Fs) Symlink(source, target string) error {
|
||||||
return errors.New("403 symlinks are not supported")
|
return errors.New("403 symlinks are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link
|
||||||
|
func (S3Fs) Readlink(name string) (string, error) {
|
||||||
|
return "", errors.New("403 readlink is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Chown changes the numeric uid and gid of the named file.
|
// Chown changes the numeric uid and gid of the named file.
|
||||||
// Silently ignored.
|
// Silently ignored.
|
||||||
func (S3Fs) Chown(name string, uid int, gid int) error {
|
func (S3Fs) Chown(name string, uid int, gid int) error {
|
||||||
|
@ -358,7 +363,7 @@ func (fs S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
}, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
|
}, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
|
||||||
for _, p := range page.CommonPrefixes {
|
for _, p := range page.CommonPrefixes {
|
||||||
name, isDir := fs.resolve(p.Prefix, prefix)
|
name, isDir := fs.resolve(p.Prefix, prefix)
|
||||||
result = append(result, NewFileInfo(name, isDir, 0, time.Now()))
|
result = append(result, NewFileInfo(name, isDir, 0, time.Now(), false))
|
||||||
}
|
}
|
||||||
for _, fileObject := range page.Contents {
|
for _, fileObject := range page.Contents {
|
||||||
objectSize := *fileObject.Size
|
objectSize := *fileObject.Size
|
||||||
|
@ -367,7 +372,7 @@ func (fs S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, NewFileInfo(name, isDir, objectSize, objectModTime))
|
result = append(result, NewFileInfo(name, isDir, objectSize, objectModTime, false))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -505,7 +510,7 @@ func (fs S3Fs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err := walkFn(fs.Join("/", *fileObject.Key), NewFileInfo(name, isDir, objectSize, objectModTime), nil)
|
err := walkFn(fs.Join("/", *fileObject.Key), NewFileInfo(name, isDir, objectSize, objectModTime, false), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -513,7 +518,7 @@ func (fs S3Fs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
metrics.S3ListObjectsCompleted(err)
|
metrics.S3ListObjectsCompleted(err)
|
||||||
walkFn(root, NewFileInfo(root, true, 0, time.Now()), err) //nolint:errcheck
|
walkFn(root, NewFileInfo(root, true, 0, time.Now(), false), err) //nolint:errcheck
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ type Fs interface {
|
||||||
Chtimes(name string, atime, mtime time.Time) error
|
Chtimes(name string, atime, mtime time.Time) error
|
||||||
Truncate(name string, size int64) error
|
Truncate(name string, size int64) error
|
||||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||||
|
Readlink(name string) (string, error)
|
||||||
IsUploadResumeSupported() bool
|
IsUploadResumeSupported() bool
|
||||||
IsAtomicUploadSupported() bool
|
IsAtomicUploadSupported() bool
|
||||||
CheckRootPath(username string, uid int, gid int) bool
|
CheckRootPath(username string, uid int, gid int) bool
|
||||||
|
|
|
@ -98,7 +98,7 @@ func (f *webDavFile) Stat() (os.FileInfo, error) {
|
||||||
f.Unlock()
|
f.Unlock()
|
||||||
if f.GetType() == common.TransferUpload && closed && errUpload == nil {
|
if f.GetType() == common.TransferUpload && closed && errUpload == nil {
|
||||||
info := webDavFileInfo{
|
info := webDavFileInfo{
|
||||||
FileInfo: vfs.NewFileInfo(f.GetFsPath(), false, atomic.LoadInt64(&f.BytesReceived), time.Now()),
|
FileInfo: vfs.NewFileInfo(f.GetFsPath(), false, atomic.LoadInt64(&f.BytesReceived), time.Now(), false),
|
||||||
file: f,
|
file: f,
|
||||||
}
|
}
|
||||||
return info, nil
|
return info, nil
|
||||||
|
|
|
@ -79,11 +79,11 @@ func (fs MockOsFs) Rename(source, target string) error {
|
||||||
// Walk returns a duplicate path for testing
|
// Walk returns a duplicate path for testing
|
||||||
func (fs MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
func (fs MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||||
if fs.err == errWalkDir {
|
if fs.err == errWalkDir {
|
||||||
walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now()), nil) //nolint:errcheck
|
walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
|
||||||
walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now()), nil) //nolint:errcheck
|
walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now()), nil) //nolint:errcheck
|
walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now(), false), nil) //nolint:errcheck
|
||||||
return fs.err
|
return fs.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,12 +317,12 @@ func TestFileAccessErrors(t *testing.T) {
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||||
}
|
}
|
||||||
info := vfs.NewFileInfo(missingPath, true, 0, time.Now())
|
info := vfs.NewFileInfo(missingPath, true, 0, time.Now(), false)
|
||||||
_, err = connection.getFile(fsMissingPath, missingPath, info)
|
_, err = connection.getFile(fsMissingPath, missingPath, info)
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||||
}
|
}
|
||||||
info = vfs.NewFileInfo(missingPath, false, 123, time.Now())
|
info = vfs.NewFileInfo(missingPath, false, 123, time.Now(), false)
|
||||||
_, err = connection.getFile(fsMissingPath, missingPath, info)
|
_, err = connection.getFile(fsMissingPath, missingPath, info)
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||||
|
@ -429,7 +429,7 @@ func TestContentType(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile,
|
baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile,
|
||||||
common.TransferDownload, 0, 0, 0, false, fs)
|
common.TransferDownload, 0, 0, 0, false, fs)
|
||||||
info := vfs.NewFileInfo(testFilePath, true, 0, time.Now())
|
info := vfs.NewFileInfo(testFilePath, true, 0, time.Now(), false)
|
||||||
davFile := newWebDavFile(baseTransfer, nil, nil, info)
|
davFile := newWebDavFile(baseTransfer, nil, nil, info)
|
||||||
fi, err := davFile.Stat()
|
fi, err := davFile.Stat()
|
||||||
if assert.NoError(t, err) {
|
if assert.NoError(t, err) {
|
||||||
|
|
Loading…
Reference in a new issue