From 47709fe935d655493dd4452908e5032ee2007e52 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sun, 31 May 2026 09:26:47 +0000 Subject: [PATCH] salvage: add repository path helpers --- include/nwsalvage.h | 10 +++ src/nwsalvage.c | 124 +++++++++++++++++++++++++++ tests/salvage/salvage_config_smoke.c | 35 ++++++++ 3 files changed, 169 insertions(+) diff --git a/include/nwsalvage.h b/include/nwsalvage.h index 1a46cf3..e2126c2 100644 --- a/include/nwsalvage.h +++ b/include/nwsalvage.h @@ -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 diff --git a/src/nwsalvage.c b/src/nwsalvage.c index d95d266..116bce2 100644 --- a/src/nwsalvage.c +++ b/src/nwsalvage.c @@ -3,6 +3,7 @@ #include #include +#include #include 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")); +} diff --git a/tests/salvage/salvage_config_smoke.c b/tests/salvage/salvage_config_smoke.c index 88fc4ff..478f681 100644 --- a/tests/salvage/salvage_config_smoke.c +++ b/tests/salvage/salvage_config_smoke.c @@ -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);