为什么 S_ISDIR() 指示的是一个目录,而实际上并没有目录?

Tre*_*key 3 c++ filesystems posix cout stat

POSIX 函数S_ISDIR有时会对我撒谎。
它告诉我一个目录存在,而它显然不存在。

这是一个说明问题的小程序:

#include <sys/stat.h>
#include <dirent.h>
#include <string>
#include <iostream>
#include <iomanip>

bool Is_Directory(const char* path_to_file){
  struct stat fileInfo;
  std::cout << lstat(path_to_file, &fileInfo) << " ";
  return S_ISDIR(fileInfo.st_mode);
}

int main(){

    std::cout << std::boolalpha;
    std::cout << Is_Directory("folder") << '\n';
    std::cout << Is_Directory("folder") << '\n';
    std::cout << Is_Directory("folder") << '\n';
    std::cout << Is_Directory("folder") << '\n';
    std::cout << Is_Directory("folder") << '\n';
    std::cout << Is_Directory("folder") << '\n';
}
Run Code Online (Sandbox Code Playgroud)

如果我运行这个程序(很多次),很快,我将看到以下输出:

$./main
-1 false
-1 false
-1 false
-1 false
-1 false
-1 false
$./main
-1 false
-1 false
-1 true
-1 true
-1 true
-1 true
$./main
-1 false
-1 false
-1 false
-1 false
-1 false
-1 false
Run Code Online (Sandbox Code Playgroud)

查看该函数如何突然返回true,即使该目录不存在。

但奇怪的是,如果我将程序置于无限循环检查中,它会继续说该目录不存在。只有快速连续地一次又一次运行该程序,我才能发现问题。

这是我到目前为止所尝试过的:

检查代码: 代码看起来没有错误。

Macro: int S_ISDIR (mode_t m)   
This macro returns non-zero if the file is a directory.
Run Code Online (Sandbox Code Playgroud)

的错误代码lstat始终为-1,因此我认为填充统计数据时不会偶尔出现错误。

阅读文档: 我看到了以下文档lstat

lstat() is identical to stat(), except that if pathname is a symbolic
       link, then it returns information about the link itself, not the file
       that it refers to.
Run Code Online (Sandbox Code Playgroud)

我不太明白这的含义,但也许它与我的问题有关?
所以我决定改用常规stat()的,但我仍然看到同样的问题。

不同的编译器: 我尝试了两种带有警告和清理程序的不同编译器。
g++clang++。两者都表现出同样的问题。

需要用C编译器编译吗?
我用 vanilla C 重新编写了程序(但仍然使用 g++/clang++ 编译)。

#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>

bool Is_Directory(const char* path_to_file){
  struct stat fileInfo;
  printf("%d ",lstat(path_to_file, &fileInfo));
  return S_ISDIR(fileInfo.st_mode);
}

int main(){

    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
}
Run Code Online (Sandbox Code Playgroud)

突然之间,问题就消失了。我一次又一次地快速启动该程序,但它总是正确地报告该目录不存在。
我切换回 C++ 代码,并再次运行测试。果然,偶尔会有误报。

是系统头吗?
我将 C++ 头文件放入 C 版本中。程序仍然可以正常运行,没有问题。

是 std::cout 吗?
也许std::cout速度较慢,这就是我看到问题的原因......或者可能完全不相关。也许间接使用std::cout会在二进制文件中保留导致问题的某些内容。或者正在std::cout对我的程序环境进行全局性的操作?
我在这里在黑暗中拍摄。

我尝试了以下方法:

#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>
#include <iostream>

bool Is_Directory(const char* path_to_file){
  struct stat fileInfo;
  printf("%d ",lstat(path_to_file, &fileInfo));
  return S_ISDIR(fileInfo.st_mode);
}

int main(){

    std::cout << "test" << std::endl;
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
    printf("%d\n",Is_Directory("folder"));
}
Run Code Online (Sandbox Code Playgroud)

啊哈!

$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 1
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 1
-1 0
-1 0
-1 0
-1 0
-1 0
$./main
test
-1 0
-1 0
-1 0
-1 0
-1 0
-1 0
Run Code Online (Sandbox Code Playgroud)

现在,这只是有时返回 true 的第一个检查。
这就像std::cout在某种程度上搞乱了S_ISDIR,但是在S_ISDIR调用之后,它不会搞乱对 的下一次调用S_ISDIR

调查来源:我找到了以下
源代码: S_ISDIR/usr/include/sys

/* Test macros for file types.  */
#define __S_ISTYPE(mode, mask)  (((mode) & __S_IFMT) == (mask))
#define S_ISDIR(mode)    __S_ISTYPE((mode), __S_IFDIR)
Run Code Online (Sandbox Code Playgroud)

S_ISDIR似乎只是一个助手,而该目录是否存在,已经由stat(). (我再次尝试了statlstat我应该使用 吗fstat 我不这么认为。我在网上找到了其他示例,人们使用的 S_ISDIR方式与我的示例代码相同)。

同样,当我将代码放入检查和打印的无限循环中时,它没有显示任何症状std::cout。这让我相信问题只发生在程序开始时,但我想这似乎也不是真的,因为如果你看看我的原始输出,它会发生:

$./main
-1 false
-1 false
-1 true
-1 true
-1 true
-1 true
Run Code Online (Sandbox Code Playgroud)

操作系统/硬盘/系统库/编译器:
我的机器有问题吗?不,我在Ubuntu 16.04.1 LTS。我去另一台机器上CentOS 6.5用旧版本的g++. 结果相同。
所以我的代码很糟糕。

隔离问题:

我已经把问题简化了。
该程序有时会返回错误。

#include <sys/stat.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
  struct stat fileInfo;
  stat(path_to_file, &fileInfo);
  return S_ISDIR(fileInfo.st_mode);
}
int main(){
    std::cout << std::endl;
    return Is_Directory("folder");
} 
Run Code Online (Sandbox Code Playgroud)

该程序永远不会返回错误。

#include <sys/stat.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
  struct stat fileInfo;
  stat(path_to_file, &fileInfo);
  return S_ISDIR(fileInfo.st_mode);
}
int main(){
    return Is_Directory("folder");
}
Run Code Online (Sandbox Code Playgroud)

为什么刷新缓冲区会导致目录有时存在?
实际上,如果我只刷新缓冲区,问题就会消失。

该程序永远不会返回错误。

#include <sys/stat.h>
#include <iostream>
bool Is_Directory(const char* path_to_file){
  struct stat fileInfo;
  stat(path_to_file, &fileInfo);
  return S_ISDIR(fileInfo.st_mode);
}
int main(){
    std::cout.flush();
    return Is_Directory("folder");
}
Run Code Online (Sandbox Code Playgroud)

嗯,那可能是因为它没有什么可冲洗的。

只要我刷新至少一个字符,我们的问题就会再次出现。
这是真正的 MCVE:

#include <sys/stat.h>
#include <iostream>
int main(){
    std::cout << std::endl;
    struct stat fileInfo;
    stat("f", &fileInfo);
    return S_ISDIR(fileInfo.st_mode);
}
Run Code Online (Sandbox Code Playgroud)

同样,无限循环不起作用。
这个程序永远不会返回(假设它在第一次尝试时很幸运):

#include <sys/stat.h>
#include <iostream>
int main(){
    while (true){
        std::cout << std::endl;
        struct stat fileInfo;
        stat("f", &fileInfo);
        if(S_ISDIR(fileInfo.st_mode)) return 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

那么重启进程和刷新时会出现问题吗?
我抛弃了程序集,但这对我来说没有多大意义。

g++ -std=c++1z -g -c a.cpp
objdump -d -M intel -S a.o > a.s

a.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include <sys/stat.h>
#include <iostream>
int main(){
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 81 ec a0 00 00 00    sub    rsp,0xa0
   b:   64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
  12:   00 00 
  14:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  18:   31 c0                   xor    eax,eax
    std::cout << std::endl;
  1a:   be 00 00 00 00          mov    esi,0x0
  1f:   bf 00 00 00 00          mov    edi,0x0
  24:   e8 00 00 00 00          call   29 <main+0x29>
    struct stat fileInfo;
    stat("f", &fileInfo);
  29:   48 8d 85 60 ff ff ff    lea    rax,[rbp-0xa0]
  30:   48 89 c6                mov    rsi,rax
  33:   bf 00 00 00 00          mov    edi,0x0
  38:   e8 00 00 00 00          call   3d <main+0x3d>
    return S_ISDIR(fileInfo.st_mode);
  3d:   8b 85 78 ff ff ff       mov    eax,DWORD PTR [rbp-0x88]
  43:   25 00 f0 00 00          and    eax,0xf000
  48:   3d 00 40 00 00          cmp    eax,0x4000
  4d:   0f 94 c0                sete   al
  50:   0f b6 c0                movzx  eax,al
  53:   48 8b 55 f8             mov    rdx,QWORD PTR [rbp-0x8]
  57:   64 48 33 14 25 28 00    xor    rdx,QWORD PTR fs:0x28
  5e:   00 00 
  60:   74 05                   je     67 <main+0x67>
  62:   e8 00 00 00 00          call   67 <main+0x67>
  67:   c9                      leave  
  68:   c3                      ret    

0000000000000069 <_Z41__static_initialization_and_destruction_0ii>:
  69:   55                      push   rbp
  6a:   48 89 e5                mov    rbp,rsp
  6d:   48 83 ec 10             sub    rsp,0x10
  71:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  74:   89 75 f8                mov    DWORD PTR [rbp-0x8],esi
  77:   83 7d fc 01             cmp    DWORD PTR [rbp-0x4],0x1
  7b:   75 27                   jne    a4 <_Z41__static_initialization_and_destruction_0ii+0x3b>
  7d:   81 7d f8 ff ff 00 00    cmp    DWORD PTR [rbp-0x8],0xffff
  84:   75 1e                   jne    a4 <_Z41__static_initialization_and_destruction_0ii+0x3b>
  extern wostream wclog;    /// Linked to standard error (buffered)
#endif
  //@}

  // For construction of filebuffers for cout, cin, cerr, clog et. al.
  static ios_base::Init __ioinit;
  86:   bf 00 00 00 00          mov    edi,0x0
  8b:   e8 00 00 00 00          call   90 <_Z41__static_initialization_and_destruction_0ii+0x27>
  90:   ba 00 00 00 00          mov    edx,0x0
  95:   be 00 00 00 00          mov    esi,0x0
  9a:   bf 00 00 00 00          mov    edi,0x0
  9f:   e8 00 00 00 00          call   a4 <_Z41__static_initialization_and_destruction_0ii+0x3b>
  a4:   90                      nop
  a5:   c9                      leave  
  a6:   c3                      ret    

00000000000000a7 <_GLOBAL__sub_I_main>:
  a7:   55                      push   rbp
  a8:   48 89 e5                mov    rbp,rsp
  ab:   be ff ff 00 00          mov    esi,0xffff
  b0:   bf 01 00 00 00          mov    edi,0x1
  b5:   e8 af ff ff ff          call   69 <_Z41__static_initialization_and_destruction_0ii>
  ba:   5d                      pop    rbp
  bb:   c3                      ret    
Run Code Online (Sandbox Code Playgroud)

我尝试遵循stat源代码,但迷失了方向。
C++ 源代码更容易理解。这是来自的刷新函数/bits/ostream.tcc

  template<typename _CharT, typename _Traits>
    basic_ostream<_CharT, _Traits>&
    basic_ostream<_CharT, _Traits>::
    flush()
    {
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 60. What is a formatted input function?
      // basic_ostream::flush() is *not* an unformatted output function.
      ios_base::iostate __err = ios_base::goodbit;
      __try
    {
      if (this->rdbuf() && this->rdbuf()->pubsync() == -1)
        __err |= ios_base::badbit;
    }
      __catch(__cxxabiv1::__forced_unwind&)
    {
      this->_M_setstate(ios_base::badbit);      
      __throw_exception_again;
    }
      __catch(...)
    { this->_M_setstate(ios_base::badbit); }
      if (__err)
    this->setstate(__err);
      return *this;
    }
Run Code Online (Sandbox Code Playgroud)

它似乎调用pubsync()这导致我找到一个sync()方法/ext/stdio_sync_filebuf.h

      sync()
      { return std::fflush(_M_file); }

      virtual std::streampos
      seekoff(std::streamoff __off, std::ios_base::seekdir __dir,
          std::ios_base::openmode = std::ios_base::in | std::ios_base::out)
      {
    std::streampos __ret(std::streamoff(-1));
    int __whence;
    if (__dir == std::ios_base::beg)
      __whence = SEEK_SET;
    else if (__dir == std::ios_base::cur)
      __whence = SEEK_CUR;
    else
      __whence = SEEK_END;
#ifdef _GLIBCXX_USE_LFS
    if (!fseeko64(_M_file, __off, __whence))
      __ret = std::streampos(ftello64(_M_file));
#else
    if (!fseek(_M_file, __off, __whence))
      __ret = std::streampos(std::ftell(_M_file));
#endif
    return __ret;
      }

      virtual std::streampos
      seekpos(std::streampos __pos,
          std::ios_base::openmode __mode =
          std::ios_base::in | std::ios_base::out)
      { return seekoff(std::streamoff(__pos), std::ios_base::beg, __mode); }
    };      sync()
      { return std::fflush(_M_file); }

      virtual std::streampos
      seekoff(std::streamoff __off, std::ios_base::seekdir __dir,
          std::ios_base::openmode = std::ios_base::in | std::ios_base::out)
      {
    std::streampos __ret(std::streamoff(-1));
    int __whence;
    if (__dir == std::ios_base::beg)
      __whence = SEEK_SET;
    else if (__dir == std::ios_base::cur)
      __whence = SEEK_CUR;
    else
      __whence = SEEK_END;
#ifdef _GLIBCXX_USE_LFS
    if (!fseeko64(_M_file, __off, __whence))
      __ret = std::streampos(ftello64(_M_file));
#else
    if (!fseek(_M_file, __off, __whence))
      __ret = std::streampos(std::ftell(_M_file));
#endif
    return __ret;
      }

      virtual std::streampos
      seekpos(std::streampos __pos,
          std::ios_base::openmode __mode =
          std::ios_base::in | std::ios_base::out)
      { return seekoff(std::streamoff(__pos), std::ios_base::beg, __mode); }
    };
Run Code Online (Sandbox Code Playgroud)

据我所知,C++ 正在将工作外包给std::fflush.

经过更多测试后,我发现 fflush()from<iostream>出现了问题,但fflush()from<stdio.h>却没有。

我试图从 向后追踪fflush(),但我认为我触及了源代码边界。

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int fflush (FILE *__stream);
__END_NAMESPACE_STD

#ifdef __USE_MISC
/* Faster versions when locking is not required.

   This function is not part of POSIX and therefore no official
   cancellation point.  But due to similarity with an POSIX interface
   or due to the implementation it is a cancellation point and
   therefore not marked with __THROW.  */
extern int fflush_unlocked (FILE *__stream);
#endif
Run Code Online (Sandbox Code Playgroud)

所以这一定是我要链接的东西?

//exhibits the problem
#include <sys/stat.h>
#include <iostream>
int main(){
    printf("\n");fflush(stdout);
    struct stat fileInfo;
    stat("f", &fileInfo);
    return S_ISDIR(fileInfo.st_mode);
}
Run Code Online (Sandbox Code Playgroud)
g++ -std=c++11 -o main a.cpp
ldd main
linux-vdso.so.1 =>  (0x00007ffdc878e000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1300c00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1300837000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f130052d000)
/lib64/ld-linux-x86-64.so.2 (0x000055bace4bc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1300316000)
Run Code Online (Sandbox Code Playgroud)
//works correctly
#include <sys/stat.h>
#include <stdio.h>
int main(){
    printf("\n");fflush(stdout);
    struct stat fileInfo;
    stat("f", &fileInfo);
    return S_ISDIR(fileInfo.st_mode);
}
Run Code Online (Sandbox Code Playgroud)
g++ -std=c++11 -o main a.cpp
ldd main
linux-vdso.so.1 =>  (0x00007ffd57f7c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f482dc6c000)
/lib64/ld-linux-x86-64.so.2 (0x000055828633a000)
Run Code Online (Sandbox Code Playgroud)

我认为libstdc++.so.6不适合使用S_ISDIR,但是libc.so.6是吗?我应该构建单独使用的代码S_ISDIR,然后将其与 C++ 代码链接吗?我怎样才能更快地发现这样的问题?我还是不明白发生了什么事。我是否因为链接了错误的库而践踏/观察了错误的记忆?您将如何解决这个问题?

Jon*_*ler 5

只有系统调用成功才能分析struct stat数据集。lstat()如果失败,它将返回(尽管值不确定,但-1它可能根本没有修改 \xe2\x80\x94 中的数据)。fileInfo你得到的东西fileInfo.st_mode是垃圾,因为lstat()失败 \xe2\x80\x94 它可以随意返回 true 或 false S_ISDIR()

\n\n

因此,您的第一个示例显示lstat()每次都会失败,因此对struct stat都是徒劳的;它没有被设置为任何确定的值,任何结果都可以。

\n\n

我相信同样的论点适用于所有示例代码。

\n\n
\n\n

stat()和之间的区别lstat()在于,如果提供的名称是符号链接,则stat()系统调用引用符号链接远端的文件系统对象(假设有一个;如果符号链接指向不存在的对象,则会失败),而lstat()系统调用指的是符号链接本身。当名称不是符号链接时,两个调用返回相同的信息。

\n