You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-07-05 00:28:52 +02:00
Create snapshot when listing contents of a path.
Previously a callback was used to list path contents and if no sort was specified then a snapshot was not required. When deleting files from the path some filesystems could omit files that still existed, which meant the path could not be removed. Filter . out of lists in the Posix driver since this special entry was only used by test code (and filtered everywhere in the core code). Also remove callbacks from the storage interface and replace with an iterator that should be easier to use and guarantees efficient use of the snapshots.
This commit is contained in:
@ -32,6 +32,19 @@
|
|||||||
<p><proper>OpenSSL 3</proper> support.</p>
|
<p><proper>OpenSSL 3</proper> support.</p>
|
||||||
</release-item>
|
</release-item>
|
||||||
|
|
||||||
|
<release-item>
|
||||||
|
<github-issue id="1754"/>
|
||||||
|
<github-pull-request id="1805"/>
|
||||||
|
|
||||||
|
<release-item-contributor-list>
|
||||||
|
<release-item-contributor id="david.steele"/>
|
||||||
|
<release-item-reviewer id="john.morris"/>
|
||||||
|
<release-item-reviewer id="stephen.frost"/>
|
||||||
|
</release-item-contributor-list>
|
||||||
|
|
||||||
|
<p>Create snapshot when listing contents of a path.</p>
|
||||||
|
</release-item>
|
||||||
|
|
||||||
<release-item>
|
<release-item>
|
||||||
<github-pull-request id="1758"/>
|
<github-pull-request id="1758"/>
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ SRCS_BUILD = \
|
|||||||
common/regExp.c \
|
common/regExp.c \
|
||||||
common/stackTrace.c \
|
common/stackTrace.c \
|
||||||
common/time.c \
|
common/time.c \
|
||||||
|
common/type/blob.c \
|
||||||
common/type/buffer.c \
|
common/type/buffer.c \
|
||||||
common/type/convert.c \
|
common/type/convert.c \
|
||||||
common/type/keyValue.c \
|
common/type/keyValue.c \
|
||||||
@ -44,6 +45,8 @@ SRCS_BUILD = \
|
|||||||
storage/posix/read.c \
|
storage/posix/read.c \
|
||||||
storage/posix/storage.c \
|
storage/posix/storage.c \
|
||||||
storage/posix/write.c \
|
storage/posix/write.c \
|
||||||
|
storage/iterator.c \
|
||||||
|
storage/list.c \
|
||||||
storage/read.c \
|
storage/read.c \
|
||||||
storage/storage.c \
|
storage/storage.c \
|
||||||
storage/write.c
|
storage/write.c
|
||||||
|
@ -489,77 +489,75 @@ backupBuildIncr(const InfoBackup *infoBackup, Manifest *manifest, Manifest *mani
|
|||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Check for a backup that can be resumed and merge into the manifest if found
|
Check for a backup that can be resumed and merge into the manifest if found
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
typedef struct BackupResumeData
|
// Helper to clean invalid paths/files/links out of the resumable backup path
|
||||||
|
static void
|
||||||
|
backupResumeClean(
|
||||||
|
StorageIterator *const storageItr, Manifest *const manifest, const Manifest *const manifestResume,
|
||||||
|
const CompressType compressType, const bool delta, const String *const backupParentPath, const String *const manifestParentName)
|
||||||
{
|
{
|
||||||
Manifest *manifest; // New manifest
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
const Manifest *manifestResume; // Resumed manifest
|
FUNCTION_LOG_PARAM(STORAGE_ITERATOR, storageItr); // Storage info
|
||||||
const CompressType compressType; // Backup compression type
|
FUNCTION_LOG_PARAM(MANIFEST, manifest); // New manifest
|
||||||
const bool delta; // Is this a delta backup?
|
FUNCTION_LOG_PARAM(MANIFEST, manifestResume); // Resumed manifest
|
||||||
const String *backupPath; // Path to the current level of the backup being cleaned
|
FUNCTION_LOG_PARAM(ENUM, compressType); // Backup compression type
|
||||||
const String *manifestParentName; // Parent manifest name used to construct manifest name
|
FUNCTION_LOG_PARAM(BOOL, delta); // Is this a delta backup?
|
||||||
} BackupResumeData;
|
FUNCTION_LOG_PARAM(STRING, backupParentPath); // Path to the current level of the backup being cleaned
|
||||||
|
FUNCTION_LOG_PARAM(STRING, manifestParentName); // Parent manifest name used to construct manifest name
|
||||||
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
// Callback to clean invalid paths/files/links out of the resumable backup path
|
ASSERT(storageItr != NULL);
|
||||||
void backupResumeCallback(void *data, const StorageInfo *info)
|
ASSERT(manifest != NULL);
|
||||||
{
|
ASSERT(manifestResume != NULL);
|
||||||
FUNCTION_TEST_BEGIN();
|
ASSERT(backupParentPath != NULL);
|
||||||
FUNCTION_TEST_PARAM_P(VOID, data);
|
|
||||||
FUNCTION_TEST_PARAM(STORAGE_INFO, *storageInfo);
|
|
||||||
FUNCTION_TEST_END();
|
|
||||||
|
|
||||||
ASSERT(data != NULL);
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
ASSERT(info != NULL);
|
{
|
||||||
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageItrNext(storageItr);
|
||||||
|
|
||||||
BackupResumeData *resumeData = data;
|
// Skip backup.manifest.copy -- it must be preserved to allow resume again if this process throws an error before
|
||||||
|
// writing the manifest for the first time
|
||||||
// Skip all . paths because they have already been handled on the previous level of recursion
|
if (manifestParentName == NULL && strEqZ(info.name, BACKUP_MANIFEST_FILE INFO_COPY_EXT))
|
||||||
if (strEq(info->name, DOT_STR))
|
continue;
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
// Skip backup.manifest.copy -- it must be preserved to allow resume again if this process throws an error before writing the
|
|
||||||
// manifest for the first time
|
|
||||||
if (resumeData->manifestParentName == NULL && strEqZ(info->name, BACKUP_MANIFEST_FILE INFO_COPY_EXT))
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
// Build the name used to lookup files in the manifest
|
// Build the name used to lookup files in the manifest
|
||||||
const String *manifestName = resumeData->manifestParentName != NULL ?
|
const String *manifestName = manifestParentName != NULL ?
|
||||||
strNewFmt("%s/%s", strZ(resumeData->manifestParentName), strZ(info->name)) : info->name;
|
strNewFmt("%s/%s", strZ(manifestParentName), strZ(info.name)) : info.name;
|
||||||
|
|
||||||
// Build the backup path used to remove files/links/paths that are invalid
|
// Build the backup path used to remove files/links/paths that are invalid
|
||||||
const String *backupPath = strNewFmt("%s/%s", strZ(resumeData->backupPath), strZ(info->name));
|
const String *const backupPath = strNewFmt("%s/%s", strZ(backupParentPath), strZ(info.name));
|
||||||
|
|
||||||
// Process file types
|
// Process file types
|
||||||
switch (info->type)
|
switch (info.type)
|
||||||
{
|
{
|
||||||
// Check paths
|
// Check paths
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
case storageTypePath:
|
case storageTypePath:
|
||||||
{
|
{
|
||||||
// If the path was not found in the new manifest then remove it
|
// If the path was not found in the new manifest then remove it
|
||||||
if (manifestPathFindDefault(resumeData->manifest, manifestName, NULL) == NULL)
|
if (manifestPathFindDefault(manifest, manifestName, NULL) == NULL)
|
||||||
{
|
{
|
||||||
LOG_DETAIL_FMT("remove path '%s' from resumed backup", strZ(storagePathP(storageRepo(), backupPath)));
|
LOG_DETAIL_FMT("remove path '%s' from resumed backup", strZ(storagePathP(storageRepo(), backupPath)));
|
||||||
storagePathRemoveP(storageRepoWrite(), backupPath, .recurse = true);
|
storagePathRemoveP(storageRepoWrite(), backupPath, .recurse = true);
|
||||||
}
|
}
|
||||||
// Else recurse into the path
|
// Else recurse into the path
|
||||||
|
else
|
||||||
{
|
{
|
||||||
BackupResumeData resumeDataSub = *resumeData;
|
backupResumeClean(
|
||||||
resumeDataSub.manifestParentName = manifestName;
|
storageNewItrP(storageRepo(), backupPath, .sortOrder = sortOrderAsc), manifest, manifestResume,
|
||||||
resumeDataSub.backupPath = backupPath;
|
compressType, delta, backupPath, manifestName);
|
||||||
|
|
||||||
storageInfoListP(
|
|
||||||
storageRepo(), resumeDataSub.backupPath, backupResumeCallback, &resumeDataSub, .sortOrder = sortOrderAsc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check files
|
// Check files
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
{
|
{
|
||||||
// If the file is compressed then strip off the extension before doing the lookup
|
// If the file is compressed then strip off the extension before doing the lookup
|
||||||
CompressType fileCompressType = compressTypeFromName(manifestName);
|
const CompressType fileCompressType = compressTypeFromName(manifestName);
|
||||||
|
|
||||||
if (fileCompressType != compressTypeNone)
|
if (fileCompressType != compressTypeNone)
|
||||||
manifestName = compressExtStrip(manifestName, fileCompressType);
|
manifestName = compressExtStrip(manifestName, fileCompressType);
|
||||||
@ -567,21 +565,21 @@ void backupResumeCallback(void *data, const StorageInfo *info)
|
|||||||
// Check if the file can be resumed or must be removed
|
// Check if the file can be resumed or must be removed
|
||||||
const char *removeReason = NULL;
|
const char *removeReason = NULL;
|
||||||
|
|
||||||
if (fileCompressType != resumeData->compressType)
|
if (fileCompressType != compressType)
|
||||||
removeReason = "mismatched compression type";
|
removeReason = "mismatched compression type";
|
||||||
else if (!manifestFileExists(resumeData->manifest, manifestName))
|
else if (!manifestFileExists(manifest, manifestName))
|
||||||
removeReason = "missing in manifest";
|
removeReason = "missing in manifest";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const ManifestFile file = manifestFileFind(resumeData->manifest, manifestName);
|
const ManifestFile file = manifestFileFind(manifest, manifestName);
|
||||||
|
|
||||||
if (file.reference != NULL)
|
if (file.reference != NULL)
|
||||||
removeReason = "reference in manifest";
|
removeReason = "reference in manifest";
|
||||||
else if (!manifestFileExists(resumeData->manifestResume, manifestName))
|
else if (!manifestFileExists(manifestResume, manifestName))
|
||||||
removeReason = "missing in resumed manifest";
|
removeReason = "missing in resumed manifest";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const ManifestFile fileResume = manifestFileFind(resumeData->manifestResume, manifestName);
|
const ManifestFile fileResume = manifestFileFind(manifestResume, manifestName);
|
||||||
|
|
||||||
if (fileResume.reference != NULL)
|
if (fileResume.reference != NULL)
|
||||||
removeReason = "reference in resumed manifest";
|
removeReason = "reference in resumed manifest";
|
||||||
@ -589,15 +587,15 @@ void backupResumeCallback(void *data, const StorageInfo *info)
|
|||||||
removeReason = "no checksum in resumed manifest";
|
removeReason = "no checksum in resumed manifest";
|
||||||
else if (file.size != fileResume.size)
|
else if (file.size != fileResume.size)
|
||||||
removeReason = "mismatched size";
|
removeReason = "mismatched size";
|
||||||
else if (!resumeData->delta && file.timestamp != fileResume.timestamp)
|
else if (!delta && file.timestamp != fileResume.timestamp)
|
||||||
removeReason = "mismatched timestamp";
|
removeReason = "mismatched timestamp";
|
||||||
else if (file.size == 0)
|
else if (file.size == 0)
|
||||||
// ??? don't resume zero size files because Perl wouldn't -- this can be removed after the migration)
|
// ??? don't resume zero size files because Perl wouldn't -- can be removed after the migration)
|
||||||
removeReason = "zero size";
|
removeReason = "zero size";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
manifestFileUpdate(
|
manifestFileUpdate(
|
||||||
resumeData->manifest, manifestName, file.size, fileResume.sizeRepo, fileResume.checksumSha1, NULL,
|
manifest, manifestName, file.size, fileResume.sizeRepo, fileResume.checksumSha1, NULL,
|
||||||
fileResume.checksumPage, fileResume.checksumPageError, fileResume.checksumPageErrorList, 0, 0);
|
fileResume.checksumPage, fileResume.checksumPageError, fileResume.checksumPageErrorList, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -607,29 +605,36 @@ void backupResumeCallback(void *data, const StorageInfo *info)
|
|||||||
if (removeReason != NULL)
|
if (removeReason != NULL)
|
||||||
{
|
{
|
||||||
LOG_DETAIL_FMT(
|
LOG_DETAIL_FMT(
|
||||||
"remove file '%s' from resumed backup (%s)", strZ(storagePathP(storageRepo(), backupPath)), removeReason);
|
"remove file '%s' from resumed backup (%s)", strZ(storagePathP(storageRepo(), backupPath)),
|
||||||
|
removeReason);
|
||||||
storageRemoveP(storageRepoWrite(), backupPath);
|
storageRemoveP(storageRepoWrite(), backupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove links. We could check that the link has not changed and preserve it but it doesn't seem worth the extra testing.
|
// Remove links. We could check that the link has not changed and preserve it but it doesn't seem worth the extra
|
||||||
// The link will be recreated during the backup if needed.
|
// testing. The link will be recreated during the backup if needed.
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
case storageTypeLink:
|
case storageTypeLink:
|
||||||
storageRemoveP(storageRepoWrite(), backupPath);
|
storageRemoveP(storageRepoWrite(), backupPath);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Remove special files
|
// Remove special files
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
case storageTypeSpecial:
|
case storageTypeSpecial:
|
||||||
LOG_WARN_FMT("remove special file '%s' from resumed backup", strZ(storagePathP(storageRepo(), backupPath)));
|
LOG_WARN_FMT("remove special file '%s' from resumed backup", strZ(storagePathP(storageRepo(), backupPath)));
|
||||||
storageRemoveP(storageRepoWrite(), backupPath);
|
storageRemoveP(storageRepoWrite(), backupPath);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
||||||
|
MEM_CONTEXT_TEMP_RESET(1000);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to find a resumable backup
|
// Helper to find a resumable backup
|
||||||
@ -789,16 +794,11 @@ backupResume(Manifest *manifest, const String *cipherPassBackup)
|
|||||||
manifestCipherSubPassSet(manifest, manifestCipherSubPass(manifestResume));
|
manifestCipherSubPassSet(manifest, manifestCipherSubPass(manifestResume));
|
||||||
|
|
||||||
// Clean resumed backup
|
// Clean resumed backup
|
||||||
BackupResumeData resumeData =
|
const String *const backupPath = strNewFmt(STORAGE_REPO_BACKUP "/%s", strZ(manifestData(manifest)->backupLabel));
|
||||||
{
|
|
||||||
.manifest = manifest,
|
|
||||||
.manifestResume = manifestResume,
|
|
||||||
.compressType = compressTypeEnum(cfgOptionStrId(cfgOptCompressType)),
|
|
||||||
.delta = cfgOptionBool(cfgOptDelta),
|
|
||||||
.backupPath = strNewFmt(STORAGE_REPO_BACKUP "/%s", strZ(manifestData(manifest)->backupLabel)),
|
|
||||||
};
|
|
||||||
|
|
||||||
storageInfoListP(storageRepo(), resumeData.backupPath, backupResumeCallback, &resumeData, .sortOrder = sortOrderAsc);
|
backupResumeClean(
|
||||||
|
storageNewItrP(storageRepo(), backupPath, .sortOrder = sortOrderAsc), manifest, manifestResume,
|
||||||
|
compressTypeEnum(cfgOptionStrId(cfgOptCompressType)), cfgOptionBool(cfgOptDelta), backupPath, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
@ -19,77 +19,59 @@ Repository List Command
|
|||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Render storage list
|
Render storage list
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
typedef struct StorageListRenderCallbackData
|
static void
|
||||||
{
|
storageListRenderInfo(const StorageInfo *const info, IoWrite *const write, const bool json)
|
||||||
IoWrite *write; // Where to write output
|
|
||||||
bool json; // Is this json output?
|
|
||||||
bool first; // Is this the first item?
|
|
||||||
} StorageListRenderCallbackData;
|
|
||||||
|
|
||||||
void
|
|
||||||
storageListRenderCallback(void *data, const StorageInfo *info)
|
|
||||||
{
|
{
|
||||||
FUNCTION_TEST_BEGIN();
|
FUNCTION_TEST_BEGIN();
|
||||||
FUNCTION_TEST_PARAM_P(VOID, data);
|
|
||||||
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
|
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
|
||||||
|
FUNCTION_TEST_PARAM(IO_WRITE, write);
|
||||||
|
FUNCTION_TEST_PARAM(BOOL, json);
|
||||||
FUNCTION_TEST_END();
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
ASSERT(data != NULL);
|
|
||||||
ASSERT(info != NULL);
|
ASSERT(info != NULL);
|
||||||
|
ASSERT(write != NULL);
|
||||||
StorageListRenderCallbackData *listData = (StorageListRenderCallbackData *)data;
|
|
||||||
|
|
||||||
// Skip . path if it is not first when json output
|
|
||||||
if (info->type == storageTypePath && strEq(info->name, DOT_STR) && (!listData->first || !listData->json))
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
// Add separator character
|
|
||||||
if (!listData->first && listData->json)
|
|
||||||
ioWrite(listData->write, COMMA_BUF);
|
|
||||||
else
|
|
||||||
listData->first = false;
|
|
||||||
|
|
||||||
// Render in json
|
// Render in json
|
||||||
if (listData->json)
|
if (json)
|
||||||
{
|
{
|
||||||
ioWriteStr(listData->write, jsonFromVar(VARSTR(info->name)));
|
ioWriteStr(write, jsonFromVar(VARSTR(info->name)));
|
||||||
ioWrite(listData->write, BUFSTRDEF(":{\"type\":\""));
|
ioWrite(write, BUFSTRDEF(":{\"type\":\""));
|
||||||
|
|
||||||
switch (info->type)
|
switch (info->type)
|
||||||
{
|
{
|
||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
ioWrite(listData->write, BUFSTRDEF("file\""));
|
ioWrite(write, BUFSTRDEF("file\""));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case storageTypeLink:
|
case storageTypeLink:
|
||||||
ioWrite(listData->write, BUFSTRDEF("link\""));
|
ioWrite(write, BUFSTRDEF("link\""));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case storageTypePath:
|
case storageTypePath:
|
||||||
ioWrite(listData->write, BUFSTRDEF("path\""));
|
ioWrite(write, BUFSTRDEF("path\""));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case storageTypeSpecial:
|
case storageTypeSpecial:
|
||||||
ioWrite(listData->write, BUFSTRDEF("special\""));
|
ioWrite(write, BUFSTRDEF("special\""));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->type == storageTypeFile)
|
if (info->type == storageTypeFile)
|
||||||
{
|
{
|
||||||
ioWriteStr(listData->write, strNewFmt(",\"size\":%" PRIu64, info->size));
|
ioWriteStr(write, strNewFmt(",\"size\":%" PRIu64, info->size));
|
||||||
ioWriteStr(listData->write, strNewFmt(",\"time\":%" PRId64, (int64_t)info->timeModified));
|
ioWriteStr(write, strNewFmt(",\"time\":%" PRId64, (int64_t)info->timeModified));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->type == storageTypeLink)
|
if (info->type == storageTypeLink)
|
||||||
ioWriteStr(listData->write, strNewFmt(",\"destination\":%s", strZ(jsonFromVar(VARSTR(info->linkDestination)))));
|
ioWriteStr(write, strNewFmt(",\"destination\":%s", strZ(jsonFromVar(VARSTR(info->linkDestination)))));
|
||||||
|
|
||||||
ioWrite(listData->write, BRACER_BUF);
|
ioWrite(write, BRACER_BUF);
|
||||||
}
|
}
|
||||||
// Render in text
|
// Render in text
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ioWrite(listData->write, BUFSTR(info->name));
|
ioWrite(write, BUFSTR(info->name));
|
||||||
ioWrite(listData->write, LF_BUF);
|
ioWrite(write, LF_BUF);
|
||||||
}
|
}
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
@ -133,18 +115,10 @@ storageListRender(IoWrite *write)
|
|||||||
const String *expression = cfgOptionStrNull(cfgOptFilter);
|
const String *expression = cfgOptionStrNull(cfgOptFilter);
|
||||||
RegExp *regExp = expression == NULL ? NULL : regExpNew(expression);
|
RegExp *regExp = expression == NULL ? NULL : regExpNew(expression);
|
||||||
|
|
||||||
// Render the info list
|
ioWriteOpen(write);
|
||||||
StorageListRenderCallbackData data =
|
|
||||||
{
|
|
||||||
.write = write,
|
|
||||||
.json = json,
|
|
||||||
.first = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
ioWriteOpen(data.write);
|
if (json)
|
||||||
|
ioWrite(write, BRACEL_BUF);
|
||||||
if (data.json)
|
|
||||||
ioWrite(data.write, BRACEL_BUF);
|
|
||||||
|
|
||||||
// Check if this is a file
|
// Check if this is a file
|
||||||
StorageInfo info = storageInfoP(storageRepo(), path, .ignoreMissing = true);
|
StorageInfo info = storageInfoP(storageRepo(), path, .ignoreMissing = true);
|
||||||
@ -154,29 +128,46 @@ storageListRender(IoWrite *write)
|
|||||||
if (regExp == NULL || regExpMatch(regExp, storagePathP(storageRepo(), path)))
|
if (regExp == NULL || regExpMatch(regExp, storagePathP(storageRepo(), path)))
|
||||||
{
|
{
|
||||||
info.name = DOT_STR;
|
info.name = DOT_STR;
|
||||||
storageListRenderCallback(&data, &info);
|
storageListRenderInfo(&info, write, json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Else try to list the path
|
// Else try to list the path
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The path will always be reported as existing so we don't get different results from storage that does not support paths
|
// The path will always be reported as existing so we don't get different results from storage that does not support paths
|
||||||
if (data.json && (regExp == NULL || regExpMatch(regExp, DOT_STR)))
|
bool first = true;
|
||||||
storageListRenderCallback(&data, &(StorageInfo){.type = storageTypePath, .name = DOT_STR});
|
|
||||||
|
if (json && (regExp == NULL || regExpMatch(regExp, DOT_STR)))
|
||||||
|
{
|
||||||
|
storageListRenderInfo(&(StorageInfo){.type = storageTypePath, .name = DOT_STR}, write, json);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
// List content of the path
|
// List content of the path
|
||||||
storageInfoListP(
|
StorageIterator *const storageItr = storageNewItrP(
|
||||||
storageRepo(), path, storageListRenderCallback, &data, .sortOrder = sortOrder, .expression = expression,
|
storageRepo(), path, .sortOrder = sortOrder, .expression = expression, .recurse = cfgOptionBool(cfgOptRecurse));
|
||||||
.recurse = cfgOptionBool(cfgOptRecurse));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.json)
|
while (storageItrMore(storageItr))
|
||||||
{
|
{
|
||||||
ioWrite(data.write, BRACER_BUF);
|
const StorageInfo info = storageItrNext(storageItr);
|
||||||
ioWrite(data.write, LF_BUF);
|
|
||||||
|
// Add separator character
|
||||||
|
if (!first && json)
|
||||||
|
ioWrite(write, COMMA_BUF);
|
||||||
|
else
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
storageListRenderInfo(&info, write, json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ioWriteClose(data.write);
|
if (json)
|
||||||
|
{
|
||||||
|
ioWrite(write, BRACER_BUF);
|
||||||
|
ioWrite(write, LF_BUF);
|
||||||
|
}
|
||||||
|
|
||||||
|
ioWriteClose(write);
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN_VOID();
|
FUNCTION_LOG_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
@ -892,27 +892,27 @@ restoreCleanMode(const String *pgPath, mode_t manifestMode, const StorageInfo *i
|
|||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// storageInfoList() callback that cleans the paths
|
// Recurse paths
|
||||||
static void
|
static void
|
||||||
restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
restoreCleanBuildRecurse(StorageIterator *const storageItr, const RestoreCleanCallbackData *const cleanData)
|
||||||
{
|
{
|
||||||
FUNCTION_TEST_BEGIN();
|
FUNCTION_TEST_BEGIN();
|
||||||
FUNCTION_TEST_PARAM_P(VOID, data);
|
FUNCTION_TEST_PARAM(STORAGE_ITERATOR, storageItr);
|
||||||
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
|
FUNCTION_TEST_PARAM_P(VOID, cleanData);
|
||||||
FUNCTION_TEST_END();
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
ASSERT(data != NULL);
|
ASSERT(storageItr != NULL);
|
||||||
ASSERT(info != NULL);
|
ASSERT(cleanData != NULL);
|
||||||
|
|
||||||
RestoreCleanCallbackData *cleanData = (RestoreCleanCallbackData *)data;
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
|
{
|
||||||
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageItrNext(storageItr);
|
||||||
|
|
||||||
// Don't include backup.manifest or recovery.conf (when preserved) in the comparison or empty directory check
|
// Don't include backup.manifest or recovery.conf (when preserved) in the comparison or empty directory check
|
||||||
if (cleanData->basePath && info->type == storageTypeFile && strLstExists(cleanData->fileIgnore, info->name))
|
if (cleanData->basePath && info.type == storageTypeFile && strLstExists(cleanData->fileIgnore, info.name))
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
continue;
|
||||||
|
|
||||||
// Skip all . paths because they have already been cleaned on the previous level of recursion
|
|
||||||
if (strEq(info->name, DOT_STR))
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
// If this is not a delta then error because the directory is expected to be empty. Ignore the . path.
|
// If this is not a delta then error because the directory is expected to be empty. Ignore the . path.
|
||||||
if (!cleanData->delta)
|
if (!cleanData->delta)
|
||||||
@ -925,12 +925,12 @@ restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct the name used to find this file/link/path in the manifest
|
// Construct the name used to find this file/link/path in the manifest
|
||||||
const String *manifestName = strNewFmt("%s/%s", strZ(cleanData->targetName), strZ(info->name));
|
const String *manifestName = strNewFmt("%s/%s", strZ(cleanData->targetName), strZ(info.name));
|
||||||
|
|
||||||
// Construct the path of this file/link/path in the PostgreSQL data directory
|
// Construct the path of this file/link/path in the PostgreSQL data directory
|
||||||
const String *pgPath = strNewFmt("%s/%s", strZ(cleanData->targetPath), strZ(info->name));
|
const String *pgPath = strNewFmt("%s/%s", strZ(cleanData->targetPath), strZ(info.name));
|
||||||
|
|
||||||
switch (info->type)
|
switch (info.type)
|
||||||
{
|
{
|
||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
{
|
{
|
||||||
@ -941,8 +941,8 @@ restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
|||||||
|
|
||||||
restoreCleanOwnership(
|
restoreCleanOwnership(
|
||||||
pgPath, manifestFile.user, cleanData->rootReplaceUser, manifestFile.group, cleanData->rootReplaceGroup,
|
pgPath, manifestFile.user, cleanData->rootReplaceUser, manifestFile.group, cleanData->rootReplaceGroup,
|
||||||
info->userId, info->groupId, false);
|
info.userId, info.groupId, false);
|
||||||
restoreCleanMode(pgPath, manifestFile.mode, info);
|
restoreCleanMode(pgPath, manifestFile.mode, &info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -959,7 +959,7 @@ restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
|||||||
|
|
||||||
if (manifestLink != NULL)
|
if (manifestLink != NULL)
|
||||||
{
|
{
|
||||||
if (!strEq(manifestLink->destination, info->linkDestination))
|
if (!strEq(manifestLink->destination, info.linkDestination))
|
||||||
{
|
{
|
||||||
LOG_DETAIL_FMT("remove link '%s' because destination changed", strZ(pgPath));
|
LOG_DETAIL_FMT("remove link '%s' because destination changed", strZ(pgPath));
|
||||||
storageRemoveP(storageLocalWrite(), pgPath, .errorOnMissing = true);
|
storageRemoveP(storageLocalWrite(), pgPath, .errorOnMissing = true);
|
||||||
@ -967,8 +967,8 @@ restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
restoreCleanOwnership(
|
restoreCleanOwnership(
|
||||||
pgPath, manifestLink->user, cleanData->rootReplaceUser, manifestLink->group, cleanData->rootReplaceGroup,
|
pgPath, manifestLink->user, cleanData->rootReplaceUser, manifestLink->group,
|
||||||
info->userId, info->groupId, false);
|
cleanData->rootReplaceGroup, info.userId, info.groupId, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -988,19 +988,20 @@ restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
|||||||
{
|
{
|
||||||
// Check ownership/permissions
|
// Check ownership/permissions
|
||||||
restoreCleanOwnership(
|
restoreCleanOwnership(
|
||||||
pgPath, manifestPath->user, cleanData->rootReplaceUser, manifestPath->group, cleanData->rootReplaceGroup,
|
pgPath, manifestPath->user, cleanData->rootReplaceUser, manifestPath->group,
|
||||||
info->userId, info->groupId, false);
|
cleanData->rootReplaceGroup, info.userId, info.groupId, false);
|
||||||
restoreCleanMode(pgPath, manifestPath->mode, info);
|
restoreCleanMode(pgPath, manifestPath->mode, &info);
|
||||||
|
|
||||||
// Recurse into the path
|
// Recurse into the path
|
||||||
RestoreCleanCallbackData cleanDataSub = *cleanData;
|
RestoreCleanCallbackData cleanDataSub = *cleanData;
|
||||||
cleanDataSub.targetName = strNewFmt("%s/%s", strZ(cleanData->targetName), strZ(info->name));
|
cleanDataSub.targetName = strNewFmt("%s/%s", strZ(cleanData->targetName), strZ(info.name));
|
||||||
cleanDataSub.targetPath = strNewFmt("%s/%s", strZ(cleanData->targetPath), strZ(info->name));
|
cleanDataSub.targetPath = strNewFmt("%s/%s", strZ(cleanData->targetPath), strZ(info.name));
|
||||||
cleanDataSub.basePath = false;
|
cleanDataSub.basePath = false;
|
||||||
|
|
||||||
storageInfoListP(
|
restoreCleanBuildRecurse(
|
||||||
storageLocalWrite(), cleanDataSub.targetPath, restoreCleanInfoListCallback, &cleanDataSub,
|
storageNewItrP(
|
||||||
.errorOnMissing = true, .sortOrder = sortOrderAsc);
|
storageLocalWrite(), cleanDataSub.targetPath, .errorOnMissing = true, .sortOrder = sortOrderAsc),
|
||||||
|
&cleanDataSub);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1018,6 +1019,12 @@ restoreCleanInfoListCallback(void *data, const StorageInfo *info)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
||||||
|
MEM_CONTEXT_TEMP_RESET(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1121,9 +1128,8 @@ restoreCleanBuild(const Manifest *const manifest, const String *const rootReplac
|
|||||||
{
|
{
|
||||||
if (cleanData->target->file == NULL)
|
if (cleanData->target->file == NULL)
|
||||||
{
|
{
|
||||||
storageInfoListP(
|
restoreCleanBuildRecurse(
|
||||||
storageLocal(), cleanData->targetPath, restoreCleanInfoListCallback, cleanData,
|
storageNewItrP(storageLocal(), cleanData->targetPath, .errorOnMissing = true), cleanData);
|
||||||
.errorOnMissing = true);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1202,9 +1208,10 @@ restoreCleanBuild(const Manifest *const manifest, const String *const rootReplac
|
|||||||
restoreCleanMode(cleanData->targetPath, manifestPath->mode, &info);
|
restoreCleanMode(cleanData->targetPath, manifestPath->mode, &info);
|
||||||
|
|
||||||
// Clean the target
|
// Clean the target
|
||||||
storageInfoListP(
|
restoreCleanBuildRecurse(
|
||||||
storageLocalWrite(), cleanData->targetPath, restoreCleanInfoListCallback, cleanData, .errorOnMissing = true,
|
storageNewItrP(
|
||||||
.sortOrder = sortOrderAsc);
|
storageLocalWrite(), cleanData->targetPath, .errorOnMissing = true, .sortOrder = sortOrderAsc),
|
||||||
|
cleanData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the target does not exist we'll attempt to create it
|
// If the target does not exist we'll attempt to create it
|
||||||
|
88
src/common/type/blob.c
Normal file
88
src/common/type/blob.c
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Blob Handler
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#include "build.auto.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/memContext.h"
|
||||||
|
#include "common/type/blob.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Object type
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
struct Blob
|
||||||
|
{
|
||||||
|
char *block; // Current block for writing
|
||||||
|
size_t pos; // Position in current block
|
||||||
|
};
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
Blob *
|
||||||
|
blbNew(void)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_VOID();
|
||||||
|
|
||||||
|
Blob *this = NULL;
|
||||||
|
|
||||||
|
OBJ_NEW_BEGIN(Blob, .allocQty = MEM_CONTEXT_QTY_MAX)
|
||||||
|
{
|
||||||
|
this = OBJ_NEW_ALLOC();
|
||||||
|
|
||||||
|
*this = (Blob)
|
||||||
|
{
|
||||||
|
.block = memNew(BLOB_BLOCK_SIZE),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
OBJ_NEW_END();
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN(BLOB, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
const void *
|
||||||
|
blbAdd(Blob *const this, const void *const data, const size_t size)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(BLOB, this);
|
||||||
|
FUNCTION_TEST_PARAM_P(VOID, data);
|
||||||
|
FUNCTION_TEST_PARAM(SIZE, size);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
void *result = NULL;
|
||||||
|
|
||||||
|
MEM_CONTEXT_OBJ_BEGIN(this)
|
||||||
|
{
|
||||||
|
// If data is >= block size then allocate a special block for the data
|
||||||
|
if (size >= BLOB_BLOCK_SIZE)
|
||||||
|
{
|
||||||
|
result = memNew(size);
|
||||||
|
memcpy(result, data, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If data will fit in the current block
|
||||||
|
if (BLOB_BLOCK_SIZE - this->pos >= size)
|
||||||
|
{
|
||||||
|
result = (unsigned char *)this->block + this->pos;
|
||||||
|
|
||||||
|
memcpy(result, data, size);
|
||||||
|
this->pos += size;
|
||||||
|
}
|
||||||
|
// Else allocate a new block
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->block = memNew(BLOB_BLOCK_SIZE);
|
||||||
|
result = this->block;
|
||||||
|
|
||||||
|
memcpy(result, data, size);
|
||||||
|
this->pos = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN_P(VOID, result);
|
||||||
|
|
||||||
|
}
|
58
src/common/type/blob.h
Normal file
58
src/common/type/blob.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Blob Handler
|
||||||
|
|
||||||
|
Efficiently store large numbers of small allocations by packing them onto larger blocks. Note that the allocations will not be
|
||||||
|
aligned unless all of them are aligned.
|
||||||
|
|
||||||
|
A usage example is to store zero-terminated strings, which do not need to be aligned, and where alignment might waste significant
|
||||||
|
space.
|
||||||
|
|
||||||
|
Note that this code is fairly primitive, and this should probably be a capability of memory contexts. However, for now it reduces
|
||||||
|
memory requirements for large numbers of zero-terminated strings.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#ifndef COMMON_TYPE_BLOB_H
|
||||||
|
#define COMMON_TYPE_BLOB_H
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Size of blocks allocated for blob data
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#ifndef BLOB_BLOCK_SIZE
|
||||||
|
#define BLOB_BLOCK_SIZE (64 * 1024)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Object type
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct Blob Blob;
|
||||||
|
|
||||||
|
#include "common/type/object.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Constructor
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
Blob *blbNew(void);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Functions
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
// Add data to the blob
|
||||||
|
const void *blbAdd(Blob *this, const void *data, size_t size);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Destructor
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
__attribute__((always_inline)) static inline void
|
||||||
|
strBlbFree(Blob *const this)
|
||||||
|
{
|
||||||
|
objFree(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Macros for function logging
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#define FUNCTION_LOG_BLOB_TYPE \
|
||||||
|
Blob *
|
||||||
|
#define FUNCTION_LOG_BLOB_FORMAT(value, buffer, bufferSize) \
|
||||||
|
objToLog(value, "Blob", buffer, bufferSize)
|
||||||
|
|
||||||
|
#endif
|
@ -680,8 +680,8 @@ manifestLinkCheck(const Manifest *this)
|
|||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
typedef struct ManifestBuildData
|
typedef struct ManifestBuildData
|
||||||
{
|
{
|
||||||
Manifest *manifest;
|
Manifest *manifest; // Manifest being build
|
||||||
const Storage *storagePg;
|
const Storage *storagePg; // PostgreSQL storage
|
||||||
const String *tablespaceId; // Tablespace id if PostgreSQL version has one
|
const String *tablespaceId; // Tablespace id if PostgreSQL version has one
|
||||||
bool online; // Is this an online backup?
|
bool online; // Is this an online backup?
|
||||||
bool checksumPage; // Are page checksums being checked?
|
bool checksumPage; // Are page checksums being checked?
|
||||||
@ -689,49 +689,46 @@ typedef struct ManifestBuildData
|
|||||||
RegExp *dbPathExp; // Identify paths containing relations
|
RegExp *dbPathExp; // Identify paths containing relations
|
||||||
RegExp *tempRelationExp; // Identify temp relations
|
RegExp *tempRelationExp; // Identify temp relations
|
||||||
const Pack *tablespaceList; // List of tablespaces in the database
|
const Pack *tablespaceList; // List of tablespaces in the database
|
||||||
ManifestLinkCheck linkCheck; // List of links found during build (used for prefix check)
|
ManifestLinkCheck *linkCheck; // List of links found during build (used for prefix check)
|
||||||
StringList *excludeContent; // Exclude contents of directories
|
StringList *excludeContent; // Exclude contents of directories
|
||||||
StringList *excludeSingle; // Exclude a single file/link/path
|
StringList *excludeSingle; // Exclude a single file/link/path
|
||||||
|
|
||||||
// These change with each level of recursion
|
|
||||||
const String *manifestParentName; // Manifest name of this file/link/path's parent
|
|
||||||
const String *pgPath; // Current path in the PostgreSQL data directory
|
|
||||||
bool dbPath; // Does this path contain relations?
|
|
||||||
} ManifestBuildData;
|
} ManifestBuildData;
|
||||||
|
|
||||||
// Callback to process files/links/paths and add them to the manifest
|
// Process files/links/paths and add them to the manifest
|
||||||
static void
|
static void
|
||||||
manifestBuildCallback(void *data, const StorageInfo *info)
|
manifestBuildInfo(
|
||||||
|
ManifestBuildData *const buildData, const String *manifestParentName, const String *pgPath, const bool dbPath,
|
||||||
|
const StorageInfo *const info)
|
||||||
{
|
{
|
||||||
FUNCTION_TEST_BEGIN();
|
FUNCTION_TEST_BEGIN();
|
||||||
FUNCTION_TEST_PARAM_P(VOID, data);
|
FUNCTION_TEST_PARAM_P(VOID, buildData);
|
||||||
FUNCTION_TEST_PARAM(STORAGE_INFO, *storageInfo);
|
FUNCTION_TEST_PARAM(STRING, manifestParentName);
|
||||||
|
FUNCTION_TEST_PARAM(STRING, pgPath);
|
||||||
|
FUNCTION_TEST_PARAM(BOOL, dbPath);
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_INFO, *info);
|
||||||
FUNCTION_TEST_END();
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
ASSERT(data != NULL);
|
ASSERT(buildData != NULL);
|
||||||
|
ASSERT(manifestParentName != NULL);
|
||||||
|
ASSERT(pgPath != NULL);
|
||||||
ASSERT(info != NULL);
|
ASSERT(info != NULL);
|
||||||
|
|
||||||
// Skip all . paths because they have already been recorded on the previous level of recursion
|
|
||||||
if (strEq(info->name, DOT_STR))
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
// Skip any path/file/link that begins with pgsql_tmp. The files are removed when the server is restarted and the directories
|
// Skip any path/file/link that begins with pgsql_tmp. The files are removed when the server is restarted and the directories
|
||||||
// are recreated.
|
// are recreated.
|
||||||
if (strBeginsWithZ(info->name, PG_PREFIX_PGSQLTMP))
|
if (strBeginsWithZ(info->name, PG_PREFIX_PGSQLTMP))
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
|
||||||
// Get build data
|
// Get build data
|
||||||
ManifestBuildData buildData = *(ManifestBuildData *)data;
|
unsigned int pgVersion = buildData->manifest->pub.data.pgVersion;
|
||||||
unsigned int pgVersion = buildData.manifest->pub.data.pgVersion;
|
|
||||||
|
|
||||||
// Construct the name used to identify this file/link/path in the manifest
|
// Construct the name used to identify this file/link/path in the manifest
|
||||||
const String *manifestName = strNewFmt("%s/%s", strZ(buildData.manifestParentName), strZ(info->name));
|
const String *manifestName = strNewFmt("%s/%s", strZ(manifestParentName), strZ(info->name));
|
||||||
|
|
||||||
// Skip excluded files/links/paths
|
// Skip excluded files/links/paths
|
||||||
if (buildData.excludeSingle != NULL && strLstExists(buildData.excludeSingle, manifestName))
|
if (buildData->excludeSingle != NULL && strLstExists(buildData->excludeSingle, manifestName))
|
||||||
{
|
{
|
||||||
LOG_INFO_FMT(
|
LOG_INFO_FMT(
|
||||||
"exclude '%s/%s' from backup using '%s' exclusion", strZ(buildData.pgPath), strZ(info->name),
|
"exclude '%s/%s' from backup using '%s' exclusion", strZ(pgPath), strZ(info->name),
|
||||||
strZ(strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA))));
|
strZ(strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA))));
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
@ -745,7 +742,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
case storageTypePath:
|
case storageTypePath:
|
||||||
{
|
{
|
||||||
// There should not be any paths in pg_tblspc
|
// There should not be any paths in pg_tblspc
|
||||||
if (strEqZ(buildData.manifestParentName, MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC))
|
if (strEqZ(manifestParentName, MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC))
|
||||||
{
|
{
|
||||||
THROW_FMT(
|
THROW_FMT(
|
||||||
LinkExpectedError, "'%s' is not a symlink - " MANIFEST_TARGET_PGTBLSPC " should contain only symlinks",
|
LinkExpectedError, "'%s' is not a symlink - " MANIFEST_TARGET_PGTBLSPC " should contain only symlinks",
|
||||||
@ -761,20 +758,20 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
.group = info->group,
|
.group = info->group,
|
||||||
};
|
};
|
||||||
|
|
||||||
manifestPathAdd(buildData.manifest, &path);
|
manifestPathAdd(buildData->manifest, &path);
|
||||||
|
|
||||||
// Skip excluded path content
|
// Skip excluded path content
|
||||||
if (buildData.excludeContent != NULL && strLstExists(buildData.excludeContent, manifestName))
|
if (buildData->excludeContent != NULL && strLstExists(buildData->excludeContent, manifestName))
|
||||||
{
|
{
|
||||||
LOG_INFO_FMT(
|
LOG_INFO_FMT(
|
||||||
"exclude contents of '%s/%s' from backup using '%s/' exclusion", strZ(buildData.pgPath), strZ(info->name),
|
"exclude contents of '%s/%s' from backup using '%s/' exclusion", strZ(pgPath), strZ(info->name),
|
||||||
strZ(strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA))));
|
strZ(strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA))));
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the contents of these paths if they exist in the base path since they won't be reused after recovery
|
// Skip the contents of these paths if they exist in the base path since they won't be reused after recovery
|
||||||
if (strEq(buildData.manifestParentName, MANIFEST_TARGET_PGDATA_STR))
|
if (strEq(manifestParentName, MANIFEST_TARGET_PGDATA_STR))
|
||||||
{
|
{
|
||||||
// Skip pg_dynshmem/* since these files cannot be reused on recovery
|
// Skip pg_dynshmem/* since these files cannot be reused on recovery
|
||||||
if (strEqZ(info->name, PG_PATH_PGDYNSHMEM) && pgVersion >= PG_VERSION_94)
|
if (strEqZ(info->name, PG_PATH_PGDYNSHMEM) && pgVersion >= PG_VERSION_94)
|
||||||
@ -808,20 +805,30 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip the contents of archive_status when online
|
// Skip the contents of archive_status when online
|
||||||
if (buildData.online && strEq(buildData.manifestParentName, buildData.manifestWalName) &&
|
if (buildData->online && strEq(manifestParentName, buildData->manifestWalName) &&
|
||||||
strEqZ(info->name, PG_PATH_ARCHIVE_STATUS))
|
strEqZ(info->name, PG_PATH_ARCHIVE_STATUS))
|
||||||
{
|
{
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into the path
|
// Recurse into the path
|
||||||
ManifestBuildData buildDataSub = buildData;
|
const String *const pgPathSub = strNewFmt("%s/%s", strZ(pgPath), strZ(info->name));
|
||||||
buildDataSub.manifestParentName = manifestName;
|
const bool dbPathSub = regExpMatch(buildData->dbPathExp, manifestName);
|
||||||
buildDataSub.pgPath = strNewFmt("%s/%s", strZ(buildData.pgPath), strZ(info->name));
|
StorageIterator *const storageItr = storageNewItrP(buildData->storagePg, pgPathSub, .sortOrder = sortOrderAsc);
|
||||||
buildDataSub.dbPath = regExpMatch(buildData.dbPathExp, manifestName);
|
|
||||||
|
|
||||||
storageInfoListP(
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
buildDataSub.storagePg, buildDataSub.pgPath, manifestBuildCallback, &buildDataSub, .sortOrder = sortOrderAsc);
|
{
|
||||||
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageItrNext(storageItr);
|
||||||
|
|
||||||
|
manifestBuildInfo(buildData, manifestName, pgPathSub, dbPathSub, &info);
|
||||||
|
|
||||||
|
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
||||||
|
MEM_CONTEXT_TEMP_RESET(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -831,7 +838,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
{
|
{
|
||||||
// There should not be any files in pg_tblspc
|
// There should not be any files in pg_tblspc
|
||||||
if (strEqZ(buildData.manifestParentName, MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC))
|
if (strEqZ(manifestParentName, MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC))
|
||||||
{
|
{
|
||||||
THROW_FMT(
|
THROW_FMT(
|
||||||
LinkExpectedError, "'%s' is not a symlink - " MANIFEST_TARGET_PGTBLSPC " should contain only symlinks",
|
LinkExpectedError, "'%s' is not a symlink - " MANIFEST_TARGET_PGTBLSPC " should contain only symlinks",
|
||||||
@ -842,7 +849,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
// the creating process id as the extension can exist so skip that as well. This seems to be a bug in PostgreSQL since
|
// the creating process id as the extension can exist so skip that as well. This seems to be a bug in PostgreSQL since
|
||||||
// the temp file should be removed on startup. Use regExpMatchOne() here instead of preparing a regexp in advance since
|
// the temp file should be removed on startup. Use regExpMatchOne() here instead of preparing a regexp in advance since
|
||||||
// the likelihood of needing the regexp should be very small.
|
// the likelihood of needing the regexp should be very small.
|
||||||
if (buildData.dbPath && strBeginsWithZ(info->name, PG_FILE_PGINTERNALINIT) &&
|
if (dbPath && strBeginsWithZ(info->name, PG_FILE_PGINTERNALINIT) &&
|
||||||
(strSize(info->name) == sizeof(PG_FILE_PGINTERNALINIT) - 1 ||
|
(strSize(info->name) == sizeof(PG_FILE_PGINTERNALINIT) - 1 ||
|
||||||
regExpMatchOne(STRDEF("\\.[0-9]+"), strSub(info->name, sizeof(PG_FILE_PGINTERNALINIT) - 1))))
|
regExpMatchOne(STRDEF("\\.[0-9]+"), strSub(info->name, sizeof(PG_FILE_PGINTERNALINIT) - 1))))
|
||||||
{
|
{
|
||||||
@ -850,7 +857,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip files in the root data path
|
// Skip files in the root data path
|
||||||
if (strEq(buildData.manifestParentName, MANIFEST_TARGET_PGDATA_STR))
|
if (strEq(manifestParentName, MANIFEST_TARGET_PGDATA_STR))
|
||||||
{
|
{
|
||||||
// Skip recovery files
|
// Skip recovery files
|
||||||
if (((strEqZ(info->name, PG_FILE_RECOVERYSIGNAL) || strEqZ(info->name, PG_FILE_STANDBYSIGNAL)) &&
|
if (((strEqZ(info->name, PG_FILE_RECOVERYSIGNAL) || strEqZ(info->name, PG_FILE_STANDBYSIGNAL)) &&
|
||||||
@ -877,11 +884,11 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
|
|
||||||
// Skip the contents of the wal path when online. WAL will be restored from the archive or stored in the wal directory
|
// Skip the contents of the wal path when online. WAL will be restored from the archive or stored in the wal directory
|
||||||
// at the end of the backup if the archive-copy option is set.
|
// at the end of the backup if the archive-copy option is set.
|
||||||
if (buildData.online && strEq(buildData.manifestParentName, buildData.manifestWalName))
|
if (buildData->online && strEq(manifestParentName, buildData->manifestWalName))
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
|
||||||
// Skip temp relations in db paths
|
// Skip temp relations in db paths
|
||||||
if (buildData.dbPath && regExpMatch(buildData.tempRelationExp, info->name))
|
if (dbPath && regExpMatch(buildData->tempRelationExp, info->name))
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
|
||||||
// Add file to manifest
|
// Add file to manifest
|
||||||
@ -897,14 +904,14 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Determine if this file should be page checksummed
|
// Determine if this file should be page checksummed
|
||||||
if (buildData.dbPath && buildData.checksumPage)
|
if (dbPath && buildData->checksumPage)
|
||||||
{
|
{
|
||||||
file.checksumPage =
|
file.checksumPage =
|
||||||
!strEndsWithZ(manifestName, "/" PG_FILE_PGFILENODEMAP) && !strEndsWithZ(manifestName, "/" PG_FILE_PGVERSION) &&
|
!strEndsWithZ(manifestName, "/" PG_FILE_PGFILENODEMAP) && !strEndsWithZ(manifestName, "/" PG_FILE_PGVERSION) &&
|
||||||
!strEqZ(manifestName, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL);
|
!strEqZ(manifestName, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL);
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestFileAdd(buildData.manifest, &file);
|
manifestFileAdd(buildData->manifest, &file);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,16 +922,16 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
// If the destination is another link then error. In the future we'll allow this by following the link chain to the
|
// If the destination is another link then error. In the future we'll allow this by following the link chain to the
|
||||||
// eventual destination but for now we are trying to maintain compatibility during the migration. To do this check we
|
// eventual destination but for now we are trying to maintain compatibility during the migration. To do this check we
|
||||||
// need to read outside of the data directory but it is a read-only operation so is considered safe.
|
// need to read outside of the data directory but it is a read-only operation so is considered safe.
|
||||||
const String *linkDestinationAbsolute = strPathAbsolute(info->linkDestination, buildData.pgPath);
|
const String *linkDestinationAbsolute = strPathAbsolute(info->linkDestination, pgPath);
|
||||||
|
|
||||||
StorageInfo linkedCheck = storageInfoP(
|
StorageInfo linkedCheck = storageInfoP(
|
||||||
buildData.storagePg, linkDestinationAbsolute, .ignoreMissing = true, .noPathEnforce = true);
|
buildData->storagePg, linkDestinationAbsolute, .ignoreMissing = true, .noPathEnforce = true);
|
||||||
|
|
||||||
if (linkedCheck.exists && linkedCheck.type == storageTypeLink)
|
if (linkedCheck.exists && linkedCheck.type == storageTypeLink)
|
||||||
{
|
{
|
||||||
THROW_FMT(
|
THROW_FMT(
|
||||||
LinkDestinationError, "link '%s/%s' cannot reference another link '%s'", strZ(buildData.pgPath),
|
LinkDestinationError, "link '%s/%s' cannot reference another link '%s'", strZ(pgPath), strZ(info->name),
|
||||||
strZ(info->name), strZ(linkDestinationAbsolute));
|
strZ(linkDestinationAbsolute));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize link and target
|
// Initialize link and target
|
||||||
@ -946,7 +953,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
const String *linkName = info->name;
|
const String *linkName = info->name;
|
||||||
|
|
||||||
// Is this a tablespace?
|
// Is this a tablespace?
|
||||||
if (strEq(buildData.manifestParentName, STRDEF(MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC)))
|
if (strEq(manifestParentName, STRDEF(MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC)))
|
||||||
{
|
{
|
||||||
// Strip pg_data off the manifest name so it begins with pg_tblspc instead. This reflects how the files are stored
|
// Strip pg_data off the manifest name so it begins with pg_tblspc instead. This reflects how the files are stored
|
||||||
// in the backup directory.
|
// in the backup directory.
|
||||||
@ -957,10 +964,10 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
target.tablespaceId = cvtZToUInt(strZ(info->name));
|
target.tablespaceId = cvtZToUInt(strZ(info->name));
|
||||||
|
|
||||||
// Look for this tablespace in the provided list (list may be null for off-line backup)
|
// Look for this tablespace in the provided list (list may be null for off-line backup)
|
||||||
if (buildData.tablespaceList != NULL)
|
if (buildData->tablespaceList != NULL)
|
||||||
{
|
{
|
||||||
// Search list
|
// Search list
|
||||||
PackRead *const read = pckReadNew(buildData.tablespaceList);
|
PackRead *const read = pckReadNew(buildData->tablespaceList);
|
||||||
|
|
||||||
while (!pckReadNullP(read))
|
while (!pckReadNullP(read))
|
||||||
{
|
{
|
||||||
@ -989,10 +996,10 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
|
|
||||||
// Add a dummy pg_tblspc path entry if it does not already exist. This entry will be ignored by restore but it is
|
// Add a dummy pg_tblspc path entry if it does not already exist. This entry will be ignored by restore but it is
|
||||||
// part of the original manifest format so we need to have it.
|
// part of the original manifest format so we need to have it.
|
||||||
lstSort(buildData.manifest->pub.pathList, sortOrderAsc);
|
lstSort(buildData->manifest->pub.pathList, sortOrderAsc);
|
||||||
const ManifestPath *pathBase = manifestPathFind(buildData.manifest, MANIFEST_TARGET_PGDATA_STR);
|
const ManifestPath *pathBase = manifestPathFind(buildData->manifest, MANIFEST_TARGET_PGDATA_STR);
|
||||||
|
|
||||||
if (manifestPathFindDefault(buildData.manifest, MANIFEST_TARGET_PGTBLSPC_STR, NULL) == NULL)
|
if (manifestPathFindDefault(buildData->manifest, MANIFEST_TARGET_PGTBLSPC_STR, NULL) == NULL)
|
||||||
{
|
{
|
||||||
ManifestPath path =
|
ManifestPath path =
|
||||||
{
|
{
|
||||||
@ -1002,14 +1009,14 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
.group = pathBase->group,
|
.group = pathBase->group,
|
||||||
};
|
};
|
||||||
|
|
||||||
manifestPathAdd(buildData.manifest, &path);
|
manifestPathAdd(buildData->manifest, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The tablespace link destination path is not the path where data will be stored so we can just store it as a dummy
|
// The tablespace link destination path is not the path where data will be stored so we can just store it as a dummy
|
||||||
// path. This is because PostgreSQL creates a subpath with the version/catalog number so that multiple versions of
|
// path. This is because PostgreSQL creates a subpath with the version/catalog number so that multiple versions of
|
||||||
// PostgreSQL can share a tablespace, which makes upgrades easier.
|
// PostgreSQL can share a tablespace, which makes upgrades easier.
|
||||||
const ManifestPath *pathTblSpc = manifestPathFind(
|
const ManifestPath *pathTblSpc = manifestPathFind(
|
||||||
buildData.manifest, STRDEF(MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC));
|
buildData->manifest, STRDEF(MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC));
|
||||||
|
|
||||||
ManifestPath path =
|
ManifestPath path =
|
||||||
{
|
{
|
||||||
@ -1019,19 +1026,19 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
.group = pathTblSpc->group,
|
.group = pathTblSpc->group,
|
||||||
};
|
};
|
||||||
|
|
||||||
manifestPathAdd(buildData.manifest, &path);
|
manifestPathAdd(buildData->manifest, &path);
|
||||||
|
|
||||||
// Update build structure to reflect the path added above and the tablespace id
|
// Update build structure to reflect the path added above and the tablespace id
|
||||||
buildData.manifestParentName = manifestName;
|
manifestParentName = manifestName;
|
||||||
manifestName = strNewFmt("%s/%s", strZ(manifestName), strZ(buildData.tablespaceId));
|
manifestName = strNewFmt("%s/%s", strZ(manifestName), strZ(buildData->tablespaceId));
|
||||||
buildData.pgPath = strNewFmt("%s/%s", strZ(buildData.pgPath), strZ(info->name));
|
pgPath = strNewFmt("%s/%s", strZ(pgPath), strZ(info->name));
|
||||||
linkName = buildData.tablespaceId;
|
linkName = buildData->tablespaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add info about the linked file/path
|
// Add info about the linked file/path
|
||||||
const String *linkPgPath = strNewFmt("%s/%s", strZ(buildData.pgPath), strZ(linkName));
|
const String *linkPgPath = strNewFmt("%s/%s", strZ(pgPath), strZ(linkName));
|
||||||
StorageInfo linkedInfo = storageInfoP(
|
StorageInfo linkedInfo = storageInfoP(
|
||||||
buildData.storagePg, linkPgPath, .followLink = true, .ignoreMissing = true);
|
buildData->storagePg, linkPgPath, .followLink = true, .ignoreMissing = true);
|
||||||
linkedInfo.name = linkName;
|
linkedInfo.name = linkName;
|
||||||
|
|
||||||
// If the link destination exists then build the target
|
// If the link destination exists then build the target
|
||||||
@ -1059,18 +1066,18 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
target.path = info->linkDestination;
|
target.path = info->linkDestination;
|
||||||
|
|
||||||
// Add target and link
|
// Add target and link
|
||||||
manifestTargetAdd(buildData.manifest, &target);
|
manifestTargetAdd(buildData->manifest, &target);
|
||||||
manifestLinkAdd(buildData.manifest, &link);
|
manifestLinkAdd(buildData->manifest, &link);
|
||||||
|
|
||||||
// Make sure the link is valid
|
// Make sure the link is valid
|
||||||
manifestLinkCheckOne(buildData.manifest, &buildData.linkCheck, manifestTargetTotal(buildData.manifest) - 1);
|
manifestLinkCheckOne(buildData->manifest, buildData->linkCheck, manifestTargetTotal(buildData->manifest) - 1);
|
||||||
|
|
||||||
// If the link check was successful but the destination does not exist then check it again to generate an error
|
// If the link check was successful but the destination does not exist then check it again to generate an error
|
||||||
if (!linkedInfo.exists)
|
if (!linkedInfo.exists)
|
||||||
storageInfoP(buildData.storagePg, linkPgPath, .followLink = true);
|
storageInfoP(buildData->storagePg, linkPgPath, .followLink = true);
|
||||||
|
|
||||||
// Recurse into the link destination
|
// Recurse into the link destination
|
||||||
manifestBuildCallback(&buildData, &linkedInfo);
|
manifestBuildInfo(buildData, manifestParentName, pgPath, dbPath, &linkedInfo);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1078,7 +1085,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
// Skip special files
|
// Skip special files
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
case storageTypeSpecial:
|
case storageTypeSpecial:
|
||||||
LOG_WARN_FMT("exclude special file '%s/%s' from backup", strZ(buildData.pgPath), strZ(info->name));
|
LOG_WARN_FMT("exclude special file '%s/%s' from backup", strZ(pgPath), strZ(info->name));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1127,6 +1134,8 @@ manifestNewBuild(
|
|||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
// Data needed to build the manifest
|
// Data needed to build the manifest
|
||||||
|
ManifestLinkCheck linkCheck = manifestLinkCheckInit();
|
||||||
|
|
||||||
ManifestBuildData buildData =
|
ManifestBuildData buildData =
|
||||||
{
|
{
|
||||||
.manifest = this,
|
.manifest = this,
|
||||||
@ -1135,10 +1144,8 @@ manifestNewBuild(
|
|||||||
.online = online,
|
.online = online,
|
||||||
.checksumPage = checksumPage,
|
.checksumPage = checksumPage,
|
||||||
.tablespaceList = tablespaceList,
|
.tablespaceList = tablespaceList,
|
||||||
.linkCheck = manifestLinkCheckInit(),
|
.linkCheck = &linkCheck,
|
||||||
.manifestParentName = MANIFEST_TARGET_PGDATA_STR,
|
|
||||||
.manifestWalName = strNewFmt(MANIFEST_TARGET_PGDATA "/%s", strZ(pgWalPath(pgVersion))),
|
.manifestWalName = strNewFmt(MANIFEST_TARGET_PGDATA "/%s", strZ(pgWalPath(pgVersion))),
|
||||||
.pgPath = storagePathP(storagePg, NULL),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build expressions to identify databases paths and temp relations
|
// Build expressions to identify databases paths and temp relations
|
||||||
@ -1180,7 +1187,8 @@ manifestNewBuild(
|
|||||||
|
|
||||||
// Build manifest
|
// Build manifest
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
StorageInfo info = storageInfoP(storagePg, buildData.pgPath, .followLink = true);
|
const String *const pgPath = storagePathP(storagePg, NULL);
|
||||||
|
StorageInfo info = storageInfoP(storagePg, pgPath, .followLink = true);
|
||||||
|
|
||||||
ManifestPath path =
|
ManifestPath path =
|
||||||
{
|
{
|
||||||
@ -1204,15 +1212,29 @@ manifestNewBuild(
|
|||||||
ManifestTarget target =
|
ManifestTarget target =
|
||||||
{
|
{
|
||||||
.name = MANIFEST_TARGET_PGDATA_STR,
|
.name = MANIFEST_TARGET_PGDATA_STR,
|
||||||
.path = buildData.pgPath,
|
.path = pgPath,
|
||||||
.type = manifestTargetTypePath,
|
.type = manifestTargetTypePath,
|
||||||
};
|
};
|
||||||
|
|
||||||
manifestTargetAdd(this, &target);
|
manifestTargetAdd(this, &target);
|
||||||
|
|
||||||
// Gather info for the rest of the files/links/paths
|
// Gather info for the rest of the files/links/paths
|
||||||
storageInfoListP(
|
StorageIterator *const storageItr = storageNewItrP(
|
||||||
storagePg, buildData.pgPath, manifestBuildCallback, &buildData, .errorOnMissing = true, .sortOrder = sortOrderAsc);
|
storagePg, pgPath, .errorOnMissing = true, .sortOrder = sortOrderAsc);
|
||||||
|
|
||||||
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
|
{
|
||||||
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageItrNext(storageItr);
|
||||||
|
|
||||||
|
manifestBuildInfo(&buildData, MANIFEST_TARGET_PGDATA_STR, pgPath, false, &info);
|
||||||
|
|
||||||
|
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
||||||
|
MEM_CONTEXT_TEMP_RESET(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
// These may not be in order even if the incoming data was sorted
|
// These may not be in order even if the incoming data was sorted
|
||||||
lstSort(this->pub.fileList, sortOrderAsc);
|
lstSort(this->pub.fileList, sortOrderAsc);
|
||||||
|
@ -24,6 +24,7 @@ src_common = files(
|
|||||||
'common/regExp.c',
|
'common/regExp.c',
|
||||||
'common/stackTrace.c',
|
'common/stackTrace.c',
|
||||||
'common/time.c',
|
'common/time.c',
|
||||||
|
'common/type/blob.c',
|
||||||
'common/type/buffer.c',
|
'common/type/buffer.c',
|
||||||
'common/type/convert.c',
|
'common/type/convert.c',
|
||||||
'common/type/keyValue.c',
|
'common/type/keyValue.c',
|
||||||
@ -42,6 +43,8 @@ src_common = files(
|
|||||||
'storage/posix/read.c',
|
'storage/posix/read.c',
|
||||||
'storage/posix/storage.c',
|
'storage/posix/storage.c',
|
||||||
'storage/posix/write.c',
|
'storage/posix/write.c',
|
||||||
|
'storage/iterator.c',
|
||||||
|
'storage/list.c',
|
||||||
'storage/read.c',
|
'storage/read.c',
|
||||||
'storage/storage.c',
|
'storage/storage.c',
|
||||||
'storage/write.c',
|
'storage/write.c',
|
||||||
|
@ -303,7 +303,7 @@ General function for listing files to be used by other list routines
|
|||||||
static void
|
static void
|
||||||
storageAzureListInternal(
|
storageAzureListInternal(
|
||||||
StorageAzure *this, const String *path, StorageInfoLevel level, const String *expression, bool recurse,
|
StorageAzure *this, const String *path, StorageInfoLevel level, const String *expression, bool recurse,
|
||||||
StorageInfoListCallback callback, void *callbackData)
|
StorageListCallback callback, void *callbackData)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STORAGE_AZURE, this);
|
FUNCTION_LOG_PARAM(STORAGE_AZURE, this);
|
||||||
@ -506,10 +506,24 @@ storageAzureInfo(THIS_VOID, const String *file, StorageInfoLevel level, StorageI
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
static bool
|
static void
|
||||||
storageAzureInfoList(
|
storageAzureListCallback(void *const callbackData, const StorageInfo *const info)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
{
|
||||||
StorageInterfaceInfoListParam param)
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM_P(VOID, callbackData);
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(callbackData != NULL);
|
||||||
|
ASSERT(info != NULL);
|
||||||
|
|
||||||
|
storageLstAdd(callbackData, info);
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
static StorageList *
|
||||||
|
storageAzureList(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
|
||||||
{
|
{
|
||||||
THIS(StorageAzure);
|
THIS(StorageAzure);
|
||||||
|
|
||||||
@ -517,18 +531,17 @@ storageAzureInfoList(
|
|||||||
FUNCTION_LOG_PARAM(STORAGE_AZURE, this);
|
FUNCTION_LOG_PARAM(STORAGE_AZURE, this);
|
||||||
FUNCTION_LOG_PARAM(STRING, path);
|
FUNCTION_LOG_PARAM(STRING, path);
|
||||||
FUNCTION_LOG_PARAM(ENUM, level);
|
FUNCTION_LOG_PARAM(ENUM, level);
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_LOG_PARAM(STRING, param.expression);
|
FUNCTION_LOG_PARAM(STRING, param.expression);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
storageAzureListInternal(this, path, level, param.expression, false, callback, callbackData);
|
StorageList *const result = storageLstNew(level);
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, true);
|
storageAzureListInternal(this, path, level, param.expression, false, storageAzureListCallback, result);
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN(STORAGE_LIST, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
@ -677,7 +690,7 @@ storageAzureRemove(THIS_VOID, const String *file, StorageInterfaceRemoveParam pa
|
|||||||
static const StorageInterface storageInterfaceAzure =
|
static const StorageInterface storageInterfaceAzure =
|
||||||
{
|
{
|
||||||
.info = storageAzureInfo,
|
.info = storageAzureInfo,
|
||||||
.infoList = storageAzureInfoList,
|
.list = storageAzureList,
|
||||||
.newRead = storageAzureNewRead,
|
.newRead = storageAzureNewRead,
|
||||||
.newWrite = storageAzureNewWrite,
|
.newWrite = storageAzureNewWrite,
|
||||||
.pathRemove = storageAzurePathRemove,
|
.pathRemove = storageAzurePathRemove,
|
||||||
|
@ -532,7 +532,7 @@ storageGcsInfoFile(StorageInfo *info, const KeyValue *file)
|
|||||||
static void
|
static void
|
||||||
storageGcsListInternal(
|
storageGcsListInternal(
|
||||||
StorageGcs *this, const String *path, StorageInfoLevel level, const String *expression, bool recurse,
|
StorageGcs *this, const String *path, StorageInfoLevel level, const String *expression, bool recurse,
|
||||||
StorageInfoListCallback callback, void *callbackData)
|
StorageListCallback callback, void *callbackData)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STORAGE_GCS, this);
|
FUNCTION_LOG_PARAM(STORAGE_GCS, this);
|
||||||
@ -744,10 +744,24 @@ storageGcsInfo(THIS_VOID, const String *const file, const StorageInfoLevel level
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
static bool
|
static void
|
||||||
storageGcsInfoList(
|
storageGcsListCallback(void *const callbackData, const StorageInfo *const info)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
{
|
||||||
StorageInterfaceInfoListParam param)
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM_P(VOID, callbackData);
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(callbackData != NULL);
|
||||||
|
ASSERT(info != NULL);
|
||||||
|
|
||||||
|
storageLstAdd(callbackData, info);
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
static StorageList *
|
||||||
|
storageGcsList(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
|
||||||
{
|
{
|
||||||
THIS(StorageGcs);
|
THIS(StorageGcs);
|
||||||
|
|
||||||
@ -755,22 +769,17 @@ storageGcsInfoList(
|
|||||||
FUNCTION_LOG_PARAM(STORAGE_GCS, this);
|
FUNCTION_LOG_PARAM(STORAGE_GCS, this);
|
||||||
FUNCTION_LOG_PARAM(STRING, path);
|
FUNCTION_LOG_PARAM(STRING, path);
|
||||||
FUNCTION_LOG_PARAM(ENUM, level);
|
FUNCTION_LOG_PARAM(ENUM, level);
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_LOG_PARAM(STRING, param.expression);
|
FUNCTION_LOG_PARAM(STRING, param.expression);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
StorageList *const result = storageLstNew(level);
|
||||||
{
|
|
||||||
storageGcsListInternal(this, path, level, param.expression, false, callback, callbackData);
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_TEMP_END();
|
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, true);
|
storageGcsListInternal(this, path, level, param.expression, false, storageGcsListCallback, result);
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN(STORAGE_LIST, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
@ -919,7 +928,7 @@ storageGcsRemove(THIS_VOID, const String *const file, const StorageInterfaceRemo
|
|||||||
static const StorageInterface storageInterfaceGcs =
|
static const StorageInterface storageInterfaceGcs =
|
||||||
{
|
{
|
||||||
.info = storageGcsInfo,
|
.info = storageGcsInfo,
|
||||||
.infoList = storageGcsInfoList,
|
.list = storageGcsList,
|
||||||
.newRead = storageGcsNewRead,
|
.newRead = storageGcsNewRead,
|
||||||
.newWrite = storageGcsNewWrite,
|
.newWrite = storageGcsNewWrite,
|
||||||
.pathRemove = storageGcsPathRemove,
|
.pathRemove = storageGcsPathRemove,
|
||||||
|
277
src/storage/iterator.c
Normal file
277
src/storage/iterator.c
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Storage Iterator
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#include "build.auto.h"
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/regExp.h"
|
||||||
|
#include "common/type/list.h"
|
||||||
|
#include "storage/iterator.h"
|
||||||
|
#include "storage/list.h"
|
||||||
|
#include "storage/storage.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Object type
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
struct StorageIterator
|
||||||
|
{
|
||||||
|
void *driver; // Storage driver
|
||||||
|
const String *path; // Path to iterate
|
||||||
|
StorageInfoLevel level; // Info level
|
||||||
|
bool recurse; // Recurse into paths
|
||||||
|
SortOrder sortOrder; // Sort order
|
||||||
|
const String *expression; // Match expression
|
||||||
|
RegExp *regExp; // Parsed match expression
|
||||||
|
|
||||||
|
List *stack; // Stack of info lists
|
||||||
|
bool returnedNext; // Next info was returned
|
||||||
|
StorageInfo infoNext; // Info to be returned by next
|
||||||
|
String *nameNext; // Name for next info
|
||||||
|
};
|
||||||
|
|
||||||
|
// Path info list
|
||||||
|
typedef struct StorageIteratorInfo
|
||||||
|
{
|
||||||
|
String *pathSub; // Subpath
|
||||||
|
StorageList *list; // Storage info list
|
||||||
|
unsigned int listIdx; // Current index in info list
|
||||||
|
bool pathContentSkip; // Skip reading path content
|
||||||
|
} StorageIteratorInfo;
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Check a path and add it to the stack if it exists and has content
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct StorageItrPathAddResult
|
||||||
|
{
|
||||||
|
bool exists; // Does the path exist?
|
||||||
|
bool content; // Does the path have content?
|
||||||
|
} StorageItrPathAddResult;
|
||||||
|
|
||||||
|
static StorageItrPathAddResult
|
||||||
|
storageItrPathAdd(StorageIterator *const this, const String *const pathSub)
|
||||||
|
{
|
||||||
|
FUNCTION_LOG_BEGIN(logLevelTrace);
|
||||||
|
FUNCTION_LOG_PARAM(STORAGE_ITERATOR, this);
|
||||||
|
FUNCTION_LOG_PARAM(STRING, pathSub);
|
||||||
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
|
ASSERT(this != NULL);
|
||||||
|
|
||||||
|
StorageItrPathAddResult result = {0};
|
||||||
|
|
||||||
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
|
{
|
||||||
|
// Get path content
|
||||||
|
StorageList *const list = storageInterfaceListP(
|
||||||
|
this->driver, pathSub == NULL ? this->path : strNewFmt("%s/%s", strZ(this->path), strZ(pathSub)), this->level,
|
||||||
|
.expression = this->expression);
|
||||||
|
|
||||||
|
// If path exists
|
||||||
|
if (list != NULL)
|
||||||
|
{
|
||||||
|
result.exists = true;
|
||||||
|
|
||||||
|
// If the path has content
|
||||||
|
if (!storageLstEmpty(list))
|
||||||
|
{
|
||||||
|
result.content = true;
|
||||||
|
|
||||||
|
// Sort if requested
|
||||||
|
if (this->sortOrder != sortOrderNone)
|
||||||
|
storageLstSort(list, this->sortOrder);
|
||||||
|
|
||||||
|
// Add path to top of stack
|
||||||
|
MEM_CONTEXT_OBJ_BEGIN(this->stack)
|
||||||
|
{
|
||||||
|
OBJ_NEW_BEGIN(StorageIteratorInfo, .childQty = MEM_CONTEXT_QTY_MAX, .allocQty = 1)
|
||||||
|
{
|
||||||
|
StorageIteratorInfo *const listInfo = OBJ_NEW_ALLOC();
|
||||||
|
*listInfo = (StorageIteratorInfo){
|
||||||
|
.pathSub = strDup(pathSub), .list = storageLstMove(list, memContextCurrent())};
|
||||||
|
|
||||||
|
lstAdd(this->stack, &listInfo);
|
||||||
|
}
|
||||||
|
OBJ_NEW_END();
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN_STRUCT(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
StorageIterator *
|
||||||
|
storageItrNew(
|
||||||
|
void *const driver, const String *const path, const StorageInfoLevel level, const bool errorOnMissing, const bool nullOnMissing,
|
||||||
|
const bool recurse, const SortOrder sortOrder, const String *const expression)
|
||||||
|
{
|
||||||
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
|
FUNCTION_LOG_PARAM_P(VOID, driver);
|
||||||
|
FUNCTION_LOG_PARAM(STRING, path);
|
||||||
|
FUNCTION_LOG_PARAM(ENUM, level);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, errorOnMissing);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, nullOnMissing);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, recurse);
|
||||||
|
FUNCTION_LOG_PARAM(ENUM, sortOrder);
|
||||||
|
FUNCTION_LOG_PARAM(STRING, expression);
|
||||||
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
|
ASSERT(driver != NULL);
|
||||||
|
ASSERT(path != NULL);
|
||||||
|
ASSERT(!recurse || level >= storageInfoLevelType);
|
||||||
|
|
||||||
|
StorageIterator *this = NULL;
|
||||||
|
|
||||||
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
|
{
|
||||||
|
OBJ_NEW_BEGIN(StorageIterator, .childQty = MEM_CONTEXT_QTY_MAX)
|
||||||
|
{
|
||||||
|
// Create object
|
||||||
|
this = OBJ_NEW_ALLOC();
|
||||||
|
|
||||||
|
*this = (StorageIterator)
|
||||||
|
{
|
||||||
|
.driver = driver,
|
||||||
|
.path = strDup(path),
|
||||||
|
.level = level,
|
||||||
|
.recurse = recurse,
|
||||||
|
.sortOrder = sortOrder,
|
||||||
|
.expression = strDup(expression),
|
||||||
|
.stack = lstNewP(sizeof(StorageIteratorInfo *)),
|
||||||
|
.nameNext = strNew(),
|
||||||
|
.returnedNext = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compile regular expression
|
||||||
|
if (this->expression != NULL)
|
||||||
|
this->regExp = regExpNew(this->expression);
|
||||||
|
|
||||||
|
// If root path does not exist
|
||||||
|
if (!storageItrPathAdd(this, NULL).exists)
|
||||||
|
{
|
||||||
|
// Throw an error when requested
|
||||||
|
if (errorOnMissing)
|
||||||
|
THROW_FMT(PathMissingError, STORAGE_ERROR_LIST_INFO_MISSING, strZ(this->path));
|
||||||
|
|
||||||
|
// Return NULL when requested
|
||||||
|
if (nullOnMissing)
|
||||||
|
this = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OBJ_NEW_END();
|
||||||
|
|
||||||
|
storageItrMove(this, memContextPrior());
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN(STORAGE_ITERATOR, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
bool
|
||||||
|
storageItrMore(StorageIterator *const this)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_ITERATOR, this);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(this != NULL);
|
||||||
|
|
||||||
|
// If next has not been called then return true
|
||||||
|
if (!this->returnedNext)
|
||||||
|
FUNCTION_TEST_RETURN(BOOL, true);
|
||||||
|
|
||||||
|
// Search stack for info
|
||||||
|
while (lstSize(this->stack) != 0)
|
||||||
|
{
|
||||||
|
// Always pull from the top of the stack
|
||||||
|
StorageIteratorInfo *const listInfo = *(StorageIteratorInfo **)lstGetLast(this->stack);
|
||||||
|
|
||||||
|
// Search list for info
|
||||||
|
for (; listInfo->listIdx < storageLstSize(listInfo->list); listInfo->listIdx++)
|
||||||
|
{
|
||||||
|
// Next info
|
||||||
|
this->infoNext = storageLstGet(listInfo->list, listInfo->listIdx);
|
||||||
|
|
||||||
|
// Update info name when in subpath
|
||||||
|
if (listInfo->pathSub != NULL)
|
||||||
|
{
|
||||||
|
strCatFmt(strTrunc(this->nameNext), "%s/%s", strZ(listInfo->pathSub), strZ(this->infoNext.name));
|
||||||
|
|
||||||
|
this->infoNext.name = this->nameNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the path have content?
|
||||||
|
const bool pathContent =
|
||||||
|
this->infoNext.type == storageTypePath && this->recurse && !listInfo->pathContentSkip &&
|
||||||
|
storageItrPathAdd(this, this->infoNext.name).content;
|
||||||
|
|
||||||
|
// Clear path content skip flag if it was set on a previous iteration
|
||||||
|
listInfo->pathContentSkip = false;
|
||||||
|
|
||||||
|
// Skip info if it does match the provided expression
|
||||||
|
if (this->regExp != NULL && !regExpMatch(this->regExp, this->infoNext.name))
|
||||||
|
{
|
||||||
|
// Path content may match the expression even if the path does not. Break so the content on the top of the stack
|
||||||
|
// will be checked and increment the index so that path will be skipped.
|
||||||
|
if (pathContent)
|
||||||
|
{
|
||||||
|
listInfo->listIdx++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip info and continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When sort order is descending the path will need to be output after the content. Break so that content gets checked
|
||||||
|
// but set a flag so on the next iteration the path will be output but the content skipped.
|
||||||
|
if (pathContent && this->sortOrder == sortOrderDesc)
|
||||||
|
{
|
||||||
|
listInfo->pathContentSkip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return next info
|
||||||
|
this->returnedNext = false;
|
||||||
|
listInfo->listIdx++;
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN(BOOL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no more info then free the list. This check is required because we may break out of the above loop early.
|
||||||
|
if (listInfo->listIdx >= storageLstSize(listInfo->list))
|
||||||
|
{
|
||||||
|
objFree(listInfo);
|
||||||
|
lstRemoveLast(this->stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN(BOOL, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
StorageInfo storageItrNext(StorageIterator *const this)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_ITERATOR, this);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(this != NULL);
|
||||||
|
ASSERT(!this->returnedNext);
|
||||||
|
|
||||||
|
this->returnedNext = true;
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN(STORAGE_INFO, this->infoNext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
String *
|
||||||
|
storageItrToLog(const StorageIterator *const this)
|
||||||
|
{
|
||||||
|
return strNewFmt("{stack: %s}", strZ(lstToLog(this->stack)));
|
||||||
|
}
|
58
src/storage/iterator.h
Normal file
58
src/storage/iterator.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Storage Iterator
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#ifndef STORAGE_ITERATOR_H
|
||||||
|
#define STORAGE_ITERATOR_H
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Object type
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct StorageIterator StorageIterator;
|
||||||
|
|
||||||
|
#include "common/type/string.h"
|
||||||
|
#include "storage/info.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Constructors
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
StorageIterator *storageItrNew(
|
||||||
|
void *driver, const String *path, StorageInfoLevel level, bool errorOnMissing, bool nullOnMissing, bool recurse,
|
||||||
|
SortOrder sortOrder, const String *expression);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Functions
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
// Is there more info to be retrieved from the iterator?
|
||||||
|
bool storageItrMore(StorageIterator *this);
|
||||||
|
|
||||||
|
// Move to a new parent mem context
|
||||||
|
__attribute__((always_inline)) static inline StorageIterator *
|
||||||
|
storageItrMove(StorageIterator *const this, MemContext *const parentNew)
|
||||||
|
{
|
||||||
|
return objMove(this, parentNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next info. An error will be thrown if there is no more data so use storageItrMore() to check. Note that StorageInfo pointer
|
||||||
|
// members (e.g. name) will be undefined after the next call to storageItrMore().
|
||||||
|
StorageInfo storageItrNext(StorageIterator *this);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Destructor
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
__attribute__((always_inline)) static inline void
|
||||||
|
storageItrFree(StorageIterator *const this)
|
||||||
|
{
|
||||||
|
objFree(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Macros for function logging
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
String *storageItrToLog(const StorageIterator *this);
|
||||||
|
|
||||||
|
#define FUNCTION_LOG_STORAGE_ITERATOR_TYPE \
|
||||||
|
StorageIterator *
|
||||||
|
#define FUNCTION_LOG_STORAGE_ITERATOR_FORMAT(value, buffer, bufferSize) \
|
||||||
|
FUNCTION_LOG_STRING_OBJECT_FORMAT(value, storageItrToLog, buffer, bufferSize)
|
||||||
|
|
||||||
|
#endif
|
215
src/storage/list.c
Normal file
215
src/storage/list.c
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Storage List
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#include "build.auto.h"
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/type/blob.h"
|
||||||
|
#include "common/type/list.h"
|
||||||
|
#include "storage/list.h"
|
||||||
|
#include "storage/storage.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Object type
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
struct StorageList
|
||||||
|
{
|
||||||
|
StorageListPub pub; // Publicly accessible variables
|
||||||
|
StringList *ownerList; // List of users/groups
|
||||||
|
Blob *blob; // Blob of info data
|
||||||
|
String *name; // Current info name
|
||||||
|
String *linkDestination; // Current link destination
|
||||||
|
};
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
A more space efficient version of StorageInfo. Each level is contained in a struct to ensure alignment when only using part of the
|
||||||
|
struct to store info. This also keeps the size calculations accurate if members are added to a level.
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct StorageListInfo
|
||||||
|
{
|
||||||
|
// Set when info type >= storageInfoLevelExists
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
const char *name; // Name of path/file/link
|
||||||
|
} exists;
|
||||||
|
|
||||||
|
// Mode is only provided at detail level but is included here to save space on 64-bit architectures
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
// Set when info type >= storageInfoLevelType (undefined at lower levels)
|
||||||
|
StorageType type; // Type file/path/link)
|
||||||
|
|
||||||
|
// Set when info type >= storageInfoLevelDetail (undefined at lower levels)
|
||||||
|
mode_t mode; // Mode of path/file/link
|
||||||
|
} type;
|
||||||
|
|
||||||
|
// Set when info type >= storageInfoLevelBasic (undefined at lower levels)
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint64_t size; // Size (path/link is 0)
|
||||||
|
time_t timeModified; // Time file was last modified
|
||||||
|
} basic;
|
||||||
|
|
||||||
|
// Set when info type >= storageInfoLevelDetail (undefined at lower levels)
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
const String *user; // Name of user that owns the file
|
||||||
|
const String *group; // Name of group that owns the file
|
||||||
|
uid_t userId; // User that owns the file
|
||||||
|
uid_t groupId; // Group that owns the file
|
||||||
|
const char *linkDestination; // Destination if this is a link
|
||||||
|
} detail;
|
||||||
|
} StorageListInfo;
|
||||||
|
|
||||||
|
// Determine size of struct to store in the list. We only need to store the members that are used by the current level.
|
||||||
|
static const uint8_t storageLstInfoSize[storageInfoLevelDetail + 1] =
|
||||||
|
{
|
||||||
|
0, // storageInfoLevelDefault (should not be used)
|
||||||
|
offsetof(StorageListInfo, type), // storageInfoLevelExists
|
||||||
|
offsetof(StorageListInfo, basic), // storageInfoLevelType
|
||||||
|
offsetof(StorageListInfo, detail), // storageInfoLevelBasic
|
||||||
|
sizeof(StorageListInfo), // storageInfoLevelDetail
|
||||||
|
};
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
StorageList *
|
||||||
|
storageLstNew(const StorageInfoLevel level)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(ENUM, level);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(level != storageInfoLevelDefault);
|
||||||
|
|
||||||
|
StorageList *this = NULL;
|
||||||
|
|
||||||
|
OBJ_NEW_BEGIN(StorageList, .childQty = MEM_CONTEXT_QTY_MAX)
|
||||||
|
{
|
||||||
|
// Create object
|
||||||
|
this = OBJ_NEW_ALLOC();
|
||||||
|
|
||||||
|
*this = (StorageList)
|
||||||
|
{
|
||||||
|
.pub =
|
||||||
|
{
|
||||||
|
.list = lstNewP(storageLstInfoSize[level], .comparator = lstComparatorZ),
|
||||||
|
.level = level,
|
||||||
|
},
|
||||||
|
.ownerList = strLstNew(),
|
||||||
|
.blob = blbNew(),
|
||||||
|
.name = strNew(),
|
||||||
|
.linkDestination = strNew(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
OBJ_NEW_END();
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN(STORAGE_LIST, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
void
|
||||||
|
storageLstInsert(StorageList *const this, const unsigned int idx, const StorageInfo *const info)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_LIST, this);
|
||||||
|
FUNCTION_TEST_PARAM(UINT, idx);
|
||||||
|
FUNCTION_TEST_PARAM_P(STORAGE_INFO, info);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(this != NULL);
|
||||||
|
ASSERT(info != NULL);
|
||||||
|
|
||||||
|
MEM_CONTEXT_OBJ_BEGIN(this->pub.list)
|
||||||
|
{
|
||||||
|
StorageListInfo listInfo = {.exists = {.name = blbAdd(this->blob, strZ(info->name), strSize(info->name) + 1)}};
|
||||||
|
|
||||||
|
switch (storageLstLevel(this))
|
||||||
|
{
|
||||||
|
case storageInfoLevelDetail:
|
||||||
|
{
|
||||||
|
listInfo.type.mode = info->mode;
|
||||||
|
listInfo.detail.user = strLstAddIfMissing(this->ownerList, info->user);
|
||||||
|
listInfo.detail.group = strLstAddIfMissing(this->ownerList, info->group);
|
||||||
|
listInfo.detail.userId = info->userId;
|
||||||
|
listInfo.detail.groupId = info->groupId;
|
||||||
|
|
||||||
|
if (info->linkDestination != NULL)
|
||||||
|
{
|
||||||
|
listInfo.detail.linkDestination = blbAdd(
|
||||||
|
this->blob, strZ(info->linkDestination), strSize(info->linkDestination) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case storageInfoLevelBasic:
|
||||||
|
listInfo.basic.size = info->size;
|
||||||
|
listInfo.basic.timeModified = info->timeModified;
|
||||||
|
|
||||||
|
case storageInfoLevelType:
|
||||||
|
listInfo.type.type = info->type;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lstInsert(this->pub.list, idx, &listInfo);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_OBJ_END();
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
StorageInfo
|
||||||
|
storageLstGet(StorageList *const this, const unsigned int idx)
|
||||||
|
{
|
||||||
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_LIST, this);
|
||||||
|
FUNCTION_TEST_PARAM(UINT, idx);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(this != NULL);
|
||||||
|
|
||||||
|
const StorageListInfo *const listInfo = lstGet(this->pub.list, idx);
|
||||||
|
|
||||||
|
StorageInfo result =
|
||||||
|
{
|
||||||
|
.name = strCatZ(strTrunc(this->name), listInfo->exists.name),
|
||||||
|
.exists = true,
|
||||||
|
.level = storageLstLevel(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (result.level)
|
||||||
|
{
|
||||||
|
case storageInfoLevelDetail:
|
||||||
|
{
|
||||||
|
result.mode = listInfo->type.mode;
|
||||||
|
result.user = listInfo->detail.user;
|
||||||
|
result.group = listInfo->detail.group;
|
||||||
|
result.userId = listInfo->detail.userId;
|
||||||
|
result.groupId = listInfo->detail.groupId;
|
||||||
|
|
||||||
|
if (listInfo->detail.linkDestination != NULL)
|
||||||
|
result.linkDestination = strCatZ(strTrunc(this->linkDestination), listInfo->detail.linkDestination);
|
||||||
|
}
|
||||||
|
|
||||||
|
case storageInfoLevelBasic:
|
||||||
|
result.size = listInfo->basic.size;
|
||||||
|
result.timeModified = listInfo->basic.timeModified;
|
||||||
|
|
||||||
|
case storageInfoLevelType:
|
||||||
|
result.type = listInfo->type.type;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN(STORAGE_INFO, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************************************************************************************/
|
||||||
|
String *
|
||||||
|
storageLstToLog(const StorageList *const this)
|
||||||
|
{
|
||||||
|
return strNewFmt("{size: %u}", lstSize(this->pub.list));
|
||||||
|
}
|
99
src/storage/list.h
Normal file
99
src/storage/list.h
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Storage List
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
#ifndef STORAGE_LIST_H
|
||||||
|
#define STORAGE_LIST_H
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Object type
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct StorageList StorageList;
|
||||||
|
|
||||||
|
#include "common/type/string.h"
|
||||||
|
#include "storage/info.h"
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Constructors
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
StorageList *storageLstNew(StorageInfoLevel level);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Getters/Setters
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef struct StorageListPub
|
||||||
|
{
|
||||||
|
List *list; // Storage list
|
||||||
|
StorageInfoLevel level; // Storage info level
|
||||||
|
} StorageListPub;
|
||||||
|
|
||||||
|
// Empty?
|
||||||
|
__attribute__((always_inline)) static inline bool
|
||||||
|
storageLstEmpty(const StorageList *const this)
|
||||||
|
{
|
||||||
|
return lstEmpty(THIS_PUB(StorageList)->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage info level
|
||||||
|
__attribute__((always_inline)) static inline StorageInfoLevel
|
||||||
|
storageLstLevel(const StorageList *const this)
|
||||||
|
{
|
||||||
|
return THIS_PUB(StorageList)->level;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List size
|
||||||
|
__attribute__((always_inline)) static inline unsigned int
|
||||||
|
storageLstSize(const StorageList *const this)
|
||||||
|
{
|
||||||
|
return lstSize(THIS_PUB(StorageList)->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List size
|
||||||
|
__attribute__((always_inline)) static inline void
|
||||||
|
storageLstSort(StorageList *const this, const SortOrder sortOrder)
|
||||||
|
{
|
||||||
|
lstSort(THIS_PUB(StorageList)->list, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Functions
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
// Insert info
|
||||||
|
void storageLstInsert(StorageList *this, unsigned int idx, const StorageInfo *info);
|
||||||
|
|
||||||
|
// Add info
|
||||||
|
__attribute__((always_inline)) static inline void
|
||||||
|
storageLstAdd(StorageList *const this, const StorageInfo *const info)
|
||||||
|
{
|
||||||
|
storageLstInsert(this, storageLstSize(this), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get info. Note that StorageInfo pointer members (e.g. name) will be undefined after the next call to storageLstGet().
|
||||||
|
StorageInfo storageLstGet(StorageList *this, unsigned int idx);
|
||||||
|
|
||||||
|
// Move to a new parent mem context
|
||||||
|
__attribute__((always_inline)) static inline StorageList *
|
||||||
|
storageLstMove(StorageList *const this, MemContext *const parentNew)
|
||||||
|
{
|
||||||
|
return objMove(this, parentNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Destructor
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
__attribute__((always_inline)) static inline void
|
||||||
|
storageLstFree(StorageList *const this)
|
||||||
|
{
|
||||||
|
objFree(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Macros for function logging
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
String *storageLstToLog(const StorageList *this);
|
||||||
|
|
||||||
|
#define FUNCTION_LOG_STORAGE_LIST_TYPE \
|
||||||
|
StorageList *
|
||||||
|
#define FUNCTION_LOG_STORAGE_LIST_FORMAT(value, buffer, bufferSize) \
|
||||||
|
FUNCTION_LOG_STRING_OBJECT_FORMAT(value, storageLstToLog, buffer, bufferSize)
|
||||||
|
|
||||||
|
#endif
|
@ -113,44 +113,40 @@ storagePosixInfo(THIS_VOID, const String *file, StorageInfoLevel level, StorageI
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
// Helper function to get info for a file if it exists. This logic can't live directly in storagePosixInfoList() because there is
|
// Helper function to get info for a file if it exists. This logic can't live directly in storagePosixList() because there is a race
|
||||||
// a race condition where a file might exist while listing the directory but it is gone before stat() can be called. In order to
|
// condition where a file might exist while listing the directory but it is gone before stat() can be called. In order to get
|
||||||
// get complete test coverage this function must be split out.
|
// complete test coverage this function must be split out.
|
||||||
static void
|
static void
|
||||||
storagePosixInfoListEntry(
|
storagePosixListEntry(
|
||||||
StoragePosix *this, const String *path, const String *name, StorageInfoLevel level, StorageInfoListCallback callback,
|
StoragePosix *const this, StorageList *const list, const String *const path, const char *const name,
|
||||||
void *callbackData)
|
const StorageInfoLevel level)
|
||||||
{
|
{
|
||||||
FUNCTION_TEST_BEGIN();
|
FUNCTION_TEST_BEGIN();
|
||||||
FUNCTION_TEST_PARAM(STORAGE_POSIX, this);
|
FUNCTION_TEST_PARAM(STORAGE_POSIX, this);
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_LIST, list);
|
||||||
FUNCTION_TEST_PARAM(STRING, path);
|
FUNCTION_TEST_PARAM(STRING, path);
|
||||||
FUNCTION_TEST_PARAM(STRING, name);
|
FUNCTION_TEST_PARAM(STRINGZ, name);
|
||||||
FUNCTION_TEST_PARAM(ENUM, level);
|
FUNCTION_TEST_PARAM(ENUM, level);
|
||||||
FUNCTION_TEST_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_TEST_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_TEST_END();
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
|
ASSERT(list != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
ASSERT(name != NULL);
|
ASSERT(name != NULL);
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
StorageInfo storageInfo = storageInterfaceInfoP(
|
StorageInfo info = storageInterfaceInfoP(this, strNewFmt("%s/%s", strZ(path), name), level);
|
||||||
this, strEq(name, DOT_STR) ? strDup(path) : strNewFmt("%s/%s", strZ(path), strZ(name)), level);
|
|
||||||
|
|
||||||
if (storageInfo.exists)
|
if (info.exists)
|
||||||
{
|
{
|
||||||
storageInfo.name = name;
|
info.name = STR(name);
|
||||||
callback(callbackData, &storageInfo);
|
storageLstAdd(list, &info);
|
||||||
}
|
}
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static StorageList *
|
||||||
storagePosixInfoList(
|
storagePosixList(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
|
||||||
StorageInterfaceInfoListParam param)
|
|
||||||
{
|
{
|
||||||
THIS(StoragePosix);
|
THIS(StoragePosix);
|
||||||
|
|
||||||
@ -158,19 +154,16 @@ storagePosixInfoList(
|
|||||||
FUNCTION_LOG_PARAM(STORAGE_POSIX, this);
|
FUNCTION_LOG_PARAM(STORAGE_POSIX, this);
|
||||||
FUNCTION_LOG_PARAM(STRING, path);
|
FUNCTION_LOG_PARAM(STRING, path);
|
||||||
FUNCTION_LOG_PARAM(ENUM, level);
|
FUNCTION_LOG_PARAM(ENUM, level);
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
(void)param; // No parameters are used
|
(void)param; // No parameters are used
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
bool result = false;
|
StorageList *result = NULL;
|
||||||
|
|
||||||
// Open the directory for read
|
// Open the directory for read
|
||||||
DIR *dir = opendir(strZ(path));
|
DIR *const dir = opendir(strZ(path));
|
||||||
|
|
||||||
// If the directory could not be opened process errors and report missing directories
|
// If the directory could not be opened process errors and report missing directories
|
||||||
if (dir == NULL)
|
if (dir == NULL)
|
||||||
@ -178,34 +171,39 @@ storagePosixInfoList(
|
|||||||
if (errno != ENOENT) // {vm_covered}
|
if (errno != ENOENT) // {vm_covered}
|
||||||
THROW_SYS_ERROR_FMT(PathOpenError, STORAGE_ERROR_LIST_INFO, strZ(path)); // {vm_covered}
|
THROW_SYS_ERROR_FMT(PathOpenError, STORAGE_ERROR_LIST_INFO, strZ(path)); // {vm_covered}
|
||||||
}
|
}
|
||||||
|
// Directory was found
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Directory was found
|
result = storageLstNew(level);
|
||||||
result = true;
|
|
||||||
|
|
||||||
TRY_BEGIN()
|
TRY_BEGIN()
|
||||||
{
|
{
|
||||||
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
{
|
{
|
||||||
// Read the directory entries
|
// Read the directory entries
|
||||||
struct dirent *dirEntry = readdir(dir);
|
const struct dirent *dirEntry = readdir(dir);
|
||||||
|
|
||||||
while (dirEntry != NULL)
|
while (dirEntry != NULL)
|
||||||
{
|
{
|
||||||
const String *name = STR(dirEntry->d_name);
|
// Always skip . and ..
|
||||||
|
if (!strEqZ(DOT_STR, dirEntry->d_name) && !strEqZ(DOTDOT_STR, dirEntry->d_name))
|
||||||
// Always skip ..
|
|
||||||
if (!strEq(name, DOTDOT_STR))
|
|
||||||
{
|
{
|
||||||
// If only making a list of files that exist then no need to go get detailed info which requires calling
|
// If only making a list of files that exist then no need to go get detailed info which requires calling
|
||||||
// stat() and is therefore relatively slow
|
// stat() and is therefore relatively slow
|
||||||
if (level == storageInfoLevelExists)
|
if (level == storageInfoLevelExists)
|
||||||
{
|
{
|
||||||
callback(callbackData, &(StorageInfo){.name = name, .level = storageInfoLevelExists, .exists = true});
|
storageLstAdd(
|
||||||
|
result,
|
||||||
|
&(StorageInfo)
|
||||||
|
{
|
||||||
|
.name = STR(dirEntry->d_name),
|
||||||
|
.level = storageInfoLevelExists,
|
||||||
|
.exists = true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Else more info is required which requires a call to stat()
|
// Else more info is required which requires a call to stat()
|
||||||
else
|
else
|
||||||
storagePosixInfoListEntry(this, path, name, level, callback, callbackData);
|
storagePosixListEntry(this, result, path, dirEntry->d_name, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get next entry
|
// Get next entry
|
||||||
@ -224,7 +222,7 @@ storagePosixInfoList(
|
|||||||
TRY_END();
|
TRY_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, result);
|
FUNCTION_LOG_RETURN(STORAGE_LIST, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
@ -392,47 +390,6 @@ storagePosixPathCreate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
typedef struct StoragePosixPathRemoveData
|
|
||||||
{
|
|
||||||
StoragePosix *driver; // Driver
|
|
||||||
const String *path; // Path
|
|
||||||
} StoragePosixPathRemoveData;
|
|
||||||
|
|
||||||
static void
|
|
||||||
storagePosixPathRemoveCallback(void *const callbackData, const StorageInfo *const info)
|
|
||||||
{
|
|
||||||
FUNCTION_TEST_BEGIN();
|
|
||||||
FUNCTION_TEST_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_TEST_PARAM_P(STORAGE_INFO, info);
|
|
||||||
FUNCTION_TEST_END();
|
|
||||||
|
|
||||||
ASSERT(callbackData != NULL);
|
|
||||||
ASSERT(info != NULL);
|
|
||||||
|
|
||||||
if (!strEqZ(info->name, "."))
|
|
||||||
{
|
|
||||||
StoragePosixPathRemoveData *const data = callbackData;
|
|
||||||
String *const file = strNewFmt("%s/%s", strZ(data->path), strZ(info->name));
|
|
||||||
|
|
||||||
// Rather than stat the file to discover what type it is, just try to unlink it and see what happens
|
|
||||||
if (unlink(strZ(file)) == -1) // {vm_covered}
|
|
||||||
{
|
|
||||||
// These errors indicate that the entry is actually a path so we'll try to delete it that way
|
|
||||||
if (errno == EPERM || errno == EISDIR) // {uncovered_branch - no EPERM on tested systems}
|
|
||||||
{
|
|
||||||
storageInterfacePathRemoveP(data->driver, file, true);
|
|
||||||
}
|
|
||||||
// Else error
|
|
||||||
else
|
|
||||||
THROW_SYS_ERROR_FMT(PathRemoveError, STORAGE_ERROR_PATH_REMOVE_FILE, strZ(file)); // {vm_covered}
|
|
||||||
}
|
|
||||||
|
|
||||||
strFree(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
storagePosixPathRemove(THIS_VOID, const String *path, bool recurse, StorageInterfacePathRemoveParam param)
|
storagePosixPathRemove(THIS_VOID, const String *path, bool recurse, StorageInterfacePathRemoveParam param)
|
||||||
{
|
{
|
||||||
@ -455,14 +412,35 @@ storagePosixPathRemove(THIS_VOID, const String *path, bool recurse, StorageInter
|
|||||||
// Recurse if requested
|
// Recurse if requested
|
||||||
if (recurse)
|
if (recurse)
|
||||||
{
|
{
|
||||||
StoragePosixPathRemoveData data =
|
StorageList *const list = storageInterfaceListP(this, path, storageInfoLevelExists);
|
||||||
{
|
|
||||||
.driver = this,
|
|
||||||
.path = path,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Remove all sub paths/files
|
if (list != NULL)
|
||||||
storageInterfaceInfoListP(this, path, storageInfoLevelExists, storagePosixPathRemoveCallback, &data);
|
{
|
||||||
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
|
{
|
||||||
|
for (unsigned int listIdx = 0; listIdx < storageLstSize(list); listIdx++)
|
||||||
|
{
|
||||||
|
const String *const file = strNewFmt("%s/%s", strZ(path), strZ(storageLstGet(list, listIdx).name));
|
||||||
|
|
||||||
|
// Rather than stat the file to discover what type it is, just try to unlink it and see what happens
|
||||||
|
if (unlink(strZ(file)) == -1) // {vm_covered}
|
||||||
|
{
|
||||||
|
// These errors indicate that the entry is actually a path so we'll try to delete it that way
|
||||||
|
if (errno == EPERM || errno == EISDIR) // {uncovered_branch - no EPERM on tested systems}
|
||||||
|
{
|
||||||
|
storageInterfacePathRemoveP(this, file, true);
|
||||||
|
}
|
||||||
|
// Else error
|
||||||
|
else
|
||||||
|
THROW_SYS_ERROR_FMT(PathRemoveError, STORAGE_ERROR_PATH_REMOVE_FILE, strZ(file)); // {vm_covered}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
||||||
|
MEM_CONTEXT_TEMP_RESET(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the path
|
// Delete the path
|
||||||
@ -556,7 +534,7 @@ static const StorageInterface storageInterfacePosix =
|
|||||||
.feature = 1 << storageFeaturePath,
|
.feature = 1 << storageFeaturePath,
|
||||||
|
|
||||||
.info = storagePosixInfo,
|
.info = storagePosixInfo,
|
||||||
.infoList = storagePosixInfoList,
|
.list = storagePosixList,
|
||||||
.move = storagePosixMove,
|
.move = storagePosixMove,
|
||||||
.newRead = storagePosixNewRead,
|
.newRead = storagePosixNewRead,
|
||||||
.newWrite = storagePosixNewWrite,
|
.newWrite = storagePosixNewWrite,
|
||||||
|
@ -280,36 +280,8 @@ storageRemoteInfoProtocol(PackRead *const param, ProtocolServer *const server)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
typedef struct StorageRemoteProtocolInfoListCallbackData
|
|
||||||
{
|
|
||||||
ProtocolServer *const server;
|
|
||||||
StorageRemoteInfoProtocolWriteData writeData;
|
|
||||||
} StorageRemoteProtocolInfoListCallbackData;
|
|
||||||
|
|
||||||
static void
|
|
||||||
storageRemoteProtocolInfoListCallback(void *const dataVoid, const StorageInfo *const info)
|
|
||||||
{
|
|
||||||
FUNCTION_TEST_BEGIN();
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, dataVoid);
|
|
||||||
FUNCTION_LOG_PARAM(STORAGE_INFO, info);
|
|
||||||
FUNCTION_TEST_END();
|
|
||||||
|
|
||||||
ASSERT(dataVoid != NULL);
|
|
||||||
ASSERT(info != NULL);
|
|
||||||
|
|
||||||
StorageRemoteProtocolInfoListCallbackData *const data = dataVoid;
|
|
||||||
|
|
||||||
PackWrite *const write = protocolPackNew();
|
|
||||||
pckWriteStrP(write, info->name);
|
|
||||||
storageRemoteInfoProtocolPut(&data->writeData, write, info);
|
|
||||||
protocolServerDataPut(data->server, write);
|
|
||||||
pckWriteFree(write);
|
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
storageRemoteInfoListProtocol(PackRead *const param, ProtocolServer *const server)
|
storageRemoteListProtocol(PackRead *const param, ProtocolServer *const server)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(PACK_READ, param);
|
FUNCTION_LOG_PARAM(PACK_READ, param);
|
||||||
@ -324,15 +296,27 @@ storageRemoteInfoListProtocol(PackRead *const param, ProtocolServer *const serve
|
|||||||
{
|
{
|
||||||
const String *const path = pckReadStrP(param);
|
const String *const path = pckReadStrP(param);
|
||||||
const StorageInfoLevel level = (StorageInfoLevel)pckReadU32P(param);
|
const StorageInfoLevel level = (StorageInfoLevel)pckReadU32P(param);
|
||||||
|
StorageRemoteInfoProtocolWriteData writeData = {.memContext = memContextCurrent()};
|
||||||
|
StorageList *const list = storageInterfaceListP(storageRemoteProtocolLocal.driver, path, level);
|
||||||
|
|
||||||
StorageRemoteProtocolInfoListCallbackData data = {.server = server, .writeData = {.memContext = memContextCurrent()}};
|
// Put list
|
||||||
|
if (list != NULL)
|
||||||
|
{
|
||||||
|
for (unsigned int listIdx = 0; listIdx < storageLstSize(list); listIdx++)
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageLstGet(list, listIdx);
|
||||||
|
|
||||||
const bool result = storageInterfaceInfoListP(
|
PackWrite *const write = protocolPackNew();
|
||||||
storageRemoteProtocolLocal.driver, path, level, storageRemoteProtocolInfoListCallback, &data);
|
pckWriteStrP(write, info.name);
|
||||||
|
storageRemoteInfoProtocolPut(&writeData, write, &info);
|
||||||
|
protocolServerDataPut(server, write);
|
||||||
|
pckWriteFree(write);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Indicate whether or not the path was found
|
// Indicate whether or not the path was found
|
||||||
PackWrite *write = protocolPackNew();
|
PackWrite *write = protocolPackNew();
|
||||||
pckWriteBoolP(write, result, .defaultWrite = true);
|
pckWriteBoolP(write, list != NULL, .defaultWrite = true);
|
||||||
protocolServerDataPut(server, write);
|
protocolServerDataPut(server, write);
|
||||||
|
|
||||||
protocolServerDataEndPut(server);
|
protocolServerDataEndPut(server);
|
||||||
|
@ -13,7 +13,7 @@ Functions
|
|||||||
// Process storage protocol requests
|
// Process storage protocol requests
|
||||||
void storageRemoteFeatureProtocol(PackRead *param, ProtocolServer *server);
|
void storageRemoteFeatureProtocol(PackRead *param, ProtocolServer *server);
|
||||||
void storageRemoteInfoProtocol(PackRead *param, ProtocolServer *server);
|
void storageRemoteInfoProtocol(PackRead *param, ProtocolServer *server);
|
||||||
void storageRemoteInfoListProtocol(PackRead *param, ProtocolServer *server);
|
void storageRemoteListProtocol(PackRead *param, ProtocolServer *server);
|
||||||
void storageRemoteOpenReadProtocol(PackRead *param, ProtocolServer *server);
|
void storageRemoteOpenReadProtocol(PackRead *param, ProtocolServer *server);
|
||||||
void storageRemoteOpenWriteProtocol(PackRead *param, ProtocolServer *server);
|
void storageRemoteOpenWriteProtocol(PackRead *param, ProtocolServer *server);
|
||||||
void storageRemotePathCreateProtocol(PackRead *param, ProtocolServer *server);
|
void storageRemotePathCreateProtocol(PackRead *param, ProtocolServer *server);
|
||||||
@ -26,7 +26,7 @@ Protocol commands for ProtocolServerHandler arrays passed to protocolServerProce
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
#define PROTOCOL_COMMAND_STORAGE_FEATURE STRID5("s-f", 0x1b730)
|
#define PROTOCOL_COMMAND_STORAGE_FEATURE STRID5("s-f", 0x1b730)
|
||||||
#define PROTOCOL_COMMAND_STORAGE_INFO STRID5("s-i", 0x27730)
|
#define PROTOCOL_COMMAND_STORAGE_INFO STRID5("s-i", 0x27730)
|
||||||
#define PROTOCOL_COMMAND_STORAGE_INFO_LIST STRID5("s-l", 0x33730)
|
#define PROTOCOL_COMMAND_STORAGE_LIST STRID5("s-l", 0x33730)
|
||||||
#define PROTOCOL_COMMAND_STORAGE_OPEN_READ STRID5("s-or", 0x93f730)
|
#define PROTOCOL_COMMAND_STORAGE_OPEN_READ STRID5("s-or", 0x93f730)
|
||||||
#define PROTOCOL_COMMAND_STORAGE_OPEN_WRITE STRID5("s-ow", 0xbbf730)
|
#define PROTOCOL_COMMAND_STORAGE_OPEN_WRITE STRID5("s-ow", 0xbbf730)
|
||||||
#define PROTOCOL_COMMAND_STORAGE_PATH_CREATE STRID5("s-pc", 0x1c3730)
|
#define PROTOCOL_COMMAND_STORAGE_PATH_CREATE STRID5("s-pc", 0x1c3730)
|
||||||
@ -37,7 +37,7 @@ Protocol commands for ProtocolServerHandler arrays passed to protocolServerProce
|
|||||||
#define PROTOCOL_SERVER_HANDLER_STORAGE_REMOTE_LIST \
|
#define PROTOCOL_SERVER_HANDLER_STORAGE_REMOTE_LIST \
|
||||||
{.command = PROTOCOL_COMMAND_STORAGE_FEATURE, .handler = storageRemoteFeatureProtocol}, \
|
{.command = PROTOCOL_COMMAND_STORAGE_FEATURE, .handler = storageRemoteFeatureProtocol}, \
|
||||||
{.command = PROTOCOL_COMMAND_STORAGE_INFO, .handler = storageRemoteInfoProtocol}, \
|
{.command = PROTOCOL_COMMAND_STORAGE_INFO, .handler = storageRemoteInfoProtocol}, \
|
||||||
{.command = PROTOCOL_COMMAND_STORAGE_INFO_LIST, .handler = storageRemoteInfoListProtocol}, \
|
{.command = PROTOCOL_COMMAND_STORAGE_LIST, .handler = storageRemoteListProtocol}, \
|
||||||
{.command = PROTOCOL_COMMAND_STORAGE_OPEN_READ, .handler = storageRemoteOpenReadProtocol}, \
|
{.command = PROTOCOL_COMMAND_STORAGE_OPEN_READ, .handler = storageRemoteOpenReadProtocol}, \
|
||||||
{.command = PROTOCOL_COMMAND_STORAGE_OPEN_WRITE, .handler = storageRemoteOpenWriteProtocol}, \
|
{.command = PROTOCOL_COMMAND_STORAGE_OPEN_WRITE, .handler = storageRemoteOpenWriteProtocol}, \
|
||||||
{.command = PROTOCOL_COMMAND_STORAGE_PATH_CREATE, .handler = storageRemotePathCreateProtocol}, \
|
{.command = PROTOCOL_COMMAND_STORAGE_PATH_CREATE, .handler = storageRemotePathCreateProtocol}, \
|
||||||
|
@ -168,10 +168,8 @@ storageRemoteInfo(THIS_VOID, const String *file, StorageInfoLevel level, Storage
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
static bool
|
static StorageList *
|
||||||
storageRemoteInfoList(
|
storageRemoteList(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
|
||||||
StorageInterfaceInfoListParam param)
|
|
||||||
{
|
{
|
||||||
THIS(StorageRemote);
|
THIS(StorageRemote);
|
||||||
|
|
||||||
@ -179,20 +177,17 @@ storageRemoteInfoList(
|
|||||||
FUNCTION_LOG_PARAM(STORAGE_REMOTE, this);
|
FUNCTION_LOG_PARAM(STORAGE_REMOTE, this);
|
||||||
FUNCTION_LOG_PARAM(STRING, path);
|
FUNCTION_LOG_PARAM(STRING, path);
|
||||||
FUNCTION_LOG_PARAM(ENUM, level);
|
FUNCTION_LOG_PARAM(ENUM, level);
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
(void)param; // No parameters are used
|
(void)param; // No parameters are used
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
bool result = false;
|
StorageList *result = NULL;
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_STORAGE_INFO_LIST);
|
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_STORAGE_LIST);
|
||||||
PackWrite *const commandParam = protocolCommandParam(command);
|
PackWrite *const commandParam = protocolCommandParam(command);
|
||||||
|
|
||||||
pckWriteStrP(commandParam, path);
|
pckWriteStrP(commandParam, path);
|
||||||
@ -203,6 +198,7 @@ storageRemoteInfoList(
|
|||||||
|
|
||||||
// Read list
|
// Read list
|
||||||
StorageRemoteInfoData parseData = {.memContext = memContextCurrent()};
|
StorageRemoteInfoData parseData = {.memContext = memContextCurrent()};
|
||||||
|
result = storageLstNew(level);
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
{
|
{
|
||||||
@ -214,7 +210,7 @@ storageRemoteInfoList(
|
|||||||
StorageInfo info = {.exists = true, .level = level, .name = pckReadStrP(read)};
|
StorageInfo info = {.exists = true, .level = level, .name = pckReadStrP(read)};
|
||||||
|
|
||||||
storageRemoteInfoGet(&parseData, read, &info);
|
storageRemoteInfoGet(&parseData, read, &info);
|
||||||
callback(callbackData, &info);
|
storageLstAdd(result, &info);
|
||||||
|
|
||||||
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
// Reset the memory context occasionally so we don't use too much memory or slow down processing
|
||||||
MEM_CONTEXT_TEMP_RESET(1000);
|
MEM_CONTEXT_TEMP_RESET(1000);
|
||||||
@ -223,15 +219,17 @@ storageRemoteInfoList(
|
|||||||
pckReadNext(read);
|
pckReadNext(read);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = pckReadBoolP(read);
|
if (!pckReadBoolP(read))
|
||||||
|
result = NULL;
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
protocolClientDataEndGet(this->client);
|
protocolClientDataEndGet(this->client);
|
||||||
|
storageLstMove(result, memContextPrior());
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, result);
|
FUNCTION_LOG_RETURN(STORAGE_LIST, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
@ -422,7 +420,7 @@ storageRemoteRemove(THIS_VOID, const String *file, StorageInterfaceRemoveParam p
|
|||||||
static const StorageInterface storageInterfaceRemote =
|
static const StorageInterface storageInterfaceRemote =
|
||||||
{
|
{
|
||||||
.info = storageRemoteInfo,
|
.info = storageRemoteInfo,
|
||||||
.infoList = storageRemoteInfoList,
|
.list = storageRemoteList,
|
||||||
.newRead = storageRemoteNewRead,
|
.newRead = storageRemoteNewRead,
|
||||||
.newWrite = storageRemoteNewWrite,
|
.newWrite = storageRemoteNewWrite,
|
||||||
.pathCreate = storageRemotePathCreate,
|
.pathCreate = storageRemotePathCreate,
|
||||||
|
@ -608,7 +608,7 @@ General function for listing files to be used by other list routines
|
|||||||
static void
|
static void
|
||||||
storageS3ListInternal(
|
storageS3ListInternal(
|
||||||
StorageS3 *this, const String *path, StorageInfoLevel level, const String *expression, bool recurse,
|
StorageS3 *this, const String *path, StorageInfoLevel level, const String *expression, bool recurse,
|
||||||
StorageInfoListCallback callback, void *callbackData)
|
StorageListCallback callback, void *callbackData)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STORAGE_S3, this);
|
FUNCTION_LOG_PARAM(STORAGE_S3, this);
|
||||||
@ -813,10 +813,24 @@ storageS3Info(THIS_VOID, const String *const file, const StorageInfoLevel level,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
static bool
|
static void
|
||||||
storageS3InfoList(
|
storageS3ListCallback(void *const callbackData, const StorageInfo *const info)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
{
|
||||||
StorageInterfaceInfoListParam param)
|
FUNCTION_TEST_BEGIN();
|
||||||
|
FUNCTION_TEST_PARAM_P(VOID, callbackData);
|
||||||
|
FUNCTION_TEST_PARAM(STORAGE_INFO, info);
|
||||||
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
|
ASSERT(callbackData != NULL);
|
||||||
|
ASSERT(info != NULL);
|
||||||
|
|
||||||
|
storageLstAdd(callbackData, info);
|
||||||
|
|
||||||
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
static StorageList *
|
||||||
|
storageS3List(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
|
||||||
{
|
{
|
||||||
THIS(StorageS3);
|
THIS(StorageS3);
|
||||||
|
|
||||||
@ -824,18 +838,17 @@ storageS3InfoList(
|
|||||||
FUNCTION_LOG_PARAM(STORAGE_S3, this);
|
FUNCTION_LOG_PARAM(STORAGE_S3, this);
|
||||||
FUNCTION_LOG_PARAM(STRING, path);
|
FUNCTION_LOG_PARAM(STRING, path);
|
||||||
FUNCTION_LOG_PARAM(ENUM, level);
|
FUNCTION_LOG_PARAM(ENUM, level);
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_LOG_PARAM(STRING, param.expression);
|
FUNCTION_LOG_PARAM(STRING, param.expression);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
storageS3ListInternal(this, path, level, param.expression, false, callback, callbackData);
|
StorageList *const result = storageLstNew(level);
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, true);
|
storageS3ListInternal(this, path, level, param.expression, false, storageS3ListCallback, result);
|
||||||
|
|
||||||
|
FUNCTION_LOG_RETURN(STORAGE_LIST, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
@ -1066,7 +1079,7 @@ storageS3Remove(THIS_VOID, const String *const file, const StorageInterfaceRemov
|
|||||||
static const StorageInterface storageInterfaceS3 =
|
static const StorageInterface storageInterfaceS3 =
|
||||||
{
|
{
|
||||||
.info = storageS3Info,
|
.info = storageS3Info,
|
||||||
.infoList = storageS3InfoList,
|
.list = storageS3List,
|
||||||
.newRead = storageS3NewRead,
|
.newRead = storageS3NewRead,
|
||||||
.newWrite = storageS3NewWrite,
|
.newWrite = storageS3NewWrite,
|
||||||
.pathRemove = storageS3PathRemove,
|
.pathRemove = storageS3PathRemove,
|
||||||
|
@ -50,7 +50,7 @@ storageNew(
|
|||||||
ASSERT(strSize(path) >= 1 && strZ(path)[0] == '/');
|
ASSERT(strSize(path) >= 1 && strZ(path)[0] == '/');
|
||||||
ASSERT(driver != NULL);
|
ASSERT(driver != NULL);
|
||||||
ASSERT(interface.info != NULL);
|
ASSERT(interface.info != NULL);
|
||||||
ASSERT(interface.infoList != NULL);
|
ASSERT(interface.list != NULL);
|
||||||
ASSERT(interface.newRead != NULL);
|
ASSERT(interface.newRead != NULL);
|
||||||
ASSERT(interface.newWrite != NULL);
|
ASSERT(interface.newWrite != NULL);
|
||||||
ASSERT(interface.pathRemove != NULL);
|
ASSERT(interface.pathRemove != NULL);
|
||||||
@ -279,182 +279,26 @@ storageInfo(const Storage *this, const String *fileExp, StorageInfoParam param)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
typedef struct StorageInfoListSortData
|
StorageIterator *
|
||||||
{
|
storageNewItr(const Storage *const this, const String *const pathExp, StorageNewItrParam param)
|
||||||
MemContext *memContext; // Mem context to use for allocating data in this struct
|
|
||||||
StringList *ownerList; // List of users and groups to reduce memory usage
|
|
||||||
List *infoList; // List of info
|
|
||||||
} StorageInfoListSortData;
|
|
||||||
|
|
||||||
static void
|
|
||||||
storageInfoListSortCallback(void *data, const StorageInfo *info)
|
|
||||||
{
|
|
||||||
FUNCTION_TEST_BEGIN();
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, data);
|
|
||||||
FUNCTION_LOG_PARAM(STORAGE_INFO, info);
|
|
||||||
FUNCTION_TEST_END();
|
|
||||||
|
|
||||||
StorageInfoListSortData *infoData = data;
|
|
||||||
|
|
||||||
MEM_CONTEXT_BEGIN(infoData->memContext)
|
|
||||||
{
|
|
||||||
// Copy info and dup strings
|
|
||||||
StorageInfo infoCopy = *info;
|
|
||||||
infoCopy.name = strDup(info->name);
|
|
||||||
infoCopy.linkDestination = strDup(info->linkDestination);
|
|
||||||
infoCopy.user = strLstAddIfMissing(infoData->ownerList, info->user);
|
|
||||||
infoCopy.group = strLstAddIfMissing(infoData->ownerList, info->group);
|
|
||||||
|
|
||||||
lstAdd(infoData->infoList, &infoCopy);
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_END();
|
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
storageInfoListSort(
|
|
||||||
const Storage *this, const String *path, StorageInfoLevel level, const String *expression, SortOrder sortOrder,
|
|
||||||
StorageInfoListCallback callback, void *callbackData)
|
|
||||||
{
|
|
||||||
FUNCTION_LOG_BEGIN(logLevelTrace);
|
|
||||||
FUNCTION_LOG_PARAM(STORAGE, this);
|
|
||||||
FUNCTION_LOG_PARAM(STRING, path);
|
|
||||||
FUNCTION_LOG_PARAM(ENUM, level);
|
|
||||||
FUNCTION_LOG_PARAM(STRING, expression);
|
|
||||||
FUNCTION_LOG_PARAM(ENUM, sortOrder);
|
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_LOG_END();
|
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
|
||||||
ASSERT(callback != NULL);
|
|
||||||
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
|
||||||
{
|
|
||||||
// If no sorting then use the callback directly
|
|
||||||
if (sortOrder == sortOrderNone)
|
|
||||||
{
|
|
||||||
result = storageInterfaceInfoListP(storageDriver(this), path, level, callback, callbackData, .expression = expression);
|
|
||||||
}
|
|
||||||
// Else sort the info before sending it to the callback
|
|
||||||
else
|
|
||||||
{
|
|
||||||
StorageInfoListSortData data =
|
|
||||||
{
|
|
||||||
.memContext = MEM_CONTEXT_TEMP(),
|
|
||||||
.ownerList = strLstNew(),
|
|
||||||
.infoList = lstNewP(sizeof(StorageInfo), .comparator = lstComparatorStr),
|
|
||||||
};
|
|
||||||
|
|
||||||
result = storageInterfaceInfoListP(
|
|
||||||
storageDriver(this), path, level, storageInfoListSortCallback, &data, .expression = expression);
|
|
||||||
lstSort(data.infoList, sortOrder);
|
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
|
||||||
{
|
|
||||||
for (unsigned int infoIdx = 0; infoIdx < lstSize(data.infoList); infoIdx++)
|
|
||||||
{
|
|
||||||
// Pass info to the caller
|
|
||||||
callback(callbackData, lstGet(data.infoList, infoIdx));
|
|
||||||
|
|
||||||
// Reset the memory context occasionally
|
|
||||||
MEM_CONTEXT_TEMP_RESET(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_TEMP_END();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_TEMP_END();
|
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct StorageInfoListData
|
|
||||||
{
|
|
||||||
const Storage *storage; // Storage object;
|
|
||||||
StorageInfoListCallback callbackFunction; // Original callback function
|
|
||||||
void *callbackData; // Original callback data
|
|
||||||
const String *expression; // Filter for names
|
|
||||||
RegExp *regExp; // Compiled filter for names
|
|
||||||
bool recurse; // Should we recurse?
|
|
||||||
SortOrder sortOrder; // Sort order
|
|
||||||
const String *path; // Top-level path for info
|
|
||||||
const String *subPath; // Path below the top-level path (starts as NULL)
|
|
||||||
} StorageInfoListData;
|
|
||||||
|
|
||||||
static void
|
|
||||||
storageInfoListCallback(void *data, const StorageInfo *info)
|
|
||||||
{
|
|
||||||
FUNCTION_TEST_BEGIN();
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, data);
|
|
||||||
FUNCTION_LOG_PARAM(STORAGE_INFO, info);
|
|
||||||
FUNCTION_TEST_END();
|
|
||||||
|
|
||||||
StorageInfoListData *listData = data;
|
|
||||||
|
|
||||||
// Is this the . path?
|
|
||||||
bool dotPath = info->type == storageTypePath && strEq(info->name, DOT_STR);
|
|
||||||
|
|
||||||
// Skip . paths when getting info for subpaths (since info was already reported in the parent path)
|
|
||||||
if (dotPath && listData->subPath != NULL)
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
// Update the name in info with the subpath
|
|
||||||
StorageInfo infoUpdate = *info;
|
|
||||||
|
|
||||||
if (listData->subPath != NULL)
|
|
||||||
infoUpdate.name = strNewFmt("%s/%s", strZ(listData->subPath), strZ(infoUpdate.name));
|
|
||||||
|
|
||||||
// Is this file a match?
|
|
||||||
bool match = listData->expression == NULL || regExpMatch(listData->regExp, infoUpdate.name);
|
|
||||||
|
|
||||||
// Callback before checking path contents when not descending
|
|
||||||
if (match && listData->sortOrder != sortOrderDesc)
|
|
||||||
listData->callbackFunction(listData->callbackData, &infoUpdate);
|
|
||||||
|
|
||||||
// Recurse into paths
|
|
||||||
if (infoUpdate.type == storageTypePath && listData->recurse && !dotPath)
|
|
||||||
{
|
|
||||||
StorageInfoListData data = *listData;
|
|
||||||
data.subPath = infoUpdate.name;
|
|
||||||
|
|
||||||
storageInfoListSort(
|
|
||||||
data.storage, strNewFmt("%s/%s", strZ(data.path), strZ(data.subPath)), infoUpdate.level, data.expression,
|
|
||||||
data.sortOrder, storageInfoListCallback, &data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback after checking path contents when descending
|
|
||||||
if (match && listData->sortOrder == sortOrderDesc)
|
|
||||||
listData->callbackFunction(listData->callbackData, &infoUpdate);
|
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
storageInfoList(
|
|
||||||
const Storage *this, const String *pathExp, StorageInfoListCallback callback, void *callbackData, StorageInfoListParam param)
|
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STORAGE, this);
|
FUNCTION_LOG_PARAM(STORAGE, this);
|
||||||
FUNCTION_LOG_PARAM(STRING, pathExp);
|
FUNCTION_LOG_PARAM(STRING, pathExp);
|
||||||
FUNCTION_LOG_PARAM(FUNCTIONP, callback);
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, callbackData);
|
|
||||||
FUNCTION_LOG_PARAM(ENUM, param.level);
|
FUNCTION_LOG_PARAM(ENUM, param.level);
|
||||||
FUNCTION_LOG_PARAM(BOOL, param.errorOnMissing);
|
FUNCTION_LOG_PARAM(BOOL, param.errorOnMissing);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, param.recurse);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, param.nullOnMissing);
|
||||||
FUNCTION_LOG_PARAM(ENUM, param.sortOrder);
|
FUNCTION_LOG_PARAM(ENUM, param.sortOrder);
|
||||||
FUNCTION_LOG_PARAM(STRING, param.expression);
|
FUNCTION_LOG_PARAM(STRING, param.expression);
|
||||||
FUNCTION_LOG_PARAM(BOOL, param.recurse);
|
FUNCTION_LOG_PARAM(BOOL, param.recurse);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(callback != NULL);
|
ASSERT(this->pub.interface.list != NULL);
|
||||||
ASSERT(this->pub.interface.infoList != NULL);
|
|
||||||
ASSERT(!param.errorOnMissing || storageFeature(this, storageFeaturePath));
|
ASSERT(!param.errorOnMissing || storageFeature(this, storageFeaturePath));
|
||||||
|
|
||||||
bool result = false;
|
StorageIterator *result = NULL;
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
@ -462,58 +306,18 @@ storageInfoList(
|
|||||||
if (param.level == storageInfoLevelDefault)
|
if (param.level == storageInfoLevelDefault)
|
||||||
param.level = storageFeature(this, storageFeatureInfoDetail) ? storageInfoLevelDetail : storageInfoLevelBasic;
|
param.level = storageFeature(this, storageFeatureInfoDetail) ? storageInfoLevelDetail : storageInfoLevelBasic;
|
||||||
|
|
||||||
// Build the path
|
result = storageItrMove(
|
||||||
String *path = storagePathP(this, pathExp);
|
storageItrNew(
|
||||||
|
storageDriver(this), storagePathP(this, pathExp), param.level, param.errorOnMissing, param.nullOnMissing,
|
||||||
// If there is an expression or recursion then the info will need to be filtered through a local callback
|
param.recurse, param.sortOrder, param.expression),
|
||||||
if (param.expression != NULL || param.recurse)
|
memContextPrior());
|
||||||
{
|
|
||||||
StorageInfoListData data =
|
|
||||||
{
|
|
||||||
.storage = this,
|
|
||||||
.callbackFunction = callback,
|
|
||||||
.callbackData = callbackData,
|
|
||||||
.expression = param.expression,
|
|
||||||
.sortOrder = param.sortOrder,
|
|
||||||
.recurse = param.recurse,
|
|
||||||
.path = path,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.expression != NULL)
|
|
||||||
data.regExp = regExpNew(param.expression);
|
|
||||||
|
|
||||||
result = storageInfoListSort(
|
|
||||||
this, path, param.level, param.expression, param.sortOrder, storageInfoListCallback, &data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result = storageInfoListSort(this, path, param.level, NULL, param.sortOrder, callback, callbackData);
|
|
||||||
|
|
||||||
if (!result && param.errorOnMissing)
|
|
||||||
THROW_FMT(PathMissingError, STORAGE_ERROR_LIST_INFO_MISSING, strZ(path));
|
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
FUNCTION_LOG_RETURN(BOOL, result);
|
FUNCTION_LOG_RETURN(STORAGE_ITERATOR, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
static void
|
|
||||||
storageListCallback(void *data, const StorageInfo *info)
|
|
||||||
{
|
|
||||||
FUNCTION_TEST_BEGIN();
|
|
||||||
FUNCTION_LOG_PARAM_P(VOID, data);
|
|
||||||
FUNCTION_LOG_PARAM(STORAGE_INFO, info);
|
|
||||||
FUNCTION_TEST_END();
|
|
||||||
|
|
||||||
// Skip . path
|
|
||||||
if (strEq(info->name, DOT_STR))
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
|
|
||||||
strLstAdd((StringList *)data, info->name);
|
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
|
||||||
}
|
|
||||||
|
|
||||||
StringList *
|
StringList *
|
||||||
storageList(const Storage *this, const String *pathExp, StorageListParam param)
|
storageList(const Storage *this, const String *pathExp, StorageListParam param)
|
||||||
{
|
{
|
||||||
@ -527,26 +331,24 @@ storageList(const Storage *this, const String *pathExp, StorageListParam param)
|
|||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
ASSERT(!param.errorOnMissing || !param.nullOnMissing);
|
ASSERT(!param.errorOnMissing || !param.nullOnMissing);
|
||||||
ASSERT(!param.errorOnMissing || storageFeature(this, storageFeaturePath));
|
|
||||||
|
|
||||||
StringList *result = NULL;
|
StringList *result = NULL;
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
|
{
|
||||||
|
StorageIterator *const storageItr = storageNewItrP(
|
||||||
|
this, pathExp, .errorOnMissing = param.errorOnMissing, .nullOnMissing = param.nullOnMissing,
|
||||||
|
.expression = param.expression);
|
||||||
|
|
||||||
|
if (storageItr != NULL)
|
||||||
{
|
{
|
||||||
result = strLstNew();
|
result = strLstNew();
|
||||||
|
|
||||||
// Build an empty list if the directory does not exist by default. This makes the logic in calling functions simpler when
|
while (storageItrMore(storageItr))
|
||||||
// the caller doesn't care if the path is missing.
|
strLstAdd(result, storageItrNext(storageItr).name);
|
||||||
if (!storageInfoListP(
|
|
||||||
this, pathExp, storageListCallback, result, .level = storageInfoLevelExists, .errorOnMissing = param.errorOnMissing,
|
|
||||||
.expression = param.expression))
|
|
||||||
{
|
|
||||||
if (param.nullOnMissing)
|
|
||||||
result = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move list up to the old context
|
strLstMove(result, memContextPrior());
|
||||||
result = strLstMove(result, memContextPrior());
|
}
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ typedef struct Storage Storage;
|
|||||||
#include "common/time.h"
|
#include "common/time.h"
|
||||||
#include "common/type/param.h"
|
#include "common/type/param.h"
|
||||||
#include "storage/info.h"
|
#include "storage/info.h"
|
||||||
|
#include "storage/iterator.h"
|
||||||
#include "storage/read.h"
|
#include "storage/read.h"
|
||||||
#include "storage/storage.intern.h"
|
#include "storage/storage.intern.h"
|
||||||
#include "storage/write.h"
|
#include "storage/write.h"
|
||||||
@ -94,24 +95,22 @@ typedef struct StorageInfoParam
|
|||||||
|
|
||||||
StorageInfo storageInfo(const Storage *this, const String *fileExp, StorageInfoParam param);
|
StorageInfo storageInfo(const Storage *this, const String *fileExp, StorageInfoParam param);
|
||||||
|
|
||||||
// Info for all files/paths in a path
|
// Iterator for all files/links/paths in a path which returns different info based on the value of the level parameter
|
||||||
typedef void (*StorageInfoListCallback)(void *callbackData, const StorageInfo *info);
|
typedef struct StorageNewItrParam
|
||||||
|
|
||||||
typedef struct StorageInfoListParam
|
|
||||||
{
|
{
|
||||||
VAR_PARAM_HEADER;
|
VAR_PARAM_HEADER;
|
||||||
StorageInfoLevel level;
|
StorageInfoLevel level;
|
||||||
bool errorOnMissing;
|
bool errorOnMissing;
|
||||||
|
bool nullOnMissing;
|
||||||
bool recurse;
|
bool recurse;
|
||||||
SortOrder sortOrder;
|
SortOrder sortOrder;
|
||||||
const String *expression;
|
const String *expression;
|
||||||
} StorageInfoListParam;
|
} StorageNewItrParam;
|
||||||
|
|
||||||
#define storageInfoListP(this, fileExp, callback, callbackData, ...) \
|
#define storageNewItrP(this, fileExp, ...) \
|
||||||
storageInfoList(this, fileExp, callback, callbackData, (StorageInfoListParam){VAR_PARAM_INIT, __VA_ARGS__})
|
storageNewItr(this, fileExp, (StorageNewItrParam){VAR_PARAM_INIT, __VA_ARGS__})
|
||||||
|
|
||||||
bool storageInfoList(
|
StorageIterator *storageNewItr(const Storage *this, const String *pathExp, StorageNewItrParam param);
|
||||||
const Storage *this, const String *pathExp, StorageInfoListCallback callback, void *callbackData, StorageInfoListParam param);
|
|
||||||
|
|
||||||
// Get a list of files from a directory
|
// Get a list of files from a directory
|
||||||
typedef struct StorageListParam
|
typedef struct StorageListParam
|
||||||
|
@ -15,6 +15,7 @@ in the description of each function.
|
|||||||
|
|
||||||
#include "common/type/param.h"
|
#include "common/type/param.h"
|
||||||
#include "storage/info.h"
|
#include "storage/info.h"
|
||||||
|
#include "storage/list.h"
|
||||||
#include "storage/read.h"
|
#include "storage/read.h"
|
||||||
#include "storage/write.h"
|
#include "storage/write.h"
|
||||||
|
|
||||||
@ -57,6 +58,11 @@ Path expression callback function type - used to modify paths based on expressio
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
typedef String *StoragePathExpressionCallback(const String *expression, const String *path);
|
typedef String *StoragePathExpressionCallback(const String *expression, const String *path);
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Storage info callback function type - used to return storage info
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
typedef void (*StorageListCallback)(void *callbackData, const StorageInfo *info);
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Required interface functions
|
Required interface functions
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
@ -143,30 +149,29 @@ typedef StorageWrite *StorageInterfaceNewWrite(void *thisVoid, const String *fil
|
|||||||
STORAGE_COMMON_INTERFACE(thisVoid).newWrite(thisVoid, file, (StorageInterfaceNewWriteParam){VAR_PARAM_INIT, __VA_ARGS__})
|
STORAGE_COMMON_INTERFACE(thisVoid).newWrite(thisVoid, file, (StorageInterfaceNewWriteParam){VAR_PARAM_INIT, __VA_ARGS__})
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------------------
|
||||||
// Get info for a path and all paths/files in the path (does not recurse)
|
// Get info for all files/links/paths in a path (does not recurse or return info about the path passed to the function)
|
||||||
//
|
//
|
||||||
// See storageInterfaceInfoP() for usage of the level parameter.
|
// See storageInterfaceInfoP() for usage of the level parameter.
|
||||||
typedef struct StorageInterfaceInfoListParam
|
typedef struct StorageInterfaceListParam
|
||||||
{
|
{
|
||||||
VAR_PARAM_HEADER;
|
VAR_PARAM_HEADER;
|
||||||
|
|
||||||
// Regular expression used to filter the results. The expression is always checked in the callback passed to
|
// Regular expression used to filter the results. The expression is always checked in the callback passed to
|
||||||
// storageInterfaceInfoListP() so checking the expression in the driver is entirely optional. The driver should only use the
|
// storageInterfaceIterP() so checking the expression in the driver is entirely optional. The driver should only use the
|
||||||
// expression if it can improve performance or limit network transfer.
|
// expression if it can improve performance or limit network transfer.
|
||||||
//
|
//
|
||||||
// Partial matching of the expression is fine as long as nothing that should match is excluded, e.g. it is OK to prefix match
|
// Partial matching of the expression is fine as long as nothing that should match is excluded, e.g. it is OK to prefix match
|
||||||
// using the prefix returned from regExpPrefix(). This may cause extra results to be sent to the callback but won't exclude
|
// using the prefix returned from regExpPrefix(). This may cause extra results to be sent to the callback but won't exclude
|
||||||
// anything that matches the expression exactly.
|
// anything that matches the expression exactly.
|
||||||
const String *expression;
|
const String *expression;
|
||||||
} StorageInterfaceInfoListParam;
|
} StorageInterfaceListParam;
|
||||||
|
|
||||||
typedef bool StorageInterfaceInfoList(
|
typedef StorageList *StorageInterfaceList(
|
||||||
void *thisVoid, const String *path, StorageInfoLevel level, void (*callback)(void *callbackData, const StorageInfo *info),
|
void *thisVoid, const String *path, StorageInfoLevel level, StorageInterfaceListParam param);
|
||||||
void *callbackData, StorageInterfaceInfoListParam param);
|
|
||||||
|
|
||||||
#define storageInterfaceInfoListP(thisVoid, path, level, callback, callbackData, ...) \
|
#define storageInterfaceListP(thisVoid, path, level, ...) \
|
||||||
STORAGE_COMMON_INTERFACE(thisVoid).infoList( \
|
STORAGE_COMMON_INTERFACE(thisVoid).list( \
|
||||||
thisVoid, path, level, callback, callbackData, (StorageInterfaceInfoListParam){VAR_PARAM_INIT, __VA_ARGS__})
|
thisVoid, path, level, (StorageInterfaceListParam){VAR_PARAM_INIT, __VA_ARGS__})
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------------------
|
||||||
// Remove a path (and optionally recurse)
|
// Remove a path (and optionally recurse)
|
||||||
@ -263,7 +268,7 @@ typedef struct StorageInterface
|
|||||||
|
|
||||||
// Required functions
|
// Required functions
|
||||||
StorageInterfaceInfo *info;
|
StorageInterfaceInfo *info;
|
||||||
StorageInterfaceInfoList *infoList;
|
StorageInterfaceList *list;
|
||||||
StorageInterfaceNewRead *newRead;
|
StorageInterfaceNewRead *newRead;
|
||||||
StorageInterfaceNewWrite *newWrite;
|
StorageInterfaceNewWrite *newWrite;
|
||||||
StorageInterfacePathRemove *pathRemove;
|
StorageInterfacePathRemove *pathRemove;
|
||||||
|
@ -134,6 +134,13 @@ unit:
|
|||||||
coverage:
|
coverage:
|
||||||
- common/type/object
|
- common/type/object
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
- name: type-blob
|
||||||
|
total: 1
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- common/type/blob
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------------
|
||||||
- name: type-string
|
- name: type-string
|
||||||
total: 27
|
total: 27
|
||||||
@ -305,6 +312,8 @@ unit:
|
|||||||
- storage/posix/read
|
- storage/posix/read
|
||||||
- storage/posix/storage
|
- storage/posix/storage
|
||||||
- storage/posix/write
|
- storage/posix/write
|
||||||
|
- storage/iterator
|
||||||
|
- storage/list
|
||||||
- storage/read
|
- storage/read
|
||||||
- storage/storage
|
- storage/storage
|
||||||
- storage/write
|
- storage/write
|
||||||
@ -483,6 +492,8 @@ unit:
|
|||||||
- storage/posix/storage
|
- storage/posix/storage
|
||||||
- storage/posix/write
|
- storage/posix/write
|
||||||
- storage/helper
|
- storage/helper
|
||||||
|
- storage/iterator
|
||||||
|
- storage/list
|
||||||
- storage/read
|
- storage/read
|
||||||
- storage/storage
|
- storage/storage
|
||||||
- storage/write
|
- storage/write
|
||||||
|
@ -29,12 +29,10 @@ storageTestDummyInfo(THIS_VOID, const String *file, StorageInfoLevel level, Stor
|
|||||||
(void)thisVoid; (void)file; (void)level; (void)param; return (StorageInfo){.exists = false};
|
(void)thisVoid; (void)file; (void)level; (void)param; return (StorageInfo){.exists = false};
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static StorageList *
|
||||||
storageTestDummyInfoList(
|
storageTestDummyList(THIS_VOID, const String *path, StorageInfoLevel level, StorageInterfaceListParam param)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
|
||||||
StorageInterfaceInfoListParam param)
|
|
||||||
{
|
{
|
||||||
(void)thisVoid; (void)path; (void)level; (void)callback; (void)callbackData; (void)param; return false;
|
(void)thisVoid; (void)path; (void)level; (void)param; return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static StorageRead *
|
static StorageRead *
|
||||||
@ -64,7 +62,7 @@ storageTestDummyRemove(THIS_VOID, const String *file, StorageInterfaceRemovePara
|
|||||||
const StorageInterface storageInterfaceTestDummy =
|
const StorageInterface storageInterfaceTestDummy =
|
||||||
{
|
{
|
||||||
.info = storageTestDummyInfo,
|
.info = storageTestDummyInfo,
|
||||||
.infoList = storageTestDummyInfoList,
|
.list = storageTestDummyList,
|
||||||
.newRead = storageTestDummyNewRead,
|
.newRead = storageTestDummyNewRead,
|
||||||
.newWrite = storageTestDummyNewWrite,
|
.newWrite = storageTestDummyNewWrite,
|
||||||
.pathRemove = storageTestDummyPathRemove,
|
.pathRemove = storageTestDummyPathRemove,
|
||||||
@ -140,28 +138,18 @@ testStorageExists(const Storage *const storage, const char *const file, const Te
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
static void
|
|
||||||
hrnStorageListCallback(void *list, const StorageInfo *info)
|
|
||||||
{
|
|
||||||
MEM_CONTEXT_BEGIN(lstMemContext(list))
|
|
||||||
{
|
|
||||||
StorageInfo infoCopy = *info;
|
|
||||||
infoCopy.name = strDup(infoCopy.name);
|
|
||||||
infoCopy.user = strDup(infoCopy.user);
|
|
||||||
infoCopy.group = strDup(infoCopy.group);
|
|
||||||
infoCopy.linkDestination = strDup(infoCopy.linkDestination);
|
|
||||||
|
|
||||||
lstAdd(list, &infoCopy);
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_END();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
hrnStorageList(const Storage *const storage, const char *const path, const char *const expected, const HrnStorageListParam param)
|
hrnStorageList(const Storage *const storage, const char *const path, const char *const expected, const HrnStorageListParam param)
|
||||||
{
|
{
|
||||||
// Check if paths are supported
|
// Check if paths are supported
|
||||||
const bool featurePath = storageFeature(storage, storageFeaturePath);
|
const bool featurePath = storageFeature(storage, storageFeaturePath);
|
||||||
|
|
||||||
|
// Determine sort order
|
||||||
|
const SortOrder sortOrder = param.sortOrder == sortOrderNone ? sortOrderAsc : param.sortOrder;
|
||||||
|
|
||||||
|
// Determine level
|
||||||
|
StorageInfoLevel level = param.level == storageInfoLevelDefault && !param.levelForce ? storageInfoLevelType : param.level;
|
||||||
|
|
||||||
// Log list test
|
// Log list test
|
||||||
hrnTestResultBegin(__func__, false);
|
hrnTestResultBegin(__func__, false);
|
||||||
|
|
||||||
@ -173,37 +161,41 @@ hrnStorageList(const Storage *const storage, const char *const path, const char
|
|||||||
hrnTestResultComment(param.comment);
|
hrnTestResultComment(param.comment);
|
||||||
|
|
||||||
// Generate a list of files/paths/etc
|
// Generate a list of files/paths/etc
|
||||||
List *list = lstNewP(sizeof(StorageInfo));
|
StorageList *const list = storageLstNew(level == storageInfoLevelDefault ? storageInfoLevelDetail : level);
|
||||||
|
|
||||||
storageInfoListP(
|
StorageIterator *const storageItr = storageNewItrP(
|
||||||
storage, pathFull, hrnStorageListCallback, list, .recurse = !param.noRecurse,
|
storage, pathFull, .recurse = !param.noRecurse, .sortOrder = sortOrder, .level = level,
|
||||||
.sortOrder = param.sortOrder == sortOrderNone ? sortOrderAsc : param.sortOrder,
|
|
||||||
.level = param.level == storageInfoLevelDefault && !param.levelForce ? storageInfoLevelType : param.level,
|
|
||||||
.expression = param.expression != NULL ? STR(param.expression) : NULL);
|
.expression = param.expression != NULL ? STR(param.expression) : NULL);
|
||||||
|
|
||||||
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
StorageInfo info = storageItrNext(storageItr);
|
||||||
|
storageLstAdd(list, &info);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove files if requested
|
// Remove files if requested
|
||||||
if (param.remove)
|
if (param.remove)
|
||||||
{
|
{
|
||||||
for (unsigned int listIdx = 0; listIdx < lstSize(list); listIdx++)
|
for (unsigned int listIdx = 0; listIdx < storageLstSize(list); listIdx++)
|
||||||
{
|
{
|
||||||
const StorageInfo *const info = lstGet(list, listIdx);
|
const StorageInfo info = storageLstGet(list, listIdx);
|
||||||
|
|
||||||
if (strEq(info->name, DOT_STR))
|
if (strEq(info.name, DOT_STR))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Only remove at the top level since path remove will recurse
|
// Only remove at the top level since path remove will recurse
|
||||||
if (strChr(info->name, '/') == -1)
|
if (strChr(info.name, '/') == -1)
|
||||||
{
|
{
|
||||||
// Remove a path recursively
|
// Remove a path recursively
|
||||||
if (info->type == storageTypePath)
|
if (info.type == storageTypePath)
|
||||||
{
|
{
|
||||||
storagePathRemoveP(
|
storagePathRemoveP(
|
||||||
storage, strNewFmt("%s/%s", strZ(pathFull), strZ(info->name)), .errorOnMissing = featurePath,
|
storage, strNewFmt("%s/%s", strZ(pathFull), strZ(info.name)), .errorOnMissing = featurePath,
|
||||||
.recurse = true);
|
.recurse = true);
|
||||||
}
|
}
|
||||||
// Remove file, link, or special
|
// Remove file, link, or special
|
||||||
else
|
else
|
||||||
storageRemoveP(storage, strNewFmt("%s/%s", strZ(pathFull), strZ(info->name)), .errorOnMissing = true);
|
storageRemoveP(storage, strNewFmt("%s/%s", strZ(pathFull), strZ(info.name)), .errorOnMissing = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,15 +203,29 @@ hrnStorageList(const Storage *const storage, const char *const path, const char
|
|||||||
// Generate list for comparison
|
// Generate list for comparison
|
||||||
StringList *listStr = strLstNew();
|
StringList *listStr = strLstNew();
|
||||||
|
|
||||||
for (unsigned int listIdx = 0; listIdx < lstSize(list); listIdx++)
|
if (level == storageInfoLevelDefault)
|
||||||
{
|
level = storageFeature(storage, storageFeatureInfoDetail) ? storageInfoLevelDetail : storageInfoLevelBasic;
|
||||||
const StorageInfo *const info = lstGet(list, listIdx);
|
|
||||||
String *const item = strCat(strNew(), info->name);
|
|
||||||
|
|
||||||
if (strEq(info->name, DOT_STR) && !param.includeDot)
|
if (param.includeDot)
|
||||||
|
{
|
||||||
|
StorageInfo info = storageInfoP(storage, pathFull);
|
||||||
|
info.name = DOT_STR;
|
||||||
|
|
||||||
|
if (sortOrder == sortOrderAsc)
|
||||||
|
storageLstInsert(list, 0, &info);
|
||||||
|
else
|
||||||
|
storageLstAdd(list, &info);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int listIdx = 0; listIdx < storageLstSize(list); listIdx++)
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageLstGet(list, listIdx);
|
||||||
|
String *const item = strCat(strNew(), info.name);
|
||||||
|
|
||||||
|
if (strEq(info.name, DOT_STR) && !param.includeDot)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
switch (info->type)
|
switch (info.type)
|
||||||
{
|
{
|
||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
break;
|
break;
|
||||||
@ -237,40 +243,40 @@ hrnStorageList(const Storage *const storage, const char *const path, const char
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (((info->type == storageTypeFile || info->type == storageTypeLink) && info->level >= storageInfoLevelBasic) ||
|
if (((info.type == storageTypeFile || info.type == storageTypeLink) && level >= storageInfoLevelBasic) ||
|
||||||
(info->type == storageTypePath && info->level >= storageInfoLevelDetail))
|
(info.type == storageTypePath && level >= storageInfoLevelDetail))
|
||||||
{
|
{
|
||||||
strCatZ(item, " {");
|
strCatZ(item, " {");
|
||||||
|
|
||||||
if (info->type == storageTypeFile)
|
if (info.type == storageTypeFile)
|
||||||
strCatFmt(item, "s=%" PRIu64 ", t=%" PRId64, info->size, (int64_t)info->timeModified);
|
strCatFmt(item, "s=%" PRIu64 ", t=%" PRId64, info.size, (int64_t)info.timeModified);
|
||||||
else if (info->type == storageTypeLink)
|
else if (info.type == storageTypeLink)
|
||||||
{
|
{
|
||||||
const StorageInfo infoLink = storageInfoP(
|
const StorageInfo infoLink = storageInfoP(
|
||||||
storage,
|
storage,
|
||||||
strEq(info->name, DOT_STR) ? pathFull : strNewFmt("%s/%s", strZ(pathFull), strZ(info->name)),
|
strEq(info.name, DOT_STR) ? pathFull : strNewFmt("%s/%s", strZ(pathFull), strZ(info.name)),
|
||||||
.level = storageInfoLevelDetail);
|
.level = storageInfoLevelDetail);
|
||||||
|
|
||||||
strCatFmt(item, "d=%s", strZ(infoLink.linkDestination));
|
strCatFmt(item, "d=%s", strZ(infoLink.linkDestination));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->level >= storageInfoLevelDetail)
|
if (level >= storageInfoLevelDetail)
|
||||||
{
|
{
|
||||||
if (info->type != storageTypePath)
|
if (info.type != storageTypePath)
|
||||||
strCatZ(item, ", ");
|
strCatZ(item, ", ");
|
||||||
|
|
||||||
if (info->user != NULL)
|
if (info.user != NULL)
|
||||||
strCatFmt(item, "u=%s", strZ(info->user));
|
strCatFmt(item, "u=%s", strZ(info.user));
|
||||||
else
|
else
|
||||||
strCatFmt(item, "u=%d", (int)info->userId);
|
strCatFmt(item, "u=%d", (int)info.userId);
|
||||||
|
|
||||||
if (info->group != NULL)
|
if (info.group != NULL)
|
||||||
strCatFmt(item, ", g=%s", strZ(info->group));
|
strCatFmt(item, ", g=%s", strZ(info.group));
|
||||||
else
|
else
|
||||||
strCatFmt(item, ", g=%d", (int)info->groupId);
|
strCatFmt(item, ", g=%d", (int)info.groupId);
|
||||||
|
|
||||||
if (info->type != storageTypeLink)
|
if (info.type != storageTypeLink)
|
||||||
strCatFmt(item, ", m=%04o", info->mode);
|
strCatFmt(item, ", m=%04o", info.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
strCatZ(item, "}");
|
strCatZ(item, "}");
|
||||||
|
@ -19,98 +19,100 @@ Test Backup Command
|
|||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Get a list of all files in the backup and a redacted version of the manifest that can be tested against a static string
|
Get a list of all files in the backup and a redacted version of the manifest that can be tested against a static string
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
typedef struct TestBackupValidateCallbackData
|
static String *
|
||||||
|
testBackupValidateList(
|
||||||
|
const Storage *const storage, const String *const path, Manifest *const manifest, const ManifestData *const manifestData,
|
||||||
|
String *const result)
|
||||||
{
|
{
|
||||||
const Storage *storage; // Storage object when needed (e.g. fileCompressed = true)
|
// Output root path if it is a link so we can verify the destination
|
||||||
const String *path; // Subpath when storage is specified
|
const StorageInfo dotInfo = storageInfoP(storage, path);
|
||||||
Manifest *manifest; // Manifest to check for files/links/paths
|
|
||||||
const ManifestData *manifestData; // Manifest data
|
|
||||||
String *content; // String where content should be added
|
|
||||||
} TestBackupValidateCallbackData;
|
|
||||||
|
|
||||||
static void
|
if (dotInfo.type == storageTypeLink)
|
||||||
testBackupValidateCallback(void *callbackData, const StorageInfo *info)
|
strCatFmt(result, ". {link, d=%s}\n", strZ(dotInfo.linkDestination));
|
||||||
{
|
|
||||||
TestBackupValidateCallbackData *data = callbackData;
|
|
||||||
|
|
||||||
// Don't include . when it is a path (we'll still include it when it is a link so we can see the destination)
|
// Output path contents
|
||||||
if (info->type == storageTypePath && strEq(info->name, DOT_STR))
|
StorageIterator *const storageItr = storageNewItrP(storage, path, .recurse = true, .sortOrder = sortOrderAsc);
|
||||||
return;
|
|
||||||
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
const StorageInfo info = storageItrNext(storageItr);
|
||||||
|
|
||||||
// Don't include backup.manifest or copy. We'll test that they are present elsewhere
|
// Don't include backup.manifest or copy. We'll test that they are present elsewhere
|
||||||
if (info->type == storageTypeFile &&
|
if (info.type == storageTypeFile &&
|
||||||
(strEqZ(info->name, BACKUP_MANIFEST_FILE) || strEqZ(info->name, BACKUP_MANIFEST_FILE INFO_COPY_EXT)))
|
(strEqZ(info.name, BACKUP_MANIFEST_FILE) || strEqZ(info.name, BACKUP_MANIFEST_FILE INFO_COPY_EXT)))
|
||||||
return;
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch (info->type)
|
switch (info.type)
|
||||||
{
|
{
|
||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
{
|
{
|
||||||
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
||||||
// mode and current user/group.
|
// mode and current user/group.
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
if (info->mode != 0640)
|
if (info.mode != 0640)
|
||||||
THROW_FMT(AssertError, "'%s' mode is not 0640", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' mode is not 0640", strZ(info.name));
|
||||||
|
|
||||||
if (!strEq(info->user, TEST_USER_STR))
|
if (!strEq(info.user, TEST_USER_STR))
|
||||||
THROW_FMT(AssertError, "'%s' user should be '" TEST_USER "'", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' user should be '" TEST_USER "'", strZ(info.name));
|
||||||
|
|
||||||
if (!strEq(info->group, TEST_GROUP_STR))
|
if (!strEq(info.group, TEST_GROUP_STR))
|
||||||
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info.name));
|
||||||
|
|
||||||
// Build file list (needed because bundles can contain multiple files)
|
// Build file list (needed because bundles can contain multiple files)
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
List *const fileList = lstNewP(sizeof(ManifestFilePack **));
|
List *const fileList = lstNewP(sizeof(ManifestFilePack **));
|
||||||
bool bundle = strBeginsWithZ(info->name, "bundle/");
|
bool bundle = strBeginsWithZ(info.name, "bundle/");
|
||||||
|
|
||||||
if (bundle)
|
if (bundle)
|
||||||
{
|
{
|
||||||
const uint64_t bundleId = cvtZToUInt64(strZ(info->name) + sizeof("bundle"));
|
const uint64_t bundleId = cvtZToUInt64(strZ(info.name) + sizeof("bundle"));
|
||||||
|
|
||||||
for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(data->manifest); fileIdx++)
|
for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(manifest); fileIdx++)
|
||||||
{
|
{
|
||||||
ManifestFilePack **const filePack = lstGet(data->manifest->pub.fileList, fileIdx);
|
ManifestFilePack **const filePack = lstGet(manifest->pub.fileList, fileIdx);
|
||||||
|
|
||||||
if (manifestFileUnpack(data->manifest, *filePack).bundleId == bundleId)
|
if (manifestFileUnpack(manifest, *filePack).bundleId == bundleId)
|
||||||
lstAdd(fileList, &filePack);
|
lstAdd(fileList, &filePack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const String *manifestName = info->name;
|
const String *manifestName = info.name;
|
||||||
|
|
||||||
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
if (manifestData->backupOptionCompressType != compressTypeNone)
|
||||||
{
|
{
|
||||||
manifestName = strSubN(
|
manifestName = strSubN(
|
||||||
info->name, 0, strSize(info->name) - strSize(compressExtStr(data->manifestData->backupOptionCompressType)));
|
info.name, 0, strSize(info.name) - strSize(compressExtStr(manifestData->backupOptionCompressType)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ManifestFilePack **const filePack = manifestFilePackFindInternal(data->manifest, manifestName);
|
ManifestFilePack **const filePack = manifestFilePackFindInternal(manifest, manifestName);
|
||||||
lstAdd(fileList, &filePack);
|
lstAdd(fileList, &filePack);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check files
|
// Check files
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
for (unsigned int fileIdx = 0; fileIdx < lstSize(fileList); fileIdx++)
|
for (unsigned int fileIdx = 0; fileIdx < lstSize(fileList); fileIdx++)
|
||||||
{
|
{
|
||||||
ManifestFilePack **const filePack = *(ManifestFilePack ***)lstGet(fileList, fileIdx);
|
ManifestFilePack **const filePack = *(ManifestFilePack ***)lstGet(fileList, fileIdx);
|
||||||
ManifestFile file = manifestFileUnpack(data->manifest, *filePack);
|
ManifestFile file = manifestFileUnpack(manifest, *filePack);
|
||||||
|
|
||||||
if (bundle)
|
if (bundle)
|
||||||
strCatFmt(data->content, "%s/%s {file", strZ(info->name), strZ(file.name));
|
strCatFmt(result, "%s/%s {file", strZ(info.name), strZ(file.name));
|
||||||
else
|
else
|
||||||
strCatFmt(data->content, "%s {file", strZ(info->name));
|
strCatFmt(result, "%s {file", strZ(info.name));
|
||||||
|
|
||||||
// Calculate checksum/size and decompress if needed
|
// Calculate checksum/size and decompress if needed
|
||||||
// -----------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------
|
||||||
StorageRead *read = storageNewReadP(
|
StorageRead *read = storageNewReadP(
|
||||||
data->storage, strNewFmt("%s/%s", strZ(data->path), strZ(info->name)), .offset = file.bundleOffset,
|
storage, strNewFmt("%s/%s", strZ(path), strZ(info.name)), .offset = file.bundleOffset,
|
||||||
.limit = VARUINT64(file.sizeRepo));
|
.limit = VARUINT64(file.sizeRepo));
|
||||||
|
|
||||||
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
if (manifestData->backupOptionCompressType != compressTypeNone)
|
||||||
{
|
{
|
||||||
ioFilterGroupAdd(
|
ioFilterGroupAdd(
|
||||||
ioReadFilterGroup(storageReadIo(read)), decompressFilter(data->manifestData->backupOptionCompressType));
|
ioReadFilterGroup(storageReadIo(read)), decompressFilter(manifestData->backupOptionCompressType));
|
||||||
}
|
}
|
||||||
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), cryptoHashNew(hashTypeSha1));
|
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), cryptoHashNew(hashTypeSha1));
|
||||||
@ -119,84 +121,86 @@ testBackupValidateCallback(void *callbackData, const StorageInfo *info)
|
|||||||
const String *checksum = pckReadStrP(
|
const String *checksum = pckReadStrP(
|
||||||
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), CRYPTO_HASH_FILTER_TYPE));
|
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), CRYPTO_HASH_FILTER_TYPE));
|
||||||
|
|
||||||
strCatFmt(data->content, ", s=%" PRIu64, size);
|
strCatFmt(result, ", s=%" PRIu64, size);
|
||||||
|
|
||||||
if (!strEqZ(checksum, file.checksumSha1))
|
if (!strEqZ(checksum, file.checksumSha1))
|
||||||
THROW_FMT(AssertError, "'%s' checksum does match manifest", strZ(file.name));
|
THROW_FMT(AssertError, "'%s' checksum does match manifest", strZ(file.name));
|
||||||
|
|
||||||
// Test size and repo-size. If compressed then set the repo-size to size so it will not be in test output. Even the
|
// Test size and repo-size. If compressed then set the repo-size to size so it will not be in test output. Even
|
||||||
// same compression algorithm can give slightly different results based on the version so repo-size is not
|
// the same compression algorithm can give slightly different results based on the version so repo-size is not
|
||||||
// deterministic for compression.
|
// deterministic for compression.
|
||||||
// -----------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------
|
||||||
if (size != file.size)
|
if (size != file.size)
|
||||||
THROW_FMT(AssertError, "'%s' size does match manifest", strZ(file.name));
|
THROW_FMT(AssertError, "'%s' size does match manifest", strZ(file.name));
|
||||||
|
|
||||||
// Repo size can only be compared to file size when not bundled
|
// Repo size can only be compared to file size when not bundled
|
||||||
if (!bundle)
|
if (!bundle)
|
||||||
{
|
{
|
||||||
if (info->size != file.sizeRepo)
|
if (info.size != file.sizeRepo)
|
||||||
THROW_FMT(AssertError, "'%s' repo size does match manifest", strZ(file.name));
|
THROW_FMT(AssertError, "'%s' repo size does match manifest", strZ(file.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
if (manifestData->backupOptionCompressType != compressTypeNone)
|
||||||
file.sizeRepo = file.size;
|
file.sizeRepo = file.size;
|
||||||
|
|
||||||
// Bundle id/offset are too noisy so remove them. They are checked size/checksum and listed with the files.
|
// Bundle id/offset are too noisy so remove them. They are checked size/checksum and listed with the files.
|
||||||
// -----------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------
|
||||||
file.bundleId = 0;
|
file.bundleId = 0;
|
||||||
file.bundleOffset = 0;
|
file.bundleOffset = 0;
|
||||||
|
|
||||||
// pg_control and WAL headers have different checksums depending on cpu architecture so remove the checksum from the
|
// pg_control and WAL headers have different checksums depending on cpu architecture so remove the checksum from
|
||||||
// test output.
|
// the test output.
|
||||||
// -----------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------
|
||||||
if (strEqZ(file.name, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) ||
|
if (strEqZ(file.name, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) ||
|
||||||
strBeginsWith(
|
strBeginsWith(
|
||||||
file.name, strNewFmt(MANIFEST_TARGET_PGDATA "/%s/", strZ(pgWalPath(data->manifestData->pgVersion)))))
|
file.name, strNewFmt(MANIFEST_TARGET_PGDATA "/%s/", strZ(pgWalPath(manifestData->pgVersion)))))
|
||||||
{
|
{
|
||||||
file.checksumSha1[0] = '\0';
|
file.checksumSha1[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
strCatZ(data->content, "}\n");
|
strCatZ(result, "}\n");
|
||||||
|
|
||||||
// Update changes to manifest file
|
// Update changes to manifest file
|
||||||
manifestFilePackUpdate(data->manifest, filePack, &file);
|
manifestFilePackUpdate(manifest, filePack, &file);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case storageTypeLink:
|
case storageTypeLink:
|
||||||
strCatFmt(data->content, "%s {link, d=%s}\n", strZ(info->name), strZ(info->linkDestination));
|
strCatFmt(result, "%s {link, d=%s}\n", strZ(info.name), strZ(info.linkDestination));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case storageTypePath:
|
case storageTypePath:
|
||||||
{
|
{
|
||||||
strCatFmt(data->content, "%s {path", strZ(info->name));
|
strCatFmt(result, "%s {path", strZ(info.name));
|
||||||
|
|
||||||
// Check against the manifest
|
// Check against the manifest
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
if (!strEq(info->name, STRDEF("bundle")))
|
if (!strEq(info.name, STRDEF("bundle")))
|
||||||
manifestPathFind(data->manifest, info->name);
|
manifestPathFind(manifest, info.name);
|
||||||
|
|
||||||
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
||||||
// mode and current user/group.
|
// mode and current user/group.
|
||||||
if (info->mode != 0750)
|
if (info.mode != 0750)
|
||||||
THROW_FMT(AssertError, "'%s' mode is not 00750", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' mode is not 00750", strZ(info.name));
|
||||||
|
|
||||||
if (!strEq(info->user, TEST_USER_STR))
|
if (!strEq(info.user, TEST_USER_STR))
|
||||||
THROW_FMT(AssertError, "'%s' user should be '" TEST_USER "'", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' user should be '" TEST_USER "'", strZ(info.name));
|
||||||
|
|
||||||
if (!strEq(info->group, TEST_GROUP_STR))
|
if (!strEq(info.group, TEST_GROUP_STR))
|
||||||
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info.name));
|
||||||
|
|
||||||
strCatZ(data->content, "}\n");
|
strCatZ(result, "}\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case storageTypeSpecial:
|
case storageTypeSpecial:
|
||||||
THROW_FMT(AssertError, "unexpected special file '%s'", strZ(info->name));
|
THROW_FMT(AssertError, "unexpected special file '%s'", strZ(info.name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String *
|
static String *
|
||||||
@ -210,24 +214,14 @@ testBackupValidate(const Storage *storage, const String *path)
|
|||||||
ASSERT(storage != NULL);
|
ASSERT(storage != NULL);
|
||||||
ASSERT(path != NULL);
|
ASSERT(path != NULL);
|
||||||
|
|
||||||
String *result = strNew();
|
String *const result = strNew();
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
// Build a list of files in the backup path and verify against the manifest
|
// Build a list of files in the backup path and verify against the manifest
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
Manifest *manifest = manifestLoadFile(storage, strNewFmt("%s/" BACKUP_MANIFEST_FILE, strZ(path)), cipherTypeNone, NULL);
|
Manifest *manifest = manifestLoadFile(storage, strNewFmt("%s/" BACKUP_MANIFEST_FILE, strZ(path)), cipherTypeNone, NULL);
|
||||||
|
testBackupValidateList(storage, path, manifest, manifestData(manifest), result);
|
||||||
TestBackupValidateCallbackData callbackData =
|
|
||||||
{
|
|
||||||
.storage = storage,
|
|
||||||
.path = path,
|
|
||||||
.content = result,
|
|
||||||
.manifest = manifest,
|
|
||||||
.manifestData = manifestData(manifest),
|
|
||||||
};
|
|
||||||
|
|
||||||
storageInfoListP(storage, path, testBackupValidateCallback, &callbackData, .recurse = true, .sortOrder = sortOrderAsc);
|
|
||||||
|
|
||||||
// Make sure both backup.manifest files exist since we skipped them in the callback above
|
// Make sure both backup.manifest files exist since we skipped them in the callback above
|
||||||
if (!storageExistsP(storage, strNewFmt("%s/" BACKUP_MANIFEST_FILE, strZ(path))))
|
if (!storageExistsP(storage, strNewFmt("%s/" BACKUP_MANIFEST_FILE, strZ(path))))
|
||||||
|
52
test/src/module/common/typeBlobTest.c
Normal file
52
test/src/module/common/typeBlobTest.c
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/***********************************************************************************************************************************
|
||||||
|
Test Blob
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
|
||||||
|
/***********************************************************************************************************************************
|
||||||
|
Test Run
|
||||||
|
***********************************************************************************************************************************/
|
||||||
|
static void
|
||||||
|
testRun(void)
|
||||||
|
{
|
||||||
|
FUNCTION_HARNESS_VOID();
|
||||||
|
|
||||||
|
// *****************************************************************************************************************************
|
||||||
|
if (testBegin("Blob"))
|
||||||
|
{
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("Add enough data to allocate a new page");
|
||||||
|
|
||||||
|
#define TEST_DATA_MAX 5
|
||||||
|
#define TEST_DATA_SIZE (BLOB_BLOCK_SIZE / (TEST_DATA_MAX - 1))
|
||||||
|
|
||||||
|
void *dataList[TEST_DATA_MAX];
|
||||||
|
const void *dataListPtr[TEST_DATA_MAX];
|
||||||
|
Blob *blob = NULL;
|
||||||
|
|
||||||
|
TEST_ASSIGN(blob, blbNew(), "new blob");
|
||||||
|
|
||||||
|
for (int dataIdx = 0; dataIdx < TEST_DATA_MAX; dataIdx++)
|
||||||
|
{
|
||||||
|
dataList[dataIdx] = memNew(TEST_DATA_SIZE);
|
||||||
|
memset(dataList[dataIdx], dataIdx, TEST_DATA_SIZE);
|
||||||
|
|
||||||
|
TEST_ASSIGN(dataListPtr[dataIdx], blbAdd(blob, dataList[dataIdx], TEST_DATA_SIZE), "data add");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int dataIdx = 0; dataIdx < TEST_DATA_MAX; dataIdx++)
|
||||||
|
TEST_RESULT_INT(memcmp(dataList[dataIdx], dataListPtr[dataIdx], TEST_DATA_SIZE), 0, "data check");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("Add data >= than block size");
|
||||||
|
|
||||||
|
void *data = memNew(BLOB_BLOCK_SIZE);
|
||||||
|
const void *dataPtr = NULL;
|
||||||
|
|
||||||
|
memset(data, 255, BLOB_BLOCK_SIZE);
|
||||||
|
|
||||||
|
TEST_ASSIGN(dataPtr, blbAdd(blob, data, BLOB_BLOCK_SIZE), "data add");
|
||||||
|
TEST_RESULT_INT(memcmp(data, dataPtr, BLOB_BLOCK_SIZE), 0, "data check");
|
||||||
|
}
|
||||||
|
|
||||||
|
FUNCTION_HARNESS_RETURN_VOID();
|
||||||
|
}
|
@ -28,53 +28,31 @@ stress testing as needed.
|
|||||||
#include "storage/remote/protocol.h"
|
#include "storage/remote/protocol.h"
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Dummy callback functions
|
Driver to test storageNewItrP()
|
||||||
***********************************************************************************************************************************/
|
|
||||||
static void
|
|
||||||
storageTestDummyInfoListCallback(void *data, const StorageInfo *info)
|
|
||||||
{
|
|
||||||
(void)info;
|
|
||||||
|
|
||||||
// Do some work in the mem context to blow up the total time if this is not efficient, i.e. if the current mem context is not
|
|
||||||
// being freed regularly
|
|
||||||
memResize(memNew(16), 32);
|
|
||||||
|
|
||||||
// Increment callback total
|
|
||||||
(*(uint64_t *)data)++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
|
||||||
Driver to test storageInfoList
|
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
STORAGE_COMMON_MEMBER;
|
STORAGE_COMMON_MEMBER;
|
||||||
uint64_t fileTotal;
|
uint64_t fileTotal;
|
||||||
} StorageTestPerfInfoList;
|
} StorageTestPerfList;
|
||||||
|
|
||||||
static bool
|
static StorageList *
|
||||||
storageTestPerfInfoList(
|
storageTestPerfList(THIS_VOID, const String *path, StorageInfoLevel level, StorageInterfaceListParam param)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
|
||||||
StorageInterfaceInfoListParam param)
|
|
||||||
{
|
{
|
||||||
THIS(StorageTestPerfInfoList);
|
THIS(StorageTestPerfList);
|
||||||
(void)path; (void)level; (void)param;
|
(void)path; (void)level; (void)param;
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
StorageList *result = NULL;
|
||||||
{
|
|
||||||
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
|
||||||
{
|
|
||||||
for (uint64_t fileIdx = 0; fileIdx < this->fileTotal; fileIdx++)
|
|
||||||
{
|
|
||||||
callback(callbackData, &(StorageInfo){.exists = true, .name = STRDEF("name")});
|
|
||||||
MEM_CONTEXT_TEMP_RESET(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_TEMP_END();
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_TEMP_END();
|
|
||||||
|
|
||||||
return this->fileTotal != 0;
|
if (this->fileTotal != 0)
|
||||||
|
{
|
||||||
|
result = storageLstNew(storageInfoLevelExists);
|
||||||
|
|
||||||
|
for (uint64_t fileIdx = 0; fileIdx < this->fileTotal; fileIdx++)
|
||||||
|
storageLstAdd(result, &(StorageInfo){.exists = true, .name = STRDEF("name")});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
@ -144,7 +122,7 @@ testRun(void)
|
|||||||
FUNCTION_HARNESS_VOID();
|
FUNCTION_HARNESS_VOID();
|
||||||
|
|
||||||
// *****************************************************************************************************************************
|
// *****************************************************************************************************************************
|
||||||
if (testBegin("storageInfoList()"))
|
if (testBegin("storageNewItrP()"))
|
||||||
{
|
{
|
||||||
TEST_TITLE_FMT("list %d million files", TEST_SCALE);
|
TEST_TITLE_FMT("list %d million files", TEST_SCALE);
|
||||||
|
|
||||||
@ -163,14 +141,14 @@ testRun(void)
|
|||||||
hrnCfgArgRawStrId(argList, cfgOptRemoteType, protocolStorageTypeRepo);
|
hrnCfgArgRawStrId(argList, cfgOptRemoteType, protocolStorageTypeRepo);
|
||||||
HRN_CFG_LOAD(cfgCmdArchivePush, argList, .role = cfgCmdRoleRemote);
|
HRN_CFG_LOAD(cfgCmdArchivePush, argList, .role = cfgCmdRoleRemote);
|
||||||
|
|
||||||
// Create a driver to test remote performance of storageInfoList() and inject it into storageRepo()
|
// Create a driver to test remote performance of storageNewItrP() and inject it into storageRepo()
|
||||||
StorageTestPerfInfoList driver =
|
StorageTestPerfList driver =
|
||||||
{
|
{
|
||||||
.interface = storageInterfaceTestDummy,
|
.interface = storageInterfaceTestDummy,
|
||||||
.fileTotal = fileTotal,
|
.fileTotal = fileTotal,
|
||||||
};
|
};
|
||||||
|
|
||||||
driver.interface.infoList = storageTestPerfInfoList;
|
driver.interface.list = storageTestPerfList;
|
||||||
|
|
||||||
Storage *storageTest = storageNew(strIdFromZ("test"), STRDEF("/"), 0, 0, false, NULL, &driver, driver.interface);
|
Storage *storageTest = storageNew(strIdFromZ("test"), STRDEF("/"), 0, 0, false, NULL, &driver, driver.interface);
|
||||||
storageHelper.storageRepoWrite = memNew(sizeof(Storage *));
|
storageHelper.storageRepoWrite = memNew(sizeof(Storage *));
|
||||||
@ -200,13 +178,18 @@ testRun(void)
|
|||||||
TimeMSec timeBegin = timeMSec();
|
TimeMSec timeBegin = timeMSec();
|
||||||
|
|
||||||
// Storage info list
|
// Storage info list
|
||||||
uint64_t fileCallbackTotal = 0;
|
uint64_t fileTotal = 0;
|
||||||
|
StorageIterator *storageItr = NULL;
|
||||||
|
|
||||||
TEST_RESULT_VOID(
|
TEST_ASSIGN(storageItr, storageNewItrP(storageRemote, NULL), "list remote files");
|
||||||
storageInfoListP(storageRemote, NULL, storageTestDummyInfoListCallback, &fileCallbackTotal),
|
|
||||||
"list remote files");
|
|
||||||
|
|
||||||
TEST_RESULT_UINT(fileCallbackTotal, fileTotal, "check callback total");
|
while (storageItrMore(storageItr))
|
||||||
|
{
|
||||||
|
storageItrNext(storageItr);
|
||||||
|
fileTotal++;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_RESULT_UINT(fileTotal, fileTotal, "check callback total");
|
||||||
|
|
||||||
TEST_LOG_FMT("list transferred in %ums", (unsigned int)(timeMSec() - timeBegin));
|
TEST_LOG_FMT("list transferred in %ums", (unsigned int)(timeMSec() - timeBegin));
|
||||||
|
|
||||||
|
@ -93,17 +93,17 @@ storageTestManifestNewBuildInfo(THIS_VOID, const String *file, StorageInfoLevel
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static StorageList *
|
||||||
storageTestManifestNewBuildInfoList(
|
storageTestManifestNewBuildList(THIS_VOID, const String *path, StorageInfoLevel level, StorageInterfaceListParam param)
|
||||||
THIS_VOID, const String *path, StorageInfoLevel level, StorageInfoListCallback callback, void *callbackData,
|
|
||||||
StorageInterfaceInfoListParam param)
|
|
||||||
{
|
{
|
||||||
THIS(StorageTestManifestNewBuild);
|
THIS(StorageTestManifestNewBuild);
|
||||||
(void)path; (void)level; (void)param;
|
(void)path; (void)level; (void)param;
|
||||||
|
|
||||||
|
StorageList *const result = storageLstNew(storageInfoLevelDetail);
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
MEM_CONTEXT_TEMP_RESET_BEGIN()
|
||||||
{
|
{
|
||||||
StorageInfo result =
|
StorageInfo info =
|
||||||
{
|
{
|
||||||
.level = storageInfoLevelDetail,
|
.level = storageInfoLevelDetail,
|
||||||
.exists = true,
|
.exists = true,
|
||||||
@ -117,25 +117,25 @@ storageTestManifestNewBuildInfoList(
|
|||||||
|
|
||||||
if (strEq(path, STRDEF("/pg")))
|
if (strEq(path, STRDEF("/pg")))
|
||||||
{
|
{
|
||||||
result.name = STRDEF("base");
|
info.name = STRDEF("base");
|
||||||
callback(callbackData, &result);
|
storageLstAdd(result, &info);
|
||||||
}
|
}
|
||||||
else if (strEq(path, STRDEF("/pg/base")))
|
else if (strEq(path, STRDEF("/pg/base")))
|
||||||
{
|
{
|
||||||
result.name = STRDEF("1000000000");
|
info.name = STRDEF("1000000000");
|
||||||
callback(callbackData, &result);
|
storageLstAdd(result, &info);
|
||||||
}
|
}
|
||||||
else if (strEq(path, STRDEF("/pg/base/1000000000")))
|
else if (strEq(path, STRDEF("/pg/base/1000000000")))
|
||||||
{
|
{
|
||||||
result.type = storageTypeFile;
|
info.type = storageTypeFile;
|
||||||
result.size = 8192;
|
info.size = 8192;
|
||||||
result.mode = 0600;
|
info.mode = 0600;
|
||||||
result.timeModified = 1595627966;
|
info.timeModified = 1595627966;
|
||||||
|
|
||||||
for (unsigned int fileIdx = 0; fileIdx < this->fileTotal; fileIdx++)
|
for (unsigned int fileIdx = 0; fileIdx < this->fileTotal; fileIdx++)
|
||||||
{
|
{
|
||||||
result.name = strNewFmt("%u", 1000000000 + fileIdx);
|
info.name = strNewFmt("%u", 1000000000 + fileIdx);
|
||||||
callback(callbackData, &result);
|
storageLstAdd(result, &info);
|
||||||
MEM_CONTEXT_TEMP_RESET(10000);
|
MEM_CONTEXT_TEMP_RESET(10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ storageTestManifestNewBuildInfoList(
|
|||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
@ -255,7 +255,7 @@ testRun(void)
|
|||||||
};
|
};
|
||||||
|
|
||||||
driver.interface.info = storageTestManifestNewBuildInfo;
|
driver.interface.info = storageTestManifestNewBuildInfo;
|
||||||
driver.interface.infoList = storageTestManifestNewBuildInfoList;
|
driver.interface.list = storageTestManifestNewBuildList;
|
||||||
|
|
||||||
const Storage *const storagePg = storageNew(
|
const Storage *const storagePg = storageNew(
|
||||||
strIdFromZ("test"), STRDEF("/pg"), 0, 0, false, NULL, &driver, driver.interface);
|
strIdFromZ("test"), STRDEF("/pg"), 0, 0, false, NULL, &driver, driver.interface);
|
||||||
|
@ -718,8 +718,8 @@ testRun(void)
|
|||||||
"</EnumerationResults>");
|
"</EnumerationResults>");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
storageInfoListP(storage, STRDEF("/"), (void *)1, NULL, .errorOnMissing = true),
|
storageNewItrP(storage, STRDEF("/"), .errorOnMissing = true), AssertError,
|
||||||
AssertError, "assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
|
"assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
|
||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
storage, "/path/to",
|
storage, "/path/to",
|
||||||
|
@ -714,8 +714,8 @@ testRun(void)
|
|||||||
"}");
|
"}");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
storageInfoListP(storage, STRDEF("/"), (void *)1, NULL, .errorOnMissing = true),
|
storageNewItrP(storage, STRDEF("/"), .errorOnMissing = true), AssertError,
|
||||||
AssertError, "assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
|
"assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
|
||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
storage, "/path/to",
|
storage, "/path/to",
|
||||||
|
@ -340,7 +340,7 @@ testRun(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************************************************************************
|
// *****************************************************************************************************************************
|
||||||
if (testBegin("storageInfoList()"))
|
if (testBegin("storageNewItrP()"))
|
||||||
{
|
{
|
||||||
#ifdef TEST_CONTAINER_REQUIRED
|
#ifdef TEST_CONTAINER_REQUIRED
|
||||||
TEST_CREATE_NOPERM();
|
TEST_CREATE_NOPERM();
|
||||||
@ -350,30 +350,29 @@ testRun(void)
|
|||||||
TEST_TITLE("path missing");
|
TEST_TITLE("path missing");
|
||||||
|
|
||||||
TEST_ERROR_FMT(
|
TEST_ERROR_FMT(
|
||||||
storageInfoListP(storageTest, STRDEF(BOGUS_STR), (StorageInfoListCallback)1, NULL, .errorOnMissing = true),
|
storageNewItrP(storageTest, STRDEF(BOGUS_STR), .errorOnMissing = true), PathMissingError,
|
||||||
PathMissingError, STORAGE_ERROR_LIST_INFO_MISSING, TEST_PATH "/BOGUS");
|
STORAGE_ERROR_LIST_INFO_MISSING, TEST_PATH "/BOGUS");
|
||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_PTR(storageNewItrP(storageTest, STRDEF(BOGUS_STR), .nullOnMissing = true), NULL, "ignore missing dir");
|
||||||
storageInfoListP(storageTest, STRDEF(BOGUS_STR), (StorageInfoListCallback)1, NULL), false, "ignore missing dir");
|
|
||||||
|
|
||||||
#ifdef TEST_CONTAINER_REQUIRED
|
#ifdef TEST_CONTAINER_REQUIRED
|
||||||
TEST_ERROR_FMT(
|
TEST_ERROR_FMT(
|
||||||
storageInfoListP(storageTest, pathNoPerm, (StorageInfoListCallback)1, NULL), PathOpenError,
|
storageNewItrP(storageTest, pathNoPerm), PathOpenError, STORAGE_ERROR_LIST_INFO ": [13] Permission denied",
|
||||||
STORAGE_ERROR_LIST_INFO ": [13] Permission denied", strZ(pathNoPerm));
|
strZ(pathNoPerm));
|
||||||
|
|
||||||
// Should still error even when ignore missing
|
// Should still error even when ignore missing
|
||||||
TEST_ERROR_FMT(
|
TEST_ERROR_FMT(
|
||||||
storageInfoListP(storageTest, pathNoPerm, (StorageInfoListCallback)1, NULL), PathOpenError,
|
storageNewItrP(storageTest, pathNoPerm), PathOpenError, STORAGE_ERROR_LIST_INFO ": [13] Permission denied",
|
||||||
STORAGE_ERROR_LIST_INFO ": [13] Permission denied", strZ(pathNoPerm));
|
strZ(pathNoPerm));
|
||||||
#endif // TEST_CONTAINER_REQUIRED
|
#endif // TEST_CONTAINER_REQUIRED
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("helper function - storagePosixInfoListEntry()");
|
TEST_TITLE("helper function - storagePosixListEntry()");
|
||||||
|
|
||||||
TEST_RESULT_VOID(
|
TEST_RESULT_VOID(
|
||||||
storagePosixInfoListEntry(
|
storagePosixListEntry(
|
||||||
(StoragePosix *)storageDriver(storageTest), STRDEF("pg"), STRDEF("missing"), storageInfoLevelBasic, (void *)1,
|
(StoragePosix *)storageDriver(storageTest), storageLstNew(storageInfoLevelBasic), STRDEF("pg"), "missing",
|
||||||
NULL),
|
storageInfoLevelBasic),
|
||||||
"missing path");
|
"missing path");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
@ -382,8 +381,8 @@ testRun(void)
|
|||||||
storagePathCreateP(storageTest, STRDEF("pg"), .mode = 0766);
|
storagePathCreateP(storageTest, STRDEF("pg"), .mode = 0766);
|
||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
storageTest,
|
storageTest, "pg",
|
||||||
"pg", "./ {u=" TEST_USER ", g=" TEST_GROUP ", m=0766}\n",
|
"./ {u=" TEST_USER ", g=" TEST_GROUP ", m=0766}\n",
|
||||||
.level = storageInfoLevelDetail, .includeDot = true);
|
.level = storageInfoLevelDetail, .includeDot = true);
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
@ -416,7 +415,16 @@ testRun(void)
|
|||||||
#endif // TEST_CONTAINER_REQUIRED
|
#endif // TEST_CONTAINER_REQUIRED
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("path - recurse");
|
TEST_TITLE("storageItrMore() twice in a row");
|
||||||
|
|
||||||
|
StorageIterator *storageItr = NULL;
|
||||||
|
|
||||||
|
TEST_ASSIGN(storageItr, storageNewItrP(storageTest, STRDEF("pg")), "new iterator");
|
||||||
|
TEST_RESULT_BOOL(storageItrMore(storageItr), true, "check more");
|
||||||
|
TEST_RESULT_BOOL(storageItrMore(storageItr), true, "check more again");
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("path - recurse desc");
|
||||||
|
|
||||||
storagePathCreateP(storageTest, STRDEF("pg/path"), .mode = 0700);
|
storagePathCreateP(storageTest, STRDEF("pg/path"), .mode = 0700);
|
||||||
storagePutP(
|
storagePutP(
|
||||||
@ -433,6 +441,20 @@ testRun(void)
|
|||||||
"./\n",
|
"./\n",
|
||||||
.level = storageInfoLevelBasic, .includeDot = true, .sortOrder = sortOrderDesc);
|
.level = storageInfoLevelBasic, .includeDot = true, .sortOrder = sortOrderDesc);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("path - recurse asc");
|
||||||
|
|
||||||
|
|
||||||
|
TEST_STORAGE_LIST(
|
||||||
|
storageTest, "pg",
|
||||||
|
"./\n"
|
||||||
|
"file {s=8, t=1656433838}\n"
|
||||||
|
"link> {d=../file}\n"
|
||||||
|
"path/\n"
|
||||||
|
"path/file {s=8, t=1656434296}\n"
|
||||||
|
"pipe*\n",
|
||||||
|
.level = storageInfoLevelBasic, .includeDot = true);
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("path basic info - recurse");
|
TEST_TITLE("path basic info - recurse");
|
||||||
|
|
||||||
@ -451,12 +473,14 @@ testRun(void)
|
|||||||
storageTest->pub.interface.feature ^= 1 << storageFeatureInfoDetail;
|
storageTest->pub.interface.feature ^= 1 << storageFeatureInfoDetail;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("path - filter");
|
TEST_TITLE("empty path - filter");
|
||||||
|
|
||||||
|
storagePathCreateP(storageTest, STRDEF("pg/empty"), .mode = 0700);
|
||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
storageTest, "pg",
|
storageTest, "pg",
|
||||||
"path/\n",
|
"empty/\n",
|
||||||
.noRecurse = true, .level = storageInfoLevelType, .expression = "^path");
|
.level = storageInfoLevelType, .expression = "^empty");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("filter in subpath during recursion");
|
TEST_TITLE("filter in subpath during recursion");
|
||||||
|
@ -193,11 +193,11 @@ testRun(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************************************************************************
|
// *****************************************************************************************************************************
|
||||||
if (testBegin("storageInfoList()"))
|
if (testBegin("storageNewItrP()"))
|
||||||
{
|
{
|
||||||
TEST_TITLE("path not found");
|
TEST_TITLE("path not found");
|
||||||
|
|
||||||
TEST_RESULT_BOOL(storageInfoListP(storageRepo, STRDEF(BOGUS_STR), (void *)1, NULL), false, "path missing");
|
TEST_RESULT_PTR(storageNewItrP(storageRepo, STRDEF(BOGUS_STR), .nullOnMissing = true), NULL, "path missing");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("list path and file (no user/group");
|
TEST_TITLE("list path and file (no user/group");
|
||||||
|
@ -1065,8 +1065,8 @@ testRun(void)
|
|||||||
"</ListBucketResult>");
|
"</ListBucketResult>");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
storageInfoListP(s3, STRDEF("/"), (void *)1, NULL, .errorOnMissing = true),
|
storageNewItrP(s3, STRDEF("/"), .errorOnMissing = true), AssertError,
|
||||||
AssertError, "assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
|
"assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
|
||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
s3, "/path/to",
|
s3, "/path/to",
|
||||||
|
Reference in New Issue
Block a user