您可以使用 inotify (see man 7 inotify) 或文件租约 (see man 2 fcntl, Leases section) 来检测其他进程何时打开和关闭文件(使用 inotify),或者确定文件是否被其他进程打开(文件租约)。
核心问题是xdg-open 通常是一个脚本,它检查环境(可能还有目标文件),然后执行一个二进制文件(反过来可能检查目标文件并执行另一个二进制文件),并且有可能这里的一个或多个阶段分叉并立即退出,客户端进程继续执行链。
这意味着system()返回的时间点基本上是无关紧要的。目标文件此时可能已被最终应用程序打开,也可能未打开;我们只是不知道,也无法知道。
一种选择是创建一个单独的进程组 (session),并监视会话,只要进程组存在就保留原始文件。但是,这假设xdg-open 链中的任何脚本或二进制文件都没有创建自己的会话。 (我不知道他们是否这样做,有很多不同的实现——每个桌面环境都使用自己的二进制文件,xdg-open 是围绕它们的兼容性包装器。)
实际上,这意味着将system() 替换为您自己的实现,使用fork()、setsid()、exec*()、waitpid() 和waitid();循环中的最后一个,短暂睡眠以检测进程组中何时没有更多进程。
另一种选择是执行命令,然后(派生一个子进程)等待一段特定的时间——也就是说,只要普通用户可以容忍等待文件开始加载;几秒钟,换句话说——,然后开始检查文件是否仍在使用中。文件不再使用后,可以取消链接。
使用inotify(),您可以在执行xdg-open 命令之前分配监视,然后监视打开和关闭。因为xdg-open 可能会检查目标文件以选择应用程序,所以您不能假设第一次关闭是最终关闭;您还需要等待上面提到的特定时间,以确保应用程序开放链已经完成。 那么,如果关闭次数与打开次数一样多,则可以取消链接文件。否则,您将等待剩余的关闭,并在最后一个关闭后取消链接。
使用file leases,方法稍微简单一些,但也比较局限。您只能获得用户自己拥有的普通文件的文件租约。只有当文件没有被任何进程(包括同一进程的其他描述符)打开以进行写入时,您才能获得读取租约。只有当文件根本没有被任何进程打开(包括同一进程的其他文件描述符)时,您才能获得写租约。当您持有租约时,任何其他打开文件的进程(如果您持有写租约)或尝试修改它(如果您持有读或写租约)将导致SIGIO 信号(默认情况下,您可以将其更改为实时信号)发送给租赁持有人。它最多有/proc/sys/fs/lease-break-time秒来降级或释放租约,直到内核强行破坏它;在此期间,打开器/文件修饰符将被 open()/truncate() 调用阻止。
在执行xdg-open 之前,您可以尝试获取文件的写租约。如果成功,您就知道这是它唯一打开的文件描述符。调用xdg-open 后,当文件打开(或由其中一个二进制文件检查)时,租约将被破坏;您可以在通话前简单地释放租约以避免麻烦。
从执行xdg-open 开始经过适当的秒数(人类等待应用程序开始打开文件的时间)后,您开始定期检查文件是否仍被其他进程打开通过尝试获得写租约。如果写入租约成功,并且从您启动 xdg-open 开始已经过去了足够的时间,那么您知道“人类用户”会变得太沮丧而无法再等待打开文件,或者应用程序已经已关闭文件,因此可以取消链接。
以上所有因素都可以结合起来,让你随心所欲,但就我个人而言,我相信建模人类行为的方法是最稳健的。我个人会让时间限制(和写入租约尝试间隔)很容易配置,分别有 10 秒和 1 秒的默认值。
最后,如果资源使用是一个问题,那么我建议编写一个单独的辅助二进制文件来为您管理它。基本上,不是运行xdg-open [OPTIONS] FILENAME,而是运行/usr/lib/myapp/open DELAY INTERVAL [OPTIONS] FILENAME。 /usr/lib/myapp/open 二进制分叉并立即退出。子进程既执行xdg-open,又执行上述过程以等待文件可以被取消链接。每个/usr/lib/myapp/open 二进制文件都需要很少的数据(最小的驻留集大小)和资源(它们大多处于休眠状态),因此即使在内存中拥有几十个它们也不会显着消耗即使在嵌入式 Linux 机器上。
如果有兴趣,我可以在此处添加 /usr/lib/myapp/open 的示例 C 实现。 (让我知道这三种方法中哪一种最有趣——进程组监控、inotify 或文件租约。)