最直接判断文件系统是否只读的方式是用os.openfile以o_wronly|o_create模式尝试创建临时文件并分类错误:errors.is(err, syscall.erofs)或os.errpermission表明只读,errors.is(err, os.errnotexist)说明路径不存在,err为nil则可写。

os.Stat + os.IsPermission 是最直接的判断方式
检测文件系统是否只读,不能靠猜路径或查挂载选项,而应直接尝试写入——但又不能真写。Go 的惯用做法是:用 os.OpenFile 以 os.O_WRONLY | os.O_CREATE 模式打开一个**不存在**的临时文件,再立刻 Close 并检查错误类型。
关键点在于错误分类:
-
errors.Is(err, os.ErrPermission)或errors.Is(err, syscall.EROFS)→ 确认为只读文件系统(如mount -o ro、容器/proc挂载、某些 initramfs 场景) -
errors.Is(err, os.ErrNotExist)→ 父目录不存在,不是只读问题 -
err == nil→ 可写(哪怕只是“能创建”,已足够说明文件系统未被挂载为只读)
注意:不要用 os.Mkdir 测试,它可能因父目录缺失失败,干扰判断;也不要依赖 os.Stat().Mode(),权限位和挂载状态无关。
为什么不能只看 os.IsNotExist(err) 就断定可写
常见误判是看到 os.OpenFile("test.tmp", os.O_WRONLY|os.O_CREATE, 0644) 返回非 nil 错误,就跳过进一步分类,直接认为“不可写”。其实很多错误和只读无关:
-
permission denied:可能是父目录无写/执行权限(0555目录无法新建文件),而非文件系统只读 -
no such file or directory:上级路径缺失,os.MkdirAll才是解法 -
read-only file system(Linux)或Access is denied(Windows)才是真只读信号
必须用 errors.Is(err, syscall.EROFS)(Linux/macOS)或 errors.Is(err, syscall.EACCES) 配合上下文判断,否则会把权限配置问题当成系统级只读。
开机自启服务中“只读文件系统”的典型诱因
使用 github.com/kardianos/service 启动的服务,在 systemd 下常报 read-only file system,根本原因往往不是磁盘挂载问题,而是工作目录被设为只读路径(如 /usr/lib/myapp)或日志路径落在只读挂载点(如某些发行版的 /run 或容器中的 /proc)。
实操建议:
- 服务启动前,显式调用
os.Chdir("/tmp")或其他可写临时目录,避免继承只读工作目录 - 日志路径必须用绝对路径,且指向
/var/log、/tmp等明确可写位置,别用相对路径如./log - 若用
GOTMPDIR,确保该路径存在且可写:os.MkdirAll(os.Getenv("GOTMPDIR"), 0755) - 在容器中,检查是否挂载了
tmpfs或ro选项,例如docker run --read-only -v /tmp:/tmp:rw ...
跨平台兼容性要注意的三个细节
Windows 和 Linux 对“只读文件系统”的语义处理差异极大,硬编码判断容易翻车:
- Windows 不支持
EROFS错误码,只读挂载极少(NTFS ACL 更常见),真正要检查的是syscall.EACCES+ 文件属性(os.Stat().Mode()&os.ModeReadOnly != 0) - macOS 的
EROFS行为接近 Linux,但 APFS 快照卷可能返回ENOTSUP,需额外覆盖 - 所有平台都需考虑符号链接目标路径的挂载状态:如果
/data是软链到/mnt/readonly,则os.OpenFile("/data/file", ...)实际检测的是/mnt/readonly的状态
真正可靠的检测,永远是对目标路径本身做一次最小化写试探,而不是解析 /proc/mounts 或 disk.Partitions() ——后者返回的是挂载选项,不是运行时实际行为。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











