c++ - 为什么没有目录时S_ISDIR()指示目录?

标签 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';
}

如果我运行该程序(很多次),很快,我将看到以下输出:
$./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

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

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

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

检查代码:
该代码似乎没有错。
Macro: int S_ISDIR (mode_t m)   
This macro returns non-zero if the file is a directory.
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.

我不完全了解此问题的含义,但也许与我的问题有关?
因此,我决定改用常规的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"));
}

突然,问题解决了。我一次又一次地快速启动该程序,但是它总是正确地报告该目录不存在。
我切换回C++代码,然后再次运行测试。果然,偶发的误报。

它是系统头吗?
我将C++ header 放入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"));
}

啊哈!
$./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

现在,它只是有时返回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)
S_ISDIR似乎只不过是一个帮助器,并且是否已经通过stat()来确定目录是否存在。 (再次,我已经尝试了statlstat。我是否想使用fstat?我不这样认为。我在网上找到了其他示例,人们正在使用S_ISDIR与我的示例代码相同)。

同样,当我将代码放入std::cout进行检查和打印的无限循环中时,它没有显示出任何症状。这使我相信问题仅出现在程序的开始,但是我想这也不是正确的,因为如果您查看我的原始输出,它就会:
$./main
-1 false
-1 false
-1 true
-1 true
-1 true
-1 true

操作系统/硬盘驱动器/系统库/编译器:
我的机器有问题吗?
不,我在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");
} 

该程序将永不返回错误。
#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");
}

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

该程序将永不返回错误。
#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");
}

好吧,那可能是因为它没有要冲洗的东西。

只要刷新至少一个字符,我们就会再次遇到问题。
这是真正的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);
}

同样,无限循环不起作用。
该程序将从不返回(假定第一次尝试就很幸运):
#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;
    }
}

因此,在重新启动进程以及刷新时会出现问题吗?
我放弃了程序集,但对我来说意义不大。
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    

我尝试遵循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;
    }

似乎调用了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); }
    };

据我所知,C++正在将工作植入std::fflush

经过更多测试之后,我发现fflush()中的<iostream>出现了问题,但fflush()中的<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

所以这一定是我要链接的东西吗?
//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);
}
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)
//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);
}
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)

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

最佳答案

如果系统调用成功,则只能分析struct stat设置的lstat()数据。如果失败,则返回-1(尽管值不确定,它可能根本没有修改fileInfo中的数据)。您在fileInfo.st_mode中得到的内容是垃圾,因为lstat()失败了-一时兴起,它对于S_ISDIR()可能返回true或false。

因此,您的第一个示例表明每次lstat()都失败,因此对struct stat的任何分析都是徒劳的。尚未将其设置为任何确定的值,并且任何结果都可以。

我相信,相同的论点适用于所有示例代码。
stat()lstat()之间的区别在于,如果提供的名称是符号链接(symbolic link),则stat()系统调用将引用符号链接(symbolic link)远端的文件系统对象(假定有一个;如果符号链接(symbolic link)指向不存在的对象,则失败)对象),而lstat()系统调用引用了符号链接(symbolic link)本身。当名称不是符号链接(symbolic link)时,这两个调用将返回相同的信息。

关于c++ - 为什么没有目录时S_ISDIR()指示目录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41762569/

相关文章:

c++ - 缺少用于 LLVM 编译器-rt 的 libclang_rt.san-x86_64.a 文件

c - c中的迭代目录遍历

php - 引用 GLOB_NOSORT 标志时的 "no sorting"顺序是什么

c - ftruncate() 的参数是什么?

java - Java 垃圾收集器会停止我从 JNI 调用创建的 POSIX 线程吗?

c - 如果正在等待的线程自行分离怎么办?

c++ - 在插入期间检测周期

c++ - 文字转语音 SAPI 语音

c++ - 在 Dev C++ 中运行一个项目

linux - 在 Linux 中创建到部分文件内容的硬链接(hard link)