* src/dbusbind.c: Include process.h.
(dbus_fd_cb, xd_find_watch_fd, xd_toggle_watch)
(xd_read_message_1): New functions.
(xd_add_watch, xd_remove_watch): Call xd_find_watch_fd. Handle
watch for both read and write.
(Fdbus_init_bus): Also register xd_toggle_watch.
(Fdbus_call_method_asynchronously, Fdbus_method_return_internal)
(Fdbus_method_error_internal, Fdbus_send_signal): Remove call
to dbus_connection_flush.
(xd_read_message): Move most of the code to xd_read_message_1.
Call xd_read_message_1 until status is COMPLETE.
* src/keyboard.c (readable_events, gobble_input): Remove DBUS code.
* src/process.c (gpm_wait_mask, max_gpm_desc): Remove.
(write_mask): New variable.
(max_input_desc): Renamed from max_keyboard_desc.
(fd_callback_info): New variable.
(add_read_fd, delete_read_fd, add_write_fd, delete_write_fd): New
functions.
(Fmake_network_process): FD_SET write_mask.
(deactivate_process): FD_CLR write_mask.
(wait_reading_process_output): Connecting renamed to Writeok.
check_connect removed. check_write is new. Remove references to
gpm. Use Writeok/check_write unconditionally (i.e. no #ifdef
NON_BLOCKING_CONNECT) instead of Connecting.
Loop over file descriptors and call callbacks in fd_callback_info
if file descriptor is ready for I/O.
(add_gpm_wait_descriptor): Just call add_keyboard_wait_descriptor.
(delete_gpm_wait_descriptor): Just call delete_keyboard_wait_descriptor.
(keyboard_bit_set): Use max_input_desc.
(add_keyboard_wait_descriptor, delete_keyboard_wait_descriptor): Remove
#ifdef subprocesses. Use max_input_desc.
(init_process): Initialize write_mask and fd_callback_info.
* src/process.h (add_read_fd, delete_read_fd, add_write_fd)
(delete_write_fd): Declare.
+2010-09-26 Jan Djärv <jan.h.d@swipnet.se>
+
+ * process.h (add_read_fd, delete_read_fd, add_write_fd)
+ (delete_write_fd): Declare.
+
+ * process.c (gpm_wait_mask, max_gpm_desc): Remove.
+ (write_mask): New variable.
+ (max_input_desc): Renamed from max_keyboard_desc.
+ (fd_callback_info): New variable.
+ (add_read_fd, delete_read_fd, add_write_fd, delete_write_fd): New
+ functions.
+ (Fmake_network_process): FD_SET write_mask.
+ (deactivate_process): FD_CLR write_mask.
+ (wait_reading_process_output): Connecting renamed to Writeok.
+ check_connect removed. check_write is new. Remove references to
+ gpm. Use Writeok/check_write unconditionally (i.e. no #ifdef
+ NON_BLOCKING_CONNECT) instead of Connecting.
+ Loop over file descriptors and call callbacks in fd_callback_info
+ if file descriptor is ready for I/O.
+ (add_gpm_wait_descriptor): Just call add_keyboard_wait_descriptor.
+ (delete_gpm_wait_descriptor): Just call delete_keyboard_wait_descriptor.
+ (keyboard_bit_set): Use max_input_desc.
+ (add_keyboard_wait_descriptor, delete_keyboard_wait_descriptor): Remove
+ #ifdef subprocesses. Use max_input_desc.
+ (init_process): Initialize write_mask and fd_callback_info.
+
+ * keyboard.c (readable_events, gobble_input): Remove DBUS code.
+
+ * dbusbind.c: Include process.h.
+ (dbus_fd_cb, xd_find_watch_fd, xd_toggle_watch)
+ (xd_read_message_1): New functions.
+ (xd_add_watch, xd_remove_watch): Call xd_find_watch_fd. Handle
+ watch for both read and write.
+ (Fdbus_init_bus): Also register xd_toggle_watch.
+ (Fdbus_call_method_asynchronously, Fdbus_method_return_internal)
+ (Fdbus_method_error_internal, Fdbus_send_signal): Remove call
+ to dbus_connection_flush.
+ (xd_read_message): Move most of the code to xd_read_message_1.
+ Call xd_read_message_1 until status is COMPLETE.
+
2010-09-26 Dan Nicolaescu <dann@ics.uci.edu>
* term.c: Do not include sys/ioctl.h, not needed.
#include "frame.h"
#include "termhooks.h"
#include "keyboard.h"
+#include "process.h"
\f
/* Subroutines. */
return connection;
}
+/* Callback called when something is read to read ow write. */
-/* Add connection file descriptor to input_wait_mask, in order to
- let select() detect, whether a new message has been arrived. */
-dbus_bool_t
-xd_add_watch (DBusWatch *watch, void *data)
+static void
+dbus_fd_cb (int fd, void *data, int for_read)
+{
+ xd_read_queued_messages ();
+}
+
+/* Return the file descriptor for WATCH, -1 if not found. */
+
+static int
+xd_find_watch_fd (DBusWatch *watch)
{
- /* We check only for incoming data. */
- if (dbus_watch_get_flags (watch) & DBUS_WATCH_READABLE)
- {
#if HAVE_DBUS_WATCH_GET_UNIX_FD
- /* TODO: Reverse these on Win32, which prefers the opposite. */
- int fd = dbus_watch_get_unix_fd(watch);
- if (fd == -1)
- fd = dbus_watch_get_socket(watch);
+ /* TODO: Reverse these on Win32, which prefers the opposite. */
+ int fd = dbus_watch_get_unix_fd (watch);
+ if (fd == -1)
+ fd = dbus_watch_get_socket (watch);
#else
- int fd = dbus_watch_get_fd(watch);
+ int fd = dbus_watch_get_fd (watch);
#endif
- XD_DEBUG_MESSAGE ("fd %d", fd);
+ return fd;
+}
- if (fd == -1)
- return FALSE;
- /* Add the file descriptor to input_wait_mask. */
- add_keyboard_wait_descriptor (fd);
- }
+/* Start monitoring WATCH for possible I/O. */
- /* Return. */
+static dbus_bool_t
+xd_add_watch (DBusWatch *watch, void *data)
+{
+ unsigned int flags = dbus_watch_get_flags (watch);
+ int fd = xd_find_watch_fd (watch);
+
+ XD_DEBUG_MESSAGE ("fd %d, write %d, enabled %d",
+ fd, flags & DBUS_WATCH_WRITABLE,
+ dbus_watch_get_enabled (watch));
+
+ if (fd == -1)
+ return FALSE;
+
+ if (dbus_watch_get_enabled (watch))
+ {
+ if (flags & DBUS_WATCH_WRITABLE)
+ add_write_fd (fd, dbus_fd_cb, NULL);
+ if (flags & DBUS_WATCH_READABLE)
+ add_read_fd (fd, dbus_fd_cb, NULL);
+ }
return TRUE;
}
-/* Remove connection file descriptor from input_wait_mask. DATA is
- the used bus, either a string or QCdbus_system_bus or
+/* Stop monitoring WATCH for possible I/O.
+ DATA is the used bus, either a string or QCdbus_system_bus or
QCdbus_session_bus. */
-void
+
+static void
xd_remove_watch (DBusWatch *watch, void *data)
{
- /* We check only for incoming data. */
- if (dbus_watch_get_flags (watch) & DBUS_WATCH_READABLE)
- {
-#if HAVE_DBUS_WATCH_GET_UNIX_FD
- /* TODO: Reverse these on Win32, which prefers the opposite. */
- int fd = dbus_watch_get_unix_fd(watch);
- if (fd == -1)
- fd = dbus_watch_get_socket(watch);
-#else
- int fd = dbus_watch_get_fd(watch);
-#endif
- XD_DEBUG_MESSAGE ("fd %d", fd);
+ unsigned int flags = dbus_watch_get_flags (watch);
+ int fd = xd_find_watch_fd (watch);
- if (fd == -1)
- return;
+ XD_DEBUG_MESSAGE ("fd %d", fd);
+
+ if (fd == -1) return;
- /* Unset session environment. */
- if ((data != NULL) && (data == (void*) XHASH (QCdbus_session_bus)))
- {
- XD_DEBUG_MESSAGE ("unsetenv DBUS_SESSION_BUS_ADDRESS");
- unsetenv ("DBUS_SESSION_BUS_ADDRESS");
- }
- /* Remove the file descriptor from input_wait_mask. */
- delete_keyboard_wait_descriptor (fd);
+ /* Unset session environment. */
+ if (data != NULL && data == (void*) XHASH (QCdbus_session_bus))
+ {
+ XD_DEBUG_MESSAGE ("unsetenv DBUS_SESSION_BUS_ADDRESS");
+ unsetenv ("DBUS_SESSION_BUS_ADDRESS");
}
- /* Return. */
- return;
+ if (flags & DBUS_WATCH_WRITABLE)
+ delete_write_fd (fd);
+ if (flags & DBUS_WATCH_READABLE)
+ delete_read_fd (fd);
+}
+
+/* Toggle monitoring WATCH for possible I/O. */
+
+static void
+xd_toggle_watch (DBusWatch *watch, void *data)
+{
+ if (dbus_watch_get_enabled (watch))
+ xd_add_watch (watch, data);
+ else
+ xd_remove_watch (watch, data);
}
DEFUN ("dbus-init-bus", Fdbus_init_bus, Sdbus_init_bus, 1, 1, 0,
if (!dbus_connection_set_watch_functions (connection,
xd_add_watch,
xd_remove_watch,
- NULL, (void*) XHASH (bus), NULL))
+ xd_toggle_watch,
+ (void*) XHASH (bus), NULL))
XD_SIGNAL1 (build_string ("Cannot add watch functions"));
/* Add bus to list of registered buses. */
result = Qnil;
}
- /* Flush connection to ensure the message is handled. */
- dbus_connection_flush (connection);
-
XD_DEBUG_MESSAGE ("Message sent");
/* Cleanup. */
if (!dbus_connection_send (connection, dmessage, NULL))
XD_SIGNAL1 (build_string ("Cannot send message"));
- /* Flush connection to ensure the message is handled. */
- dbus_connection_flush (connection);
-
XD_DEBUG_MESSAGE ("Message sent");
/* Cleanup. */
if (!dbus_connection_send (connection, dmessage, NULL))
XD_SIGNAL1 (build_string ("Cannot send message"));
- /* Flush connection to ensure the message is handled. */
- dbus_connection_flush (connection);
-
XD_DEBUG_MESSAGE ("Message sent");
/* Cleanup. */
if (!dbus_connection_send (connection, dmessage, NULL))
XD_SIGNAL1 (build_string ("Cannot send message"));
- /* Flush connection to ensure the message is handled. */
- dbus_connection_flush (connection);
-
XD_DEBUG_MESSAGE ("Signal sent");
/* Cleanup. */
return FALSE;
}
-/* Read queued incoming message of the D-Bus BUS. BUS is either a
- Lisp symbol, :system or :session, or a string denoting the bus
- address. */
-static Lisp_Object
-xd_read_message (Lisp_Object bus)
+/* Read one queued incoming message of the D-Bus BUS.
+ BUS is either a Lisp symbol, :system or :session, or a string denoting
+ the bus address. */
+
+static void
+xd_read_message_1 (DBusConnection *connection, Lisp_Object bus)
{
Lisp_Object args, key, value;
struct gcpro gcpro1;
struct input_event event;
- DBusConnection *connection;
DBusMessage *dmessage;
DBusMessageIter iter;
unsigned int dtype;
int mtype, serial;
const char *uname, *path, *interface, *member;
- /* Open a connection to the bus. */
- connection = xd_initialize (bus, TRUE);
-
- /* Non blocking read of the next available message. */
- dbus_connection_read_write (connection, 0);
dmessage = dbus_connection_pop_message (connection);
/* Return if there is no queued message. */
if (dmessage == NULL)
- return Qnil;
+ return;
/* Collect the parameters. */
args = Qnil;
cleanup:
dbus_message_unref (dmessage);
- RETURN_UNGCPRO (Qnil);
+ UNGCPRO;
+}
+
+/* Read queued incoming messages of the D-Bus BUS.
+ BUS is either a Lisp symbol, :system or :session, or a string denoting
+ the bus address. */
+
+static Lisp_Object
+xd_read_message (Lisp_Object bus)
+{
+ /* Open a connection to the bus. */
+ DBusConnection *connection = xd_initialize (bus, TRUE);
+
+ /* Non blocking read of the next available message. */
+ dbus_connection_read_write (connection, 0);
+
+ while (dbus_connection_get_dispatch_status (connection)
+ != DBUS_DISPATCH_COMPLETE)
+ xd_read_message_1 (connection, bus);
+ return Qnil;
}
/* Read queued incoming messages from all buses. */
static int
readable_events (int flags)
{
-#ifdef HAVE_DBUS
- /* Check whether a D-Bus message has arrived. */
- if (xd_pending_messages () > 0)
- return 1;
-#endif /* HAVE_DBUS */
-
if (flags & READABLE_EVENTS_DO_TIMERS_NOW)
timer_check (1);
void
gobble_input (int expected)
{
-#ifdef HAVE_DBUS
- /* Read D-Bus messages. */
- xd_read_queued_messages ();
-#endif /* HAVE_DBUS */
-
#ifdef SIGIO
if (interrupt_input)
{
static SELECT_TYPE non_process_wait_mask;
-/* Mask for the gpm mouse input descriptor. */
+/* Mask for selecting for write. */
-static SELECT_TYPE gpm_wait_mask;
+static SELECT_TYPE write_mask;
#ifdef NON_BLOCKING_CONNECT
/* Mask of bits indicating the descriptors that we wait for connect to
/* The largest descriptor currently in use for a process object. */
static int max_process_desc;
-/* The largest descriptor currently in use for keyboard input. */
-static int max_keyboard_desc;
-
-/* The largest descriptor currently in use for gpm mouse input. */
-static int max_gpm_desc;
+/* The largest descriptor currently in use for input. */
+static int max_input_desc;
/* Indexed by descriptor, gives the process (if any) for that descriptor */
Lisp_Object chan_process[MAXDESC];
static char pty_name[24];
#endif
+\f
+
+struct fd_callback_data
+{
+ fd_callback func;
+ void *data;
+#define FOR_READ 1
+#define FOR_WRITE 2
+ int condition; /* mask of the defines above. */
+} fd_callback_info[MAXDESC];
+
+
+/* Add a file descriptor FD to be monitored for when read is possible.
+ When read is possible, call FUNC with argument DATA. */
+
+void
+add_read_fd (int fd, fd_callback func, void *data)
+{
+ xassert (fd < MAXDESC);
+ add_keyboard_wait_descriptor (fd);
+
+ fd_callback_info[fd].func = func;
+ fd_callback_info[fd].data = data;
+ fd_callback_info[fd].condition |= FOR_READ;
+}
+
+/* Stop monitoring file descriptor FD for when read is possible. */
+
+void
+delete_read_fd (int fd)
+{
+ xassert (fd < MAXDESC);
+ delete_keyboard_wait_descriptor (fd);
+
+ fd_callback_info[fd].condition &= ~FOR_READ;
+ if (fd_callback_info[fd].condition == 0)
+ {
+ fd_callback_info[fd].func = 0;
+ fd_callback_info[fd].data = 0;
+ }
+}
+
+/* Add a file descriptor FD to be monitored for when write is possible.
+ When write is possible, call FUNC with argument DATA. */
+
+void
+add_write_fd (int fd, fd_callback func, void *data)
+{
+ xassert (fd < MAXDESC);
+ FD_SET (fd, &write_mask);
+ if (fd > max_input_desc)
+ max_input_desc = fd;
+
+ fd_callback_info[fd].func = func;
+ fd_callback_info[fd].data = data;
+ fd_callback_info[fd].condition |= FOR_WRITE;
+}
+
+/* Stop monitoring file descriptor FD for when write is possible. */
+
+void
+delete_write_fd (int fd)
+{
+ int lim = max_input_desc;
+
+ xassert (fd < MAXDESC);
+ FD_CLR (fd, &write_mask);
+ fd_callback_info[fd].condition &= ~FOR_WRITE;
+ if (fd_callback_info[fd].condition == 0)
+ {
+ fd_callback_info[fd].func = 0;
+ fd_callback_info[fd].data = 0;
+
+ if (fd == max_input_desc)
+ for (fd = lim; fd >= 0; fd--)
+ if (FD_ISSET (fd, &input_wait_mask) || FD_ISSET (fd, &write_mask))
+ {
+ max_input_desc = fd;
+ break;
+ }
+
+ }
+}
+
\f
/* Compute the Lisp form of the process status, p->status, from
the numeric status that was returned by `wait'. */
if (!FD_ISSET (inch, &connect_wait_mask))
{
FD_SET (inch, &connect_wait_mask);
+ FD_SET (inch, &write_mask);
num_pending_connects++;
}
}
if (FD_ISSET (inchannel, &connect_wait_mask))
{
FD_CLR (inchannel, &connect_wait_mask);
+ FD_CLR (inchannel, &write_mask);
if (--num_pending_connects < 0)
abort ();
}
{
register int channel, nfds;
SELECT_TYPE Available;
-#ifdef NON_BLOCKING_CONNECT
- SELECT_TYPE Connecting;
- int check_connect;
-#endif
+ SELECT_TYPE Writeok;
+ int check_write;
int check_delay, no_avail;
int xerrno;
Lisp_Object proc;
int count = SPECPDL_INDEX ();
FD_ZERO (&Available);
-#ifdef NON_BLOCKING_CONNECT
- FD_ZERO (&Connecting);
-#endif
+ FD_ZERO (&Writeok);
if (time_limit == 0 && microsecs == 0 && wait_proc && !NILP (Vinhibit_quit)
&& !(CONSP (wait_proc->status) && EQ (XCAR (wait_proc->status), Qexit)))
if (update_tick != process_tick)
{
SELECT_TYPE Atemp;
-#ifdef NON_BLOCKING_CONNECT
SELECT_TYPE Ctemp;
-#endif
if (kbd_on_hold_p ())
FD_ZERO (&Atemp);
else
Atemp = input_wait_mask;
- IF_NON_BLOCKING_CONNECT (Ctemp = connect_wait_mask);
+ Ctemp = write_mask;
EMACS_SET_SECS_USECS (timeout, 0, 0);
- if ((select (max (max (max_process_desc, max_keyboard_desc),
- max_gpm_desc) + 1,
+ if ((select (max (max_process_desc, max_input_desc) + 1,
&Atemp,
#ifdef NON_BLOCKING_CONNECT
(num_pending_connects > 0 ? &Ctemp : (SELECT_TYPE *)0),
break;
FD_SET (wait_proc->infd, &Available);
check_delay = 0;
- IF_NON_BLOCKING_CONNECT (check_connect = 0);
+ check_write = 0;
}
else if (!NILP (wait_for_cell))
{
Available = non_process_wait_mask;
check_delay = 0;
- IF_NON_BLOCKING_CONNECT (check_connect = 0);
+ check_write = 0;
}
else
{
Available = non_keyboard_wait_mask;
else
Available = input_wait_mask;
- IF_NON_BLOCKING_CONNECT (check_connect = (num_pending_connects > 0));
+ Writeok = write_mask;
+ check_write = 1;
check_delay = wait_channel >= 0 ? 0 : process_output_delay_count;
}
}
else
{
-#ifdef NON_BLOCKING_CONNECT
- if (check_connect)
- Connecting = connect_wait_mask;
-#endif
#ifdef ADAPTIVE_READ_BUFFERING
/* Set the timeout for adaptive read buffering if any
#else
nfds = select
#endif
- (max (max (max_process_desc, max_keyboard_desc),
- max_gpm_desc) + 1,
- &Available,
-#ifdef NON_BLOCKING_CONNECT
- (check_connect ? &Connecting : (SELECT_TYPE *)0),
-#else
- (SELECT_TYPE *)0,
-#endif
- (SELECT_TYPE *)0, &timeout);
+ (max (max_process_desc, max_input_desc) + 1,
+ &Available,
+ (check_write ? &Writeok : (SELECT_TYPE *)0),
+ (SELECT_TYPE *)0, &timeout);
}
xerrno = errno;
if (no_avail)
{
FD_ZERO (&Available);
- IF_NON_BLOCKING_CONNECT (check_connect = 0);
+ check_write = 0;
}
#if 0 /* When polling is used, interrupt_input is 0,
if (no_avail || nfds == 0)
continue;
+ for (channel = 0; channel <= max_input_desc; ++channel)
+ {
+ struct fd_callback_data *d = &fd_callback_info[channel];
+ if (FD_ISSET (channel, &Available)
+ && d->func != 0
+ && (d->condition & FOR_READ) != 0)
+ d->func (channel, d->data, 1);
+ if (FD_ISSET (channel, &write_mask)
+ && d->func != 0
+ && (d->condition & FOR_WRITE) != 0)
+ d->func (channel, d->data, 0);
+ }
+
/* Really FIRST_PROC_DESC should be 0 on Unix,
but this is safer in the short run. */
for (channel = 0; channel <= max_process_desc; channel++)
{
if (FD_ISSET (channel, &Available)
- && FD_ISSET (channel, &non_keyboard_wait_mask))
+ && FD_ISSET (channel, &non_keyboard_wait_mask)
+ && !FD_ISSET (channel, &non_process_wait_mask))
{
int nread;
}
}
#ifdef NON_BLOCKING_CONNECT
- if (check_connect && FD_ISSET (channel, &Connecting)
+ if (FD_ISSET (channel, &Writeok)
&& FD_ISSET (channel, &connect_wait_mask))
{
struct Lisp_Process *p;
\f
-static int add_gpm_wait_descriptor_called_flag;
-
void
add_gpm_wait_descriptor (int desc)
{
- if (! add_gpm_wait_descriptor_called_flag)
- FD_CLR (0, &input_wait_mask);
- add_gpm_wait_descriptor_called_flag = 1;
- FD_SET (desc, &input_wait_mask);
- FD_SET (desc, &gpm_wait_mask);
- if (desc > max_gpm_desc)
- max_gpm_desc = desc;
+ add_keyboard_wait_descriptor (desc);
}
void
delete_gpm_wait_descriptor (int desc)
{
- int fd;
- int lim = max_gpm_desc;
-
- FD_CLR (desc, &input_wait_mask);
- FD_CLR (desc, &non_process_wait_mask);
-
- if (desc == max_gpm_desc)
- for (fd = 0; fd < lim; fd++)
- if (FD_ISSET (fd, &input_wait_mask)
- && !FD_ISSET (fd, &non_keyboard_wait_mask)
- && !FD_ISSET (fd, &non_process_wait_mask))
- max_gpm_desc = fd;
+ delete_keyboard_wait_descriptor (desc);
}
/* Return nonzero if *MASK has a bit set
{
int fd;
- for (fd = 0; fd <= max_keyboard_desc; fd++)
+ for (fd = 0; fd <= max_input_desc; fd++)
if (FD_ISSET (fd, mask) && FD_ISSET (fd, &input_wait_mask)
&& !FD_ISSET (fd, &non_keyboard_wait_mask))
return 1;
void
add_keyboard_wait_descriptor (int desc)
{
-#ifdef subprocesses
FD_SET (desc, &input_wait_mask);
FD_SET (desc, &non_process_wait_mask);
- if (desc > max_keyboard_desc)
- max_keyboard_desc = desc;
-#endif
+ if (desc > max_input_desc)
+ max_input_desc = desc;
}
/* From now on, do not expect DESC to give keyboard input. */
void
delete_keyboard_wait_descriptor (int desc)
{
-#ifdef subprocesses
int fd;
- int lim = max_keyboard_desc;
+ int lim = max_input_desc;
FD_CLR (desc, &input_wait_mask);
FD_CLR (desc, &non_process_wait_mask);
- if (desc == max_keyboard_desc)
+ if (desc == max_input_desc)
for (fd = 0; fd < lim; fd++)
- if (FD_ISSET (fd, &input_wait_mask)
- && !FD_ISSET (fd, &non_keyboard_wait_mask)
- && !FD_ISSET (fd, &gpm_wait_mask))
- max_keyboard_desc = fd;
-#endif /* subprocesses */
+ if (FD_ISSET (fd, &input_wait_mask) || FD_ISSET (fd, &write_mask))
+ max_input_desc = fd;
}
/* Setup coding systems of PROCESS. */
FD_ZERO (&input_wait_mask);
FD_ZERO (&non_keyboard_wait_mask);
FD_ZERO (&non_process_wait_mask);
+ FD_ZERO (&write_mask);
max_process_desc = 0;
+ memset (fd_callback_info, 0, sizeof (fd_callback_info));
#ifdef NON_BLOCKING_CONNECT
FD_ZERO (&connect_wait_mask);
extern void unhold_keyboard_input (void);
extern int kbd_on_hold_p (void);
+typedef void (*fd_callback)(int fd, void *data, int for_read);
+
+extern void add_read_fd (int fd, fd_callback func, void *data);
+extern void delete_read_fd (int fd);
+extern void add_write_fd (int fd, fd_callback func, void *data);
+extern void delete_write_fd (int fd);
+
/* arch-tag: dffedfc4-d7bc-4b58-a26f-c16155449c72
(do not change this comment) */