mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-08 11:22:14 -04:00
generator: fall back to a copy when the destination cannot hard-link
CAN_HARDLINK_SYMLINK and CAN_HARDLINK_SPECIAL are decided by configure running linkat() on whatever filesystem the build tree happened to sit on. The destination is free to disagree, and one host can hold both answers: macOS builds on APFS, which can hard-link a symlink, and backs up to HFS+, which returns ENOTSUP. A build that said yes had no fallback left. try_dests_non() reported the refusal as a transfer error and returned a matched basis, so the caller created the entry anyway -- correctly -- and the run still exited 23. Every neighbouring case copes: a regular file whose link() fails goes to try_a_copy, and a build compiled without either macro resorts to --copy-dest behaviour. This was the same situation, discovered a little later, and the only one treated as fatal. Take the existing fallback on any refusal. Singling out the "cannot" errnos is not possible: link(2) documents EPERM both for a filesystem with no hard-link support and for an ordinary permission refusal, and FUSE reports ENOSYS for the same thing. It is also what the regular-file path next door has always done (try_dests_reg -> hard_link_one -> try_a_copy), and consistency between the two was the point. Where the errno does matter the surrounding transfer says so anyway: ENOSPC, EDQUOT and EROFS fail the creation independently, EMLINK and EXDEV mean the link was never possible. EIO alone goes unremarked; reporting it would put a line into --link-dest's itemised output, so it is left out on purpose. Returning -3 rather than -2 keeps the caller out of the "already up to date, skip it" arm, which under --link-dest would drop the entry entirely. Both callers give -3 the treatment the compile-time fallback already gets -- clearing itemizing and code -- because try_dests_non() has itemised the match itself and would otherwise report the entry twice. The fallback is silent, matching a build that cannot link these at compile time; documented under --link-dest instead.
This commit is contained in:
1 parent
36be3b4e70
commit
d09edb85e6
2 files changed
+64
-11
No files matched your search
+47
-11
@@ -1198,8 +1198,10 @@ got_nothing_for_ya:
|
||||
}
|
||||
|
||||
/* This is only called for non-regular files. We return -2 if we've finished
|
||||
* handling the file, or -1 if no dest-linking occurred, or a non-negative
|
||||
* value if we found an alternate basis file. */
|
||||
* handling the file, -3 if we matched one but the destination refused to
|
||||
* hard-link it (the caller creates it instead, and must not report it again),
|
||||
* or -1 if no dest-linking occurred, or a non-negative value if we found an
|
||||
* alternate basis file. */
|
||||
static int try_dests_non(struct file_struct *file, char *fname, int ndx,
|
||||
char *cmpbuf, stat_x *sxp, int itemizing,
|
||||
enum logcode code)
|
||||
@@ -1254,6 +1256,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
|
||||
}
|
||||
|
||||
if (match_level == 3) {
|
||||
int cannot_hardlink = 0;
|
||||
#ifdef SUPPORT_HARD_LINKS
|
||||
if (alt_dest_type == LINK_DEST
|
||||
#ifndef CAN_HARDLINK_SYMLINK
|
||||
@@ -1264,12 +1267,29 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
|
||||
#endif
|
||||
&& !S_ISDIR(file->mode)) {
|
||||
if (do_link_at(cmpbuf, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to hard-link %s with %s",
|
||||
cmpbuf, fname);
|
||||
return j;
|
||||
}
|
||||
if (preserve_hard_links && F_IS_HLINKED(file))
|
||||
/* CAN_HARDLINK_SYMLINK/_SPECIAL answer for whatever
|
||||
* filesystem the build tree sat on; the destination is
|
||||
* free to disagree, and one host can hold both (macOS
|
||||
* builds on APFS, backs up to HFS+). A refusal here is
|
||||
* that same answer arriving late, so fall back to a copy
|
||||
* as a build without the macro does -- the caller creates
|
||||
* the entry either way, so failing the transfer only cost
|
||||
* the exit status.
|
||||
*
|
||||
* Every errno, as the regular-file path next door already
|
||||
* does (try_dests_reg -> hard_link_one -> try_a_copy).
|
||||
* Picking out the "cannot" errnos is not possible anyway:
|
||||
* link(2) documents EPERM both for a filesystem with no
|
||||
* hard-link support and for an ordinary permission
|
||||
* refusal, and FUSE reports ENOSYS for the same thing.
|
||||
*
|
||||
* The rest report themselves: ENOSPC/EDQUOT/EROFS fail the
|
||||
* copy too, EMLINK and EXDEV mean it was never linkable.
|
||||
* EIO alone goes unremarked, deliberately -- a diagnostic
|
||||
* here lands in --link-dest's itemised output. */
|
||||
cannot_hardlink = 1;
|
||||
match_level = 2;
|
||||
} else if (preserve_hard_links && F_IS_HLINKED(file))
|
||||
finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1);
|
||||
} else
|
||||
#endif
|
||||
@@ -1285,7 +1305,11 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
|
||||
rprintf(FCLIENT, "%s%s is uptodate\n",
|
||||
fname, ftype == FT_DIR ? "/" : "");
|
||||
}
|
||||
return -2;
|
||||
/* -2 tells the caller the entry is already up to date, which for
|
||||
* --link-dest means "skip it". We could not link it, so say -3
|
||||
* instead: no caller claims that, and the fall-through creates the
|
||||
* entry -- the same place a build without the macro ends up. */
|
||||
return cannot_hardlink ? -3 : -2;
|
||||
}
|
||||
|
||||
return j;
|
||||
@@ -1953,7 +1977,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
}
|
||||
} else if (basis_dir[0] != NULL) {
|
||||
int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
|
||||
if (j == -2) {
|
||||
if (j == -3) {
|
||||
/* The destination cannot hard-link this type. Land exactly
|
||||
* where a build without CAN_HARDLINK_SYMLINK lands: create
|
||||
* the entry, but leave the reporting to the itemisation
|
||||
* try_dests_non() already emitted. */
|
||||
itemizing = 0;
|
||||
code = FNONE;
|
||||
} else if (j == -2) {
|
||||
#ifndef CAN_HARDLINK_SYMLINK
|
||||
if (alt_dest_type == LINK_DEST) {
|
||||
/* Resort to --copy-dest behavior. */
|
||||
@@ -2032,7 +2063,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
}
|
||||
} else if (basis_dir[0] != NULL) {
|
||||
int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
|
||||
if (j == -2) {
|
||||
if (j == -3) {
|
||||
/* As above: a destination that cannot hard-link this type
|
||||
* behaves like a build without CAN_HARDLINK_SPECIAL. */
|
||||
itemizing = 0;
|
||||
code = FNONE;
|
||||
} else if (j == -2) {
|
||||
#ifndef CAN_HARDLINK_SPECIAL
|
||||
if (alt_dest_type == LINK_DEST) {
|
||||
/* Resort to --copy-dest behavior. */
|
||||
|
||||
+17
@@ -2926,6 +2926,23 @@ sign) if you want the local shell to expand it.
|
||||
attributes updated. If a match is not found, a basis file from one of the
|
||||
_DIRs_ will be selected to try to speed up the transfer.
|
||||
|
||||
Not every filesystem can hard-link a symlink, a device node, a FIFO or a
|
||||
socket, and the destination need not agree with the one rsync was built on
|
||||
-- macOS builds on APFS, which can, and may write to HFS+, which cannot.
|
||||
Where the destination refuses to link such an entry, it is copied instead
|
||||
and the transfer carries on, so only that entry loses the space saving.
|
||||
This applies to any refusal, because the error alone does not identify one:
|
||||
link(2) reports `EPERM` both for a filesystem without hard links and for an
|
||||
ordinary permission refusal. Regular files have always behaved this way.
|
||||
|
||||
One case is not covered. With [`--hard-links`](#opt) (`-H`), a group of
|
||||
such entries hard-linked to *each other* in the source needs a second link,
|
||||
from the first member to the rest, inside the destination itself. Where
|
||||
that link is refused too -- a destination that cannot hard-link the type at
|
||||
all -- the members after the first are not created and the transfer fails.
|
||||
A `--link-dest` on another filesystem is fine: only the link to _DIR_ is
|
||||
impossible there, and the ones within the destination still succeed.
|
||||
|
||||
This option works best when copying into an empty destination hierarchy, as
|
||||
existing files may get their attributes tweaked, and that can affect
|
||||
alternate destination files via hard-links. Also, itemizing of changes can
|
||||
|
||||
Reference in new issue
Block a user