1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-30 05:39:12 +02:00

Add recursion and json output to the ls command.

These features finally make the ls command practical.

Currently the JSON contains only name, type, and size.  We may add more fields in the future, but these seem like the minimum needed to be useful.
This commit is contained in:
David Steele 2019-09-12 16:29:50 -04:00
parent e45baa1830
commit 15d04ca19c
12 changed files with 397 additions and 47 deletions

View File

@ -163,6 +163,8 @@ use constant CFGOPT_HOST_ID => 'host-id'
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPT_FILTER => 'filter';
push @EXPORT, qw(CFGOPT_FILTER);
use constant CFGOPT_RECURSE => 'recurse';
push @EXPORT, qw(CFGOPT_RECURSE);
use constant CFGOPT_SORT => 'sort';
push @EXPORT, qw(CFGOPT_SORT);
@ -415,10 +417,10 @@ use constant CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC => 'aes-256-
# Info output
#-----------------------------------------------------------------------------------------------------------------------------------
use constant CFGOPTVAL_INFO_OUTPUT_TEXT => 'text';
push @EXPORT, qw(CFGOPTVAL_INFO_OUTPUT_TEXT);
use constant CFGOPTVAL_INFO_OUTPUT_JSON => 'json';
push @EXPORT, qw(CFGOPTVAL_INFO_OUTPUT_JSON);
use constant CFGOPTVAL_OUTPUT_TEXT => 'text';
push @EXPORT, qw(CFGOPTVAL_OUTPUT_TEXT);
use constant CFGOPTVAL_OUTPUT_JSON => 'json';
push @EXPORT, qw(CFGOPTVAL_OUTPUT_JSON);
# Restore type
#-----------------------------------------------------------------------------------------------------------------------------------
@ -1013,11 +1015,21 @@ my %hConfigDefine =
{
&CFGCMD_INFO =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_INFO_OUTPUT_TEXT,
&CFGDEF_DEFAULT => CFGOPTVAL_OUTPUT_TEXT,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_INFO_OUTPUT_TEXT,
&CFGOPTVAL_INFO_OUTPUT_JSON,
&CFGOPTVAL_OUTPUT_TEXT,
&CFGOPTVAL_OUTPUT_JSON,
]
},
&CFGCMD_STORAGE_LIST =>
{
&CFGDEF_DEFAULT => CFGOPTVAL_OUTPUT_TEXT,
&CFGDEF_ALLOW_LIST =>
[
&CFGOPTVAL_OUTPUT_TEXT,
&CFGOPTVAL_OUTPUT_JSON,
]
}
}
@ -1085,12 +1097,23 @@ my %hConfigDefine =
}
},
&CFGOPT_RECURSE =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_BOOLEAN,
&CFGDEF_DEFAULT => false,
&CFGDEF_COMMAND =>
{
&CFGCMD_STORAGE_LIST => {},
}
},
&CFGOPT_SORT =>
{
&CFGDEF_TYPE => CFGDEF_TYPE_STRING,
&CFGDEF_DEFAULT => 'asc',
&CFGDEF_ALLOW_LIST =>
[
'none',
'asc',
'desc',
],

View File

@ -1243,14 +1243,37 @@
<example>(F|D|I)$</example>
</option>
<!-- OPERATION - LS COMMAND - OUTPUT OPTION -->
<option id="output" name="Output">
<summary>Output format.</summary>
<text>The following output types are supported:
<ul>
<li><id>text</id> - Simple list with one file/link/path name on each line.</li>
<li><id>json</id> - Detailed file/link/path information in JSON format.</li>
</ul></text>
<example>json</example>
</option>
<!-- OPERATION - LS COMMAND - RECURSE OPTION -->
<option id="recurse" name="Recurse Subpaths">
<summary>Include all subpaths in output.</summary>
<text>All subpaths and their files will be included in the output.</text>
<example>y</example>
</option>
<!-- OPERATION - LS COMMAND - SORT OPTION -->
<option id="sort" name="Sort Output">
<summary>Sort output ascending/descending.</summary>
<summary>Sort output ascending, descending, or none.</summary>
<text>The following sort types are supported:
<ul>
<li><id>asc</id> - sort ascending.</li>
<li><id>desc</id> - sort descending.</li>
<li><id>none</id> - no sorting.</li>
</ul></text>
<example>desc</example>

View File

@ -14,6 +14,9 @@ sub libcAutoConstant
CFGOPTVAL_INFO_OUTPUT_TEXT => 'text',
CFGOPTVAL_INFO_OUTPUT_JSON => 'json',
CFGOPTVAL_LS_OUTPUT_TEXT => 'text',
CFGOPTVAL_LS_OUTPUT_JSON => 'json',
CFGOPTVAL_REPO_CIPHER_TYPE_NONE => 'none',
CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC => 'aes-256-cbc',
@ -25,6 +28,7 @@ sub libcAutoConstant
CFGOPTVAL_REPO_TYPE_POSIX => 'posix',
CFGOPTVAL_REPO_TYPE_S3 => 's3',
CFGOPTVAL_SORT_NONE => 'none',
CFGOPTVAL_SORT_ASC => 'asc',
CFGOPTVAL_SORT_DESC => 'desc',
@ -80,6 +84,8 @@ sub libcAutoExportTag
[
'CFGOPTVAL_INFO_OUTPUT_TEXT',
'CFGOPTVAL_INFO_OUTPUT_JSON',
'CFGOPTVAL_LS_OUTPUT_TEXT',
'CFGOPTVAL_LS_OUTPUT_JSON',
'CFGOPTVAL_REPO_CIPHER_TYPE_NONE',
'CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC',
'CFGOPTVAL_REPO_RETENTION_ARCHIVE_TYPE_FULL',
@ -88,6 +94,7 @@ sub libcAutoExportTag
'CFGOPTVAL_REPO_TYPE_CIFS',
'CFGOPTVAL_REPO_TYPE_POSIX',
'CFGOPTVAL_REPO_TYPE_S3',
'CFGOPTVAL_SORT_NONE',
'CFGOPTVAL_SORT_ASC',
'CFGOPTVAL_SORT_DESC',
'CFGOPTVAL_RESTORE_TARGET_ACTION_PAUSE',
@ -249,6 +256,7 @@ sub libcAutoExportTag
'CFGOPT_PROCESS_MAX',
'CFGOPT_PROTOCOL_TIMEOUT',
'CFGOPT_RECOVERY_OPTION',
'CFGOPT_RECURSE',
'CFGOPT_REPO_CIPHER_PASS',
'CFGOPT_REPO_CIPHER_TYPE',
'CFGOPT_REPO_HARDLINK',

View File

@ -294,7 +294,7 @@ command/stanza/delete.o: command/stanza/delete.c build.auto.h command/backup/com
command/stanza/upgrade.o: command/stanza/upgrade.c build.auto.h command/control/common.h command/stanza/common.h command/stanza/upgrade.h common/assert.h common/crypto/common.h common/debug.h common/error.auto.h common/error.h common/ini.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h info/info.h info/infoArchive.h info/infoBackup.h info/infoPg.h postgres/interface.h postgres/version.h protocol/client.h protocol/command.h protocol/helper.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/stanza/upgrade.c -o command/stanza/upgrade.o
command/storage/list.o: command/storage/list.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/handleWrite.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h
command/storage/list.o: command/storage/list.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/handleWrite.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/storage/list.c -o command/storage/list.o
common/compress/gzip/common.o: common/compress/gzip/common.c build.auto.h common/assert.h common/compress/gzip/common.h common/debug.h common/error.auto.h common/error.h common/logLevel.h common/memContext.h common/stackTrace.h common/type/convert.h

View File

@ -10,40 +10,155 @@ Storage List Command
#include "common/memContext.h"
#include "common/log.h"
#include "common/memContext.h"
#include "common/type/json.h"
#include "common/type/string.h"
#include "config/config.h"
#include "storage/helper.h"
/***********************************************************************************************************************************
Render storage list as a string
Render storage list
***********************************************************************************************************************************/
static String *
storageListRender(void)
typedef struct StorageListRenderCallbackData
{
FUNCTION_LOG_VOID(logLevelDebug);
IoWrite *write; // Where to write output
bool json; // Is this json output?
bool first; // Is this the first item?
} StorageListRenderCallbackData;
// Get the path if passed
void
storageListRenderCallback(void *data, const StorageInfo *info)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(VOID, data);
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
FUNCTION_TEST_END();
ASSERT(data != NULL);
ASSERT(info != NULL);
StorageListRenderCallbackData *listData = (StorageListRenderCallbackData *)data;
// Skip . path when output is text
if (info->type == storageTypePath && strEq(info->name, DOT_STR) && !listData->json)
{
FUNCTION_TEST_RETURN_VOID();
return;
}
// Add seperator character
if (!listData->first)
{
if (listData->json)
ioWrite(listData->write, COMMA_BUF);
else
ioWrite(listData->write, LF_BUF);
}
else
listData->first = false;
// Render in json
if (listData->json)
{
ioWriteStr(listData->write, jsonFromStr(info->name));
ioWrite(listData->write, BUFSTRDEF(":{\"type\":\""));
switch (info->type)
{
case storageTypeFile:
{
ioWrite(listData->write, BUFSTRDEF("file\""));
break;
}
case storageTypeLink:
{
ioWrite(listData->write, BUFSTRDEF("link\""));
break;
}
case storageTypePath:
{
ioWrite(listData->write, BUFSTRDEF("path\""));
break;
}
case storageTypeSpecial:
{
ioWrite(listData->write, BUFSTRDEF("special\""));
break;
}
}
if (info->type == storageTypeFile)
ioWriteStr(listData->write, strNewFmt(",\"size\":%" PRIu64, info->size));
if (info->type == storageTypeLink)
ioWriteStr(listData->write, strNewFmt(",\"destination\":%s", strPtr(jsonFromStr(info->linkDestination))));
ioWrite(listData->write, BRACER_BUF);
}
// Render in text
else
{
ioWrite(listData->write, BUFSTR(info->name));
}
FUNCTION_TEST_RETURN_VOID();
}
static void
storageListRender(IoWrite *write)
{
FUNCTION_LOG_BEGIN(logLevelDebug)
FUNCTION_LOG_PARAM(IO_WRITE, write);
FUNCTION_LOG_END();
// Get sort order
SortOrder sortOrder = sortOrderAsc;
if (strEqZ(cfgOptionStr(cfgOptSort), "desc"))
sortOrder = sortOrderDesc;
else if (!strEqZ(cfgOptionStr(cfgOptSort), "asc"))
{
ASSERT(strEqZ(cfgOptionStr(cfgOptSort), "none"));
sortOrder = sortOrderNone;
}
// Get path
const String *path = NULL;
if (strLstSize(cfgCommandParam()) == 1)
path = strLstGet(cfgCommandParam(), 0);
else if (strLstSize(cfgCommandParam()) > 1)
THROW(ParamInvalidError, "only one path may be specified");
// Output the list
String *result = strNew("");
StringList *list = storageListP(storageRepo(), path, .nullOnMissing = true, .expression = cfgOptionStr(cfgOptFilter));
// Get output
bool json = strEqZ(cfgOptionStr(cfgOptOutput), "json") ? true : false;
if (list != NULL)
// Render the info list
StorageListRenderCallbackData data =
{
strLstSort(list, strEqZ(cfgOptionStr(cfgOptSort), "asc") ? sortOrderAsc : sortOrderDesc);
.write = write,
.json = json,
.first = true,
};
for (unsigned int listIdx = 0; listIdx < strLstSize(list); listIdx++)
{
strCat(result, strPtr(strLstGet(list, listIdx)));
strCat(result, "\n");
}
}
ioWriteOpen(data.write);
FUNCTION_LOG_RETURN(STRING, result);
if (data.json)
ioWrite(data.write, BRACEL_BUF);
storageInfoListP(
storageRepo(), path, storageListRenderCallback, &data, .sortOrder = sortOrder, .expression = cfgOptionStr(cfgOptFilter),
.recurse = cfgOptionBool(cfgOptRecurse));
if (data.json)
ioWrite(data.write, BRACER_BUF);
ioWriteClose(data.write);
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
@ -56,7 +171,8 @@ cmdStorageList(void)
MEM_CONTEXT_TEMP_BEGIN()
{
ioHandleWriteOneStr(STDOUT_FILENO, storageListRender());
storageListRender(ioHandleWriteNew(STRDEF("stdout"), STDOUT_FILENO));
ioHandleWriteOneStr(STDOUT_FILENO, LF_STR);
}
MEM_CONTEXT_TEMP_END();

View File

@ -425,6 +425,7 @@ STRING_EXTERN(CFGOPT_PROCESS_STR, CFGOPT_PROCE
STRING_EXTERN(CFGOPT_PROCESS_MAX_STR, CFGOPT_PROCESS_MAX);
STRING_EXTERN(CFGOPT_PROTOCOL_TIMEOUT_STR, CFGOPT_PROTOCOL_TIMEOUT);
STRING_EXTERN(CFGOPT_RECOVERY_OPTION_STR, CFGOPT_RECOVERY_OPTION);
STRING_EXTERN(CFGOPT_RECURSE_STR, CFGOPT_RECURSE);
STRING_EXTERN(CFGOPT_REPO1_CIPHER_PASS_STR, CFGOPT_REPO1_CIPHER_PASS);
STRING_EXTERN(CFGOPT_REPO1_CIPHER_TYPE_STR, CFGOPT_REPO1_CIPHER_TYPE);
STRING_EXTERN(CFGOPT_REPO1_HARDLINK_STR, CFGOPT_REPO1_HARDLINK);
@ -1459,6 +1460,14 @@ static ConfigOptionData configOptionData[CFG_OPTION_TOTAL] = CONFIG_OPTION_LIST
CONFIG_OPTION_DEFINE_ID(cfgDefOptRecoveryOption)
)
//------------------------------------------------------------------------------------------------------------------------------
CONFIG_OPTION
(
CONFIG_OPTION_NAME(CFGOPT_RECURSE)
CONFIG_OPTION_INDEX(0)
CONFIG_OPTION_DEFINE_ID(cfgDefOptRecurse)
)
//------------------------------------------------------------------------------------------------------------------------------
CONFIG_OPTION
(

View File

@ -299,6 +299,8 @@ Option constants
STRING_DECLARE(CFGOPT_PROTOCOL_TIMEOUT_STR);
#define CFGOPT_RECOVERY_OPTION "recovery-option"
STRING_DECLARE(CFGOPT_RECOVERY_OPTION_STR);
#define CFGOPT_RECURSE "recurse"
STRING_DECLARE(CFGOPT_RECURSE_STR);
#define CFGOPT_REPO1_CIPHER_PASS "repo1-cipher-pass"
STRING_DECLARE(CFGOPT_REPO1_CIPHER_PASS_STR);
#define CFGOPT_REPO1_CIPHER_TYPE "repo1-cipher-type"
@ -388,7 +390,7 @@ Option constants
#define CFGOPT_TYPE "type"
STRING_DECLARE(CFGOPT_TYPE_STR);
#define CFG_OPTION_TOTAL 167
#define CFG_OPTION_TOTAL 168
/***********************************************************************************************************************************
Command enum
@ -545,6 +547,7 @@ typedef enum
cfgOptProcessMax,
cfgOptProtocolTimeout,
cfgOptRecoveryOption,
cfgOptRecurse,
cfgOptRepoCipherPass,
cfgOptRepoCipherType,
cfgOptRepoHardlink,

View File

@ -1901,6 +1901,7 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST
CFGDEFDATA_OPTION_COMMAND_LIST
(
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdInfo)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdLs)
)
CFGDEFDATA_OPTION_OPTIONAL_LIST
@ -1926,6 +1927,28 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST
"* json - Exhaustive machine-readable backup information in JSON format."
)
)
CFGDEFDATA_OPTION_OPTIONAL_COMMAND_OVERRIDE
(
CFGDEFDATA_OPTION_OPTIONAL_COMMAND(cfgDefCmdLs)
CFGDEFDATA_OPTION_OPTIONAL_ALLOW_LIST
(
"text",
"json"
)
CFGDEFDATA_OPTION_OPTIONAL_DEFAULT("text")
CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY("Output format.")
CFGDEFDATA_OPTION_OPTIONAL_HELP_DESCRIPTION
(
"The following output types are supported:\n"
"\n"
"* text - Simple list with one file/link/path name on each line.\n"
"* json - Detailed file/link/path information in JSON format."
)
)
)
)
@ -2626,6 +2649,40 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST
)
)
// -----------------------------------------------------------------------------------------------------------------------------
CFGDEFDATA_OPTION
(
CFGDEFDATA_OPTION_NAME("recurse")
CFGDEFDATA_OPTION_REQUIRED(true)
CFGDEFDATA_OPTION_SECTION(cfgDefSectionCommandLine)
CFGDEFDATA_OPTION_TYPE(cfgDefOptTypeBoolean)
CFGDEFDATA_OPTION_INTERNAL(false)
CFGDEFDATA_OPTION_INDEX_TOTAL(1)
CFGDEFDATA_OPTION_SECURE(false)
CFGDEFDATA_OPTION_COMMAND_LIST
(
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdLs)
)
CFGDEFDATA_OPTION_OPTIONAL_LIST
(
CFGDEFDATA_OPTION_OPTIONAL_DEFAULT("0")
CFGDEFDATA_OPTION_OPTIONAL_COMMAND_OVERRIDE
(
CFGDEFDATA_OPTION_OPTIONAL_COMMAND(cfgDefCmdLs)
CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY("Include all subpaths in output.")
CFGDEFDATA_OPTION_OPTIONAL_HELP_DESCRIPTION
(
"All subpaths and their files will be included in the output."
)
)
)
)
// -----------------------------------------------------------------------------------------------------------------------------
CFGDEFDATA_OPTION
(
@ -4079,6 +4136,7 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST
(
CFGDEFDATA_OPTION_OPTIONAL_ALLOW_LIST
(
"none",
"asc",
"desc"
)
@ -4089,13 +4147,14 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST
(
CFGDEFDATA_OPTION_OPTIONAL_COMMAND(cfgDefCmdLs)
CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY("Sort output ascending/descending.")
CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY("Sort output ascending, descending, or none.")
CFGDEFDATA_OPTION_OPTIONAL_HELP_DESCRIPTION
(
"The following sort types are supported:\n"
"\n"
"* asc - sort ascending.\n"
"* desc - sort descending."
"* desc - sort descending.\n"
"* none - no sorting."
)
)
)

View File

@ -105,6 +105,7 @@ typedef enum
cfgDefOptProcessMax,
cfgDefOptProtocolTimeout,
cfgDefOptRecoveryOption,
cfgDefOptRecurse,
cfgDefOptRepoCipherPass,
cfgDefOptRepoCipherType,
cfgDefOptRepoHardlink,

View File

@ -1638,6 +1638,13 @@ static const struct option optionList[] =
.val = PARSE_OPTION_FLAG | PARSE_RESET_FLAG | cfgOptRecoveryOption,
},
// recurse option
// -----------------------------------------------------------------------------------------------------------------------------
{
.name = CFGOPT_RECURSE,
.val = PARSE_OPTION_FLAG | cfgOptRecurse,
},
// repo-cipher-pass option and deprecations
// -----------------------------------------------------------------------------------------------------------------------------
{
@ -2394,6 +2401,7 @@ static const ConfigOption optionResolveOrder[] =
cfgOptProcess,
cfgOptProcessMax,
cfgOptProtocolTimeout,
cfgOptRecurse,
cfgOptRepoCipherType,
cfgOptRepoHardlink,
cfgOptRepoHost,

View File

@ -7822,6 +7822,9 @@ static const EmbeddedModule embeddedModule[] =
"CFGOPTVAL_INFO_OUTPUT_TEXT => 'text',\n"
"CFGOPTVAL_INFO_OUTPUT_JSON => 'json',\n"
"\n"
"CFGOPTVAL_LS_OUTPUT_TEXT => 'text',\n"
"CFGOPTVAL_LS_OUTPUT_JSON => 'json',\n"
"\n"
"CFGOPTVAL_REPO_CIPHER_TYPE_NONE => 'none',\n"
"CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC => 'aes-256-cbc',\n"
"\n"
@ -7833,6 +7836,7 @@ static const EmbeddedModule embeddedModule[] =
"CFGOPTVAL_REPO_TYPE_POSIX => 'posix',\n"
"CFGOPTVAL_REPO_TYPE_S3 => 's3',\n"
"\n"
"CFGOPTVAL_SORT_NONE => 'none',\n"
"CFGOPTVAL_SORT_ASC => 'asc',\n"
"CFGOPTVAL_SORT_DESC => 'desc',\n"
"\n"
@ -7887,6 +7891,8 @@ static const EmbeddedModule embeddedModule[] =
"[\n"
"'CFGOPTVAL_INFO_OUTPUT_TEXT',\n"
"'CFGOPTVAL_INFO_OUTPUT_JSON',\n"
"'CFGOPTVAL_LS_OUTPUT_TEXT',\n"
"'CFGOPTVAL_LS_OUTPUT_JSON',\n"
"'CFGOPTVAL_REPO_CIPHER_TYPE_NONE',\n"
"'CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC',\n"
"'CFGOPTVAL_REPO_RETENTION_ARCHIVE_TYPE_FULL',\n"
@ -7895,6 +7901,7 @@ static const EmbeddedModule embeddedModule[] =
"'CFGOPTVAL_REPO_TYPE_CIFS',\n"
"'CFGOPTVAL_REPO_TYPE_POSIX',\n"
"'CFGOPTVAL_REPO_TYPE_S3',\n"
"'CFGOPTVAL_SORT_NONE',\n"
"'CFGOPTVAL_SORT_ASC',\n"
"'CFGOPTVAL_SORT_DESC',\n"
"'CFGOPTVAL_RESTORE_TARGET_ACTION_PAUSE',\n"
@ -8056,6 +8063,7 @@ static const EmbeddedModule embeddedModule[] =
"'CFGOPT_PROCESS_MAX',\n"
"'CFGOPT_PROTOCOL_TIMEOUT',\n"
"'CFGOPT_RECOVERY_OPTION',\n"
"'CFGOPT_RECURSE',\n"
"'CFGOPT_REPO_CIPHER_PASS',\n"
"'CFGOPT_REPO_CIPHER_TYPE',\n"
"'CFGOPT_REPO_HARDLINK',\n"

View File

@ -1,6 +1,7 @@
/***********************************************************************************************************************************
Test Storage Commands
***********************************************************************************************************************************/
#include "common/io/bufferWrite.h"
#include "storage/posix/storage.h"
#include "common/harnessConfig.h"
@ -23,33 +24,116 @@ testRun(void)
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAdd(argList, strNewFmt("--repo-path=%s/repo", testPath()));
strLstAddZ(argList, "--output=text");
strLstAddZ(argList, "--sort=none");
strLstAddZ(argList, "ls");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR(strPtr(storageListRender()), "", "missing directory");
// Missing directory
// -------------------------------------------------------------------------------------------------------------------------
Buffer *output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "missing directory (text)");
TEST_RESULT_STR(strNewBuf(output), "", " check output");
storagePathCreateNP(storageTest, strNew("repo"));
TEST_RESULT_STR(strPtr(storageListRender()), "", "empty directory");
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("json"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "missing directory (json)");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "{}", " check output");
// Empty directory
// -------------------------------------------------------------------------------------------------------------------------
storagePathCreateP(storageTest, strNew("repo"), .mode = 0700);
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "empty directory (text)");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "", " check output");
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("json"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "empty directory (json)");
TEST_RESULT_STR_Z(
strNewBuf(output),
"{"
"\".\":{\"type\":\"path\"}"
"}",
" check output");
// Add path and file
// -------------------------------------------------------------------------------------------------------------------------
cfgOptionSet(cfgOptSort, cfgSourceParam, VARSTRDEF("asc"));
storagePathCreateNP(storageTest, strNew("repo/bbb"));
storagePutNP(storageNewWriteNP(storageTest, strNew("repo/aaa")), NULL);
TEST_RESULT_STR(strPtr(storageListRender()), "aaa\nbbb\n", "list files");
ASSERT(system(strPtr(strNewFmt("sudo chown :77777 %s/repo/bbb", testPath()))) == 0);
storagePutNP(storageNewWriteNP(storageTest, strNew("repo/aaa")), BUFSTRDEF("TESTDATA"));
ASSERT(system(strPtr(strNewFmt("sudo chown 77777 %s/repo/aaa", testPath()))) == 0);
ASSERT(system(strPtr(strNewFmt("sudo chmod 000 %s/repo/aaa", testPath()))) == 0);
storagePutNP(storageNewWriteNP(storageTest, strNew("repo/bbb/ccc")), BUFSTRDEF("TESTDATA2"));
ASSERT(system(strPtr(strNewFmt("ln -s ../bbb %s/repo/link", testPath()))) == 0);
ASSERT(system(strPtr(strNewFmt("mkfifo %s/repo/pipe", testPath()))) == 0);
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "path and file (text)");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "aaa\nbbb\nlink\npipe", " check output");
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("json"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "path and file (text)");
TEST_RESULT_STR_Z(
strNewBuf(output),
"{"
"\".\":{\"type\":\"path\"},"
"\"aaa\":{\"type\":\"file\",\"size\":8},"
"\"bbb\":{\"type\":\"path\"},"
"\"link\":{\"type\":\"link\",\"destination\":\"../bbb\"},"
"\"pipe\":{\"type\":\"special\"}"
"}",
" check output");
// Reverse sort
// -------------------------------------------------------------------------------------------------------------------------
cfgOptionSet(cfgOptSort, cfgSourceParam, VARSTRDEF("desc"));
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "path and file (text)");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "pipe\nlink\nbbb\naaa", " check output");
// Recurse
// -------------------------------------------------------------------------------------------------------------------------
cfgOptionValidSet(cfgOptRecurse, true);
cfgOptionSet(cfgOptRecurse, cfgSourceParam, VARBOOL(true));
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "filter");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "pipe\nlink\nbbb/ccc\nbbb\naaa", " check output");
// Filter
// -------------------------------------------------------------------------------------------------------------------------
cfgOptionValidSet(cfgOptFilter, true);
cfgOptionSet(cfgOptFilter, cfgSourceParam, VARSTRDEF("^aaa$"));
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "filter");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "aaa", " check output");
// Subdirectory
// -------------------------------------------------------------------------------------------------------------------------
StringList *argListTmp = strLstDup(argList);
strLstAddZ(argListTmp, "--filter=^aaa$");
harnessCfgLoad(strLstSize(argListTmp), strLstPtr(argListTmp));
TEST_RESULT_STR(strPtr(storageListRender()), "aaa\n", "list files with filter");
argListTmp = strLstDup(argList);
strLstAddZ(argListTmp, "--sort=desc");
harnessCfgLoad(strLstSize(argListTmp), strLstPtr(argListTmp));
TEST_RESULT_STR(strPtr(storageListRender()), "bbb\naaa\n", "list files with filter and sort");
storagePutNP(storageNewWriteNP(storageTest, strNew("repo/bbb/ccc")), NULL);
argListTmp = strLstDup(argList);
strLstAddZ(argListTmp, "bbb");
harnessCfgLoad(strLstSize(argListTmp), strLstPtr(argListTmp));
TEST_RESULT_STR(strPtr(storageListRender()), "ccc\n", "list files");
output = bufNew(0);
cfgOptionSet(cfgOptOutput, cfgSourceParam, VARSTRDEF("text"));
TEST_RESULT_VOID(storageListRender(ioBufferWriteNew(output)), "subdirectory");
TEST_RESULT_STR(strPtr(strNewBuf(output)), "ccc", " check output");
// -------------------------------------------------------------------------------------------------------------------------
// Redirect stdout to a file
@ -65,6 +149,14 @@ testRun(void)
dup2(stdoutSave, STDOUT_FILENO);
TEST_RESULT_STR(strPtr(strNewBuf(storageGetNP(storageNewReadNP(storageTest, stdoutFile)))), "ccc\n", " check text");
// Too many paths
// -------------------------------------------------------------------------------------------------------------------------
strLstAddZ(argListTmp, "ccc");
harnessCfgLoad(strLstSize(argListTmp), strLstPtr(argListTmp));
output = bufNew(0);
TEST_ERROR(storageListRender(ioBufferWriteNew(output)), ParamInvalidError, "only one path may be specified");
}
FUNCTION_HARNESS_RESULT_VOID();