sshfs fuse mount多用户目录rsync失败

Failure of rsync of multi-user directory with sshfs fuse mount

我使用 rsync 在多人使用的 linux 服务器中自动定期同步主文件夹(root 用户)。用户需要的一项服务是可以通过 sshfs 挂载远程目录。但是,当存在 sshfs 挂载时,rsync 无法给出以下消息

rsync: readlink_stat("/home/???/???") failed: Permission denied (13)
IO error encountered -- skipping file deletion
...
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1183) [sender=3.1.1]

由于此错误,自动同步无法按预期工作,特别是由于跳过文件删除和非零退出代码。只有挂载 home 的文件系统才需要同步,因此所需的行为是忽略 sshfs 挂载。 -x / --one-file-system rsync 选项无法解决它。

这个问题在https://www.agwa.name/blog/post/how_fuse_can_break_rsync_backups . The follow-up article (https://www.agwa.name/blog/post/easily_running_fuse_in_an_isolated_mount_namespace中有清楚的解释)提出了一个解决方案,虽然不是一个可以接受的解决方案,因为保险丝安装只对创建安装的进程可见。

我正在寻找一种不影响 sshfs 可用性并且对用户透明的解决方案。

如果在rsync命令中排除了fuse挂载点,则不会出现该错误。由于是自动同步,可以使用mount命令获取所有fuse挂载点。 mount 命令的输出可能因系统而异,但在 debian jessie sshfs mounts 中显示为 USER@HOST:MOUNTED_DIR on /path/to/mount/point type fuse.sshfs (rw,...) . bash+sed 中自动排除保险丝安装的简单方法如下

SOURCE="/home/"

FUSEEXCLUDE=( $( mount |
  sed -rn "
    / type fuse/ {
      s|^[^ ]+ on ([^ ]+) type fuse.+||;
      /^${SOURCE//\//\\/}.+/ {
        s|^${SOURCE//\//\\/}| --exclude |;
        p;
      }
    }" ) )

rsync $OPTIONS "${FUSEEXCLUDE[@]}" "$SOURCE" "$TARGET"

问题是 FUSE 拒绝其他用户(包括 root)的统计访问。 Rsync 需要对所有指定的源文件和目录进行统计访问。但是当另一个用户拥有的 rsync 进程统计一个 FUSE 挂载点时,FUSE 拒绝该进程访问挂载点的属性,导致 rsync 抛出上述 "permission denied" 错误。 Mauricio Villega 的解决方案通过告诉 rsync 跳过 mount 命令列出的 FUSE 挂载点来工作。这是 Villega 解决方案的另一个版本,它使用 findmnt 命令指定文件系统类型的白名单。我选择了 ext3 和 ext4,但您可以根据需要添加其他类型。

#!/bin/sh
# Which paths to rsync (note the lack of trailing slash tells rsync to preserve source path name at destination).
SOURCES=(
/home
)
# Which filesystem types are supported.
FSTYPES=(
ext3
ext4
)
# Rsync each source.
for SOURCE in ${SOURCES[@]}; do
  # Build exclusion list (array of "--exclude=PATH").
  excludedPaths=$(findmnt --invert --list --noheadings --output TARGET --types $(IFS=',';echo "${FSTYPES[*]}"))
  printf -v exclusionList -- "--exclude=%s " ${excludedPaths[@]}
  # Rsync.
  rsync --archive ${exclusionList[@]} --hard-links --delete --inplace --one-file-system ${SOURCE} /backup
done

请注意,它在循环内构建排除列表以解决此解决方案的根本问题。该问题是由于 rsync 来自实时系统,用户可以在 rsync 为 运行 时创建新的 FUSE 挂载点。排除列表需要足够频繁地更新以包含新的 FUSE 安装点。您可以通过修改 SOURCES 数组,按每个用户名进一步划分主目录。

SOURCES=(
/home/user1
/home/user2
)

如果您使用的是 LVM,另一种解决方案是从 LVM 快照进行 rsync。 LVM 快照提供了一个简单的(例如,没有 FUSE 挂载点)和它所链接的逻辑卷的冻结视图。缺点是您必须为 LVM 快照的写时复制 (COW) activity 保留 space。使用完 LVM 快照后丢弃它是至关重要的;否则,随着修改的进行,LVM 快照的大小将继续增长。下面是一个使用 LVM 快照的示例脚本。注意不需要为rsync建立排除列表。

# Create and mount LVM snapshot.
lvcreate --extents 100%FREE --snapshot --name snapRoot /dev/vgSystem/lvRoot
mount -o ro /dev/mapper/snapRoot /root/mnt # Note that only root has access to this mount-point.
# Rsync each source.
for SOURCE in ${SOURCES[@]}; do
  rsync --archive --hard-links --delete --inplace --one-file-system /root/mnt/${SOURCE} /backup
done
# Discard LVM snapshot.
umount /root/mnt
lvremove vgSystem/snapRoot

参考文献:

"How FUSE Can Break Rsync Backups"