Skip to content

Commit 5151732

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
wolfsftp: keep the local file when resuming a get
- The Windows local open in wolfSSH_SFTP_Get() passes OPEN_EXISTING when the write offset is nonzero and CREATE_ALWAYS otherwise, and drops FILE_APPEND_DATA from the desired access. A new DWORD creationDisp replaces the block-scoped desiredAccess. - That open reports INVALID_HANDLE_VALUE as WS_BAD_FILE_E and moves to STATE_GET_CLEANUP; the OVERLAPPED offset is set from gOfst on every open rather than only when resuming. - STATE_GET_LOOKUP_OFFSET clears a saved offset when the remote size STATE_GET_LSTAT stored in state->attrib is no larger than it, and again when the local destination does not hold exactly that many bytes. The destination stat overwrites state->attrib. - tests/api.c adds test_wolfSSH_SFTP_GetResume() and its sftpGetToCompletion() helper, six cases over the resume paths, built where the hosted file wrappers are available. - test_wolfSSH_SFTP_PutResume() and test_wolfSSH_SFTP_GetResume() drop the WOLFSSH_ZEPHYR k_sleep() block their bodies exclude. - .gitignore covers every wolfssh_*.tmp the api tests leave behind on an aborted run, replacing the known_hosts-only entry. Issue: F-12547
1 parent c71202f commit 5151732

3 files changed

Lines changed: 206 additions & 19 deletions

File tree

.gitignore

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,10 @@ keys/*-ossh-*cert.pub
8585
random-test.txt
8686
random-test-result.txt
8787
test.dat
88-
# Scratch file the api tests write in the working directory. Removed on a
89-
# clean run, left behind when one aborts.
88+
# Scratch files the api and regression tests write in the working
89+
# directory. Removed on a clean run, left behind when one aborts.
9090
ossh-cert-line.tmp
91-
# Same for the regression tests' known_hosts fixtures, named by pid.
92-
wolfssh_kh_*.tmp
91+
wolfssh_*.tmp
9392
regress_known_hosts*.tmp
9493

9594
# test output

src/wolfsftp.c

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9787,6 +9787,9 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from,
97879787
WS_SFTP_GET_STATE* state = NULL;
97889788
int sz;
97899789
int ret = WS_SUCCESS;
9790+
#ifdef USE_WINDOWS_API
9791+
DWORD creationDisp;
9792+
#endif
97909793

97919794
WLOG(WS_LOG_SFTP, "Entering wolfSSH_SFTP_Get()");
97929795
if (ssh == NULL || from == NULL || to == NULL)
@@ -9865,6 +9868,36 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from,
98659868
/* if resuming then check for saved offset */
98669869
if (resume) {
98679870
wolfSSH_SFTP_GetOfst(ssh, from, to, state->gOfst);
9871+
9872+
/* A saved offset is only usable while the remote file
9873+
* still holds bytes past it. */
9874+
if ((state->gOfst[0] > 0 || state->gOfst[1] > 0)
9875+
&& (state->attrib.flags & WOLFSSH_FILEATRB_SIZE)
9876+
&& (state->attrib.sz[1] < state->gOfst[1]
9877+
|| (state->attrib.sz[1] == state->gOfst[1]
9878+
&& state->attrib.sz[0]
9879+
<= state->gOfst[0]))) {
9880+
WLOG(WS_LOG_SFTP, "Remote file too short");
9881+
state->gOfst[0] = 0;
9882+
state->gOfst[1] = 0;
9883+
}
9884+
9885+
/* a saved offset is only usable if the local file still
9886+
* holds exactly that many bytes */
9887+
if (state->gOfst[0] > 0 || state->gOfst[1] > 0) {
9888+
WMEMSET(&state->attrib, 0, sizeof(state->attrib));
9889+
if (SFTP_GetAttributes(ssh->fs, to, &state->attrib, 1,
9890+
ssh->ctx->heap) != WS_SUCCESS
9891+
|| (state->attrib.flags
9892+
& WOLFSSH_FILEATRB_SIZE) == 0
9893+
|| state->attrib.sz[0] != state->gOfst[0]
9894+
|| state->attrib.sz[1] != state->gOfst[1]) {
9895+
WLOG(WS_LOG_SFTP,
9896+
"Size does not match, starting over");
9897+
state->gOfst[0] = 0;
9898+
state->gOfst[1] = 0;
9899+
}
9900+
}
98689901
}
98699902
state->state = STATE_GET_OPEN_LOCAL;
98709903
FALL_THROUGH;
@@ -9877,20 +9910,23 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from,
98779910
else
98789911
ret = WFOPEN(ssh->fs, &state->fl, to, WOLFSSH_O_WRONLY);
98799912
#elif defined(USE_WINDOWS_API)
9880-
{
9881-
DWORD desiredAccess = GENERIC_WRITE;
9882-
if (state->gOfst[0] > 0 || state->gOfst[1] > 0)
9883-
desiredAccess |= FILE_APPEND_DATA;
9884-
state->fileHandle = WS_CreateFileA(to, desiredAccess,
9885-
(FILE_SHARE_DELETE | FILE_SHARE_READ |
9886-
FILE_SHARE_WRITE), CREATE_ALWAYS,
9887-
FILE_ATTRIBUTE_NORMAL, ssh->ctx->heap);
9888-
}
9889-
if (resume) {
9890-
WMEMSET(&state->offset, 0, sizeof(OVERLAPPED));
9891-
state->offset.OffsetHigh = state->gOfst[1];
9892-
state->offset.Offset = state->gOfst[0];
9913+
creationDisp = CREATE_ALWAYS;
9914+
if (state->gOfst[0] > 0 || state->gOfst[1] > 0)
9915+
creationDisp = OPEN_EXISTING;
9916+
state->fileHandle = WS_CreateFileA(to, GENERIC_WRITE,
9917+
(FILE_SHARE_DELETE | FILE_SHARE_READ |
9918+
FILE_SHARE_WRITE), creationDisp,
9919+
FILE_ATTRIBUTE_NORMAL, ssh->ctx->heap);
9920+
if (state->fileHandle == INVALID_HANDLE_VALUE) {
9921+
WLOG(WS_LOG_SFTP, "Unable to open output file");
9922+
ssh->error = WS_BAD_FILE_E;
9923+
ret = WS_FATAL_ERROR;
9924+
state->state = STATE_GET_CLEANUP;
9925+
continue;
98939926
}
9927+
WMEMSET(&state->offset, 0, sizeof(OVERLAPPED));
9928+
state->offset.OffsetHigh = state->gOfst[1];
9929+
state->offset.Offset = state->gOfst[0];
98949930
#else
98959931
if (state->gOfst[0] > 0 || state->gOfst[1] > 0)
98969932
ret = WFOPEN(ssh->fs, &state->fl, to, "ab");

tests/api.c

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5901,6 +5901,24 @@ static int sftpPutToCompletion(WOLFSSH* ssh, char* from, char* to, byte resume)
59015901

59025902
return ret;
59035903
}
5904+
5905+
5906+
/* Drives one wolfSSH_SFTP_Get() to a terminal result. */
5907+
static int sftpGetToCompletion(WOLFSSH* ssh, char* from, char* to, byte resume)
5908+
{
5909+
int ret = WS_FATAL_ERROR;
5910+
int tries;
5911+
5912+
for (tries = 0; tries < SFTP_MAX_RETRY_TRIES; tries++) {
5913+
ret = wolfSSH_SFTP_Get(ssh, from, to, resume, NULL);
5914+
if (ret == WS_SUCCESS ||
5915+
!sftp_error_is_notice(wolfSSH_get_error(ssh))) {
5916+
break;
5917+
}
5918+
}
5919+
5920+
return ret;
5921+
}
59045922
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM && !WOLFSSH_ZEPHYR */
59055923

59065924

@@ -6031,9 +6049,141 @@ static void test_wolfSSH_SFTP_PutResume(void)
60316049

60326050
wolfSSH_free(ssh);
60336051
wolfSSH_CTX_free(ctx);
6034-
#ifdef WOLFSSH_ZEPHYR
6035-
k_sleep(Z_TIMEOUT_TICKS(100));
6052+
ThreadJoin(serThread);
6053+
FreeTcpReady(&ready);
6054+
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM && !WOLFSSH_ZEPHYR */
6055+
}
6056+
6057+
6058+
/* A resumed get must keep the bytes already at the local destination, and
6059+
* must only resume onto one whose size matches the saved offset. The
6060+
* echoserver shares this process, so the "remote" file is local. */
6061+
static void test_wolfSSH_SFTP_GetResume(void)
6062+
{
6063+
/* staging both files needs the hosted fopen()/getcwd() wrappers */
6064+
#if !defined(NO_FILESYSTEM) && !defined(WOLFSSH_USER_FILESYSTEM) && \
6065+
!defined(WOLFSSH_ZEPHYR)
6066+
func_args ser;
6067+
tcp_ready ready;
6068+
int argsCount;
6069+
WS_SOCKET_T clientFd;
6070+
6071+
const char* args[10];
6072+
WOLFSSH_CTX* ctx = NULL;
6073+
WOLFSSH* ssh = NULL;
6074+
6075+
THREAD_TYPE serThread;
6076+
6077+
byte src[SFTP_PUT_RESUME_SZ];
6078+
byte stale[SFTP_PUT_RESUME_SZ + SFTP_PUT_RESUME_OFST];
6079+
byte expect[SFTP_PUT_RESUME_SZ];
6080+
word32 ofst[2];
6081+
char srcName[] = "wolfssh_12547_src.tmp";
6082+
char dstName[] = "wolfssh_12547_dst.tmp";
6083+
6084+
WMEMSET(&ser, 0, sizeof(func_args));
6085+
6086+
argsCount = 0;
6087+
args[argsCount++] = ".";
6088+
args[argsCount++] = "-1";
6089+
args[argsCount++] = "-p";
6090+
args[argsCount++] = "0";
6091+
ser.argv = (char**)args;
6092+
ser.argc = argsCount;
6093+
ser.signal = &ready;
6094+
InitTcpReady(ser.signal);
6095+
ThreadStart(echoserver_test, (void*)&ser, &serThread);
6096+
WaitTcpReady(&ready);
6097+
6098+
sftp_client_connect(&ctx, &ssh, ready.port);
6099+
AssertNotNull(ctx);
6100+
AssertNotNull(ssh);
6101+
6102+
sftpPutFillPattern(src, (word32)sizeof(src));
6103+
WMEMSET(stale, 0xFF, sizeof(stale));
6104+
AssertIntEQ(sftpPutWriteFile(srcName, src, (word32)sizeof(src)), 0);
6105+
6106+
/* the saved offset matches the destination, so the download picks up
6107+
* where it left off. Staging a prefix unlike the source, and expecting it
6108+
* back untouched, is what separates a resume from a full re-download. */
6109+
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST), 0);
6110+
WMEMCPY(expect, stale, SFTP_PUT_RESUME_OFST);
6111+
WMEMCPY(expect + SFTP_PUT_RESUME_OFST, src + SFTP_PUT_RESUME_OFST,
6112+
sizeof(expect) - SFTP_PUT_RESUME_OFST);
6113+
ofst[0] = SFTP_PUT_RESUME_OFST;
6114+
ofst[1] = 0;
6115+
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
6116+
WS_SUCCESS);
6117+
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
6118+
AssertIntEQ(sftpPutFileMatches(dstName, expect, (word32)sizeof(expect)),
6119+
0);
6120+
6121+
/* the destination is gone, so the whole file is fetched again */
6122+
WREMOVE(NULL, dstName);
6123+
ofst[0] = SFTP_PUT_RESUME_OFST;
6124+
ofst[1] = 0;
6125+
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
6126+
WS_SUCCESS);
6127+
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
6128+
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
6129+
6130+
/* the destination is shorter than the saved offset, so the whole file is
6131+
* fetched again */
6132+
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST / 2), 0);
6133+
ofst[0] = SFTP_PUT_RESUME_OFST;
6134+
ofst[1] = 0;
6135+
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
6136+
WS_SUCCESS);
6137+
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
6138+
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
6139+
6140+
/* the destination is longer than the saved offset, so the whole file is
6141+
* fetched again */
6142+
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST * 2), 0);
6143+
ofst[0] = SFTP_PUT_RESUME_OFST;
6144+
ofst[1] = 0;
6145+
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
6146+
WS_SUCCESS);
6147+
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
6148+
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
6149+
6150+
/* a plain get still truncates a longer destination */
6151+
AssertIntEQ(sftpPutWriteFile(dstName, stale, (word32)sizeof(stale)), 0);
6152+
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 0), WS_SUCCESS);
6153+
AssertIntEQ(sftpPutFileMatches(dstName, src, (word32)sizeof(src)), 0);
6154+
6155+
/* the source has nothing left past the saved offset, so the whole file
6156+
* is fetched again. This case shortens the source, so it runs last. */
6157+
AssertIntEQ(sftpPutWriteFile(dstName, stale, SFTP_PUT_RESUME_OFST), 0);
6158+
AssertIntEQ(sftpPutWriteFile(srcName, src, SFTP_PUT_RESUME_OFST / 2), 0);
6159+
ofst[0] = SFTP_PUT_RESUME_OFST;
6160+
ofst[1] = 0;
6161+
AssertIntEQ(wolfSSH_SFTP_SaveOfst(ssh, srcName, dstName, ofst),
6162+
WS_SUCCESS);
6163+
AssertIntEQ(sftpGetToCompletion(ssh, srcName, dstName, 1), WS_SUCCESS);
6164+
AssertIntEQ(sftpPutFileMatches(dstName, src, SFTP_PUT_RESUME_OFST / 2), 0);
6165+
6166+
WREMOVE(NULL, dstName);
6167+
WREMOVE(NULL, srcName);
6168+
6169+
/* take care of re-keying state before shutdown call */
6170+
while (wolfSSH_get_error(ssh) == WS_REKEYING) {
6171+
wolfSSH_worker(ssh, NULL);
6172+
}
6173+
6174+
argsCount = AbsorbBenignReset(ssh, wolfSSH_shutdown(ssh));
6175+
#if DEFAULT_HIGHWATER_MARK < 8000
6176+
if (argsCount == WS_REKEYING) {
6177+
argsCount = WS_SUCCESS;
6178+
}
60366179
#endif
6180+
AssertIntEQ(argsCount, WS_SUCCESS);
6181+
6182+
clientFd = wolfSSH_get_fd(ssh);
6183+
WCLOSESOCKET(clientFd);
6184+
6185+
wolfSSH_free(ssh);
6186+
wolfSSH_CTX_free(ctx);
60376187
ThreadJoin(serThread);
60386188
FreeTcpReady(&ready);
60396189
#endif /* !NO_FILESYSTEM && !WOLFSSH_USER_FILESYSTEM && !WOLFSSH_ZEPHYR */
@@ -6051,6 +6201,7 @@ static void test_wolfSSH_SFTP_SetConfinePath(void) { ; }
60516201
static void test_wolfSSH_SFTP_SetDefaultPath(void) { ; }
60526202
static void test_wolfSSH_SFTP_SaveOfst(void) { ; }
60536203
static void test_wolfSSH_SFTP_PutResume(void) { ; }
6204+
static void test_wolfSSH_SFTP_GetResume(void) { ; }
60546205
#endif /* WOLFSSH_SFTP && !NO_WOLFSSH_CLIENT && !SINGLE_THREADED */
60556206

60566207

@@ -8003,6 +8154,7 @@ int wolfSSH_ApiTest(int argc, char** argv)
80038154
test_wolfSSH_SFTP_SetDefaultPath();
80048155
test_wolfSSH_SFTP_SaveOfst();
80058156
test_wolfSSH_SFTP_PutResume();
8157+
test_wolfSSH_SFTP_GetResume();
80068158

80078159
/* Either SCP or SFTP */
80088160
test_wolfSSH_RealPath();

0 commit comments

Comments
 (0)