Add a basic curses interface. Much more work needed here.
authorRoy Marples <roy@marples.name>
Thu, 5 Mar 2015 15:42:44 +0000 (15:42 +0000)
committerRoy Marples <roy@marples.name>
Thu, 5 Mar 2015 15:42:44 +0000 (15:42 +0000)
src/dhcpcd-curses/Makefile [new file with mode: 0644]
src/dhcpcd-curses/dhcpcd-curses.8 [new file with mode: 0644]
src/dhcpcd-curses/dhcpcd-curses.c [new file with mode: 0644]
src/dhcpcd-curses/dhcpcd-curses.h [new file with mode: 0644]
src/dhcpcd-curses/eloop.c [new file with mode: 0644]
src/dhcpcd-curses/eloop.h [new file with mode: 0644]
src/dhcpcd-curses/queue.h [new file with mode: 0644]

diff --git a/src/dhcpcd-curses/Makefile b/src/dhcpcd-curses/Makefile
new file mode 100644 (file)
index 0000000..8e117e3
--- /dev/null
@@ -0,0 +1,16 @@
+PROG=          dhcpcd-curses
+SRCS=          dhcpcd-curses.c eloop.c
+
+TOPDIR=                ../..
+include ${TOPDIR}/iconfig.mk
+
+MAN8=          dhcpcd-curses.8
+
+CPPFLAGS+=     -I${TOPDIR} ${CURSES_CPPFLAGS}
+LDADD+=                ${LIB_DHCPCD} ${LIB_CURSES} ${LIB_INTL}
+
+.PHONY: dhcpcd-curses
+
+include ../libdhcpcd/Makefile.inc
+
+include ${MKDIR}/prog.mk
diff --git a/src/dhcpcd-curses/dhcpcd-curses.8 b/src/dhcpcd-curses/dhcpcd-curses.8
new file mode 100644 (file)
index 0000000..523aa1a
--- /dev/null
@@ -0,0 +1,68 @@
+.\" Copyright (c) 2009-2015 Roy Marples
+.\" All rights reserved
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd January 7, 2015
+.Dt DHCPCD-CURSES 8
+.Os
+.Sh NAME
+.Nm dhcpcd-curses
+.Nd a curses frontend for network configuration
+.Sh DESCRIPTION
+.Nm
+is a curses frontend for network configuration.
+It uses
+.Xr dhcpcd 8
+and
+.Xr wpa_supplicant 8
+as backends.
+.Pp
+If
+.Nm
+is used to make configuration changes then the user needs to be able
+to write to the privileged
+.Nm dhcpcd
+control socket.
+See
+.Xr dhcpcd.conf 5
+for details.
+.Ss wpa_supplicant
+.Nm
+relies on
+.Xr wpa_supplicant 8
+being configured to write its sockets to
+.Pa /var/run/wpa_supplicant.
+If 
+.Nm
+is used to select and set pass phrases for wireless networks then
+update_config=1
+needs to be set in wpa_supplicant.conf.
+.Sh SEE ALSO
+.Xr dhcpcd 8 ,
+.Xr dhcpcd.conf 5 ,
+.Xr wpa_supplicant 8 ,
+.Xr wpa_supplicant.conf 5
+.Sh AUTHORS
+.An Roy Marples Aq roy@marples.name
+.Sh BUGS
+Please report them to http://roy.marples.name/projects/dhcpcd-ui
diff --git a/src/dhcpcd-curses/dhcpcd-curses.c b/src/dhcpcd-curses/dhcpcd-curses.c
new file mode 100644 (file)
index 0000000..332cf11
--- /dev/null
@@ -0,0 +1,564 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/ioctl.h>
+
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "dhcpcd-curses.h"
+
+#ifdef HAVE_NC_FREE_AND_EXIT
+       void _nc_free_and_exit(void);
+#endif
+
+const int handle_sigs[] = {
+       SIGHUP,
+       SIGINT,
+       SIGPIPE,
+       SIGTERM,
+       SIGWINCH,
+       0
+};
+
+/* Handling signals needs *some* context */
+static struct ctx *_ctx;
+
+static void
+set_status(struct ctx *ctx, const char *status)
+{
+       int h, w;
+
+       getmaxyx(ctx->win_status, h, w);
+       w -= strlen(status);
+       mvwprintw(ctx->win_status, 0, w, "%s", status);
+       wrefresh(ctx->win_status);
+}
+
+static int
+set_summary(struct ctx *ctx, const char *msg)
+{
+       int r;
+
+       wclear(ctx->win_summary);
+       r = wprintw(ctx->win_summary, "%s", msg);
+       wrefresh(ctx->win_summary);
+       return r;
+}
+
+static int
+debug(struct ctx *ctx, const char *fmt, ...)
+{
+       va_list args;
+       int r;
+
+       if (ctx->win_debug == NULL)
+               return 0;
+       waddch(ctx->win_debug, '\n');
+       va_start(args, fmt);
+       r = vwprintw(ctx->win_debug, fmt, args);
+       va_end(args);
+       wrefresh(ctx->win_debug);
+       return r;
+}
+
+static int
+warning(struct ctx *ctx, const char *fmt, ...)
+{
+       va_list args;
+       int r;
+
+       if (ctx->win_debug == NULL)
+               return 0;
+       waddch(ctx->win_debug, '\n');
+       va_start(args, fmt);
+       r = vwprintw(ctx->win_debug, fmt, args);
+       va_end(args);
+       wrefresh(ctx->win_debug);
+       return r;
+}
+
+static int
+notify(struct ctx *ctx, const char *fmt, ...)
+{
+       va_list args;
+       int r;
+
+       if (ctx->win_debug == NULL)
+               return 0;
+       waddch(ctx->win_debug, '\n');
+       va_start(args, fmt);
+       r = vwprintw(ctx->win_debug, fmt, args);
+       va_end(args);
+       wrefresh(ctx->win_debug);
+       return r;
+}
+
+static void
+update_online(struct ctx *ctx, bool show_if)
+{
+       bool online, carrier;
+       char *msg, *msgs, *nmsg;
+       size_t msgs_len, mlen;
+       DHCPCD_IF *ifs, *i;
+
+       online = carrier = false;
+       msgs = NULL;
+       msgs_len = 0;
+       ifs = dhcpcd_interfaces(ctx->con);
+       for (i = ifs; i; i = i->next) {
+               if (strcmp(i->type, "link") == 0) {
+                       if (i->up)
+                               carrier = true;
+               } else {
+                       if (i->up)
+                               online = true;
+               }
+               msg = dhcpcd_if_message(i, NULL);
+               if (msg) {
+                       if (show_if) {
+                               if (i->up)
+                                       notify(ctx, "%s", msg);
+                               else
+                                       warning(ctx, "%s", msg);
+                       }
+                       if (msgs == NULL) {
+                               msgs = msg;
+                               msgs_len = strlen(msgs) + 1;
+                       } else {
+                               mlen = strlen(msg) + 1;
+                               nmsg = realloc(msgs, msgs_len + mlen);
+                               if (nmsg) {
+                                       msgs = nmsg;
+                                       msgs[msgs_len - 1] = '\n';
+                                       memcpy(msgs + msgs_len, msg, mlen);
+                                       msgs_len += mlen;
+                               } else
+                                       warn("realloc");
+                               free(msg);
+                       }
+               } else if (show_if) {
+                       if (i->up)
+                               notify(ctx, "%s: %s", i->ifname, i->reason);
+                       else
+                               warning(ctx, "%s: %s", i->ifname, i->reason);
+               }
+       }
+
+       set_summary(ctx, msgs);
+       free(msgs);
+}
+
+static void
+dispatch(void *arg)
+{
+       struct ctx *ctx = arg;
+
+       dhcpcd_dispatch(ctx->con);
+}
+
+static void
+try_open(void *arg)
+{
+       struct ctx *ctx = arg;
+       static int last_error;
+
+       ctx->fd = dhcpcd_open(ctx->con, true);
+       if (ctx->fd == -1) {
+               if (errno == EACCES || errno == EPERM) {
+                       ctx->fd = dhcpcd_open(ctx->con, false);
+                       if (ctx->fd != -1)
+                               goto unprived;
+               }
+               if (errno != last_error) {
+                       last_error = errno;
+                       set_status(ctx, strerror(errno));
+               }
+               eloop_timeout_add_sec(ctx->eloop, DHCPCD_RETRYOPEN,
+                   try_open, ctx);
+               return;
+       }
+
+unprived:
+       /* Start listening to WPA events */
+       dhcpcd_wpa_start(ctx->con);
+
+       eloop_event_add(ctx->eloop, ctx->fd, dispatch, ctx, NULL, NULL);
+}
+
+static void
+status_cb(DHCPCD_CONNECTION *con, const char *status, void *arg)
+{
+       struct ctx *ctx = arg;
+
+       debug(ctx, _("Status changed to %s"), status);
+       set_status(ctx, status);
+
+       if (strcmp(status, "down") != 0) {
+               bool refresh;
+
+               if (ctx->last_status == NULL || strcmp(ctx->last_status, "down") == 0) {
+                       debug(ctx, _("Connected to dhcpcd-%s"), dhcpcd_version(con));
+                       refresh = true;
+               } else
+                       refresh = strcmp(ctx->last_status, "opened") ? false : true;
+               update_online(ctx, refresh);
+       }
+
+       free(ctx->last_status);
+       ctx->last_status = strdup(status);
+
+       if (strcmp(status, "down") == 0) {
+               ctx->online = ctx->carrier = false;
+               eloop_timeout_add_sec(ctx->eloop, DHCPCD_RETRYOPEN,
+                   try_open, ctx);
+               return;
+       }
+}
+
+static void
+if_cb(DHCPCD_IF *i, void *arg)
+{
+       struct ctx *ctx = arg;
+
+       if (strcmp(i->reason, "RENEW") &&
+           strcmp(i->reason, "STOP") &&
+           strcmp(i->reason, "STOPPED"))
+       {
+               char *msg;
+               bool new_msg;
+
+               msg = dhcpcd_if_message(i, &new_msg);
+               if (msg) {
+                       if (i->up)
+                               warning(ctx, msg);
+                       else
+                               notify(ctx, msg);
+                       free(msg);
+               }
+       }
+
+       update_online(ctx, false);
+
+       if (i->wireless) {
+               /* PROCESS SCANS */
+       }
+}
+
+static void
+wpa_dispatch(void *arg)
+{
+       DHCPCD_WPA *wpa = arg;
+
+       dhcpcd_wpa_dispatch(wpa);
+}
+
+
+static void
+wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
+{
+       struct ctx *ctx = arg;
+       DHCPCD_IF *i;
+       WI_SCAN *wi;
+       DHCPCD_WI_SCAN *scans, *s1, *s2;
+       int fd, lerrno;
+
+       /* This could be a new WPA so watch it */
+       if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
+               debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
+               return;
+       }
+       eloop_event_add(ctx->eloop,
+           dhcpcd_wpa_get_fd(wpa), wpa_dispatch, wpa, NULL, NULL);
+
+       i = dhcpcd_wpa_if(wpa);
+       if (i == NULL) {
+               debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
+               return;
+       }
+       debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
+       lerrno = errno;
+       errno = 0;
+       scans = dhcpcd_wi_scans(i);
+       if (scans == NULL && errno)
+               debug(ctx, "%s: %s", i->ifname, strerror(errno));
+       errno = lerrno;
+       TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
+               if (wi->interface == i)
+                       break;
+       }
+       if (wi == NULL) {
+               wi = malloc(sizeof(*wi));
+               wi->interface = i;
+               wi->scans = scans;
+               TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
+       } else {
+               const char *title;
+               char *msgs, *nmsg;
+               size_t msgs_len, mlen;
+
+               title = NULL;
+               msgs = NULL;
+               for (s1 = scans; s1; s1 = s1->next) {
+                       for (s2 = wi->scans; s2; s2 = s2->next)
+                               if (strcmp(s1->ssid, s2->ssid) == 0)
+                                       break;
+                       if (s2 == NULL) {
+                               if (msgs == NULL) {
+                                       msgs = strdup(s1->ssid);
+                                       msgs_len = strlen(msgs) + 1;
+                               } else {
+                                       if (title == NULL)
+                                               title = _("New Access Points");
+                                       mlen = strlen(s1->ssid) + 1;
+                                       nmsg = realloc(msgs, msgs_len + mlen);
+                                       if (nmsg) {
+                                               msgs = nmsg;
+                                               msgs[msgs_len - 1] = '\n';
+                                               memcpy(msgs + msgs_len,
+                                                   s1->ssid, mlen);
+                                               msgs_len += mlen;
+                                       } else
+                                               warn("realloc");
+                               }
+                       }
+               }
+               if (msgs) {
+                       if (title == NULL)
+                               title = _("New Access Point");
+                       mlen = strlen(title) + 1;
+                       nmsg = realloc(msgs, msgs_len + mlen);
+                       if (nmsg) {
+                               msgs = nmsg;
+                               memmove(msgs + mlen, msgs, msgs_len);
+                               memcpy(msgs, title, mlen);
+                               msgs[mlen - 1] = '\n';
+                       } else
+                               warn("realloc");
+                       notify(ctx, "%s", msgs);
+                       free(msgs);
+               }
+
+               dhcpcd_wi_scans_free(wi->scans);
+               wi->scans = scans;
+       }
+}
+
+static void
+wpa_status_cb(DHCPCD_WPA *wpa, const char *status, void *arg)
+{
+       struct ctx *ctx = arg;
+       DHCPCD_IF *i;
+       WI_SCAN *w, *wn;
+
+       i = dhcpcd_wpa_if(wpa);
+       debug(ctx, _("%s: WPA status %s"), i->ifname, status);
+       if (strcmp(status, "down") == 0) {
+               eloop_event_delete(ctx->eloop, dhcpcd_wpa_get_fd(wpa), 0);
+               TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
+                       if (w->interface == i) {
+                               TAILQ_REMOVE(&ctx->wi_scans, w, next);
+                               dhcpcd_wi_scans_free(w->scans);
+                               free(w);
+                       }
+               }
+       }
+}
+
+#ifdef BG_SCAN
+static void
+bg_scan(void *arg)
+{
+       struct ctx *ctx = arg;
+       WI_SCAN *w;
+       DHCPCD_WPA *wpa;
+
+       TAILQ_FOREACH(w, &ctx->wi_scans, next) {
+               if (w->interface->wireless && w->interface->up) {
+                       wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
+                       if (wpa)
+                               dhcpcd_wpa_scan(wpa);
+               }
+       }
+
+       /* DHCPCD_WPA_SCAN_SHORT is in milliseconds */
+       eloop_timeout_add_sec(ctx->eloop,
+            DHCPCD_WPA_SCAN_SHORT / 1000, bg_scan, ctx);
+}
+#endif
+
+static void
+signal_handler(int sig)
+{
+       struct winsize ws;
+
+       switch(sig) {
+       case SIGWINCH:
+               if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
+                       resizeterm(ws.ws_row, ws.ws_col);
+               break;
+       case SIGINT:
+               debug(_ctx, _("SIGINT caught, exiting"));
+               eloop_exit(_ctx->eloop, EXIT_FAILURE);
+               break;
+       case SIGTERM:
+               debug(_ctx, _("SIGTERM caught, exiting"));
+               eloop_exit(_ctx->eloop, EXIT_FAILURE);
+               break;
+       case SIGHUP:
+               debug(_ctx, _("SIGHUP caught, ignoring"));
+               break;
+       case SIGPIPE:
+               debug(_ctx, _("SIGPIPE caught, ignoring"));
+               break;
+       }
+}
+
+static int
+setup_signals()
+{
+       struct sigaction sa;
+       int i;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = signal_handler;
+
+       for (i = 0; handle_sigs[i]; i++) {
+               if (sigaction(handle_sigs[i], &sa, NULL) == -1)
+                       return -1;
+       }
+       return 0;
+}
+
+static int
+create_windows(struct ctx *ctx)
+{
+       int h, w;
+
+       getmaxyx(ctx->stdscr, h, w);
+
+       if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
+               return -1;
+
+       if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
+               return -1;
+       box(ctx->win_summary_border, 0, 0);
+       mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
+           _("Connection Summary"));
+       wrefresh(ctx->win_summary_border);
+       if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
+               return -1;
+       scrollok(ctx->win_summary, TRUE);
+
+#if 1
+       if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
+               return -1;
+       box(ctx->win_debug_border, 0, 0);
+       mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
+           _("Event Log"));
+       wrefresh(ctx->win_debug_border);
+       if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
+               return -1;
+       scrollok(ctx->win_debug, TRUE);
+#endif
+       return 0;
+}
+
+int
+main(void)
+{
+       struct ctx ctx;
+       WI_SCAN *wi;
+
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.fd = -1;
+       TAILQ_INIT(&ctx.wi_scans);
+       _ctx = &ctx;
+
+       if (setup_signals() == -1)
+               err(EXIT_FAILURE, "setup_signals");
+
+       if ((ctx.con = dhcpcd_new()) == NULL)
+               err(EXIT_FAILURE, "dhcpcd_new");
+
+       if ((ctx.eloop = eloop_init()) == NULL)
+               err(EXIT_FAILURE, "malloc");
+       
+       if ((ctx.stdscr = initscr()) == NULL)
+               err(EXIT_FAILURE, "initscr");
+
+       if (create_windows(&ctx) == -1)
+               err(EXIT_FAILURE, "create_windows");
+
+       curs_set(0);
+       noecho();
+       keypad(ctx.stdscr, TRUE);
+
+       wprintw(ctx.win_status, "%s %s", _("dhcpcd Curses Interface"), VERSION);
+       dhcpcd_set_progname(ctx.con, "dhcpcd-curses");
+       dhcpcd_set_status_callback(ctx.con, status_cb, &ctx);
+       dhcpcd_set_if_callback(ctx.con, if_cb, &ctx);
+       dhcpcd_wpa_set_scan_callback(ctx.con, wpa_scan_cb, &ctx);
+       dhcpcd_wpa_set_status_callback(ctx.con, wpa_status_cb, &ctx);
+
+       eloop_timeout_add_sec(ctx.eloop, 0, try_open, &ctx);
+#ifdef BG_SCAN
+       /* DHCPCD_WPA_SCAN_SHORT is in milliseconds */
+       eloop_timeout_add_sec(ctx.eloop,
+           DHCPCD_WPA_SCAN_SHORT / 1000, bg_scan, &ctx);
+#endif
+       eloop_start(ctx.eloop);
+
+       /* Close the dhcpcd connection first incase any callbacks trigger */
+       dhcpcd_close(ctx.con);
+       dhcpcd_free(ctx.con);
+
+       /* Free our saved scans */
+       while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
+               TAILQ_REMOVE(&ctx.wi_scans, wi, next);
+               dhcpcd_wi_scans_free(wi->scans);
+               free(wi);
+       }
+
+       /* Free everything else */
+       eloop_free(ctx.eloop);
+       endwin();
+       free(ctx.last_status);
+
+#ifdef HAVE_NC_FREE_AND_EXIT
+       /* undefined ncurses function to allow valgrind debugging */
+       _nc_free_and_exit();
+#endif
+
+       return EXIT_SUCCESS;
+}
diff --git a/src/dhcpcd-curses/dhcpcd-curses.h b/src/dhcpcd-curses/dhcpcd-curses.h
new file mode 100644 (file)
index 0000000..2568299
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef DHCPCD_CURSES_H
+#define DHCPCD_CURSES_H
+
+#ifdef HAS_GETTEXT
+#include <libintl.h>
+#define _ gettext
+#else
+#define _(a) (a)
+#endif
+
+#include <curses.h>
+
+#include "config.h"
+#include "dhcpcd.h"
+#include "eloop.h"
+#include "queue.h"
+
+typedef struct wi_scan {
+       TAILQ_ENTRY(wi_scan) next;
+       DHCPCD_IF *interface;
+       DHCPCD_WI_SCAN *scans;
+} WI_SCAN;
+typedef TAILQ_HEAD(wi_scan_head, wi_scan) WI_SCANS;
+
+struct ctx {
+       ELOOP_CTX *eloop;
+       DHCPCD_CONNECTION *con;
+       int fd;
+       bool online;
+       bool carrier;
+       char *last_status;
+       WI_SCANS wi_scans;
+
+       WINDOW *stdscr;
+       WINDOW *win_status;
+       WINDOW *win_debug;
+       WINDOW *win_debug_border;
+       WINDOW *win_summary;
+       WINDOW *win_summary_border;
+};
+
+#endif
diff --git a/src/dhcpcd-curses/eloop.c b/src/dhcpcd-curses/eloop.c
new file mode 100644 (file)
index 0000000..b32a8cf
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/time.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#define IN_ELOOP
+
+#include "config.h"
+#include "eloop.h"
+
+#ifndef TIMEVAL_TO_TIMESPEC
+#define        TIMEVAL_TO_TIMESPEC(tv, ts) do {                                \
+       (ts)->tv_sec = (tv)->tv_sec;                                    \
+       (ts)->tv_nsec = (tv)->tv_usec * 1000;                           \
+} while (0 /* CONSTCOND */)
+#endif
+
+/* Handy function to get the time.
+ * We only care about time advancements, not the actual time itself
+ * Which is why we use CLOCK_MONOTONIC, but it is not available on all
+ * platforms.
+ */
+#define NO_MONOTONIC "host does not support a monotonic clock - timing can skew"
+static int
+get_monotonic(struct timeval *tp)
+{
+#if defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC)
+       struct timespec ts;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+               tp->tv_sec = ts.tv_sec;
+               tp->tv_usec = (suseconds_t)(ts.tv_nsec / 1000);
+               return 0;
+       }
+#elif defined(__APPLE__)
+#define NSEC_PER_SEC 1000000000
+       /* We can use mach kernel functions here.
+        * This is crap though - why can't they implement clock_gettime?*/
+       static struct mach_timebase_info info = { 0, 0 };
+       static double factor = 0.0;
+       uint64_t nano;
+       long rem;
+
+       if (!posix_clock_set) {
+               if (mach_timebase_info(&info) == KERN_SUCCESS) {
+                       factor = (double)info.numer / (double)info.denom;
+                       clock_monotonic = posix_clock_set = 1;
+               }
+       }
+       if (clock_monotonic) {
+               nano = mach_absolute_time();
+               if ((info.denom != 1 || info.numer != 1) && factor != 0.0)
+                       nano *= factor;
+               tp->tv_sec = nano / NSEC_PER_SEC;
+               rem = nano % NSEC_PER_SEC;
+               if (rem < 0) {
+                       tp->tv_sec--;
+                       rem += NSEC_PER_SEC;
+               }
+               tp->tv_usec = rem / 1000;
+               return 0;
+       }
+#endif
+
+#if 0
+       /* Something above failed, so fall back to gettimeofday */
+       if (!posix_clock_set) {
+               syslog(LOG_WARNING, NO_MONOTONIC);
+               posix_clock_set = 1;
+       }
+#endif
+       return gettimeofday(tp, NULL);
+}
+
+static void
+eloop_event_setup_fds(ELOOP_CTX *ctx)
+{
+       struct eloop_event *e;
+       size_t i;
+
+       i = 0;
+       TAILQ_FOREACH(e, &ctx->events, next) {
+               ctx->fds[i].fd = e->fd;
+               ctx->fds[i].events = 0;
+               if (e->read_cb)
+                       ctx->fds[i].events |= POLLIN;
+               if (e->write_cb)
+                       ctx->fds[i].events |= POLLOUT;
+               ctx->fds[i].revents = 0;
+               e->pollfd = &ctx->fds[i];
+               i++;
+       }
+}
+
+int
+eloop_event_add(ELOOP_CTX *ctx, int fd,
+    void (*read_cb)(void *), void *read_cb_arg,
+    void (*write_cb)(void *), void *write_cb_arg)
+{
+       struct eloop_event *e;
+       struct pollfd *nfds;
+
+       /* We should only have one callback monitoring the fd */
+       TAILQ_FOREACH(e, &ctx->events, next) {
+               if (e->fd == fd) {
+                       if (read_cb) {
+                               e->read_cb = read_cb;
+                               e->read_cb_arg = read_cb_arg;
+                       }
+                       if (write_cb) {
+                               e->write_cb = write_cb;
+                               e->write_cb_arg = write_cb_arg;
+                       }
+                       eloop_event_setup_fds(ctx);
+                       return 0;
+               }
+       }
+
+       /* Allocate a new event if no free ones already allocated */
+       if ((e = TAILQ_FIRST(&ctx->free_events))) {
+               TAILQ_REMOVE(&ctx->free_events, e, next);
+       } else {
+               e = malloc(sizeof(*e));
+               if (e == NULL) {
+                       syslog(LOG_ERR, "%s: %m", __func__);
+                       return -1;
+               }
+       }
+
+       /* Ensure we can actually listen to it */
+       ctx->events_len++;
+       if (ctx->events_len > ctx->fds_len) {
+               ctx->fds_len += 5;
+               nfds = malloc(sizeof(*ctx->fds) * (ctx->fds_len + 5));
+               if (nfds == NULL) {
+                       syslog(LOG_ERR, "%s: %m", __func__);
+                       ctx->events_len--;
+                       TAILQ_INSERT_TAIL(&ctx->free_events, e, next);
+                       return -1;
+               }
+               ctx->fds_len += 5;
+               free(ctx->fds);
+               ctx->fds = nfds;
+       }
+
+       /* Now populate the structure and add it to the list */
+       e->fd = fd;
+       e->read_cb = read_cb;
+       e->read_cb_arg = read_cb_arg;
+       e->write_cb = write_cb;
+       e->write_cb_arg = write_cb_arg;
+       /* The order of events should not matter.
+        * However, some PPP servers love to close the link right after
+        * sending their final message. So to ensure dhcpcd processes this
+        * message (which is likely to be that the DHCP addresses are wrong)
+        * we insert new events at the queue head as the link fd will be
+        * the first event added. */
+       TAILQ_INSERT_HEAD(&ctx->events, e, next);
+       eloop_event_setup_fds(ctx);
+       return 0;
+}
+
+void
+eloop_event_delete(ELOOP_CTX *ctx, int fd, int write_only)
+{
+       struct eloop_event *e;
+
+       TAILQ_FOREACH(e, &ctx->events, next) {
+               if (e->fd == fd) {
+                       if (write_only) {
+                               e->write_cb = NULL;
+                               e->write_cb_arg = NULL;
+                       } else {
+                               TAILQ_REMOVE(&ctx->events, e, next);
+                               TAILQ_INSERT_TAIL(&ctx->free_events, e, next);
+                               ctx->events_len--;
+                       }
+                       eloop_event_setup_fds(ctx);
+                       break;
+               }
+       }
+}
+
+int
+eloop_q_timeout_add_tv(ELOOP_CTX *ctx, int queue,
+    const struct timeval *when, void (*callback)(void *), void *arg)
+{
+       struct timeval now;
+       struct timeval w;
+       struct eloop_timeout *t, *tt = NULL;
+
+       get_monotonic(&now);
+       timeradd(&now, when, &w);
+       /* Check for time_t overflow. */
+       if (timercmp(&w, &now, <)) {
+               errno = ERANGE;
+               return -1;
+       }
+
+       /* Remove existing timeout if present */
+       TAILQ_FOREACH(t, &ctx->timeouts, next) {
+               if (t->callback == callback && t->arg == arg) {
+                       TAILQ_REMOVE(&ctx->timeouts, t, next);
+                       break;
+               }
+       }
+
+       if (t == NULL) {
+               /* No existing, so allocate or grab one from the free pool */
+               if ((t = TAILQ_FIRST(&ctx->free_timeouts))) {
+                       TAILQ_REMOVE(&ctx->free_timeouts, t, next);
+               } else {
+                       t = malloc(sizeof(*t));
+                       if (t == NULL) {
+                               syslog(LOG_ERR, "%s: %m", __func__);
+                               return -1;
+                       }
+               }
+       }
+
+       t->when.tv_sec = w.tv_sec;
+       t->when.tv_usec = w.tv_usec;
+       t->callback = callback;
+       t->arg = arg;
+       t->queue = queue;
+
+       /* The timeout list should be in chronological order,
+        * soonest first. */
+       TAILQ_FOREACH(tt, &ctx->timeouts, next) {
+               if (timercmp(&t->when, &tt->when, <)) {
+                       TAILQ_INSERT_BEFORE(tt, t, next);
+                       return 0;
+               }
+       }
+       TAILQ_INSERT_TAIL(&ctx->timeouts, t, next);
+       return 0;
+}
+
+int
+eloop_q_timeout_add_sec(ELOOP_CTX *ctx, int queue, time_t when,
+    void (*callback)(void *), void *arg)
+{
+       struct timeval tv;
+
+       tv.tv_sec = when;
+       tv.tv_usec = 0;
+       return eloop_q_timeout_add_tv(ctx, queue, &tv, callback, arg);
+}
+
+int
+eloop_timeout_add_now(ELOOP_CTX *ctx,
+    void (*callback)(void *), void *arg)
+{
+
+       if (ctx->timeout0 != NULL) {
+               syslog(LOG_WARNING, "%s: timeout0 already set", __func__);
+               return eloop_q_timeout_add_sec(ctx, 0, 0, callback, arg);
+       }
+
+       ctx->timeout0 = callback;
+       ctx->timeout0_arg = arg;
+       return 0;
+}
+
+void
+eloop_q_timeout_delete(ELOOP_CTX *ctx, int queue,
+    void (*callback)(void *), void *arg)
+{
+       struct eloop_timeout *t, *tt;
+
+       TAILQ_FOREACH_SAFE(t, &ctx->timeouts, next, tt) {
+               if ((queue == 0 || t->queue == queue) &&
+                   t->arg == arg &&
+                   (!callback || t->callback == callback))
+               {
+                       TAILQ_REMOVE(&ctx->timeouts, t, next);
+                       TAILQ_INSERT_TAIL(&ctx->free_timeouts, t, next);
+               }
+       }
+}
+
+void
+eloop_exit(ELOOP_CTX *ctx, int code)
+{
+
+       ctx->exitcode = code;
+       ctx->exitnow = 1;
+}
+
+ELOOP_CTX *
+eloop_init(void)
+{
+       ELOOP_CTX *ctx;
+
+       ctx = calloc(1, sizeof(*ctx));
+       if (ctx) {
+               TAILQ_INIT(&ctx->events);
+               TAILQ_INIT(&ctx->free_events);
+               TAILQ_INIT(&ctx->timeouts);
+               TAILQ_INIT(&ctx->free_timeouts);
+               ctx->exitcode = EXIT_FAILURE;
+       }
+       return ctx;
+}
+
+
+void eloop_free(ELOOP_CTX *ctx)
+{
+       struct eloop_event *e;
+       struct eloop_timeout *t;
+
+       if (ctx == NULL)
+               return;
+
+       while ((e = TAILQ_FIRST(&ctx->events))) {
+               TAILQ_REMOVE(&ctx->events, e, next);
+               free(e);
+       }
+       while ((e = TAILQ_FIRST(&ctx->free_events))) {
+               TAILQ_REMOVE(&ctx->free_events, e, next);
+               free(e);
+       }
+       while ((t = TAILQ_FIRST(&ctx->timeouts))) {
+               TAILQ_REMOVE(&ctx->timeouts, t, next);
+               free(t);
+       }
+       while ((t = TAILQ_FIRST(&ctx->free_timeouts))) {
+               TAILQ_REMOVE(&ctx->free_timeouts, t, next);
+               free(t);
+       }
+       free(ctx->fds);
+       free(ctx);
+}
+
+int
+eloop_start(ELOOP_CTX *ctx)
+{
+       struct timeval now;
+       int n;
+       struct eloop_event *e;
+       struct eloop_timeout *t;
+       struct timeval tv;
+       struct timespec ts, *tsp;
+       void (*t0)(void *);
+       int timeout;
+
+       for (;;) {
+               if (ctx->exitnow)
+                       break;
+
+               /* Run all timeouts first */
+               if (ctx->timeout0) {
+                       t0 = ctx->timeout0;
+                       ctx->timeout0 = NULL;
+                       t0(ctx->timeout0_arg);
+                       continue;
+               }
+               if ((t = TAILQ_FIRST(&ctx->timeouts))) {
+                       get_monotonic(&now);
+                       if (timercmp(&now, &t->when, >)) {
+                               TAILQ_REMOVE(&ctx->timeouts, t, next);
+                               t->callback(t->arg);
+                               TAILQ_INSERT_TAIL(&ctx->free_timeouts, t, next);
+                               continue;
+                       }
+                       timersub(&t->when, &now, &tv);
+                       TIMEVAL_TO_TIMESPEC(&tv, &ts);
+                       tsp = &ts;
+               } else
+                       /* No timeouts, so wait forever */
+                       tsp = NULL;
+
+               if (tsp == NULL && ctx->events_len == 0) {
+                       syslog(LOG_ERR, "nothing to do");
+                       break;
+               }
+
+               if (tsp == NULL)
+                       timeout = -1;
+               else if (tsp->tv_sec > INT_MAX / 1000 ||
+                   (tsp->tv_sec == INT_MAX / 1000 &&
+                   (tsp->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000))
+                       timeout = INT_MAX;
+               else
+                       timeout = (int)(tsp->tv_sec * 1000 +
+                           (tsp->tv_nsec + 999999) / 1000000);
+               n = poll(ctx->fds, ctx->events_len, timeout);
+               if (n == -1) {
+                       if (errno == EINTR)
+                               continue;
+                       syslog(LOG_ERR, "poll: %m");
+                       break;
+               }
+
+               /* Process any triggered events. */
+               if (n > 0) {
+                       TAILQ_FOREACH(e, &ctx->events, next) {
+                               if (e->pollfd->revents & POLLOUT &&
+                                       e->write_cb)
+                               {
+                                       e->write_cb(e->write_cb_arg);
+                                       /* We need to break here as the
+                                        * callback could destroy the next
+                                        * fd to process. */
+                                       break;
+                               }
+                               if (e->pollfd->revents) {
+                                       e->read_cb(e->read_cb_arg);
+                                       /* We need to break here as the
+                                        * callback could destroy the next
+                                        * fd to process. */
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       return ctx->exitcode;
+}
diff --git a/src/dhcpcd-curses/eloop.h b/src/dhcpcd-curses/eloop.h
new file mode 100644 (file)
index 0000000..bf40c90
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * dhcpcd - DHCP client daemon
+ * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef ELOOP_H
+#define ELOOP_H
+
+#include <time.h>
+
+#ifndef ELOOP_QUEUE
+  #define ELOOP_QUEUE 1
+#endif
+
+/* EXIT_FAILURE is a non zero value and EXIT_SUCCESS is zero.
+ * To add a CONTINUE definition, simply do the opposite of EXIT_FAILURE. */
+#define ELOOP_CONTINUE -EXIT_FAILURE
+
+#ifdef IN_ELOOP
+#include "queue.h"
+
+struct eloop_event {
+       TAILQ_ENTRY(eloop_event) next;
+       int fd;
+       void (*read_cb)(void *);
+       void *read_cb_arg;
+       void (*write_cb)(void *);
+       void *write_cb_arg;
+       struct pollfd *pollfd;
+};
+
+struct eloop_timeout {
+       TAILQ_ENTRY(eloop_timeout) next;
+       struct timeval when;
+       void (*callback)(void *);
+       void *arg;
+       int queue;
+};
+
+typedef struct eloop_ctx {
+       size_t events_len;
+       TAILQ_HEAD (event_head, eloop_event) events;
+       struct event_head free_events;
+
+       TAILQ_HEAD (timeout_head, eloop_timeout) timeouts;
+       struct timeout_head free_timeouts;
+
+       void (*timeout0)(void *);
+       void *timeout0_arg;
+
+       struct pollfd *fds;
+       size_t fds_len;
+
+       int exitnow;
+       int exitcode;
+} ELOOP_CTX;
+#else
+typedef void *ELOOP_CTX;
+#endif
+
+#define eloop_timeout_add_tv(a, b, c, d) \
+    eloop_q_timeout_add_tv(a, ELOOP_QUEUE, b, c, d)
+#define eloop_timeout_add_sec(a, b, c, d) \
+    eloop_q_timeout_add_sec(a, ELOOP_QUEUE, b, c, d)
+#define eloop_timeout_delete(a, b, c) \
+    eloop_q_timeout_delete(a, ELOOP_QUEUE, b, c)
+
+int eloop_event_add(ELOOP_CTX *, int,
+    void (*)(void *), void *,
+    void (*)(void *), void *);
+void eloop_event_delete(ELOOP_CTX *, int, int);
+int eloop_q_timeout_add_sec(ELOOP_CTX *, int queue,
+    time_t, void (*)(void *), void *);
+int eloop_q_timeout_add_tv(ELOOP_CTX *, int queue,
+    const struct timeval *, void (*)(void *), void *);
+int eloop_timeout_add_now(ELOOP_CTX *, void (*)(void *), void *);
+void eloop_q_timeout_delete(ELOOP_CTX *, int, void (*)(void *), void *);
+ELOOP_CTX * eloop_init(void);
+void eloop_free(ELOOP_CTX *);
+void eloop_exit(ELOOP_CTX *, int);
+int eloop_start(ELOOP_CTX *);
+
+#endif
diff --git a/src/dhcpcd-curses/queue.h b/src/dhcpcd-curses/queue.h
new file mode 100644 (file)
index 0000000..99ac6b9
--- /dev/null
@@ -0,0 +1,175 @@
+/*     $NetBSD: queue.h,v 1.65 2013/12/25 17:19:34 christos Exp $      */
+
+/*
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)queue.h     8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef COMPAT_QUEUE_H
+#define COMPAT_QUEUE_H
+
+/*
+ * Tail queue definitions.
+ */
+#ifndef TAILQ_END
+#define        TAILQ_END(head)                 (NULL)
+#endif
+
+#ifndef TAILQ_HEAD
+#define        _TAILQ_HEAD(name, type, qual)                                   \
+struct name {                                                          \
+       qual type *tqh_first;           /* first element */             \
+       qual type *qual *tqh_last;      /* addr of last next element */ \
+}
+#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
+
+#define        TAILQ_HEAD_INITIALIZER(head)                                    \
+       { TAILQ_END(head), &(head).tqh_first }
+
+#define        _TAILQ_ENTRY(type, qual)                                        \
+struct {                                                               \
+       qual type *tqe_next;            /* next element */              \
+       qual type *qual *tqe_prev;      /* address of previous next element */\
+}
+#define TAILQ_ENTRY(type)      _TAILQ_ENTRY(struct type,)
+#endif /* !TAILQ_HEAD */
+
+/*
+ * Tail queue access methods.
+ */
+#ifndef TAILQ_FIRST
+#define        TAILQ_FIRST(head)               ((head)->tqh_first)
+#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
+#define        TAILQ_LAST(head, headname) \
+       (*(((struct headname *)((head)->tqh_last))->tqh_last))
+#define        TAILQ_PREV(elm, headname, field) \
+       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define        TAILQ_EMPTY(head)               (TAILQ_FIRST(head) == TAILQ_END(head))
+#endif /* !TAILQ_FIRST */
+
+#ifndef TAILQ_FOREACH
+#define        TAILQ_FOREACH(var, head, field)                                 \
+       for ((var) = ((head)->tqh_first);                               \
+           (var) != TAILQ_END(head);                                   \
+           (var) = ((var)->field.tqe_next))
+
+#define        TAILQ_FOREACH_REVERSE(var, head, headname, field)               \
+       for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\
+           (var) != TAILQ_END(head);                                   \
+           (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))
+#endif /* !TAILQ_FOREACH */
+
+#ifndef TAILQ_INIT
+#define        TAILQ_INIT(head) do {                                           \
+       (head)->tqh_first = TAILQ_END(head);                            \
+       (head)->tqh_last = &(head)->tqh_first;                          \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_HEAD(head, elm, field) do {                        \
+       if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\
+               (head)->tqh_first->field.tqe_prev =                     \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (head)->tqh_first = (elm);                                      \
+       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_TAIL(head, elm, field) do {                        \
+       (elm)->field.tqe_next = TAILQ_END(head);                        \
+       (elm)->field.tqe_prev = (head)->tqh_last;                       \
+       *(head)->tqh_last = (elm);                                      \
+       (head)->tqh_last = &(elm)->field.tqe_next;                      \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_AFTER(head, listelm, elm, field) do {              \
+       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) !=      \
+           TAILQ_END(head))                                            \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (listelm)->field.tqe_next = (elm);                              \
+       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+       (elm)->field.tqe_next = (listelm);                              \
+       *(listelm)->field.tqe_prev = (elm);                             \
+       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
+} while (/*CONSTCOND*/0)
+
+#define        TAILQ_REMOVE(head, elm, field) do {                             \
+       if (((elm)->field.tqe_next) != TAILQ_END(head))                 \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   (elm)->field.tqe_prev;                              \
+       else                                                            \
+               (head)->tqh_last = (elm)->field.tqe_prev;               \
+       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
+} while (/*CONSTCOND*/0)
+#endif /* !TAILQ_INIT */
+
+#ifndef TAILQ_REPLACE
+#define TAILQ_REPLACE(head, elm, elm2, field) do {                      \
+        if (((elm2)->field.tqe_next = (elm)->field.tqe_next) !=                \
+           TAILQ_END(head))                                            \
+                (elm2)->field.tqe_next->field.tqe_prev =                \
+                    &(elm2)->field.tqe_next;                            \
+        else                                                            \
+                (head)->tqh_last = &(elm2)->field.tqe_next;             \
+        (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                 \
+        *(elm2)->field.tqe_prev = (elm2);                               \
+} while (/*CONSTCOND*/0)
+#endif /* !TAILQ_REPLACE */
+
+#ifndef TAILQ_FOREACH_SAFE
+#define        TAILQ_FOREACH_SAFE(var, head, field, next)                      \
+       for ((var) = TAILQ_FIRST(head);                                 \
+           (var) != TAILQ_END(head) &&                                 \
+           ((next) = TAILQ_NEXT(var, field), 1); (var) = (next))
+
+#define        TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev)    \
+       for ((var) = TAILQ_LAST((head), headname);                      \
+           (var) != TAILQ_END(head) &&                                 \
+           ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev))
+#endif /* !TAILQ_FOREACH_SAFE */
+
+#ifndef TAILQ_CONCAT
+#define        TAILQ_CONCAT(head1, head2, field) do {                          \
+       if (!TAILQ_EMPTY(head2)) {                                      \
+               *(head1)->tqh_last = (head2)->tqh_first;                \
+               (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+               (head1)->tqh_last = (head2)->tqh_last;                  \
+               TAILQ_INIT((head2));                                    \
+       }                                                               \
+} while (/*CONSTCOND*/0)
+#endif /* !TAILQ_CONCAT */
+
+#endif /* !COMAPT_QUEUE_H */