Files
mars-nwe/src/nwsalvage.c
ai 92b0c4a34a
All checks were successful
Source release / source-package (push) Successful in 55s
afp: add deleted file Macintosh info endpoint
2026-06-01 11:07:43 +02:00

2504 lines
69 KiB
C

/* nwsalvage.c - NetWare salvage/recycle backend helpers */
#include "nwsalvage.h"
#include "net.h"
#include "nwvolume.h"
#include "nwarchive.h"
#include "nwatalk.h"
#include "nwattrib.h"
#include "nwfile.h"
#include "trustee.h"
#include "tools.h"
#include "connect.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fnmatch.h>
#include <limits.h>
#include <stdint.h>
#include <yyjson.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>
#ifndef FNM_CASEFOLD
#define FNM_CASEFOLD 0
#endif
int nwsalvage_repository_name_valid(const char *name)
{
size_t len;
if (!name || !*name) {
errno = EINVAL;
return(0);
}
len = strlen(name);
if (len >= NWSALVAGE_REPOSITORY_NAME_MAX) {
errno = ENAMETOOLONG;
return(0);
}
if (!strcmp(name, ".") || !strcmp(name, "..")) {
errno = EINVAL;
return(0);
}
if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) {
errno = EINVAL;
return(0);
}
return(1);
}
int nwsalvage_config_set_repositories(struct nwsalvage_config *config,
const char *recycle_repository,
const char *metadata_repository)
{
if (!config ||
!nwsalvage_repository_name_valid(recycle_repository) ||
!nwsalvage_repository_name_valid(metadata_repository)) {
if (!errno) errno = EINVAL;
return(-1);
}
strncpy(config->recycle_repository, recycle_repository,
sizeof(config->recycle_repository));
config->recycle_repository[sizeof(config->recycle_repository) - 1] = '\0';
strncpy(config->metadata_repository, metadata_repository,
sizeof(config->metadata_repository));
config->metadata_repository[sizeof(config->metadata_repository) - 1] = '\0';
return(0);
}
int nwsalvage_config_defaults(struct nwsalvage_config *config)
{
if (!config) {
errno = EINVAL;
return(-1);
}
memset(config, 0, sizeof(*config));
config->enabled = NWSALVAGE_DEFAULT_ENABLED;
config->flags = NWSALVAGE_DEFAULT_FLAGS;
return(nwsalvage_config_set_repositories(config,
NWSALVAGE_DEFAULT_RECYCLE_NAME,
NWSALVAGE_DEFAULT_METADATA_NAME));
}
int nwsalvage_config_parse_enabled(struct nwsalvage_config *config,
const char *line)
{
int enabled;
char tail;
if (!config || !line) {
errno = EINVAL;
return(-1);
}
if (sscanf(line, " %i %c", &enabled, &tail) != 1) {
errno = EINVAL;
return(-1);
}
config->enabled = enabled ? 1 : 0;
return(0);
}
static int parse_token(const char **cursor, char *out, size_t out_len)
{
const char *p = *cursor;
size_t len = 0;
while (*p && isspace((unsigned char)*p)) p++;
if (!*p) {
errno = EINVAL;
return(-1);
}
while (p[len] && !isspace((unsigned char)p[len])) len++;
if (len >= out_len) {
errno = ENAMETOOLONG;
return(-1);
}
memcpy(out, p, len);
out[len] = '\0';
*cursor = p + len;
return(0);
}
int nwsalvage_config_parse_repositories(struct nwsalvage_config *config,
const char *line)
{
const char *p = line;
char recycle_repository[NWSALVAGE_REPOSITORY_NAME_MAX];
char metadata_repository[NWSALVAGE_REPOSITORY_NAME_MAX];
if (!config || !line) {
errno = EINVAL;
return(-1);
}
if (parse_token(&p, recycle_repository, sizeof(recycle_repository)) < 0 ||
parse_token(&p, metadata_repository, sizeof(metadata_repository)) < 0)
return(-1);
while (*p && isspace((unsigned char)*p)) p++;
if (*p) {
errno = EINVAL;
return(-1);
}
return(nwsalvage_config_set_repositories(config,
recycle_repository,
metadata_repository));
}
static int nwsalvage_flag_from_char(int c, unsigned int *flag)
{
switch (c) {
case 'k':
case 'K':
*flag = NWSALVAGE_FLAG_KEEP_TREE;
return(0);
case 'v':
case 'V':
*flag = NWSALVAGE_FLAG_VERSIONS;
return(0);
case 't':
case 'T':
*flag = NWSALVAGE_FLAG_TOUCH;
return(0);
case 'm':
case 'M':
*flag = NWSALVAGE_FLAG_TOUCH_MTIME;
return(0);
default:
errno = EINVAL;
return(-1);
}
}
int nwsalvage_config_parse_flags(struct nwsalvage_config *config,
const char *line)
{
const char *p = line;
unsigned int flags = 0;
if (!config || !line) {
errno = EINVAL;
return(-1);
}
while (*p && isspace((unsigned char)*p)) p++;
if (!*p) {
config->flags = 0;
return(0);
}
if (*p == '-') {
p++;
while (*p && isspace((unsigned char)*p)) p++;
if (*p) {
errno = EINVAL;
return(-1);
}
config->flags = 0;
return(0);
}
while (*p && !isspace((unsigned char)*p)) {
unsigned int flag = 0;
if (nwsalvage_flag_from_char((unsigned char)*p, &flag) < 0)
return(-1);
flags |= flag;
p++;
}
while (*p && isspace((unsigned char)*p)) p++;
if (*p) {
errno = EINVAL;
return(-1);
}
config->flags = flags;
return(0);
}
static int nwsalvage_parse_size_value(const char *token,
unsigned long long *out)
{
char *end;
unsigned long long value;
unsigned long long multiplier = 1;
if (!token || !*token || !out) {
errno = EINVAL;
return(-1);
}
errno = 0;
value = strtoull(token, &end, 10);
if (end == token || errno == ERANGE)
return(-1);
if (*end) {
char unit[8];
size_t len = strlen(end);
size_t i;
if (len >= sizeof(unit)) {
errno = EINVAL;
return(-1);
}
for (i = 0; i <= len; i++)
unit[i] = (char)tolower((unsigned char)end[i]);
if (!strcmp(unit, "b") || !strcmp(unit, "byte") || !strcmp(unit, "bytes"))
multiplier = 1;
else if (!strcmp(unit, "k") || !strcmp(unit, "kb"))
multiplier = 1024ULL;
else if (!strcmp(unit, "m") || !strcmp(unit, "mb"))
multiplier = 1024ULL * 1024ULL;
else if (!strcmp(unit, "g") || !strcmp(unit, "gb"))
multiplier = 1024ULL * 1024ULL * 1024ULL;
else {
errno = EINVAL;
return(-1);
}
}
if (value && multiplier > ULLONG_MAX / value) {
errno = ERANGE;
return(-1);
}
*out = value * multiplier;
return(0);
}
int nwsalvage_config_parse_size_limits(struct nwsalvage_config *config,
const char *line)
{
const char *p = line;
char min_token[64];
char max_token[64];
unsigned long long min_size;
unsigned long long max_size;
if (!config || !line) {
errno = EINVAL;
return(-1);
}
if (parse_token(&p, min_token, sizeof(min_token)) < 0 ||
parse_token(&p, max_token, sizeof(max_token)) < 0)
return(-1);
while (*p && isspace((unsigned char)*p)) p++;
if (*p) {
errno = EINVAL;
return(-1);
}
if (nwsalvage_parse_size_value(min_token, &min_size) < 0 ||
nwsalvage_parse_size_value(max_token, &max_size) < 0)
return(-1);
if (max_size && min_size > max_size) {
errno = EINVAL;
return(-1);
}
config->min_size = min_size;
config->max_size = max_size;
return(0);
}
static int nwsalvage_config_parse_patterns(char patterns[][NWSALVAGE_PATTERN_LEN_MAX],
unsigned int *count,
const char *line)
{
const char *p = line;
char token[NWSALVAGE_PATTERN_LEN_MAX];
unsigned int used = 0;
if (!patterns || !count || !line) {
errno = EINVAL;
return(-1);
}
while (*p && isspace((unsigned char)*p)) p++;
if (!*p || *p == '-') {
if (*p == '-') {
p++;
while (*p && isspace((unsigned char)*p)) p++;
if (*p) {
errno = EINVAL;
return(-1);
}
}
*count = 0;
return(0);
}
while (*p) {
if (used >= NWSALVAGE_PATTERN_MAX) {
errno = E2BIG;
return(-1);
}
if (parse_token(&p, token, sizeof(token)) < 0)
return(-1);
strcpy(patterns[used++], token);
while (*p && isspace((unsigned char)*p)) p++;
}
*count = used;
return(0);
}
int nwsalvage_config_parse_exclude(struct nwsalvage_config *config,
const char *line)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(nwsalvage_config_parse_patterns(config->exclude_patterns,
&config->exclude_count, line));
}
int nwsalvage_config_parse_exclude_dir(struct nwsalvage_config *config,
const char *line)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(nwsalvage_config_parse_patterns(config->exclude_dir_patterns,
&config->exclude_dir_count, line));
}
int nwsalvage_config_parse_noversions(struct nwsalvage_config *config,
const char *line)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(nwsalvage_config_parse_patterns(config->noversions_patterns,
&config->noversions_count, line));
}
int nwsalvage_config_load_from_ini(struct nwsalvage_config *config,
nwsalvage_ini_getter getter,
void *data)
{
char line[256];
if (!config || !getter) {
errno = EINVAL;
return(-1);
}
if (nwsalvage_config_defaults(config) < 0)
return(-1);
if (getter(NWSALVAGE_ENABLE_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_enabled(config, line) < 0)
return(-1);
}
if (getter(NWSALVAGE_REPOSITORY_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_repositories(config, line) < 0)
return(-1);
}
if (getter(NWSALVAGE_FLAGS_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_flags(config, line) < 0)
return(-1);
}
if (getter(NWSALVAGE_SIZE_LIMIT_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_size_limits(config, line) < 0)
return(-1);
}
if (getter(NWSALVAGE_EXCLUDE_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_exclude(config, line) < 0)
return(-1);
}
if (getter(NWSALVAGE_EXCLUDE_DIR_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_exclude_dir(config, line) < 0)
return(-1);
}
if (getter(NWSALVAGE_NOVERSIONS_INI_SECTION,
line, sizeof(line), data)) {
if (nwsalvage_config_parse_noversions(config, line) < 0)
return(-1);
}
return(0);
}
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_relative_repository_path(char *out, size_t out_len,
const char *repository,
const char *relative_path,
const char *suffix)
{
int n;
if (!out || !out_len ||
!nwsalvage_repository_name_valid(repository) ||
!nwsalvage_relative_path_valid(relative_path)) {
if (!errno) errno = EINVAL;
return(-1);
}
n = snprintf(out, out_len, "%s/%s%s", repository, relative_path,
suffix ? suffix : "");
if (n < 0 || (size_t)n >= out_len) {
errno = ENAMETOOLONG;
return(-1);
}
return(0);
}
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_relative_path(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *relative_path)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(build_relative_repository_path(out, out_len,
config->recycle_repository,
relative_path, NULL));
}
int nwsalvage_build_metadata_relative_path(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *relative_path)
{
if (!config) {
errno = EINVAL;
return(-1);
}
return(build_relative_repository_path(out, out_len,
config->metadata_repository,
relative_path, ".json"));
}
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"));
}
static int make_dir_if_missing(const char *path)
{
struct stat st;
if (!path || !*path) {
errno = EINVAL;
return(-1);
}
if (!stat(path, &st)) {
if (S_ISDIR(st.st_mode))
return(0);
errno = ENOTDIR;
return(-1);
}
if (mkdir(path, 0777) == 0)
return(0);
if (errno == EEXIST)
return(0);
return(-1);
}
static int make_parent_dirs(const char *path)
{
char tmp[NWSALVAGE_PATH_MAX];
char *p;
size_t len;
if (!path || !*path) {
errno = EINVAL;
return(-1);
}
len = strlen(path);
if (len >= sizeof(tmp)) {
errno = ENAMETOOLONG;
return(-1);
}
memcpy(tmp, path, len + 1);
p = strrchr(tmp, '/');
if (!p) {
errno = EINVAL;
return(-1);
}
if (p == tmp)
return(0);
*p = '\0';
for (p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
if (make_dir_if_missing(tmp) < 0)
return(-1);
*p = '/';
}
}
return(make_dir_if_missing(tmp));
}
static const char *metadata_source(const struct nwsalvage_deleted_entry *entry)
{
return(entry->source && *entry->source ? entry->source : "mars_nwe");
}
static const char *metadata_finder_info_hex(
const struct nwsalvage_deleted_entry *entry)
{
return(entry->finder_info_hex && *entry->finder_info_hex ?
entry->finder_info_hex : NULL);
}
static int validate_hex_string(const char *value, size_t expected_len)
{
size_t i;
if (!value || strlen(value) != expected_len) {
errno = EINVAL;
return(-1);
}
for (i = 0; i < expected_len; i++) {
if (!isxdigit((unsigned char)value[i])) {
errno = EINVAL;
return(-1);
}
}
return(0);
}
static int metadata_time_valid(long value)
{
if (value < 0) {
errno = EINVAL;
return(0);
}
return(1);
}
static int json_add_string(yyjson_mut_doc *doc,
yyjson_mut_val *object,
const char *name,
const char *value)
{
if (!doc || !object || !name || !value) {
errno = EINVAL;
return(-1);
}
if (!yyjson_mut_obj_add_strcpy(doc, object, name, value)) {
errno = ENOMEM;
return(-1);
}
return(0);
}
static int json_add_uint64(yyjson_mut_doc *doc,
yyjson_mut_val *object,
const char *name,
unsigned long long value)
{
if (!doc || !object || !name) {
errno = EINVAL;
return(-1);
}
if (!yyjson_mut_obj_add_uint(doc, object, name, (uint64_t)value)) {
errno = ENOMEM;
return(-1);
}
return(0);
}
static int json_add_long(yyjson_mut_doc *doc,
yyjson_mut_val *object,
const char *name,
long value)
{
if (!metadata_time_valid(value))
return(-1);
return(json_add_uint64(doc, object, name, (unsigned long long)value));
}
static int json_get_string_copy(yyjson_val *object,
const char *name,
char *out,
size_t out_len)
{
yyjson_val *value_object;
const char *value;
size_t len;
if (!object || !name || !out || !out_len) {
errno = EINVAL;
return(-1);
}
value_object = yyjson_obj_get(object, name);
if (!yyjson_is_str(value_object)) {
errno = EINVAL;
return(-1);
}
value = yyjson_get_str(value_object);
len = strlen(value);
if (len >= out_len) {
errno = ENAMETOOLONG;
return(-1);
}
memcpy(out, value, len + 1);
return(0);
}
static int json_get_uint64(yyjson_val *object,
const char *name,
unsigned long long *out)
{
yyjson_val *value_object;
if (!object || !name || !out) {
errno = EINVAL;
return(-1);
}
value_object = yyjson_obj_get(object, name);
if (!yyjson_is_uint(value_object)) {
errno = EINVAL;
return(-1);
}
*out = (unsigned long long)yyjson_get_uint(value_object);
return(0);
}
static int json_get_long(yyjson_val *object, const char *name, long *out)
{
unsigned long long value;
if (!out) {
errno = EINVAL;
return(-1);
}
if (json_get_uint64(object, name, &value) < 0)
return(-1);
if (value > (unsigned long long)LONG_MAX) {
errno = ERANGE;
return(-1);
}
*out = (long)value;
return(0);
}
static int json_add_trustees(yyjson_mut_doc *doc,
yyjson_mut_val *object,
const struct nwsalvage_deleted_entry *entry)
{
yyjson_mut_val *array;
unsigned int i;
if (!doc || !object || !entry) {
errno = EINVAL;
return(-1);
}
if (entry->trustee_count > NWSALVAGE_TRUSTEE_MAX) {
errno = EINVAL;
return(-1);
}
array = yyjson_mut_arr(doc);
if (!array) {
errno = ENOMEM;
return(-1);
}
for (i = 0; i < entry->trustee_count; i++) {
yyjson_mut_val *trustee = yyjson_mut_obj(doc);
if (!trustee) {
errno = ENOMEM;
return(-1);
}
if (json_add_uint64(doc, trustee, "object_id",
entry->trustees[i].object_id) < 0 ||
json_add_uint64(doc, trustee, "rights",
entry->trustees[i].rights) < 0 ||
!yyjson_mut_arr_append(array, trustee)) {
if (!errno) errno = ENOMEM;
return(-1);
}
}
if (!yyjson_mut_obj_add_val(doc, object, "trustees", array)) {
errno = ENOMEM;
return(-1);
}
return(0);
}
static int json_get_trustees(yyjson_val *object,
struct nwsalvage_metadata_entry *entry)
{
yyjson_val *array;
yyjson_val *item;
size_t idx;
size_t max;
if (!object || !entry) {
errno = EINVAL;
return(-1);
}
array = yyjson_obj_get(object, "trustees");
if (!yyjson_is_arr(array)) {
errno = EINVAL;
return(-1);
}
max = yyjson_arr_size(array);
if (max > NWSALVAGE_TRUSTEE_MAX) {
errno = EINVAL;
return(-1);
}
entry->trustee_count = (unsigned int)max;
yyjson_arr_foreach(array, idx, max, item) {
unsigned long long value;
if (!yyjson_is_obj(item)) {
errno = EINVAL;
return(-1);
}
if (json_get_uint64(item, "object_id", &value) < 0)
return(-1);
entry->trustees[idx].object_id = (unsigned long)value;
if (json_get_uint64(item, "rights", &value) < 0)
return(-1);
entry->trustees[idx].rights = (unsigned int)value;
}
return(0);
}
int nwsalvage_write_metadata(const char *metadata_path,
const struct nwsalvage_deleted_entry *entry)
{
yyjson_mut_doc *doc;
yyjson_mut_val *object;
yyjson_write_err err;
const char *finder_info_hex;
int failed = 0;
if (!metadata_path || !*metadata_path || !entry ||
!entry->volume_name || !entry->deleted_by ||
!entry->original_path || !entry->original_name ||
!entry->recycle_relative_path || !entry->salvage_relative_path) {
errno = EINVAL;
return(-1);
}
finder_info_hex = metadata_finder_info_hex(entry);
if (finder_info_hex &&
validate_hex_string(finder_info_hex, NWSALVAGE_FINDER_INFO_HEX_LEN) < 0)
return(-1);
if (!metadata_time_valid(entry->deleted_at) ||
!metadata_time_valid(entry->atime) ||
!metadata_time_valid(entry->mtime) ||
!metadata_time_valid(entry->ctime))
return(-1);
if (make_parent_dirs(metadata_path) < 0)
return(-1);
doc = yyjson_mut_doc_new(NULL);
if (!doc) {
errno = ENOMEM;
return(-1);
}
object = yyjson_mut_obj(doc);
if (!object) {
yyjson_mut_doc_free(doc);
errno = ENOMEM;
return(-1);
}
yyjson_mut_doc_set_root(doc, object);
if (json_add_uint64(doc, object, "version", 1) < 0) failed = 1;
if (!failed && json_add_string(doc, object, "source", metadata_source(entry)) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "volume", entry->volume_name) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "deleted_by", entry->deleted_by) < 0)
failed = 1;
if (!failed && json_add_long(doc, object, "deleted_at", entry->deleted_at) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "original_path", entry->original_path) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "original_parent_entry_id",
entry->original_parent_entry_id) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "original_name", entry->original_name) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "recycle_relative_path",
entry->recycle_relative_path) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "salvage_relative_path",
entry->salvage_relative_path) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "attributes", entry->attributes) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "mode", entry->mode) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "size", entry->size) < 0)
failed = 1;
if (!failed && json_add_long(doc, object, "atime", entry->atime) < 0)
failed = 1;
if (!failed && json_add_long(doc, object, "mtime", entry->mtime) < 0)
failed = 1;
if (!failed && json_add_long(doc, object, "ctime", entry->ctime) < 0)
failed = 1;
if (!failed && finder_info_hex &&
json_add_string(doc, object, "finder_info_hex", finder_info_hex) < 0)
failed = 1;
if (!failed && json_add_string(doc, object, "afp_entry_id",
entry->afp_entry_id ? entry->afp_entry_id : "") < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "afp_attributes",
entry->afp_attributes) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "resource_fork_size",
entry->resource_fork_size) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archive_flags",
entry->netware_archive_flags) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archive_date",
entry->netware_archive_date) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archive_time",
entry->netware_archive_time) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_archiver_id",
entry->netware_archiver_id) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_fileinfo_flags",
entry->netware_fileinfo_flags) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_create_date",
entry->netware_create_date) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_create_time",
entry->netware_create_time) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_creator_id",
entry->netware_creator_id) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "netware_modifier_id",
entry->netware_modifier_id) < 0)
failed = 1;
if (!failed && json_add_uint64(doc, object, "inherited_rights_mask",
entry->inherited_rights_mask) < 0)
failed = 1;
if (!failed && json_add_trustees(doc, object, entry) < 0)
failed = 1;
if (!failed && !yyjson_mut_write_file(metadata_path, doc,
YYJSON_WRITE_PRETTY_TWO_SPACES |
YYJSON_WRITE_NEWLINE_AT_END,
NULL, &err)) {
(void)err;
errno = EIO;
failed = 1;
}
yyjson_mut_doc_free(doc);
if (failed) {
unlink(metadata_path);
return(-1);
}
return(0);
}
int nwsalvage_read_metadata(const char *metadata_path,
struct nwsalvage_metadata_entry *entry)
{
yyjson_doc *doc;
yyjson_val *object;
yyjson_read_err err;
unsigned long long value;
int failed = 0;
if (!metadata_path || !*metadata_path || !entry) {
errno = EINVAL;
return(-1);
}
memset(entry, 0, sizeof(*entry));
doc = yyjson_read_file(metadata_path, YYJSON_READ_NOFLAG, NULL, &err);
if (!doc) {
(void)err;
errno = EINVAL;
return(-1);
}
object = yyjson_doc_get_root(doc);
if (!yyjson_is_obj(object))
failed = 1;
if (!failed &&
(json_get_uint64(object, "version", &value) < 0 || value != 1))
failed = 1;
if (!failed && json_get_string_copy(object, "source",
entry->source,
sizeof(entry->source)) < 0)
failed = 1;
if (!failed && json_get_string_copy(object, "volume",
entry->volume_name,
sizeof(entry->volume_name)) < 0)
failed = 1;
if (!failed && json_get_string_copy(object, "deleted_by",
entry->deleted_by,
sizeof(entry->deleted_by)) < 0)
failed = 1;
if (!failed && json_get_long(object, "deleted_at", &entry->deleted_at) < 0)
failed = 1;
if (!failed && json_get_string_copy(object, "original_path",
entry->original_path,
sizeof(entry->original_path)) < 0)
failed = 1;
if (!failed && json_get_uint64(object, "original_parent_entry_id", &value) < 0)
failed = 1;
else if (!failed)
entry->original_parent_entry_id = (unsigned long)value;
if (!failed && json_get_string_copy(object, "original_name",
entry->original_name,
sizeof(entry->original_name)) < 0)
failed = 1;
if (!failed && json_get_string_copy(object, "recycle_relative_path",
entry->recycle_relative_path,
sizeof(entry->recycle_relative_path)) < 0)
failed = 1;
if (!failed && json_get_string_copy(object, "salvage_relative_path",
entry->salvage_relative_path,
sizeof(entry->salvage_relative_path)) < 0)
failed = 1;
if (!failed && json_get_uint64(object, "attributes", &value) < 0)
failed = 1;
else if (!failed)
entry->attributes = (unsigned int)value;
if (!failed && json_get_uint64(object, "mode", &value) < 0)
failed = 1;
else if (!failed)
entry->mode = (unsigned long)value;
if (!failed && json_get_uint64(object, "size", &value) < 0)
failed = 1;
else if (!failed)
entry->size = value;
if (!failed && json_get_long(object, "atime", &entry->atime) < 0)
failed = 1;
if (!failed && json_get_long(object, "mtime", &entry->mtime) < 0)
failed = 1;
if (!failed && json_get_long(object, "ctime", &entry->ctime) < 0)
failed = 1;
{
yyjson_val *finder = yyjson_obj_get(object, "finder_info_hex");
if (finder) {
if (!yyjson_is_str(finder) ||
strmaxcpy((uint8 *)entry->finder_info_hex,
yyjson_get_str(finder),
sizeof(entry->finder_info_hex) - 1) < 0 ||
validate_hex_string(entry->finder_info_hex,
NWSALVAGE_FINDER_INFO_HEX_LEN) < 0)
failed = 1;
}
}
if (!failed && json_get_string_copy(object, "afp_entry_id",
entry->afp_entry_id,
sizeof(entry->afp_entry_id)) < 0)
failed = 1;
if (!failed && json_get_uint64(object, "afp_attributes", &value) < 0)
failed = 1;
else if (!failed)
entry->afp_attributes = (unsigned int)value;
if (!failed && json_get_uint64(object, "resource_fork_size", &value) < 0)
failed = 1;
else if (!failed)
entry->resource_fork_size = value;
if (!failed && json_get_uint64(object, "netware_archive_flags", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archive_flags = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_archive_date", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archive_date = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_archive_time", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archive_time = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_archiver_id", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_archiver_id = (unsigned long)value;
if (!failed && json_get_uint64(object, "netware_fileinfo_flags", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_fileinfo_flags = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_create_date", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_create_date = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_create_time", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_create_time = (unsigned int)value;
if (!failed && json_get_uint64(object, "netware_creator_id", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_creator_id = (unsigned long)value;
if (!failed && json_get_uint64(object, "netware_modifier_id", &value) < 0)
failed = 1;
else if (!failed)
entry->netware_modifier_id = (unsigned long)value;
if (!failed && json_get_uint64(object, "inherited_rights_mask", &value) < 0)
failed = 1;
else if (!failed)
entry->inherited_rights_mask = (unsigned int)value;
if (!failed && json_get_trustees(object, entry) < 0)
failed = 1;
yyjson_doc_free(doc);
if (failed) {
errno = EINVAL;
return(-1);
}
return(0);
}
static int nwsalvage_touch_recycled(const char *path,
const struct nwsalvage_config *config,
const struct nwsalvage_deleted_entry *entry);
static int nwsalvage_move_deleted_file(const char *live_path,
const char *recycle_path,
const char *metadata_path,
const struct nwsalvage_config *config,
const struct nwsalvage_deleted_entry *entry)
{
int saved_errno;
if (!live_path || !*live_path ||
!recycle_path || !*recycle_path ||
!metadata_path || !*metadata_path ||
!config || !entry) {
errno = EINVAL;
return(-1);
}
if (!strcmp(live_path, recycle_path) ||
!strcmp(live_path, metadata_path) ||
!strcmp(recycle_path, metadata_path)) {
errno = EINVAL;
return(-1);
}
if (access(recycle_path, F_OK) == 0) {
errno = EEXIST;
return(-1);
}
if (errno != ENOENT)
return(-1);
if (access(metadata_path, F_OK) == 0) {
errno = EEXIST;
return(-1);
}
if (errno != ENOENT)
return(-1);
if (make_parent_dirs(recycle_path) < 0 || make_parent_dirs(metadata_path) < 0)
return(-1);
if (rename(live_path, recycle_path) < 0)
return(-1);
if (nwsalvage_touch_recycled(recycle_path, config, entry) < 0) {
saved_errno = errno;
(void)rename(recycle_path, live_path);
errno = saved_errno;
return(-1);
}
if (nwsalvage_write_metadata(metadata_path, entry) == 0)
return(0);
saved_errno = errno;
unlink(metadata_path);
if (rename(recycle_path, live_path) < 0) {
errno = saved_errno;
return(-1);
}
errno = saved_errno;
return(-1);
}
static int nwsalvage_ini_get(int entry, char *str, size_t strsize, void *data)
{
(void)data;
return(get_ini_entry(NULL, entry, (uint8 *)str, (int)strsize));
}
static int nwsalvage_copy_volume_root(int volume, char *root, size_t root_len)
{
size_t len;
if (volume < 0 || volume >= used_nw_volumes ||
!nw_volumes[volume].unixname || !nw_volumes[volume].unixnamlen ||
!root || !root_len) {
errno = EINVAL;
return(-1);
}
len = (size_t)nw_volumes[volume].unixnamlen;
if (len >= root_len) {
errno = ENAMETOOLONG;
return(-1);
}
memcpy(root, nw_volumes[volume].unixname, len);
root[len] = '\0';
while (len > 1 && root[len - 1] == '/') {
root[--len] = '\0';
}
return(0);
}
static int nwsalvage_relative_from_unix(int volume, const char *unixname,
char *relative, size_t relative_len)
{
char root[NWSALVAGE_PATH_MAX];
size_t root_len;
const char *p;
size_t len;
if (!unixname || !*unixname || !relative || !relative_len) {
errno = EINVAL;
return(-1);
}
if (nwsalvage_copy_volume_root(volume, root, sizeof(root)) < 0)
return(-1);
root_len = strlen(root);
if (strncmp(unixname, root, root_len)) {
errno = EINVAL;
return(-1);
}
p = unixname + root_len;
if (*p == '/')
p++;
if (!*p) {
errno = EINVAL;
return(-1);
}
len = strlen(p);
if (len >= relative_len) {
errno = ENAMETOOLONG;
return(-1);
}
memcpy(relative, p, len + 1);
return(0);
}
static int nwsalvage_relative_is_repo_path(const char *relative,
const char *repository)
{
size_t len;
if (!relative || !repository)
return(0);
len = strlen(repository);
return(!strcmp(relative, repository) ||
(!strncmp(relative, repository, len) && relative[len] == '/'));
}
static const char *nwsalvage_basename(const char *path)
{
const char *p;
if (!path)
return("");
p = strrchr(path, '/');
return(p ? p + 1 : path);
}
static int nwsalvage_dirname_copy(const char *path, char *out, size_t out_len)
{
const char *slash;
size_t len;
if (!path || !out || !out_len) {
errno = EINVAL;
return(-1);
}
slash = strrchr(path, '/');
if (!slash) {
out[0] = '\0';
return(0);
}
len = (size_t)(slash - path);
if (len >= out_len) {
errno = ENAMETOOLONG;
return(-1);
}
memcpy(out, path, len);
out[len] = '\0';
return(0);
}
static int nwsalvage_build_salvage_relative(char *out, size_t out_len,
const struct nwsalvage_config *config,
const char *deleted_by,
const char *relative_path)
{
const char *payload_path;
if (!out || !out_len || !config || !deleted_by || !*deleted_by ||
!relative_path || !*relative_path) {
errno = EINVAL;
return(-1);
}
payload_path = (config->flags & NWSALVAGE_FLAG_KEEP_TREE) ?
relative_path : nwsalvage_basename(relative_path);
if (slprintf(out, (int)out_len - 1, "%s/%s", deleted_by, payload_path) < 0) {
errno = ENAMETOOLONG;
return(-1);
}
return(0);
}
static int nwsalvage_build_copy_relative(char *out, size_t out_len,
const char *base_relative,
unsigned int copy_number)
{
char dir[NWSALVAGE_PATH_MAX];
const char *name;
if (!out || !out_len || !base_relative || !*base_relative || !copy_number) {
errno = EINVAL;
return(-1);
}
name = nwsalvage_basename(base_relative);
if (nwsalvage_dirname_copy(base_relative, dir, sizeof(dir)) < 0)
return(-1);
if (dir[0]) {
if (slprintf(out, (int)out_len - 1, "%s/Copy #%u of %s",
dir, copy_number, name) < 0) {
errno = ENAMETOOLONG;
return(-1);
}
} else {
if (slprintf(out, (int)out_len - 1, "Copy #%u of %s",
copy_number, name) < 0) {
errno = ENAMETOOLONG;
return(-1);
}
}
return(0);
}
static int nwsalvage_path_exists(const char *path)
{
if (access(path, F_OK) == 0)
return(1);
if (errno == ENOENT)
return(0);
return(-1);
}
static int nwsalvage_match_patterns(const char patterns[][NWSALVAGE_PATTERN_LEN_MAX],
unsigned int count,
const char *relative_path)
{
const char *base;
unsigned int i;
if (!relative_path)
return(0);
base = nwsalvage_basename(relative_path);
for (i = 0; i < count; i++) {
const char *pattern = patterns[i];
if (!pattern[0])
continue;
if (fnmatch(pattern, relative_path, FNM_CASEFOLD) == 0 ||
fnmatch(pattern, base, FNM_CASEFOLD) == 0)
return(1);
}
return(0);
}
static int nwsalvage_match_exclude_dir(const struct nwsalvage_config *config,
const char *relative_path)
{
char dir[NWSALVAGE_PATH_MAX];
unsigned int i;
if (!config || !relative_path)
return(0);
if (nwsalvage_dirname_copy(relative_path, dir, sizeof(dir)) < 0)
return(0);
if (!dir[0])
return(0);
for (i = 0; i < config->exclude_dir_count; i++) {
const char *pattern = config->exclude_dir_patterns[i];
const char *p = pattern;
if (!*p)
continue;
while (*p == '/')
p++;
if (fnmatch(p, dir, FNM_CASEFOLD) == 0 ||
!strcasecmp(p, dir) ||
(strlen(dir) > strlen(p) &&
!strncasecmp(dir, p, strlen(p)) && dir[strlen(p)] == '/'))
return(1);
}
return(0);
}
static int nwsalvage_should_skip(const struct nwsalvage_config *config,
const char *relative_path,
const struct stat *stb)
{
unsigned long long size;
if (!config || !relative_path || !stb)
return(0);
size = (unsigned long long)stb->st_size;
if (config->min_size && size < config->min_size)
return(1);
if (config->max_size && size > config->max_size)
return(1);
if (nwsalvage_match_patterns(config->exclude_patterns,
config->exclude_count, relative_path))
return(1);
if (nwsalvage_match_exclude_dir(config, relative_path))
return(1);
return(0);
}
static int nwsalvage_select_paths(const struct nwsalvage_config *config,
const char *volume_root,
const char *base_relative,
char *selected_relative,
size_t selected_relative_len,
char *recycle_path,
size_t recycle_path_len,
char *metadata_path,
size_t metadata_path_len,
char *recycle_relative_path,
size_t recycle_relative_path_len,
char *metadata_relative_path,
size_t metadata_relative_path_len)
{
unsigned int copy;
int recycle_exists;
int metadata_exists;
int use_versions;
if (!config || !volume_root || !base_relative || !selected_relative ||
!recycle_path || !metadata_path || !recycle_relative_path ||
!metadata_relative_path) {
errno = EINVAL;
return(-1);
}
use_versions = (config->flags & NWSALVAGE_FLAG_VERSIONS) &&
!nwsalvage_match_patterns(config->noversions_patterns,
config->noversions_count, base_relative);
for (copy = 0; copy < 1000000; copy++) {
if (!copy) {
if (strlen(base_relative) >= selected_relative_len) {
errno = ENAMETOOLONG;
return(-1);
}
strcpy(selected_relative, base_relative);
} else {
if (!use_versions)
break;
if (nwsalvage_build_copy_relative(selected_relative,
selected_relative_len,
base_relative, copy) < 0)
return(-1);
}
if (nwsalvage_build_recycle_path(recycle_path, recycle_path_len,
config, volume_root,
selected_relative) < 0 ||
nwsalvage_build_metadata_path(metadata_path, metadata_path_len,
config, volume_root,
selected_relative) < 0 ||
nwsalvage_build_recycle_relative_path(recycle_relative_path,
recycle_relative_path_len,
config, selected_relative) < 0 ||
nwsalvage_build_metadata_relative_path(metadata_relative_path,
metadata_relative_path_len,
config, selected_relative) < 0)
return(-1);
recycle_exists = nwsalvage_path_exists(recycle_path);
if (recycle_exists < 0)
return(-1);
metadata_exists = nwsalvage_path_exists(metadata_path);
if (metadata_exists < 0)
return(-1);
if (!recycle_exists && !metadata_exists)
return(0);
if (!use_versions) {
if (recycle_exists && unlink(recycle_path) < 0)
return(-1);
if (metadata_exists && unlink(metadata_path) < 0)
return(-1);
return(0);
}
}
errno = EEXIST;
return(-1);
}
static int nwsalvage_touch_recycled(const char *path,
const struct nwsalvage_config *config,
const struct nwsalvage_deleted_entry *entry)
{
struct utimbuf times;
time_t now;
if (!path || !config || !entry)
return(0);
if (!(config->flags & (NWSALVAGE_FLAG_TOUCH | NWSALVAGE_FLAG_TOUCH_MTIME)))
return(0);
now = time(NULL);
times.actime = now;
times.modtime = (config->flags & NWSALVAGE_FLAG_TOUCH_MTIME) ?
now : (time_t)entry->mtime;
return(utime(path, &times));
}
static void nwsalvage_format_deleted_by(char *out, size_t out_len)
{
if (!out || !out_len)
return;
if (act_obj_id == 1)
strmaxcpy((uint8 *)out, "SUPERVISOR", (int)out_len - 1);
else
slprintf(out, (int)out_len - 1, "0x%08x", act_obj_id);
}
static void nwsalvage_format_hex(char *out, size_t out_len,
const uint8 *data, size_t data_len)
{
static const char hex[] = "0123456789abcdef";
size_t i;
if (!out || out_len < data_len * 2 + 1)
return;
for (i = 0; i < data_len; i++) {
out[i * 2] = hex[(data[i] >> 4) & 0xf];
out[i * 2 + 1] = hex[data[i] & 0xf];
}
out[data_len * 2] = '\0';
}
static unsigned long nwsalvage_parent_entry_id(const char *unixname)
{
char parent[NWSALVAGE_PATH_MAX];
char *slash;
uint32 entry_id = 0;
size_t len;
if (!unixname || !*unixname)
return(0);
len = strlen(unixname);
if (len >= sizeof(parent))
return(0);
memcpy(parent, unixname, len + 1);
slash = strrchr(parent, '/');
if (!slash || slash == parent)
return(0);
*slash = '\0';
if (nwatalk_get_entry_id(parent, &entry_id) == 0)
return((unsigned long)entry_id);
return(0);
}
static void nwsalvage_fill_afp_metadata(const char *unixname,
struct nwsalvage_deleted_entry *entry,
char *finder_info_hex,
size_t finder_info_hex_len,
char *afp_entry_id,
size_t afp_entry_id_len)
{
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
uint16 afp_attributes = 0;
uint32 entry_id = 0;
uint32 resource_size = 0;
memset(finder_info, 0, sizeof(finder_info));
if (nwatalk_get_finder_info(unixname, finder_info, sizeof(finder_info)) == 0) {
nwsalvage_format_hex(finder_info_hex, finder_info_hex_len,
finder_info, sizeof(finder_info));
entry->finder_info_hex = finder_info_hex;
}
if (nwatalk_get_entry_id(unixname, &entry_id) == 0)
slprintf(afp_entry_id, (int)afp_entry_id_len - 1, "0x%08x", entry_id);
else
afp_entry_id[0] = '\0';
entry->afp_entry_id = afp_entry_id;
if (nwatalk_get_afp_attributes(unixname, &afp_attributes) == 0)
entry->afp_attributes = afp_attributes;
if (nwatalk_get_resource_fork_size(unixname, &resource_size) == 0)
entry->resource_fork_size = resource_size;
}
static void nwsalvage_fill_netware_xattrs(const char *unixname,
struct nwsalvage_deleted_entry *entry)
{
uint16 archive_date = 0;
uint16 archive_time = 0;
uint32 archiver_id = 0;
uint8 archive_flags = 0;
uint16 create_date = 0;
uint16 create_time = 0;
uint32 creator_id = 0;
uint32 modifier_id = 0;
uint8 fileinfo_flags = 0;
uint8 modifier_flags = 0;
mars_nwe_get_archive_info((char *)unixname, &archive_date, &archive_time,
&archiver_id, &archive_flags);
mars_nwe_get_file_info((char *)unixname, &create_date, &create_time,
&creator_id, &fileinfo_flags);
mars_nwe_get_file_modifier_info((char *)unixname, &modifier_id,
&modifier_flags);
entry->netware_archive_flags = archive_flags;
entry->netware_archive_date = archive_date;
entry->netware_archive_time = archive_time;
entry->netware_archiver_id = archiver_id;
entry->netware_fileinfo_flags = fileinfo_flags | modifier_flags;
entry->netware_create_date = create_date;
entry->netware_create_time = create_time;
entry->netware_creator_id = creator_id;
entry->netware_modifier_id = modifier_id;
}
static int nwsalvage_path_join(char *out, size_t out_len,
const char *a, const char *b)
{
size_t alen;
if (!out || !a || !b || !*a || !*b) {
errno = EINVAL;
return(-1);
}
alen = strlen(a);
if (snprintf(out, out_len, "%s%s%s", a,
(alen && a[alen - 1] == '/') ? "" : "/", b) >= (int)out_len) {
errno = ENAMETOOLONG;
return(-1);
}
return(0);
}
static int nwsalvage_metadata_dir_matches(const struct nwsalvage_metadata_entry *entry,
const char *volume_name,
unsigned long directory_base)
{
if (!entry || !volume_name)
return(0);
if (strcasecmp(entry->volume_name, volume_name) != 0)
return(0);
return(entry->original_parent_entry_id == directory_base);
}
static int nwsalvage_scan_metadata_dir(const char *metadata_dir,
const struct nwsalvage_config *config,
const char *volume_root,
const char *volume_name,
unsigned long directory_base,
unsigned long start_index,
unsigned long *match_index,
struct nwsalvage_scan_result *result)
{
DIR *dir;
struct dirent *de;
int found = 0;
dir = opendir(metadata_dir);
if (!dir)
return(errno == ENOENT ? 0 : -1);
while ((de = readdir(dir)) != NULL) {
char metadata_path[NWSALVAGE_PATH_MAX];
char recycle_path[NWSALVAGE_PATH_MAX];
struct nwsalvage_metadata_entry entry;
struct stat st;
size_t len;
if (de->d_name[0] == '.')
continue;
len = strlen(de->d_name);
if (len < 6 || strcmp(de->d_name + len - 5, ".json") != 0)
continue;
if (nwsalvage_path_join(metadata_path, sizeof(metadata_path),
metadata_dir, de->d_name) < 0)
continue;
memset(&entry, 0, sizeof(entry));
if (nwsalvage_read_metadata(metadata_path, &entry) < 0)
continue;
if (!nwsalvage_metadata_dir_matches(&entry, volume_name, directory_base))
continue;
if (*match_index < start_index) {
(*match_index)++;
continue;
}
if (nwsalvage_path_join(recycle_path, sizeof(recycle_path), volume_root,
entry.recycle_relative_path) < 0)
continue;
/*
* The .salvage JSON is only a sidecar for the real payload below
* .recycle. Samba, an administrator, or filesystem cleanup may remove
* the payload behind our back. Do not return such stale metadata from
* NCP 87/16 scans; remove the orphaned sidecar and keep scanning.
*/
if (stat(recycle_path, &st) < 0) {
if (errno == ENOENT) {
XDPRINTF((1, 0,
"WARN SALVAGE 87/16 STALE msg=\"payload missing; removing sidecar\" sidecar=\"%s\" recycle=\"%s\" name=\"%s\"",
metadata_path, recycle_path, entry.original_name));
(void)unlink(metadata_path);
}
continue;
}
if (!S_ISREG(st.st_mode))
continue;
memset(result, 0, sizeof(*result));
result->scan_sequence = *match_index;
result->scan_volume = (unsigned long)atoi(volume_name); /* overwritten below */
result->scan_directory_base = entry.original_parent_entry_id;
strmaxcpy(result->metadata_path, metadata_path,
sizeof(result->metadata_path) - 1);
strmaxcpy(result->recycle_path, recycle_path,
sizeof(result->recycle_path) - 1);
result->metadata = entry;
found = 1;
break;
}
closedir(dir);
return(found);
}
static int nwsalvage_scan_metadata_tree_by_directory_number(
const char *metadata_dir, const struct nwsalvage_config *config,
const char *volume_root, const char *volume_name,
unsigned long dos_directory_number,
struct nwsalvage_scan_result *result)
{
DIR *dir;
struct dirent *de;
unsigned long match_index;
int found;
if (!metadata_dir || !config || !volume_root || !volume_name || !result) {
errno = EINVAL;
return(-1);
}
match_index = 0;
found = nwsalvage_scan_metadata_dir(metadata_dir, config, volume_root,
volume_name, dos_directory_number,
0, &match_index, result);
if (found != 0)
return(found);
dir = opendir(metadata_dir);
if (!dir)
return(errno == ENOENT ? 0 : -1);
while ((de = readdir(dir)) != NULL) {
char child[NWSALVAGE_PATH_MAX];
struct stat st;
if (de->d_name[0] == '.')
continue;
if (nwsalvage_path_join(child, sizeof(child), metadata_dir, de->d_name) < 0)
continue;
if (stat(child, &st) < 0 || !S_ISDIR(st.st_mode))
continue;
found = nwsalvage_scan_metadata_tree_by_directory_number(
child, config, volume_root, volume_name, dos_directory_number, result);
if (found != 0)
break;
}
closedir(dir);
return(found);
}
int nwsalvage_find_by_deleted_directory_number(int volume,
unsigned long dos_directory_number,
struct nwsalvage_scan_result *result)
{
struct nwsalvage_config config;
char volume_root[NWSALVAGE_PATH_MAX];
char metadata_root[NWSALVAGE_PATH_MAX];
char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX];
DIR *root;
struct dirent *de;
int found = 0;
if (!result) {
errno = EINVAL;
return(-1);
}
if (nwsalvage_config_load_from_ini(&config, nwsalvage_ini_get, NULL) < 0)
return(-1);
if (!config.enabled)
return(0);
if (nwsalvage_copy_volume_root(volume, volume_root, sizeof(volume_root)) < 0)
return(-1);
if (nw_get_volume_name(volume, (uint8 *)volume_name,
sizeof(volume_name)) < 1)
return(-1);
if (nwsalvage_path_join(metadata_root, sizeof(metadata_root), volume_root,
config.metadata_repository) < 0)
return(-1);
root = opendir(metadata_root);
if (!root)
return(errno == ENOENT ? 0 : -1);
while ((de = readdir(root)) != NULL) {
char user_dir[NWSALVAGE_PATH_MAX];
struct stat st;
if (de->d_name[0] == '.')
continue;
if (nwsalvage_path_join(user_dir, sizeof(user_dir),
metadata_root, de->d_name) < 0)
continue;
if (stat(user_dir, &st) < 0 || !S_ISDIR(st.st_mode))
continue;
found = nwsalvage_scan_metadata_tree_by_directory_number(
user_dir, &config, volume_root, volume_name,
dos_directory_number, result);
if (found != 0)
break;
}
closedir(root);
if (found > 0)
result->scan_volume = (unsigned long)volume;
return(found);
}
int nwsalvage_scan_directory(int volume, const char *relative_dir,
unsigned long directory_base,
unsigned long scan_sequence,
struct nwsalvage_scan_result *result)
{
struct nwsalvage_config config;
char volume_root[NWSALVAGE_PATH_MAX];
char metadata_root[NWSALVAGE_PATH_MAX];
char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX];
unsigned long start_index;
unsigned long match_index = 0;
DIR *root;
struct dirent *de;
int found = 0;
if (!relative_dir || !result) {
errno = EINVAL;
return(-1);
}
if (nwsalvage_config_load_from_ini(&config, nwsalvage_ini_get, NULL) < 0)
return(-1);
if (!config.enabled)
return(0);
if (nwsalvage_copy_volume_root(volume, volume_root, sizeof(volume_root)) < 0)
return(-1);
if (nw_get_volume_name(volume, (uint8 *)volume_name,
sizeof(volume_name)) < 1)
return(-1);
if (nwsalvage_path_join(metadata_root, sizeof(metadata_root), volume_root,
config.metadata_repository) < 0)
return(-1);
start_index = (scan_sequence == 0xffffffffUL) ? 0 : scan_sequence + 1;
root = opendir(metadata_root);
if (!root)
return(errno == ENOENT ? 0 : -1);
while ((de = readdir(root)) != NULL) {
char user_dir[NWSALVAGE_PATH_MAX];
char scan_dir[NWSALVAGE_PATH_MAX];
struct stat st;
if (de->d_name[0] == '.')
continue;
if (nwsalvage_path_join(user_dir, sizeof(user_dir),
metadata_root, de->d_name) < 0)
continue;
if (stat(user_dir, &st) < 0 || !S_ISDIR(st.st_mode))
continue;
if ((config.flags & NWSALVAGE_FLAG_KEEP_TREE) && *relative_dir) {
if (nwsalvage_path_join(scan_dir, sizeof(scan_dir),
user_dir, relative_dir) < 0)
continue;
} else {
strmaxcpy(scan_dir, user_dir, sizeof(scan_dir) - 1);
}
found = nwsalvage_scan_metadata_dir(scan_dir, &config, volume_root,
volume_name, directory_base,
start_index, &match_index, result);
if (found != 0)
break;
}
closedir(root);
if (found > 0)
result->scan_volume = (unsigned long)volume;
return(found);
}
static int nwsalvage_hex_nibble(int c)
{
if (c >= '0' && c <= '9') return(c - '0');
if (c >= 'a' && c <= 'f') return(c - 'a' + 10);
if (c >= 'A' && c <= 'F') return(c - 'A' + 10);
return(-1);
}
static int nwsalvage_hex_to_bytes(const char *hex, uint8 *out, size_t out_len)
{
size_t i;
if (!hex || !out || strlen(hex) != out_len * 2) {
errno = EINVAL;
return(-1);
}
for (i = 0; i < out_len; i++) {
int hi = nwsalvage_hex_nibble((unsigned char)hex[i * 2]);
int lo = nwsalvage_hex_nibble((unsigned char)hex[i * 2 + 1]);
if (hi < 0 || lo < 0) {
errno = EINVAL;
return(-1);
}
out[i] = (uint8)((hi << 4) | lo);
}
return(0);
}
int nwsalvage_metadata_finder_info(const struct nwsalvage_metadata_entry *entry,
unsigned char *out, size_t out_len)
{
if (!entry || !out) {
errno = EINVAL;
return(-1);
}
memset(out, 0, out_len);
if (!entry->finder_info_hex[0])
return(0);
return(nwsalvage_hex_to_bytes(entry->finder_info_hex, out, out_len));
}
static int nwsalvage_finder_info_is_zero(const uint8 *finder_info, size_t len)
{
size_t i;
for (i = 0; i < len; i++)
if (finder_info[i])
return(0);
return(1);
}
static int nwsalvage_restore_metadata(int volume, const char *unixname,
const struct nwsalvage_metadata_entry *entry)
{
struct stat stb;
struct utimbuf times;
int i;
if (!unixname || !entry) {
errno = EINVAL;
return(-1);
}
if (stat(unixname, &stb) < 0)
return(-1);
(void)set_nw_attrib_dword(volume, (char *)unixname, &stb,
(uint32)entry->attributes);
(void)mars_nwe_set_archive_info((char *)unixname,
!!(entry->netware_archive_flags & MARS_NWE_ARCHIVE_HAS_DATE),
(uint16)entry->netware_archive_date,
!!(entry->netware_archive_flags & MARS_NWE_ARCHIVE_HAS_TIME),
(uint16)entry->netware_archive_time,
!!(entry->netware_archive_flags & MARS_NWE_ARCHIVE_HAS_ARCHIVER),
(uint32)entry->netware_archiver_id);
(void)mars_nwe_set_file_info((char *)unixname,
!!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_CREATE_DATE),
(uint16)entry->netware_create_date,
!!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_CREATE_TIME),
(uint16)entry->netware_create_time,
!!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_CREATOR),
(uint32)entry->netware_creator_id);
(void)mars_nwe_set_file_modifier_info((char *)unixname,
!!(entry->netware_fileinfo_flags & MARS_NWE_FILEINFO_HAS_MODIFIER),
(uint32)entry->netware_modifier_id);
if (entry->finder_info_hex[0]) {
uint8 finder_info[NWATALK_FINDER_INFO_LEN];
if (nwsalvage_hex_to_bytes(entry->finder_info_hex, finder_info,
sizeof(finder_info)) == 0 &&
!nwsalvage_finder_info_is_zero(finder_info, sizeof(finder_info)))
(void)nwatalk_set_finder_info(unixname, finder_info, sizeof(finder_info));
}
if (entry->afp_attributes)
(void)nwatalk_set_afp_attributes(unixname, (uint16)entry->afp_attributes);
if (entry->afp_entry_id[0]) {
char *end = NULL;
unsigned long entry_id = strtoul(entry->afp_entry_id, &end, 0);
if (end && *end == '\0')
(void)nwatalk_set_entry_id(unixname, (uint32)entry_id);
}
(void)tru_set_inherited_mask(volume, (uint8 *)unixname, &stb,
(int)entry->inherited_rights_mask);
if (entry->trustee_count) {
NW_OIC nwoic[NWSALVAGE_TRUSTEE_MAX];
int count = (int)entry->trustee_count;
if (count > NWSALVAGE_TRUSTEE_MAX)
count = NWSALVAGE_TRUSTEE_MAX;
memset(nwoic, 0, sizeof(nwoic));
for (i = 0; i < count; i++) {
nwoic[i].id = (uint32)entry->trustees[i].object_id;
nwoic[i].trustee = (int)entry->trustees[i].rights;
}
(void)tru_add_trustee_set(volume, (uint8 *)unixname, &stb, count, nwoic);
}
if (entry->mode) {
if (seteuid(0)) {}
(void)chmod(unixname, (mode_t)(entry->mode & 07777));
(void)reseteuid();
}
times.actime = (time_t)entry->atime;
times.modtime = (time_t)entry->mtime;
if (times.actime || times.modtime)
(void)utime(unixname, &times);
return(0);
}
static int nwsalvage_copy_payload_with_novell_io(int volume,
const char *source_unixname,
const char *dest_unixname,
const struct stat *source_stb,
int task)
{
struct stat src_open_stb;
struct stat dst_open_stb;
int source_handle = -1;
int dest_handle = -1;
unsigned long long remaining;
uint32 offset = 0;
int result = 0;
if (!source_unixname || !dest_unixname || !source_stb) {
errno = EINVAL;
return(-1);
}
source_handle = file_creat_open(volume, (uint8 *)source_unixname,
&src_open_stb, 0, 1, 8, task);
if (source_handle < 0)
return(source_handle);
dest_handle = file_creat_open(volume, (uint8 *)dest_unixname,
&dst_open_stb, FILE_ATTR_NORMAL, 3, 2 | 8,
task);
if (dest_handle < 0) {
(void)nw_close_file(source_handle, 0, task);
return(dest_handle);
}
remaining = (unsigned long long)source_stb->st_size;
while (remaining) {
uint32 chunk;
int copied;
if (offset == 0xffffffffUL) {
result = -0xfe;
break;
}
chunk = (remaining > 0x7fffffffUL) ? 0x7fffffffUL : (uint32)remaining;
copied = nw_server_copy(source_handle, offset, dest_handle, offset, chunk);
if (copied < 0) {
result = copied;
break;
}
if ((uint32)copied != chunk) {
result = -0xff;
break;
}
remaining -= chunk;
offset += chunk;
}
if (!result)
result = nw_commit_file(dest_handle);
(void)nw_close_file(dest_handle, 0, task);
(void)nw_close_file(source_handle, 0, task);
return(result);
}
int nwsalvage_purge_scan_result(const struct nwsalvage_scan_result *scan)
{
int failed = 0;
if (!scan) {
errno = EINVAL;
return(-1);
}
if (scan->recycle_path[0] && unlink(scan->recycle_path) < 0 && errno != ENOENT)
failed = 1;
if (scan->metadata_path[0] && unlink(scan->metadata_path) < 0 && errno != ENOENT)
failed = 1;
if (failed)
return(-1);
return(1);
}
int nwsalvage_recover_scan_result(int volume,
const struct nwsalvage_scan_result *scan,
const char *dest_unixname, int task)
{
struct stat source_stb;
int result;
if (!scan || !dest_unixname || !*dest_unixname) {
errno = EINVAL;
return(-1);
}
if (access(dest_unixname, F_OK) == 0) {
errno = EEXIST;
return(-1);
}
if (errno != ENOENT)
return(-1);
if (stat(scan->recycle_path, &source_stb) < 0)
return(-1);
if (!S_ISREG(source_stb.st_mode)) {
errno = EINVAL;
return(-1);
}
result = nwsalvage_copy_payload_with_novell_io(volume, scan->recycle_path,
dest_unixname, &source_stb,
task);
if (result < 0) {
unlink(dest_unixname);
errno = EIO;
return(-1);
}
if (nwsalvage_restore_metadata(volume, dest_unixname, &scan->metadata) < 0) {
/* The payload is already safely restored through mars_nwe I/O. Keep it. */
}
if (unlink(scan->recycle_path) < 0)
return(-1);
if (unlink(scan->metadata_path) < 0)
return(-1);
return(1);
}
static void nwsalvage_fill_trustees(int volume, const char *unixname,
const struct stat *stb,
struct nwsalvage_deleted_entry *entry)
{
uint32 ids[NWSALVAGE_TRUSTEE_MAX];
int trustees[NWSALVAGE_TRUSTEE_MAX];
int count;
int i;
entry->inherited_rights_mask =
(unsigned int)tru_get_inherited_mask(volume, (uint8 *)unixname,
(struct stat *)stb);
count = tru_get_trustee_set(volume, (uint8 *)unixname, (struct stat *)stb,
0, NWSALVAGE_TRUSTEE_MAX, ids, trustees);
if (count <= 0)
return;
if (count > NWSALVAGE_TRUSTEE_MAX)
count = NWSALVAGE_TRUSTEE_MAX;
entry->trustee_count = (unsigned int)count;
for (i = 0; i < count; i++) {
entry->trustees[i].object_id = ids[i];
entry->trustees[i].rights = (unsigned int)trustees[i];
}
}
int nwsalvage_capture_node_delete(int volume, const char *unixname,
const struct stat *stb)
{
struct nwsalvage_config config;
struct nwsalvage_deleted_entry entry;
char volume_root[NWSALVAGE_PATH_MAX];
char relative_path[NWSALVAGE_PATH_MAX];
char recycle_path[NWSALVAGE_PATH_MAX];
char metadata_path[NWSALVAGE_PATH_MAX];
char base_salvage_relative_path[NWSALVAGE_PATH_MAX];
char selected_salvage_relative_path[NWSALVAGE_PATH_MAX];
char recycle_relative_path[NWSALVAGE_PATH_MAX];
char metadata_relative_path[NWSALVAGE_PATH_MAX];
char original_path[NWSALVAGE_PATH_MAX];
char volume_name[NWSALVAGE_REPOSITORY_NAME_MAX];
char deleted_by[NWSALVAGE_USER_NAME_MAX];
char finder_info_hex[NWSALVAGE_FINDER_INFO_HEX_LEN + 1];
char afp_entry_id[NWSALVAGE_AFP_ENTRY_ID_MAX];
if (!unixname || !*unixname || !stb) {
errno = EINVAL;
return(-1);
}
if (!S_ISREG(stb->st_mode))
return(1);
if (nwsalvage_config_load_from_ini(&config, nwsalvage_ini_get, NULL) < 0)
return(-1);
if (!config.enabled)
return(1);
if (nwsalvage_copy_volume_root(volume, volume_root, sizeof(volume_root)) < 0 ||
nwsalvage_relative_from_unix(volume, unixname,
relative_path, sizeof(relative_path)) < 0)
return(-1);
if (nwsalvage_relative_is_repo_path(relative_path,
config.recycle_repository) ||
nwsalvage_relative_is_repo_path(relative_path,
config.metadata_repository))
return(1);
if (nwsalvage_should_skip(&config, relative_path, stb))
return(1);
nwsalvage_format_deleted_by(deleted_by, sizeof(deleted_by));
if (nwsalvage_build_salvage_relative(base_salvage_relative_path,
sizeof(base_salvage_relative_path),
&config, deleted_by, relative_path) < 0)
return(-1);
if (nwsalvage_select_paths(&config, volume_root, base_salvage_relative_path,
selected_salvage_relative_path,
sizeof(selected_salvage_relative_path),
recycle_path, sizeof(recycle_path),
metadata_path, sizeof(metadata_path),
recycle_relative_path,
sizeof(recycle_relative_path),
metadata_relative_path,
sizeof(metadata_relative_path)) < 0)
return(-1);
if (nw_get_volume_name(volume, (uint8 *)volume_name,
sizeof(volume_name)) < 1)
return(-1);
if (slprintf(original_path, sizeof(original_path) - 1, "%s:%s",
volume_name, relative_path) < 0) {
errno = ENAMETOOLONG;
return(-1);
}
memset(&entry, 0, sizeof(entry));
finder_info_hex[0] = '\0';
afp_entry_id[0] = '\0';
entry.source = "mars_nwe";
entry.volume_name = volume_name;
entry.deleted_by = deleted_by;
entry.deleted_at = (long)time(NULL);
entry.original_path = original_path;
entry.original_parent_entry_id = nwsalvage_parent_entry_id(unixname);
entry.original_name = nwsalvage_basename(relative_path);
entry.recycle_relative_path = recycle_relative_path;
entry.salvage_relative_path = metadata_relative_path;
entry.attributes = get_nw_attrib_dword(volume, (char *)unixname,
(struct stat *)stb);
entry.mode = (unsigned long)stb->st_mode;
entry.size = (unsigned long long)stb->st_size;
entry.atime = (long)stb->st_atime;
entry.mtime = (long)stb->st_mtime;
entry.ctime = (long)stb->st_ctime;
nwsalvage_fill_afp_metadata(unixname, &entry, finder_info_hex,
sizeof(finder_info_hex), afp_entry_id,
sizeof(afp_entry_id));
nwsalvage_fill_netware_xattrs(unixname, &entry);
nwsalvage_fill_trustees(volume, unixname, stb, &entry);
if (nwsalvage_move_deleted_file(unixname, recycle_path,
metadata_path, &config, &entry) < 0)
return(-1);
return(0);
}