1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-06-20 01:17:49 +02:00

Refactor SFTP unit tests.

Replace the verbose, hand-built HrnLibSsh2 script entries in the SFTP unit tests with a set of per-function HRN_LIBSSH2_* response macros (one per libssh2 shim function). Each macro names its function, bakes in the fixed parameters the production code always passes, and defaults the common result, so a test supplies only the values that vary as trailing designated initializers.

The harness now defaults omitted values rather than requiring every field to be spelled out: libssh2_session_hostkey() defaults length/type/value; libssh2_sftp_stat_ex() defaults an omitted .attr to a regular file with mode 0640, defaults .flags to the standard attribute set (adding a size for regular files), and defaults .uid/.gid to the test user/group. Add HRN_LIBSSH2_ATTR_EXISTENCE (path exists but reports no attributes) and HRN_LIBSSH2_OWNER_ROOT (report ownership by root) sentinels, plus HRN_LIBSSH2_DIR/FILE/LINK/FIFO() helpers that OR a file type with an octal mode.

Replace the NULL-terminated script array and hrnLibSsh2ScriptSet(array) with HRN_LIBSSH2_SCRIPT_SET(...), which computes the script length so no terminator entry is needed; hrnLibSsh2ScriptSet() now takes an explicit size. Rebuild HRNLIBSSH2_MACRO_STARTUP/SHUTDOWN() and HOSTKEY_HASH_ENTRY() on top of the new macros, and stat_ex now verifies the requested stat_type (via .follow) instead of scripting it as a parameter.

Reorganize the tests themselves: split bundled comment groups into TEST_TITLE sections, split scripts per section, and drop redundant connect/disconnect setup. Remove tests that duplicate Posix tests for common code. Remove duplicative SFTP tests. Rename HrnLibSsh2 fields for clarity (attrPerms -> attr/mode, symlinkExTarget -> target).

These changes cut sftpTest.c roughly in half.
This commit is contained in:
David Steele
2026-06-17 12:15:48 +07:00
parent ffa325b8a4
commit 5b77f9bc22
4 changed files with 2646 additions and 6225 deletions
+1 -1
View File
@@ -632,7 +632,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: sftp
total: 20
total: 14
harness:
name: libSsh2
integration: false
+80 -49
View File
@@ -11,6 +11,7 @@ libssh2 Test Harness
#include "common/type/json.h"
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "common/user.h"
#include "common/harnessLibSsh2.h"
#include "common/harnessTest.h"
@@ -20,12 +21,19 @@ libssh2 shim error prefix
***********************************************************************************************************************************/
#define LIBSSH2_ERROR_PREFIX "LIBSSH2 SHIM ERROR"
/***********************************************************************************************************************************
Standard stat attribute flags reported when a scripted response omits .flags
***********************************************************************************************************************************/
#define HRN_LIBSSH2_ATTR_DEFAULT \
(LIBSSH2_SFTP_ATTR_PERMISSIONS | LIBSSH2_SFTP_ATTR_ACMODTIME | LIBSSH2_SFTP_ATTR_UIDGID)
/***********************************************************************************************************************************
Script that defines how shim functions operate
***********************************************************************************************************************************/
static HrnLibSsh2 hrnLibSsh2Script[1024];
static bool hrnLibSsh2ScriptDone = true;
static unsigned int hrnLibSsh2ScriptIdx;
static unsigned int hrnLibSsh2ScriptSize;
// If there is a script failure change the behavior of cleanup functions to return immediately so the real error will be reported
// rather than a bogus scripting error during cleanup
@@ -36,24 +44,19 @@ static char hrnLibSsh2ScriptError[4096];
Set libssh2 script
***********************************************************************************************************************************/
void
hrnLibSsh2ScriptSet(HrnLibSsh2 *hrnLibSsh2ScriptParam)
hrnLibSsh2ScriptSet(const HrnLibSsh2 *const script, const unsigned int scriptSize)
{
if (!hrnLibSsh2ScriptDone)
THROW(AssertError, "previous libssh2 script has not yet completed");
if (hrnLibSsh2ScriptParam[0].function == NULL)
if (scriptSize == 0)
THROW(AssertError, "libssh2 script must have entries");
// Copy records into local storage
unsigned int copyIdx = 0;
for (unsigned int copyIdx = 0; copyIdx < scriptSize; copyIdx++)
hrnLibSsh2Script[copyIdx] = script[copyIdx];
while (hrnLibSsh2ScriptParam[copyIdx].function != NULL)
{
hrnLibSsh2Script[copyIdx] = hrnLibSsh2ScriptParam[copyIdx];
copyIdx++;
}
hrnLibSsh2Script[copyIdx].function = NULL;
hrnLibSsh2ScriptSize = scriptSize;
hrnLibSsh2ScriptDone = false;
hrnLibSsh2ScriptIdx = 0;
}
@@ -146,7 +149,7 @@ hrnLibSsh2ScriptRun(const char *const function, const VariantList *const param,
hrnLibSsh2ScriptIdx++;
if (hrnLibSsh2Script[hrnLibSsh2ScriptIdx].function == NULL)
if (hrnLibSsh2ScriptIdx >= hrnLibSsh2ScriptSize)
hrnLibSsh2ScriptDone = true;
strFree(paramStr);
@@ -333,10 +336,11 @@ libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len, int *type)
{
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(HRNLIBSSH2_SESSION_HOSTKEY, NULL, (HrnLibSsh2 *)session);
*len = (size_t)hrnLibSsh2->len;
*type = (int)hrnLibSsh2->type;
// Default the host key length (20), type (RSA), and value (HOSTKEY) when omitted
*len = hrnLibSsh2->len == 0 ? 20 : (size_t)hrnLibSsh2->len;
*type = hrnLibSsh2->type == 0 ? LIBSSH2_HOSTKEY_TYPE_RSA : (int)hrnLibSsh2->type;
return hrnLibSsh2->resultNull ? NULL : (const char *)hrnLibSsh2->resultZ;
return hrnLibSsh2->resultNull ? NULL : (const char *)(hrnLibSsh2->resultZ != NULL ? hrnLibSsh2->resultZ : HOSTKEY);
}
/***********************************************************************************************************************************
@@ -557,27 +561,45 @@ libssh2_sftp_stat_ex(
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_STAT_EX,
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(path)),
varNewInt(stat_type)),
(HrnLibSsh2 *)sftp);
HRNLIBSSH2_SFTP_STAT_EX, varLstAdd(varLstNew(), varNewStrZ(path)), (HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
// When the scripted response follows symlinks, verify the production code requested LIBSSH2_SFTP_STAT (otherwise stat_type is
// not checked, since it only affects the result for symlinks)
if (hrnLibSsh2->follow && stat_type != LIBSSH2_SFTP_STAT)
THROW_FMT(AssertError, "libssh2_sftp_stat_ex expected stat_type LIBSSH2_SFTP_STAT but got %d", stat_type);
if (attrs == NULL)
THROW(AssertError, "attrs is NULL");
attrs->flags = 0;
attrs->flags |= (unsigned long)hrnLibSsh2->flags;
// An omitted (0) attr defaults to a regular file with mode 0640
attrs->permissions =
hrnLibSsh2->attr == 0 ? (unsigned long)(LIBSSH2_SFTP_S_IFREG | 0640) : (unsigned long)hrnLibSsh2->attr;
attrs->permissions = 0;
attrs->permissions |= (unsigned long)hrnLibSsh2->attrPerms;
// Default the response flags: omitted (0) reports the standard attribute set, plus LIBSSH2_SFTP_ATTR_SIZE for a regular file
// (which reports a size); HRN_LIBSSH2_ATTR_EXISTENCE reports none (the path exists but provides no attributes)
uint64_t flags = hrnLibSsh2->flags;
if (flags == 0)
{
flags = HRN_LIBSSH2_ATTR_DEFAULT;
if (LIBSSH2_SFTP_S_ISREG(attrs->permissions))
flags |= LIBSSH2_SFTP_ATTR_SIZE;
}
else if (flags == HRN_LIBSSH2_ATTR_EXISTENCE)
flags = 0;
attrs->flags = (unsigned long)flags;
attrs->mtime = (unsigned long)hrnLibSsh2->mtime;
attrs->uid = (unsigned long)hrnLibSsh2->uid;
attrs->gid = (unsigned long)hrnLibSsh2->gid;
// An omitted (0) uid/gid defaults to the test user/group; HRN_LIBSSH2_OWNER_ROOT selects root (0)
attrs->uid =
hrnLibSsh2->uid == HRN_LIBSSH2_OWNER_ROOT ? 0 : hrnLibSsh2->uid == 0 ? (unsigned long)userId() : (unsigned long)hrnLibSsh2->uid;
attrs->gid =
hrnLibSsh2->gid == HRN_LIBSSH2_OWNER_ROOT ? 0 : hrnLibSsh2->gid == 0 ? (unsigned long)groupId() : (unsigned long)hrnLibSsh2->gid;
attrs->filesize = hrnLibSsh2->filesize;
return hrnLibSsh2->resultInt;
@@ -626,15 +648,15 @@ libssh2_sftp_symlink_ex(
{
case LIBSSH2_SFTP_READLINK:
case LIBSSH2_SFTP_REALPATH:
if (hrnLibSsh2->symlinkExTarget != NULL)
if (hrnLibSsh2->target != NULL)
{
if (strSize(hrnLibSsh2->symlinkExTarget) < PATH_MAX)
strncpy(target, strZ(hrnLibSsh2->symlinkExTarget), strSize(hrnLibSsh2->symlinkExTarget));
if (strSize(hrnLibSsh2->target) < PATH_MAX)
strncpy(target, strZ(hrnLibSsh2->target), strSize(hrnLibSsh2->target));
else
THROW_FMT(AssertError, "symlinkExTarget too large for target buffer");
THROW_FMT(AssertError, "link target too large for buffer");
}
rc = hrnLibSsh2->resultInt != 0 ? hrnLibSsh2->resultInt : (int)strSize(hrnLibSsh2->symlinkExTarget);
rc = hrnLibSsh2->resultInt != 0 ? hrnLibSsh2->resultInt : (int)strSize(hrnLibSsh2->target);
break;
default:
@@ -665,15 +687,21 @@ libssh2_sftp_open_ex(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(filename)),
varNewUInt64(flags)),
varNewInt64(mode)),
varLstNew(), varNewStrZ(filename)),
varNewUInt64(flags)),
varNewInt(open_type)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
// Verify the open mode. It is not part of the scripted param so it can be defaulted: a write open with an omitted (0) mode
// expects 0640, while other opens expect mode 0.
long modeExpected =
hrnLibSsh2->mode != 0 ? (long)hrnLibSsh2->mode : ((flags & LIBSSH2_FXF_WRITE) != 0 ? 0640 : 0);
if (mode != modeExpected)
THROW_FMT(AssertError, "libssh2_sftp_open_ex expected mode %ld but got %ld", modeExpected, mode);
return hrnLibSsh2->resultNull ? NULL : (LIBSSH2_SFTP_HANDLE *)hrnLibSsh2;
}
@@ -697,9 +725,7 @@ libssh2_sftp_readdir_ex(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(buffer)),
varNewUInt64(buffer_maxlen)),
varLstNew(), varNewUInt64(buffer_maxlen)),
varNewStrZ(longentry)),
varNewUInt64(longentry_maxlen)),
(HrnLibSsh2 *)handle);
@@ -709,7 +735,10 @@ libssh2_sftp_readdir_ex(
if (hrnLibSsh2->fileName != NULL)
strncpy(buffer, strZ(hrnLibSsh2->fileName), buffer_maxlen);
return hrnLibSsh2->resultInt;
// libssh2_sftp_readdir_ex() returns the number of bytes in the entry name (0 at end of directory). Derive it from the scripted
// file name so tests need not specify it; a negative resultInt (e.g. EAGAIN) is returned as-is.
return hrnLibSsh2->resultInt < 0 ?
hrnLibSsh2->resultInt : hrnLibSsh2->fileName != NULL ? (int)strSize(hrnLibSsh2->fileName) : 0;
}
/***********************************************************************************************************************************
@@ -745,15 +774,16 @@ libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, const char *path, unsigned int path_le
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_MKDIR_EX,
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(path)),
varNewInt64(mode)),
(HrnLibSsh2 *)sftp);
HRNLIBSSH2_SFTP_MKDIR_EX, varLstAdd(varLstNew(), varNewStrZ(path)), (HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
// Verify the mode. It is not part of the scripted param so it can be defaulted: an omitted (0) mode expects 0750.
long modeExpected = hrnLibSsh2->mode != 0 ? (long)hrnLibSsh2->mode : 0750;
if (mode != modeExpected)
THROW_FMT(AssertError, "libssh2_sftp_mkdir_ex expected mode %ld but got %ld", modeExpected, mode);
return hrnLibSsh2->resultInt;
}
@@ -781,8 +811,9 @@ libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxle
if (hrnLibSsh2->readBuffer != NULL)
strncpy(buffer, strZ(hrnLibSsh2->readBuffer), strSize(hrnLibSsh2->readBuffer));
// number of bytes populated
return hrnLibSsh2->resultInt;
// Return the number of bytes read, defaulting an omitted (0) result to the read buffer size (0 when there is no read buffer)
return hrnLibSsh2->resultInt != 0 ?
hrnLibSsh2->resultInt : (hrnLibSsh2->readBuffer != NULL ? (ssize_t)strSize(hrnLibSsh2->readBuffer) : 0);
}
/***********************************************************************************************************************************
@@ -908,8 +939,8 @@ libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, size_t count
}
MEM_CONTEXT_TEMP_END();
// Return number of bytes written
return hrnLibSsh2->resultInt;
// Return the number of bytes written, defaulting an omitted (0) result to the full count
return hrnLibSsh2->resultInt != 0 ? hrnLibSsh2->resultInt : (ssize_t)count;
}
#endif // HAVE_LIBSSH2
+222 -37
View File
@@ -12,7 +12,9 @@ Scripted testing for libssh2 so exact results can be returned for unit testing.
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include "common/macro.h"
#include "common/time.h"
@@ -31,6 +33,10 @@ libssh2 authorization constants
#define ETC_KNOWNHOSTS2_FILE_CSTR "/etc/ssh/ssh_known_hosts2"
#define HOSTKEY "12345678901234567890"
// Sentinel .uid/.gid for scripted stat responses selecting root (0). An omitted (0) .uid/.gid instead defaults to the test
// user/group, so use HRN_LIBSSH2_OWNER_ROOT when a response must report ownership by root.
#define HRN_LIBSSH2_OWNER_ROOT UINT_MAX
/***********************************************************************************************************************************
Function constants
***********************************************************************************************************************************/
@@ -68,40 +74,6 @@ Function constants
#define HRNLIBSSH2_SFTP_WRITE "libssh2_sftp_write"
#define HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX "libssh2_userauth_publickey_fromfile_ex"
/***********************************************************************************************************************************
Macros for defining groups of functions that implement commands
***********************************************************************************************************************************/
// Set of functions mimicking libssh2 initialization and authorization
#define HRNLIBSSH2_MACRO_STARTUP() \
{.function = HRNLIBSSH2_INIT, .param = "[0]", .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]"}, \
{.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, .resultInt = 0}, \
{.function = HRNLIBSSH2_KNOWNHOST_INIT}, \
{.function = HRNLIBSSH2_KNOWNHOST_READFILE, .param = "[\"" KNOWNHOSTS_FILE_CSTR "\",1]", .resultInt = 5}, \
{.function = HRNLIBSSH2_SESSION_HOSTKEY, .len = 20, .type = LIBSSH2_HOSTKEY_TYPE_RSA, .resultZ = HOSTKEY}, \
{.function = HRNLIBSSH2_KNOWNHOST_CHECKP, .param = "[\"localhost\",22,\"" HOSTKEY "\",20,65537]", \
.resultInt = LIBSSH2_KNOWNHOST_CHECK_MATCH}, \
{.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, \
.param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", \
.resultInt = 0}, \
{.function = HRNLIBSSH2_SFTP_INIT}
// Set of functions mimicking libssh2 shutdown and disconnect
#define HRNLIBSSH2_MACRO_SHUTDOWN() \
{.function = HRNLIBSSH2_SFTP_SHUTDOWN, .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_DISCONNECT_EX, .param ="[11,\"pgBackRest instance shutdown\",\"\"]", .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_FREE, .resultInt = 0}, \
{.function = NULL} \
// Older systems do not support LIBSSH2_HOSTKEY_HASH_SHA256
#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
#define HOSTKEY_HASH_ENTRY() \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[3]", .resultZ = "12345678910123456789"}
#else
#define HOSTKEY_HASH_ENTRY() \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678910123456789"}
#endif
/***********************************************************************************************************************************
Structure for scripting libssh2 responses
***********************************************************************************************************************************/
@@ -115,12 +87,14 @@ typedef struct HrnLibSsh2
const char *resultZ; // Zero-terminated result value
bool resultNull; // Return null from function that normally returns a struct ptr
uint64_t flags; // libssh2 flags
uint64_t attrPerms; // libssh2 attr perms
uint64_t attr; // libssh2 attributes (file type and mode bits)
uint64_t mode; // libssh2_sftp_open_ex file mode (write open defaults to 0640)
bool follow; // stat_ex follows symlinks (LIBSSH2_SFTP_STAT vs LSTAT)
uint64_t atime, mtime; // libssh2 timestamps
uint64_t uid, gid; // libssh2 uid/gid
uint64_t filesize; // libssh2 filesize
uint64_t offset; // libssh2 seek offset
const String *symlinkExTarget; // libssh2_sftp_symlink_ex target
const String *target; // libssh2_sftp_symlink_ex target
const String *fileName; // libssh2_readdir* libssh2_stat* filename
const String *readBuffer; // what to copy into read buffer
TimeMSec sleep; // Sleep specified milliseconds before returning from function
@@ -129,10 +103,221 @@ typedef struct HrnLibSsh2
const char *errMsg; // libssh2_session_last_error error msg
} HrnLibSsh2;
/***********************************************************************************************************************************
Older systems do not support LIBSSH2_HOSTKEY_HASH_SHA256
***********************************************************************************************************************************/
#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
#define HOSTKEY_HASH_ENTRY() HRN_LIBSSH2_HOSTKEY_HASH(3, .resultZ = "12345678910123456789")
#else
#define HOSTKEY_HASH_ENTRY() HRN_LIBSSH2_HOSTKEY_HASH(2, .resultZ = "12345678910123456789")
#endif
/***********************************************************************************************************************************
Scripted response macros, one per libssh2 shim function
Each expands to an HrnLibSsh2 script entry that names the function, bakes its fixed parameters, and defaults the common result, so a
test supplies only the values that vary as trailing designated initializers (e.g. .resultInt, .resultNull). The associated helper
macros build the values those entries take (.attr, .flags, ...).
***********************************************************************************************************************************/
// Sentinel .flags reporting a path that exists but carries no attributes (the harness maps it to libssh2 flags 0). Uses a value
// that cannot collide with a real LIBSSH2_SFTP_ATTR_* combination.
#define HRN_LIBSSH2_ATTR_EXISTENCE UINT64_MAX
// .attr shorthand: file type OR'd with an octal mode (the LIBSSH2_SFTP_S_I* permission bits are the octal mode bits)
#define HRN_LIBSSH2_DIR(mode) (LIBSSH2_SFTP_S_IFDIR | (mode))
#define HRN_LIBSSH2_FILE(mode) (LIBSSH2_SFTP_S_IFREG | (mode))
#define HRN_LIBSSH2_LINK(mode) (LIBSSH2_SFTP_S_IFLNK | (mode))
#define HRN_LIBSSH2_FIFO(mode) (LIBSSH2_SFTP_S_IFIFO | (mode))
// libssh2_sftp_readdir_ex() response returning a directory entry (an empty name signals end of directory)
#define HRN_LIBSSH2_READDIR(name, ...) \
{.function = HRNLIBSSH2_SFTP_READDIR_EX, .param = "[4095,null,0]", .fileName = STRDEF(name), __VA_ARGS__}
// libssh2_sftp_stat_ex() response. The path is the full server path (e.g. TEST_PATH "/sub"). Attributes are trailing arguments
// (.attr, .flags, .mtime, .uid, .gid, .filesize, ...); the harness defaults omitted ones: .attr (0) to a regular file (set it with
// the HRN_LIBSSH2_DIR/FILE/LINK/FIFO() helpers), .uid/.gid (0) to the test user/group (HRN_LIBSSH2_OWNER_ROOT for root), and
// .flags (0) to the standard attribute set (permissions, times, owner) plus a size for a regular file. Reports success
// (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure. Set .follow = true to require LIBSSH2_SFTP_STAT.
#define HRN_LIBSSH2_STAT(path, ...) \
{.function = HRNLIBSSH2_SFTP_STAT_EX, .param = "[\"" path "\"]", __VA_ARGS__}
// libssh2_sftp_close_handle() response. Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure.
#define HRN_LIBSSH2_CLOSE(...) {.function = HRNLIBSSH2_SFTP_CLOSE_HANDLE, __VA_ARGS__}
// libssh2_sftp_fsync() response. Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure.
#define HRN_LIBSSH2_FSYNC(...) {.function = HRNLIBSSH2_SFTP_FSYNC, __VA_ARGS__}
// libssh2_session_block_directions() response. Reports no blocked direction (SSH2_NO_BLOCK_READING_WRITING) by default; set
// .resultInt to an SSH2_BLOCK_* direction to make the production code wait.
#define HRN_LIBSSH2_BLOCK(...) {.function = HRNLIBSSH2_SESSION_BLOCK_DIRECTIONS, __VA_ARGS__}
// libssh2_session_last_errno() response returning the given libssh2 error
#define HRN_LIBSSH2_ERRNO(error, ...) \
{.function = HRNLIBSSH2_SESSION_LAST_ERRNO, .resultInt = (error), __VA_ARGS__}
// libssh2_sftp_last_error() response returning the given SFTP status (LIBSSH2_FX_*)
#define HRN_LIBSSH2_SFTP_ERROR(code, ...) \
{.function = HRNLIBSSH2_SFTP_LAST_ERROR, .resultUInt = (code), __VA_ARGS__}
// libssh2_session_last_error() response returning the given libssh2 error and, via .errMsg, the message string
#define HRN_LIBSSH2_SESSION_ERROR(error, ...) \
{.function = HRNLIBSSH2_SESSION_LAST_ERROR, .resultInt = (error), __VA_ARGS__}
// libssh2_init() response (always called with flags 0). Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a
// failure.
#define HRN_LIBSSH2_INIT(...) \
{.function = HRNLIBSSH2_INIT, .param = "[0]", __VA_ARGS__}
// libssh2_session_init_ex() response (the production code always passes NULL allocators). Returns a session by default; set
// .resultNull = true for a failure.
#define HRN_LIBSSH2_SESSION_INIT(...) \
{.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]", __VA_ARGS__}
// libssh2_session_handshake() response. Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure.
#define HRN_LIBSSH2_HANDSHAKE(...) \
{.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, __VA_ARGS__}
// libssh2_session_hostkey() response. The harness defaults the host key length (20), type (LIBSSH2_HOSTKEY_TYPE_RSA), and
// value (HOSTKEY); set .type for another key type or .resultNull = true for a failure.
#define HRN_LIBSSH2_HOSTKEY(...) \
{.function = HRNLIBSSH2_SESSION_HOSTKEY, __VA_ARGS__}
// libssh2_hostkey_hash() response for the given hash type. Set .resultZ to the hash, or .resultNull = true for a failure.
#define HRN_LIBSSH2_HOSTKEY_HASH(hashType, ...) \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[" #hashType "]", __VA_ARGS__}
// libssh2_knownhost_init() response. Returns a known-hosts collection by default; set .resultNull = true for a failure.
#define HRN_LIBSSH2_KNOWNHOST_INIT(...) \
{.function = HRNLIBSSH2_KNOWNHOST_INIT, __VA_ARGS__}
// libssh2_knownhost_readfile() response for the given file (LIBSSH2_KNOWNHOST_FILE_OPENSSH). Reports 0 hosts read by default; set
// .resultInt to the host count or a failure.
#define HRN_LIBSSH2_KNOWNHOST_READFILE(file, ...) \
{.function = HRNLIBSSH2_KNOWNHOST_READFILE, .param = "[\"" file "\",1]", __VA_ARGS__}
// libssh2_knownhost_writefile() response for the given file (LIBSSH2_KNOWNHOST_FILE_OPENSSH). Reports success (LIBSSH2_ERROR_NONE)
// by default; set .resultInt for a failure.
#define HRN_LIBSSH2_KNOWNHOST_WRITEFILE(file, ...) \
{.function = HRNLIBSSH2_KNOWNHOST_WRITEFILE, .param = "[\"" file "\",1]", __VA_ARGS__}
// libssh2_knownhost_checkp() response for localhost returning the given LIBSSH2_KNOWNHOST_CHECK_* result
#define HRN_LIBSSH2_KNOWNHOST_CHECK(result) \
{.function = HRNLIBSSH2_KNOWNHOST_CHECKP, .param = "[\"localhost\",22,\"" HOSTKEY "\",20,65537]", .resultInt = (result)}
// libssh2_knownhost_addc() response adding localhost with the given type mask. Reports success by default; set .resultInt for a
// failure.
#define HRN_LIBSSH2_KNOWNHOST_ADD(typeMask, ...) \
{.function = HRNLIBSSH2_KNOWNHOST_ADDC, \
.param = "[\"localhost\",null,\"" HOSTKEY "\",20,\"Generated from pgBackRest\",25," #typeMask "]", __VA_ARGS__}
// libssh2_userauth_publickey_fromfile_ex() response for the standard public-key login (TEST_USER with KEYPUB_CSTR / KEYPRIV_CSTR
// and no passphrase). Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure. A login that omits the public
// key or supplies a passphrase is scripted longhand.
#define HRN_LIBSSH2_USERAUTH(...) \
{.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, \
.param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", __VA_ARGS__}
// libssh2_sftp_init() response. Returns an SFTP session by default; set .resultNull = true for a failure.
#define HRN_LIBSSH2_SFTP_INIT(...) \
{.function = HRNLIBSSH2_SFTP_INIT, __VA_ARGS__}
// libssh2_sftp_shutdown() response. Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure.
#define HRN_LIBSSH2_SHUTDOWN(...) {.function = HRNLIBSSH2_SFTP_SHUTDOWN, __VA_ARGS__}
// libssh2_session_disconnect_ex() response (the production code always passes the standard shutdown reason). Reports success
// (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure.
#define HRN_LIBSSH2_DISCONNECT(...) \
{.function = HRNLIBSSH2_SESSION_DISCONNECT_EX, .param = "[11,\"pgBackRest instance shutdown\",\"\"]", __VA_ARGS__}
// libssh2_session_free() response. Reports success (LIBSSH2_ERROR_NONE) by default; set .resultInt for a failure.
#define HRN_LIBSSH2_SESSION_FREE(...) {.function = HRNLIBSSH2_SESSION_FREE, __VA_ARGS__}
// libssh2_sftp_seek64() expectation verifying the production code seeks to the given byte offset (the function returns void)
#define HRN_LIBSSH2_SEEK(offset, ...) \
{.function = HRNLIBSSH2_SFTP_SEEK64, .param = "[" #offset "]", __VA_ARGS__}
// libssh2_sftp_read() response (size is the production buffer_maxlen). Set .readBuffer to the bytes returned; the result
// (bytes read) defaults to the read buffer size, so set .resultInt only for a short read or a failure.
#define HRN_LIBSSH2_READ(size, ...) \
{.function = HRNLIBSSH2_SFTP_READ, .param = "[" #size "]", __VA_ARGS__}
// libssh2_sftp_write() response (size is the production byte size). The result (bytes written) defaults to the full size, so set
// .resultInt only for a short write or a failure.
#define HRN_LIBSSH2_WRITE(size, ...) \
{.function = HRNLIBSSH2_SFTP_WRITE, .param = "[" #size "]", __VA_ARGS__}
// libssh2_sftp_open_ex() responses, one per production open path (param is ["path",flags,open_type]): a directory (flags 0,
// LIBSSH2_SFTP_OPENDIR), a file for reading (LIBSSH2_FXF_READ, LIBSSH2_SFTP_OPENFILE), or a file for writing
// (LIBSSH2_FXF_CREAT | LIBSSH2_FXF_WRITE | LIBSSH2_FXF_TRUNC = 26, LIBSSH2_SFTP_OPENFILE). The path is the full server path (e.g.
// TEST_PATH "/sub"). The harness verifies the open mode separately; a write open defaults to 0640, so set .mode only when it
// differs. Pass .resultNull = true (optionally with .resultInt) for a failed open.
#define HRN_LIBSSH2_OPENDIR(path, ...) \
{.function = HRNLIBSSH2_SFTP_OPEN_EX, .param = "[\"" path "\",0,1]", __VA_ARGS__}
#define HRN_LIBSSH2_OPEN_READ(path, ...) \
{.function = HRNLIBSSH2_SFTP_OPEN_EX, .param = "[\"" path "\",1,0]", __VA_ARGS__}
#define HRN_LIBSSH2_OPEN_WRITE(path, ...) \
{.function = HRNLIBSSH2_SFTP_OPEN_EX, .param = "[\"" path "\",26,0]", __VA_ARGS__}
// libssh2_sftp_symlink_ex() response reading a link destination (param is ["path","",LIBSSH2_SFTP_READLINK]; the empty target is
// the production output buffer). The path is the full server path (e.g. TEST_PATH "/sub"). Reports success by default; set .target
// to the destination, or .resultInt for a failure.
#define HRN_LIBSSH2_READLINK(path, ...) \
{.function = HRNLIBSSH2_SFTP_SYMLINK_EX, .param = "[\"" path "\",\"\",1]", __VA_ARGS__}
// libssh2_sftp_unlink_ex() response. The path is the full server path (e.g. TEST_PATH "/sub"). Reports success (LIBSSH2_ERROR_NONE)
// by default; set .resultInt for a failure.
#define HRN_LIBSSH2_UNLINK(path, ...) \
{.function = HRNLIBSSH2_SFTP_UNLINK_EX, .param = "[\"" path "\"]", __VA_ARGS__}
// libssh2_sftp_mkdir_ex() response. The path is the full server path (e.g. TEST_PATH "/sub"). The harness verifies the mode
// separately and defaults an omitted .mode to 0750. Reports success by default; set .resultInt for a failure.
#define HRN_LIBSSH2_MKDIR(path, ...) \
{.function = HRNLIBSSH2_SFTP_MKDIR_EX, .param = "[\"" path "\"]", __VA_ARGS__}
// libssh2_sftp_rmdir_ex() response. The path is the full server path (e.g. TEST_PATH "/sub"). Reports success by default; set
// .resultInt for a failure.
#define HRN_LIBSSH2_RMDIR(path, ...) \
{.function = HRNLIBSSH2_SFTP_RMDIR_EX, .param = "[\"" path "\"]", __VA_ARGS__}
// libssh2_sftp_rename_ex() response (flags 7 = LIBSSH2_SFTP_RENAME_OVERWRITE | _ATOMIC | _NATIVE, as the production code always
// passes). Both paths are full server paths (e.g. TEST_PATH "/sub"). Reports success by default; set .resultInt for a failure.
#define HRN_LIBSSH2_RENAME(source, dest, ...) \
{.function = HRNLIBSSH2_SFTP_RENAME_EX, .param = "[\"" source "\",\"" dest "\",7]", __VA_ARGS__}
/***********************************************************************************************************************************
Macros for defining groups of functions that implement commands
***********************************************************************************************************************************/
// Set of functions mimicking libssh2 initialization and authorization
#define HRNLIBSSH2_MACRO_STARTUP() \
HRN_LIBSSH2_INIT(), \
HRN_LIBSSH2_SESSION_INIT(), \
HRN_LIBSSH2_HANDSHAKE(), \
HRN_LIBSSH2_KNOWNHOST_INIT(), \
HRN_LIBSSH2_KNOWNHOST_READFILE(KNOWNHOSTS_FILE_CSTR, .resultInt = 5), \
HRN_LIBSSH2_HOSTKEY(), \
HRN_LIBSSH2_KNOWNHOST_CHECK(LIBSSH2_KNOWNHOST_CHECK_MATCH), \
HRN_LIBSSH2_USERAUTH(), \
HRN_LIBSSH2_SFTP_INIT()
// Set of functions mimicking libssh2 shutdown and disconnect
#define HRNLIBSSH2_MACRO_SHUTDOWN() \
HRN_LIBSSH2_SHUTDOWN(), \
HRN_LIBSSH2_DISCONNECT(), \
HRN_LIBSSH2_SESSION_FREE()
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void hrnLibSsh2ScriptSet(HrnLibSsh2 *hrnLibSsh2ScriptParam);
// Set a libssh2 script. The prior script must have completed. The script length is computed, so no terminator entry is needed.
#define HRN_LIBSSH2_SCRIPT_SET(...) \
do \
{ \
const HrnLibSsh2 script[] = {__VA_ARGS__}; \
hrnLibSsh2ScriptSet(script, LENGTH_OF(script)); \
} \
while (0)
void hrnLibSsh2ScriptSet(const HrnLibSsh2 *script, unsigned int scriptSize);
#endif // HARNESS_LIBSSH2_REAL
File diff suppressed because it is too large Load Diff