是否有与操作系统无关的方法来验证文件没有被其他进程写入或打开?

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

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

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.