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

时间:2015-11-10 19:00:15

标签: c++ windows winapi file-io filesystems

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

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

  2. Test重命名为YXCV

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

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

  5. 重复。

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

    我在这里运行Windows 7。我怀疑一些配置更改(公司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() << "\n\t"
                      << strerror( errno ) << '\n';
        }
        std::cin.get();
    }
    

2 个答案:

答案 0 :(得分:1)

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

但是,一旦我使用其他程序同时执行某些文件系统操作,您的代码就会像您所描述的那样完全失败:

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

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

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

PS: 为了帮助您了解正在发生的情况,您可以使用Microsoft的process explorer并使用 Ctrl搜索文件句柄+ F 。在“句柄”字段中输入文件的名称,它将显示哪些进程使用该文件。注意:还需要以管理员身份运行以搜索系统进程。

答案 1 :(得分:1)

我在这里发布了一个答案,以展示我现在的工作方式。

采取&#34;打开文件YXCV/held/info问题&#34;例如,区别在于我在打开文件之前明确检查文件是否存在(通过调用stat())。

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

编辑:正如对Christophe的帖子的评论中所述,rmdir在内部调用RemoveDirectory,而且只是&#34;标记了关闭&#34;上的删除目录,该函数异步。因此,我不必循环遍历rmdir,我只需要等待删除发生。显然,rename(调用MoveFileEx)也可以,也许是由于同样的原因,虽然我看不清楚in the documentation,但仍有要解决的谜团

请参阅此处的完整代码:

#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() << "\n\t"
                  << strerror( errno ) << '\n';
    }
    std::cin.get();
}