为何StandardOpenOption.CREATE在Ubuntu挂载的SMB共享上失效?
问题背景
我把一台Windows文件服务器通过SMB协议挂载到了Ubuntu服务器的/mnt/DrawingsPB目录下,想通过Java代码创建文件,并且如果文件已经存在就直接覆盖。但最初写的代码抛出了AccessDeniedException异常,后来改成先手动删除已存在的文件,再用CREATE_NEW选项创建,代码就能正常运行了。请问这是什么原因?
初始代码(抛出权限异常)
private Path drawingsTargetDirectory = Paths.get("/mnt/DrawingsPB/Test"); public OutputStream getOutputStream() throws IOException { Path directory = drawingsTargetDirectory.resolve("TestDir"); Files.createDirectories(directory); Path path = directory.resolve("TestFile.txt"); return Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); }
抛出的异常信息
Caused by: java.nio.file.AccessDeniedException: /mnt/DrawingsPB/Test/TestDir/TestFile.txt at sun.nio.fs.UnixException.translateToIOException(UnixException.java:84) [rt.jar:1.8.0_382] at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) [rt.jar:1.8.0_382] at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) [rt.jar:1.8.0_382] at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:214) [rt.jar:1.8.0_382] at java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:434) [rt.jar:1.8.0_382] at java.nio.file.Files.newOutputStream(Files.java:216) [rt.jar:1.8.0_382] at com.company.router.drawing.impl.FileSystemStreamProvider.lambda$4(FileSystemStreamProvider.java:75) [classes:] ... 50 more
修改后可行的代码
public OutputStream getOutputStream() throws IOException { Path directory = drawingsTargetDirectory.resolve("TestDir"); Files.createDirectories(directory); Path path = directory.resolve("TestFile.txt"); if (Files.exists(path)) Files.delete(path); // 这步能正常执行 return Files.newOutputStream(path, StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); // 这步也能正常执行 }
原因分析
- SMB挂载的权限逻辑差异:SMB是Windows的文件共享协议,挂载到Linux后,权限控制和本地文件系统不一样。用
TRUNCATE_EXISTING选项时,Java会直接去截断已存在的文件,这需要同时拥有文件的写入权限和修改文件大小的权限。但Windows端可能给文件设了只读属性,或者Linux挂载SMB时的权限映射没配置对,导致没法直接截断文件,就抛出了权限异常。而删除后用CREATE_NEW是创建全新文件,只需要目录的写入权限,这在挂载时一般是允许的,所以能成功。 - Linux SMB客户端的特性:Ubuntu用的SMB客户端(比如cifs-utils)如果挂载时没明确指定
file_mode、dir_mode这类权限参数,对已存在文件的修改操作会更严格。截断文件属于修改现有文件,删除重建是新增文件,两者触发的权限校验逻辑不一样,前者过不了校验,后者可以。 - Java NIO与远程文件系统的兼容性:Java的
UnixFileSystemProvider是针对本地Linux文件系统做的实现,对SMB这种远程文件系统的兼容性有问题。TRUNCATE_EXISTING对应的底层系统调用在SMB挂载目录下没法正常执行,就抛出了权限错误,而先删后建的操作绕开了这个兼容性问题。
解决建议
- 挂载SMB时明确指定权限参数,比如在mount命令或者
/etc/fstab里加上file_mode=0666,dir_mode=0777(权限值根据实际需求调整),确保对已存在文件有足够的修改权限。 - 如果没法改挂载配置,就保持现在先删后建的逻辑,虽然多一步操作,但能稳定规避SMB的权限限制。
- 试试升级Java版本,或者用JCIFS这类第三方SMB客户端库直接连接Windows服务器,绕过Linux挂载层的兼容性问题。
内容的提问来源于stack exchange,提问作者Cardinal System
相关产品推荐
相关产品推荐

