当我截断正在使用的文件时会发生什么?

c4f*_*t0r 7 linux centos filesystems redhat-enterprise-linux

在网络上很多人说你可以截断一个文件 using> filename或者truncate -s0 filenamewhile file begin used

我知道每次进程写入文件时,该进程都会使用偏移量写入文件,并使用这样的脚本进行测试。

#!/usr/bin/env python

import os, time

with open("passwd","w") as f: #copy of passwd file in my current directory
        f.seek(0)
        for x in xrange(1,1000):
                f.write("hello world \n" + time.ctime() + "\n")
                f.flush()
                time.sleep(2)
Run Code Online (Sandbox Code Playgroud)

每次我的脚本进行写入系统调用时,/proc/pid_number/fdinfo/3 pos字段中的偏移量都会改变,但是当我尝试使用上面列出的方法截断文件时,在我的文件中,^@当我使用vim或更少打开文件时,我看到许多这样的字符-u 和文件类型从 更改ASCII textdata并且当我使用时ls -l filename大小未更改

因此,当截断文件时,文件的偏移量不会报告回来,我正在Centos 7和中测试这个Redhat 5,所以我可以告诉在进程使用文件时更改了文件大小不会释放空间并使我的文件变脏文件。

所以我的问题是,如果我的进程有一个打开的文件pos 1000并且我打开了truncate -s0 filename,如果截断有效,下一个进程写入会发生什么?

strace truncate -s0 passwd
open("passwd", O_WRONLY|O_CREAT|O_NONBLOCK, 0666) = 3
ftruncate(3, 0)                         = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?

 ls -l passwd 
 -rw-rw-r--. 1 user91 users 13832 Feb 23 17:16 passwd
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我的文件没有被截断

如果我打开附加模式,则不会发生此问题,例如使用此代码。

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


int main(){
        int range = 1000;
        int x; x = open("passwd", O_WRONLY|O_CREAT|O_APPEND);
        int i = 0;
        for( i = 0; i <= range; range++)
                write(x,"hello world\n",12);
                sleep(2);
}
Run Code Online (Sandbox Code Playgroud)

小智 12

请注意,虽然系统调用被称为 truncate,但实际上最好将其解释为“让我的文件报告这么多字节的大小”。根据系统调用联机帮助页:

truncate() 和 ftruncate() 函数导致由 path 命名或由 fd 引用的常规文件被截断为精确长度字节的大小。

如果以前的文件大于此大小,则额外的数据将丢失。如果以前的文件较短,则会对其进行扩展,并且扩展部分读取为空字节 ('\0')。

因此,您可以截断文件并将其变大,而不是变小。

所以我的问题是,如果我的进程在 pos 1000 中有一个打开的文件并且我做了 truncate -s0 文件名,如果 truncate 有效,下一个进程写入会发生什么?

  • 你已经截断了。此阶段的文件大小为 0 字节。偏移量为 1000。
  • 发生在位置 1001 的写入。
  • 文件大小为 1002 字节。字节 0-1000 包含 '\0'(空)。字节 1001+ 包含写入的数据。

当您从比文件本身大的位置写入文件时,文件末尾和新写入之间的数据变为空字节,这两点之间的文件数据称为sparse

实际上,您可以执行以下操作并产生相同的效果。

import os, sys

f = open('data.txt','w')
f.seek(1048576)
f.write('a')
f.flush()
f.close()
Run Code Online (Sandbox Code Playgroud)

您还提到以追加模式打开可以避免这种行为。这是真的,因为在这种情况下您正在指示内核“每次都写入文件的实际末尾”。如果您截断,则文件的末尾确实会发生变化。此外,您无法重新定位文件指针。

这是一个示例程序,它演示了文件、偏移量和已被截断的文件中的数据会发生什么。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <err.h>

#define FPATH "/tmp/data.txt"
#define FSIZE 65536

int main() {
  int a,b;
  char buf[FSIZE];
  char byte;
  struct stat st;
  memset(buf, 'A', FSIZE);

  a = open(FPATH, O_WRONLY|O_CREAT);
  b = open(FPATH, O_RDONLY);

  if (a < 0 || b < 0)
    errx(EXIT_FAILURE, "Could not open file");

  printf("Writing %d * 'A' into file\n", FSIZE);
  /* Write some bytes */
  if(write(a, buf, FSIZE) != FSIZE)
    errx(EXIT_FAILURE, "Couldn't write complete size out");

  /* Seek to a  new position in the file */
  lseek(b, FSIZE/2, SEEK_SET);

  printf("Current position of handle 'a': %d\n", lseek(a, 0, SEEK_CUR));
  printf("Current position of handle 'b': %d\n", lseek(b, 0, SEEK_CUR));
  stat(FPATH, &st);
  printf("Reported size on filesystem of %s: %d\n", FPATH, st.st_size);

  /* OK -- now, read the byte at the position */
  if (read(b, &byte, 1) < 0)
    err(EXIT_FAILURE, "Could not read file");
  printf("Character at current position of handle 'b': '%c'\n", byte);

  /* Truncate the file in the 'a' handle */
  printf("Performing truncate...\n");
  if (ftruncate(a, 0) < 0)
    err(EXIT_FAILURE, "Cannot truncate file");

  printf("Current position of handle 'a': %d\n", lseek(a, 0, SEEK_CUR));
  printf("Current position of handle 'b': %d\n", lseek(b, 0, SEEK_CUR));
  stat(FPATH, &st);
  printf("Reported size on filesystem of %s: %d\n", FPATH, st.st_size);

  printf("Writing one byte via handle 'a'\n");
  if (write(a, buf, 1) < 0)
    err(EXIT_FAILURE, "Cannot perform second write");

  printf("Current position of handle 'a': %d\n", lseek(a, 0, SEEK_CUR));
  printf("Current position of handle 'b': %d\n", lseek(b, 0, SEEK_CUR));
  stat(FPATH, &st);
  printf("Reported size on filesystem of %s: %d\n", FPATH, st.st_size);

  if (read(b, &byte, 1) < 0)
    err(EXIT_FAILURE, "Could not read file");
  printf("Character at current position of handle 'b': '%c'\n", byte);


  close(a);
  close(b);
  exit(0);
}
Run Code Online (Sandbox Code Playgroud)

这导致以下输出;

Writing 65536 * 'A' into file
Current position of handle 'a': 65536
Current position of handle 'b': 32768
Reported size on filesystem of /tmp/data.txt: 65536
Character at current position of handle 'b': 'A'
Performing truncate...
Current position of handle 'a': 65536
Current position of handle 'b': 32769
Reported size on filesystem of /tmp/data.txt: 0
Writing one byte via handle 'a'
Current position of handle 'a': 65537
Current position of handle 'b': 32769
Reported size on filesystem of /tmp/data.txt: 65537
Character at current position of handle 'b': ''
Run Code Online (Sandbox Code Playgroud)