多用户目录使用sshfs挂载时rsync失败

3
我使用rsync来自动定期同步一个由多个人使用的Linux服务器上的根用户的主目录。用户需要的一项服务是通过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中得到了清晰的解释。后续文章(https://www.agwa.name/blog/post/easily_running_fuse_in_an_isolated_mount_namespace)提出了一个解决方案,但不可接受,因为fuse挂载只对创建该挂载点的进程可见。
我正在寻找一种不影响sshfs可用性且对用户透明的解决方案。

1
SO是用于编程问题的,而不是关于使用或配置Linux的问题。请尝试SuperUser.com或unix.stackexchange.com。 - undefined
2个回答

2
问题在于FUSE拒绝其他用户(包括root)访问stat。Rsync需要对指定的所有源文件和目录进行stat访问。但是,当属于另一个用户的rsync进程统计FUSE挂载点时,FUSE会拒绝该进程访问挂载点的属性,导致rsync抛出所述的“权限被拒绝”错误。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的活动系统同步时,用户可能会创建新的FUSE挂载点。排除列表需要经常更新,以包括新的FUSE挂载点。您可以通过修改SOURCES数组来进一步按每个用户名划分主目录。
SOURCES=(
/home/user1
/home/user2
)

如果您正在使用LVM,另一种解决方案是从LVM快照中使用rsync。 LVM快照提供了与其链接的逻辑卷的简单(例如,没有FUSE挂载点)和冻结视图。缺点是您必须为LVM快照的写时复制(COW)活动保留空间。非常重要的是,在完成后丢弃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

参考资料:

“如何使用FUSE破坏Rsync备份”


1
如果在rsync命令中排除了熔断装载点,则不会出现此错误。由于这是自动同步,因此可以使用mount命令获取所有熔断装载点。mount命令的输出可能因系统而异,但在debian jessie sshfs挂载上,它显示为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.+|\1|;
      /^${SOURCE//\//\\\/}.+/ {
        s|^${SOURCE//\//\\\/}| --exclude |;
        p;
      }
    }" ) )

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

很好地运用了sed来查找sshfs挂载点。可能有一些正则表达式被弄乱了吗?在第一个表达式中,s/^.+ on ([^.]+) type fuse.+/\1/对我来说有效。 - undefined
@SimonSC,正则表达式没有出错。也许你的挂载命令的输出格式有些不同,所以它才无法工作。请注意,在我的版本中,“on”之前的字符串和“on”与“type fuse”之间的挂载点都不能包含空格。实际上,挂载点中不能有空格是为了正确定义bash数组。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接