mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-27 15:52:00 -08:00
4af5981Add a comment in generated refcards about the sourceef880a5; * etc/refcards/calccard.tex: Remove obsolete comment.4887e7cjs-mode: Fix indent problem after a regexpe992ac0Fix sluggish display of symbols in UTF-8 language environment1fc101bDon't confuse how Texinfo outputs @var with the input91aa5d1* doc/lispref/display.texi (Scroll Bars): * doc/lispref/frame...f758fcd* doc/emacs/cmdargs.texi (Initial Options): Copyedit for --da...5b0cdddMore fixes in copyright notices in etc/refcards/f994c20Update copyright text in refcards9ad2ae7Fix Outline command names26c3554Send text received by bracketed paste to processdb0b58dCorrect the statement about programming modes always running ...78aece4Improve documentation of 'occur'eb364fdDo call debugger on failed cl-assert3ef4ee8Avoid infloop in python8da810fDon't refer to obsolete FEATURE-unload-hook4f478caImprove documentation of dabbrevs7272e5d* lisp/chistory.el (list-command-history): Doc fix. (Bug#24890)89b7482* lisp/simple.el (set-mark-command): Doc fix. (Bug#24890)3b199f7Improve documentation of some Help commands93d3a0eFix documentation of yes-or-no promptsaf04919Fix documentation of partial completion styleed80184Fix documentation of the mode line on emacsclient framese6be855Fix description of 'C-z' in User manual16f7007Improve and clarify documentation of Outline Mode31d93aaAdd Emacs version number to nt/README.W320b6b815Fix python-mode hideshow regexpdc152c5Modernize usage of 'macOS' in doc and comments84c5343Prefer comments /* like this */ in C codebb61e50* doc/lispref/loading.texi (Autoload): Better link (Bug#24845).3ef86fdClarify documentation of face attribute functionsde51d59; * nt/README.W32: Minor copyedits.db436e9Don't call debug on failed cl-assert # Conflicts: # doc/emacs/cmdargs.texi # etc/NEWS # etc/PROBLEMS # lisp/auth-source.el # lisp/net/tramp-sh.el
539 lines
16 KiB
C
539 lines
16 KiB
C
/* Filesystem notifications support with kqueue API.
|
||
|
||
Copyright (C) 2015-2016 Free Software Foundation, Inc.
|
||
|
||
This file is part of GNU Emacs.
|
||
|
||
GNU Emacs is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or (at
|
||
your option) any later version.
|
||
|
||
GNU Emacs is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
|
||
|
||
#include <config.h>
|
||
|
||
#ifdef HAVE_KQUEUE
|
||
#include <stdio.h>
|
||
#include <sys/types.h>
|
||
#include <sys/event.h>
|
||
#include <sys/time.h>
|
||
#include <sys/file.h>
|
||
#include "lisp.h"
|
||
#include "keyboard.h"
|
||
#include "process.h"
|
||
|
||
#ifdef HAVE_SYS_RESOURCE_H
|
||
#include <sys/resource.h>
|
||
#endif /* HAVE_SYS_RESOURCE_H */
|
||
|
||
|
||
/* File handle for kqueue. */
|
||
static int kqueuefd = -1;
|
||
|
||
/* This is a list, elements are (DESCRIPTOR FILE FLAGS CALLBACK [DIRLIST]). */
|
||
static Lisp_Object watch_list;
|
||
|
||
/* Generate a list from the directory_files_internal output.
|
||
Items are (INODE FILE-NAME LAST-MOD LAST-STATUS-MOD SIZE). */
|
||
Lisp_Object
|
||
kqueue_directory_listing (Lisp_Object directory_files)
|
||
{
|
||
Lisp_Object dl, result = Qnil;
|
||
|
||
for (dl = directory_files; ! NILP (dl); dl = XCDR (dl)) {
|
||
/* We ignore "." and "..". */
|
||
if ((strcmp (".", SSDATA (XCAR (XCAR (dl)))) == 0) ||
|
||
(strcmp ("..", SSDATA (XCAR (XCAR (dl)))) == 0))
|
||
continue;
|
||
|
||
result = Fcons
|
||
(list5 (/* inode. */
|
||
Fnth (make_number (11), XCAR (dl)),
|
||
/* filename. */
|
||
XCAR (XCAR (dl)),
|
||
/* last modification time. */
|
||
Fnth (make_number (6), XCAR (dl)),
|
||
/* last status change time. */
|
||
Fnth (make_number (7), XCAR (dl)),
|
||
/* size. */
|
||
Fnth (make_number (8), XCAR (dl))),
|
||
result);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/* Generate a file notification event. */
|
||
static void
|
||
kqueue_generate_event (Lisp_Object watch_object, Lisp_Object actions,
|
||
Lisp_Object file, Lisp_Object file1)
|
||
{
|
||
Lisp_Object flags, action, entry;
|
||
struct input_event event;
|
||
|
||
/* Check, whether all actions shall be monitored. */
|
||
flags = Fnth (make_number (2), watch_object);
|
||
action = actions;
|
||
do {
|
||
if (NILP (action))
|
||
break;
|
||
entry = XCAR (action);
|
||
if (NILP (Fmember (entry, flags))) {
|
||
action = XCDR (action);
|
||
actions = Fdelq (entry, actions);
|
||
} else
|
||
action = XCDR (action);
|
||
} while (1);
|
||
|
||
/* Store it into the input event queue. */
|
||
if (! NILP (actions)) {
|
||
EVENT_INIT (event);
|
||
event.kind = FILE_NOTIFY_EVENT;
|
||
event.frame_or_window = Qnil;
|
||
event.arg = list2 (Fcons (XCAR (watch_object),
|
||
Fcons (actions,
|
||
NILP (file1)
|
||
? Fcons (file, Qnil)
|
||
: list2 (file, file1))),
|
||
Fnth (make_number (3), watch_object));
|
||
kbd_buffer_store_event (&event);
|
||
}
|
||
}
|
||
|
||
/* This compares two directory listings in case of a `write' event for
|
||
a directory. Generate resulting file notification events. The old
|
||
directory listing is retrieved from watch_object, it will be
|
||
replaced by the new directory listing at the end of this
|
||
function. */
|
||
static void
|
||
kqueue_compare_dir_list (Lisp_Object watch_object)
|
||
{
|
||
Lisp_Object dir, pending_dl, deleted_dl;
|
||
Lisp_Object old_directory_files, old_dl, new_directory_files, new_dl, dl;
|
||
|
||
dir = XCAR (XCDR (watch_object));
|
||
pending_dl = Qnil;
|
||
deleted_dl = Qnil;
|
||
|
||
old_directory_files = Fnth (make_number (4), watch_object);
|
||
old_dl = kqueue_directory_listing (old_directory_files);
|
||
|
||
/* When the directory is not accessible anymore, it has been deleted. */
|
||
if (NILP (Ffile_directory_p (dir))) {
|
||
kqueue_generate_event (watch_object, Fcons (Qdelete, Qnil), dir, Qnil);
|
||
return;
|
||
}
|
||
new_directory_files =
|
||
directory_files_internal (dir, Qnil, Qnil, Qnil, 1, Qnil);
|
||
new_dl = kqueue_directory_listing (new_directory_files);
|
||
|
||
/* Parse through the old list. */
|
||
dl = old_dl;
|
||
while (1) {
|
||
Lisp_Object old_entry, new_entry, dl1;
|
||
if (NILP (dl))
|
||
break;
|
||
|
||
/* Search for an entry with the same inode. */
|
||
old_entry = XCAR (dl);
|
||
new_entry = assq_no_quit (XCAR (old_entry), new_dl);
|
||
if (! NILP (Fequal (old_entry, new_entry))) {
|
||
/* Both entries are identical. Nothing to do. */
|
||
new_dl = Fdelq (new_entry, new_dl);
|
||
goto the_end;
|
||
}
|
||
|
||
/* Both entries have the same inode. */
|
||
if (! NILP (new_entry)) {
|
||
/* Both entries have the same file name. */
|
||
if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
|
||
SSDATA (XCAR (XCDR (new_entry)))) == 0) {
|
||
/* Modification time has been changed, the file has been written. */
|
||
if (NILP (Fequal (Fnth (make_number (2), old_entry),
|
||
Fnth (make_number (2), new_entry))))
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (old_entry)), Qnil);
|
||
/* Status change time has been changed, the file attributes
|
||
have changed. */
|
||
if (NILP (Fequal (Fnth (make_number (3), old_entry),
|
||
Fnth (make_number (3), new_entry))))
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qattrib, Qnil),
|
||
XCAR (XCDR (old_entry)), Qnil);
|
||
|
||
} else {
|
||
/* The file has been renamed. */
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qrename, Qnil),
|
||
XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
|
||
deleted_dl = Fcons (new_entry, deleted_dl);
|
||
}
|
||
new_dl = Fdelq (new_entry, new_dl);
|
||
goto the_end;
|
||
}
|
||
|
||
/* Search, whether there is a file with the same name but another
|
||
inode. */
|
||
for (dl1 = new_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
|
||
new_entry = XCAR (dl1);
|
||
if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
|
||
SSDATA (XCAR (XCDR (new_entry)))) == 0) {
|
||
pending_dl = Fcons (new_entry, pending_dl);
|
||
new_dl = Fdelq (new_entry, new_dl);
|
||
goto the_end;
|
||
}
|
||
}
|
||
|
||
/* Check, whether this a pending file. */
|
||
new_entry = assq_no_quit (XCAR (old_entry), pending_dl);
|
||
|
||
if (NILP (new_entry)) {
|
||
/* Check, whether this is an already deleted file (by rename). */
|
||
for (dl1 = deleted_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
|
||
new_entry = XCAR (dl1);
|
||
if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
|
||
SSDATA (XCAR (XCDR (new_entry)))) == 0) {
|
||
deleted_dl = Fdelq (new_entry, deleted_dl);
|
||
goto the_end;
|
||
}
|
||
}
|
||
/* The file has been deleted. */
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qdelete, Qnil), XCAR (XCDR (old_entry)), Qnil);
|
||
|
||
} else {
|
||
/* The file has been renamed. */
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qrename, Qnil),
|
||
XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)));
|
||
pending_dl = Fdelq (new_entry, pending_dl);
|
||
}
|
||
|
||
the_end:
|
||
dl = XCDR (dl);
|
||
old_dl = Fdelq (old_entry, old_dl);
|
||
}
|
||
|
||
/* Parse through the resulting new list. */
|
||
dl = new_dl;
|
||
while (1) {
|
||
Lisp_Object entry;
|
||
if (NILP (dl))
|
||
break;
|
||
|
||
/* A new file has appeared. */
|
||
entry = XCAR (dl);
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qcreate, Qnil), XCAR (XCDR (entry)), Qnil);
|
||
|
||
/* Check size of that file. */
|
||
Lisp_Object size = Fnth (make_number (4), entry);
|
||
if (FLOATP (size) || (XINT (size) > 0))
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
|
||
|
||
dl = XCDR (dl);
|
||
new_dl = Fdelq (entry, new_dl);
|
||
}
|
||
|
||
/* Parse through the resulting pending_dl list. */
|
||
dl = pending_dl;
|
||
while (1) {
|
||
Lisp_Object entry;
|
||
if (NILP (dl))
|
||
break;
|
||
|
||
/* A file is still pending. Assume it was a write. */
|
||
entry = XCAR (dl);
|
||
kqueue_generate_event
|
||
(watch_object, Fcons (Qwrite, Qnil), XCAR (XCDR (entry)), Qnil);
|
||
|
||
dl = XCDR (dl);
|
||
pending_dl = Fdelq (entry, pending_dl);
|
||
}
|
||
|
||
/* At this point, old_dl, new_dl and pending_dl shall be empty.
|
||
deleted_dl might not be empty when there was a rename to a
|
||
nonexistent file. Let's make a check for this (might be removed
|
||
once the code is stable). */
|
||
if (! NILP (old_dl))
|
||
report_file_error ("Old list not empty", old_dl);
|
||
if (! NILP (new_dl))
|
||
report_file_error ("New list not empty", new_dl);
|
||
if (! NILP (pending_dl))
|
||
report_file_error ("Pending events list not empty", pending_dl);
|
||
|
||
/* Replace old directory listing with the new one. */
|
||
XSETCDR (Fnthcdr (make_number (3), watch_object),
|
||
Fcons (new_directory_files, Qnil));
|
||
return;
|
||
}
|
||
|
||
/* This is the callback function for arriving input on kqueuefd. It
|
||
shall create a Lisp event, and put it into the Emacs input queue. */
|
||
static void
|
||
kqueue_callback (int fd, void *data)
|
||
{
|
||
for (;;) {
|
||
struct kevent kev;
|
||
static const struct timespec nullts = { 0, 0 };
|
||
Lisp_Object descriptor, watch_object, file, actions;
|
||
|
||
/* Read one event. */
|
||
int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts);
|
||
if (ret < 1) {
|
||
/* All events read. */
|
||
return;
|
||
}
|
||
|
||
/* Determine descriptor and file name. */
|
||
descriptor = make_number (kev.ident);
|
||
watch_object = assq_no_quit (descriptor, watch_list);
|
||
if (CONSP (watch_object))
|
||
file = XCAR (XCDR (watch_object));
|
||
else
|
||
continue;
|
||
|
||
/* Determine event actions. */
|
||
actions = Qnil;
|
||
if (kev.fflags & NOTE_DELETE)
|
||
actions = Fcons (Qdelete, actions);
|
||
if (kev.fflags & NOTE_WRITE) {
|
||
/* Check, whether this is a directory event. */
|
||
if (NILP (Fnth (make_number (4), watch_object)))
|
||
actions = Fcons (Qwrite, actions);
|
||
else
|
||
kqueue_compare_dir_list (watch_object);
|
||
}
|
||
if (kev.fflags & NOTE_EXTEND)
|
||
actions = Fcons (Qextend, actions);
|
||
if (kev.fflags & NOTE_ATTRIB)
|
||
actions = Fcons (Qattrib, actions);
|
||
if (kev.fflags & NOTE_LINK)
|
||
actions = Fcons (Qlink, actions);
|
||
/* It would be useful to know the target of the rename operation.
|
||
At this point, it is not possible. Happens only when the upper
|
||
directory is monitored. */
|
||
if (kev.fflags & NOTE_RENAME)
|
||
actions = Fcons (Qrename, actions);
|
||
|
||
/* Create the event. */
|
||
if (! NILP (actions))
|
||
kqueue_generate_event (watch_object, actions, file, Qnil);
|
||
|
||
/* Cancel monitor if file or directory is deleted or renamed. */
|
||
if (kev.fflags & (NOTE_DELETE | NOTE_RENAME))
|
||
Fkqueue_rm_watch (descriptor);
|
||
}
|
||
return;
|
||
}
|
||
|
||
DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0,
|
||
doc: /* Add a watch for filesystem events pertaining to FILE.
|
||
|
||
This arranges for filesystem events pertaining to FILE to be reported
|
||
to Emacs. Use `kqueue-rm-watch' to cancel the watch.
|
||
|
||
Returned value is a descriptor for the added watch. If the file cannot be
|
||
watched for some reason, this function signals a `file-notify-error' error.
|
||
|
||
FLAGS is a list of events to be watched for. It can include the
|
||
following symbols:
|
||
|
||
`create' -- FILE was created
|
||
`delete' -- FILE was deleted
|
||
`write' -- FILE has changed
|
||
`extend' -- FILE was extended
|
||
`attrib' -- a FILE attribute was changed
|
||
`link' -- a FILE's link count was changed
|
||
`rename' -- FILE was moved to FILE1
|
||
|
||
When any event happens, Emacs will call the CALLBACK function passing
|
||
it a single argument EVENT, which is of the form
|
||
|
||
(DESCRIPTOR ACTIONS FILE [FILE1])
|
||
|
||
DESCRIPTOR is the same object as the one returned by this function.
|
||
ACTIONS is a list of events.
|
||
|
||
FILE is the name of the file whose event is being reported. FILE1
|
||
will be reported only in case of the `rename' event. This is possible
|
||
only when the upper directory of the renamed file is watched. */)
|
||
(Lisp_Object file, Lisp_Object flags, Lisp_Object callback)
|
||
{
|
||
Lisp_Object watch_object, dir_list;
|
||
int maxfd, fd, oflags;
|
||
u_short fflags = 0;
|
||
struct kevent kev;
|
||
#ifdef HAVE_GETRLIMIT
|
||
struct rlimit rlim;
|
||
#endif /* HAVE_GETRLIMIT */
|
||
|
||
/* Check parameters. */
|
||
CHECK_STRING (file);
|
||
file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
|
||
if (NILP (Ffile_exists_p (file)))
|
||
report_file_error ("File does not exist", file);
|
||
|
||
CHECK_LIST (flags);
|
||
|
||
if (! FUNCTIONP (callback))
|
||
wrong_type_argument (Qinvalid_function, callback);
|
||
|
||
/* Check available file descriptors. */
|
||
#ifdef HAVE_GETRLIMIT
|
||
if (! getrlimit (RLIMIT_NOFILE, &rlim))
|
||
maxfd = rlim.rlim_cur;
|
||
else
|
||
#endif /* HAVE_GETRLIMIT */
|
||
maxfd = 256;
|
||
|
||
/* We assume 50 file descriptors are sufficient for the rest of Emacs. */
|
||
if ((maxfd - 50) < XINT (Flength (watch_list)))
|
||
xsignal2
|
||
(Qfile_notify_error,
|
||
build_string ("File watching not possible, no file descriptor left"),
|
||
Flength (watch_list));
|
||
|
||
if (kqueuefd < 0)
|
||
{
|
||
/* Create kqueue descriptor. */
|
||
kqueuefd = kqueue ();
|
||
if (kqueuefd < 0)
|
||
report_file_notify_error ("File watching is not available", Qnil);
|
||
|
||
/* Start monitoring for possible I/O. */
|
||
add_read_fd (kqueuefd, kqueue_callback, NULL);
|
||
|
||
watch_list = Qnil;
|
||
}
|
||
|
||
/* Open file. */
|
||
file = ENCODE_FILE (file);
|
||
oflags = O_NONBLOCK;
|
||
#if O_EVTONLY
|
||
oflags |= O_EVTONLY;
|
||
#else
|
||
oflags |= O_RDONLY;
|
||
#endif
|
||
#if O_SYMLINK
|
||
oflags |= O_SYMLINK;
|
||
#else
|
||
oflags |= O_NOFOLLOW;
|
||
#endif
|
||
fd = emacs_open (SSDATA (file), oflags, 0);
|
||
if (fd == -1)
|
||
report_file_error ("File cannot be opened", file);
|
||
|
||
/* Assemble filter flags */
|
||
if (! NILP (Fmember (Qdelete, flags))) fflags |= NOTE_DELETE;
|
||
if (! NILP (Fmember (Qwrite, flags))) fflags |= NOTE_WRITE;
|
||
if (! NILP (Fmember (Qextend, flags))) fflags |= NOTE_EXTEND;
|
||
if (! NILP (Fmember (Qattrib, flags))) fflags |= NOTE_ATTRIB;
|
||
if (! NILP (Fmember (Qlink, flags))) fflags |= NOTE_LINK;
|
||
if (! NILP (Fmember (Qrename, flags))) fflags |= NOTE_RENAME;
|
||
|
||
/* Register event. */
|
||
EV_SET (&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
|
||
fflags, 0, NULL);
|
||
|
||
if (kevent (kqueuefd, &kev, 1, NULL, 0, NULL) < 0) {
|
||
emacs_close (fd);
|
||
report_file_error ("Cannot watch file", file);
|
||
}
|
||
|
||
/* Store watch object in watch list. */
|
||
Lisp_Object watch_descriptor = make_number (fd);
|
||
if (NILP (Ffile_directory_p (file)))
|
||
watch_object = list4 (watch_descriptor, file, flags, callback);
|
||
else {
|
||
dir_list = directory_files_internal (file, Qnil, Qnil, Qnil, 1, Qnil);
|
||
watch_object = list5 (watch_descriptor, file, flags, callback, dir_list);
|
||
}
|
||
watch_list = Fcons (watch_object, watch_list);
|
||
|
||
return watch_descriptor;
|
||
}
|
||
|
||
DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch, Skqueue_rm_watch, 1, 1, 0,
|
||
doc: /* Remove an existing WATCH-DESCRIPTOR.
|
||
|
||
WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
|
||
(Lisp_Object watch_descriptor)
|
||
{
|
||
Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list);
|
||
|
||
if (! CONSP (watch_object))
|
||
xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"),
|
||
watch_descriptor);
|
||
|
||
eassert (INTEGERP (watch_descriptor));
|
||
int fd = XINT (watch_descriptor);
|
||
if ( fd >= 0)
|
||
emacs_close (fd);
|
||
|
||
/* Remove watch descriptor from watch list. */
|
||
watch_list = Fdelq (watch_object, watch_list);
|
||
|
||
if (NILP (watch_list) && (kqueuefd >= 0)) {
|
||
delete_read_fd (kqueuefd);
|
||
emacs_close (kqueuefd);
|
||
kqueuefd = -1;
|
||
}
|
||
|
||
return Qt;
|
||
}
|
||
|
||
DEFUN ("kqueue-valid-p", Fkqueue_valid_p, Skqueue_valid_p, 1, 1, 0,
|
||
doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
|
||
|
||
WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
|
||
|
||
A watch can become invalid if the file or directory it watches is
|
||
deleted, or if the watcher thread exits abnormally for any other
|
||
reason. Removing the watch by calling `kqueue-rm-watch' also makes it
|
||
invalid. */)
|
||
(Lisp_Object watch_descriptor)
|
||
{
|
||
return NILP (assq_no_quit (watch_descriptor, watch_list)) ? Qnil : Qt;
|
||
}
|
||
|
||
|
||
void
|
||
globals_of_kqueue (void)
|
||
{
|
||
watch_list = Qnil;
|
||
}
|
||
|
||
void
|
||
syms_of_kqueue (void)
|
||
{
|
||
defsubr (&Skqueue_add_watch);
|
||
defsubr (&Skqueue_rm_watch);
|
||
defsubr (&Skqueue_valid_p);
|
||
|
||
/* Event types. */
|
||
DEFSYM (Qcreate, "create");
|
||
DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */
|
||
DEFSYM (Qwrite, "write"); /* NOTE_WRITE */
|
||
DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */
|
||
DEFSYM (Qattrib, "attrib"); /* NOTE_ATTRIB */
|
||
DEFSYM (Qlink, "link"); /* NOTE_LINK */
|
||
DEFSYM (Qrename, "rename"); /* NOTE_RENAME */
|
||
|
||
staticpro (&watch_list);
|
||
|
||
Fprovide (intern_c_string ("kqueue"), Qnil);
|
||
}
|
||
|
||
#endif /* HAVE_KQUEUE */
|
||
|
||
/* PROBLEMS
|
||
* https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
|
||
prevents tests on Ubuntu. */
|