salvage: hook delete path through nwsalvage
All checks were successful
Source release / source-package (push) Successful in 55s
All checks were successful
Source release / source-package (push) Successful in 55s
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
typedef int (*nwsalvage_ini_getter)(int entry, char *str,
|
||||
size_t strsize, void *data);
|
||||
@@ -145,5 +146,14 @@ int nwsalvage_write_metadata(const char *metadata_path,
|
||||
const struct nwsalvage_deleted_entry *entry);
|
||||
int nwsalvage_read_metadata(const char *metadata_path,
|
||||
struct nwsalvage_metadata_entry *entry);
|
||||
/*
|
||||
* Capture a server-side delete before nw_unlink_node() would remove it.
|
||||
* Returns 0 when the file was moved to the recycle repository and metadata
|
||||
* was written. Returns 1 when salvage is disabled or the node is not
|
||||
* salvageable and the caller should continue with the normal unlink path.
|
||||
* Returns -1 on a real salvage failure; the caller should keep the live file.
|
||||
*/
|
||||
int nwsalvage_capture_node_delete(int volume, const char *unixname,
|
||||
const struct stat *stb);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "net.h"
|
||||
#include "unxfile.h"
|
||||
#include "nwsalvage.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <utime.h>
|
||||
@@ -1976,6 +1977,7 @@ static int nw_rmdir(uint8 *unname)
|
||||
int nw_unlink_node(int volume, uint8 *unname, struct stat *stb)
|
||||
{
|
||||
int result=-1;
|
||||
int salvaged=0;
|
||||
uint32 attrib=get_nw_attrib_dword(volume, unname, stb);
|
||||
/* first we look for attributes */
|
||||
if (attrib & (FILE_ATTR_R|FILE_ATTR_DELETE_INH))
|
||||
@@ -2000,13 +2002,20 @@ int nw_unlink_node(int volume, uint8 *unname, struct stat *stb)
|
||||
if (!(entry8_flags&0x10) &&
|
||||
-1 == share_file(stb->st_dev, stb->st_ino, 0x10f, 2))
|
||||
return(-0x8a); /* NO Delete Privileges, file is open */
|
||||
if (0 != (result=unlink(unname))){
|
||||
if (seteuid(0)) {}
|
||||
result=unlink(unname) ? -0x8a : 0;
|
||||
(void)reseteuid();
|
||||
result = nwsalvage_capture_node_delete(volume, (const char *)unname, stb);
|
||||
if (!result) {
|
||||
salvaged = 1;
|
||||
} else if (result > 0) {
|
||||
if (0 != (result=unlink(unname))){
|
||||
if (seteuid(0)) {}
|
||||
result=unlink(unname) ? -0x8a : 0;
|
||||
(void)reseteuid();
|
||||
}
|
||||
} else if (result < 0) {
|
||||
result = -0x8a;
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
if (!result && !salvaged) {
|
||||
free_nw_ext_inode(volume, unname, stb->st_dev, stb->st_ino);
|
||||
}
|
||||
return(result);
|
||||
|
||||
415
src/nwsalvage.c
415
src/nwsalvage.c
@@ -1,6 +1,15 @@
|
||||
/* 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 "trustee.h"
|
||||
#include "tools.h"
|
||||
#include "connect.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
@@ -12,6 +21,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int nwsalvage_repository_name_valid(const char *name)
|
||||
@@ -999,3 +1009,408 @@ int nwsalvage_read_metadata(const char *metadata_path,
|
||||
return(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MARS_NWE_HAVE_YYJSON
|
||||
static int nwsalvage_move_deleted_file(const char *live_path,
|
||||
const char *recycle_path,
|
||||
const char *metadata_path,
|
||||
const struct nwsalvage_deleted_entry *entry)
|
||||
{
|
||||
int saved_errno;
|
||||
|
||||
if (!live_path || !*live_path ||
|
||||
!recycle_path || !*recycle_path ||
|
||||
!metadata_path || !*metadata_path ||
|
||||
!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_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);
|
||||
}
|
||||
#endif
|
||||
|
||||
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 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)
|
||||
memset(finder_info, 0, sizeof(finder_info));
|
||||
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 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)
|
||||
{
|
||||
#ifndef MARS_NWE_HAVE_YYJSON
|
||||
(void)volume;
|
||||
(void)unixname;
|
||||
(void)stb;
|
||||
return(1);
|
||||
#else
|
||||
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 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);
|
||||
|
||||
nwsalvage_format_deleted_by(deleted_by, sizeof(deleted_by));
|
||||
if (slprintf(salvage_relative_path, sizeof(salvage_relative_path) - 1,
|
||||
"%s/%s", deleted_by, relative_path) < 0) {
|
||||
errno = ENAMETOOLONG;
|
||||
return(-1);
|
||||
}
|
||||
|
||||
if (nwsalvage_build_recycle_path(recycle_path, sizeof(recycle_path),
|
||||
&config, volume_root,
|
||||
salvage_relative_path) < 0 ||
|
||||
nwsalvage_build_metadata_path(metadata_path, sizeof(metadata_path),
|
||||
&config, volume_root,
|
||||
salvage_relative_path) < 0 ||
|
||||
nwsalvage_build_recycle_relative_path(recycle_relative_path,
|
||||
sizeof(recycle_relative_path),
|
||||
&config,
|
||||
salvage_relative_path) < 0 ||
|
||||
nwsalvage_build_metadata_relative_path(metadata_relative_path,
|
||||
sizeof(metadata_relative_path),
|
||||
&config,
|
||||
salvage_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));
|
||||
memset(finder_info_hex, '0', NWSALVAGE_FINDER_INFO_HEX_LEN);
|
||||
finder_info_hex[NWSALVAGE_FINDER_INFO_HEX_LEN] = '\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, &entry) < 0)
|
||||
return(-1);
|
||||
|
||||
return(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -27,11 +27,56 @@ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES
|
||||
${SALVAGE_LAYOUT_SMOKE_SCRIPT}
|
||||
)
|
||||
|
||||
set(SALVAGE_NCP_DELETE_SMOKE_SCRIPT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/salvage_ncp_delete_smoke.sh
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/salvage_ncp_delete_smoke.sh
|
||||
${SALVAGE_NCP_DELETE_SMOKE_SCRIPT}
|
||||
COMMAND ${CMAKE_COMMAND} -E env chmod +x ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT}
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/salvage_ncp_delete_smoke.sh
|
||||
COMMENT "Copying salvage NCP delete smoke helper"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(salvage_ncp_delete_smoke_suite ALL
|
||||
DEPENDS ${SALVAGE_NCP_DELETE_SMOKE_SCRIPT}
|
||||
)
|
||||
|
||||
find_path(SALVAGE_NCPFS_INCLUDE_DIR
|
||||
NAMES ncp/nwcalls.h ncp/ncplib.h
|
||||
)
|
||||
|
||||
find_library(SALVAGE_NCPFS_LIBRARY
|
||||
NAMES ncp
|
||||
)
|
||||
|
||||
if(SALVAGE_NCPFS_INCLUDE_DIR AND SALVAGE_NCPFS_LIBRARY)
|
||||
add_executable(ncp_delete_smoke ncp_delete_smoke.c)
|
||||
target_include_directories(ncp_delete_smoke PRIVATE ${SALVAGE_NCPFS_INCLUDE_DIR})
|
||||
target_link_libraries(ncp_delete_smoke ${SALVAGE_NCPFS_LIBRARY})
|
||||
else()
|
||||
message(STATUS
|
||||
"Skipping salvage NCP delete smoke helper: ncpfs/libncp headers or library not found"
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(salvage_config_smoke
|
||||
salvage_config_smoke.c
|
||||
${CMAKE_SOURCE_DIR}/src/nwsalvage.c
|
||||
)
|
||||
|
||||
# nwsalvage.c now also contains the server-side delete hook. The config smoke
|
||||
# only exercises the standalone helper surface, so let the linker discard the
|
||||
# unreferenced server hook sections instead of requiring all nwconn objects.
|
||||
set_target_properties(salvage_config_smoke PROPERTIES
|
||||
COMPILE_FLAGS "-ffunction-sections -fdata-sections"
|
||||
LINK_FLAGS "-Wl,--gc-sections"
|
||||
)
|
||||
|
||||
target_include_directories(salvage_config_smoke PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
@@ -48,3 +48,35 @@ volume/dev/inode, rather than in the file payload itself. Salvage metadata
|
||||
therefore needs to preserve the inherited rights mask and the explicit trustee
|
||||
object/right pairs so recovery can recreate equivalent trustee records for the
|
||||
restored object.
|
||||
|
||||
## NCP delete capture smoke
|
||||
|
||||
`salvage_ncp_delete_smoke.sh` is the integration check for the first real
|
||||
server-side salvage hook. It uses `ncp_delete_smoke`, a small libncp client, to
|
||||
create and delete a file through the classic NetWare NCP file functions:
|
||||
|
||||
- `0x43` create file
|
||||
- `0x42` close file
|
||||
- `0x44` delete file
|
||||
|
||||
The script does not call local `rm`/`unlink` for the tested live path. Local
|
||||
filesystem access is used only after the NCP delete to inspect the expected
|
||||
recycle payload and JSON sidecar:
|
||||
|
||||
```text
|
||||
SYS/.recycle/SUPERVISOR/PUBLIC/SLVGCHK.TXT
|
||||
SYS/.salvage/SUPERVISOR/PUBLIC/SLVGCHK.TXT.json
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
./tests/salvage/salvage_ncp_delete_smoke.sh \
|
||||
-S MARS -U SUPERVISOR -P secret \
|
||||
--path SYS:PUBLIC/SLVGCHK.TXT \
|
||||
--unix-path /var/mars_nwe/SYS/public/SLVGCHK.TXT
|
||||
```
|
||||
|
||||
This check lives under `tests/salvage` rather than `tests/afp` because later
|
||||
salvage scan/recover/purge and versioning tests should share the same fixture.
|
||||
AFP can use the same backend later as an adapter for `0x13`.
|
||||
|
||||
245
tests/salvage/ncp_delete_smoke.c
Normal file
245
tests/salvage/ncp_delete_smoke.c
Normal file
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Linux smoke helper for classic NetWare file create/delete.
|
||||
*
|
||||
* The helper intentionally uses normal NCP file create/close/delete requests,
|
||||
* not a local Unix unlink. It is used by the salvage smoke script to verify
|
||||
* that mars_nwe captures files deleted through the server path.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ncp/nwcalls.h>
|
||||
#include <ncp/ncplib.h>
|
||||
#include <ncp/kernel/ncp.h>
|
||||
|
||||
#define NCP_CREATE_FILE_OVERWRITE 0x43
|
||||
#define NCP_CLOSE_FILE 0x42
|
||||
#define NCP_DELETE_FILE 0x44
|
||||
#define NWE_INVALID_PATH 0x9c
|
||||
#ifndef NWE_INVALID_NCP_PACKET_LENGTH
|
||||
#define NWE_INVALID_NCP_PACKET_LENGTH 0x7e
|
||||
#endif
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %s [--expect-delete CODE] [--create-only] [--delete-only] "
|
||||
"[ncpfs options] PATH\n"
|
||||
"\n"
|
||||
"ncpfs options are parsed by ncp_initialize(), for example:\n"
|
||||
" -S SERVER -U USER -P PASSWORD -n\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
" %s -S MARS -U SUPERVISOR -P secret SYS:PUBLIC/SALVAGE.TXT\n",
|
||||
prog, prog);
|
||||
}
|
||||
|
||||
static int parse_u32(const char *text, uint32_t *value)
|
||||
{
|
||||
char *end = NULL;
|
||||
unsigned long v;
|
||||
|
||||
errno = 0;
|
||||
v = strtoul(text, &end, 0);
|
||||
if (errno || !end || *end || v > 0xffffffffUL)
|
||||
return -1;
|
||||
*value = (uint32_t)v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t le32_to_cpu(const uint8_t p[4])
|
||||
{
|
||||
return ((uint32_t)p[0]) |
|
||||
((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
static void cpu_to_le32(uint32_t v, uint8_t p[4])
|
||||
{
|
||||
p[0] = (uint8_t)v;
|
||||
p[1] = (uint8_t)(v >> 8);
|
||||
p[2] = (uint8_t)(v >> 16);
|
||||
p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
static NWCCODE ncp_create_file(NWCONN_HANDLE conn, const char *path,
|
||||
uint32_t *file_handle)
|
||||
{
|
||||
size_t path_len = strlen(path);
|
||||
uint8_t request[1 + 1 + 1 + 255];
|
||||
uint8_t reply_buf[2 + 4 + 2 + 64];
|
||||
NW_FRAGMENT reply;
|
||||
NWCCODE err;
|
||||
|
||||
if (path_len > 255)
|
||||
return NWE_INVALID_PATH;
|
||||
|
||||
request[0] = 0; /* directory handle 0, raw VOL:path */
|
||||
request[1] = 0; /* create attributes */
|
||||
request[2] = (uint8_t)path_len;
|
||||
memcpy(request + 3, path, path_len);
|
||||
|
||||
memset(reply_buf, 0, sizeof(reply_buf));
|
||||
reply.fragAddr.rw = reply_buf;
|
||||
reply.fragSize = sizeof(reply_buf);
|
||||
|
||||
err = NWRequestSimple(conn, NCP_CREATE_FILE_OVERWRITE,
|
||||
request, 3 + path_len, &reply);
|
||||
if (err)
|
||||
return err;
|
||||
if (reply.fragSize < 6)
|
||||
return NWE_INVALID_NCP_PACKET_LENGTH;
|
||||
|
||||
*file_handle = le32_to_cpu(reply_buf + 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NWCCODE ncp_close_file(NWCONN_HANDLE conn, uint32_t file_handle)
|
||||
{
|
||||
uint8_t request[1 + 2 + 4];
|
||||
NW_FRAGMENT reply;
|
||||
NWCCODE err;
|
||||
|
||||
memset(request, 0, sizeof(request));
|
||||
request[0] = 0; /* reserved */
|
||||
request[1] = 0;
|
||||
request[2] = 0; /* extended file handle */
|
||||
cpu_to_le32(file_handle, request + 3);
|
||||
|
||||
reply.fragAddr.rw = NULL;
|
||||
reply.fragSize = 0;
|
||||
|
||||
err = NWRequestSimple(conn, NCP_CLOSE_FILE,
|
||||
request, sizeof(request), &reply);
|
||||
return err;
|
||||
}
|
||||
|
||||
static NWCCODE ncp_delete_file(NWCONN_HANDLE conn, const char *path)
|
||||
{
|
||||
size_t path_len = strlen(path);
|
||||
uint8_t request[1 + 1 + 1 + 255];
|
||||
NW_FRAGMENT reply;
|
||||
|
||||
if (path_len > 255)
|
||||
return NWE_INVALID_PATH;
|
||||
|
||||
request[0] = 0; /* directory handle 0, raw VOL:path */
|
||||
request[1] = 0; /* search attributes */
|
||||
request[2] = (uint8_t)path_len;
|
||||
memcpy(request + 3, path, path_len);
|
||||
|
||||
reply.fragAddr.rw = NULL;
|
||||
reply.fragSize = 0;
|
||||
|
||||
return NWRequestSimple(conn, NCP_DELETE_FILE,
|
||||
request, 3 + path_len, &reply);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
NWCONN_HANDLE conn;
|
||||
long init_err = 0;
|
||||
const char *path = NULL;
|
||||
uint32_t expect_delete = 0xffffffffU;
|
||||
uint32_t file_handle = 0;
|
||||
int create = 1;
|
||||
int delete = 1;
|
||||
int i;
|
||||
NWCCODE err;
|
||||
|
||||
if (NWCallsInit(NULL, NULL)) {
|
||||
fprintf(stderr, "NWCallsInit failed\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
conn = ncp_initialize(&argc, argv, 1, &init_err);
|
||||
if (!conn) {
|
||||
fprintf(stderr, "ncp_initialize/login failed: %ld\n", init_err);
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--expect-delete")) {
|
||||
if (++i >= argc || parse_u32(argv[i], &expect_delete) ||
|
||||
expect_delete > 255) {
|
||||
fprintf(stderr, "invalid --expect-delete value\n");
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
} else if (!strcmp(argv[i], "--create-only")) {
|
||||
create = 1;
|
||||
delete = 0;
|
||||
} else if (!strcmp(argv[i], "--delete-only")) {
|
||||
create = 0;
|
||||
delete = 1;
|
||||
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||
usage(argv[0]);
|
||||
ncp_close(conn);
|
||||
return 0;
|
||||
} else if (!path) {
|
||||
path = argv[i];
|
||||
} else {
|
||||
fprintf(stderr, "unexpected argument: %s\n", argv[i]);
|
||||
usage(argv[0]);
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
usage(argv[0]);
|
||||
ncp_close(conn);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (create) {
|
||||
err = ncp_create_file(conn, path, &file_handle);
|
||||
if (err) {
|
||||
fprintf(stderr, "NCP create failed: path=%s error=0x%04x\n",
|
||||
path, (unsigned int)err);
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
}
|
||||
err = ncp_close_file(conn, file_handle);
|
||||
if (err) {
|
||||
fprintf(stderr,
|
||||
"NCP close failed: path=%s handle=0x%08x error=0x%04x\n",
|
||||
path, (unsigned int)file_handle, (unsigned int)err);
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
}
|
||||
printf("NCP create path=%s handle=0x%08x verified\n",
|
||||
path, (unsigned int)file_handle);
|
||||
}
|
||||
|
||||
if (delete) {
|
||||
err = ncp_delete_file(conn, path);
|
||||
if (expect_delete != 0xffffffffU) {
|
||||
if (err != (NWCCODE)expect_delete) {
|
||||
fprintf(stderr,
|
||||
"NCP delete completion mismatch: path=%s got=0x%04x expected=0x%02x\n",
|
||||
path, (unsigned int)err, (unsigned int)expect_delete);
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
}
|
||||
printf("NCP delete returned expected completion 0x%02x: path=%s\n",
|
||||
(unsigned int)expect_delete, path);
|
||||
} else if (err) {
|
||||
fprintf(stderr, "NCP delete failed: path=%s error=0x%04x\n",
|
||||
path, (unsigned int)err);
|
||||
ncp_close(conn);
|
||||
return 1;
|
||||
} else {
|
||||
printf("NCP delete path=%s verified\n", path);
|
||||
}
|
||||
}
|
||||
|
||||
ncp_close(conn);
|
||||
return 0;
|
||||
}
|
||||
256
tests/salvage/salvage_ncp_delete_smoke.sh
Executable file
256
tests/salvage/salvage_ncp_delete_smoke.sh
Executable file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify that a file deleted through mars_nwe's normal NCP delete path is
|
||||
# captured into .recycle with a .salvage JSON sidecar.
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
SERVER="MARS"
|
||||
USER_NAME="SUPERVISOR"
|
||||
PASSWORD=""
|
||||
NETWARE_PATH="SYS:PUBLIC/SLVGCHK.TXT"
|
||||
UNIX_PATH="/var/mars_nwe/SYS/public/SLVGCHK.TXT"
|
||||
RECYCLE_REPOSITORY=".recycle"
|
||||
SALVAGE_REPOSITORY=".salvage"
|
||||
DELETED_BY=""
|
||||
OUT_FILE=""
|
||||
KEEP_GOING=1
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: $0 -S SERVER -U USER -P PASSWORD [options]
|
||||
|
||||
Options:
|
||||
--path NETWARE_PATH NetWare file path to create/delete (default: $NETWARE_PATH)
|
||||
--unix-path UNIX_PATH Unix path corresponding to --path (default: $UNIX_PATH)
|
||||
--deleted-by NAME Expected directory below .recycle/.salvage (default: USER)
|
||||
--recycle-name NAME Recycle repository name (default: $RECYCLE_REPOSITORY)
|
||||
--salvage-name NAME Salvage metadata repository name (default: $SALVAGE_REPOSITORY)
|
||||
--out FILE Write the complete report to FILE as well as stdout
|
||||
--stop-on-failure Stop after the first failing check
|
||||
-h, --help Show this help
|
||||
|
||||
The script uses tests/salvage/ncp_delete_smoke to create and delete the file
|
||||
through classic NCP requests. It never removes the live file through local Unix
|
||||
unlink/rm; local filesystem access is only used after the NCP delete to inspect
|
||||
.recycle and .salvage.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-S|--server)
|
||||
SERVER=$2; shift 2 ;;
|
||||
-U|--user)
|
||||
USER_NAME=$2; shift 2 ;;
|
||||
-P|--password)
|
||||
PASSWORD=$2; shift 2 ;;
|
||||
--path)
|
||||
NETWARE_PATH=$2; shift 2 ;;
|
||||
--unix-path)
|
||||
UNIX_PATH=$2; shift 2 ;;
|
||||
--deleted-by)
|
||||
DELETED_BY=$2; shift 2 ;;
|
||||
--recycle-name)
|
||||
RECYCLE_REPOSITORY=$2; shift 2 ;;
|
||||
--salvage-name)
|
||||
SALVAGE_REPOSITORY=$2; shift 2 ;;
|
||||
--out)
|
||||
OUT_FILE=$2; shift 2 ;;
|
||||
--stop-on-failure)
|
||||
KEEP_GOING=0; shift ;;
|
||||
-h|--help)
|
||||
usage; exit 0 ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$PASSWORD" ]; then
|
||||
echo "Missing password. Use -P PASSWORD." >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ -z "$DELETED_BY" ]; then
|
||||
DELETED_BY=$USER_NAME
|
||||
fi
|
||||
case "$NETWARE_PATH" in
|
||||
*:*) ;;
|
||||
*) echo "--path must include a NetWare volume prefix, e.g. SYS:PUBLIC/FILE" >&2; exit 2 ;;
|
||||
esac
|
||||
case "$UNIX_PATH" in
|
||||
/*) ;;
|
||||
*) echo "--unix-path must be absolute" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
REPORT_TMP=$(mktemp "${TMPDIR:-/tmp}/mars-salvage-ncp.XXXXXX")
|
||||
FAILURES=0
|
||||
|
||||
cleanup() {
|
||||
rm -f "$REPORT_TMP"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
emit() {
|
||||
printf '%s\n' "$*" | tee -a "$REPORT_TMP"
|
||||
}
|
||||
|
||||
section() {
|
||||
emit ""
|
||||
emit "## $*"
|
||||
}
|
||||
|
||||
finish_report() {
|
||||
section "Summary"
|
||||
emit "failures=$FAILURES"
|
||||
if [ -n "$OUT_FILE" ]; then
|
||||
cp "$REPORT_TMP" "$OUT_FILE"
|
||||
emit "report=$OUT_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
fail_check() {
|
||||
emit "$*"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
if [ "$KEEP_GOING" -eq 0 ]; then
|
||||
finish_report
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
local label=$1
|
||||
local printable=$2
|
||||
shift 2
|
||||
|
||||
section "$label"
|
||||
emit "\$ $printable"
|
||||
"$@" 2>&1 | tee -a "$REPORT_TMP"
|
||||
local status=${PIPESTATUS[0]}
|
||||
emit "[exit=$status]"
|
||||
if [ "$status" -ne 0 ]; then
|
||||
fail_check "$label failed"
|
||||
fi
|
||||
}
|
||||
|
||||
count_path_components() {
|
||||
local path=$1
|
||||
local old_ifs=$IFS
|
||||
local count=0
|
||||
local part
|
||||
|
||||
path=${path#*:}
|
||||
path=${path#/}
|
||||
path=${path#\\}
|
||||
path=${path//\\//}
|
||||
IFS=/
|
||||
for part in $path; do
|
||||
[ -n "$part" ] && count=$((count + 1))
|
||||
done
|
||||
IFS=$old_ifs
|
||||
printf '%s\n' "$count"
|
||||
}
|
||||
|
||||
compute_unix_volume_root() {
|
||||
local root
|
||||
local components
|
||||
|
||||
root=$(dirname -- "$UNIX_PATH")
|
||||
components=$(count_path_components "$NETWARE_PATH") || return 1
|
||||
while [ "$components" -gt 1 ]; do
|
||||
root=$(dirname -- "$root")
|
||||
components=$((components - 1))
|
||||
done
|
||||
printf '%s\n' "$root"
|
||||
}
|
||||
|
||||
relative_to_volume_root() {
|
||||
local path=$1
|
||||
local root=$2
|
||||
|
||||
case "$path" in
|
||||
"$root"/*) printf '%s\n' "${path#$root/}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_file() {
|
||||
local label=$1
|
||||
local path=$2
|
||||
|
||||
section "$label"
|
||||
emit "path=$path"
|
||||
if [ ! -f "$path" ]; then
|
||||
fail_check "missing file: $path"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_metadata() {
|
||||
local path=$1
|
||||
|
||||
section "salvage metadata JSON"
|
||||
emit "\$ cat '$path'"
|
||||
cat "$path" 2>&1 | tee -a "$REPORT_TMP"
|
||||
local status=${PIPESTATUS[0]}
|
||||
emit "[exit=$status]"
|
||||
if [ "$status" -ne 0 ]; then
|
||||
fail_check "could not cat metadata: $path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
grep -q '"source"[[:space:]]*:[[:space:]]*"mars_nwe"' "$path" || fail_check "metadata missing source=mars_nwe"
|
||||
grep -q '"original_path"' "$path" || fail_check "metadata missing original_path"
|
||||
grep -q '"recycle_relative_path"' "$path" || fail_check "metadata missing recycle_relative_path"
|
||||
grep -q '"salvage_relative_path"' "$path" || fail_check "metadata missing salvage_relative_path"
|
||||
grep -q '"trustees"' "$path" || fail_check "metadata missing trustees"
|
||||
}
|
||||
|
||||
section "mars_nwe salvage NCP delete smoke"
|
||||
emit "date=$(date -Is)"
|
||||
emit "server=$SERVER"
|
||||
emit "user=$USER_NAME"
|
||||
emit "path=$NETWARE_PATH"
|
||||
emit "unix_path=$UNIX_PATH"
|
||||
emit "deleted_by=$DELETED_BY"
|
||||
emit "recycle_repository=$RECYCLE_REPOSITORY"
|
||||
emit "salvage_repository=$SALVAGE_REPOSITORY"
|
||||
|
||||
run_cmd \
|
||||
"NCP create/delete" \
|
||||
"./ncp_delete_smoke -S '$SERVER' -U '$USER_NAME' -P ****** '$NETWARE_PATH'" \
|
||||
"$SCRIPT_DIR/ncp_delete_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" "$NETWARE_PATH"
|
||||
|
||||
VOLUME_ROOT=$(compute_unix_volume_root) || {
|
||||
fail_check "could not compute Unix volume root from $NETWARE_PATH and $UNIX_PATH"
|
||||
VOLUME_ROOT=""
|
||||
}
|
||||
RELATIVE_PATH=""
|
||||
if [ -n "$VOLUME_ROOT" ]; then
|
||||
RELATIVE_PATH=$(relative_to_volume_root "$UNIX_PATH" "$VOLUME_ROOT") || {
|
||||
fail_check "could not derive relative path: unix_path=$UNIX_PATH volume_root=$VOLUME_ROOT"
|
||||
RELATIVE_PATH=""
|
||||
}
|
||||
fi
|
||||
|
||||
if [ -n "$RELATIVE_PATH" ]; then
|
||||
RECYCLE_PATH="$VOLUME_ROOT/$RECYCLE_REPOSITORY/$DELETED_BY/$RELATIVE_PATH"
|
||||
METADATA_PATH="$VOLUME_ROOT/$SALVAGE_REPOSITORY/$DELETED_BY/$RELATIVE_PATH.json"
|
||||
|
||||
emit "volume_root=$VOLUME_ROOT"
|
||||
emit "relative_path=$RELATIVE_PATH"
|
||||
emit "recycle_path=$RECYCLE_PATH"
|
||||
emit "metadata_path=$METADATA_PATH"
|
||||
|
||||
check_file "recycle payload" "$RECYCLE_PATH"
|
||||
check_file "salvage metadata sidecar" "$METADATA_PATH"
|
||||
if [ -f "$METADATA_PATH" ]; then
|
||||
check_metadata "$METADATA_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
finish_report
|
||||
[ "$FAILURES" -eq 0 ]
|
||||
Reference in New Issue
Block a user