salvage: add repository path helpers

This commit is contained in:
Mario Fetka
2026-05-31 09:26:47 +00:00
parent bedec0d2c0
commit 47709fe935
3 changed files with 169 additions and 0 deletions

View File

@@ -10,6 +10,7 @@
#define NWSALVAGE_DEFAULT_RECYCLE_NAME ".recycle"
#define NWSALVAGE_DEFAULT_METADATA_NAME ".salvage"
#define NWSALVAGE_REPOSITORY_NAME_MAX 64
#define NWSALVAGE_PATH_MAX 4096
struct nwsalvage_config {
int enabled;
@@ -24,5 +25,14 @@ int nwsalvage_config_set_repositories(struct nwsalvage_config *config,
int nwsalvage_config_parse_repositories(struct nwsalvage_config *config,
const char *line);
int nwsalvage_repository_name_valid(const char *name);
int nwsalvage_relative_path_valid(const char *relative_path);
int nwsalvage_build_recycle_path(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *volume_root,
const char *relative_path);
int nwsalvage_build_metadata_path(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *volume_root,
const char *relative_path);
#endif

View File

@@ -3,6 +3,7 @@
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
int nwsalvage_repository_name_valid(const char *name)
@@ -118,3 +119,126 @@ int nwsalvage_config_parse_repositories(struct nwsalvage_config *config,
recycle_repository,
metadata_repository));
}
static int path_is_separator(char c)
{
return(c == '/');
}
int nwsalvage_relative_path_valid(const char *relative_path)
{
const char *p;
size_t component_len = 0;
int component_is_dot = 0;
int component_is_dotdot = 0;
if (!relative_path || !*relative_path) {
errno = EINVAL;
return(0);
}
if (path_is_separator(relative_path[0])) {
errno = EINVAL;
return(0);
}
p = relative_path;
while (1) {
char c = *p;
if (!c || path_is_separator(c)) {
if (!component_len || component_is_dot || component_is_dotdot) {
errno = EINVAL;
return(0);
}
if (!c)
break;
component_len = 0;
component_is_dot = 0;
component_is_dotdot = 0;
p++;
continue;
}
if (c == ':' || c == '\\') {
errno = EINVAL;
return(0);
}
if (!component_len) {
component_is_dot = (c == '.');
component_is_dotdot = (c == '.');
} else if (component_len == 1 && component_is_dot && c == '.') {
component_is_dot = 0;
component_is_dotdot = 1;
} else {
component_is_dot = 0;
component_is_dotdot = 0;
}
component_len++;
p++;
}
return(1);
}
static int build_path(char *out, size_t out_len,
const char *volume_root,
const char *repository,
const char *relative_path,
const char *suffix)
{
int n;
const char *separator = "/";
size_t volume_root_len;
if (!out || !out_len || !volume_root || !*volume_root ||
!nwsalvage_repository_name_valid(repository) ||
!nwsalvage_relative_path_valid(relative_path)) {
if (!errno) errno = EINVAL;
return(-1);
}
volume_root_len = strlen(volume_root);
if (volume_root_len && path_is_separator(volume_root[volume_root_len - 1]))
separator = "";
n = snprintf(out, out_len, "%s%s%s/%s%s",
volume_root, separator, repository, relative_path,
suffix ? suffix : "");
if (n < 0 || (size_t)n >= out_len) {
errno = ENAMETOOLONG;
return(-1);
}
return(0);
}
int nwsalvage_build_recycle_path(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *volume_root,
const char *relative_path)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(build_path(out, out_len, volume_root,
config->recycle_repository, relative_path, NULL));
}
int nwsalvage_build_metadata_path(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *volume_root,
const char *relative_path)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(build_path(out, out_len, volume_root,
config->metadata_repository, relative_path, ".json"));
}

View File

@@ -16,6 +16,7 @@ static int expect_true(int condition, const char *message)
int main(void)
{
struct nwsalvage_config config;
char path[NWSALVAGE_PATH_MAX];
int failures = 0;
failures += expect_true(nwsalvage_config_defaults(&config) == 0,
@@ -53,6 +54,40 @@ int main(void)
&config, ".recycle .salvage extra") < 0,
"repository line rejects extra tokens");
failures += expect_true(nwsalvage_config_defaults(&config) == 0,
"defaults reset after custom repository parse");
failures += expect_true(nwsalvage_build_recycle_path(
path, sizeof(path), &config, "/srv/nw/SYS",
"SUPERVISOR/PUBLIC/PMDFLTS.INI") == 0,
"recycle path builds");
failures += expect_true(!strcmp(path,
"/srv/nw/SYS/.recycle/SUPERVISOR/PUBLIC/PMDFLTS.INI"),
"recycle path layout");
failures += expect_true(nwsalvage_build_metadata_path(
path, sizeof(path), &config, "/srv/nw/SYS/",
"SUPERVISOR/PUBLIC/PMDFLTS.INI") == 0,
"metadata path builds with trailing volume slash");
failures += expect_true(!strcmp(path,
"/srv/nw/SYS/.salvage/SUPERVISOR/PUBLIC/PMDFLTS.INI.json"),
"metadata path layout");
failures += expect_true(nwsalvage_build_recycle_path(
path, sizeof(path), &config, "/srv/nw/SYS",
"../PMDFLTS.INI") < 0,
"relative path rejects dot-dot components");
failures += expect_true(nwsalvage_build_recycle_path(
path, sizeof(path), &config, "/srv/nw/SYS",
"/SUPERVISOR/PUBLIC/PMDFLTS.INI") < 0,
"relative path rejects absolute input");
failures += expect_true(nwsalvage_build_recycle_path(
path, sizeof(path), &config, "/srv/nw/SYS",
"SUPERVISOR\\PUBLIC\\PMDFLTS.INI") < 0,
"relative path rejects unnormalised backslashes");
failures += expect_true(nwsalvage_build_metadata_path(
path, 16, &config, "/srv/nw/SYS",
"SUPERVISOR/PUBLIC/PMDFLTS.INI") < 0,
"path builder rejects too-small buffers");
if (failures)
return(1);