diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 6e8fc800..96c3ff89 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -2445,7 +2445,20 @@ static int windows_rebuild_link(const struct fssnapshot_struct* fss, const char* return 0; } -int fssnapshot(const char* dir, struct fssnapshot_struct* fss) +static int windows_delete_link(const struct fssnapshot_struct* fss, const char* name) +{ + char link_path[PATH_MAX]; + + pathcpy(link_path, sizeof(link_path), fss->snapshot_dir); + pathcat(link_path, sizeof(link_path), name); + + /* remove link if present */ + windows_rmdir(link_path); + + return 0; +} + +int fssnapshot_mount(const char* dir, struct fssnapshot_struct* fss) { wchar_t conv_buf_vol[CONV_MAX]; char conv_buf_root[CONV_MAX]; @@ -2537,6 +2550,7 @@ int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat pathcpy(link_path, sizeof(link_path), fss->snapshot_dir); pathcat(link_path, sizeof(link_path), name); + pathcat(link_path, sizeof(link_path), SNAPSHOT_GUID); /* we check the GUID as it's the one that matter */ /* use lstat because we want to check the link */ if (windows_lstat(link_path, st) != 0) @@ -2600,6 +2614,9 @@ int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name) pathcpy(link_path, sizeof(link_path), fss->snapshot_dir); pathcat(link_path, sizeof(link_path), name); + /* remove potential stale link */ + windows_rmdir(link_path); + int ret = windows_symlink_directory(target_path, link_path); if (ret != 0) { log_error(errno, "Error creating symlink '%s'. %s.\n", link_path, strerror(errno)); @@ -2629,7 +2646,6 @@ bail_and_delete: int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name) { - char link_path[PATH_MAX]; char guid[PATH_MAX]; char cmd[PS_CMD_MAX]; char guid_link[PATH_MAX]; @@ -2638,16 +2654,13 @@ int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name) pathcat(guid_link, sizeof(guid_link), name); pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID); - pathcpy(link_path, sizeof(link_path), fss->snapshot_dir); - pathcat(link_path, sizeof(link_path), name); - if (windows_read_guid(guid_link, guid, sizeof(guid)) != 0) return -1; /* if the GUID link is gone, assume everything is gone */ if (guid[0] == 0) { /* remove as not valid anymore */ - windows_rmdir(link_path); + windows_delete_link(fss, name); return 0; } @@ -2657,12 +2670,12 @@ int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name) if (ret != 0) { log_error(errno, "Error destroy the snapshot from GUID '%s'. %s.\n", guid, strerror(errno)); /* remove as not valid anymore */ - windows_rmdir(link_path); + windows_delete_link(fss, name); return -1; } /* remove live symlink if present */ - windows_rmdir(link_path); + windows_delete_link(fss, name); /* remove persistent GUID symlink */ if (windows_rmdir(guid_link) != 0) { @@ -2675,16 +2688,9 @@ int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name) int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name) { - char old_link[PATH_MAX]; - char new_link[PATH_MAX]; char old_guid[PATH_MAX]; char new_guid[PATH_MAX]; - pathcpy(old_link, sizeof(old_link), fss->snapshot_dir); - pathcat(old_link, sizeof(old_link), old_name); - pathcpy(new_link, sizeof(new_link), fss->snapshot_dir); - pathcat(new_link, sizeof(new_link), new_name); - pathcpy(old_guid, sizeof(old_guid), fss->snapshot_dir); pathcat(old_guid, sizeof(old_guid), old_name); pathcat(old_guid, sizeof(old_guid), SNAPSHOT_GUID); @@ -2692,21 +2698,33 @@ int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, pathcat(new_guid, sizeof(new_guid), new_name); pathcat(new_guid, sizeof(new_guid), SNAPSHOT_GUID); - if (windows_rename(old_link, new_link) != 0) { - log_error(errno, "Error renaming '%s' to '%s'. %s.\n", old_link, new_link, strerror(errno)); - return -1; - } - if (windows_rename(old_guid, new_guid) != 0) { log_error(errno, "Error renaming '%s' to '%s'. %s.\n", old_guid, new_guid, strerror(errno)); - /* try to restore */ - windows_rename(new_link, old_link); return -1; } + /* the rename is the latest operation, and then we don't care about the links, we just remove both */ + windows_delete_link(fss, old_name); + windows_delete_link(fss, new_name); + return 0; } +void fssnapshot_unmount(const struct fssnapshot_struct* fss) +{ + /* + * Remove symlinks on exit to prevent "Dangling Links." + * Since \Device\HarddiskVolumeShadowCopyN paths are reassigned by the kernel + * at boot, a stale link would either point to a non-existent device or, + * worse, a different disk's snapshot. + * + * Deleting the link ensures system hygiene and prevents users from seeing + * "Location not available" errors or incorrect data after a reboot. + */ + windows_delete_link(fss, SNAPSHOT_PENDING); + windows_delete_link(fss, SNAPSHOT_STABLE); +} + uint64_t os_tick(void) { LARGE_INTEGER t; diff --git a/cmdline/portable.h b/cmdline/portable.h index a1ba71b0..13e941a4 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -426,7 +426,16 @@ struct fssnapshot_struct { * @param fss Output snapshot context * @return 0 on success, -1 on failure */ -int fssnapshot(const char* dir, struct fssnapshot_struct* fss); +int fssnapshot_mount(const char* dir, struct fssnapshot_struct* fss); + +/** + * Unmount the snapshots. + * + * Required in Windows to remove the temporary symlinks. + * + * @param fss Snapshot context + */ +void fssnapshot_unmount(const struct fssnapshot_struct* fss); /** * Stat the snapshot directory. diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index f0522f7d..6074edfa 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -1856,8 +1856,10 @@ int snapraid_main(int argc, char* argv[]) ret = state_sync(&state, blockstart, blockcount); /* commit the snapshot as stable */ - if (ret == 0) + if (ret == 0) { state_snapshot_commit(&state); + state_snapshot_cleanup(&state); + } } else if (operation == OPERATION_DRY) { state_read(&state); @@ -1899,6 +1901,10 @@ int snapraid_main(int argc, char* argv[]) state_snapshot_read(&state); ret = state_scrub(&state, plan100, olderthan); + + if (ret == 0) { + state_snapshot_cleanup(&state); + } } else if (operation == OPERATION_REWRITE) { state_read(&state); @@ -2023,6 +2029,10 @@ int snapraid_main(int argc, char* argv[]) if (opt.gui_rescan_after) state_scan(&state); } + + if (ret == 0) { + state_snapshot_cleanup(&state); + } } else { /* LCOV_EXCL_START */ log_fatal(errno, "Unexpected command '%s'\n", command); diff --git a/cmdline/state.c b/cmdline/state.c index 3faea59d..0ec203c8 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -5834,7 +5834,7 @@ int state_snapshot_new(struct snapraid_state* state) struct snapraid_disk* disk = i->data; /* check if it supports snapshot */ - if (fssnapshot(disk->mount_point, &disk->fss) != 0) + if (fssnapshot_mount(disk->mount_point, &disk->fss) != 0) continue; size_t root_len = strlen(disk->fss.root_dir); @@ -5906,7 +5906,7 @@ void state_snapshot_read(struct snapraid_state* state) struct snapraid_disk* disk = i->data; /* check if it supports snapshot */ - if (fssnapshot(disk->mount_point, &disk->fss) != 0) + if (fssnapshot_mount(disk->mount_point, &disk->fss) != 0) continue; size_t root_len = strlen(disk->fss.root_dir); @@ -5939,7 +5939,7 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d struct snapraid_disk* disk = i->data; /* check if it supports snapshot */ - if (fssnapshot(disk->mount_point, &disk->fss) != 0) + if (fssnapshot_mount(disk->mount_point, &disk->fss) != 0) continue; size_t root_len = strlen(disk->fss.root_dir); @@ -5979,3 +5979,16 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d } } +void state_snapshot_cleanup(struct snapraid_state* state) +{ + for (tommy_node* i = state->disklist; i != 0; i = i->next) { + struct snapraid_disk* disk = i->data; + + /* check if it supports snapshot */ + if (disk->fss.magic == 0) + continue; + + fssnapshot_unmount(&disk->fss); + } +} + diff --git a/cmdline/state.h b/cmdline/state.h index 20592cac..4030d7bb 100644 --- a/cmdline/state.h +++ b/cmdline/state.h @@ -445,6 +445,13 @@ void state_snapshot_read(struct snapraid_state* state); */ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_disk); +/** + * Cleanup snapshots + * + * Required only in Windows. + */ +void state_snapshot_cleanup(struct snapraid_state* state); + /****************************************************************************/ /* misc */ diff --git a/cmdline/unix.c b/cmdline/unix.c index 96f4766d..ebf69ee1 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -2558,7 +2558,7 @@ static int fssnapshot_zfs(const char* dir, uint32_t magic, struct fssnapshot_str } #endif -int fssnapshot(const char* path, struct fssnapshot_struct* fss) +int fssnapshot_mount(const char* path, struct fssnapshot_struct* fss) { #if HAVE_LINUX_DEVICE struct statfs sfs; @@ -3069,6 +3069,11 @@ int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, #endif } +void fssnapshot_unmount(const struct fssnapshot_struct* fss) +{ + (void)fss; +} + uint64_t os_tick(void) { #if HAVE_MACH_ABSOLUTE_TIME