Commit Graph
2713 Commits
Author SHA1 Message Date
Sanjay Kanth AandGitHub 13084df67c yandex: add app_folder option to support cloud_api:disk.app_folder OAuth scope - Fixes #9848 2026-09-08 16:40:17 +01:00
ferrumclaudepilgrimandNick Craig-Wood 2cec6065d3 local: make out of space errors fatal during multi-thread transfers 2026-09-08 16:35:36 +01:00
ferrumclaudepilgrimandNick Craig-Wood 7bfc9ca648 local: clarify what --local-fatal-if-no-space catches
The flag applies to out of space errors while writing and while creating
files or directories, not only while writing. Describe those operations
without naming ENOSPC, which is a Unix error that Windows never reports, so
the help is accurate on every platform.
2026-09-08 16:35:36 +01:00
ferrumclaudepilgrimandNick Craig-Wood ca41db095b fserrors: fix out of space detection on Windows - fixes #8011
IsErrNoSpace compared against syscall.ENOSPC. Go defines that constant on
Windows as a value in its application reserved range which no Windows API
returns, so the comparison could never be true there. A full disk on Windows
reports ERROR_DISK_FULL or ERROR_HANDLE_DISK_FULL instead.

Preallocation failures were still caught, because those return a separate
sentinel, but a disk that is already full fails at the directory creation or
at the open long before preallocation is reached. That is the case reported.

The errors are now held in a list which platform specific files add to in
their init, which is the shape retriable_errors already uses in this package,
and the comparison itself is unchanged. Windows appends the two codes that
lib/file already recognises when preallocation fails. Every other platform
keeps exactly the behaviour it had.

This also reaches the VFS cache, which uses the same helper and has no
preallocation path of its own, so its out of space handling has been inert
on Windows.
2026-09-08 16:35:36 +01:00
Nick Craig-Wood b88e237e8c archive: fix listing entries with a leading slash as if they were in the root
The check that an entry returned by an archiver is a direct child of
the directory being listed normalised a parent of "/" to the root, so
an entry named "/x" passed as a child of the root while "x/" and
"dir//x" were rejected.

Decide by stripping the directory prefix and checking what is left
with sanitize.Leaf, which rejects an empty name, ".", ".." and any
name containing a "/". This also covers the leading slash case.
2026-09-05 12:14:46 +01:00
Nick Craig-Wood da352a2a1b archive: fix zip file entries named for a directory causing confusion
A file entry in a zip whose name refers to a directory, such as ".",
"/", "" or "sub/.", was only skipped when it named the root of an
archive which was itself the root of the remote. When the archive was
found by listing its parent directory the entry appeared as a file
with the same name as the archive alongside the directory for it, and
copying the archive tried to write both. When the entry named a
subdirectory it appeared as a file alongside that directory, and with
that subdirectory mounted as the archive root the entry was taken to
be the single file the root points at, hiding every real entry.

Skip any file entry whose last path component is "", "." or "..",
checked on the raw name before it is cleaned or joined on the prefix.
2026-09-05 12:14:46 +01:00
Nick Craig-Wood 68eab60564 archive: fix corrupt listings when listing a zip directory more than once
The zip archiver handed out its cached directory tree directly. Any
caller which filters a listing in place (as the core listing code
does) altered the cache, so later listings of the same directory could
be corrupted.

Return a copy of the cached listing instead.
2026-09-05 12:14:46 +01:00
Nick Craig-Wood 6cdd0ea761 seafile: fix corrupted uploads after a retried upload error
When an upload failed with a 500 error the upload was retried with a
new upload link but the same input stream. The stream had already been
consumed by the first attempt so the retry uploaded an empty file.

This fixes it by returning a RetryError instead so the caller retries
the upload with a fresh stream, which will fetch a new upload link.
2026-09-05 12:14:46 +01:00
Nick Craig-Wood fa43f10af2 filescom: fix corrupted uploads after a retried upload error
When an upload failed with a retryable error the pacer retried the
whole upload with the same input stream. The stream had already been
consumed by the first attempt so the retry uploaded an empty file.

This fixes it by using CallNoRetry for the upload, as the other
backends do, so retryable errors are returned wrapped in a RetryError
for the caller to retry the upload with a fresh stream.

It also makes 5xx errors from the upload storage servers retryable.
These come back from the SDK as a different error type to API errors
so were not being retried at all.
2026-09-05 12:14:46 +01:00
Nick Craig-Wood f4cd80a535 pixeldrain: fix corrupted uploads after a retried upload error
When an upload failed with a retryable error the pacer retried the
whole PUT with the same input stream. The stream had already been
consumed by the first attempt so the retry uploaded an empty file.

This fixes it by using CallNoRetry for the upload, as the other
backends do, so retryable errors are returned wrapped in a RetryError
for the caller to retry the upload with a fresh stream.
2026-09-05 12:14:46 +01:00
Nick Craig-Wood aef94cd0e7 http: don't leak configured headers to other hosts or over plaintext on redirect GHSA-486v-q2wf-fp2r CVE-PENDING
The headers set with --http-headers are documented for passing
credentials such as Authorization or Cookie. The backend used the
default net/http redirect policy which copies all but a handful of
well known headers to any redirect target, so a redirect from the
configured server to another host would send those credentials to
that host, and a redirect from https to http would send them in
plaintext.

When headers are configured this installs a CheckRedirect function
which:

- removes the configured headers from every hop once the redirect
  chain has left the originally requested host
- refuses a redirect from https to http with an error
2026-09-04 19:00:22 +01:00
Nick Craig-Wood e1b0c09040 archive: hide any archive entry which escapes the directory being listed GHSA-66hp-wgxq-6f5q
Whether an archive entry name can escape the archive's namespace was
left entirely to each archiver. Enforce it in the archive backend too.

List only passes on direct children of the directory listed and
NewObject only returns the object asked for, so a future archiver
which forgets to validate names cannot expose a traversal to fs/sync
and fs/operations.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 32175374ba archive: fix "directory not found" for archive paths containing "./" or "//" GHSA-66hp-wgxq-6f5q
The path inside the archive was compared against the cleaned entry
names without being cleaned itself, so `archive.zip/sub/./dir` or
`archive.zip/sub//dir` failed to list even though `archive.zip/sub/dir`
worked.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 45391c04ff archive: fix zip entry named "." hiding every other file GHSA-66hp-wgxq-6f5q
A zip containing a file entry whose name refers to the archive's own
root (".", "/" or "") was presented as a single file called "." and
all its other entries disappeared. A file at the root can only be the
archive member the backend was pointed at, so with no root such an
entry is skipped like any other unsafe name.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 7a00e13cd0 archive: reject unsafe entry names when mounting squashfs images GHSA-66hp-wgxq-6f5q
Entry names read from a squashfs directory are not sanitized by
go-diskfs. The squashfs backend joined each leaf name onto its
directory to form the object's remote, so a crafted image could escape
its directory.

Use sanitize.Leaf to skip unsafe entries in List. A "\" is an
ordinary character in a file name on the systems squashfs images are
made on and in an rclone remote path, so it is deliberately not
rejected; making it safe for the destination is the destination
backend's job.

Skipped entries are logged at DEBUG with a single NOTICE count per
listing so a crafted image under a mount cannot flood the log.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood f75008de07 archive: fix zip subdirectory root matching sibling directories GHSA-66hp-wgxq-6f5q
When a zip archive was mounted at a subdirectory root, readZip used a bare
strings.HasPrefix to decide which entries fell inside the root. This
matched on a raw string prefix rather than a path boundary, so mounting
root "foo" also exposed sibling entries such as "foobar/..." with their
names left uncorrected.

Require a path boundary when filtering by root.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 842430d2d4 archive: fix zip slip path traversal in untrusted zip files GHSA-66hp-wgxq-6f5q CVE-PENDING
The zip backend mounts a zip file as a browsable Fs. Go's archive/zip
does not sanitize entry names, and readZip applied path.Clean but did
not reject a cleaned name that still pointed outside the archive. A
crafted zip could make rclone copy/sync attempt writes outside the
intended destination.

Sanitize entry names with sanitize.Path - the same check used by
rclone archive extract - skipping any entry with a ".." path
component, whether separated by "/" or "\". A backslash is otherwise
kept as an ordinary character in the name, as archive extract does. It
is up to the destination backend to make names safe for its storage.

Skipped entries are logged as a single count per archive so a crafted
archive with many escaping entries cannot flood the log.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 6453374403 local: fix panic on Range request past the end of a symlink GHSA-p6m2-r3w9-mpxw CVE-PENDING
With --links/-l, a symlink is served as a .rclonelink object whose
content is the target path. A Range request with a start offset beyond
the target length (e.g. "Range: bytes=99999999999-") reached
openTranslatedLink and sliced the target string at that offset, panicking
with "slice bounds out of range".

Clamp the offset to the target length so an out-of-range start reads
empty, matching how a real file read past EOF behaves.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 215e6dbc65 local: fix btime escaping the root via a planted symlink GHSA-f8g7-2xjc-7mfh CVE-PENDING
The birth-time (btime) write in writeMetadataToFile followed symlinks for
any object that was not a translated link, so under -l/--links a symlink
planted by an untrusted source at the destination path could redirect the
btime write to a target outside the backup destination on OSes where
birth time is settable (Windows).

Use the NOFOLLOW birth-time write whenever translating symlinks, not only
for translated links. It is a no-op on a real file or directory and stops
a planted symlink from being followed out of the destination.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 29bb1e1134 local: fix dir metadata escaping the root through a planted symlink GHSA-f8g7-2xjc-7mfh CVE-PENDING
With -l/--links the local backend faithfully recreates a source ".rclonelink" as
a real symlink at the destination. Directory metadata (chmod/chown/chtimes),
however, was applied with the raw following syscalls
os.Chmod/os.Chown/os.Chtimes rather than through the os.Root sandbox used for
content writes. A Directory is never a translatedLink, so when the destination
path already existed as a symlink planted by an untrusted source, the metadata
was applied through it to a target outside the backup destination.

Route directory metadata through os.Root when translating symlinks, so a planted
symlink can no longer redirect chmod/chown/chtimes out of the destination, while
legitimate in-tree directories are unaffected.
2026-09-04 19:00:22 +01:00
Nick Craig-Wood 891fddc28c s3: avoid buffering Object Lock uploads when the source MD5 is known
Single part uploads with Object Lock parameters need a Content-MD5
header, which the SDK can't compute from a stream, so the whole body
was read into memory with io.ReadAll to hash it - up to
--s3-upload-cutoff per file. prepareUpload already sets Content-MD5
from the source object's hash when it has one, so skip the buffering
entirely in that case and only buffer when the hash is unavailable.

When buffering is needed, read the body into a multipart.NewRW buffer
from the global pool, hashing in transit, so the memory is reused
across uploads and released after the request. The presigned request
path hands the body straight to http.NewRequest, so wrap it in
readers.NoCloser there to stop the transport closing the pooled buffer.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 921c149f7e mailru: buffer speedup hashing in the global memory pool and fix upload retries
With speedup enabled, files up to --mailru-speedup-max-memory are read
into memory so their hash can be tried against the server before
uploading. This used io.ReadAll, which allocates a fresh heap slice per
file and grows it by doubling, so with the default 32 MiB limit and
several transfers this churned a lot of garbage outside rclone's memory
accounting.

Buffer the file with multipart.NewRW instead, hashing it in transit,
so the memory comes from the global pool and is reused.

When the hash isn't known to the server the buffered file is uploaded
from the same buffer. Previously a low level retry of that upload
resent an already drained reader, so the retry always failed. Rewind
seekable bodies at the start of each attempt so retries resend the
whole file. Add a test which drops the connection on the first attempt
and checks the retried body is complete.

The body is sent through lib/rest, which wraps it in readers.NoCloser,
so the transport can't close the pooled buffer early; Update closes it
when it returns.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 91e7942da5 linkbox: buffer the hashed 10 MiB file prefix in the global memory pool
The Linkbox API needs the MD5 of the first 10 MiB of each uploaded
file, so Update reads that prefix into memory before the upload. This
used io.ReadAll, which allocates a fresh heap slice per file and grows
it by doubling, churning well over 10 MiB of garbage per upload.

Read the prefix into a multipart.NewRW buffer instead so the memory
comes from rclone's global pool and is reused across uploads, and hash
it in transit rather than computing the same MD5 twice.

The PUT body goes through lib/rest, which stops the http transport
closing it, so Update owns the buffer and closes it on every exit path.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 885e2478e7 jottacloud: buffer MD5 pre-reads in the global memory pool
When the source has no MD5, Update reads the whole file into memory to
hash it before uploading if it is under --jottacloud-md5-memory-limit.
This used io.ReadAll, which grows a fresh heap slice per file (up to
10 MiB by default, roughly doubled by the growth strategy), so syncs
of many files churned allocations and GC.

Buffer the data with multipart.NewRW instead so the memory comes from
rclone's global pool, is reused across uploads and is released by the
existing cleanup function.

Unknown sized streams previously took the in-memory branch regardless
of the limit, so an rcat of an arbitrarily large stream could read it
all into memory. Spool those to the temporary file instead, as is
already done for files over the limit.

The buffered body is sent through lib/rest, which wraps request bodies
in readers.NoCloser, so the transport can't close the pooled buffer
early.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood f5bdceab49 filelu: reuse multipart upload buffers via the global pool and retry failed parts
The multipart upload allocated a fresh chunk-sized buffer (64 MiB by
default) plus a 1 MiB scratch buffer per large file, copying every byte
twice, and never returned them to rclone's memory pool.

Buffer each part with multipart.NewRW instead so the memory is reused
across uploads and part of rclone's central memory management.

The pooled buffer is seekable, so a part can now be re-sent.
uploadPart previously had no retry at all and any transient error
failed the whole upload. It is now wrapped in the pacer with the
backend's usual shouldRetry rules, seeking to the start before each
attempt. The body is wrapped in readers.NoCloser so the http transport
can't close the pooled buffer between attempts, and Content-Length is
set explicitly since net/http can't infer it from a pool.RW.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood fb8783732d box: reuse multipart upload part buffers via the global pool
Each part of a multipart upload allocated a fresh part-sized buffer
(the size is chosen by Box, typically 8-32 MiB) with up to --transfers
parts in flight, so large uploads churned allocations and GC.

Buffer parts with multipart.NewRW instead so the memory comes from
rclone's global pool, is reused across parts and files, and is part of
rclone's central memory management.

The pool.RW is seekable so the retry closure seeks back to the start
before each attempt instead of rebuilding a bytes.Reader, and the
per-part SHA1 digest is computed by reading the buffer and seeking
back. The body goes through lib/rest which already stops the transport
from closing it; the uploading goroutine owns and closes the buffer.
The whole-file SHA1 used for the commit is unchanged.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 6613d4ec63 opendrive: reuse upload chunk buffers via the global memory pool
Each upload allocated a fresh chunk-sized buffer (10 MiB by default)
regardless of the file size, so bulk transfers of many files churned
allocations and GC.

Buffer chunks with multipart.NewRW instead so chunk memory is reused
across uploads and is part of rclone's central memory management. The
pool.RW is seekable, so the existing rewind on retry carries over.

The body goes through lib/rest which already wraps it so the transport
can't close the pool buffer. The upload loop closes it after every
chunk, on error paths included. A source which ends before the
declared size is now reported as a short read before the chunk is sent
rather than as an incomplete write afterwards.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 738eadf8df dropbox: reuse upload chunk buffers via the global memory pool
Each chunked upload allocated a fresh chunk-sized buffer (48 MiB by
default), so bulk transfers of many files churned allocations and GC.

Buffer chunks with multipart.NewRW instead so chunk memory is reused
across uploads and is part of rclone's central memory management. The
pool.RW is seekable, so the existing IncorrectOffset recovery which
skips already-received bytes on retry carries over unchanged, and the
"chunk received OK" check now compares against the bytes actually
buffered so a short final chunk is recognised too.

The Dropbox SDK wraps the request body in io.NopCloser, so the transport
never closes the pool buffer. The upload loop closes it after every
chunk, on error paths included.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood cdabcc7cc4 putio: reuse upload chunk buffers via the global memory pool
Each upload allocated a fresh 48 MiB chunk buffer, so bulk transfers of
many files churned allocations and GC, and small files paid for the full
buffer.

Buffer chunks with multipart.NewRW instead so chunk memory is reused
across uploads and is part of rclone's central memory management.

The pool.RW implements io.Closer, so the PATCH request body is wrapped in
readers.NoCloser to stop the http transport closing it after a failed
attempt and freeing its pages before the retry. The retry closure now
seeks the chunk back to the start explicitly before resending, a short
read of the source is reported as an error rather than sent as an
under-length chunk, and the request carries an explicit ContentLength.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood da868b06f8 compress: stop buffering the compressibility check output in memory
The compressibility heuristic compressed a 1 MiB sample of each upload
into a bytes.Buffer only to read its length, growing up to ~1 MiB of
garbage per file. Write the sample through a counting io.Discard-style
writer instead so no output buffer is allocated at all.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 03c1c08a27 compress: buffer small uploads in the global memory pool
Every compressed upload allocated a fresh buffer the size of
--compress-ram-cache-limit (20 MiB by default) regardless of how big
the file actually was, so uploading many small files churned large
allocations and the memory sat outside rclone's pool accounting.

Read the head of the stream into a multipart.NewRW instead, which takes
pages from the global pool only for the bytes actually read and returns
them when the upload finishes. The pool.RW is seekable, so a wrapped
backend which needs to retry a small upload can rewind the body, which
the previous bytes.Buffer did not allow. A read error while filling the
cache is returned rather than falling through to the streaming path.

Add a unit test covering the buffered, streamed and spooled paths which
checks the body handed to the wrapped remote, that it can be re-read
for a retry, and that the pool pages are returned.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 204f817100 huaweidrive: reuse resumable-upload chunk buffers via the global pool
Each resumable upload allocated a fresh chunk-sized buffer (8 MiB by
default, up to 64 MiB), so bulk transfers of large files churned
allocations and GC.

Buffer each chunk in a pool.RW from the global page pool instead so
the memory is reused across uploads and bounded by rclone's central
memory management.

The pool.RW is seekable, so the chunk is rewound at the start of each
retry rather than re-wrapped. lib/rest wraps request bodies in
readers.NoCloser so the transport cannot return the pages to the pool
between attempts - the Content-Length is set through rest.Opts because
the wrapped body is not a *bytes.Reader net/http can measure. A source
that runs dry before its declared size is reported as an unexpected EOF
rather than sending the short chunk.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 57eb80f182 huaweidrive: build small uploads in pooled memory instead of a growing buffer
Files below upload_cutoff (up to 20 MiB) were assembled into a bytes.Buffer
which grows by doubling, so each upload allocated roughly twice its size
and threw it away afterwards, churning the GC on bulk transfers. Write the
multipart/related body into a pool.RW from the global page pool instead so
the memory is reused across uploads and bounded by rclone's memory
management.

The pool.RW is seekable, so the same body is rewound at the start of each
retry rather than being re-wrapped. lib/rest already wraps request bodies
in readers.NoCloser so the transport cannot free the pages between
attempts; the Content-Length is passed explicitly because the wrapped body
is no longer a *bytes.Reader net/http can measure.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood bd217a1082 hidrive: buffer small file updates from the global memory pool
Updating a file below --hidrive-upload-cutoff copied the whole file into
an append-grown slice so the request could be retried, costing roughly
twice the file size in transient allocations for every such update.

Buffer the file in a multipart.NewRW from rclone's global page pool
instead and return it to the pool once the request has finished. The
pool.RW is seekable so retries re-send the same buffer. Accounting is
applied as the buffer is sent so bandwidth limits and progress still
track the upload. The request now carries an explicit Content-Length
rather than being sent chunked.

The upload is bounded by the size the source declares - a source that
delivers more bytes than its declared size has the excess ignored, where
previously the stream was sent to EOF.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood e8034e6548 hidrive: reuse chunked upload buffers via the global memory pool
Each chunk of a chunked upload allocated a fresh buffer of
--hidrive-chunk-size bytes (48 MiB by default), with up to
--hidrive-upload-concurrency of them in flight, so large uploads churned
allocations and GC and a short final chunk still cost a whole chunk.

Buffer chunks with multipart.NewRW from rclone's global page pool
instead, sized to the data actually read, and return each buffer to the
pool once its PATCH request has finished. The pool.RW is seekable, so a
chunk which fails with a retryable error is re-sent from the same
buffer. Accounting is applied as a chunk is sent so bandwidth limits
and progress still track the upload. Chunk requests now carry an
explicit Content-Length rather than being sent chunked.

The prefix sent with the creating request in PutUnchecked is buffered
through the same helper.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood e8f421d285 hidrive: buffer the start of each upload from the global memory pool
Every upload allocated a fresh buffer of --hidrive-upload-cutoff bytes
(96 MiB by default) to hold the part of the file sent with the creating
request, however small the file was, so copying many small files churned
large allocations and GC.

Buffer that prefix in a multipart.NewRW from rclone's global page pool
instead, sized to the smaller of the declared file size and the cutoff,
and return it to the pool once the file has been created. Accounting is
applied as the buffer is sent so bandwidth limits and progress still
track the upload. The request now carries an explicit Content-Length
rather than being sent chunked.

The upload is bounded by the size the source declares - a source that
delivers more bytes than its declared size has the excess ignored,
where previously they were read up to the cutoff.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 2a492cc355 hidrive: stop copying every upload chunk a second time
Upload chunks are buffered into a bytes.Reader and then, when transfer
accounting is active (always for a real copy), re-wrapped in accounting
before being handed to cachedReader. cachedReader only recognised a bare
*bytes.Reader, so the accounted chunk fell through to
readers.NewRepeatableReader, which copied the whole chunk again into an
append-grown slice. Every chunk (and the upload-cutoff prefix of every
file) therefore cost roughly twice its size in memory.

Look through the accounting wrapper when deciding whether the reader is
already a seekable buffer and, if so, seek the buffer underneath while
still reading through the accounting, so retries rewind without a copy.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 689081b410 onedrive: reuse upload chunk buffers via the global memory pool
Each chunk of a resumable upload was buffered in a fresh RepeatableReader
which grew by appending, so bulk transfers churned up to a chunk size (10
MiB by default) of heap per chunk and GC pressure.

Buffer chunks instead with multipart.NewRW from the global page pool
instead so memory is reused across uploads and bounded by rclone's
central memory management.

A source which delivers fewer bytes than its declared size now fails
before the chunk is sent with an unexpected EOF error rather than being
rejected by the transport.

Note that accounting now happens as the chunk is read into the buffer
rather than as it is sent, as in the drive backend.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 2ed0c688f6 pikpak: use pooled memory for the gcid hash buffer and spool unknown-size uploads to disk
When the source can't be re-opened, the whole input is read once to
compute its gcid before upload and held back for the upload proper. For
inputs at or below --pikpak-hash-memory-limit this used a bytes.Buffer,
a fresh heap allocation of up to the limit (and more while growing) per
file.

Hold the data in a buffer from the global memory pool instead so the
pages are reused and released on cleanup.

Inputs of unknown size were also always held in memory regardless of
their length, as only sizes above the limit chose the temp file.

Spool unknown sizes to the temp file so a large stream can't exhaust
memory.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood b6b3a0a485 pikpak: share upload chunk buffers with the global memory pool
The multipart uploader kept its own private buffer pool, a copy of the
one in lib/pool with identical settings, so its chunk memory was never
shared with the rest of rclone. Pages cached here were invisible to
other backends and vice versa, costing up to 64 MiB of extra idle cache.

Allocate chunks with multipart.NewRW, the global pool used by the
other backends instead.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 337f762c79 shade: stop copying each upload chunk into a second heap buffer
WriteChunk copied the whole chunk, which lib/multipart already hands over
in a buffer from the global memory pool, into a bytes.Buffer so that
retries could re-send it. That doubled the per-part memory and made a
fresh chunk-sized heap allocation (64 MiB by default) for every part,
times the upload concurrency.

The chunk reader is seekable, so find its size with Seek and rewind it
inside the pacer closure instead, sending the pooled buffer directly.

Also fix the error for a part that fails to upload, which formatted the
buffer instead of the part number.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 2f0228029e quatrix: fix chunk upload retries and fix memory leak
Each upload chunk is buffered in a pool.RW from the global memory pool
but was never closed, so its pages were never returned to the pool.

Close the buffer after each chunk is uploaded and on the read error
path.

A chunk that failed with a retryable error was also retried without
rewinding the buffer, so the retry sent an empty body with the original
Content-Length and Content-Range and failed.

Seek the chunk back to the start inside the pacer closure so each
attempt re-sends it in full.

The FsPutRetry integration test covers the retry of a failed upload
request and checks the buffers are returned to the pool.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 76016d9947 huaweidrive: run the chunked upload integration tests
Implement SetUploadChunkSizer and SetUploadCutoffer in the tests so
fstests can exercise the resumable upload path with small chunk sizes.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 76b5ee8259 filelu: run the chunked upload integration tests
Implement SetUploadChunkSizer and SetUploadCutoffer in the tests so
fstests can exercise the multipart upload path with small chunk sizes.
2026-09-01 14:21:52 +01:00
Nick Craig-Wood 5fef6a84a8 opendrive: run the chunked upload integration tests
Implement SetUploadChunkSizer in the tests so fstests can exercise the
chunked upload path with small chunk sizes.
2026-09-01 14:21:52 +01:00
SillyZirandNick Craig-Wood 03fe2ef794 onedrive: fall back to manual drive ID entry when drive listing fails
When both /me/drives and /me/drive fail during config (for example an
account-level 403 serviceReadOnly "Database Is Read Only"), send the
config state machine to the existing manual drive ID entry state
instead of dead-ending at choose_type with the raw error. The drive
itself remains usable when only the enumeration API is blocked.

Fixes #9794
2026-09-01 10:52:49 +01:00
SillyZirandNick Craig-Wood 393544b116 drive: reuse resumable-upload chunk buffers via multipart.NewRW
Each resumable upload allocated a fresh chunk-sized buffer (8 MiB by
default), so bulk transfers of many files churned allocations and GC.
Buffer chunks with multipart.NewRW instead — the global page pool used
by the other backends — so chunk memory is reused across uploads and
bounded by rclone's central memory management. The pool.RW is seekable,
which also keeps chunk reads repeatable for retries.

The pool.RW implements io.Closer, so http.NewRequestWithContext upgraded
it to the request body and the transport closed it after each attempt,
returning its pages to the global pool — a chunk retried after a 5xx
then read a freed buffer and panicked in pool.(*RW).readPage. Wrap the
request body in readers.NoCloser so the transport can't take ownership
and the upload loop remains solely responsible for the buffer's
lifetime. Add a regression test that fails a chunk with a 500 and then
accepts the retry; it reproduces the panic without the fix.

Fixes #9684
2026-08-30 13:07:41 +01:00
4722b94d1a sftp: implement multi-thread uploads - fixes #8185
The sftp backend could be used as the source of a multi-thread copy -
each chunk of a download is read on its own connection - but not as the
destination, because it did not implement OpenWriterAt. Uploading a
single large file was therefore limited to one connection while
downloading the same file was not, capping upload throughput well below
the link speed on high latency connections.

This implements OpenWriterAt for the sftp backend using lib/filepool. A
small pool of open write handles, each backed by its own connection,
lets the core write the chunks of a large file concurrently over several
connections. The file is created and truncated once up front so every
chunk offset is valid before the concurrent writes start.

Multi-thread uploads are off by default and turned on with the new
--sftp-multithread-upload flag, since many sftp servers only accept
sequential writes and would fail large uploads otherwise. Even when
enabled they fall back to a single connection when
--sftp-disable-concurrent-writes is set (a server that can't take
out-of-order packets on one handle won't take several handles either) or
when --sftp-connections caps the pool (the per-file fan-out would
otherwise deadlock waiting on it).

The OpenSSH test server (TestSFTPOpenssh) enables
--sftp-multithread-upload so the feature is exercised in CI against a
real server.

Tested against OpenSSH: a single large upload over a high latency link
went from ~12 MB/s to ~90 MB/s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Splainte <r.wycke@hotmail.fr>
2026-08-30 13:06:06 +01:00
613b335962 filepool: add generic file handle pool in lib/filepool
Factor the connection-backed write handle pool out of the smb backend
into a generic lib/filepool.Pool[T] with its own tests, so it can be
reused by other backends that implement fs.OpenWriterAter over a
connection pool.

The smb backend keeps its behaviour, opening and releasing handles
through small closures passed to the pool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Splainte <r.wycke@hotmail.fr>
2026-08-30 13:06:06 +01:00
Mikel Olasagasti UrangaandNick Craig-Wood 6a617a379b protondrive: fix Go 1.27 vet warning in retry test
Pass the wrapped API error through the error-typed test helper so the
%w operand satisfies Go 1.27's printf analyzer without changing test
behavior.
2026-08-29 13:13:34 +01:00