wolfsftp: Improve POSIX and Windows write/append handling

Ensures EOF appending on all POSIX systems and fails short writes on Windows.
Consolidates the Windows SFTP flag matrix testing with shared helpers.
pull/1225/head
Emma Stensland 2026-09-02 11:56:56 -06:00 committed by John Safranek
parent 5a1ff71acd
commit ea530fe05d
3 changed files with 252 additions and 353 deletions

View File

@ -2347,21 +2347,27 @@ static DWORD SFTP_WinCreationDisp(word32 reason)
DWORD disp;
if (reason & WOLFSSH_FXF_CREAT) {
if (reason & WOLFSSH_FXF_EXCL)
if (reason & WOLFSSH_FXF_EXCL) {
disp = CREATE_NEW;
else if (reason & WOLFSSH_FXF_TRUNC)
}
else if (reason & WOLFSSH_FXF_TRUNC) {
disp = CREATE_ALWAYS;
else
}
else {
disp = OPEN_ALWAYS;
}
}
else {
/* TRUNCATE_EXISTING requires GENERIC_WRITE in dwDesiredAccess or
* CreateFile() fails with ERROR_INVALID_PARAMETER; without WRITE
* there is no way to truncate, so fall back to OPEN_EXISTING. */
if ((reason & WOLFSSH_FXF_TRUNC) && (reason & WOLFSSH_FXF_WRITE))
* CreateFile() fails with ERROR_INVALID_PARAMETER. TRUNC without
* WRITE is deliberately ignored and the open succeeds untruncated,
* as O_RDONLY|O_TRUNC does on most POSIX systems. */
if ((reason & WOLFSSH_FXF_TRUNC) && (reason & WOLFSSH_FXF_WRITE)) {
disp = TRUNCATE_EXISTING;
else
}
else {
disp = OPEN_EXISTING;
}
}
return disp;
@ -2692,12 +2698,15 @@ cleanup:
}
#endif
if (reason & WOLFSSH_FXF_READ)
if (reason & WOLFSSH_FXF_READ) {
desiredAccess |= GENERIC_READ;
if (reason & WOLFSSH_FXF_WRITE)
}
if (reason & WOLFSSH_FXF_WRITE) {
desiredAccess |= GENERIC_WRITE;
if (reason & WOLFSSH_FXF_APPEND)
}
if (reason & WOLFSSH_FXF_APPEND) {
desiredAccess |= FILE_APPEND_DATA;
}
creationDisp = SFTP_WinCreationDisp(reason);
@ -4265,6 +4274,7 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz)
WFD fd = 0;
int ret = WS_SUCCESS;
int rc;
int isAppend = 0;
word32 idx = 0;
word32 ofst[2] = {0,0};
@ -4279,6 +4289,7 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz)
if (ssh == NULL) {
return WS_BAD_ARGUMENT;
}
WOLFSSH_UNUSED(isAppend); /* only read on ports that define WWRITE */
WLOG(WS_LOG_SFTP, "Receiving WOLFSSH_FTP_WRITE");
@ -4313,6 +4324,7 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz)
}
else {
fd = fileEntry->fd;
isAppend = fileEntry->isAppend;
}
}
}
@ -4335,8 +4347,20 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz)
/* Retry while WPWRITE makes forward progress; bail on error
* or zero return to avoid spinning on a stuck backend. */
while (written < strSz) {
ret = WPWRITE(ssh->fs, fd, (byte*)str + written,
strSz - written, ofst);
#ifdef WWRITE
if (isAppend) {
/* FXF_APPEND: the offset is ignored and the O_APPEND
* fd puts the write at EOF. pwrite() would honor the
* offset on every POSIX system but Linux. */
ret = WWRITE(ssh->fs, fd, (byte*)str + written,
strSz - written);
}
else
#endif
{
ret = WPWRITE(ssh->fs, fd, (byte*)str + written,
strSz - written, ofst);
}
if (ret <= 0) {
break;
}
@ -4459,8 +4483,8 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz)
* non-atomic read-modify-write: the file is shared FILE_SHARE_WRITE,
* so concurrent appenders would resolve the same offset and overwrite
* each other. Setting both OVERLAPPED offset fields to 0xFFFFFFFF
* tells WriteFile() to append atomically at end of file, matching the
* POSIX O_APPEND path. */
* tells WriteFile() to append atomically at end of file, matching
* the POSIX O_APPEND path. */
if (isAppend) {
offset.Offset = 0xFFFFFFFF;
offset.OffsetHigh = 0xFFFFFFFF;
@ -4473,7 +4497,9 @@ int wolfSSH_SFTP_RecvWrite(WOLFSSH* ssh, int reqId, byte* data, word32 maxSz)
return WS_BUFFER_E;
}
if (WriteFile(fd, str, strSz, &bytesWritten, &offset) == 0) {
/* A short write is a failure too, as on the POSIX side. */
if (WriteFile(fd, str, strSz, &bytesWritten, &offset) == 0 ||
bytesWritten != strSz) {
WLOG(WS_LOG_SFTP, "Error writing to file");
res = err;
type = WOLFSSH_FTP_FAILURE;

View File

@ -8128,6 +8128,146 @@ static void AssertSftpStatusReply(WOLFSSH* ssh, int reqId, word32 code)
AssertIntEQ((int)SftpGetU32(reply + LENGTH_SZ + MSG_ID_SZ), reqId);
AssertIntEQ((int)SftpGetU32(reply + WOLFSSH_SFTP_HEADER), (int)code);
}
/* The FXP_HANDLE reply carries the handle after the header and its length. */
#define SFTP_TEST_HANDLE_OFF (WOLFSSH_SFTP_HEADER + UINT32_SZ)
#ifdef USE_WINDOWS_API
#define SFTP_TEST_PID() ((unsigned long)GetCurrentProcessId())
#else
#define SFTP_TEST_PID() ((unsigned long)getpid())
#endif
/* FXP_OPEN path with flags and empty attributes. On success the handle from
* the FXP_HANDLE reply is copied to handle. */
static int SftpOpenPath(WOLFSSH* ssh, int reqId, const char* path,
word32 flags, byte* handle)
{
byte pkt[WOLFSSH_MAX_FILENAME + 3 * UINT32_SZ];
word32 idx = 0;
word32 pathSz = (word32)WSTRLEN(path);
int ret;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(flags, pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
ret = wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx);
if (ret == WS_SUCCESS) {
const byte* reply;
word32 replySz;
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= SFTP_TEST_HANDLE_OFF + WOLFSSH_HANDLE_ID_SZ);
/* no request id check: RecvOpen stamps the reply with ssh->reqId,
* which the dispatch loop sets and a direct call leaves at 0 */
AssertIntEQ(reply[LENGTH_SZ], WOLFSSH_FTP_HANDLE);
WMEMCPY(handle, reply + SFTP_TEST_HANDLE_OFF, WOLFSSH_HANDLE_ID_SZ);
}
return ret;
}
/* FXP_WRITE dataSz bytes at the split 64-bit offset through handle. */
static int SftpWriteHandle(WOLFSSH* ssh, int reqId, const byte* handle,
word32 ofstHi, word32 ofstLo, const void* data, word32 dataSz)
{
byte pkt[64 + WOLFSSH_HANDLE_ID_SZ + 4 * UINT32_SZ];
word32 idx = 0;
AssertTrue(dataSz <= 64);
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(ofstHi, pkt + idx); idx += UINT32_SZ;
SftpPutU32(ofstLo, pkt + idx); idx += UINT32_SZ;
SftpPutU32(dataSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, data, dataSz); idx += dataSz;
return wolfSSH_SFTP_RecvWrite(ssh, reqId, pkt, idx);
}
/* FXP_CLOSE handle. */
static int SftpCloseHandle(WOLFSSH* ssh, int reqId, const byte* handle)
{
byte pkt[WOLFSSH_HANDLE_ID_SZ + UINT32_SZ];
word32 idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
return wolfSSH_SFTP_RecvClose(ssh, reqId, pkt, idx);
}
/* Assert the file at path holds exactly expSz bytes matching exp. */
static void AssertSftpFileContents(const char* path, const void* exp,
word32 expSz)
{
WFILE* file;
char readBuf[64];
word32 readSz;
AssertTrue(expSz < sizeof(readBuf));
AssertIntEQ(WFOPEN(NULL, &file, path, "rb"), 0);
AssertTrue(file != WBADFILE);
readSz = (word32)WFREAD(NULL, readBuf, 1, sizeof(readBuf), file);
WFCLOSE(NULL, file);
AssertIntEQ((int)readSz, (int)expSz);
AssertIntEQ(WMEMCMP(readBuf, exp, expSz), 0);
}
/* FXF_APPEND puts every write at EOF whatever offset the client sends
* (draft-ietf-secsh-filexfer-02 6.3). On Windows that is the OVERLAPPED
* 0xFFFFFFFF encoding; on POSIX it needs write() rather than pwrite(),
* which ignores O_APPEND everywhere but Linux. */
static void TestSftpAppendWritesAtEof(void)
{
WOLFSSH_CTX* ctx;
WOLFSSH* ssh;
int rid = 600;
WSTAT_T st;
byte handle[WOLFSSH_HANDLE_ID_SZ];
char cwd[WOLFSSH_MAX_FILENAME];
char path[64];
const char content[] = "0123456789";
const char content2[] = "abcde";
const char expect[] = "0123456789abcde";
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctx);
ssh = wolfSSH_new(ctx);
AssertNotNull(ssh);
AssertIntEQ(wolfSSH_SFTP_TestRecvStateInit(ssh), WS_SUCCESS);
WSNPRINTF(path, sizeof(path), "wolfssh_append_%lu.tmp", SFTP_TEST_PID());
WMEMSET(cwd, 0, sizeof(cwd));
AssertNotNull(WGETCWD(ssh->fs, cwd, sizeof(cwd) - 1));
AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, cwd), WS_SUCCESS);
(void)WREMOVE(ssh->fs, path);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_APPEND | WOLFSSH_FXF_CREAT,
handle), WS_SUCCESS);
/* first write at offset 0 lands at EOF 0 */
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0,
content, (word32)(sizeof(content) - 1)), WS_SUCCESS);
/* stale offset 0 again: must append, not overwrite the start */
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0,
content2, (word32)(sizeof(content2) - 1)), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, (int)(sizeof(expect) - 1));
AssertSftpFileContents(path, expect, (word32)(sizeof(expect) - 1));
(void)WREMOVE(ssh->fs, path);
wolfSSH_SFTP_TestRecvStateFree(ssh);
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
}
#endif /* !NO_WOLFSSH_SERVER && !NO_FILESYSTEM */
#if !defined(NO_WOLFSSH_SERVER) && !defined(USE_WINDOWS_API) && \
@ -9217,21 +9357,14 @@ static void TestSftpWindowsOpenFlagMatrix(void)
WOLFSSH* ssh;
int rid = 500;
int reqId;
word32 idx;
word32 replySz;
const byte* reply;
const word32 hOff = WOLFSSH_SFTP_HEADER + UINT32_SZ; /* handle in reply */
WSTAT_T st;
byte handle[WOLFSSH_HANDLE_ID_SZ];
byte pkt[256];
char cwd[WOLFSSH_MAX_FILENAME];
char path[64];
word32 pathSz;
const char content[] = "0123456789";
const char content2[] = "abcde";
WFILE* file;
char readBuf[32];
word32 readSz;
const word32 contentSz = (word32)(sizeof(content) - 1);
const word32 content2Sz = (word32)(sizeof(content2) - 1);
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
AssertNotNull(ctx);
@ -9241,8 +9374,7 @@ static void TestSftpWindowsOpenFlagMatrix(void)
/* unique per-process fixture name so parallel runs don't collide */
WSNPRINTF(path, sizeof(path), "wolfssh_winflags_%lu.tmp",
(unsigned long)GetCurrentProcessId());
pathSz = (word32)WSTRLEN(path);
SFTP_TEST_PID());
WMEMSET(cwd, 0, sizeof(cwd));
AssertNotNull(WGETCWD(ssh->fs, cwd, sizeof(cwd) - 1));
@ -9251,390 +9383,123 @@ static void TestSftpWindowsOpenFlagMatrix(void)
(void)WREMOVE(ssh->fs, path);
/* WRITE only, no CREAT: must fail against a missing file, untouched. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE, pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
reqId = rid++;
AssertTrue(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx) != WS_SUCCESS);
AssertTrue(SftpOpenPath(ssh, reqId, path, WOLFSSH_FXF_WRITE, handle)
!= WS_SUCCESS);
AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE);
AssertTrue(WSTAT(ssh->fs, path, &st) != 0);
/* WRITE|CREAT, no TRUNC: must create the missing file (OPEN_ALWAYS). */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
/* seed content through the handle just opened */
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content, sizeof(content) - 1);
idx += (word32)(sizeof(content) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
/* WRITE|CREAT, no TRUNC: must create the missing file (OPEN_ALWAYS).
* Seed content through the handle just opened. */
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, handle), WS_SUCCESS);
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0, content,
contentSz), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1));
AssertIntEQ((int)st.st_size, (int)contentSz);
/* WRITE|CREAT, no TRUNC, on the existing file: must not truncate it. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1));
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ((int)st.st_size, (int)contentSz);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
/* WRITE|CREAT|TRUNC: must truncate the existing file immediately. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_TRUNC,
handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, 0);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
/* WRITE|CREAT|EXCL against the existing file: must fail. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_EXCL,
pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
reqId = rid++;
AssertTrue(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx) != WS_SUCCESS);
AssertTrue(SftpOpenPath(ssh, reqId, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_EXCL,
handle) != WS_SUCCESS);
AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE);
(void)WREMOVE(ssh->fs, path);
/* WRITE|CREAT|EXCL against a missing path: must succeed. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_EXCL,
pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT | WOLFSSH_FXF_EXCL,
handle), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
(void)WREMOVE(ssh->fs, path);
/* READ|WRITE|CREAT, no TRUNC, against a missing path: must create it. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_READ | WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT,
pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_READ | WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT,
handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
(void)WREMOVE(ssh->fs, path);
/* WRITE|TRUNC, no CREAT, against a missing path: must fail
* (TRUNCATE_EXISTING requires the file to already exist). */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_TRUNC, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
reqId = rid++;
AssertTrue(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx) != WS_SUCCESS);
AssertTrue(SftpOpenPath(ssh, reqId, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_TRUNC, handle) != WS_SUCCESS);
AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE);
/* seed an existing file with content, then WRITE|TRUNC, no CREAT: must
* truncate it immediately (TRUNCATE_EXISTING). */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content, sizeof(content) - 1);
idx += (word32)(sizeof(content) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, handle), WS_SUCCESS);
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0, content,
contentSz), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1));
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_TRUNC, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
AssertIntEQ((int)st.st_size, (int)contentSz);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_TRUNC, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, 0);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
(void)WREMOVE(ssh->fs, path);
/* seed an existing file with content for the READ|TRUNC case below. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content, sizeof(content) - 1);
idx += (word32)(sizeof(content) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_WRITE | WOLFSSH_FXF_CREAT, handle), WS_SUCCESS);
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0, content,
contentSz), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1));
AssertIntEQ((int)st.st_size, (int)contentSz);
/* READ|TRUNC, no WRITE, no CREAT: TRUNCATE_EXISTING requires
* GENERIC_WRITE in dwDesiredAccess, so this must still open (falling
* back to OPEN_EXISTING) rather than fail with ERROR_INVALID_PARAMETER,
* and must leave the content untouched since it cannot actually
* truncate. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_READ | WOLFSSH_FXF_TRUNC, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
reqId = rid++;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, reqId, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_READ | WOLFSSH_FXF_TRUNC, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size, (int)(sizeof(content) - 1));
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
(void)WREMOVE(ssh->fs, path);
/* WRITE|APPEND|CREAT against a missing path: must create it, and every
* write must land at EOF regardless of the client-supplied offset. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_WRITE | WOLFSSH_FXF_APPEND | WOLFSSH_FXF_CREAT,
pkt + idx); idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
/* first write, offset 0: lands at EOF (0), file becomes "0123456789" */
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content, sizeof(content) - 1);
idx += (word32)(sizeof(content) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
/* second write, offset stale at 0 again: must still append at EOF (10)
* rather than overwrite the start of the file. */
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content2) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content2, sizeof(content2) - 1);
idx += (word32)(sizeof(content2) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size,
(int)(sizeof(content) - 1 + sizeof(content2) - 1));
/* content, not just length: a build that appended the right byte
* count in the wrong order, or left a gap, would still pass the size
* check above. */
AssertIntEQ(WFOPEN(NULL, &file, path, "rb"), 0);
AssertTrue(file != WBADFILE);
readSz = (word32)WFREAD(NULL, readBuf, 1,
sizeof(content) - 1 + sizeof(content2) - 1, file);
WFCLOSE(NULL, file);
AssertIntEQ((int)readSz,
(int)(sizeof(content) - 1 + sizeof(content2) - 1));
AssertIntEQ(WMEMCMP(readBuf, "0123456789abcde", readSz), 0);
AssertIntEQ((int)st.st_size, (int)contentSz);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
(void)WREMOVE(ssh->fs, path);
/* APPEND|CREAT, no WRITE: FILE_APPEND_DATA alone is enough to create
* the file and append to it, unlike master, which could not open this
* combination at all. */
idx = 0;
SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
SftpPutU32(WOLFSSH_FXF_APPEND | WOLFSSH_FXF_CREAT, pkt + idx);
idx += UINT32_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvOpen(ssh, rid++, pkt, idx), WS_SUCCESS);
reply = wolfSSH_SFTP_TestRecvReply(ssh, &replySz);
AssertNotNull(reply);
AssertTrue(replySz >= hOff + WOLFSSH_HANDLE_ID_SZ);
WMEMCPY(handle, reply + hOff, WOLFSSH_HANDLE_ID_SZ);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content, sizeof(content) - 1);
idx += (word32)(sizeof(content) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset hi */
SftpPutU32(0, pkt + idx); idx += UINT32_SZ; /* offset lo */
SftpPutU32((word32)(sizeof(content2) - 1), pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, content2, sizeof(content2) - 1);
idx += (word32)(sizeof(content2) - 1);
AssertIntEQ(wolfSSH_SFTP_RecvWrite(ssh, rid++, pkt, idx), WS_SUCCESS);
idx = 0;
SftpPutU32(WOLFSSH_HANDLE_ID_SZ, pkt + idx); idx += UINT32_SZ;
WMEMCPY(pkt + idx, handle, WOLFSSH_HANDLE_ID_SZ);
idx += WOLFSSH_HANDLE_ID_SZ;
AssertIntEQ(wolfSSH_SFTP_RecvClose(ssh, rid++, pkt, idx), WS_SUCCESS);
* combination at all. (WRITE|APPEND|CREAT is covered on every platform
* by TestSftpAppendWritesAtEof.) */
AssertIntEQ(SftpOpenPath(ssh, rid++, path,
WOLFSSH_FXF_APPEND | WOLFSSH_FXF_CREAT, handle), WS_SUCCESS);
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0, content,
contentSz), WS_SUCCESS);
AssertIntEQ(SftpWriteHandle(ssh, rid++, handle, 0, 0, content2,
content2Sz), WS_SUCCESS);
AssertIntEQ(SftpCloseHandle(ssh, rid++, handle), WS_SUCCESS);
AssertIntEQ(WSTAT(ssh->fs, path, &st), 0);
AssertIntEQ((int)st.st_size,
(int)(sizeof(content) - 1 + sizeof(content2) - 1));
AssertIntEQ((int)st.st_size, (int)(contentSz + content2Sz));
(void)WREMOVE(ssh->fs, path);
wolfSSH_SFTP_TestRecvStateFree(ssh);
@ -12498,6 +12363,10 @@ int main(int argc, char** argv)
/* SETSTAT/FSETSTAT apply the attributes they acknowledge */
TestSftpSetStatAttributes();
#endif
#if !defined(NO_WOLFSSH_SERVER) && !defined(NO_FILESYSTEM)
/* FXF_APPEND writes land at EOF regardless of the request offset */
TestSftpAppendWritesAtEof();
#endif
#if !defined(NO_WOLFSSH_SERVER) && defined(USE_WINDOWS_API) && \
!defined(NO_FILESYSTEM)
/* RecvOpen's Windows open-flag matrix */

View File

@ -1630,6 +1630,10 @@ extern "C" {
const unsigned int* shortOffset);
#define WPWRITE(fs,fd,b,s,o) wPwrite((fd),(b),(s),(o))
#define WPREAD(fs,fd,b,s,o) wPread((fd),(b),(s),(o))
/* Positionless write, for O_APPEND handles: pwrite() only honors
* O_APPEND on Linux, write() does everywhere. */
#define WWRITE(fs,fd,b,s) (int)write((fd),(b),(s))
#define WTRUNCATE(fs,f,sz) truncate((f),(off_t)(sz))
#define WFTRUNCATE(fs,fd,sz) ftruncate((fd),(off_t)(sz))