1

我有一些我继承的代码,它是用于迭代、访问目录内容并使用 boost::filesystem::path 的类的一部分。代码部分内容如下:

struct directory_iterator_impl
{
private:
  void skip_dots(const path& p, const char* who)
  {
    static const std::string d(".");
    static const std::string dd("..");
    ASSERT( m_current_entry != 0 );
    while ( m_current_entry != 0 &&
        (m_current_entry->d_name == d ||
         m_current_entry->d_name == dd) ) {
      read(p, who);
    }
  }

  void handle_read_error(const path& p, const char* who)
  {
    ASSERT( errno != EBADF );
    ASSERT( errno != ENOENT );
    switch ( errno ) {
    case EOVERFLOW:
    case ESTALE:
      throw_error(who,
        error_code_impl::expected_unix_error(errno), p);
      break;
    default:
      base::utilities::abort_with_unknown_error();
      throw_error(who,
          error_code_impl::unexpected_unix_error(errno), p);
    }
  }

  void read(const path& p, const char* who)
  {
    errno = 0;
    m_current_entry = readdir(m_directory_stream);
    if ( errno != 0 ) {
      handle_read_error(p, who);
    }
  }

  void sync_current()
  {
    ASSERT( m_current_entry != 0 );
    m_current_name = m_current_entry->d_name;
  }

  void handle_open_error(const path& p, const char* who)
  {
    ASSERT( errno != EFAULT );
    switch ( errno ) {
    case EACCES: 
    case ELOOP:
    case ENAMETOOLONG:
    case ENOENT:
    case ENOTDIR:
    case EMFILE:
    case ENFILE:
    case ENOMEM:
    case ESTALE:
      throw_error(who,
          error_code_impl::expected_unix_error(errno), p);
      break;
    default:
      base::utilities::abort_with_unknown_error();
      throw_error(who,
          error_code_impl::unexpected_unix_error(errno), p);
    }
  }

public:
  directory_iterator_impl(const path& p, const char* w)
  {
    m_directory_stream = opendir(p.as_string().c_str());
    if (m_directory_stream == 0) {
      handle_open_error(p, w);
    }
    unsafe_increment(p, w);
  }

  ~directory_iterator_impl()
  {
    do {
      // coverity[double_free]
      if ( (m_directory_stream != 0) && 
        (closedir(m_directory_stream)==0) ) {
        return;
      }
    } while (errno == EINTR);
    ASSERT( errno != EBADF );
    base::utilities::abort_with_unknown_error();
  }

  void unsafe_increment(const path& p, const char* who)
  {
    read(p, who);
    if ( !no_entries() ) {
      skip_dots(p, who);
    }
    if ( !no_entries() ) {
      sync_current();
    }
  }

  void increment(const char* w)
  {
    ASSERT( !no_entries() );
    unsafe_increment("", w);
  }

  const std::string& dereference() const
  {
    return m_current_name;
  }

  bool no_entries() const
  {
    return m_current_entry == 0;
  }

private:
  DIR* m_directory_stream;
  struct dirent* m_current_entry;
  std::string m_current_name;
};

我遇到的问题是不可重现的 ASSERT,其中 errno == EBADF in handle_read_error()。现在检查代码,在我看来,在构造函数中,m_directory_stream 已设置,没有其他任何东西触及它。它不是 NULL 或者构造函数会调用 handle_open_error() 并且不会出现这种情况。因此,在构建时,m_directory_stream 是有效的,并且在打开时没有发生错误。然而,一段时间后,unsafe_increment() 被调用,也许是在构造函数的后面调用,此时 DIR 对象现在是 EBADF,我们在堆栈中得到断言失败。

以下是检测到故障的机器的信息(注意应用程序是单线程的):

Linux 2.6.9-67.ELsmp x86_64
OS:        RedHat Enterprise Linux 4.0 U6
CPU: 8 x 3000 MHz, Intel(R)Xeon(R) (2 socket, quad core, No HT)
Memory: 16 GB RAM, 33 GB Swap

当我们持有它时,文件描述符怎么会变坏?这是 dirent.h 接口的已知问题吗?

请建议如何使此代码更好并避免此不可重现的问题。

4

0 回答 0