mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 19:01:57 -04:00
Improve directory scan filesystem boundary detection using statx() for mount and subvolume IDs
This commit is contained in:
parent
0416366e2d
commit
78e87549ca
8 changed files with 184 additions and 10 deletions
|
|
@ -11,6 +11,8 @@
|
|||
*/
|
||||
#include "tommyds/tommylist.h"
|
||||
|
||||
#define STATIC_ASSERT(name, cond) typedef char static_assert_ ## name[(cond) ? 1 : -1]
|
||||
|
||||
#define EINTERNAL EFAULT /**< Internal assertion failed. */
|
||||
#define EDATA EIO /**< Silent data corruption. */
|
||||
#define ESOFT EINVAL /**< Software error, like permission denied. */
|
||||
|
|
@ -45,6 +47,27 @@ static inline int hardlink(const char* a, const char* b)
|
|||
*/
|
||||
int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t size);
|
||||
|
||||
/**
|
||||
* Filesystem identity for directory boundary checks.
|
||||
*/
|
||||
struct fsidentity {
|
||||
uint64_t device; /**< Device identifier (st_dev). */
|
||||
uint64_t mnt_id; /**< Mount identifier (stx_mnt_id or 0). */
|
||||
uint64_t subvol; /**< Subvolume identifier (stx_subvol or 0). */
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the filesystem identity of a path.
|
||||
*
|
||||
* The caller already provides the normal stat information.
|
||||
* This function must NOT repeat lstat()/stat() for normal metadata.
|
||||
*
|
||||
* mnt_id and subvol are set to zero when unavailable.
|
||||
*
|
||||
* Return 0 on success, -1 on error.
|
||||
*/
|
||||
int fsidentity(const char* path, const struct stat* st, struct fsidentity* identity);
|
||||
|
||||
/**
|
||||
* Check if the underline file-system support persistent inodes.
|
||||
* Return -1 on error, 0 on success.
|
||||
|
|
|
|||
|
|
@ -870,7 +870,6 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev
|
|||
disk->fstype[0] = 0;
|
||||
disk->fslabel[0] = 0;
|
||||
disk->mount_device = dev;
|
||||
disk->dir_device = dev;
|
||||
disk->tick = 0;
|
||||
disk->cached_blocks = 0;
|
||||
disk->progress_file = 0;
|
||||
|
|
|
|||
|
|
@ -402,7 +402,6 @@ struct snapraid_disk {
|
|||
char fslabel[FSINFO_MAX]; /**< Filesystem label */
|
||||
|
||||
uint64_t mount_device; /**< Device identifier of the mount_point. */
|
||||
uint64_t dir_device; /**< Device identifier of the effective dir. */
|
||||
block_off_t total_blocks; /**< Number of total blocks. */
|
||||
block_off_t free_blocks; /**< Number of free blocks at the last sync. */
|
||||
|
||||
|
|
|
|||
|
|
@ -170,6 +170,20 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
|
|||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* identity */
|
||||
|
||||
int fsidentity(const char* path, const struct stat* st, struct fsidentity* identity)
|
||||
{
|
||||
(void)path;
|
||||
|
||||
identity->device = st->st_dev;
|
||||
identity->mnt_id = 0;
|
||||
identity->subvol = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* snapshot */
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ struct snapraid_scan {
|
|||
tommy_list link_insert_list; /**< Links to insert. */
|
||||
tommy_list dir_insert_list; /**< Dirs to insert. */
|
||||
tommy_list local_filter_list; /**< Filter list specific for the disk. */
|
||||
struct fsidentity dir_identity; /**< Effective scan root identity. */
|
||||
|
||||
/* nodes for data structures */
|
||||
tommy_node node;
|
||||
|
|
@ -64,6 +65,9 @@ static struct snapraid_scan* scan_alloc(struct snapraid_state* state, struct sna
|
|||
tommy_list_init(&scan->link_insert_list);
|
||||
tommy_list_init(&scan->dir_insert_list);
|
||||
tommy_list_init(&scan->local_filter_list);
|
||||
scan->dir_identity.device = 0;
|
||||
scan->dir_identity.mnt_id = 0;
|
||||
scan->dir_identity.subvol = 0;
|
||||
scan->is_diff = is_diff;
|
||||
scan->need_write = 0;
|
||||
|
||||
|
|
@ -1611,13 +1615,31 @@ static int scan_sub(struct snapraid_scan* scan, int level, int is_diff, char* pa
|
|||
if (!st)
|
||||
st = DSTAT(path_next, dd, &st_buf);
|
||||
|
||||
struct fsidentity identity;
|
||||
if (fsidentity(path_next, st, &identity) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
/* restore removing additions */
|
||||
path_next[path_len] = 0;
|
||||
sub_next[sub_len] = 0;
|
||||
log_tag("%s:%u:%s:%s: Statx error. %s.\n", es(errno), 0, disk->name, esc_tag(path_next), strerror(errno));
|
||||
log_fatal(errno, "Error accessing directory '%s'. %s.\n", path_next, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/*
|
||||
* In Unix don't follow mount points in different devices
|
||||
* In Unix don't follow mount points in different devices or subvolumes
|
||||
* in Windows we are already skipping them reporting them as special files
|
||||
*/
|
||||
if ((uint64_t)st->st_dev != disk->dir_device) {
|
||||
if (identity.device != scan->dir_identity.device) {
|
||||
log_tag("%s:%u:%s:%s: Ignoring mount point.\n", es(ESOFT), 0, disk->name, esc_tag(path_next));
|
||||
log_error(ESOFT, "WARNING! Ignoring mount point '%s' because it appears to be in a different device\n", path_next);
|
||||
} else if (identity.mnt_id != scan->dir_identity.mnt_id) {
|
||||
log_tag("%s:%u:%s:%s: Ignoring mount point.\n", es(ESOFT), 0, disk->name, esc_tag(path_next));
|
||||
log_error(ESOFT, "WARNING! Ignoring mount point '%s' because it belongs to a different mount\n", path_next);
|
||||
} else if (identity.subvol != scan->dir_identity.subvol) {
|
||||
log_tag("%s:%u:%s:%s: Ignoring nested subvolume.\n", es(ESOFT), 0, disk->name, esc_tag(path_next));
|
||||
log_error(ESOFT, "WARNING! Ignoring nested subvolume '%s' because it belongs to a different subvolume\n", path_next);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
|
|
@ -1727,6 +1749,23 @@ static void* scan_disk(void* arg)
|
|||
|
||||
start = os_tick_ms();
|
||||
|
||||
struct stat st;
|
||||
if (lstat(disk->dir, &st) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("%s:%u:%s:%s: Stat error. %s.\n", es(errno), 0, disk->name, esc_tag(disk->dir), strerror(errno));
|
||||
log_fatal(errno, "Error accessing directory '%s'. %s.\n", disk->dir, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (fsidentity(disk->dir, &st, &scan->dir_identity) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("%s:%u:%s:%s: Statx error. %s.\n", es(errno), 0, disk->name, esc_tag(disk->dir), strerror(errno));
|
||||
log_fatal(errno, "Error accessing directory '%s'. %s.\n", disk->dir, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
scan_dir(scan, 0, scan->is_diff, disk->dir, "");
|
||||
|
||||
if (!scan->is_diff)
|
||||
|
|
|
|||
|
|
@ -278,7 +278,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t
|
|||
continue;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (disk->dir_device == 0) {
|
||||
if (disk->mount_device == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal(ESOFT, "Disk '%s' has a zero serial number.\n", disk->dir);
|
||||
log_fatal(ESOFT, "This is not necessarily wrong, but for using SnapRAID\n");
|
||||
|
|
@ -304,7 +304,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t
|
|||
/* LCOV_EXCL_START */
|
||||
log_fatal(ESOFT, "Disks '%s' and '%s' are on the same device.\n", disk->mount_point, other->mount_point);
|
||||
#ifdef _WIN32
|
||||
log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device);
|
||||
log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->mount_device);
|
||||
log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
|
||||
log_fatal(ESOFT, "to change one of the disk serial.\n");
|
||||
#endif
|
||||
|
|
@ -336,7 +336,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t
|
|||
/* LCOV_EXCL_START */
|
||||
log_fatal(ESOFT, "Disk '%s' and %s '%s' are on the same device.\n", disk->mount_point, lev_name(l), state->parity[l].split_map[s].path);
|
||||
#ifdef _WIN32
|
||||
log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device);
|
||||
log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->mount_device);
|
||||
log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
|
||||
log_fatal(ESOFT, "to change one of the disk serial.\n");
|
||||
#endif
|
||||
|
|
@ -352,7 +352,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t
|
|||
/* LCOV_EXCL_START */
|
||||
log_fatal(ESOFT, "Disk '%s' and pool '%s' are on the same device.\n", disk->mount_point, state->pool);
|
||||
#ifdef _WIN32
|
||||
log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device);
|
||||
log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->mount_device);
|
||||
log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n");
|
||||
log_fatal(ESOFT, "to change one of the disk serial.\n");
|
||||
#endif
|
||||
|
|
@ -6041,7 +6041,6 @@ static int state_snapshot_dir(struct fssnapshot_struct* fss, const char* name, s
|
|||
return -1;
|
||||
|
||||
pathcpy(disk->dir, sizeof(disk->dir), vol);
|
||||
disk->dir_device = st.st_dev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -2138,6 +2138,107 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
|
|||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* identity */
|
||||
|
||||
#if HAVE_STATX
|
||||
|
||||
#ifndef STATX_MNT_ID
|
||||
#define STATX_MNT_ID 0x00001000U
|
||||
#endif
|
||||
#ifndef STATX_SUBVOL
|
||||
#define STATX_SUBVOL 0x00008000U
|
||||
#endif
|
||||
|
||||
struct snapraid_statx_timestamp_abi {
|
||||
int64_t tv_sec;
|
||||
uint32_t tv_nsec;
|
||||
int32_t reserved;
|
||||
};
|
||||
|
||||
struct snapraid_statx_abi {
|
||||
/* 0x00 */
|
||||
uint32_t stx_mask;
|
||||
uint32_t stx_blksize;
|
||||
uint64_t stx_attributes;
|
||||
|
||||
/* 0x10 */
|
||||
uint32_t stx_nlink;
|
||||
uint32_t stx_uid;
|
||||
uint32_t stx_gid;
|
||||
uint16_t stx_mode;
|
||||
uint16_t spare0;
|
||||
|
||||
/* 0x20 */
|
||||
uint64_t stx_ino;
|
||||
uint64_t stx_size;
|
||||
uint64_t stx_blocks;
|
||||
uint64_t stx_attributes_mask;
|
||||
|
||||
/* 0x40 */
|
||||
struct snapraid_statx_timestamp_abi stx_atime;
|
||||
struct snapraid_statx_timestamp_abi stx_btime;
|
||||
struct snapraid_statx_timestamp_abi stx_ctime;
|
||||
struct snapraid_statx_timestamp_abi stx_mtime;
|
||||
|
||||
/* 0x80 */
|
||||
uint32_t stx_rdev_major;
|
||||
uint32_t stx_rdev_minor;
|
||||
uint32_t stx_dev_major;
|
||||
uint32_t stx_dev_minor;
|
||||
|
||||
/* 0x90 */
|
||||
uint64_t stx_mnt_id;
|
||||
uint32_t stx_dio_mem_align;
|
||||
uint32_t stx_dio_offset_align;
|
||||
|
||||
/* 0xa0 */
|
||||
uint64_t stx_subvol;
|
||||
|
||||
/* ABI continues to 0x100. */
|
||||
unsigned char tail[0x100 - 0xa8];
|
||||
};
|
||||
|
||||
STATIC_ASSERT(statx_abi_size, sizeof(struct snapraid_statx_abi) == 0x100);
|
||||
STATIC_ASSERT(statx_abi_mnt_id, offsetof(struct snapraid_statx_abi, stx_mnt_id) == 0x90);
|
||||
STATIC_ASSERT(statx_abi_subvol, offsetof(struct snapraid_statx_abi, stx_subvol) == 0xa0);
|
||||
#endif
|
||||
|
||||
int fsidentity(const char* path, const struct stat* st, struct fsidentity* identity)
|
||||
{
|
||||
(void)path;
|
||||
|
||||
identity->device = st->st_dev;
|
||||
identity->mnt_id = 0;
|
||||
identity->subvol = 0;
|
||||
|
||||
#if HAVE_STATX
|
||||
struct statx stx;
|
||||
|
||||
/* query mount ID and subvolume ID without following symlinks */
|
||||
if (statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_MNT_ID | STATX_SUBVOL, &stx) != 0) {
|
||||
/*
|
||||
* Fall back if statx is unsupported by the kernel (ENOSYS) or blocked
|
||||
* by a seccomp/security sandbox filter (EPERM/EACCES in older Docker/OCI).
|
||||
*/
|
||||
if (errno == ENOSYS || errno == EPERM || errno == EACCES)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* copy to fixed ABI representation to support libc headers lacking named fields */
|
||||
struct snapraid_statx_abi abi;
|
||||
memcpy(&abi, &stx, sizeof(abi));
|
||||
|
||||
if ((abi.stx_mask & STATX_MNT_ID) != 0)
|
||||
identity->mnt_id = abi.stx_mnt_id;
|
||||
if ((abi.stx_mask & STATX_SUBVOL) != 0)
|
||||
identity->subvol = abi.stx_subvol;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* snapshot */
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ AC_CHECK_FUNCS([ftruncate fallocate access])
|
|||
AC_CHECK_FUNCS([fsync posix_fadvise sync_file_range syncfs])
|
||||
AC_CHECK_FUNCS([getc_unlocked ferror_unlocked])
|
||||
AC_CHECK_FUNCS([futimes futimens futimesat localtime_r lutimes utimensat])
|
||||
AC_CHECK_FUNCS([fstatat flock renameat])
|
||||
AC_CHECK_FUNCS([fstatat flock renameat statx])
|
||||
AC_CHECK_FUNCS([mach_absolute_time])
|
||||
AC_CHECK_FUNCS([backtrace backtrace_symbols])
|
||||
AC_SEARCH_LIBS([clock_gettime], [rt])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue