diff options
-rw-r--r-- | sysutils/fam/MESSAGE | 19 | ||||
-rw-r--r-- | sysutils/fam/MESSAGE.kqueue | 10 | ||||
-rw-r--r-- | sysutils/fam/Makefile | 32 | ||||
-rw-r--r-- | sysutils/fam/distinfo | 4 | ||||
-rw-r--r-- | sysutils/fam/files/IMonKQueue.c++ | 407 | ||||
-rw-r--r-- | sysutils/fam/files/famd.sh | 17 | ||||
-rw-r--r-- | sysutils/fam/files/imon-compat.h | 53 | ||||
-rw-r--r-- | sysutils/fam/patches/patch-ag | 10 |
8 files changed, 536 insertions, 16 deletions
diff --git a/sysutils/fam/MESSAGE b/sysutils/fam/MESSAGE index 07d709071cf..65120dd1c34 100644 --- a/sysutils/fam/MESSAGE +++ b/sysutils/fam/MESSAGE @@ -1,17 +1,20 @@ =========================================================================== -$NetBSD: MESSAGE,v 1.3 2004/03/28 22:00:04 minskim Exp $ +$NetBSD: MESSAGE,v 1.4 2004/10/17 19:20:53 jmmv Exp $ -FAM uses RPC and is usually started by the inetd(8) superserver. You need -to enable it manually, by issuing the following steps: - -1) Add FAM to the system's portmapper by appending the following line to - the /etc/rpc file: +Because FAM uses RPC, you have to add the following line to the system's +portmapper file (/etc/rpc): sgi_fam 391002 fam # File Alteration Monitor -2) Add FAM to the system's superserver by appending the following line to - the /etc/inetd.conf file: +After that, restart the rpcbind(8) server. + +Furthermore, you have to enable the FAM daemon. There are two ways to do +this: through inetd(8) or as a standalone server. If you prefer the former +way, add the following line to /etc/inetd.conf: sgi_fam/1-2 stream rpc/tcp wait root ${PREFIX}/sbin/famd famd +and reload inetd(8)'s daemon. However, if you prefer the later way, use +the provided famd rc.d(8) script. + =========================================================================== diff --git a/sysutils/fam/MESSAGE.kqueue b/sysutils/fam/MESSAGE.kqueue new file mode 100644 index 00000000000..809a0b41d6f --- /dev/null +++ b/sysutils/fam/MESSAGE.kqueue @@ -0,0 +1,10 @@ +=========================================================================== +$NetBSD: MESSAGE.kqueue,v 1.1 2004/10/17 19:20:53 jmmv Exp $ + +FAM has been built with kqueue support. Consider raising the amount of +open files the kernel can handle (i.e., change kern.maxfiles using +sysctl(8)). FAM will consume a maximum of a half of that limit; this can +be quickly exhausted if you monitor large directories (a quite common thing +if you navigate through your filesystem using Nautilus). + +=========================================================================== diff --git a/sysutils/fam/Makefile b/sysutils/fam/Makefile index cfa64668fa0..2e1c8ab1b22 100644 --- a/sysutils/fam/Makefile +++ b/sysutils/fam/Makefile @@ -1,8 +1,8 @@ -# $NetBSD: Makefile,v 1.17 2004/10/16 14:40:23 jmmv Exp $ +# $NetBSD: Makefile,v 1.18 2004/10/17 19:20:53 jmmv Exp $ # DISTNAME= fam-2.7.0 -PKGREVISION= 3 +PKGREVISION= 4 CATEGORIES= sysutils devel MASTER_SITES= ftp://oss.sgi.com/projects/fam/download/stable/ \ ftp://ftp.tuwien.ac.at/opsys/linux/gentoo/distfiles/ \ @@ -26,10 +26,36 @@ CONFIGURE_ENV+= CPPFLAGS="${CPPFLAGS} -DNDEBUG" EGDIR= ${PREFIX}/share/examples/fam CONF_FILES= ${EGDIR}/fam.conf ${PKG_SYSCONFDIR}/fam.conf +RCD_SCRIPTS= famd + SUBST_CLASSES+= paths SUBST_MESSAGE.paths= "Fixing hardcoded paths." -SUBST_STAGE.paths= post-patch +SUBST_STAGE.paths= pre-configure SUBST_FILES.paths= man/famd.conf.5 man/famd.8 SUBST_SED.paths= -e 's,/usr/local/etc/,${PKG_SYSCONFDIR}/,g' +PKG_OPTIONS_VAR= PKG_OPTIONS.fam +PKG_SUPPORTED_OPTIONS= kqueue + +.include "../../mk/bsd.options.mk" + +.if !empty(PKG_OPTIONS:Mkqueue) && ${OPSYS} == "NetBSD" +CPPFLAGS+= -DHAVE_KQUEUE +LIBS+= -lpthread + +SUBST_CLASSES+= kqueue +SUBST_MESSAGE.kqueue= "Enabling kqueue monitoring." +SUBST_STAGE.kqueue= pre-configure +SUBST_FILES.kqueue= configure +SUBST_SED.kqueue= -e 's,IMonNone,IMonKQueue,g' + +MESSAGE_SRC= ${.CURDIR}/MESSAGE ${.CURDIR}/MESSAGE.kqueue + +.include "../../mk/pthread.buildlink3.mk" +.endif + +post-extract: + ${CP} ${FILESDIR}/IMonKQueue.c++ ${WRKSRC}/src + ${CP} ${FILESDIR}/imon-compat.h ${WRKSRC}/src + .include "../../mk/bsd.pkg.mk" diff --git a/sysutils/fam/distinfo b/sysutils/fam/distinfo index 845c060a0cc..8172ca11b7e 100644 --- a/sysutils/fam/distinfo +++ b/sysutils/fam/distinfo @@ -1,4 +1,4 @@ -$NetBSD: distinfo,v 1.14 2004/10/16 14:40:23 jmmv Exp $ +$NetBSD: distinfo,v 1.15 2004/10/17 19:20:53 jmmv Exp $ SHA1 (fam-2.7.0.tar.gz) = 6c2316f02acf89a41c42ffc3d7fd9cf5eada83a8 Size (fam-2.7.0.tar.gz) = 301974 bytes @@ -8,7 +8,7 @@ SHA1 (patch-ac) = 676966b0372780af4ee5536276132a04dd038863 SHA1 (patch-ad) = b8e621acd36811a76a84af82e6f2b5962973e344 SHA1 (patch-ae) = 225a0bd5195be3d3d75edf021b27bed19d84dc15 SHA1 (patch-af) = 57946b3837479b641bb002620ae41008f49af995 -SHA1 (patch-ag) = fa5889ad6d93af72d7efe83784caf61b2ac39d6a +SHA1 (patch-ag) = 978fa2a582c5f9d2c8660c0a8d933211e97ef500 SHA1 (patch-ah) = d7763198df76d1f0783342a8961b59879e8e1241 SHA1 (patch-ai) = b80aafbb3849fc8c828b6829d8975b910e4d0fd5 SHA1 (patch-aj) = 39391961fd7929d6a5fb49ecb492585cb821afaa diff --git a/sysutils/fam/files/IMonKQueue.c++ b/sysutils/fam/files/IMonKQueue.c++ new file mode 100644 index 00000000000..17da4d9cb6e --- /dev/null +++ b/sysutils/fam/files/IMonKQueue.c++ @@ -0,0 +1,407 @@ +// $NetBSD: IMonKQueue.c++,v 1.1 2004/10/17 19:20:53 jmmv Exp $ +// +// Copyright (c) 2004 Julio M. Merino Vidal. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of version 2 of the GNU General Public License as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it would be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Further, any +// license provided herein, whether implied or otherwise, is limited to +// this program in accordance with the express provisions of the GNU +// General Public License. Patent licenses, if any, provided herein do not +// apply to combinations of this program with other product or programs, or +// any other product whatsoever. This program is distributed without any +// warranty that the program is delivered free of the rightful claim of any +// third person by way of infringement or the like. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write the Free Software Foundation, Inc., 59 +// Temple Place - Suite 330, Boston MA 02111-1307, USA. + +// ------------------------------------------------------------------------ + +// imon emulation through kqueue +// ----------------------------- +// +// The code in this file provides an imon-like interface to FAM using kqueue, +// the kernel event notification mechanism found in NetBSD and OpenBSD. +// +// The idea is the following: a thread, kqueue_monitor, simulates the kernel +// part of imon. This thread can receive commands (ioctl(2)s) and produces +// notifications when there is something to notify. The thread is constantly +// running in the background, calling kevent(2) to see if there are new +// events in the monitored files since the last call. +// +// Communication with kqueue_monitor is accomplished by using two pipes. +// pipe_out is used by the monitor to provide notifications; i.e., it is the +// same as the read end of the regular /dev/imon device, and produces +// compatible messages. On the other hand we have pipe_in, which is used +// to give commands to the monitor (express and revoke); we can't emulate +// ioctl(2)s from user space, so we have to go this route. +// +// Why we use pipe_in to provide commands to the thread, instead of some +// mutexes? If we used mutexes, we'd have to give kevent(2) a timeout, to +// let it "reload" the list of changes to be monitored in case it was +// externally modified. By using a pipe, we can tell kqueue(2) to monitor +// it for us, and let kevent(2) immediately return when there is a command +// to process. +// +// However, there is a little problem when using kqueue instead of imon or +// polling. kqueue(2) works by monitoring open file descriptors, instead +// of inodes on the disk. Therefore we must keep all files being monitored +// open, and the number of open files can quickly raise in some environments. +// This is why the code unlimits the number of open files in imon_open and +// sets a reasonable maximum based on kern.maxfiles (to avoid overflowing +// it quickly). If we overflow this limit, the poller will enter the game +// (because we will return an error). +// +// Known problem: if we receive *lots* of events quickly, we may end up +// locked in pipewr. I haven't located where the problem is, but I +// suspect of the read code in read_handler in IMon.c++. To reproduce, +// run the test program provided by fam against a local directory, say +// /tmp/foo, and run the following: +// cd /tmp/foo; for f in $(jot 1000); do touch $(jot 100); rm *; done +// +// Having said all this, let's go to the code... + +// ------------------------------------------------------------------------ + +#include "IMon.h" +#include "Log.h" + +#include "config.h" +#include "imon-compat.h" + +#include <sys/event.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/sysctl.h> +#include <sys/time.h> + +#include <assert.h> +#include <fcntl.h> +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +#include <map> + +// ------------------------------------------------------------------------ + +// devino is a structure that holds a device/inode pair. It is used as an +// indentifier of files managed by imon. +struct devino { + dev_t di_dev; + ino_t di_ino; + + bool operator<(const struct devino& di) const + { return (di_dev < di.di_dev) || (di_ino < di.di_ino); } +}; + +// imon_cmd simulates commands thrown to imon as ioctl(2)s (but remember +// we use a pipe). +struct imon_cmd { +#define IMON_CMD_EXPRESS 0 +#define IMON_CMD_REVOKE 1 + int ic_type; + + // imon identifies files through a device/inode pair. + struct devino ic_di; + + // A pipe that will be used to receive the result of the command + // (asynchronously). + int ic_stat[2]; + + // If this is an 'express' command, we need the descriptor to monitor. + int ic_fd; +}; + +// ------------------------------------------------------------------------ + +static int max_changes; +static int last_change; +static int kqueue_fd; +static int pipe_in[2], pipe_out[2]; +static pthread_t kevent_thread; +static struct kevent *changes; + +typedef std::map<struct devino, int> DEVINOFD_MAP; +static DEVINOFD_MAP devino_to_fd; +typedef std::map<int, struct devino> FDDEVINO_MAP; +static FDDEVINO_MAP fd_to_devino; + +// ------------------------------------------------------------------------ + +static void *kqueue_monitor(void *data); +static void process_command(void); + +// ------------------------------------------------------------------------ + +int +IMon::imon_open(void) +{ + // Get the kernel event queue. We only need one during all the life + // of famd. + kqueue_fd = kqueue(); + if (kqueue_fd == -1) + return -1; + + // Create "emulation" pipes. + if (pipe(pipe_in) == -1) { + close(kqueue_fd); + return -1; + } + if (pipe(pipe_out) == -1) { + close(kqueue_fd); + close(pipe_in[0]); close(pipe_in[1]); + return -1; + } + + // Get the maximum number of files we can open and use it to set a + // limit of the files we can monitor. + size_t len = sizeof(max_changes); + if (sysctlbyname("kern.maxfiles", &max_changes, &len, NULL, 0) == -1) + max_changes = 128; + else + max_changes /= 2; + + // Unlimit maximum number of open files. We don't go to RLIM_INFINITY + // to avoid possible open descriptor leaks produce a system DoS. 75% + // of the system limit seems a good number (we request more than the + // number calculated previously to leave room for temporary pipes). + // We need to be root to do this. + uid_t olduid = geteuid(); + seteuid(0); + struct rlimit rlp; + rlp.rlim_cur = rlp.rlim_max = max_changes * 3 / 2; + if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) + Log::error("can't unlimit number of open files"); + seteuid(olduid); + + changes = new struct kevent[max_changes]; + + // We must monitor pipe_in for any commands that may alter the actual + // set of files being monitored. + EV_SET(&changes[0], pipe_in[0], EVFILT_READ, + EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, 0); + last_change = 1; + + // Create a thread that will run the kevent(2) function continuously. + if (pthread_create(&kevent_thread, NULL, kqueue_monitor, NULL) != 0) { + close(kqueue_fd); + close(pipe_in[0]); close(pipe_in[1]); + close(pipe_out[0]); close(pipe_out[1]); + return -1; + } + + return pipe_out[0]; +} + +// ------------------------------------------------------------------------ + +IMon::Status +IMon::imon_express(const char *name, struct stat *status) +{ + // Get file information. + if (status == NULL) { + // XXX This leaks memory. AFAICT, the code calling this function + // expects status to be filled if it was NULL. If this is true, + // the code in IMonLinux does worse than us, because it returns a + // pointer to a local variable... + status = new struct stat; + } + if (lstat(name, status) == -1) + return BAD; + + // Open the file to be monitored; kqueue only works with open descriptors + // so we have to keep this descriptor during the life of this 'interest'. + int fd = open(name, O_RDONLY); + if (fd == -1) + return BAD; + + // Construct a command to 'express' interest in a file. This will be + // handled by the kqueue_monitor thread as soon as possible. + struct imon_cmd cmd; + cmd.ic_type = IMON_CMD_EXPRESS; + cmd.ic_di.di_dev = status->st_dev; + cmd.ic_di.di_ino = status->st_ino; + cmd.ic_fd = fd; + if (pipe(cmd.ic_stat) == -1) { + close(fd); + return BAD; + } + write(pipe_in[1], &cmd, sizeof(struct imon_cmd)); + + // Wait for a result form the previous operation. + bool result; + read(cmd.ic_stat[0], &result, sizeof(bool)); + close(cmd.ic_stat[0]); close(cmd.ic_stat[1]); + if (!result) { + close(fd); + Log::error("kqueue can't monitor more than %d files", max_changes); + return BAD; + } + + Log::debug("told kqueue to monitor \"%s\", descriptor = %d, dev = %d, " + "ino = %d", name, cmd.ic_fd, cmd.ic_di.di_dev, + cmd.ic_di.di_ino); + + return OK; +} + +// ------------------------------------------------------------------------ + +IMon::Status +IMon::imon_revoke(const char *name, dev_t dev, ino_t ino) +{ + // Construct a command to 'revoke' interest from a file. This will be + // handled by the kqueue_monitor thread as soon as possible. + struct imon_cmd cmd; + cmd.ic_type = IMON_CMD_REVOKE; + cmd.ic_di.di_dev = dev; + cmd.ic_di.di_ino = ino; + if (pipe(cmd.ic_stat) == -1) + return BAD; + write(pipe_in[1], &cmd, sizeof(struct imon_cmd)); + + // Wait for a result form the previous operation. + bool result; + read(cmd.ic_stat[0], &result, sizeof(bool)); + close(cmd.ic_stat[0]); close(cmd.ic_stat[1]); + if (!result) { + Log::error("kqueue can't revoke \"%s\", dev = %d, ino = %d", name, + cmd.ic_di.di_dev, cmd.ic_di.di_ino); + return BAD; + } + + Log::debug("told kqueue to forget \"%s\", dev = %d, ino = %d", name, + cmd.ic_di.di_dev, cmd.ic_di.di_ino); + + return OK; +} + +// ------------------------------------------------------------------------ + +static void * +kqueue_monitor(void *data) +{ + struct kevent event; + + for (;;) { + int nev = kevent(kqueue_fd, changes, last_change, &event, 1, NULL); + if (nev == -1) + Log::perror("kevent"); + else if (nev > 0) { + if (event.ident == pipe_in[0]) { + // We have got a control command, so process it. + process_command(); + } else { + // One of the descriptors we are monitoring has got activity. + FDDEVINO_MAP::const_iterator iter = + fd_to_devino.find(event.ident); + if (iter != fd_to_devino.end()) { + qelem_t elem; + + // Set device/inode identifier on imon element. + const struct devino &di = (*iter).second; + elem.qe_dev = di.di_dev; + elem.qe_inode = di.di_ino; + + // Convert the modification flags reported by kqueue to + // flags understood by imon. + elem.qe_what = 0; + if (event.fflags & NOTE_DELETE) + elem.qe_what |= IMON_DELETE; + if (event.fflags & NOTE_RENAME) + elem.qe_what |= IMON_RENAME; + if (event.fflags & NOTE_ATTRIB || event.fflags & NOTE_LINK) + elem.qe_what |= IMON_ATTRIBUTE; + if (event.fflags & NOTE_WRITE || event.fflags & NOTE_EXTEND) + elem.qe_what |= IMON_CONTENT; + + // Deliver the element. + write(pipe_out[1], &elem, sizeof(qelem_t)); + } else + Log::error("got an event from an unhandled device/inode " + "pair"); + } + } + } +} + +// ------------------------------------------------------------------------ + +static void +process_command(void) +{ + bool result = false; + struct imon_cmd cmd; + + // Read the command from the control pipe. + read(pipe_in[0], &cmd, sizeof(struct imon_cmd)); + if (cmd.ic_type == IMON_CMD_EXPRESS) { + if (devino_to_fd.find(cmd.ic_di) != devino_to_fd.end()) { + // The file is already being monitored. + close(cmd.ic_fd); + result = true; + } else if (fd_to_devino.find(cmd.ic_fd) != fd_to_devino.end()) { + // We can't receive a new interest of a descriptor that is + // already being monitored. If this happens, there is an + // inconsistency in the data somewhere. + assert(false); + } else if (last_change < max_changes) { + // Add the new descriptor to the list of changes to monitor. + // We watch for any change that happens on it. + EV_SET(&changes[last_change], cmd.ic_fd, EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_ONESHOT, + NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | + NOTE_LINK | NOTE_RENAME | NOTE_REVOKE, + 0, 0); + last_change++; + + // Map the device/inode pair to the file descriptor associated + // to it and viceversa. We will need this information during + // 'revoke' and when we receive events. We use two different + // maps to speed up searches in both directions later. + devino_to_fd.insert + (DEVINOFD_MAP::value_type(cmd.ic_di, cmd.ic_fd)); + fd_to_devino.insert + (FDDEVINO_MAP::value_type(cmd.ic_fd, cmd.ic_di)); + + result = true; + } + } else if (cmd.ic_type == IMON_CMD_REVOKE) { + DEVINOFD_MAP::const_iterator iter = devino_to_fd.find(cmd.ic_di); + if (iter != devino_to_fd.end()) { + // Get the descriptor associated to the given device/inode pair + // and remove the mapping from the required structure. + int fd = (*iter).second; + devino_to_fd.erase(cmd.ic_di); + fd_to_devino.erase(fd); + + // Remove the entry associated to the descriptor from the list + // of changes monitored by kqueue. + int i; + for (i = 1; i < last_change; i++) + if (changes[i].ident == fd) + break; + for (int j = i; j < last_change - 1; j++) + changes[j] = changes[j + 1]; + last_change--; + + close(fd); + + result = true; + } + } else { + // Huh? Unknown command received. + assert(false); + } + + // Deliver the result of the operation. + write(cmd.ic_stat[1], &result, sizeof(bool)); +} diff --git a/sysutils/fam/files/famd.sh b/sysutils/fam/files/famd.sh new file mode 100644 index 00000000000..9ca6126fb1d --- /dev/null +++ b/sysutils/fam/files/famd.sh @@ -0,0 +1,17 @@ +#!@RCD_SCRIPTS_SHELL@ +# +# $NetBSD: famd.sh,v 1.1 2004/10/17 19:20:53 jmmv Exp $ +# +# PROVIDE: famd +# REQUIRE: DAEMON +# KEYWORD: shutdown + +. /etc/rc.subr + +name="famd" +rcvar=$name +command="@PREFIX@/sbin/famd" +command_args="-T 0 -L &" + +load_rc_config $name +run_rc_command "$1" diff --git a/sysutils/fam/files/imon-compat.h b/sysutils/fam/files/imon-compat.h new file mode 100644 index 00000000000..65d9fdb198a --- /dev/null +++ b/sysutils/fam/files/imon-compat.h @@ -0,0 +1,53 @@ +// $NetBSD: imon-compat.h,v 1.1 2004/10/17 19:20:53 jmmv Exp $ +// +// Copyright (c) 2004 Julio M. Merino Vidal. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of version 2 of the GNU General Public License as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it would be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Further, any +// license provided herein, whether implied or otherwise, is limited to +// this program in accordance with the express provisions of the GNU +// General Public License. Patent licenses, if any, provided herein do not +// apply to combinations of this program with other product or programs, or +// any other product whatsoever. This program is distributed without any +// warranty that the program is delivered free of the rightful claim of any +// third person by way of infringement or the like. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write the Free Software Foundation, Inc., 59 +// Temple Place - Suite 330, Boston MA 02111-1307, USA. + +#if !defined(IMON_COMPAT_H) +#define IMON_COMPAT_H + +#if defined(HAVE_IMON) +# error "cannot include imon-compat.h if imon is really present" +#endif + +#if defined(HAVE_KQUEUE) +#define HAVE_IMON 1 + +typedef int intmask_t; + +typedef struct { + dev_t qe_dev; + ino_t qe_inode; + intmask_t qe_what; +} qelem_t; + +#define IMON_CONTENT (1 << 0) +#define IMON_ATTRIBUTE (1 << 1) +#define IMON_DELETE (1 << 2) +#define IMON_EXEC (1 << 3) +#define IMON_EXIT (1 << 4) +#define IMON_RENAME (1 << 5) +#define IMON_OVER 0xff + +#endif // defined(HAVE_KQUEUE) + +#endif // !defined(IMON_COMPAT_H) diff --git a/sysutils/fam/patches/patch-ag b/sysutils/fam/patches/patch-ag index 3d8fce2cc61..01224f0acb9 100644 --- a/sysutils/fam/patches/patch-ag +++ b/sysutils/fam/patches/patch-ag @@ -1,6 +1,6 @@ -$NetBSD: patch-ag,v 1.4 2004/03/28 22:00:05 minskim Exp $ +$NetBSD: patch-ag,v 1.5 2004/10/17 19:20:53 jmmv Exp $ ---- src/IMon.c++.orig 2003-01-18 08:18:12.000000000 -0600 +--- src/IMon.c++.orig 2003-01-18 15:18:12.000000000 +0100 +++ src/IMon.c++ @@ -25,6 +25,7 @@ #include <assert.h> @@ -10,8 +10,12 @@ $NetBSD: patch-ag,v 1.4 2004/03/28 22:00:05 minskim Exp $ #if HAVE_IMON #ifdef __sgi -@@ -34,7 +35,9 @@ +@@ -32,9 +33,13 @@ + #else + #include <linux/imon.h> #endif ++#else // HAVE_IMON ++#include "imon-compat.h" #endif +#if HAVE_SYS_SYSMACROS_H |