소스 검색

improve chtimes handling on open files

Nicola Murino 3 년 전
부모
커밋
f2480ce5c9
8개의 변경된 파일93개의 추가작업 그리고 3개의 파일을 삭제
  1. 1 0
      common/common.go
  2. 12 0
      common/connection.go
  3. 52 0
      common/protocol_test.go
  4. 20 0
      common/transfer.go
  5. 1 0
      docs/portable-mode.md
  6. 4 0
      sftpd/sftpd_test.go
  7. 1 1
      templates/webadmin/folders.html
  8. 2 2
      templates/webadmin/users.html

+ 1 - 0
common/common.go

@@ -291,6 +291,7 @@ type ActiveTransfer interface {
 	SignalClose()
 	SignalClose()
 	Truncate(fsPath string, size int64) (int64, error)
 	Truncate(fsPath string, size int64) (int64, error)
 	GetRealFsPath(fsPath string) string
 	GetRealFsPath(fsPath string) string
+	SetTimes(fsPath string, atime time.Time, mtime time.Time) bool
 }
 }
 
 
 // ActiveConnection defines the interface for the current active connections
 // ActiveConnection defines the interface for the current active connections

+ 12 - 0
common/connection.go

@@ -201,6 +201,17 @@ func (c *BaseConnection) getRealFsPath(fsPath string) string {
 	return fsPath
 	return fsPath
 }
 }
 
 
+func (c *BaseConnection) setTimes(fsPath string, atime time.Time, mtime time.Time) {
+	c.RLock()
+	defer c.RUnlock()
+
+	for _, t := range c.activeTransfers {
+		if t.SetTimes(fsPath, atime, mtime) {
+			return
+		}
+	}
+}
+
 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()
@@ -558,6 +569,7 @@ func (c *BaseConnection) handleChtimes(fs vfs.Fs, fsPath, pathForPerms string, a
 			fsPath, attributes.Atime, attributes.Mtime, err)
 			fsPath, attributes.Atime, attributes.Mtime, err)
 		return c.GetFsError(fs, err)
 		return c.GetFsError(fs, err)
 	}
 	}
+	c.setTimes(fsPath, attributes.Atime, attributes.Mtime)
 	accessTimeString := attributes.Atime.Format(chtimesFormat)
 	accessTimeString := attributes.Atime.Format(chtimesFormat)
 	modificationTimeString := attributes.Mtime.Format(chtimesFormat)
 	modificationTimeString := attributes.Mtime.Format(chtimesFormat)
 	logger.CommandLog(chtimesLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1,
 	logger.CommandLog(chtimesLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1,

+ 52 - 0
common/protocol_test.go

@@ -321,6 +321,58 @@ func TestSetStat(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
 }
 
 
+func TestChtimesOpenHandle(t *testing.T) {
+	localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
+	assert.NoError(t, err)
+	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
+	assert.NoError(t, err)
+	u := getCryptFsUser()
+	u.Username += "_crypt"
+	cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
+	assert.NoError(t, err)
+
+	for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
+		conn, client, err := getSftpClient(user)
+		if assert.NoError(t, err) {
+			defer conn.Close()
+			defer client.Close()
+
+			f, err := client.Create(testFileName)
+			assert.NoError(t, err, "user %v", user.Username)
+			f1, err := client.Create(testFileName + "1")
+			assert.NoError(t, err, "user %v", user.Username)
+			acmodTime := time.Now().Add(36 * time.Hour)
+			err = client.Chtimes(testFileName, acmodTime, acmodTime)
+			assert.NoError(t, err, "user %v", user.Username)
+			_, err = f.Write(testFileContent)
+			assert.NoError(t, err, "user %v", user.Username)
+			err = f.Close()
+			assert.NoError(t, err, "user %v", user.Username)
+			err = f1.Close()
+			assert.NoError(t, err, "user %v", user.Username)
+			info, err := client.Lstat(testFileName)
+			assert.NoError(t, err, "user %v", user.Username)
+			diff := math.Abs(info.ModTime().Sub(acmodTime).Seconds())
+			assert.LessOrEqual(t, diff, float64(1), "user %v", user.Username)
+			info1, err := client.Lstat(testFileName + "1")
+			assert.NoError(t, err, "user %v", user.Username)
+			diff = math.Abs(info1.ModTime().Sub(acmodTime).Seconds())
+			assert.Greater(t, diff, float64(86400), "user %v", user.Username)
+		}
+	}
+
+	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
+	assert.NoError(t, err)
+	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
+	assert.NoError(t, err)
+	err = os.RemoveAll(localUser.GetHomeDir())
+	assert.NoError(t, err)
+	_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
+	assert.NoError(t, err)
+	err = os.RemoveAll(cryptFsUser.GetHomeDir())
+	assert.NoError(t, err)
+}
+
 func TestPermissionErrors(t *testing.T) {
 func TestPermissionErrors(t *testing.T) {
 	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
 	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
 	assert.NoError(t, err)
 	assert.NoError(t, err)

+ 20 - 0
common/transfer.go

@@ -38,6 +38,8 @@ type BaseTransfer struct { //nolint:maligned
 	isNewFile       bool
 	isNewFile       bool
 	transferType    int
 	transferType    int
 	AbortTransfer   int32
 	AbortTransfer   int32
+	aTime           time.Time
+	mTime           time.Time
 	sync.Mutex
 	sync.Mutex
 	ErrTransfer error
 	ErrTransfer error
 }
 }
@@ -115,6 +117,15 @@ func (t *BaseTransfer) GetFsPath() string {
 	return t.fsPath
 	return t.fsPath
 }
 }
 
 
+func (t *BaseTransfer) SetTimes(fsPath string, atime time.Time, mtime time.Time) bool {
+	if fsPath == t.GetFsPath() {
+		t.aTime = atime
+		t.mTime = mtime
+		return true
+	}
+	return false
+}
+
 // GetRealFsPath returns the real transfer filesystem path.
 // GetRealFsPath returns the real transfer filesystem path.
 // If atomic uploads are enabled this differ from fsPath
 // If atomic uploads are enabled this differ from fsPath
 func (t *BaseTransfer) GetRealFsPath(fsPath string) string {
 func (t *BaseTransfer) GetRealFsPath(fsPath string) string {
@@ -252,6 +263,7 @@ func (t *BaseTransfer) Close() error {
 		}
 		}
 		t.Connection.Log(logger.LevelDebug, "uploaded file size %v", fileSize)
 		t.Connection.Log(logger.LevelDebug, "uploaded file size %v", fileSize)
 		t.updateQuota(numFiles, fileSize)
 		t.updateQuota(numFiles, fileSize)
+		t.updateTimes()
 		logger.TransferLog(uploadLogSender, t.fsPath, elapsed, atomic.LoadInt64(&t.BytesReceived), t.Connection.User.Username,
 		logger.TransferLog(uploadLogSender, t.fsPath, elapsed, atomic.LoadInt64(&t.BytesReceived), t.Connection.User.Username,
 			t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode)
 			t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode)
 		ExecuteActionNotification(&t.Connection.User, operationUpload, t.fsPath, t.requestPath, "", "", "", t.Connection.protocol,
 		ExecuteActionNotification(&t.Connection.User, operationUpload, t.fsPath, t.requestPath, "", "", "", t.Connection.protocol,
@@ -266,6 +278,14 @@ func (t *BaseTransfer) Close() error {
 	return err
 	return err
 }
 }
 
 
+func (t *BaseTransfer) updateTimes() {
+	if !t.aTime.IsZero() && !t.mTime.IsZero() {
+		err := t.Fs.Chtimes(t.fsPath, t.aTime, t.mTime)
+		t.Connection.Log(logger.LevelDebug, "set times for file %#v, atime: %v, mtime: %v, err: %v",
+			t.fsPath, t.aTime, t.mTime, err)
+	}
+}
+
 func (t *BaseTransfer) updateQuota(numFiles int, fileSize int64) bool {
 func (t *BaseTransfer) updateQuota(numFiles int, fileSize int64) bool {
 	// S3 uploads are atomic, if there is an error nothing is uploaded
 	// S3 uploads are atomic, if there is an error nothing is uploaded
 	if t.File == nil && t.ErrTransfer != nil {
 	if t.File == nil && t.ErrTransfer != nil {

+ 1 - 0
docs/portable-mode.md

@@ -84,6 +84,7 @@ Flags:
       --s3-acl string
       --s3-acl string
       --s3-bucket string
       --s3-bucket string
       --s3-endpoint string
       --s3-endpoint string
+      --s3-force-path-style             Force path style bucket URL
       --s3-key-prefix string            Allows to restrict access to the
       --s3-key-prefix string            Allows to restrict access to the
                                         virtual folder identified by this
                                         virtual folder identified by this
                                         prefix and its contents
                                         prefix and its contents

+ 4 - 0
sftpd/sftpd_test.go

@@ -9762,6 +9762,8 @@ func getScpDownloadCommand(localPath, remotePath string, preserveTime, recursive
 	args = append(args, "2022")
 	args = append(args, "2022")
 	args = append(args, "-o")
 	args = append(args, "-o")
 	args = append(args, "StrictHostKeyChecking=no")
 	args = append(args, "StrictHostKeyChecking=no")
+	args = append(args, "-o")
+	args = append(args, "HostKeyAlgorithms=+ssh-rsa")
 	args = append(args, "-i")
 	args = append(args, "-i")
 	args = append(args, privateKeyPath)
 	args = append(args, privateKeyPath)
 	args = append(args, remotePath)
 	args = append(args, remotePath)
@@ -9787,6 +9789,8 @@ func getScpUploadCommand(localPath, remotePath string, preserveTime, remoteToRem
 	args = append(args, "2022")
 	args = append(args, "2022")
 	args = append(args, "-o")
 	args = append(args, "-o")
 	args = append(args, "StrictHostKeyChecking=no")
 	args = append(args, "StrictHostKeyChecking=no")
+	args = append(args, "-o")
+	args = append(args, "HostKeyAlgorithms=+ssh-rsa")
 	args = append(args, "-i")
 	args = append(args, "-i")
 	args = append(args, privateKeyPath)
 	args = append(args, privateKeyPath)
 	args = append(args, localPath)
 	args = append(args, localPath)

+ 1 - 1
templates/webadmin/folders.html

@@ -232,7 +232,7 @@ function deleteAction() {
                 },
                 },
                 {
                 {
                     "targets": [2],
                     "targets": [2],
-                    "render": $.fn.dataTable.render.ellipsis(50, true),
+                    "render": $.fn.dataTable.render.ellipsis(60, true),
                 },
                 },
                 {
                 {
                     "targets": [3],
                     "targets": [3],

+ 2 - 2
templates/webadmin/users.html

@@ -255,11 +255,11 @@
                 },
                 },
                 {
                 {
                     "targets": [3],
                     "targets": [3],
-                    "render": $.fn.dataTable.render.ellipsis(30, true),
+                    "render": $.fn.dataTable.render.ellipsis(40, true),
                 },
                 },
                 {
                 {
                     "targets": [4],
                     "targets": [4],
-                    "render": $.fn.dataTable.render.ellipsis(40, true),
+                    "render": $.fn.dataTable.render.ellipsis(70, true),
                 }
                 }
             ],
             ],
             "scrollX": false,
             "scrollX": false,