SDE II · VO Operating Systems / C++ Design

Microsoft Linux `cp` 实现:短读短写、覆盖策略与文件身份

从最小文件复制循环出发,讨论 partial write、源目标同文件、失败清理、元数据与符号链接策略。

题目重点:从最小文件复制循环出发,讨论 partial write、源目标同文件、失败清理、元数据与符号链接策略。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 最小训练版只复制一个普通源文件到目标路径;若目标已存在、是否覆盖和如何保持属性必须由产品接口明确。
  • 默认源与目标不是同一底层文件;实现需用文件身份检查发现同 inode/同路径(含硬链接)并拒绝自覆盖。
  • 要求复制内容完整或报错,异常后不把不完整临时文件当成功结果;是否事务级原子替换由文件系统与接口合同决定。

解题路线

  1. 解析源/目标路径与选项,检查源文件类型、目录目标语义及权限;先打开源并以文件身份标识,避免直接截断目标才发现它与源相同。
  2. 以固定大小 buffer 循环 read,读到 EOF 结束;每次 read 返回的字节数可能短于 buffer,write 也可能只写部分,必须在内部循环写完本次实际读取的全部字节;EINTR 等可重试错误依系统调用语义处理。
  3. 为避免复制中途失败留下貌似完整的目标,可在目标同目录建临时文件、写入并按要求同步,再 rename 发布;是否需要严格 crash durability,还要 fsync 文件及目录并确认目标文件系统语义。
  4. 复制期间源可能改变:若要求一致快照,需先锁/快照/复制后验证大小与版本;普通 best-effort cp 不一定保证此点,必须明示。
  5. 符号链接、目录递归、权限/所有者/时间戳、稀疏文件和 reflink 是不同扩展;指定 follow-symlink 还是复制链接本身,递归时防环并处理目标在源目录内。
  6. C++ 层用 RAII 包装 fd 以免错误路径泄漏资源;测试短写、磁盘满、权限拒绝、同文件、零字节文件、大文件和用户中断。

手动走一遍

read 返回 4,096 字节,第一次 write 只接受 1,000,第二次再写剩 3,096;若实现只调用一次 write 并把 1,000 当完整,就会静默截断文件。若 source 和 destination 经硬链接指向同一 inode,先以截断方式打开目标会毁掉源内容,应先核验身份。

规模与取舍

文件大小 B、buffer 大小 C:复制时间 O(B) 字节处理与约 O(B/C) 次正常读写系统调用,附加内存 O(C)。真实吞吐受磁盘/网络文件系统、缓存、同步策略和稀疏 extent 影响,题目未明确性能目标。

易错点与追问

容易失分的边界

  • 假设 read/write 总会返回请求的全部字节。
  • 先 truncate 目标再发现它其实是源文件的硬链接。
  • 中途失败却留下可见的部分目标并报告成功。
  • 把 rename 直接等同于持久落盘,没有讨论 fsync。
  • 未给选项却声称已实现真实 cp 的递归、符号链接和全部元数据语义。

继续深挖

  • 源文件复制过程中被并发修改如何定义结果?
  • 跨文件系统 rename 不可用时如何降级?
  • 复制稀疏文件与 reflink 怎样减少 IO,语义有何差别?
  • 如何避免递归复制时把目标目录不断复制进自己?
✓已复制微信号!