0

I have used kquque to monitor desktop with:

  • flags - EV_ADD | EV_CLEAR
  • fflags - NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | NOTE_REVOKE
  • filter - EVFILT_VNODE

However when I edit a .js file on desktop with sublime2 software, it doesnt trigger a notification :(

Please advise

Here is my js-ctypes code:

var rez_fd = ostypes.API('kqueue')();
console.info('rez_fd:', rez_fd.toString(), uneval(rez_fd));
if (ctypes.errno != 0) {
    throw new Error('Failed rez_fd, errno: ' + ctypes.errno);
}

this.kq = rez_fd;
this.path = OS.Constants.Path.desktopDir;

// Open a file descriptor for the file/directory that you want to monitor.
var event_fd = ostypes.API('open')(this.path, OS.Constants.libc.O_EVTONLY);
console.info('event_fd:', event_fd.toString(), uneval(event_fd));
if (ctypes.errno != 0) {
    throw new Error('Failed event_fd, errno: ' + ctypes.errno);
}

// The address in user_data will be copied into a field in the event.If you are monitoring multiple files,you could,for example,pass in different data structure for each file.For this example,the path string is used.
var user_data = ctypes.cast(ctypes.char.array()(this.path), ctypes.void.ptr);

// Set the timeout to wake us every half second.
var timeout = ostypes.TYPE.timespec();
var useSec = 0;
var useNsec = 500000000;
timeout.tv_sec = useSec; // 0 seconds
timeout.tv_nsec = useNsec; // 500 milliseconds

// Set up a list of events to monitor.
var fflags = vnode_events = ostypes.CONST.NOTE_DELETE | ostypes.CONST.NOTE_WRITE | ostypes.CONST.NOTE_EXTEND | ostypes.CONST.NOTE_ATTRIB | ostypes.CONST.NOTE_LINK | ostypes.CONST.NOTE_RENAME | ostypes.CONST.NOTE_REVOKE; // ostypes.TYPE.unsigned_int
var events_to_monitor = ostypes.TYPE.kevent.array(ostypes.CONST.NUM_EVENT_FDS)();
var filter = ostypes.CONST.EVFILT_VNODE;
var flags = ostypes.CONST.EV_ADD | ostypes.CONST.EV_CLEAR;
EV_SET(events_to_monitor.addressOfElement(0), event_fd, filter, flags, fflags, 0, user_data);

// Handle events
var event_data = ostypes.TYPE.kevent.array(ostypes.CONST.NUM_EVENT_SLOTS)(); // 1 slot

var num_files = 1; // ostypes.TYPE.int
var continue_loop = 40; // Monitor for twenty seconds. // ostypes.TYPE.int
while (--continue_loop) {
    var event_count = ostypes.API('kevent')(this.kq, ctypes.cast(events_to_monitor.address(), ostypes.TYPE.kevent.ptr), ostypes.CONST.NUM_EVENT_SLOTS, ctypes.cast(event_data.address(), ostypes.TYPE.kevent.ptr), num_files, timeout.address());
    console.info('event_count:', event_count.toString(), uneval(event_count));
    if (ctypes.errno != 0) {
        throw new Error('Failed event_count, errno: ' + ctypes.errno + ' and event_count: ' + cutils.jscGetDeepest(event_count));
    }
    if (cutils.jscEqual(event_data.addressOfElement(0).contents.flags, ostypes.CONST.EV_ERROR)) {
        throw new Error('Failed event_count, due to event_data.flags == EV_ERROR, errno: ' + ctypes.errno + ' and event_count: ' + cutils.jscGetDeepest(event_count));
    }

    if (!cutils.jscEqual(event_count, '0')) {
        console.log('Event ' + cutils.jscGetDeepest(event_data.addressOfElement(0).contents.ident) + ' occurred. Filter ' + cutils.jscGetDeepest(event_data.addressOfElement(0).contents.filter) + ', flags ' + cutils.jscGetDeepest(event_data.addressOfElement(0).contents.flags) + ', filter flags ' + cutils.jscGetDeepest(event_data.addressOfElement(0).contents.fflags) + ', filter data ' + cutils.jscGetDeepest(event_data.addressOfElement(0).contents.data) + ', path ' + cutils.jscGetDeepest(event_data.addressOfElement(0).contents.udata /*.contents.readString()*/ ));
    } else {
        // No event
    }

    // Reset the timeout. In case of a signal interrruption, the values may change.
    timeout.tv_sec = useSec; // 0 seconds
    timeout.tv_nsec = useNsec; // 500 milliseconds
}
ostypes.API('close')(event_fd);
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • 1
    Well, that _should_ work; probably you did something wrong in the code you haven't shown us. So show us the code. – abarnert May 02 '15 at 06:45
  • Thanks @abarnert I'll look into it. My code is js-ctypes so I hesitate to share as it confuses people haha. – Noitidart May 02 '15 at 06:52
  • 1
    Yeah, I've had similar problems with using bridge/FFI libraries; first you figure out how to write your code from documentation and sample code written for C (or C++ or ObjC or whatever), and then, when you have a problem, you have to translate it all back before a relevant expert can understand it. If you can create an [MCVE](http://stackoverflow.com/help/mcve) in C, it would help; if you can't, just post one in JS. – abarnert May 02 '15 at 07:18
  • Thanks @abarnert ! I'll try to write something up that's clear to understand :) – Noitidart May 02 '15 at 08:29
  • I added the code @abarnert :) It's in js-ctypes, everything works fine, its so weird it doesnt trigger contents-modified on the .js file, i do get contents-modified on the .DSStroe file – Noitidart May 02 '15 at 08:36
  • 1
    No, never mind… The point I was going to make is that most editors don't actually overwrite your file, they instead write a temp file and then `rename` it over your original, so you're not going to get a "contents-modified" notification. But you should still get an "unlink" notification (`NOTE_DELETE`, which you don't seem to have missed), and you're not getting that either. – abarnert May 02 '15 at 08:58
  • Ah thanks for that info about unlike I didnt know that, Ill keep debugging this :) – Noitidart May 02 '15 at 09:18
  • 1
    You may want to test doing similar things outside of Sublime—e.g., `cp myfile tmpfile; mv myfile thefile` and make sure that triggers `NOTE_DELETE` on `myfile`. – abarnert May 02 '15 at 09:24
  • 1
    What gets written to the console (both the JavaScript console and the system console log)? In what context is this running (e.g. a browser)? Could there be a sandboxing issue? – Ken Thomases May 02 '15 at 10:45
  • 1
    One last thing: according to [this thread](https://www.sublimetext.com/forum/viewtopic.php?f=2&t=11531), Sublime actually has an option to choose atomic save vs. overwriting, and it may be set to overwriting by default, so my last pair of comments might not be relevant. – abarnert May 02 '15 at 10:51
  • Thanks @abarnert that's what I was hoping to catch, sublime is doing something different, the file is being modified, and all other OS'es are catching the event. For instance FSEvents catches it, but for OSX < 10.7 I have to use kqueue and for *bsd. So I'm trying to detect atomic save and overwriting. – Noitidart May 02 '15 at 11:57
  • Thanks @KenThomases it's definitely working, it's just not triggering for certain events like this sublime case, even though the file contents does change, its very weird. I'm making an API for use in Firefox addons for OSX and BSD systems. (OSX 10.7+ I use FSEvents). – Noitidart May 02 '15 at 11:58
  • 1
    If you're monitoring the directory and Sublime is **not** doing an atomic save, then you shouldn't expect the directory to change. Sublime is opening the file and writing to it. That doesn't change the directory containing the file. – Ken Thomases May 02 '15 at 12:05
  • Thanks @KenThomases but when things like .DSStore is modified I get a trigger on the directory, I then stat the directory and compare the lastModTimes. But Im not getting a trigger with the sublime for overwrite/atomic-save :( Im just trying to get it to trigger the notification on the dir as the stat takes care of identifiying what happend. – Noitidart May 02 '15 at 12:12
  • 1
    That means that .DS_Store is being modified in a different way than how Sublime modifies files. Look at the inode numbers. The directory is only changed when a file is renamed, an existing file is unlinked, or a new file is created. One or more of those things happens with an atomic save, but not if a file is simply opened and modified. The change to a file's last modified time is **not** a change of the directory. It's a change of the file. You should probably investigate using the FSEvents API rather than `kqueue` to monitor this. – Ken Thomases May 02 '15 at 12:17
  • Thanks @KenThomases for that awesome explanation. I used FSEvents for OSX 10.7+ but for BSD system kqueue is my only option :( – Noitidart May 02 '15 at 12:19

1 Answers1

5

I just realized that you're not monitoring the .js file, you're monitoring its directory. That makes everything a lot less mysterious.

The short version is: If you open a file and write it, that doesn't change anything on the directory. If you atomically save a file, that does change the directory, but Sublime 2 doesn't atomically save by default.

So, to watch for any changes to any file in the directory, you need to enumerate all files in the directory and add them all to the kqueue,* as well as the directory.

Watching the directory will catch atomic saves (and new files being created); watching the files will catch overwrites. (Files being unlinked will trigger both.) If you're worried about performance… well, kqueue is designed to handle switching on 10000 file descriptors, and neither UFS nor HFS+ is a good filesystem for hundreds of thousands of directory entries in the same directory, so you're probably OK… but you may want to add some code that warns or aborts if the directory turns out to be massively huge.


If you want to understand why this is necessary, you have to think about how the two different kinds of saves work.

A write just writes to a file descriptor. That file descriptor could have one directory entry link on the filesystem—but it could just as easily have none (e.g., it was created in a temporary namespace, or you just unlinked the file after creating it), or many (e.g., you've created hard links to it). So it can't actually update "the directory entry for the file", because there is no such thing.

An atomic save, on the other hand, works by creating a new temporary file, writing to that, and then rename-ing the temporary over the original file. That rename has to update the directory, replacing the entry pointing at the old file with an entry pointing at the new file. (Of course it also sends a DELETE notification for the file itself, because the file is losing a link. And you'll also usually sends an ATTRIB, because most apps want the new file to have the same extended attributes, extra forks, etc.)


* There is an obvious race condition here: if a file is moved or deleted between the readdir and adding it to the kqueue, you'll get an error. You may want to handle that error by generating an immediate notification, or maybe you just want to ignore it—after all, from the user perspective, it's not much different from the case where someone deletes a file between the time your program starts and the time you do the readdir.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 2
    I think that claiming that "`kqueue` is designed to handle switching on tens of thousands of file descriptors" is an exaggeration. There's a limit on the number of file descriptors a process can have open and it's probably not *multiple* tens of thousands. On OS X, it's 10240. Also, if you're watching a whole hierarchy of files, you need to start and stop watching as files are added to and removed from the hierarchy, which is non-trivial to do efficiently. – Ken Thomases May 03 '15 at 22:38
  • 1
    @KenThomases OK, you're right, it was designed for 10000, not 10s of thousands; edited. Anyway, I'm betting he's interested in watching directories of around 100 files, and he can just raise a warning if the directory is bigger than expected, as I said. – abarnert May 03 '15 at 22:46
  • 1
    @KenThomases For the rest: I don't think he wants to watch a whole hierarchy, just a flat directory. And it's non-trivial to do in the first place, but the obvious way to do it is efficient enough. At any rate, what other option does he have if he wants to write code that works on FreeBSD and OS X? Either require a wrapper or gamin-style server, or kqueue up the whole directory himself. – abarnert May 03 '15 at 22:48
  • Thank you for such an awesome reply!! Yep diretory is less than 100 files typically, but it's API other users might have more, so thanks very much for this. I think I'll just tell users of the API if they want to watch a file for overwriting they should specifically add a watch for it. – Noitidart May 04 '15 at 01:28
  • 1
    @Noitidart: If you're building something for general-purpose use, you may want to look at one of the existing wrappers, which give you an `FSEvents`-like interface on top of whatever the OS provides, on everything from FreeBSD to pre-`inotify` linux to Windows, in the most efficient way possible (which includes doing things like using a daemon process and subprocesses to manage all the `kqueue` fd's, instead of making each client do it). I don't know the current state of the art for such wrappers, but it's worth looking into rather than reinventing the wheel. – abarnert May 04 '15 at 03:55