Windows上不可靠的文件系统操作

Unreliable file system operations on Windows

本文关键字:文件系统 操作 不可靠 Windows      更新时间:2023-10-16

我不得不注意到Bazaar的目录锁定机制在我的机器上的一些奇怪行为,并试图复制它。这是我的简单测试用例:

  1. 创建一个目录Test,然后创建Test/held,然后创建一个文件Test/held/info

  2. Test重命名为YXCV

  3. 读取步骤1中创建的文件(现在从路径YXCV/held/info)。

  4. 清理(删除文件和目录)。

  5. 重复。

奇怪的是,这次失败了。有时在第2步("权限被拒绝"),有时在第3步(文件无法打开,尽管之后我可以在常规文本编辑器中打开文件)有时这会立即失败,有时会成功执行数千次迭代

我在这里运行Windows7。我怀疑有些配置更改(公司IT管理超出了我的控制范围),因为问题发生在一周前。

你知道任何可能的合理解释吗?

这是我的测试代码:

#include <iostream>
#include <fstream>
#include <direct.h>
#include <stdio.h>
void mkdir() {
    if ( mkdir( "Test" ) ) throw std::runtime_error( "mkdir" );
    if ( mkdir( "Test/held" ) ) throw std::runtime_error( "mkdir" );
}
void create() {
    if ( !std::ofstream( "Test/held/info" ).write( "asdf", 4 ) )
        throw std::runtime_error( "create" );
}
void rename() {
    if ( rename( "Test", "YXCV" ) ) throw std::runtime_error( "rename" );
}
void peek() {
    char buf[ 4 ];
    if ( !std::ifstream( "YXCV/held/info" ).read( buf, 4 ) )
        throw std::runtime_error( "peek" );
}
void del() {
    if ( unlink( "YXCV/held/info" ) ) throw std::runtime_error( "remove" );
    if ( rmdir( "YXCV/held" ) ) throw std::runtime_error( "remove" );
    if ( rmdir( "YXCV" ) ) throw std::runtime_error( "remove" );
}
void cleanup() {
    unlink( "Test/held/info" );
    rmdir( "Test/held" );
    rmdir( "Test" );
    unlink( "YXCV/held/info" );
    rmdir( "YXCV/held" );
    rmdir( "YXCV" );
}
int main() {
    cleanup();
    int count = 1;
    try {
        for ( ;; ++count ) {
            mkdir ();
            create();
            rename();
            peek  ();
            del   ();
        }
    }
    catch ( const std::exception &e ) {
        std::cout << "Run: " << count << "nError: " << e.what() << "nt"
                  << strerror( errno ) << 'n';
    }
    std::cin.get();
}

当我单独运行您的程序时,它将永远循环而不会出错。

然而,一旦我使用其他程序同时执行一些文件系统操作,您的代码就会失败,正如您所描述的那样:

  • 如果打开资源管理器窗口并在创建的叶目录中导航并停留在那里,则代码无法删除或重命名目录(步骤1或4)
  • 如果我用一些文本编辑器打开新文件,则读取文件失败(步骤3)

这是windows文件系统上的正常行为。例如,如果一个程序有一个目录句柄,则不能删除该句柄(rmdir()错误代码EACCESS)。

您已经解释过您在Bazaar版本管理目录结构中工作。这意味着一些后台服务进程监视目录和文件中的更改,并最终执行一些钩子和插件(这可能会延长锁定条件)。这通常会产生上述锁定情况。

p.S: 为了帮助您了解发生了什么,您可以使用Microsoft的流程资源管理器,并使用Ctrl+F搜索文件句柄。在"handle"字段中输入文件名,它将显示哪些进程使用该文件。注意:需要以管理员身份运行才能搜索系统进程

我在这里发布了一个答案,展示我现在是如何解决这个问题的。

以"打开文件YXCV/held/info问题"为例,不同之处在于我在打开文件之前明确检查文件的存在(通过调用stat()

有趣的是:我不必重试rename(),根据返回代码,这已经成功了,我的函数seek()只是没有立即看到它。我观察到的所有错误都可以通过等待预期可观察到的文件系统状态来解决。

编辑:正如Christophe的帖子评论中所指出的,rmdir在内部调用RemoveDirectory,这只是"在关闭时标记要删除的目录",使该函数异步。因此,我不必在rmdir上循环,我只需要等待删除发生。显然,rename(称为MoveFileEx)也做得很好,也许是出于同样的原因,尽管我在文档中看不清楚这一点,所以仍然有的谜团需要解决

请参阅此处的完整代码:

#include <iostream>
#include <fstream>
#include <direct.h>
#include <io.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <Windows.h>
bool exists( const char *p ) {
    struct stat buffer;
    return stat( p, &buffer ) == 0;
}
void wait_for( const char *p ) {
    while ( !exists( p ) ) {
        std::cout << "wait for " << p << 'n';
        Sleep( 500 );
    }
}
void wait_for_del( const char *p ) {
    while ( exists( p ) ) {
        std::cout << "wait for deletion of " << p << 'n';
        Sleep( 500 );
    }
}
void mkdir() {
    if ( mkdir( "Test" ) ) throw std::runtime_error( "mkdir" );
    if ( mkdir( "Test/held" ) ) throw std::runtime_error( "mkdir" );
}
void create() {
    if ( !std::ofstream( "Test/held/info" ).write( "asdf", 4 ) )
        throw std::runtime_error( "create" );
}
void rename() {
    wait_for( "Test" );
    if ( rename( "Test", "YXCV" ) ) throw std::runtime_error( "rename" );
}
void peek() {
    wait_for( "YXCV/held/info" );
    char buf[ 4 ];
    if ( !std::ifstream( "YXCV/held/info" ).read( buf, 4 ) )
        throw std::runtime_error( "peek" );
}
void del() {
    wait_for( "YXCV/held/info" );
    if ( unlink( "YXCV/held/info" ) ) throw std::runtime_error( "remove" );
    if ( rmdir( "YXCV/held" ) ) throw std::runtime_error( "remove" );
    if ( rmdir( "YXCV" ) ) throw std::runtime_error( "remove" );
    wait_for_del( "YXCV" );
}
void cleanup() {
    unlink( "Test/held/info" );
    wait_for_del( "Test/held/info" );
    rmdir( "Test/held" );
    wait_for_del( "Test/held" );
    rmdir( "Test" );
    wait_for_del( "Test" );
    unlink( "YXCV/held/info" );
    wait_for_del( "YXCV/held/info" );
    rmdir( "YXCV/held" );
    wait_for_del( "YXCV/held" );
    rmdir( "YXCV" );
    wait_for_del( "YXCV" );
}
int main() {
    cleanup();
    int count = 1;
    try {
        for ( ; count <= 1000; ++count ) {
            mkdir ();
            create();
            rename();
            peek  ();
            del   ();
        }
        std::cout << "OK.";
    }
    catch ( const std::exception &e ) {
        std::cout << "Run: " << count << "nError: " << e.what() << "nt"
                  << strerror( errno ) << 'n';
    }
    std::cin.get();
}