Skip to content

Commit

Permalink
sh: support reflinking directories
Browse files Browse the repository at this point in the history
- 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 sahib#618.
  • Loading branch information
intelfx committed Mar 26, 2023
1 parent 4d9f215 commit 28f54dd
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 11 deletions.
2 changes: 2 additions & 0 deletions lib/formats/sh.c.in
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
21 changes: 15 additions & 6 deletions lib/formats/sh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 3 additions & 1 deletion lib/reflink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Expand All @@ -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);
Expand Down
26 changes: 22 additions & 4 deletions lib/utilities.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions lib/utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 28f54dd

Please sign in to comment.