【问题标题】:cpp file reading error with stat and read() [closed]stat和read()的cpp文件读取错误[关闭]
【发布时间】:2015-07-06 18:50:16
【问题描述】:
  1. 我不经常遇到此错误,无法重现。
  2. 正在读取的文件是只读文件,不能删除或修改。
  3. 代码并不完全相同,因为它是我正在编写的更大内容的一部分,但这是导致问题的代码的重要部分。
  4. 此代码仅用于解释目的,而不是因为第 1 点而重现问题

我正在尝试使用

读取文件
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <memory>
#include <exception>
#include <iostream>
#include <glog/logging.h>
using namespace std;
int main() {
    string fileName="blah";
    struct stat fileStat;
    int status = ::stat(fileName.c_str(), &fileStat);
    if (status != 0) {
     LOG(ERROR) << "Error stating the file";
    }
    size_t fileSize = fileStat.st_size;
    // fileSize is 79626240. I am trying to read block starting from 
    // 67108864 bytes, so there will be 1251736
    size_t fileBlockSize = 16 * 1024 * 1024;
    size_t numBlocks = fileSize / fileBlockSize;    
    size_t offset = numBlocks;
    size_t actualSize = fileSize - offset * fileBlockSize;
    if (actualSize == 0) {
      LOG(INFO) << "You read the entire file";
      return 1;
    }

    int fd = ::open(fileName.c_str(), O_RDONLY);
    if (fd < 0) {
      throw std::runtime_error("Error opening the file");
    } else if (offset > 0 && lseek(fd, offset, SEEK_SET) < 0) {
     throw std::runtime_error("Error seeking the file");
    }

    uint64_t readBlockSize = 256 * 1024;    
    char *data = new char[readBlockSize + 1];
    uint64_t totalRead = 0;
    while (totalRead < actualSize) {
      ssize_t numRead = ::read(fd, data, readBlockSize);
      // Use the data you read upto numRead
      if (numRead == 0) {
       LOG(ERROR) << "Reached end of file";
       break;
      } else if (numRead < 0) {
       throw std::runtime_error("read unsuccessful");
      } 
      totalRead += numRead;
    }
    if (totalRead != actualSize) { 
      LOG(ERROR) << "Error reading the file";
    }
}

如果您想象我将文件切成 16 mybtes 大小的块,然后读取最后一个块。我正在以较小的大小循环读取块,但是在完成读取整个块之前我得到了 EOF。 stat 报告的大小是否会大于文件中数据的大小?

我看到的输出:

Reached end of file
Error reading the file

我不需要其他解决方案,我可以做其他事情,例如 lseek 到 END 但是我想知道为什么会这样?

PS 这不是因为磁盘上的块数。我正在使用 st_size,仅此而已

【问题讨论】:

  • 请给出一个最低限度完整的示例,最好是一个可以自行编译的示例。我们都懂代码,但很少有人在英语课上取得好成绩。
  • “我得到 EOF”是什么意思?究竟是什么操作返回了什么值?
  • 添加了更完整的 sn-p 代码。 EOF 我的意思是文件结束
  • 还添加了头文件。这段代码可能会编译,但这里写的代码更少,更多的是为了演示我正在尝试做的事情

标签: c++ linux io posix


【解决方案1】:

您必须小心在文件上使用stat,最好使用fstat 以避免TOCTOU 竞争条件。

int fileDescriptor = -1;
struct stat fileStat;
std::vector<char> fileContent;
std::string filename("test.txt");

fileDescriptor = open(filename.c_str(),O_RDONLY);
// Do error check of fileDescriptor
fstat(fileDescriptor,&fileStat);
// Do error check of fstat
fileContent.resize(fileStat.st_size);
::read(fileDescriptor,fileContent.data(),fileStat.st_size);

close(fileDescriptor);

另外,考虑到 read 可能返回一个小于 fileStat.st_size 的值,你必须读取剩余的字节(在文件 I/O 中相当困难,但在套接字中很常见),代码只是一个小例子。

编辑

我已经复制了你的代码并修改为加载一个本地 9MB 文件,在使用 g++ -g -std=c++11 -lglog main.cpp 编译后,我在第 51 行设置了一个断点

if (totalRead != actualSize)

这是我的调试会话的结果:

(gdb) b main.cpp:51 0x4013fc 处的断点 1:文件 main.cpp,第 51 行。

(gdb) r 启动程序:/home/jpalma/Documents/functionTest/a.out

[启用使用 libthread_db 进行线程调试] 使用主机 libthread_db 库“/lib64/libthread_db.so.1”。

断点1,main.cpp:51处的main()

51 if (totalRead !=> actualSize) {

(gdb) p totalRead

$1 = 9000032

(gdb) p 实际大小

$2 = 9000032

所以基本上你的程序对我来说完美无缺。也许您的文件系统有问题或与此无关。

我使用 ext4 作为文件系统。

ll 从我正在阅读的文件9000032 abr 29 16:10 WebDev.pdf 报告这个大小,所以你可以看到它实际上是正确的。我的页面大小是

$ getconf PAGESIZE
4096

【讨论】:

  • 感谢您的回答。我应该在问题中指定更多。我无法解决 TOCTOU 问题。我正在读取一个只读且无法删除的文件。请阅读更新后的问题,如果您有更新的答案,我将不胜感激。
【解决方案2】:

所以你的问题是:fstatstat 在只读未修改文件上报告的大小是否大于将从文件中读取的大小

read 上的一些元素首先返回值(来自手册页): 成功时,返回读取的字节数(零表示文件结束),并且文件位置提前该数字。如果此数字小于请求的字节数,则不是错误...错误时,返回-1,并适当设置errno

所以返回值最大是请求的大小,可以更小,0 表示文件结束,-1 表示错误。

手册页还说读取的字节数少于请求的大小可能会发生,例如因为现在实际可用的字节数更少(可能是因为我们接近文件结尾,或者因为我们正在读取管道,或来自终端),或因为 read() 被信号中断

因此,即使我永远看不到这一点,文档中的任何内容都不能保证即使读取文件,您也将获得所需的数据,除非已到达文件末尾。

但它明确指出,返回值 0 表示您在文件末尾。当您使用0 == read(...) 测试文件结尾时,一切都很好。

对于您的问题:stat 报告的大小是否可以不同于可以从文件中读取的字节数,答案是,除非文件系统损坏,或者存在物理读取错误。那是size 成员的定义:st_size 字段以字节为单位给出文件的大小(如果是常规文件或符号链接)(来自stat 手册页)

但我真的无法理解你的代码。首先我看到:

size_t fileSize = fileStat.st_size;
// fileSize is 79626240. I am trying to read block starting from 
// 67108864 bytes, so there will be 1251736
size_t fileBlockSize = 16 * 1024 * 1024;
size_t numBlocks = fileSize / fileBlockSize;    
size_t offset = numBlocks;
size_t actualSize = fileSize - offset * fileBlockSize;

所以当文件为 79626240 字节长时,actualSize 现在是 1251736

后来,没有actualSize 已经改变了:

uint64_t totalRead = 0;
while (totalRead < actualSize) {
  ssize_t numRead = ::read(fd, data, readBlockSize);
  ...
  totalRead += numRead;
}
if (totalRead != actualSize) { 
  LOG(ERROR) << "Error reading the file";
}

由于actualSize 尽管它的名字不是实际文件大小,你可以进入Error reading the file 分支。但如果它发生在真实文件大小上,请仔细检查您的文件系统。

【讨论】:

  • 我知道 cr == ::read(fd, data, actualSize) 应该做什么。但我要问的是,您是否有可能获得比 st_size 更小的读取大小。这就是问题所在。
  • @NikunjYadav :如果这是问题,我的答案不是。查看我的编辑。
猜你喜欢
  • 2013-03-15
  • 2020-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-18
  • 1970-01-01
相关资源
最近更新 更多