【发布时间】:2020-06-11 15:20:37
【问题描述】:
给定 CWD 中的文件text.txt,我从fopen() 得到两个不同的答案,即“此文件是否存在?”这个问题。对于a/b/../../test.txt(减少为test.txt):
- Windows:文件存在
- Linux:文件不(!)存在
但是,如果我随后 mkdir -p a/b Linux 改变其调子并说 a/b/../../test.txt 现在存在。
就好像 Linux 在处理相对路径时检查每个目录是否存在(如果其中任何一个不存在,则提前输出),而不是先折叠任何 ../,然后像 Windows 一样进行文件存在检查似乎可以。
两个问题:
- 在这种情况下,如何让 Linux 像 Windows 一样运行?像某种环境变量来修改 glibc/syscall 行为。
-
应该
fopen()如何处理这样的相对路径?我可以为fopen()找到的文档/规范只是表明它接受了相对路径(也许有几个例子),而不是在中间路径../的情况下应该如何解决这些路径。
测试程序:
#include <filesystem>
#include <fstream>
#include <iostream>
#include <cstdio>
void file_exists( const char* filename )
{
FILE* file = fopen( filename, "r" );
std::cout << ( file ? "Y" : "N" ) << ": " << filename << std::endl;
if( file )
{
fclose( file );
}
}
int main( int, char** )
{
// initial tests
std::filesystem::remove( "test.txt" );
std::filesystem::remove( "doesnt-exist.txt" );
file_exists( "test.txt" );
file_exists( "doesnt-exist.txt" );
std::cout << std::endl;
// create file
std::cout << "creating text.txt..." << std::endl;
std::ofstream output( "test.txt", std::ios::trunc );
output << "test" << std::endl;
output.close();
// more tests
file_exists( "test.txt" );
file_exists( "doesnt-exist.txt" );
file_exists( "a/b/../../test.txt" );
file_exists( "a/b/../../doesnt-exist.txt" );
std::cout << std::endl;
std::cout << "creating directory 'a/b'..." << std::endl;
std::filesystem::create_directories( "a/b" );
file_exists( "a/b/../../test.txt" );
file_exists( "a/b/../../doesnt-exist.txt" );
std::cout << std::endl;
// cleanup
std::filesystem::remove( "test.txt" );
std::filesystem::remove_all( "a/b" );
return 0;
}
结果:
-
Windows 10、VS2017:
N: test.txt N: doesnt-exist.txt creating text.txt... Y: test.txt N: doesnt-exist.txt Y: a/b/../../test.txt # file exists, expected behavior N: a/b/../../doesnt-exist.txt creating directory 'a/b'... Y: a/b/../../test.txt N: a/b/../../doesnt-exist.txt -
Ubuntu 20.04,
g++ (Ubuntu 9.3.0-10ubuntu2) 9.3.0N: test.txt N: doesnt-exist.txt creating text.txt... Y: test.txt N: doesnt-exist.txt N: a/b/../../test.txt # file *doesn't* exist, unexpected behavior N: a/b/../../doesnt-exist.txt creating directory 'a/b'... Y: a/b/../../test.txt N: a/b/../../doesnt-exist.txt
【问题讨论】:
-
在这种情况下,Windows 行为可能对您更方便,但我认为 Ubuntu 行为看起来更“正确”。如果以这种方式指定路径,也许它应该与指定 c/d/../../text.txt 不同。如果“a/b”或“c/d”(以指定者为准)不存在,则所写的路径是没有意义的。
-
Windows底层NT系统不支持“.”和路径中的“..”组件,而不是在某些文件系统中保留名称(例如 NTFS 保留它们;FAT32 不允许并允许创建名为“.”和“..”的文件)。因此,当将路径规范化为 NT 路径时,Windows API 必须将它们解析为纯字符串操作(例如,在调用
NtCreateFile之前规范化CreateFileW中的路径)。 -
@Basya:您当然希望操作系统会检查
a中的b的权限。 -
@dxiv:The cppreference page for
std::filesystem::path表示路径规范化是在 Windows 样式中完成的,没有文件系统检查。 -
一个例外是 NT 中的文件系统相关符号链接,它在内核中相对于符号链接的路径进行重新解析,其语义类似于 Unix——当且仅当组件存在时(即非- 存在“a”和“b”)。这是正确的方法,因为天真地解析“..”以删除符号链接(例如“symlink/../spam”)可能会导致完全不同的目标路径。因此,“..”组件的处理在 Windows 中是自相矛盾的,与 Unix 相比,这使得手动解析 Windows 中的相关符号链接(例如通过 readlink 实现)极其困难。
标签: c++ linux windows fopen relative-path