程序终止时保证文件被彻底删除(C/C++)

21

Win32的CreateFileFILE_FLAG_DELETE_ON_CLOSE,但我在Linux上。

我想打开一个临时文件,该文件将始终在程序终止时被删除。我可以理解,在程序崩溃的情况下,保证这一点可能是不切实际的,但在任何其他情况下,我希望它能起作用。

我知道RAII。我知道信号。我知道atexit(3)。我知道我可以打开文件并立即删除它,直到关闭文件描述符为止(甚至处理崩溃)。但是这些都不像是完整而直接的解决方案:

  1. RAII:已经做过了:我有一个对象,其析构函数删除该文件,但如果程序被信号终止,则析构函数不会被调用。
  2. 信号:我正在编写一个底层库,使得注册信号处理器成为棘手的问题。例如,如果应用程序本身使用信号怎么办?我不想踩任何人的脚趾。我可能会考虑一些巧妙地使用sigaction(2)来应对......但还没有对这种可能性进行足够的思考。
  3. atexit(3):显然无用,因为在异常终止(例如通过信号)时不会调用它。
  4. 预先删除unlink(2):这非常好,除了我需要文件保持可见(否则系统更难以监视/故障排除)。

你会在这里怎么做?

进一步解释

在我原始的帖子中,我省略了一个细节,现在意识到我应该包含它。 在这种情况下,“文件”不是严格意义上的普通文件,而是POSIX消息队列。 我通过mq_open()创建它。它可以通过mq_close()close()(前者在我的系统上是后者的别名)关闭。 它可以通过mq_unlink()从系统中删除。所有这些使它类似于常规文件,但是我无法选择文件所在的目录。 这使得当前最流行的答案(将文件放置在/tmp中)无法使用,因为“文件”是由系统在虚拟文件系统中创建的,其容量非常有限。(我按照man mq_overview的示例将虚拟文件系统挂载在/dev/mqueue中)。

这也解释了为什么需要名称保持可见(使即时取消链接方法无法使用):必须在两个或多个进程之间共享“文件”。


正是第四项(保持名称可访问)使得它变得困难。 - Jonathan Leffler
每当重要细节被遗漏,答案就会出错。你会记住下次的。 - Jonathan Leffler
8个回答

7
要求在进程运行时名称保持可见,这使得实现变得困难。你能否重新考虑这个要求吗?
如果不能,那么可能没有完美的解决方案。我建议将信号处理策略与Kamil Kisiel的建议相结合。您可以在安装信号处理程序之前跟踪已安装的信号处理程序。如果默认处理程序是SIG_IGN,则通常不会安装自己的处理程序;如果它是SIG_DFL,则会记住它;如果它是其他东西(用户定义的信号处理程序),则会记住该指针并安装自己的处理程序。当调用您的处理程序时,您将执行所需操作,然后调用记忆的处理程序,从而链接处理程序。您还将安装一个atexit()处理程序。您还将记录您这样做的方式以及您这样做的信号。
请注意,信号处理是一种不完美的策略;SIGKILL无法捕获,并且atexit()处理程序不会被调用,文件将被保留。
David Segond的建议-一个临时文件名守护进程-很有趣。对于简单的进程,它已经足够了;如果请求临时文件的进程分叉并期望子进程随后拥有该文件(并退出),则守护进程在检测到使用它的最后一个进程死亡时存在问题-因为它不会自动知道拥有它的进程。

2
我不相信我可以取消文件名可见性的要求。我已经在原始问题中添加了“进一步解释”,说明这不是一个常规文件,并且它位于一个固定的位置,容量非常有限。我认为我需要将这些文件可见以进行监测。 - John Zwinck
当然,如果有其他方法可以实现我的目标而不需要名称可见性要求,我也可以选择那种方式。基本上,我需要一种合适的方法来防止文件堆积,因为它们会消耗非常有限的资源(想象一下一个只有16MB容量和大约200KB文件的文件系统)。 - John Zwinck
1
哦,还有我不要忘记的一点,这些名称需要在正常操作期间保持可见,因为它们在程序之间共享。我不能在创建后立即取消链接 - 那将使它们无用。我应该最初就明确说明这一点。 - John Zwinck

6
如果您只是在创建临时文件,那么请将其创建在/tmp或其子目录中。然后尽最大努力通过atexit(3)或类似方法在完成后删除它。只要您使用通过mkstemp(3)或类似方法选择的唯一名称,即使由于程序崩溃而无法删除,也不会在后续运行或其他情况下再次读取它。
此时,这只是一个系统级问题,需要保持/tmp的清洁。大多数发行版都会在启动或关闭时清除它,或者定期运行cron任务来删除旧文件。

1
这不是你的错,但我不能使用这个解决方案。请查看我的问题中的“进一步解释”。我无法选择文件的路径,而且它所在的文件系统非常有限。如果没有其他选择,我可能会用cron来处理它-这是一个丑陋的权宜之计。 - John Zwinck

4
也许有人已经提出了这个建议,但是考虑到您的所有要求,我认为最好的方法是将文件名以某种方式传递给父进程,例如启动脚本,该脚本将在进程死亡后进行清理,如果未能这样做,则会添加更常见的用例以杀死和/或重新启动进程。这可能主要被称为看门狗,但是加入了更常见的用例,当进程出现故障时,可以杀死和/或重新启动它。如果您的父进程也死亡,那么您就几乎没有机会了,但是大多数脚本环境都相当强大,并且很少会死亡,除非脚本本身有问题,通常比程序更容易保持正确。

2
实际上,这是一个相当不错的想法 - 只是正在编写低级库。库初始化例程创建文件,然后进行分叉。子进程继续执行所有真正的工作。父进程只是坐在那里等待子进程终止,然后删除文件。 - Jonathan Leffler
1
这在一个通用的例程集中很难证明合理性;程序可能对进程结构有自己的要求。如果允许的话 - 您对客户端有足够的控制权 - 那么它将非常有效。 - Jonathan Leffler

3
在过去,我曾经构建了一个“临时文件管理器”,用于跟踪临时文件。
用户可以向管理器请求一个临时文件名,并且这个名称会被注册。
一旦您不再需要该临时文件名,您会通知管理器,然后文件名将被注销。
在接收到终止信号时,所有已注册的临时文件都将被销毁。
临时文件名基于UUID以避免冲突。

复杂,但显然它可行——尤其是如果临时文件管理器有一种方式来检测请求临时文件的进程是否仍在运行。如果该进程分叉,则会变得有些棘手;如果父进程退出,则会变得更加棘手。 - Jonathan Leffler
1
您可以添加一个要求,即当进程分叉时,父进程必须通知管理器子进程的pid。这也允许使用其他类型的进程间通信来传递tmp文件的文件名。 - KeithB
1
David,你的回答听起来好像这个“管理器”作为同一进程中的一个模块存在。Jonathan的评论让人觉得它应该是一个单独的进程。我可以看出它作为一个单独的进程是可行的,但你是在建议它可以在进程内运行吗?如果是这样,我认为它的价值不太大... - John Zwinck
临时文件管理器是进程内的。在大多数情况下,这很有效,但我同意它并不完美可靠。 - David Segonds

2

你真的需要保持名称可见吗?

假设您选择立即取消链接文件。然后:

  • 预防性 unlink(2):这非常好,除了我需要文件在文件系统中保持可见(否则系统将更难以监视/排除故障)。

    即使在删除文件后,您仍然可以进行调试,因为它仍然会显示在/proc/$pid/fd/下。只要您知道您的进程的 pid,枚举其打开的文件应该很容易。

  • 名称在正常操作期间需要保持可见,因为它们在程序之间共享。

    您仍然可以通过在 Unix 域套接字上传递文件描述符来在进程之间共享已删除的打开文件。有关更多信息,请参见在不同进程之间传递文件描述符的便携式方法


2
您可以在创建文件后使用进程分叉,然后等待子进程关闭,然后父进程可以取消链接该文件并退出。

1
  • 在您的点目录下设置一个临时文件的记账目录。
  • 创建临时文件时,首先创建包含路径或UUID到待处理临时文件的记账文件,并将其放置于记账目录中。
  • 创建该临时文件。
  • 当临时文件被删除时,删除相应的记账文件。
  • 程序启动时,扫描记账目录以查找包含指向临时文件路径的文件,并在发现时尝试删除它们,然后删除相应的记账文件。
  • (如果任何步骤失败,请记录详细信息。)

我没有看到更简单的方法来实现这个功能。这是任何生产质量程序必须经过的模板;+500行代码很容易就能实现。


我喜欢这个。如果同时存在多个程序实例,你如何处理?在启动时,如果我看到一个簿记文件,我就不知道它是来自之前崩溃的程序(因此应该被删除),还是来自当前正常运行的程序(因此绝对不应该被删除)。 - Nick Crews

1

我刚加入stackoverflow并在这里找到了你 :)

如果您的问题是管理mq文件并防止它们堆积,您并不需要保证在终止时删除文件。如果您只想避免无用的文件堆积,那么仅使用日志可能就足够了。在打开mq时向日志文件添加一个条目,关闭mq后再添加另一个条目,以及在初始化库时检查日志中的不一致性,并采取必要的措施来纠正不一致性。如果您担心在调用函数时崩溃,您还可以在调用这些函数之前添加一个日志条目。


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