From 28f54ddf3b2ccb2e02f8bfadfaf9874a37010ef4 Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Thu, 9 Mar 2023 22:51:09 +0400 Subject: [PATCH] sh: support reflinking directories - modify rm_util_link_type() to specifically detect directory pairs - modify sh output generator to emit cp_reflink for directory pairs - modify sh template to support reflinking directories and extend stampfile code to preserve attributes recursively Fixes #618. --- lib/formats/sh.c.in | 2 ++ lib/formats/sh.sh | 21 +++++++++++++++------ lib/reflink.c | 4 +++- lib/utilities.c | 26 ++++++++++++++++++++++---- lib/utilities.h | 1 + 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/lib/formats/sh.c.in b/lib/formats/sh.c.in index dd4c42b8..a1783f4f 100644 --- a/lib/formats/sh.c.in +++ b/lib/formats/sh.c.in @@ -106,6 +106,7 @@ static bool rm_sh_emit_handler_clone(RmFmtHandlerShScript *self, char **out, RmF case RM_LINK_XDEV: case RM_LINK_SYMLINK: case RM_LINK_BOTH_EMPTY: + case RM_LINK_DIR: rm_log_warning_line("Unexpected return code %d from rm_util_link_type()", link_type); return FALSE; case RM_LINK_HARDLINK: @@ -144,6 +145,7 @@ static bool rm_sh_emit_handler_reflink(RmFmtHandlerShScript *self, char **out, R case RM_LINK_BOTH_EMPTY: rm_log_warning_line("Unexpected return code %d from rm_util_link_type()", link_type); return FALSE; + case RM_LINK_DIR: case RM_LINK_HARDLINK: case RM_LINK_SYMLINK: case RM_LINK_NONE: diff --git a/lib/formats/sh.sh b/lib/formats/sh.sh index c654c8f6..2f2d77ca 100644 --- a/lib/formats/sh.sh +++ b/lib/formats/sh.sh @@ -225,25 +225,34 @@ cp_hardlink() { } cp_reflink() { - if [ -d "$1" ]; then - # for duplicate dir's, can't clone so use symlink - cp_symlink "$@" - return $? - fi print_progress_prefix # reflink $1 to $2's data, preserving $1's mtime printf "${COL_YELLOW}Reflinking to original: ${COL_RESET}%%s\n" "$1" if original_check "$1" "$2"; then if [ -z "$DO_DRY_RUN" ]; then - if [ -z "$STAMPFILE2" ]; then + if [ -d "$1" ]; then + local STAMPFILE2="$(mktemp -d "${TMPDIR:-/tmp}/rmlint.XXXXXXXX.stamp.d")" + elif [ -z "$STAMPFILE2" ]; then STAMPFILE2=$(mktemp "${TMPDIR:-/tmp}/rmlint.XXXXXXXX.stamp") fi cp --archive --attributes-only --no-target-directory -- "$1" "$STAMPFILE2" if [ -d "$1" ]; then + # to reflink a directory, we will have to delete it, thus changing parent mtime + # take care of preserving parent mtime if requested + if [ -n "$DO_KEEP_DIR_TIMESTAMPS" ]; then + touch -r "$(dirname "$1")" -- "$STAMPFILE" + fi rm -rf -- "$1" fi cp --archive --reflink=always -- "$2" "$1" cp --archive --attributes-only --no-target-directory -- "$STAMPFILE2" "$1" + if [ -d "$1" ]; then + rm -rf -- "$STAMPFILE2" + if [ -n "$DO_KEEP_DIR_TIMESTAMPS" ]; then + # restore parent mtime if we saved it + touch -r "$STAMPFILE" -- "$(dirname "$1")" + fi + fi fi fi } diff --git a/lib/reflink.c b/lib/reflink.c index 8eaf9c9e..723caf64 100644 --- a/lib/reflink.c +++ b/lib/reflink.c @@ -468,6 +468,7 @@ int rm_is_reflink_main(int argc, const char **argv) { " %i: %s\n" " %i: %s\n" " %i: %s\n" + " %i: %s\n" " %i: %s\n", _("Test if two files are reflinks (share same data extents)"), _("Returns 0 if the files are reflinks."), @@ -481,7 +482,8 @@ int rm_is_reflink_main(int argc, const char **argv) { RM_LINK_HARDLINK, desc[RM_LINK_HARDLINK], RM_LINK_SYMLINK, desc[RM_LINK_SYMLINK], RM_LINK_XDEV, desc[RM_LINK_XDEV], - RM_LINK_NONE, desc[RM_LINK_NONE]); + RM_LINK_NONE, desc[RM_LINK_NONE], + RM_LINK_DIR, desc[RM_LINK_DIR]); g_option_context_set_summary(context, summary); diff --git a/lib/utilities.c b/lib/utilities.c index 9743f611..8fbf363b 100644 --- a/lib/utilities.c +++ b/lib/utilities.c @@ -1384,7 +1384,7 @@ RmLinkType rm_util_link_type(const char *path1, const char *path2, bool use_fiem RM_RETURN(RM_LINK_ERROR); } - if(!S_ISREG(stat1.st_mode)) { + if(!S_ISREG(stat1.st_mode) && !S_ISDIR(stat1.st_mode)) { RM_RETURN(S_ISLNK(stat1.st_mode) ? RM_LINK_SYMLINK : RM_LINK_NOT_FILE); } @@ -1409,11 +1409,23 @@ RmLinkType rm_util_link_type(const char *path1, const char *path2, bool use_fiem RM_RETURN(RM_LINK_ERROR); } - if(!S_ISREG(stat2.st_mode)) { + if(!S_ISREG(stat2.st_mode) && !S_ISDIR(stat2.st_mode)) { RM_RETURN(S_ISLNK(stat2.st_mode) ? RM_LINK_SYMLINK : RM_LINK_NOT_FILE); } - if(stat1.st_size != stat2.st_size) { + /* At this point, path1 or path2 may be a regular file or a directory. + * Ensure they both have the same type, otherwise fail. */ + bool is_dir; + if(S_ISDIR(stat1.st_mode) && S_ISDIR(stat2.st_mode)) { + is_dir = true; + } else if (S_ISREG(stat1.st_mode) && S_ISREG(stat2.st_mode)) { + is_dir = false; + } else { + RM_RETURN(RM_LINK_NOT_FILE); + } + + if(!is_dir && stat1.st_size != stat2.st_size) { + /* st_size is not defined for directories */ #if _RM_OFFSET_DEBUG rm_log_debug_line( "rm_util_link_type: Files have different sizes: %" G_GUINT64_FORMAT @@ -1446,6 +1458,11 @@ RmLinkType rm_util_link_type(const char *path1, const char *path2, bool use_fiem } } + if (is_dir) { + /* further tests do not make sense for directories */ + RM_RETURN(RM_LINK_DIR); + } + if(use_fiemap) { RmLinkType reflink_type = rm_reflink_type_from_fd(fd1, fd2); RM_RETURN(reflink_type); @@ -1468,7 +1485,8 @@ const char **rm_link_type_to_desc() { N_("Encountered a symlink"), N_("Files are on different devices"), N_("Not linked"), - N_("Both files are empty")}; + N_("Both files are empty"), + N_("Both files are directories")}; return RM_LINK_TYPE_TO_DESC; } diff --git a/lib/utilities.h b/lib/utilities.h index 2a548094..abdf784e 100644 --- a/lib/utilities.h +++ b/lib/utilities.h @@ -52,6 +52,7 @@ typedef enum RmLinkType { RM_LINK_XDEV = 10, RM_LINK_NONE = 11, RM_LINK_BOTH_EMPTY = 12, + RM_LINK_DIR = 13, } RmLinkType; #if HAVE_STAT64 && !RM_IS_APPLE