Committer : klmitch
CVSROOT : /cvsroot/undernet-ircu
Module : ircu2.10
Commit time: 2008-03-22 15:08:57 UTC
Modified files:
ChangeLog
Added files:
include/register.h ircd/register.c
Log message:
Author: Kev <[EMAIL PROTECTED]>
Log message:
Creation of a new generic registration subsystem. The purpose is basically
to allow dynamic addition of entries for formerly table-driven stuff, like
/stats; this will enable us to respect abstraction boundaries much better.
For instance, the /stats subcommands for iauth could be declared in
s_auth.c itself, instead of having to make the called functions visible to
s_stats.c.
Note that this is only the implementation, and that the only tests done so
far are to check that it compiles. It is not added to the sources list,
and nothing in the core code has yet been modified to make use of the new
facility. This is part of the modularization effort for ircu2.10.13.
---------------------- diff included ----------------------
Index: ircu2.10/ChangeLog
diff -u ircu2.10/ChangeLog:1.852 ircu2.10/ChangeLog:1.853
--- ircu2.10/ChangeLog:1.852 Thu Mar 20 11:58:58 2008
+++ ircu2.10/ChangeLog Sat Mar 22 08:08:46 2008
@@ -1,3 +1,11 @@
+2008-03-22 Kevin L. Mitchell <[EMAIL PROTECTED]>
+
+ * ircd/register.c: implementation of generic registration
+ subsystem
+
+ * include/register.h: header file for generic registration
+ subsystem
+
2008-03-20 Kevin L. Mitchell <[EMAIL PROTECTED]>
* include/client.h (infousermodes): add +O to the list of
Index: ircu2.10/include/register.h
diff -u /dev/null ircu2.10/include/register.h:1.1
--- /dev/null Sat Mar 22 08:08:57 2008
+++ ircu2.10/include/register.h Sat Mar 22 08:08:47 2008
@@ -0,0 +1,129 @@
+/*
+ * IRC - Internet Relay Chat, include/register.h
+ * Copyright (C) 2008 Kevin L. Mitchell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+/** @file
+ * @brief Structures and functions for handling generic function registration.
+ * @version $Id: register.h,v 1.1 2008/03/22 15:08:47 klmitch Exp $
+ */
+#ifndef INCLUDED_register_h
+#define INCLUDED_register_h
+#ifndef INCLUDED_sys_types_h
+#include <sys/types.h> /* size_t */
+#define INCLUDED_sys_types_h
+#endif
+
+/** Name of table containing registered registration tables. */
+#define REG_TABLE "tables"
+
+/** Specifies an element in the list of registered entities. */
+typedef struct RegEnt regent_t;
+/** Describes a table of registered entities. */
+typedef struct RegTab regtab_t;
+
+/** Optional registration callback for a table.
+ * @param[in] table Table entry is to be registered in.
+ * @param[in] entry Entry being registered.
+ * @return 0 to accept registration, non-zero to reject it.
+ */
+typedef int (*reg_t)(regtab_t* table, void* entry);
+
+/** Optional unregistration callback for a table.
+ * @param[in] table Table entry is to be unregistered from.
+ * @param[in] entry Entry being unregistered.
+ * @return 0 to accept unregistration, non-zero to reject it.
+ */
+typedef int (*unreg_t)(regtab_t* table, void* entry);
+
+/** Iteration callback for reg_iter().
+ * @param[in] table Table entry is in.
+ * @param[in] entry Entry in table.
+ * @param[in] extra Extra pointer passed to reg_iter().
+ * @return 0 to continue iteration, non-zero to stop iteration.
+ */
+typedef int (*regiter_t)(regtab_t* table, void* entry, void* extra);
+
+/** Describes an entry in a table. */
+struct RegEnt {
+ unsigned long rl_magic; /**< Magic number */
+ regent_t* rl_next; /**< Next entry in list */
+ regent_t* rl_prev; /**< Previous entry in list */
+ const char* rl_id; /**< Name we're identified by */
+ regtab_t* rl_desc; /**< Descriptor for registration */
+};
+
+/** Initialize a regent_t.
+ * @param[in] magic Magic number for this entry.
+ * @param[in] id Name of the entry.
+ */
+#define REGENT_INIT(magic, id) \
+ { (magic), 0, 0, (id), 0 }
+
+/** Check the regent_t magic number. */
+#define REGENT_CHECK(re, magic) ((re) &&
\
+ ((regent_t*) (re))->rl_magic == (magic))
+/** Get the name of an entry. */
+#define rl_id(re) (((regent_t*) (re))->rl_id)
+/** Get the table entry is in. */
+#define rl_desc(re) (((regent_t*) (re))->rl_desc)
+
+/** Describes a table of registered entries. */
+struct RegTab {
+ regent_t reg_entry; /**< Entry in registers list */
+ unsigned long reg_magic; /**< Magic number for members of list */
+ reg_t reg_reg; /**< Registration function */
+ unreg_t reg_unreg; /**< Unregistration function */
+ regent_t* reg_list; /**< List of registration descriptors */
+};
+
+/** Magic number for regtab_t. */
+#define REGTAB_MAGIC 0xed99058e
+
+/** Initialize a regtab_t.
+ * @param[in] id Name of the table.
+ * @param[in] magic Magic number for all entries in table.
+ * @param[in] reg Pointer to registration callback, or NULL. See reg_t.
+ * @param[in] unreg Pointer to unregistration callback, or NULL. See unreg_t.
+ */
+#define REGTAB_INIT(id, magic, reg, unreg) \
+ { REGENT_INIT(REGTAB_MAGIC, (id)), (magic), (reg), (unreg), 0 }
+
+/** Check a registration table. */
+#define REGTAB_CHECK(tab) REGENT_CHECK((tab), REGTAB_MAGIC)
+/** Get the name of a table. */
+#define reg_name(tab) rl_id(tab)
+/** Get the magic number for table entries. */
+#define reg_magic(tab) ((tab)->reg_magic)
+/** Get the registration callback function. */
+#define reg_reg(tab) ((tab)->reg_reg)
+/** Get the unregistration callback function. */
+#define reg_unreg(tab) ((tab)->reg_unreg)
+
+/* register an entry in a named table */
+extern int reg(const char* table, void* entry);
+/* register an array of entries in a named table */
+extern int reg_n(const char* table, void* ent_array, int n, size_t size);
+/* unregister an entry from a named table */
+extern int unreg(const char* table, void* entry);
+/* unregister an array of entries from a named table */
+extern int unreg_n(const char* table, void* ent_array, int n, size_t size);
+/* look up an entry in the table */
+extern void* reg_find(const char* table, const char* id);
+/* iterate over entries in the table */
+extern int reg_iter(const char* table, regiter_t func, void* extra);
+
+#endif /* INCLUDED_register_h */
Index: ircu2.10/ircd/register.c
diff -u /dev/null ircu2.10/ircd/register.c:1.1
--- /dev/null Sat Mar 22 08:08:57 2008
+++ ircu2.10/ircd/register.c Sat Mar 22 08:08:47 2008
@@ -0,0 +1,469 @@
+/*
+ * IRC - Internet Relay Chat, include/register.h
+ * Copyright (C) 2008 Kevin L. Mitchell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+/** @file
+ * @brief Implementation of generic function registration.
+ * @version $Id: register.c,v 1.1 2008/03/22 15:08:47 klmitch Exp $
+ */
+#include "config.h"
+
+#include "register.h"
+#include "ircd_log.h"
+
+#include <string.h>
+
+/** @page register Generic registration subsystem.
+ *
+ * @section introreg Introduction
+ *
+ * Many things in ircu are table-driven. These are mostly
+ * subcommands, such as for /stats, or even the protocol commands
+ * themselves. Modules wishing to enhance the subcommand table for
+ * something like /stats have formerly been required to directly
+ * modify the table at the code level. This makes it impossible for
+ * dynamically loadable modules to do this form of enhancement. This
+ * subsystem is an attempt to unify many of these tables in a way that
+ * does not significantly increase the overhead while allowing the
+ * desired extensibility. Note that this first version is based on
+ * simple linked lists in the backend; if a different backend is
+ * desired, change the definitions of struct RegEnt and struct RegTab
+ * in register.h and update the functions _reg_find(), reg_add(),
+ * reg_rem(), and reg_iter() in register.c.
+ *
+ * The basic structures in the registration subsystem are
+ * <i>registration tables</i> and <i>registration entries</i>. To
+ * give a concrete example, each /stats subcommand would be defined by
+ * an instance of a structure beginning with a registration entry (the
+ * regent_t type). The full list of registered /stats subcommands
+ * would be represented by a registration table (the regtab_t type).
+ *
+ * @section howtoreg Registration How-To
+ *
+ * How does one create a new registration table, as in the case of the
+ * /stats example above? Simply allocate an instance of regtab_t,
+ * initializing it with the REGTAB_INIT() macro. The \a id attribute
+ * specifies the unique name of the table, and \a magic specifies a
+ * unique magic number used for sanity checks. (See @ref advreg for
+ * discussion of the \a reg and \a unreg parameters.) Once you have a
+ * valid regtab_t, pass a pointer to it to the reg() function to
+ * register it with the table specified by the REG_TABLE constant.
+ * Now you can register and unregister entries at will.
+ *
+ * Now, how does one go about registering and unregistering those
+ * entries? As noted above, the structure describing an entry in the
+ * table must begin with a regent_t, which can be initialized with the
+ * REGENT_INIT() macro. The \a magic argument of this macro must
+ * match the \a magic parameter of the intended registration table,
+ * and the \a id argument of REGENT_INIT() must be a unique name for
+ * the entry you're creating. Now, you register the entry using the
+ * reg() function, passing the name of the table and a pointer to the
+ * entry; unregistering the entry is as simple as calling the unreg()
+ * function.
+ *
+ * @section arrayreg Registration of Arrays of Entries
+ *
+ * "Great," you say. "Now I can register new entries on the fly.
+ * What if I have an array of entries to add, though?" This,
+ * fortunately, is simple; both reg() and unreg() come in reg_n() and
+ * unreg_n() versions tailored to this task. For these functions,
+ * simply pass a pointer to the array--note, \b NOT an array of
+ * pointers--and the number of elements goes in the \a n parameter.
+ * You must also pass the size of a single element in \a size, so that
+ * reg_n() and unreg_n() can find the next element in the array. If
+ * they fail while adding or removing an entry, the return value tells
+ * you the index of the first entry that couldn't be added; otherwise,
+ * if the functions process every entry successfully, the return value
+ * will be \a n.
+ *
+ * @section searchreg Looking Up Registered Entries
+ *
+ * What good is being able to add entries if you can't find them?
+ * This is where the reg_find() function comes in. You pass it the
+ * name of the table to search and the name of the entry to look for,
+ * and it will return either a pointer to the relevant entry, or NULL
+ * if there is no entry by that name. Some applications also require
+ * being able to iterate over all the entries in a table--/stats, for
+ * instance, can send the user a list of all recognized subcommands,
+ * along with some descriptive text for each of them. This can be
+ * done using the reg_iter() function, which simply calls a callback
+ * on each element in the table. Note that the visited order is
+ * unspecified, and will change over time; if ordering is important to
+ * your application, replace the linked list implementation with an
+ * ordered data structure.
+ *
+ * @section advreg Advanced Registration
+ *
+ * The registration system provides table suppliers with the option of
+ * calling registration and unregistration callbacks. This might be
+ * used, for instance, to double-check that certain required fields
+ * are provided, or to add an entry to an application-defined
+ * table--for instance, /stats might add an entry to a table indexed
+ * by character code, to handle cases when a user uses "/stats i".
+ * These callbacks must match the reg_t and unreg_t types, and are
+ * called from the reg() and unreg() functions, respectively. (They
+ * are also, of course, called by reg_n() and unreg_n().) If the
+ * callbacks return a non-zero value, that will be returned to the
+ * callers of reg() and unreg() (but not, alas, reg_n() or
+ * unreg_n()). Both callbacks are optional, so only specify the ones
+ * you need--but always make sure that any added entry can be safely
+ * removed, and won't be referenced after it is removed!
+ *
+ * @section inforeg Important Subsystem Information
+ *
+ * This subsystem provides two structures--struct RegEnt and struct
+ * RegTab--and 5 types: regent_t and regtab_t, corresponding to the
+ * two structures; reg_t and unreg_t, for the registration and
+ * unregistration callback functions; and regiter_t, used for the
+ * iterator function passed to reg_iter(). In particular, regent_t
+ * and regtab_t should be treated as opaque by all callers, and struct
+ * RegEnt and struct RegTab should not be referenced directly. Any
+ * data needed from these structures may be obtained using the
+ * provided macros.
+ *
+ * This subsystem is intentionally designed to pull in as few other
+ * ircu subsystems as possible, in order to minimize issues with
+ * modularity. It is almost completely self-contained in the
+ * register.h and register.c files, and only references the assert()
+ * macro in ircd_log.h. Explicit initialization is also not needed;
+ * it makes use of "lazy initialization," where the first statement in
+ * every publicly-exposed function is a call to a macro which checks
+ * whether initialization has been performed and performs it if not.
+ * Finally, the subsystem performs no memory allocation whatsoever,
+ * and the recommended allocation procedure for callers is to
+ * statically allocate the structures that will be passed in.
+ */
+
+/** Macro to perform "lazy" initialization of the system. */
+#define reg_init(void) \
+ do { \
+ if (!reg_initted) { \
+ reg_add(®_table, (regent_t*) ®_table); \
+ reg_initted++; /* remember that we're done */ \
+ } \
+ } while (0)
+
+/* pre-declare reg_flush() */
+static int reg_flush(regtab_t* table, regtab_t* entry);
+
+/** Flag whether initialization has been done */
+static int reg_initted = 0;
+/** Table of registration tables */
+static regtab_t reg_table = REGTAB_INIT(REG_TABLE, REGTAB_MAGIC, 0,
+ (unreg_t) reg_flush);
+
+/** Search a table for a named entry.
+ * @param[in] table Table to search.
+ * @param[in] id Identifier of the entry to look up.
+ * @return Pointer to the identified entry, or NULL if there isn't one.
+ */
+static regent_t* _reg_find(regtab_t* table, const char* id)
+{
+ regent_t *cursor, *tmp;
+
+ assert(0 != table);
+ assert(0 != id);
+
+ /* Loop through the linked list... */
+ for (cursor = table->reg_list; cursor; cursor = cursor->rl_next)
+ if (!strcmp(id, cursor->rl_id)) {
+ /* simple optimization step: bubble found entries up in the list;
+ * this will tend to move frequently accessed entries toward the
+ * beginning of the list, while moving infrequently accessed
+ * entries toward the end.
+ */
+ if ((tmp = cursor->rl_prev)) {
+ tmp->rl_next = cursor->rl_next;
+ cursor->rl_prev = tmp->rl_prev;
+
+ tmp->rl_prev = cursor;
+ cursor->rl_next = tmp;
+
+ /* update surrounding entries in the list */
+ if (cursor->rl_prev)
+ cursor->rl_prev->rl_next = cursor;
+ if (tmp->rl_next)
+ tmp->rl_next->rl_prev = tmp;
+ }
+
+ return cursor; /* we found it! */
+ }
+
+ return 0; /* not found */
+}
+
+/** Add an entry to a table. Searches the table to make sure the
+ * entry isn't already there somewhere.
+ * @param[in] table Table to add the entry to.
+ * @param[in] entry Entry to add.
+ */
+static void reg_add(regtab_t* table, regent_t* entry)
+{
+ assert(0 != table);
+ assert(0 != entry);
+ assert(0 != entry->rl_desc);
+
+ /* OK, add the new entry to the head of the linked list */
+ entry->rl_next = table->reg_list;
+ entry->rl_prev = 0;
+ if (table->reg_list) /* update prev in following entry */
+ table->reg_list->rl_prev = entry;
+ table->reg_list = entry; /* update the list */
+
+ /* and make sure the entry points to its descriptor */
+ entry->rl_desc = table;
+}
+
+/** Remove an entry from a table.
+ * @param[in] table Table to remove entry from.
+ * @param[in] entry Entry to remove.
+ */
+static void reg_rem(regtab_t* table, regent_t* entry)
+{
+ assert(0 != table);
+ assert(0 != entry);
+ assert(table == entry->rl_desc);
+
+ /* Simply clip it out of the list */
+ if (entry->rl_next)
+ entry->rl_next->rl_prev = entry->rl_prev;
+ if (entry->rl_prev)
+ entry->rl_prev->rl_next = entry->rl_next;
+ else /* first entry in list */
+ table->reg_list = entry->rl_next;
+
+ /* zero what has to be zeroed */
+ entry->rl_next = entry->rl_prev = 0;
+ entry->rl_desc = 0;
+}
+
+/** Remove all entries from a table.
+ * @param[in] table Pointer to reg_table.
+ * @param[in] entry Table being unregistered.
+ * @return 0 to accept the unregistration.
+ */
+static int reg_flush(regtab_t* table, regtab_t* entry)
+{
+ /* Unregister each entry in turn */
+ while (entry->reg_list) {
+ if (entry->reg_unreg) /* call unregistration callback */
+ (entry->reg_unreg)(entry, (void*) entry->reg_list);
+ /* Note: ignoring failures and trying hard */
+
+ /* remove entry from the table */
+ reg_rem(entry, entry->reg_list);
+ }
+
+ return 0;
+}
+
+/** Register a new entry in a table.
+ * @param[in] table Name of table entry should belong to.
+ * @param[in] entry Entry to add; must begin with a regent_t.
+ * @return 0 if entry was added, non-zero otherwise; -1 means no table by
+ * that name; -2 means the magic numbers don't match; -3 means it already
+ * exists in the table; other values returned by table reg function.
+ */
+int reg(const char* table, void* entry)
+{
+ int retval;
+ regtab_t *tab;
+
+ reg_init(); /* initialize subsystem */
+
+ /* look up the requested table */
+ if (!(tab = (regtab_t*) _reg_find(®_table, table)))
+ return -1; /* no such table */
+
+ /* double-check the magic numbers */
+ if (((regent_t*) entry)->rl_magic != tab->reg_magic)
+ return -2; /* bad magic number */
+
+ /* make sure the entry doesn't already exist */
+ if (_reg_find(tab, ((regent_t*) entry)->rl_id))
+ return -3; /* already exists in the table */
+
+ /* perform any table-specific registration */
+ if (tab->reg_reg && (retval = (tab->reg_reg)(tab, entry)))
+ return retval;
+
+ /* add it to the table */
+ reg_add(tab, (regent_t*) entry);
+
+ return 0; /* all set */
+}
+
+/** Register an array of entries in a table.
+ * @param[in] table Name of table entries should belong to.
+ * @param[in] ent_array Array of entries to add; each must be same size and
+ * begin with a regent_t.
+ * @param[in] n Number of entries in array.
+ * @param[in] size Size of each entry in array.
+ * @return n if all entries were added; -1 means no table by that name;
+ * otherwise, the index of the first entry not added to the table.
+ */
+int reg_n(const char* table, void* ent_array, int n, size_t size)
+{
+ int i;
+ regtab_t *tab;
+
+ reg_init(); /* initialize subsystem */
+
+ /* look up the requested table */
+ if (!(tab = (regtab_t*) _reg_find(®_table, table)))
+ return -1; /* no such table */
+
+ /* walk through each array entry and add it */
+ for (i = 0; i < n; i++) {
+ /* obtain the indexed entry */
+ regent_t *ent = (regent_t*) (((char*) ent_array) + i * size);
+
+ /* double-check the magic numbers */
+ if (ent->rl_magic != tab->reg_magic)
+ return i;
+
+ /* make sure the entry doesn't already exist */
+ if (_reg_find(tab, ent->rl_id))
+ return i; /* already exists in the table */
+
+ /* perform any table-specific registration */
+ if (tab->reg_reg && (tab->reg_reg)(tab, ent))
+ return i;
+
+ /* add it to the table */
+ reg_add(tab, ent);
+ }
+
+ return i; /* should be n, indicating all entries processed */
+}
+
+/** Unregister an entry from a table.
+ * @param[in] table Name of table entry should be removed from.
+ * @param[in] entry Entry to remove; must begin with a regent_t.
+ * @return 0 if entry was removed, non-zero otherwise; -1 means no table by
+ * that name; other values returned by table unreg function.
+ */
+int unreg(const char* table, void* entry)
+{
+ int retval;
+ regtab_t *tab;
+
+ reg_init(); /* initialize subsystem */
+
+ /* look up the requested table */
+ if (!(tab = (regtab_t*) _reg_find(®_table, table)))
+ return -1;
+
+ assert(tab == ((regent_t*) entry)->rl_desc);
+
+ /* perform any table-specific unregistration */
+ if (tab->reg_unreg && (retval = (tab->reg_unreg)(tab, entry)))
+ return retval;
+
+ /* remove it from the table */
+ reg_rem(tab, (regent_t*) entry);
+
+ return 0; /* all set */
+}
+
+/** Unregister an array of entries from a table.
+ * @param[in] table Name of table entries should be removed from.
+ * @param[in] ent_array Array of entries to remove; each must be same size and
+ * begin with a regent_t.
+ * @param[in] n Number of entries in array.
+ * @param[in] size Size of each entry in array.
+ * @return n if all entries were removed; -1 means no table by that name;
+ * otherwise, the index of the first entry not removed from the table. Note
+ * only table unreg function returning non-zero can halt processing.
+ */
+int unreg_n(const char* table, void* ent_array, int n, size_t size)
+{
+ int i;
+ regtab_t *tab;
+
+ reg_init(); /* initialize subsystem */
+
+ /* look up the requested table */
+ if (!(tab = (regtab_t*) _reg_find(®_table, table)))
+ return -1; /* no such table */
+
+ /* walk through each array entry and add it */
+ for (i = 0; i < n; i++) {
+ /* obtain the indexed entry */
+ regent_t *ent = (regent_t*) (((char*) ent_array) + i * size);
+
+ assert(tab == ent->rl_desc);
+
+ /* perform any table-specific unregistration */
+ if (tab->reg_unreg && (tab->reg_unreg)(tab, ent))
+ return i;
+
+ /* remove it from the table */
+ reg_add(tab, ent);
+ }
+
+ return i; /* should be n, indicating all entries processed */
+}
+
+
+/** Find an entry in a table.
+ * @param[in] table Name of table to search.
+ * @param[in] id Name of entry to look up.
+ * @return Pointer to entry, or NULL if not found.
+ */
+void* reg_find(const char* table, const char* id)
+{
+ regtab_t *tab;
+
+ reg_init(); /* initialize subsystem */
+
+ /* look up the requested table */
+ if (!(tab = (regtab_t*) _reg_find(®_table, table)))
+ return 0; /* couldn't even find table */
+
+ /* return the result of looking up the entry */
+ return (void*) _reg_find(tab, id);
+}
+
+/** Iterate over all entries in a table.
+ * @param[in] table Name of table to walk.
+ * @param[in] func Iteration function to execute.
+ * @param[in] extra Extra data to pass to iteration function.
+ * @return -1 if table doesn't exist; otherwise, 0 or whatever
+ * non-zero value \a func returns.
+ */
+int reg_iter(const char* table, regiter_t func, void* extra)
+{
+ int retval;
+ regtab_t *tab;
+ regent_t *cursor;
+
+ assert(0 != func);
+
+ reg_init(); /* initialize subsystem */
+
+ /* look up the requested table */
+ if (!(tab = (regtab_t*) _reg_find(®_table, table)))
+ return -1;
+
+ /* walk the linked list */
+ for (cursor = tab->reg_list; cursor; cursor = cursor->rl_next)
+ if ((retval = (func)(tab, (void*) cursor, extra)))
+ return retval; /* iteration function signaled for stop */
+
+ return 0; /* all done */
+}
----------------------- End of diff -----------------------
_______________________________________________
Patches mailing list
[email protected]
http://undernet.sbg.org/mailman/listinfo/patches