posix_fallocate函数能在以追加模式打开的文件上工作吗?

4
我正在尝试为文件操作预分配磁盘空间,然而,当我调用posix_fallocate函数为以附加模式打开的文件分配磁盘空间时,遇到了一个奇怪的问题,它只分配了一个字节的空间,并且文件内容也不符合预期。有人知道这个问题吗?以下是我的测试代码,


#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#include  <sys/stat.h>
#include  <cerrno>

int main(int argc, char **argv)
{
     FILE *fp = fopen("append.txt", "w");
     for (int i = 0; i < 5; ++i)
          fprintf(fp, "## Test loop %d\n", i);
     fclose(fp);
     sleep(1);

     int  fid = open("append.txt", O_WRONLY | O_APPEND);

     struct stat  status;
     fstat(fid, &status);
     printf("INFO: sizeof 'append.txt' is %ld Bytes.\n", status.st_size);

     int  ret = posix_fallocate(fid, (off_t)status.st_size, 1024);
     if (ret) {
         switch (ret) {
         case  EBADF:
            fprintf(stderr, "ERROR: %d is not a valid file descriptor, or is not opened for writing.\n", fid);
            break;
         case  EFBIG:
              fprintf(stderr, "ERROR: exceed the maximum file size.\n");
              break;
         case  ENOSPC:
              fprintf(stderr, "ERROR: There is not enough space left on the device\n");
               break;
         default:
               break;
        }
     }

     fstat(fid, &status);
     printf("INFO: sizeof 'append.txt' is %ld Bytes.\n", status.st_size);

     char  *hello = "hello world\n";
     write(fid, hello, 12);
     close(fid);

     return 0; 
 }

预期结果应该是:
## Test loop 0
## Test loop 1
## Test loop 2
## Test loop 3
## Test loop 4
hello world

然而,以上程序的结果是:
## Test loop 0
## Test loop 1
## Test loop 2
## Test loop 3
## Test loop 4
^@hello world

所以,"^@"是什么?
而且信息显示,
INFO: sizeof 'append.txt' is 75 Bytes.
INFO: sizeof 'append.txt' is 76 Bytes.

有什么线索吗?
谢谢。

^@ 可能表示 NUL 字节 ('\0')。 - jxh
2个回答

5

快速回答

是的,posix_fallocate与以APPEND模式打开的文件一起使用。前提是你的文件系统支持fallocate系统调用。如果你的文件系统不支持它,则glibc模拟会在APPEND模式下添加一个单独的0字节到末尾。

更多信息

这是一个奇怪的问题,让我感到困惑。我通过使用strace程序查看正在进行的系统调用来找到答案。

看看这个:

fallocate(3, 0, 74, 1000) = -1 EOPNOTSUPP (Operation not supported)
fstat(3, {st_mode=S_IFREG|0664, st_size=75, ...}) = 0
fstatfs(3, {f_type=0xf15f, f_bsize=4096, f_blocks=56777565, f_bfree=30435527, f_bavail=27551380, f_files=14426112, f_ffree=13172614, f_fsid={1863489073, -1456395543}, f_namelen=143, f_frsize=4096}) = 0
pwrite(3, "\0", 1, 1073) = 1

看起来GNU C Library在这里试图“帮助”你。fallocate系统调用显然没有在你的文件系统上实现,因此GLibC通过使用pwrite在请求的分配末尾写入一个0字节来模拟它,从而扩展文件。

这在正常的写模式下工作正常。但是在APPEND模式下,写入总是在文件末尾完成,因此pwrite会在末尾写入一个0字节。

这不是预期的结果。可能是GNU C Library中的一个bug。

看起来ext4确实支持fallocate。如果我将文件写入/tmp,则可以工作。在我的主目录中它失败了,因为我在Ubuntu中使用加密主目录以及ecryptfs文件系统。


确实,这是一个glibc的bug。我会报告它。 - R.. GitHub STOP HELPING ICE
是的,我的系统不支持fallocate。不过,这似乎是一个Glibc的bug。 - tianya
+1,这应该是被接受的答案。就像Duck所说,你做了侦探工作并发现了一个轻微严重的glibc漏洞(请参见我的漏洞跟踪链接,了解为什么它比一开始看起来更严重)。 - R.. GitHub STOP HELPING ICE

1

基于 POSIX 的解释:

如果偏移量加上长度超出了当前文件大小,则 posix_fallocate() 将调整文件大小为偏移量加上长度。否则,文件大小将不会改变。

因此,在追加模式下使用 posix_fallocate 没有意义,因为它将扩展文件的大小(填充空字节),并且随后的写入将在那些空字节之后进行,而这部分空间尚未保留。

至于为什么只将文件扩展了一个字节,你确定这是正确的吗?已经进行了测量吗?那听起来像是实现中的错误。


我已经测量了很多次,每次测试都显示相同的行为。这真的很奇怪。此外,您可以在您的端上编译我的代码并测试其行为。 - tianya
@tianya - 我运行了你的程序,但没有看到你的结果。文件大小为1099。循环部分有一堆二进制零,然后是"hello world"。 - Duck
@Duck - 你可以查看Zan的评论。 - tianya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接