Get notified when a file gets opened and closed (macOS)

I would like to get notified, when a specific file gets opened or closed by another application.

I tried different things like DispatchSource, FSEvents and kqueue, but nothing worked. The only option so far is searching for all process ids, that accesses the file with proc_listpidspath (or with the command line tool lsof). Unfortunately I need to use a Timer for this. But checking i. e. every second will need a lot of CPU, so I'm curious, if there is a built in notification in macOS?

Replies

Let’s start with a link to the Swift Forums thread, which has more context.

On Swift Forums you wrote:

I am developing a server client application with a document management system. When a client wants to access a file, the file is sent from the server to the client and so that no other clients can edit the file, it's getting locked for everbody else. If the client, where the file is opened, now save his changes and when he closes the file, the new file should automatically be uploaded to the server and then be unlocked.

That’s not gonna work, at least not at this level and in the general case.

And in response to tera’s point about apps not keeping files open you wrote:

What do you mean with "many apps don't keep files open during editing"?

Consider this:

  1. Find or create an RTF file somewhere on your Mac.

  2. Open it in TextEdit.

  3. Check to see if TextEdit has it open:

    % lsof -p `pgrep TextEdit` | grep rtf
    %
    

Spoiler alert: It does not.

Would be interesting, which apps do this

Many apps. Even traditional Unix-y apps don’t necessarily keep the file open. For example, if you repeat the above test with vi you’ll find that it has .test.rtf.swp open, but not test.rtf.

(and especially why).

Because they don’t need to keep the file open. Their model of the file is in memory.

Or because they do all their work in a secondary file and only commit to the real file when you save.

This comes back to another point you raised on DevForums:

the FileEventHandler doesn't contain the .write, when my file is saved.

Right. That’s because most apps use a safe-save mechanism, where they write to a temporary and then replace the original file with that. In that case you won’t see a write event for the file, but a write event for the parent directory.


Coming back to the big picture, your approach is not going to work reliably. This isn’t due to macOS API limitations — although there are plenty of those (-: — but because you’re working at the wrong level. I see this quite a bit in my job: Folks trying to infer high-level semantics (in this case document open and close) from low-level operations (file system activity). I even have a post that discusses the problem in general: Inferring High-Level Semantics from Low-Level Operations.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you very much for your detailed answer!

Your're right, I just tested it with a simple .txt-file. lsof only checks, if there is an open file descriptor, but of course, there is none, if the file is completely read to the memory and then closed. It was true for pdf files opened by Preview (I just tested them so far).

Wow, that completely changes my plans, because it then seems, that there is no possibility to know, if my file is opened by another application or not. In the case of a txt-file it even would lead to the opposite, as then I would get notified, that the file was opened and then immediately closed.

Anyways, I could maybe implement it for those files (like pdf or other files), where the application keeps the file descriptor opened. So independently to my use case, is there any API, with which I get notified, when the file (desriptor) is opened or closed?

EDIT: After reading your linked topic, it seems to be only possible with Endpoint Security system extension?

So independently to my use case, is there any API, with which I get notified, when the file (desriptor) is opened or closed?

Yes and no.

An ES client is the obvious choice here, but it also has problems. Consider this sequence:

1 let fd = open(…)
2 mmap(…, fd, …)
3 close(fd)
4 … access the file through the mapping …

I think you’ll get the close event (ES_EVENT_TYPE_NOTIFY_CLOSE) at line 3, but the program can still access the file after that.


The fundamental problem here is that you’re working at the wrong level. There is an API-level concept of a file and a user-level concept of a document but there’s no one-to-one mapping between them. What you need is APIs that work in terms of documents. We have some of that [1], but it’s not comprehensive.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] One of my colleagues whose lurking on this thread reminded me of the NSFilePresenter protocol and the related NSFileCoordinator class.

Thank you very much. This really did bring a lot of light into my question.

I took a look on NSFilePresenter and at least I now can easily find out, that the file is changed and it seems to work even if the target application works with a file copy.

But yes, the main problem still will be, that apps can just load the file into memory and then close the file descriptor. Then there is no chance to know, if the file is still opened, except that I know, in which way the application creates those file copys for safe-save.