2504 lines
69 KiB
C
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, ×));
|
|
}
|
|
|
|
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, ×);
|
|
|
|
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);
|
|
}
|