【问题标题】:write () call failed: No space left on device: ENOSPC handling写入()调用失败:设备上没有剩余空间:ENOSPC 处理
【发布时间】:2013-07-28 21:09:14
【问题描述】:

write() 调用失败,errno = 28 (ENOSPC),设备上没有剩余空间。 我正在尝试通过以下方式处理此错误。当磁盘已满时,我正在做lseek() 将文件指针移动到文件的开头。

我相信现在write() 应该不会失败,因为现在文件将从顶部被覆盖(文件不会扩展)。 但是write() 调用仍然失败并出现同样的错误。请解释这种行为。

  if(errno == ENOSPC)
  {
      curPos = lseek(gi4LogFd, 0, SEEK_SET);
      break;
  }

【问题讨论】:

  • 这会释放任何空间吗?
  • 是否曾经将任何数据写入偏移量 0?
  • 不,它不会释放空间。但我想说的是,即使空间已满,我也不会尝试扩展文件(因为文件指针位于文件的开头)。这就像从顶部覆盖同一个文件一样。为什么需要更多空间?
  • @alk 当我在设置 offest = 0 后调用 write 时,write() 再次失败,说没有空格。
  • 这不能回答我的问题,对不起。

标签: c linux file error-handling


【解决方案1】:

仅仅因为您写入文件的开头并不意味着文件系统将写入磁盘上的相同空间或完全分配文件开头的空间。

文件中可能有一个漏洞,在这种情况下,无论如何写入都会失败。漏洞是许多文件系统所做的优化,他们假装文件的一部分在那里,而实际上它只是很多零,所以这些部分永远不会被写入磁盘,只是簿记说文件的特定部分是空的。

您可能已经向文件系统过度使用数据(许多文件系统实际上不会在磁盘上分配空间,直到数据从缓冲区缓存中刷新,这可能是在写入完成后几秒钟,如果不是几分钟的话),其中万一写无论如何都会失败。您得到的 ENOSPC 实际上可能是因为您已经将文件系统填充到超过 100% 的容量,并且文件系统代码直到它尝试刷新您前一段时间所做的写入时才发现它。

您可能位于日志记录/日志文件系统上,其中在刷新日志之前不会发生实际的块分配,在这种情况下写入将失败。与缓冲区缓存情况相同的逻辑。

您可能已经用完了文件系统上某些特定的预分配元数据,即使它甚至还没有满,它也会因 ENOSPC 而失败。这在今天并不像过去那样普遍。

您的磁盘可能发现它的某些部分坏了,并告诉文件系统不要使用这些块并占用空间。

简而言之,无法保证文件系统的行为会像我们天真地认为它已满时那样。除此之外,还有其他原因永远不会填充超过 95% 的文件系统。几乎所有文件系统在快满时都是众所周知的不确定性。

【讨论】:

  • “文件开头的空间完全分配”是什么意思。 ?
  • @SumitTrehan 我的意思是,仅仅因为您可以读取文件并不意味着它的所有部分都已放置在磁盘上的任何位置(或者它们将永远放置)。
  • @Art 根据我的理解,数据存储在磁盘块中的磁盘中。所以必须有一个磁盘块,它将具有文件的开头。所以当我用 SEEK_SET offset = 0 写入 fp 时,它应该写入同一个磁盘块。为什么文件系统会分配一个新块?
  • @SumitTrehan 首先,“必须有一个磁盘块”是不正确的。还不必分配块。您的所有文件仍可能在缓冲区缓存或日志中。其次,同一个块不会被重用可能有几十个不同的原因。一切都取决于您使用的文件系统。重复数据删除、压缩、片段合并、数据版本控制、由于操作顺序而无法安全写入块等。一旦您的文件系统已满或接近满,所有赌注都将失败。
【解决方案2】:

仅仅因为您正在寻找文件的开头并不意味着文件被截断。可以对文件进行随机写入。

在完整的文件系统上写下一个块会导致问题。如果您想截断文件,请使用 truncate(2)ftruncate 在 lseek 之前调用文件

试试:

    if(errno == ENOSPC) {
        ftruncate(gi4LogFd, 0);
        lseek(gi4LogFd, 0, SEEK_SET);
        break;
    }

好的,因此具有日志支持的 ext3 文件系统不会在完整的 fs 上产生问题:

设置:

创建图像文件:

   dd if=/dev/zero of=/tmp/img.dsk count=8192

在 4k 图像文件上创建了 ext3 文件系统:

mkfs.ext3 /tmp/img.dsk
sudo mount /tmp/img.dsk /mnt/internal
sudo chown masud.users /mnt/internal

touch /mnt/internal/file.bin 

sudo dd if=/dev/urandom of=/mnt/internal/file.bin

这里 sudo 是 dd 所必需的,以确保超级用户的预留已填满。

现在:

df /mnt/internal/ 显示:

/dev/loop/0         3963  3963         0 100% /mnt/internal

使用以下代码:

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

char buf[8192];

int main(int argc, char *argv[])
{

    int rv;
    char *filename;

    if ( argc < 2 ) {
         fprintf(stderr, "provide the filename\n");
          return -1;
    }

    filename = argv[1];
    int rd = open("/dev/urandom", O_RDONLY);
    read(rd, buf, sizeof(buf));
    close(rd);

    int fd = open(filename, O_SYNC|O_RDWR);

    lseek(fd, -sizeof(buf), SEEK_END);
    rv = write(fd, buf, sizeof(buf));

    if ( rv < 0 ) {
        perror(filename);
        goto out;
    }
    lseek(fd, 0, SEEK_SET);

    rv = write(fd, "foo", 3);
    if ( rv < 0 ) {
       perror(filename);
    }
out:

   close(fd);
   return rv;


}

现在: ./foo /mnt/internal/file.bin

成功了。

所以问题是这与您的环境有何不同?

【讨论】:

  • 仍然应该能够在现有文件的任何位置写入而不截断它。
  • @phoxis 显然他可以在任何地方写,只是他们没有剩余空间:因此它不能在文件开头附加任何内容,事件,然后失败。
  • 取决于底层文件系统如何处理新写入的块。无法保证原始 inode 将被回收,很可能原始 inode 被标记为清理,并编写了一个新 inode 以确保日志或其他一些奇怪的优化。此外,其他进程也可能正在写入文件系统,并且正在使用粗略的舍入方案计算超级用户文件系统保留。
  • 当磁盘中没有剩余空间时,我们将无法向文件中添加新数据,即。附加。但是我们仍然应该能够覆盖现有数据,这可以通过寻找文件现有数据中的任何位置并覆盖它来完成。
  • @Xaqq 我不想追加。我已将 fp 移到开头,您不认为这是一种覆盖,而不是附加。
猜你喜欢
  • 2013-05-19
  • 2018-10-13
  • 2022-08-19
  • 2019-11-28
  • 1970-01-01
  • 2019-08-20
  • 1970-01-01
  • 1970-01-01
  • 2021-08-02
相关资源
最近更新 更多