4

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

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

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

2 回答 2

7

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

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

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

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

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

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

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

于 2013-07-29T07:28:17.033 回答
3

Just because you are seeking to the beginning of the file doesn't mean that the file is truncated. It's possible to do random writes on a file.

Writing down a block on a full file system is going to cause issues. If you want to truncate the file use truncate(2) or ftruncate call on the file BEFORE lseek

try:

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

Okay so ext3 filesystem with journaling support does not create an issue on a full fs:

Setup:

Create an image file:

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

Created an ext3 filesystem on a 4k image file:

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

here sudo is necessary for dd to make sure that the reserve for superuser is filled up.

so now :

df /mnt/internal/ shows:

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

Using the following code:

#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;


}

Now: ./foo /mnt/internal/file.bin

Succeeds.

So question is how is this different from your environment?

于 2013-07-29T07:14:04.187 回答