libssh2/sftp: fix resume corruption by avoiding O_APPEND with rresume

Opening the remote file with O_APPEND while attempting to resume causes
all writes to be forced to EOF on servers/implementations where O_APPEND
semantics override a prior seek(). As a result, sftp_seek64() is ignored
and the resumed data is appended, duplicating/corrupting the file.

Fix by:
- Using O_WRONLY (without O_APPEND) when resume_from > 0.
- Skipping the seek entirely if remote_append mode is requested.

Closes #18952
This commit is contained in:
Joshua Rogers 2025-10-09 06:06:40 +08:00 committed by Daniel Stenberg
parent 391e3fbeec
commit dae19dd94a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -1027,15 +1027,21 @@ sftp_upload_init(struct Curl_easy *data,
}
}
if(data->set.remote_append)
/* Try to open for append, but create if nonexisting */
flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND;
else if(data->state.resume_from > 0)
/* If we have restart position then open for append */
flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND;
else
if(data->set.remote_append) {
/* True append mode: create if nonexisting */
flags = LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_APPEND;
}
else if(data->state.resume_from > 0) {
/*
* Resume MUST NOT use APPEND; some servers force writes to EOF when
* APPEND is set, ignoring a prior seek().
*/
flags = LIBSSH2_FXF_WRITE;
}
else {
/* Clear file before writing (normal behavior) */
flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC;
flags = LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC;
}
sshc->sftp_handle =
libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
@ -1093,8 +1099,8 @@ sftp_upload_init(struct Curl_easy *data,
}
/* If we have a restart point then we need to seek to the correct
position. */
if(data->state.resume_from > 0) {
Skip if in explicit remote append mode. */
if(data->state.resume_from > 0 && !data->set.remote_append) {
int seekerr = CURL_SEEKFUNC_OK;
/* Let's read off the proper amount of bytes from the input. */
if(data->set.seek_func) {