Skip to content

云挂载为什么不能直写 ​

异地备份做到最后一步,我差点以为备份成功了 —— 文件在、大小看着对、tar 退出码 0。第二天恢复演练才发现:那个包是 0 字节。

这篇讲的就是这类"看起来成功"的失败,以及云挂载(rclone / WebDAV 挂到本地路径)到底能怎么用。

背景 ​

备份架构很常规:

  • 本地:每天打包数据库 + 上传目录,保留 14 份
  • 异地:每天同步到网盘,保留 7 份

网盘是通过 NAS 的"云存储挂载"功能挂成本地目录的,于是路径看起来跟普通目录没区别:

bash
/vol02/<uid>-<hash>/   # FUSE 挂载点(rclone)

那么"把备份直接 tar 到挂载点"看起来完全合理:

bash
tar czf /vol02/<uid>-<hash>/backup-$(date +%F).tar.gz ./data

现象 ​

这条命令的表现非常迷惑:

写法结果
tar czf <挂载点>/x.tar.gz ...退出码 0,文件存在,大小 0 字节
dd if=/dev/zero of=<挂载点>/x bs=1M count=10Bad file descriptor
echo hello > <挂载点>/x.txtBad file descriptor
cp /tmp/x.tar.gz <挂载点>/✅ 正常,md5 一致

同一个目录:cp 能写,重定向不能写,dd 不能写,tar 假装写成功但其实什么都没写。

原因 ​

这是 FUSE(这里底层是 rclone 的 WebDAV 后端)对写入模式的支持不完整造成的:

  • cp:打开临时文件、顺序写、关闭、重命名 —— 整块顺序写 + rename,rclone 的 VFS 缓存层能完整承接
  • > 重定向 / dd:直接 O_WRONLY 打开目标路径做随机写,部分操作(特别是 seek 和直接写)在 WebDAV 后端上无法映射,直接返回 EBADF
  • tar 直写:tar 会先写文件、再回填头部(seek 回去改),这种"先写后回填"在流式后端的映射最容易出问题(配合 O_WRONLY 打开方式,就成了静默失败)

关键在于:tar 的失败是静默的。 它自己不知道写入没落地,退出码照样 0。

正确做法 ​

分两步:先本地打包,再复制。

bash
# 1. 在本地磁盘打包(本地 ext4/btrfs 随便怎么写)
tar czf /tmp/backup-$(date +%F).tar.gz -C /path/to/data .

# 2. 复制到挂载点(cp 走顺序写 + rename,安全)
cp /tmp/backup-$(date +%F).tar.gz /vol02/<uid>-<hash>/backup/

# 3. 校验(关键)
md5sum /tmp/backup-$(date +%F).tar.gz
md5sum /vol02/<uid>-<hash>/backup/backup-$(date +%F).tar.gz

# 4. 清理本地临时文件
rm -f /tmp/backup-$(date +%F).tar.gz

代价是需要一份额外临时空间(等于备份包大小),所以 /tmp 或本地盘要留够。如果包很大,可以用管道 tar | tee 之类优化,但别为了省这一步空间把备份搞成静默失败。

三条能救命的习惯 ​

1. 任何跨文件系统/跨协议的写入,落盘后必须校验

bash
# 只信 md5,不信退出码
[ "$(md5sum a | cut -d' ' -f1)" = "$(md5sum b | cut -d' ' -f1)" ] && echo OK

cp 的退出码 0 只代表"系统调用没报错",不代表"对端真的存下了"。

2. 备份必须做恢复演练

我现在的规矩是:异地备份每周抽一个包,实际解出来跑一次 sqlite3 integrity_check 或者直接把库导入一遍。

如果没做这一步,这次 0 字节的事故会藏到真正需要恢复的那天才爆 —— 那时候就没有"重做一次备份"的机会了。

3. 用 stat 而不是"看起来在"

bash
stat -c '%s %n' /vol02/<uid>-<hash>/backup/*.tar.gz

云挂载的目录列表有时候是缓存/占位信息,ls 显示有文件不代表底层对象存在、也不代表大小对。读到实际大小才算数。

补充:这些限制是通用的 ​

同样的坑我在别的地方也踩到过,本质一样 —— FUSE / 网络文件系统对"随机写"和"先写后回填"支持不完整:

  • WebDAV 挂载点写 SQLite 数据库文件 → 直接损坏(SQLite 大量随机写 + fsync)
  • 云挂载目录上跑 git clone → 索引损坏
  • 在 SMB / NFS 上跑数据库 → 除非确认支持锁和 fsync,否则当只读用

判断标准很简单:这个写入会不会 seek 回前面改东西? 会,就别在网络挂载点上做。

一句话版本:云挂载能当"仓库",不能当"工作区"。