【问题标题】:How do I detect a file write error in C?如何检测 C 中的文件写入错误?
【发布时间】:2012-07-04 04:03:42
【问题描述】:

我有一个嵌入式环境,用户可以在其中插入或移除 USB 闪存驱动器。我想知道驱动器是否已被移除,或者当我尝试写入驱动器时是否存在其他问题。但是,Linux 只是将信息保存在其缓冲区中并返回而没有指示错误。
我使用的计算机带有 2.4.26 内核和 libc 2.3.2。
我正在以这种方式安装驱动器:
i = mount(MEMORY_DEV_PATH, MEMORY_MNT_PATH, "vfat", MS_SYNCHRONOUS, NULL);
可行:
50:/root # mount
/dev/scsi/host0/bus0/target0/lun0/part1 on /mem type vfat (rw,sync)
50:/root #

稍后,我尝试将文件复制到其中:

int ifile, ofile;
ifile = open("/tmp/tmpmidi.mid", O_RDONLY);
if (ifile < 0)
{
    perror("open in");
    break;
}
ofile = open(current_file_name.c_str(), O_WRONLY | O_SYNC);
if (ofile < 0)
{
    perror("open out");
    break;
}
#define BUFSZ 256
char buffer[BUFSZ];
while (1)
{
    i = read(ifile, buffer, BUFSZ);
    if (i < 0)
    {
        perror("read");
        break;
    }
    j = write(ofile, buffer, i);
    if (j < 0)
    {
        perror("write");
        break;
    }
    if (i != j)
    {
        perror("Sizes wrong");
        break;
    }
    if (i < BUFSZ)
    {
        printf("Copy is finished, I hope\n");
        close(ifile);
        close(ofile);
        break;
    }
}

如果这个sn-p代码是用写保护的USB存储器执行的,结果是

Copy is finished, I hope

在来自控制台内核的一连串错误消息中。
我相信如果我只是移除 USB 驱动器(不卸载它)也会发生同样的事情。
我也摆弄过devfs。我想出了如何让它自动安装驱动器(使用 REGISTER 事件),但是当我拉出内存时它似乎永远不会触发 UNREGISTER。
如何在我的程序中确定我是否成功创建了文件?

7 月 4 日更新: 不检查 close() 的结果是对我的愚蠢疏忽。不幸的是,该文件可以正确关闭。所以这没有帮助。 fsync() 呢?这听起来是个好主意,但也没有发现错误。
如果我有这样的东西,/sys 中可能会有一些有趣的信息。我相信直到 2.6 才添加它。?.
关于我的闪存驱动器质量的评论可能是有道理的。这是较早的之一。事实上,写保护开关现在似乎极为罕见。
我想我必须使用 overkill 选项:创建一个文件,卸载并重新安装驱动器,然后检查文件是否存在。如果这不能解决我的问题,那么事情就真的搞砸了!提醒自己:确保您尝试创建的文件不存在!
顺便说一句,这确实是一个 C++ 程序。您可以通过 .c_str() 来判断,为了简单起见,我打算将其编辑掉。

【问题讨论】:

  • 您是否尝试过 C 标准库 I/O 函数(fopenfreadfwrite 等)而不是 POSIX 函数?
  • 您可能还想在关闭时检查错误。

标签: c linux


【解决方案1】:

如果您的应用程序想要将一个或多个文件保存到 U 盘,并让用户在小 LED 熄灭后立即拔出 U 盘,您需要

  1. mount() 写入文件之前的 U 盘

    您不需要为此安装 SYNC,这有助于使用劣质 U 盘

  2. 使用正确的代码保存文件

    您对 C 低级 I/O 的尝试非常不正确。特别是,任何read()write() 都允许返回一个短计数,即介于1 和所请求大小之间的任何正值(含),而不表示任何类型的错误。

  3. fsync() U 盘上的文件

    验证它是否返回成功。如果是这样,那么您就知道数据已进入 USB 记忆棒。

  4. fsync() U 盘上包含文件的目录

    验证它是否返回成功。如果是这样,那么您就知道文件元数据已进入 USB 记忆棒,即使用户拔出 USB 记忆棒,该文件现在也应该可以访问了。

  5. umount() U 盘

    这将一直阻塞,直到 USB 记忆棒准备好断开连接,因此您的应用程序应该看起来像是正在保存到文件中,直到 umount() 返回。

    如果您执行了上述fsync()s,则umount() 应该非常直接。根据文件系统的不同,内核可能希望做一些记账,但无论如何都不应该花很长时间。

其他任何事情都是不可靠的。您可以通过安装具有同步访问的 VFAT 分区来做出某些假设,但这基本上只是手动操作。

如果您不想以 root 权限运行您的应用程序,您始终可以编写一个简单的特权服务来管理挂载和卸载。如果您怀疑在某个时候可能需要多个应用程序,我会非常热情地建议您这样做,因为只有集中式安装程序/卸载程序才能判断媒体何时准备就绪。 (如果另一个应用程序同时写入同一个 USB 媒体,它可能会延迟返回“未安装”消息。顺便说一下,当您不需要同步安装 USB 媒体时,这会很好。)我个人会在/var/run/,可能是/var/run/mounter.socket 中使用unix 域数据报套接字进行进程间通信。

最后,如果你的 Linux 内核配置正确并且你已经挂载了/sys/ 分区,你可以扫描所有/sys/block/sd?/ 目录来寻找可移动媒体:

  • /sys/block/sd?/removable 将包含一个非零十进制数字字符串
  • /sys/block/sd?/size 包含设备大小,以 512 字节为单位,为十进制数字字符串
  • /sys/block/sd?/device/vendor 包含供应商名称作为字符串
  • /sys/block/sd?/device/model 包含模型名称作为字符串

这些条目是硬件级别的,只要 USB 记忆棒连接并通电即可使用;安装与否都没有关系。如果/当用户拔出 U 盘时,整个设备目录树将立即消失。

【讨论】:

    【解决方案2】:

    如果你想检测所有的写错误,你需要检查的不仅仅是write()的返回码——你还必须调用fsync()(并检查返回值),还要检查@报告的错误987654323@.

    【讨论】:

    • +1 "检查 close() 报告的错误" 那。有人会认为这没有必要,因为当关闭文件失败时,无论如何你能做些什么。但是,此 Q 只是您需要这样做的一个示例。文档也明确地将未能做到这一点称为“严重的编程错误”。
    【解决方案3】:

    简短回答:问题出在操作系统和/或 USB 驱动器上。 Linux 会(有时?)愉快地将 USB 驱动器安装为可写的 设置了写保护开关。当它实际尝试写入时 但是,驱动器拒绝,这或多或少地被处理为 有缺陷的驱动器。 (我不确定为什么会发生这种情况。我的猜测是 闪存驱动器未向操作系统报告其只读状态。这 可能取决于您使用的闪存驱动器的品牌和型号。)

    我可以在 3.2.0-26 上使用(非常旧的)USB 密钥获得相同的行为 内核(特别是 Ubuntu 12)。我安装没有问题 写保护的 USB 密钥读写和 'cp' 不会抱怨,如果我复制一个 文件给它。该文件甚至可能出现在目录中(因为 缓冲),但实际上没有写入任何内容。我确实得到了一个 系统日志中有很多错误消息。

    如果我是你,我会尝试实际写入闪存驱动器并确保 在假设驱动器实际上是可写的之前它成功了。 具体来说,我会:

    1. 将驱动器挂载为可写。

    2. 在驱动器上创建一个具有唯一名称的新文件。

    3. 卸载驱动器以清空缓冲区。

    4. 重新安装驱动器。

    5. 检查新文件是否仍然存在并包含您写入的数据。

    6. 删除测试文件。

    我没有足够的硬件和驱动程序状态来告诉你 如果有办法从 API 检测不可写的驱动器——可能 是,但我不知道。但即使有,这种情况也会检测到 运行不正常的 USB 驱动器。

    更新:

    我做了更多的研究,结果发现如果设置了写保护开关,对文件句柄执行的 fsync() 将失败。因此,我收回上述建议。相反,这是我的测试程序:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    
    int
    main(int argc, char *argv[]) {
        char *path;
        int f;
        size_t stat;
        const char *message = "Hello, world.\n";
    
        if (argc != 2) {
            printf("Need filename\n");
            exit(1);
        }
    
        path = argv[1];
    
        f = open(path, O_CREAT | O_WRONLY | O_SYNC, S_IRWXU);
        if (f < 0) {
            perror("open out");
            exit(1);
        }/* if */
    
        stat = write(f, message, strlen(message));
        if (stat < 0) {
            perror("write");
            exit(1);
        }/* if */
    
        stat = fsync(f);
        if (stat) {
            perror("fsync");
            exit(1);
        }
    
        stat = close(f);
        if (stat) {
            perror("close");
            exit(1);
        }/* if */
    
        printf("(Apparently) successfully wrote '%s'\n", path);
        return 0;
    }
    

    如果设备不可写,这应该会在 fsync() 调用中失败。

    【讨论】:

      【解决方案4】:

      这是 POSIX 的做事方式。如果write 的返回值是-1,你肯定知道出了什么可怕的问题。但是,如果write 返回0,也可能出现问题。检查errno 变量以查看它是否与此处显示的预定义写入错误之一匹配:http://www.kernel.org/doc/man-pages/online/pages/man2/write.2.html

      【讨论】:

      • OP 使用的是 C,而不是 C++。 iostream 是 C++ 标准库的一部分。
      • 不完全。 :) OP 使用的是 POSIX 文件 I/O 函数,而不是 C 标准库函数。
      • 他说他正在使用 libc。这包括 stdio.h
      • 看看他的代码:我看到openreadwrite,而不是fopenfreadfwriteferror 不用于检查 POSIX 文件 I/O 函数的状态。
      • @EmileCormier 好的。为 POSIX 更新了它!
      【解决方案5】:

      如果你找不到解决方案,你可以试试这个丑陋的 hack。在写入和关闭输出文件后,您可以简单地尝试以读取模式打开它并检查它的大小是否正确。如果您真的想确保它具有正确的内容,您可以验证它是否具有与您刚刚编写的文件相同的校验和。这假定操作系统将直接从 USB 驱动器读取文件,而不是某种缓存。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-05-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-10
        • 1970-01-01
        • 2011-09-20
        相关资源
        最近更新 更多