From fc16423cea0e837b0ca4a9d0a66cf7c8ae585989 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 21cd0170..ea07e2c0 100644 --- a/lib/formats/sh.c.in +++ b/lib/formats/sh.c.in @@ -103,6 +103,7 @@ static bool rm_sh_emit_handler_clone(RmFmtHandlerShScript *self, char **out, RmF case RM_LINK_ERROR: case RM_LINK_XDEV: case RM_LINK_SYMLINK: + 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: @@ -137,6 +138,7 @@ static bool rm_sh_emit_handler_reflink(RmFmtHandlerShScript *self, char **out, R case RM_LINK_ERROR: 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_INLINE_EXTENTS: diff --git a/lib/formats/sh.sh b/lib/formats/sh.sh index 893588c4..0453b185 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 920497ed..bbff8ed3 100644 --- a/lib/reflink.c +++ b/lib/reflink.c @@ -473,6 +473,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."), @@ -486,7 +487,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 aae0ec0f..4ae21321 100644 --- a/lib/utilities.c +++ b/lib/utilities.c @@ -1361,7 +1361,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); } @@ -1386,11 +1386,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 @@ -1419,6 +1431,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, stat1.st_size); RM_RETURN(reflink_type); @@ -1440,7 +1457,8 @@ const char **rm_link_type_to_desc() { N_("Hardlink"), N_("Encountered a symlink"), N_("Files are on different devices"), - N_("Not linked")}; + N_("Not linked"), + N_("Both files are directories")}; return RM_LINK_TYPE_TO_DESC; } diff --git a/lib/utilities.h b/lib/utilities.h index 9ad8da08..b95b074e 100644 --- a/lib/utilities.h +++ b/lib/utilities.h @@ -49,6 +49,7 @@ typedef enum RmLinkType { RM_LINK_SYMLINK = 9, RM_LINK_XDEV = 10, RM_LINK_NONE = 11, + RM_LINK_DIR = 12, } RmLinkType; #if HAVE_STAT64 && !RM_IS_APPLE