在C语言中确定一个打开的文件是否被修改

8
有没有办法在POSIX下确定已打开的文件是否已被修改?更具体地说,我该如何实现下面的is_modified()函数?
FILE *f = fopen("myfile", "r+");

// do various things with f

if (is_modified(f))
    foo(f);

为了提供上下文,我正在使用C语言编写一个模块,需要为每个文件存储其哈希值在一个表中。该接口提供fopen()fclose()的包装器,可以在关闭文件时进行哈希计算。我找到了几种方法来完成这个任务,但是没有一种方法是像我希望的那样高效、清晰和无误的:
  • 对于每个以写方式打开的文件都进行哈希计算。
  • fflush(f)并检查时间戳是否更改。
  • 提供fwrite()fprintf()等的包装器。
有什么建议吗?

1
如果你是打开文件的人,为什么不简单地跟踪你是否对它进行了写入? - Greg Hewgill
为了抽象化。这应该是一个模块,可以提供文件句柄,客户端可以以任何方式操作。我不想修改使用标准库函数操作打开文件的其他代码的大部分内容。 - nccc
如果您想避免其他进程同时修改您的文件,请查看file locking - jweyrich
不,进程间锁定不是问题。在这种情况下,拥有许多线程也不会改变任何事情。 - nccc
抱歉,我上面应该澄清一下,可以安全地假设单个进程始终在使用文件。 - nccc
3个回答

6

1
这是一个非常简单的解决方案,但它容易受到竞态条件的影响。在获取修改时间之后,在比较并采取任何操作之前,文件可能已经被另一个进程/线程更改了。尽管如此,还是要点赞。 - jweyrich
我完全同意。我猜sleep()可以执行以等待最短时间。在我看来,这是一个架构问题的次优解决方案。 - nmjohn
2
我建议使用 fstat() 而不是 stat(),因为您已经有文件描述符可用。 - Greg Hewgill
这基本上是我上面提到的第二个解决方案。我必须使用 fclose() 函数关闭文件,然后使用 stat() 函数检查文件是否更改,如果检测到更改,则再次使用 fopen() 函数打开文件,对其进行哈希处理,最后再次使用 fclose() 函数关闭文件。这种方法效率太低。 - nccc
fstat() 接近,但请检查 @JonathanLeffler 的答案。 - nccc

3

很有趣,但是:a)意味着每次写操作都会生成一个事件;b)由于kevent()会阻塞,所以我需要至少两个线程。正确吗? - nccc
a) 可以使用队列事件对其进行摊销,然后将它们一起发送到其他线程。 b) 是的,你需要第二个线程来等待事件。 - jackdoe

3
由于您正在为fopen()fclose()提供带有封面的句柄,因此您可以在打开文件时记录fstat()的结果,并在关闭文件之前再次记录,然后进行比较。如果有任何更改,那么您就有了积极的更改,需要重新计算哈希值。如果没有更改,您可以相当有信心地认为您拥有与之前相同的文件。如果您需要消除这种不确定性,那么无论如何都需要重新计算哈希值,知道文件可能会在您计算哈希值时被另一个线程或另一个进程更改。
请注意,现代POSIX( POSIX 2008)提供了具有时间成员的struct stat
  • struct timespec st_atim - 最后一次数据访问时间戳。
  • struct timespec st_mtim - 最后一次数据修改时间戳。
  • struct timespec st_ctim - 最后一次文件状态更改时间戳。
这些提供了纳秒级的修改时间分辨率。由于向后兼容性的原因,可能会存在一些宏定义,例如:
#define st_atime st_atim.tv_sec
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec

尽管我所看到的情况下,POSIX标准并没有规定这一点。然而,自(Unix)时间的开始-1978年的第7版Unix以及可能更早的版本-就一直使用了st_Xtime名称,因此系统将希望保持旧代码的编译,而这些宏提供了一个相对无痛的方法来实现这一点。

不幸的是,st_mtime和其他成员在文件打开时不会更新。因此,这个解决方案归结为类似于fclose(); stat();的东西。 - nccc
更新会被安排(只要更改(如果有)被fflush()),如果你真的很担心,可以对文件描述符应用O_xSYNC选项。这个程序(带有适当的头文件)演示了在MacOS X 10.7.2上测试的结果,但在任何POSIX机器上都是预期的结果:int main(void) { struct stat s1,s2; char name[] = "zzz"; FILE *fp; if ((fp = fopen(name, "w+")) != 0) { fstat(fileno(fp), &s1); sleep(5); putc('x', fp); fflush(fp); fstat(fileno(fp), &s2); printf("t1 %ld; t2 %ld\n", (long)s1.st_mtime, (long)s2.st_mtime); fclose(fp); unlink(name); } return(0); } - Jonathan Leffler
是的,fflush()会更新时间戳,可能是因为它们引用了inode。我想这就是我们能做到的了;内核肯定知道文件是否脏了,但似乎没有以某种方式导出此信息。 - nccc
请记住,内核不知道FILE缓冲区中的内容,直到写入磁盘(这就是为什么fflush()很关键)。请注意,时间戳可能会更改,即使内容没有更改;但是,除非时间戳更改,否则我认为内容不会更改 - 除非有人使用utimes()utimens()等进行欺骗。 - Jonathan Leffler
我期望内核能够维护一些已修改文件块的列表。这是无法伪造的,直接提供了我所需要的内容。我想我将不得不检查Linux源代码以了解更多信息。 - nccc
是的,内核还维护着其磁盘缓冲池中磁盘块列表,并知道每个这样的块的状态。然而,它仍然不知道应用程序中所做的仅在“FILE”结构中记录的更改(那些尚未被刷新的更改,在显式“fflush()”或因缓冲区填充或文件指针移动(“fseek()”等)且内容是脏的情况下可以发生刷新)。此外,内核不会记录已被修改但已从内核缓冲池中刷新的块的记录。 - Jonathan Leffler

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