From 9688beedbec2ea6d2ee993e5bb8b918531664247 Mon Sep 17 00:00:00 2001 From: Cebtenzzre Date: Sat, 27 Jun 2020 12:56:09 -0400 Subject: [PATCH 1/3] sh format: improve stampfile handling - mktemp in /tmp - don't assume $0 is a file - remove stampfiles at exit - don't create stampfiles if -n is passed Closes #554 --- lib/formats/sh.sh | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/formats/sh.sh b/lib/formats/sh.sh index ee956eeb..a8405350 100644 --- a/lib/formats/sh.sh +++ b/lib/formats/sh.sh @@ -41,6 +41,10 @@ DO_KEEP_DIR_TIMESTAMPS= # Set to true on -i DO_ASK_BEFORE_DELETE= +# Tempfiles for saving timestamps +STAMPFILE= +STAMPFILE2= + ################################## # GENERAL LINT HANDLER FUNCTIONS # ################################## @@ -51,6 +55,19 @@ COL_GREEN='\e[0;32m' COL_YELLOW='\e[0;33m' COL_RESET='\e[0m' +exit_cleanup() { + trap - INT TERM EXIT + if [ -n "$STAMPFILE" ]; then + rm -f -- "$STAMPFILE" + fi + if [ -n "$STAMPFILE2" ]; then + rm -f -- "$STAMPFILE2" + fi +} + +trap exit_cleanup EXIT +trap exit INT TERM + print_progress_prefix() { if [ -n "$DO_SHOW_PROGRESS" ]; then PROGRESS_PERC=0 @@ -218,12 +235,15 @@ cp_reflink() { printf "${COL_YELLOW}Reflinking to original: ${COL_RESET}%%s\n" "$1" if original_check "$1" "$2"; then if [ -z "$DO_DRY_RUN" ]; then - touch -mr "$1" -- "$0" + if [ -z "$STAMPFILE2" ]; then + STAMPFILE2=$(mktemp "${TMPDIR:-/tmp}/rmlint.XXXXXXXX.stamp") + fi + touch -mr "$1" -- "$STAMPFILE2" if [ -d "$1" ]; then rm -rf -- "$1" fi cp --archive --reflink=always -- "$2" "$1" - touch -mr "$0" -- "$1" + touch -mr "$STAMPFILE2" -- "$1" fi fi } @@ -278,7 +298,6 @@ remove_cmd() { if [ -n "$DO_KEEP_DIR_TIMESTAMPS" ]; then # Swap back old directory timestamp: touch -r "$STAMPFILE" -- "$(dirname "$1")" - rm -- "$STAMPFILE" fi if [ -n "$DO_DELETE_EMPTY_DIRS" ]; then @@ -380,7 +399,6 @@ do ;; k) DO_KEEP_DIR_TIMESTAMPS=true - STAMPFILE=$(mktemp 'rmlint.XXXXXXXX.stamp') ;; i) DO_ASK_BEFORE_DELETE=true @@ -402,11 +420,12 @@ then ask fi -if [ -n "$DO_DRY_RUN" ] -then +if [ -n "$DO_DRY_RUN" ]; then printf "#${COL_YELLOW} ////////////////////////////////////////////////////////////${COL_RESET}\n" printf "#${COL_YELLOW} /// ${COL_RESET} This is only a dry run; nothing will be modified! ${COL_YELLOW}///${COL_RESET}\n" printf "#${COL_YELLOW} ////////////////////////////////////////////////////////////${COL_RESET}\n" +elif [ -n "$DO_KEEP_DIR_TIMESTAMPS" ]; then + STAMPFILE=$(mktemp "${TMPDIR:-/tmp}/rmlint.XXXXXXXX.stamp") fi ######### START OF AUTOGENERATED OUTPUT ######### From 220f6d676e528f09540235e7cb17d55b5d86d615 Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Thu, 9 Mar 2023 23:09:29 +0400 Subject: [PATCH 2/3] sh: use `cp -a --attributes-only` instead of `touch -mr` --- lib/formats/sh.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/formats/sh.sh b/lib/formats/sh.sh index a8405350..893588c4 100644 --- a/lib/formats/sh.sh +++ b/lib/formats/sh.sh @@ -238,12 +238,12 @@ cp_reflink() { if [ -z "$STAMPFILE2" ]; then STAMPFILE2=$(mktemp "${TMPDIR:-/tmp}/rmlint.XXXXXXXX.stamp") fi - touch -mr "$1" -- "$STAMPFILE2" + cp --archive --attributes-only --no-target-directory -- "$1" "$STAMPFILE2" if [ -d "$1" ]; then rm -rf -- "$1" fi cp --archive --reflink=always -- "$2" "$1" - touch -mr "$STAMPFILE2" -- "$1" + cp --archive --attributes-only --no-target-directory -- "$STAMPFILE2" "$1" fi fi } From fc16423cea0e837b0ca4a9d0a66cf7c8ae585989 Mon Sep 17 00:00:00 2001 From: Ivan Shapovalov Date: Thu, 9 Mar 2023 22:51:09 +0400 Subject: [PATCH 3/3] 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