From 2a91ea8cf27011f831925588c4920727f0d9b938 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 14 Jul 2026 12:12:24 +0100 Subject: [PATCH] ftp: fix incorrect modtime after uploading a file or setting its modtime SetModTime (and the no_check_upload Update path) kept the caller's full precision modtime in memory while the server stores second precision, so until the object was re-read the modtime did not match what a fresh listing would report. This broke wrappers which compare exact modtimes, such as the hasher backend's fingerprint and the VFS cache. Truncate the modtime to second precision to match what the server stores. --- backend/ftp/ftp.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/ftp/ftp.go b/backend/ftp/ftp.go index 5b73801b0..ce90d3f0f 100644 --- a/backend/ftp/ftp.go +++ b/backend/ftp/ftp.go @@ -1235,7 +1235,10 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error { path = o.fs.opt.Enc.FromStandardPath(path) err = c.SetTime(path, modTime.In(time.UTC)) if err == nil && o.info != nil { - o.info.ModTime = modTime + // The server stores modtimes with second precision so + // truncate here too to keep the in-memory modtime identical + // to the one a fresh listing returns. + o.info.ModTime = modTime.Truncate(time.Second) o.info.precise = true } o.fs.putFtpConnection(&c, err) @@ -1390,9 +1393,12 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op o.fs.putFtpConnection(&c, nil) if o.fs.opt.NoCheckUpload { o.info = &FileInfo{ - Name: o.remote, - Size: uint64(src.Size()), - ModTime: src.ModTime(ctx), + Name: o.remote, + Size: uint64(src.Size()), + // The server stores modtimes with second precision so + // truncate here too to keep the in-memory modtime + // identical to the one a fresh listing returns. + ModTime: src.ModTime(ctx).Truncate(time.Second), precise: true, IsDir: false, }