Is there an OS-agnostic way to verify that a file isn't being written to or opened by another process?

huangapple go评论103阅读模式
英文:

Is there an OS-agnostic way to verify that a file isn't being written to or opened by another process?

问题

在运行时,是否有一种方法可以验证文件没有被其他进程写入或已被打开。最好是一种适用于所有操作系统的方法。

英文:

Wondering if there is a way to validate that a file isn't being written to or has been opened by another process at runtime. Preferably a way that would work on all OS's

答案1

得分: 1

一般情况下不行。

最常见的应用级机制来检测和防止其他进程使用或更改正在使用的文件的方法是文件锁定。

没有跨平台解决方案的一个原因是,一些操作系统提供了协作锁定,其中文件锁是有建议性的。例如,大多数Unix变体和Linux。

因此,在这些平台上,只有在其他进程事先已知使用特定类型的建议性锁时,才能确保知道其他进程是否正在使用文件。

这些平台中的大多数确实提供了强制锁定。它是作为文件属性的一部分,针对每个文件进行设置的。这种方法存在一些问题(例如竞争条件)。

因此,不能提供您所需的验证的底层机制是非常不同的。在Go语言中提供一个可靠的跨平台机制,可以保证在各种流行平台上工作,其中其他进程可能不合作,可能会非常麻烦。

参考资料:

英文:

Not in general.

The most ubiquitous general application-level mechanism for detecting and preventing use or alteration of a file that is being used by another process is file locking

One reason there isn't a cross-platform solution is that some operating systems provide for cooperative locking where file locks are advisory. For example most Unix variants and Linux.

So, on those platforms, you can only guarantee knowledge of other process using a file where the other process is known in advance to be using a specific type of advisory lock.

Most of those platforms do have mandatory locking available. It is set on a per-file basis as part of the file attributes. There are some problems with this (e.g. race conditions).

So no, the underlying mechanisms that could provide the verification you seek are very different. It would probably be very troublesome to provide a reliable cross-platform mechanism in Go that would be guaranteed to work on a variety of popular platforms where other processes are or can be uncooperative.

References

答案2

得分: 1

这不会回答你的问题,但由于我们可能正在处理一个 XY 问题,我想从一个不同的角度来看待这个问题,而不是锁定和检测文件是否正在被写入:一种称为“更新后重命名”的方法,这是对文件进行原子更新的唯一明智的方式,但可惜并不为(初学者)程序员所熟知。

由于文件系统本质上是竞争的,为了确保与文件的“类似数据库”的正确工作,即每个人都看到文件内容的一致状态,你必须使用锁定或原子更新或两者兼而有之。

要以原子方式更新文件的内容,你可以按照以下步骤进行操作:

  1. 读取文件的数据。
  2. 打开一个临时文件(在同一文件系统上)。
  3. 将更新的数据写入临时文件。
  4. 将新文件重命名为旧文件。

在所有现代通用操作系统上,重命名操作都是原子的,因此当一个进程尝试打开一个文件时,它要么打开旧版本,要么打开新版本,而不会出现中间状态。

在 POSIX 系统上,Go 的 os.Rename() 函数一直是原子的,因为它最终会调用 rename(2) 函数;在 Windows 上,自 Go 1.5 版本以来,这个问题也得到了修复。

请注意,这种方法仅在文件内容方面提供一致性,即不会有两个进程同时更新它的情况发生,但它不能确保“串行化”更新,只有通过锁定或其他“侧信道”信号才能实现这一点。也就是说,使用原子更新仍然可能出现以下情况:

  1. 进程 A 和进程 B 读取文件的数据。
  2. 它们都对其进行修改并进行原子更新。

文件的内容将是一致的,但状态将取决于最后调用操作系统重命名 API 函数的进程。

因此,如果你需要串行化,你需要使用锁定。

很遗憾,Go 并没有跨平台的文件锁定解决方案(无论如何,即使在类 Unix 系统中,锁定方法也存在很大差异,更不用说 Windows 了;可以参考这篇有趣的文章:链接),但一种方法是使用在步骤(2)中创建的临时文件的特定于平台的锁定机制。

更新文件的方法如下:

  1. 使用一个众所周知的名称打开一个临时文件。

    比如,如果要更新的文件名为 "foo.state",则将其命名为 "foo.state.lock"。

  2. 使用特定于平台的锁定机制对其进行锁定。

    如果锁定失败,这意味着另一个进程正在更新该文件,因此可以回退或等待,具体取决于你的需求。

  3. 一旦获得锁定,读取文件的数据。

  4. 修改数据后,使用被锁定的临时文件重新写入数据。

  5. 将临时文件重命名为原始文件。

  6. 关闭临时文件并释放锁定。

英文:

That won't answer your question but since we might be dealing with an XY problem here, I'd like to look at the problem from a PoV different to locking and otherwise detecting the file is not being written to: an update-then-rename-over approach which is the only sensible way to do atomic updates to files which is sadly not very well known by (novice) programmers.

Since filesystem is inherently racy, to ensure proper "database-like" work with files—where everyone sees consistent state of the file's contents,—you have to use either locking or atomic updates or both.

To update the file's contents in an atomic way, you do this:

  1. Read the file's data.
  2. Open a temporary file (on the same filesystem).
  3. Write the updated data into it.
  4. Rename the new file over the old one.

Renaming is guaranteed to be atomic on all contemporary commodity OSes so that when a process tries to open a file, it opens either an old copy or the new one but not something in between.

On POSIX systems, Go's os.Rename() has always been atomic since it would end up calling rename(2); on Windows it was fixed since Go 1.5.

Note that this approach merely provides consistency of the file's contents
in the sense no two processes would ever end up updating it at the same time
but it does not ensure "serialized" updates which is only possible to ensure
through locking or other "side-channel" signaling.
That is, with atomic updates, it's still possible to have this situation:

  1. Processes A and B read the file's data.
  2. They both modify it and do atomic updates.

The file's contents will be consistent, but the state would be of whatever
process ended up calling the OS's renaming API function last.

So if you need serialization, you need locking.

I'm afraid, that no cross-platform file locking solution exists for Go
(anyway, approaches to locking differ greatly even across Unix-y systems
— let alone Windows; see this for an entertaining read) but one way to do it is to use platform-specific
locking of that temporary file created on the step (2) above.

The approach to update a file then changes to:

  1. Open a temporary file with a well-known name.

    Say, if the file to update is named "foo.state", call it "foo.state.lock".

  2. Lock it using any platform-specific locking.

    If locking fails, this means another process is updating the file,
    so back out or wait—this really depends on what you're after.

  3. Once the lock is held, read the file's data.

  4. Modify it, re-write the temporary file being locked with this data.

  5. Rename the temporary file over the original one.

  6. Close the temp. file and release the lock.

huangapple
  • 本文由 发表于 2017年3月8日 08:22:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/42660794.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定