diff options
| author | tv <tv@krebsco.de> | 2026-11-12 11:13:53 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-12 11:13:53 +0000 |
| commit | 1ecf303545d70da9b861b08874b71fd259ac4d3f (patch) | |
| tree | f015eaacebd66df1b54ee806ceb0c02cabb3873e /pkgs/populate | |
| parent | a0238c05909757ec44015fafc786284d4dbf329e (diff) | |
populate file: add insecureLinks
rsync 3.5.0 opens merge files, including per-directory .rsync-filter
files, walking the path one directory at a time, refusing symlinks that
neither root nor the user running rsync owns, and opening each directory
for reading. It treats the files it can't open that way as missing,
without saying so. That happens, e.g., below a home directory with mode
711, which the user may enter but not read, or behind another user's
symlink. Such a source then got synced without its .rsync-filter rules:
excluded files got transferred, and --delete-excluded deleted nothing.
Before syncing with rsync 3.5.0 or later a source containing .rsync-filter
files, populate asks rsync which of them it reads: a dry run with
--debug=FILTER2. If it reports an existing one as missing, the file
source type's new insecureLinks option decides: with null, the default,
populate fails with an error naming the file; with true, rsync reads such
files anyway (--insecure-links); with false, rsync syncs without them.
Both print a warning.
File scenarios sync a source through a symlink owned by nobody, with each
setting.
Diffstat (limited to 'pkgs/populate')
| -rw-r--r-- | pkgs/populate/default.nix | 55 |
1 files changed, 55 insertions, 0 deletions
diff --git a/pkgs/populate/default.nix b/pkgs/populate/default.nix index 992ac90..6362f92 100644 --- a/pkgs/populate/default.nix +++ b/pkgs/populate/default.nix @@ -753,6 +753,7 @@ let if test -d "$source_path"; then source_path=$source_path/ fi + ${insecureLinks config.insecureLinks} # Each item gets logged with what changed about it, and its size, as # transferred and in total. ${rsync}/bin/rsync \ @@ -765,6 +766,7 @@ let (filter: /* sh */ "--${filter.type} ${quote filter.pattern}") config.filters} \ -e ${quote (ssh' target)} \ + $insecure_links \ --itemize-changes \ --out-format=${quote "%i %n%L %'''b/%'''l"} \ --info=backup,del,copy,misc,mount,name,nonreg,remove,skip,symsafe \ @@ -787,8 +789,61 @@ let exclude = []; filters = []; deleteExcluded = true; + insecureLinks = null; }; + # rsync 3.5.0 and later open .rsync-filter files with a stricter walk than + # the files they transfer, and treat the ones that walk can't open as + # missing, without saying so. It fails, e.g., for a directory on the way + # that the user may enter but not read, like a home directory with mode + # 711, or a symlink that neither root nor the user owns. So rsync, when + # asked, tells which .rsync-filter files in the source in $source_path it + # reads. If it reports existing ones as missing, setting fails with an + # error if it's null, syncs without them if it's false, and makes rsync + # open them like the files it transfers with --insecure-links if it's true, + # both with a warning. Sets insecure_links to rsync's extra argument. + insecureLinks = setting: /* sh */ '' + insecure_links= + rsync_version=$(${rsync}/bin/rsync --version | ${coreutils}/bin/head -n 1) + rsync_version=$(set -- $rsync_version; echo "$3") + if test "$(printf '%s\n' 3.5.0 "$rsync_version" | ${coreutils}/bin/sort -V | + ${coreutils}/bin/head -n 1)" = 3.5.0 && + test -n "$(${findutils}/bin/find "$source_path" -name .rsync-filter -print -quit)" + then + # The first existing .rsync-filter file rsync reports as missing. + ignored=$( + ${rsync}/bin/rsync --dry-run --debug=FILTER2 -F -rlptD \ + "$source_path" "$TMPDIR"/rsync-filter-check 2>&1 | + while read -r line; do + case $line in + "[sender] parse_filter_file("*") [not found]") + file=''${line#*parse_filter_file(} + file=''${file%,*,*} + if test -e "$file"; then + printf '%s\n' "$file" + break + fi + ;; + esac + done + ) + if test -n "$ignored"; then + ${if setting == null then /* sh */ '' + >&2 printf 'error: rsync %s can'"'"'t read %s, so it would sync %s without it. This happens, e.g., if a directory on the way isn'"'"'t readable, or if a symlink on the way belongs to neither root nor the user running rsync. Set the source'"'"'s insecureLinks to true to make rsync read it anyway, or to false to sync without it.\n' \ + "$rsync_version" "$ignored" "$source_path" + exit 1 + '' else if setting then /* sh */ '' + >&2 printf 'warning: rsync %s can'"'"'t read %s; it reads it with --insecure-links anyway, because insecureLinks is true.\n' \ + "$rsync_version" "$ignored" + insecure_links=--insecure-links + '' else /* sh */ '' + >&2 printf 'warning: rsync %s can'"'"'t read %s, so it syncs %s without it, because insecureLinks is false.\n' \ + "$rsync_version" "$ignored" "$source_path" + ''} + fi + fi + ''; + runShell = target: command: if isLocalTarget target then command |
