summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorTimo Sirainen <cras@irssi.org>2000-04-26 08:03:38 +0000
committercras <cras@dbcabf3a-b0e7-0310-adc4-f8d773084564>2000-04-26 08:03:38 +0000
commitc95034c6de1bf72536595e1e3431d8ec64b9880e (patch)
treee51aa4528257ed8aa9d53640649519f299aaf0c7 /src/core
parentd01b094151705d433bc43cae9eeb304e6f110a17 (diff)
downloadirssi-c95034c6de1bf72536595e1e3431d8ec64b9880e.zip
..adding new files..
git-svn-id: http://svn.irssi.org/repos/irssi/trunk@171 dbcabf3a-b0e7-0310-adc4-f8d773084564
Diffstat (limited to 'src/core')
-rw-r--r--src/core/Makefile.am59
-rw-r--r--src/core/args.c60
-rw-r--r--src/core/args.h15
-rw-r--r--src/core/commands.c462
-rw-r--r--src/core/commands.h74
-rw-r--r--src/core/core.c68
-rw-r--r--src/core/core.h17
-rw-r--r--src/core/levels.c160
-rw-r--r--src/core/levels.h44
-rw-r--r--src/core/line-split.c147
-rw-r--r--src/core/line-split.h13
-rw-r--r--src/core/log.c438
-rw-r--r--src/core/log.h49
-rw-r--r--src/core/memdebug.c356
-rw-r--r--src/core/memdebug.h31
-rw-r--r--src/core/misc.c467
-rw-r--r--src/core/misc.h57
-rw-r--r--src/core/module.h3
-rw-r--r--src/core/modules.c185
-rw-r--r--src/core/modules.h33
-rw-r--r--src/core/net-disconnect.c155
-rw-r--r--src/core/net-disconnect.h7
-rw-r--r--src/core/net-internal.h6
-rw-r--r--src/core/net-nonblock.c210
-rw-r--r--src/core/net-nonblock.h26
-rw-r--r--src/core/network.c451
-rw-r--r--src/core/network.h71
-rw-r--r--src/core/pidwait.c74
-rw-r--r--src/core/pidwait.h12
-rw-r--r--src/core/rawlog.c167
-rw-r--r--src/core/rawlog.h28
-rw-r--r--src/core/server-redirect.c323
-rw-r--r--src/core/server-redirect.h38
-rw-r--r--src/core/server.c273
-rw-r--r--src/core/server.h66
-rw-r--r--src/core/settings.c336
-rw-r--r--src/core/settings.h56
-rw-r--r--src/core/signals.c356
-rw-r--r--src/core/signals.h30
-rw-r--r--src/core/special-vars.c635
-rw-r--r--src/core/special-vars.h27
41 files changed, 6085 insertions, 0 deletions
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
new file mode 100644
index 00000000..c7cd93b5
--- /dev/null
+++ b/src/core/Makefile.am
@@ -0,0 +1,59 @@
+noinst_LTLIBRARIES = libcore.la
+
+INCLUDES = \
+ $(GLIB_CFLAGS) \
+ -DSYSCONFDIR=\""$(sysconfdir)"\" \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/core
+
+if BUILD_MEMDEBUG
+memdebug_src=memdebug.c
+else
+memdebug_src=
+endif
+
+libcore_la_SOURCES = \
+ args.c \
+ commands.c \
+ core.c \
+ levels.c \
+ line-split.c \
+ log.c \
+ $(memdebug_src) \
+ misc.c \
+ modules.c \
+ net-disconnect.c \
+ net-nonblock.c \
+ network.c \
+ pidwait.c \
+ rawlog.c \
+ server.c \
+ server-redirect.c \
+ settings.c \
+ signals.c \
+ special-vars.c
+
+noinst_HEADERS = \
+ args.h \
+ commands.h \
+ core.h \
+ levels.h \
+ line-split.h \
+ log.h \
+ memdebug.h \
+ misc.h \
+ module.h \
+ modules.h \
+ net-disconnect.h \
+ net-nonblock.h \
+ network.h \
+ pidwait.h \
+ rawlog.h \
+ server.h \
+ server-redirect.h \
+ settings.h \
+ signals.h \
+ special-vars.h
+
+EXTRA_DIST = \
+ memdebug.c
diff --git a/src/core/args.c b/src/core/args.c
new file mode 100644
index 00000000..cf48e02c
--- /dev/null
+++ b/src/core/args.c
@@ -0,0 +1,60 @@
+/*
+ args.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "args.h"
+
+GArray *iopt_tables = NULL;
+
+void args_register(struct poptOption *options)
+{
+ if (iopt_tables == NULL)
+ iopt_tables = g_array_new(TRUE, TRUE, sizeof(struct poptOption));
+
+ while (options->longName != NULL || options->shortName != '\0' || options->arg != NULL) {
+ g_array_append_val(iopt_tables, *options);
+ options = options+1;
+ }
+}
+
+void args_execute(int argc, char *argv[])
+{
+ poptContext con;
+ int nextopt;
+
+ if (iopt_tables == NULL)
+ return;
+
+ con = poptGetContext(PACKAGE, argc, argv, (struct poptOption *) (iopt_tables->data), 0);
+ poptReadDefaultConfig(con, TRUE);
+
+ while ((nextopt = poptGetNextOpt(con)) > 0) ;
+
+ if (nextopt != -1) {
+ printf(_("Error on option %s: %s.\nRun '%s --help' to see a full list of available command line options.\n"),
+ poptBadOption(con, 0),
+ poptStrerror(nextopt),
+ argv[0]);
+ exit(1);
+ }
+
+ g_array_free(iopt_tables, TRUE);
+ iopt_tables = NULL;
+}
diff --git a/src/core/args.h b/src/core/args.h
new file mode 100644
index 00000000..4d1b82fb
--- /dev/null
+++ b/src/core/args.h
@@ -0,0 +1,15 @@
+#ifndef __ARGS_H
+#define __ARGS_H
+
+#ifdef HAVE_POPT_H
+# include <popt.h>
+#else
+# include "lib-popt/popt.h"
+#endif
+
+extern GArray *iopt_tables;
+
+void args_register(struct poptOption *options);
+void args_execute(int argc, char *argv[]);
+
+#endif
diff --git a/src/core/commands.c b/src/core/commands.c
new file mode 100644
index 00000000..e7f2560a
--- /dev/null
+++ b/src/core/commands.c
@@ -0,0 +1,462 @@
+/*
+ commands.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "modules.h"
+#include "signals.h"
+#include "commands.h"
+#include "misc.h"
+#include "server.h"
+#include "server-redirect.h"
+#include "special-vars.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#define alias_find(alias) \
+ iconfig_get_str("aliases", alias, NULL)
+
+GSList *commands;
+char *current_command;
+
+static GSList *cmdget_funcs;
+static int signal_default_command;
+
+void command_bind(const char *cmd, const char *category, SIGNAL_FUNC func)
+{
+ COMMAND_REC *rec;
+ char *str;
+
+ g_return_if_fail(cmd != NULL);
+
+ rec = g_new0(COMMAND_REC, 1);
+ rec->cmd = g_strdup(cmd);
+ rec->category = category == NULL ? NULL : g_strdup(category);
+ commands = g_slist_append(commands, rec);
+
+ if (func != NULL) {
+ str = g_strconcat("command ", cmd, NULL);
+ signal_add(str, func);
+ g_free(str);
+ }
+
+ signal_emit("commandlist new", 1, rec);
+}
+
+void command_free(COMMAND_REC *rec)
+{
+ commands = g_slist_remove(commands, rec);
+ signal_emit("commandlist remove", 1, rec);
+
+ g_free_not_null(rec->category);
+ g_free(rec->cmd);
+ g_free(rec);
+}
+
+void command_unbind(const char *cmd, SIGNAL_FUNC func)
+{
+ GSList *tmp;
+ char *str;
+
+ g_return_if_fail(cmd != NULL);
+
+ for (tmp = commands; tmp != NULL; tmp = tmp->next) {
+ COMMAND_REC *rec = tmp->data;
+
+ if (g_strcasecmp(rec->cmd, cmd) == 0) {
+ command_free(rec);
+ break;
+ }
+ }
+
+ if (func != NULL) {
+ str = g_strconcat("command ", cmd, NULL);
+ signal_remove(str, func);
+ g_free(str);
+ }
+}
+
+void command_runsub(const char *cmd, const char *data, void *p1, void *p2)
+{
+ char *subcmd, *defcmd, *args;
+
+ g_return_if_fail(data != NULL);
+
+ /* get command.. */
+ subcmd = g_strdup_printf("command %s %s", cmd, data);
+ args = strchr(subcmd+9 + strlen(cmd), ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+ while (*args == ' ') args++;
+
+ g_strdown(subcmd);
+ if (!signal_emit(subcmd, 3, args, p1, p2)) {
+ defcmd = g_strdup_printf("default command %s", cmd);
+ if (!signal_emit(defcmd, 3, data, p1, p2))
+ signal_emit("unknown command", 3, strchr(subcmd, ' ')+1, p1, p2);
+ g_free(defcmd);
+ }
+ g_free(subcmd);
+}
+
+char *cmd_get_param(char **data)
+{
+ char *pos;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ while (**data == ' ') (*data)++;
+ pos = *data;
+
+ while (**data != '\0' && **data != ' ') (*data)++;
+ if (**data == ' ') *(*data)++ = '\0';
+
+ return pos;
+}
+
+char *cmd_get_quoted_param(char **data)
+{
+ char *pos, quote;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ while (**data == ' ') (*data)++;
+ if (**data != '\'' && **data != '"')
+ return cmd_get_param(data);
+
+ quote = **data; (*data)++;
+
+ pos = *data;
+ while (**data != '\0' && **data != quote) {
+ if (**data == '\\' && (*data)[1] != '\0')
+ g_memmove(*data, (*data)+1, strlen(*data));
+ (*data)++;
+ }
+
+ if (**data != '\0') *(*data)++ = '\0';
+
+ return pos;
+}
+
+static char *get_opt_args(char **data)
+{
+ /* -cmd1 -cmd2 -cmd3 ... */
+ char *p, *ret;
+
+ g_return_val_if_fail(data != NULL, NULL);
+ g_return_val_if_fail(*data != NULL, NULL);
+
+ ret = NULL;
+ for (p = *data;;) {
+ if (*p != '-') {
+ if (p == *data) return "";
+
+ while (isspace(p[-1]) && p > *data) p--;
+ if (*p != '\0') *p++ = '\0';
+ ret = *data;
+ *data = p;
+ return ret;
+ }
+
+ while (!isspace(*p) && *p != '\0') p++;
+ while (isspace(*p)) p++;
+ }
+}
+
+static void cmd_params_pack(char ***subargs, char *end, char *start, char *newstart)
+{
+ char ***tmp;
+ char *data;
+ int bufsize, datalen, len;
+
+ bufsize = (int) (end-newstart)+1;
+
+ data = g_malloc(bufsize); datalen = 0;
+ for (tmp = subargs; *tmp != NULL; tmp++) {
+ if (**tmp < start || **tmp > end)
+ continue;
+
+ len = strlen(**tmp)+1;
+ if (datalen+len > bufsize)
+ g_error("cmd_params_pack() : buffer overflow!");
+
+ memcpy(data+datalen, **tmp, len);
+ **tmp = newstart+datalen;
+ datalen += len;
+ }
+
+ g_memmove(newstart, data, datalen);
+ g_free(data);
+}
+
+int arg_find(char **array, const char *item)
+{
+ char **tmp;
+ int index;
+
+ g_return_val_if_fail(array != NULL, 0);
+ g_return_val_if_fail(item != NULL, 0);
+
+ index = 0;
+ for (tmp = array; *tmp != NULL; tmp++, index++) {
+ if (g_strcasecmp(*tmp + (**tmp == '@'), item) == 0)
+ return index;
+ }
+
+ return -1;
+}
+
+static int get_multi_args(char **data, va_list va)
+{
+ /* -cmd1 arg1 -cmd2 "argument two" -cmd3 */
+ GString *returnargs;
+ char **args, **arglist, *arg, *origdata;
+ char **nextarg, ***subargs;
+ int eat, pos;
+
+ eat = 0;
+ args = (char **) va_arg(va, char **);
+ g_return_val_if_fail(args != NULL && *args != NULL && **args != '\0', 0);
+
+ arglist = g_strsplit(*args, " ", -1);
+ eat = strarray_length(arglist);
+
+ subargs = g_new(char **, eat+1);
+ for (pos = 0; pos < eat; pos++) {
+ subargs[pos] = (char **) va_arg(va, char **);
+ if (subargs[pos] == NULL) {
+ g_free(subargs);
+ g_warning("get_multi_args() : subargument == NULL");
+ return eat;
+ }
+ *subargs[pos] = "";
+ }
+ subargs[eat] = NULL;
+
+ origdata = *data;
+ returnargs = g_string_new(NULL);
+ nextarg = NULL;
+ for (;;) {
+ if (**data == '-') {
+ (*data)++; arg = cmd_get_param(data);
+ g_string_sprintfa(returnargs, "-%s ", arg);
+
+ /* check if this argument can have parameter */
+ pos = arg_find(arglist, arg);
+ nextarg = pos == -1 ? NULL : subargs[pos];
+
+ while (isspace(**data)) (*data)++;
+ continue;
+ }
+
+ if (nextarg == NULL)
+ break;
+
+ if (*arglist[pos] == '@' && !isdigit(**data))
+ break; /* expected a numeric argument */
+
+ /* save the sub-argument to `nextarg' */
+ arg = cmd_get_quoted_param(data);
+ *nextarg = arg; nextarg = NULL;
+
+ while (isspace(**data)) (*data)++;
+ }
+
+ /* ok, this is a bit stupid. this will pack the arguments in `data'
+ like "-arg1 subarg -arg2 sub2\0" -> "-arg1 -arg2\0subarg\0sub2\0"
+ this is because it's easier to free only _one_ string instead of
+ two (`args') when using PARAM_FLAG_MULTIARGS. */
+ if (origdata == *data)
+ *args = "";
+ else {
+ cmd_params_pack(subargs, **data == '\0' ? *data : (*data)-1,
+ origdata, origdata+returnargs->len);
+
+ g_string_truncate(returnargs, returnargs->len-1);
+ strcpy(origdata, returnargs->str);
+ *args = origdata;
+ }
+
+ g_string_free(returnargs, TRUE);
+ g_strfreev(arglist);
+ g_free(subargs);
+
+ return eat;
+}
+
+char *cmd_get_callfuncs(const char *data, int *count, va_list *args)
+{
+ CMD_GET_FUNC func;
+ GSList *tmp;
+ char *ret, *old;
+
+ ret = g_strdup(data);
+ for (tmp = cmdget_funcs; tmp != NULL; tmp = tmp->next) {
+ func = tmp->data;
+
+ old = ret;
+ ret = func(ret, count, args);
+ g_free(old);
+ }
+
+ return ret;
+}
+
+char *cmd_get_params(const char *data, int count, ...)
+{
+ char **str, *arg, *ret, *datad;
+ va_list args;
+ int cnt, eat;
+
+ g_return_val_if_fail(data != NULL, NULL);
+
+ va_start(args, count);
+ ret = datad = cmd_get_callfuncs(data, &count, &args);
+
+ cnt = PARAM_WITHOUT_FLAGS(count);
+ while (cnt-- > 0) {
+ if (count & PARAM_FLAG_OPTARGS) {
+ arg = get_opt_args(&datad);
+ count &= ~PARAM_FLAG_OPTARGS;
+ } else if (count & PARAM_FLAG_MULTIARGS) {
+ eat = get_multi_args(&datad, args)+1;
+ count &= ~PARAM_FLAG_MULTIARGS;
+
+ cnt -= eat-1;
+ while (eat-- > 0)
+ str = (char **) va_arg(args, char **);
+ continue;
+ } else if (cnt == 0 && count & PARAM_FLAG_GETREST) {
+ /* get rest */
+ arg = datad;
+ } else {
+ arg = cmd_get_quoted_param(&datad);
+ }
+
+ str = (char **) va_arg(args, char **);
+ if (str != NULL) *str = arg;
+ }
+ va_end(args);
+
+ return ret;
+}
+
+void cmd_get_add_func(CMD_GET_FUNC func)
+{
+ cmdget_funcs = g_slist_prepend(cmdget_funcs, func);
+}
+
+void cmd_get_remove_func(CMD_GET_FUNC func)
+{
+ cmdget_funcs = g_slist_prepend(cmdget_funcs, func);
+}
+
+static void parse_outgoing(const char *line, SERVER_REC *server, void *item)
+{
+ const char *cmdchars, *alias;
+ char *cmd, *str, *args, *oldcmd;
+ int use_alias = TRUE;
+
+ g_return_if_fail(line != NULL);
+
+ if (*line == '\0') {
+ /* empty line, forget it. */
+ signal_stop();
+ return;
+ }
+
+ cmdchars = settings_get_str("cmdchars");
+ if (strchr(cmdchars, *line) == NULL)
+ return; /* handle only /commands here */
+ line++;
+
+ /* //command ignores aliases */
+ if (strchr(cmdchars, *line) != NULL) {
+ line++;
+ use_alias = FALSE;
+ }
+
+ cmd = str = g_strconcat("command ", line, NULL);
+ args = strchr(cmd+8, ' ');
+ if (args != NULL) *args++ = '\0'; else args = "";
+
+ /* check if there's an alias for command */
+ alias = use_alias ? alias_find(cmd+8) : NULL;
+ if (alias != NULL)
+ eval_special_string(alias, args, server, item);
+ else {
+ if (server != NULL)
+ server_redirect_default((SERVER_REC *) server, cmd);
+
+ g_strdown(cmd);
+ oldcmd = current_command;
+ current_command = cmd+8;
+ if (!signal_emit(cmd, 3, args, server, item))
+ signal_emit_id(signal_default_command, 3, line, server, item);
+ current_command = oldcmd;
+ }
+
+ g_free(str);
+}
+
+static void cmd_eval(const char *data, SERVER_REC *server, void *item)
+{
+ g_return_if_fail(data != NULL);
+
+ eval_special_string(data, "", server, item);
+}
+
+static void cmd_cd(const char *data)
+{
+ char *str;
+
+ g_return_if_fail(data != NULL);
+ if (*data == '\0') return;
+
+ str = convert_home(data);
+ chdir(str);
+ g_free(str);
+}
+
+void commands_init(void)
+{
+ cmdget_funcs = NULL;
+ current_command = NULL;
+
+ signal_default_command = module_get_uniq_id_str("signals", "default command");
+
+ settings_add_str("misc", "cmdchars", "/");
+ signal_add("send command", (SIGNAL_FUNC) parse_outgoing);
+
+ command_bind("eval", NULL, (SIGNAL_FUNC) cmd_eval);
+ command_bind("cd", NULL, (SIGNAL_FUNC) cmd_cd);
+}
+
+void commands_deinit(void)
+{
+ g_free_not_null(current_command);
+ g_slist_free(cmdget_funcs);
+
+ signal_remove("send command", (SIGNAL_FUNC) parse_outgoing);
+
+ command_unbind("eval", (SIGNAL_FUNC) cmd_eval);
+ command_unbind("cd", (SIGNAL_FUNC) cmd_cd);
+}
diff --git a/src/core/commands.h b/src/core/commands.h
new file mode 100644
index 00000000..f91bc2ce
--- /dev/null
+++ b/src/core/commands.h
@@ -0,0 +1,74 @@
+#ifndef __COMMANDS_H
+#define __COMMANDS_H
+
+#include "signals.h"
+
+typedef struct {
+ char *category;
+ char *cmd;
+}
+COMMAND_REC;
+
+enum {
+ CMDERR_PARAM, /* invalid parameter */
+ CMDERR_NOT_ENOUGH_PARAMS, /* not enough parameters given */
+ CMDERR_NOT_CONNECTED, /* not connected to IRC server */
+ CMDERR_NOT_JOINED, /* not joined to any channels in this window */
+ CMDERR_GETSOCKNAME, /* getsockname() failed */
+ CMDERR_LISTEN, /* listen() failed */
+ CMDERR_MULTIPLE_MATCHES, /* multiple matches found, didn't do anything */
+ CMDERR_NICK_NOT_FOUND, /* nick not found */
+ CMDERR_CHAN_NOT_FOUND, /* channel not found */
+ CMDERR_SERVER_NOT_FOUND, /* server not found */
+ CMDERR_CHAN_NOT_SYNCED, /* channel not fully synchronized yet */
+ CMDERR_NOT_GOOD_IDEA /* not good idea to do, -yes overrides this */
+};
+
+#define cmd_return_error(a) { signal_emit("error command", 1, GINT_TO_POINTER(a)); return; }
+#define cmd_param_error(a) { g_free(params); cmd_return_error(a); }
+
+extern GSList *commands;
+extern char *current_command;
+
+void command_bind(const char *cmd, const char *category, SIGNAL_FUNC func);
+void command_unbind(const char *cmd, SIGNAL_FUNC func);
+
+void command_runsub(const char *cmd, const char *data, void *p1, void *p2);
+
+/* count can have these flags: */
+#define PARAM_WITHOUT_FLAGS(a) ((a) & 0x00ffffff)
+/* cmd_get_params() */
+#define PARAM_FLAG_GETREST 0x02000000
+/* optional arguments (-cmd -cmd2 -cmd3) */
+#define PARAM_FLAG_OPTARGS 0x04000000
+/* arguments can have arguments too. Example:
+
+ -cmd arg -noargcmd -cmd2 "another arg -optnumarg rest of the text
+
+ You would call this with:
+
+ args = "cmd cmd2 @optnumarg";
+ cmd_get_params(data, 5 | PARAM_FLAG_MULTIARGS | PARAM_FLAG_GETREST,
+ &args, &arg_cmd, &arg_cmd2, &arg_optnum, &rest);
+
+ The variables are filled as following:
+
+ args = "-cmd -noargcmd -cmd2 -optnumarg"
+ arg_cmd = "arg"
+ arg_cmd2 = "another arg"
+ rest = "rest of the text"
+ arg_optnum = "" - this is because "rest" isn't a numeric value
+*/
+#define PARAM_FLAG_MULTIARGS 0x08000000
+
+char *cmd_get_param(char **data);
+char *cmd_get_params(const char *data, int count, ...);
+
+typedef char* (*CMD_GET_FUNC) (const char *data, int *count, va_list *args);
+void cmd_get_add_func(CMD_GET_FUNC func);
+void cmd_get_remove_func(CMD_GET_FUNC func);
+
+void commands_init(void);
+void commands_deinit(void);
+
+#endif
diff --git a/src/core/core.c b/src/core/core.c
new file mode 100644
index 00000000..c850a572
--- /dev/null
+++ b/src/core/core.c
@@ -0,0 +1,68 @@
+/*
+ core.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+
+#include "modules.h"
+#include "pidwait.h"
+
+#include "net-disconnect.h"
+#include "signals.h"
+#include "settings.h"
+
+#include "server.h"
+#include "commands.h"
+#include "log.h"
+#include "rawlog.h"
+#include "special-vars.h"
+
+int irssi_gui;
+
+void core_init(void)
+{
+ modules_init();
+ pidwait_init();
+
+ net_disconnect_init();
+ signals_init();
+ settings_init();
+
+ servers_init();
+ commands_init();
+ log_init();
+ rawlog_init();
+ special_vars_init();
+}
+
+void core_deinit(void)
+{
+ special_vars_deinit();
+ rawlog_deinit();
+ log_deinit();
+ commands_deinit();
+ servers_deinit();
+
+ settings_deinit();
+ signals_deinit();
+ net_disconnect_deinit();
+
+ pidwait_deinit();
+ modules_deinit();
+}
diff --git a/src/core/core.h b/src/core/core.h
new file mode 100644
index 00000000..61d6ef70
--- /dev/null
+++ b/src/core/core.h
@@ -0,0 +1,17 @@
+#ifndef __IRSSI_CORE_H
+#define __IRSSI_CORE_H
+
+/* for determining what GUI is currently in use: */
+#define IRSSI_GUI_NONE 0
+#define IRSSI_GUI_TEXT 1
+#define IRSSI_GUI_GTK 2
+#define IRSSI_GUI_GNOME 3
+#define IRSSI_GUI_QT 4
+#define IRSSI_GUI_KDE 5
+
+extern int irssi_gui;
+
+void core_init(void);
+void core_deinit(void);
+
+#endif
diff --git a/src/core/levels.c b/src/core/levels.c
new file mode 100644
index 00000000..7e55d738
--- /dev/null
+++ b/src/core/levels.c
@@ -0,0 +1,160 @@
+/*
+ levels.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "levels.h"
+
+static char *levels[] =
+{
+ "CRAP",
+ "MSGS",
+ "PUBLICS",
+ "NOTICES",
+ "SNOTES",
+ "CTCPS",
+ "ACTIONS",
+ "JOINS",
+ "PARTS",
+ "QUITS",
+ "KICKS",
+ "MODES",
+ "TOPICS",
+ "WALLS",
+ "WALLOPS",
+ "INVITES",
+ "NICKS",
+ "DCC",
+ "CLIENTNOTICES",
+ "CLIENTCRAP",
+ "CLIENTERRORS",
+ "HILIGHT",
+
+ "NOHILIGHT",
+ NULL
+};
+
+int level_get(const char *level)
+{
+ int n, len;
+
+ if (strcmp(level, "ALL") == 0)
+ return MSGLEVEL_ALL;
+
+ if (strcmp(level, "NEVER") == 0)
+ return MSGLEVEL_NEVER;
+
+ /* I never remember if it was PUBLIC or PUBLICS, MSG or MSGS, etc.
+ So, make it work with both. */
+ len = strlen(level);
+ if (toupper(level[len-1]) == 'S') len--;
+
+ for (n = 0; levels[n] != NULL; n++) {
+ if (strncmp(levels[n], level, len) == 0 &&
+ (levels[n][len] == '\0' || strcmp(levels[n]+len, "S") == 0))
+ return 1 << n;
+ }
+
+ return 0;
+}
+
+int level2bits(const char *level)
+{
+ char *orig, *str, *ptr;
+ int ret, slevel, neg;
+
+ g_return_val_if_fail(level != NULL, 0);
+
+ if (*level == '\0')
+ return 0;
+
+ orig = str = g_strdup(level);
+ g_strup(str);
+
+ ret = 0;
+ for (ptr = str; ; str++) {
+ if (*str == ' ')
+ *str++ = '\0';
+ else if (*str != '\0')
+ continue;
+
+ neg = *ptr == '-' ? 1 : 0;
+ if (*ptr == '-' || *ptr == '+') ptr++;
+
+ slevel = level_get(ptr);
+ if (slevel != 0) {
+ ret = !neg ? (ret | slevel) :
+ (ret & ~slevel);
+ }
+
+ while (*str == ' ') str++;
+ if (*str == '\0') break;
+
+ ptr = str;
+ }
+ g_free(orig);
+
+ return ret;
+}
+
+char *bits2level(int bits)
+{
+ GString *str;
+ char *ret;
+ int n;
+
+ if (bits == MSGLEVEL_ALL)
+ return g_strdup("ALL");
+
+ str = g_string_new(NULL);
+ if (bits & MSGLEVEL_NEVER)
+ g_string_append(str, "NEVER ");
+
+ for (n = 0; levels[n] != NULL; n++) {
+ if (bits & (1 << n))
+ g_string_sprintfa(str, "%s ", levels[n]);
+ }
+ g_string_truncate(str, str->len-1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+
+ return ret;
+}
+
+int combine_level(int dest, const char *src)
+{
+ char **list, **item;
+ int itemlevel;
+
+ g_return_val_if_fail(src != NULL, dest);
+
+ list = g_strsplit(src, " ", -1);
+ for (item = list; *item != NULL; item++) {
+ g_strup(*item);
+ itemlevel = level_get(*item + (**item == '+' || **item == '-' ? 1 : 0));
+ if (**item == '-')
+ dest &= ~(itemlevel);
+ else
+ dest |= itemlevel;
+ }
+ g_strfreev(list);
+
+ return dest;
+}
diff --git a/src/core/levels.h b/src/core/levels.h
new file mode 100644
index 00000000..f3a54507
--- /dev/null
+++ b/src/core/levels.h
@@ -0,0 +1,44 @@
+#ifndef __LEVELS_H
+#define __LEVELS_H
+
+/* This is pretty much IRC specific, but I think it would be easier for
+ other chats to try to use these same levels instead of implementing too
+ difficult message leveling system (which might be done if really
+ needed..). */
+
+/* Message levels */
+#define MSGLEVEL_CRAP 0x0000001
+#define MSGLEVEL_MSGS 0x0000002
+#define MSGLEVEL_PUBLIC 0x0000004
+#define MSGLEVEL_NOTICES 0x0000008
+#define MSGLEVEL_SNOTES 0x0000010
+#define MSGLEVEL_CTCPS 0x0000020
+#define MSGLEVEL_ACTIONS 0x0000040
+#define MSGLEVEL_JOINS 0x0000080
+#define MSGLEVEL_PARTS 0x0000100
+#define MSGLEVEL_QUITS 0x0000200
+#define MSGLEVEL_KICKS 0x0000400
+#define MSGLEVEL_MODES 0x0000800
+#define MSGLEVEL_TOPICS 0x0001000
+#define MSGLEVEL_WALLS 0x0002000
+#define MSGLEVEL_WALLOPS 0x0004000
+#define MSGLEVEL_INVITES 0x0008000
+#define MSGLEVEL_NICKS 0x0010000
+#define MSGLEVEL_DCC 0x0020000
+#define MSGLEVEL_CLIENTNOTICE 0x0040000
+#define MSGLEVEL_CLIENTCRAP 0x0080000
+#define MSGLEVEL_CLIENTERROR 0x0100000
+#define MSGLEVEL_HILIGHT 0x0200000
+
+#define MSGLEVEL_ALL 0x03fffff
+
+#define MSGLEVEL_NOHILIGHT 0x1000000 /* Don't try to hilight words in this message */
+#define MSGLEVEL_NO_ACT 0x2000000 /* Don't trigger channel activity */
+#define MSGLEVEL_NEVER 0x4000000 /* never ignore / never log */
+#define MSGLEVEL_LASTLOG 0x8000000 /* never ignore / never log */
+
+int level2bits(const char *level);
+char *bits2level(int bits);
+int combine_level(int dest, const char *src);
+
+#endif
diff --git a/src/core/line-split.c b/src/core/line-split.c
new file mode 100644
index 00000000..fd27256e
--- /dev/null
+++ b/src/core/line-split.c
@@ -0,0 +1,147 @@
+/*
+ line-split.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+
+/* Maximum line length - split to two lines if it's longer than this.
+
+ This is mostly to prevent excessive memory usage. Like if someone DCC
+ chats you, you both have very fast connections and the other side sends
+ you 100 megs of text without any line feeds -> irssi will (try to)
+ allocate 128M of memory for the line and will eventually crash when it
+ can't allocate any more memory. If the line is split at every 64k the
+ text buffer will free the old lines and the memory usage never gets
+ too high. */
+#define MAX_CHARS_IN_LINE 65536
+
+typedef struct {
+ int len;
+ int alloc;
+ int remove;
+ char *str;
+} LINEBUF_REC;
+
+static inline int nearest_power(int num)
+{
+ int n = 1;
+
+ while (n < num) n <<= 1;
+ return n;
+}
+
+static void linebuf_append(LINEBUF_REC *rec, const char *data, int len)
+{
+ if (rec->len+len > rec->alloc) {
+ rec->alloc = nearest_power(rec->len+len);;
+ rec->str = rec->str == NULL ? g_malloc(rec->alloc) :
+ g_realloc(rec->str, rec->alloc);
+ }
+
+ memcpy(rec->str + rec->len, data, len);
+ rec->len += len;
+}
+
+static char *linebuf_find(LINEBUF_REC *rec, char chr)
+{
+ int n;
+
+ for (n = 0; n < rec->len; n++)
+ if (rec->str[n] == chr) return rec->str+n;
+
+ return NULL;
+}
+
+static int remove_newline(LINEBUF_REC *rec)
+{
+ char *ptr;
+
+ ptr = linebuf_find(rec, '\n');
+ if (ptr == NULL) {
+ /* LF wasn't found, wait for more data.. */
+ if (rec->len < MAX_CHARS_IN_LINE)
+ return 0;
+
+ /* line buffer is too big - force a newline. */
+ linebuf_append(rec, "\n", 1);
+ ptr = rec->str+rec->len-1;
+ }
+
+ rec->remove = (int) (ptr-rec->str)+1;
+ if (ptr != rec->str && ptr[-1] == '\r') {
+ /* remove CR too. */
+ ptr--;
+ }
+
+ *ptr = '\0';
+ return 1;
+}
+
+/* line-split `data'. Initially `*buffer' should contain NULL. */
+int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer)
+{
+ LINEBUF_REC *rec;
+
+ g_return_val_if_fail(data != NULL, -1);
+ g_return_val_if_fail(output != NULL, -1);
+ g_return_val_if_fail(buffer != NULL, -1);
+
+ if (*buffer == NULL)
+ *buffer = g_new0(LINEBUF_REC, 1);
+ rec = *buffer;
+
+ if (rec->remove > 0) {
+ rec->len = rec->len - rec->remove;
+ memcpy(rec->str, rec->str+rec->remove, rec->len);
+ rec->remove = 0;
+ }
+
+ if (len > 0)
+ linebuf_append(rec, data, len);
+ else if (len < 0) {
+ /* connection closed.. */
+ if (rec->len == 0)
+ return -1;
+
+ /* no new data got but still something in buffer.. */
+ len = 0;
+ if (linebuf_find(rec, '\n') == NULL) {
+ /* connection closed and last line is missing \n ..
+ just add it so we can see if it had anything useful.. */
+ linebuf_append(rec, "\n", 1);
+ }
+ }
+
+ *output = rec->str;
+ return remove_newline(rec);
+}
+
+void line_split_free(LINEBUF_REC *buffer)
+{
+ if (buffer != NULL) {
+ if (buffer->str != NULL) g_free(buffer->str);
+ g_free(buffer);
+ }
+}
+
+/* Return 1 if there is no data in the buffer */
+int line_split_is_empty(LINEBUF_REC *buffer)
+{
+ return buffer->len == 0;
+}
diff --git a/src/core/line-split.h b/src/core/line-split.h
new file mode 100644
index 00000000..56d694c3
--- /dev/null
+++ b/src/core/line-split.h
@@ -0,0 +1,13 @@
+#ifndef __LINE_SPLIT_H
+#define __LINE_SPLIT_H
+
+typedef struct _LINEBUF_REC LINEBUF_REC;
+
+/* line-split `data'. Initially `*buffer' should contain NULL. */
+int line_split(const char *data, int len, char **output, LINEBUF_REC **buffer);
+void line_split_free(LINEBUF_REC *buffer);
+
+/* Return 1 if there is no data in the buffer */
+int line_split_is_empty(LINEBUF_REC *buffer);
+
+#endif
diff --git a/src/core/log.c b/src/core/log.c
new file mode 100644
index 00000000..69c2fab5
--- /dev/null
+++ b/src/core/log.c
@@ -0,0 +1,438 @@
+/*
+ log.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "commands.h"
+#include "levels.h"
+#include "misc.h"
+#include "log.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+
+#define LOG_FILE_CREATE_MODE 644
+
+#ifdef HAVE_FCNTL
+static struct flock lock;
+#endif
+
+GSList *logs;
+
+const char *log_timestamp;
+static int log_file_create_mode;
+static int rotate_tag;
+
+static void log_write_timestamp(int handle, const char *format, const char *suffix, time_t t)
+{
+ struct tm *tm;
+ char str[256];
+
+ tm = localtime(&t);
+
+ str[sizeof(str)-1] = '\0';
+ strftime(str, sizeof(str)-1, format, tm);
+
+ write(handle, str, strlen(str));
+ if (suffix != NULL) write(handle, suffix, strlen(suffix));
+}
+
+int log_start_logging(LOG_REC *log)
+{
+ char *str, fname[1024];
+ struct tm *tm;
+ time_t t;
+
+ g_return_val_if_fail(log != NULL, FALSE);
+
+ if (log->handle != -1)
+ return TRUE;
+
+ t = time(NULL);
+ tm = localtime(&t);
+
+ /* Append/create log file */
+ str = convert_home(log->fname);
+ strftime(fname, sizeof(fname), str, tm);
+ log->handle = open(fname, O_WRONLY | O_APPEND | O_CREAT, log_file_create_mode);
+ g_free(str);
+
+ if (log->handle == -1) return FALSE;
+#ifdef HAVE_FCNTL
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_WRLCK;
+ if (fcntl(log->handle, F_SETLK, &lock) == -1) {
+ close(log->handle);
+ log->handle = -1;
+ signal_emit("log locked", 1, log);
+ return FALSE;
+ }
+#endif
+ lseek(log->handle, 0, SEEK_END);
+
+ log->opened = log->last = time(NULL);
+ log_write_timestamp(log->handle, settings_get_str("log_open_string"), "\n", log->last);
+
+ signal_emit("log started", 1, log);
+ return TRUE;
+}
+
+void log_stop_logging(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log->handle == -1)
+ return;
+
+ signal_emit("log stopped", 1, log);
+
+ log_write_timestamp(log->handle, settings_get_str("log_close_string"), "\n", time(NULL));
+
+#ifdef HAVE_FCNTL
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_UNLCK;
+ fcntl(log->handle, F_SETLK, &lock);
+#endif
+
+ close(log->handle);
+ log->handle = -1;
+}
+
+void log_write_rec(LOG_REC *log, const char *str)
+{
+ struct tm *tm;
+ time_t t;
+ int day;
+
+ g_return_if_fail(log != NULL);
+ g_return_if_fail(str != NULL);
+
+ if (log->handle == -1)
+ return;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ day = tm->tm_mday;
+
+ tm = localtime(&log->last);
+ if (tm->tm_mday != day) {
+ /* day changed */
+ log_write_timestamp(log->handle, settings_get_str("log_day_changed"), "\n", t);
+ }
+ log->last = t;
+
+ log_write_timestamp(log->handle, log_timestamp, str, t);
+ write(log->handle, "\n", 1);
+
+ signal_emit("log written", 2, log, str);
+}
+
+void log_file_write(const char *item, int level, const char *str, int no_fallbacks)
+{
+ GSList *tmp, *fallbacks;
+ char *tmpstr;
+ int found;
+
+ g_return_if_fail(str != NULL);
+
+ fallbacks = NULL; found = FALSE;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (rec->handle == -1)
+ continue; /* log not opened yet */
+
+ if ((level & rec->level) == 0)
+ continue;
+
+ if (rec->items == NULL)
+ fallbacks = g_slist_append(fallbacks, rec);
+ else if (item != NULL && strarray_find(rec->items, item) != -1)
+ log_write_rec(rec, str);
+ }
+
+ if (!found && !no_fallbacks && fallbacks != NULL) {
+ /* not found from any items, so write it to all main logs */
+ tmpstr = NULL;
+ if (level & MSGLEVEL_PUBLIC)
+ tmpstr = g_strconcat(item, ": ", str, NULL);
+
+ g_slist_foreach(fallbacks, (GFunc) log_write_rec, tmpstr ? tmpstr : (char *)str);
+ g_free_not_null(tmpstr);
+ }
+ g_slist_free(fallbacks);
+}
+
+void log_write(const char *item, int level, const char *str)
+{
+ log_file_write(item, level, str, TRUE);
+}
+
+LOG_REC *log_find(const char *fname)
+{
+ GSList *tmp;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (strcmp(rec->fname, fname) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+const char *log_rotate2str(int rotate)
+{
+ switch (rotate) {
+ case LOG_ROTATE_HOUR:
+ return "hour";
+ case LOG_ROTATE_DAY:
+ return "day";
+ case LOG_ROTATE_WEEK:
+ return "week";
+ case LOG_ROTATE_MONTH:
+ return "month";
+ }
+
+ return NULL;
+}
+
+int log_str2rotate(const char *str)
+{
+ if (str == NULL)
+ return -1;
+
+ if (g_strncasecmp(str, "hour", 4) == 0)
+ return LOG_ROTATE_HOUR;
+ if (g_strncasecmp(str, "day", 3) == 0)
+ return LOG_ROTATE_DAY;
+ if (g_strncasecmp(str, "week", 4) == 0)
+ return LOG_ROTATE_WEEK;
+ if (g_strncasecmp(str, "month", 5) == 0)
+ return LOG_ROTATE_MONTH;
+ if (g_strncasecmp(str, "never", 5) == 0)
+ return LOG_ROTATE_NEVER;
+
+ return -1;
+}
+
+static void log_set_config(LOG_REC *log)
+{
+ CONFIG_NODE *node;
+ char *levelstr;
+
+ node = iconfig_node_traverse("logs", TRUE);
+ node = config_node_section(node, log->fname, NODE_TYPE_BLOCK);
+
+ if (log->autoopen)
+ config_node_set_bool(node, "auto_open", TRUE);
+ else
+ config_node_set_str(node, "auto_open", NULL);
+
+ config_node_set_str(node, "rotate", log_rotate2str(log->rotate));
+
+ levelstr = bits2level(log->level);
+ config_node_set_str(node, "level", levelstr);
+ g_free(levelstr);
+
+ config_node_set_str(node, "items", NULL);
+
+ if (log->items != NULL && *log->items != NULL) {
+ node = config_node_section(node, "items", NODE_TYPE_LIST);
+ config_node_add_list(node, log->items);
+ }
+}
+
+static void log_remove_config(LOG_REC *log)
+{
+ iconfig_set_str("logs", log->fname, NULL);
+}
+
+LOG_REC *log_create_rec(const char *fname, int level, const char *items)
+{
+ LOG_REC *rec;
+
+ g_return_val_if_fail(fname != NULL, NULL);
+
+ rec = log_find(fname);
+ if (rec == NULL) {
+ rec = g_new0(LOG_REC, 1);
+ rec->fname = g_strdup(fname);
+ rec->handle = -1;
+ } else {
+ g_strfreev(rec->items);
+ }
+
+ rec->items = items == NULL || *items == '\0' ? NULL :
+ g_strsplit(items, " ", -1);
+ rec->level = level;
+ return rec;
+}
+
+void log_update(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ if (log_find(log->fname) == NULL) {
+ logs = g_slist_append(logs, log);
+ log->handle = -1;
+ }
+
+ log_set_config(log);
+ signal_emit("log new", 1, log);
+}
+
+void log_close(LOG_REC *log)
+{
+ g_return_if_fail(log != NULL);
+
+ log_remove_config(log);
+ if (log->handle != -1)
+ log_stop_logging(log);
+
+ logs = g_slist_remove(logs, log);
+ signal_emit("log remove", 1, log);
+
+ if (log->items != NULL) g_strfreev(log->items);
+ g_free(log->fname);
+ g_free(log);
+}
+
+static void sig_printtext_stripped(void *server, const char *item, gpointer levelp, const char *str)
+{
+ int level;
+
+ g_return_if_fail(str != NULL);
+
+ level = GPOINTER_TO_INT(levelp);
+ if (logs != NULL && level != MSGLEVEL_NEVER)
+ log_file_write(item, level, str, FALSE);
+}
+
+static int sig_rotate_check(void)
+{
+ static int last_hour = -1;
+ struct tm tm_now, *tm;
+ GSList *tmp;
+ time_t now;
+
+ /* don't do anything until hour is changed */
+ now = time(NULL);
+ memcpy(&tm_now, localtime(&now), sizeof(tm_now));
+ if (tm_now.tm_hour == last_hour) return 1;
+ last_hour = tm_now.tm_hour;
+
+ for (tmp = logs; tmp != NULL; tmp = tmp->next) {
+ LOG_REC *rec = tmp->data;
+
+ if (rec->handle == -1 || rec->rotate == LOG_ROTATE_NEVER)
+ continue;
+
+ tm = localtime(&rec->opened);
+ if (rec->rotate == LOG_ROTATE_MONTH) {
+ if (tm->tm_mon == tm_now.tm_mon)
+ continue;
+ } else if (rec->rotate == LOG_ROTATE_WEEK) {
+ if (tm->tm_wday != 1 || tm->tm_mday == tm_now.tm_mday)
+ continue;
+ } else if (rec->rotate == LOG_ROTATE_DAY) {
+ if (tm->tm_mday == tm_now.tm_mday)
+ continue;
+ }
+
+ log_stop_logging(rec);
+ log_start_logging(rec);
+ }
+
+ return 1;
+}
+
+static void log_read_config(void)
+{
+ CONFIG_NODE *node;
+ LOG_REC *log;
+ GSList *tmp;
+
+ while (logs != NULL)
+ log_close(logs->data);
+
+ node = iconfig_node_traverse("logs", FALSE);
+ if (node == NULL) return;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ log = g_new0(LOG_REC, 1);
+ logs = g_slist_append(logs, log);
+
+ log->handle = -1;
+ log->fname = g_strdup(node->key);
+ log->autoopen = config_node_get_bool(node, "auto_open", FALSE);
+ log->level = level2bits(config_node_get_str(node, "level", 0));
+ log->rotate = log_str2rotate(config_node_get_str(node, "rotate", NULL));
+ if (log->rotate < 0) log->rotate = LOG_ROTATE_NEVER;
+
+ node = config_node_section(node, "items", -1);
+ if (node != NULL) log->items = config_node_get_list(node);
+
+ if (log->autoopen) log_start_logging(log);
+ }
+}
+
+static void read_settings(void)
+{
+ log_timestamp = settings_get_str("log_timestamp");
+ log_file_create_mode = octal2dec(settings_get_int("log_create_mode"));
+}
+
+void log_init(void)
+{
+ rotate_tag = g_timeout_add(60000, (GSourceFunc) sig_rotate_check, NULL);
+ logs = NULL;
+
+ settings_add_int("log", "log_create_mode", LOG_FILE_CREATE_MODE);
+ settings_add_str("log", "log_timestamp", "%H:%M ");
+ settings_add_str("log", "log_open_string", "--- Log opened %a %b %d %H:%M:%S %Y");
+ settings_add_str("log", "log_close_string", "--- Log closed %a %b %d %H:%M:%S %Y");
+ settings_add_str("log", "log_day_changed", "--- Day changed %a %b %d %Y");
+
+ read_settings();
+ log_read_config();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("setup reread", (SIGNAL_FUNC) log_read_config);
+ signal_add("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped);
+}
+
+void log_deinit(void)
+{
+ g_source_remove(rotate_tag);
+
+ while (logs != NULL)
+ log_close(logs->data);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("setup reread", (SIGNAL_FUNC) log_read_config);
+ signal_remove("print text stripped", (SIGNAL_FUNC) sig_printtext_stripped);
+}
diff --git a/src/core/log.h b/src/core/log.h
new file mode 100644
index 00000000..599be9e2
--- /dev/null
+++ b/src/core/log.h
@@ -0,0 +1,49 @@
+#ifndef __LOG_H
+#define __LOG_H
+
+enum {
+ LOG_ROTATE_NEVER,
+ LOG_ROTATE_HOUR,
+ LOG_ROTATE_DAY,
+ LOG_ROTATE_WEEK,
+ LOG_ROTATE_MONTH
+};
+
+typedef struct {
+ char *fname; /* file name */
+ int handle; /* file handle */
+ time_t opened;
+
+ int level; /* log only these levels */
+ char **items; /* log only on these items (channels, queries, window refnums) */
+
+ time_t last; /* when last message was written */
+ int rotate;
+
+ int autoopen:1; /* automatically start logging at startup */
+ int temp:1; /* don't save this to config file */
+} LOG_REC;
+
+extern GSList *logs;
+
+/* Create log record - you still need to call log_update() to actually add it
+ into log list */
+LOG_REC *log_create_rec(const char *fname, int level, const char *items);
+void log_update(LOG_REC *log);
+void log_close(LOG_REC *log);
+
+LOG_REC *log_find(const char *fname);
+
+void log_write(const char *item, int level, const char *str);
+void log_write_rec(LOG_REC *log, const char *str);
+
+const char *log_rotate2str(int rotate);
+int log_str2rotate(const char *str);
+
+int log_start_logging(LOG_REC *log);
+void log_stop_logging(LOG_REC *log);
+
+void log_init(void);
+void log_deinit(void);
+
+#endif
diff --git a/src/core/memdebug.c b/src/core/memdebug.c
new file mode 100644
index 00000000..1be7bbd8
--- /dev/null
+++ b/src/core/memdebug.c
@@ -0,0 +1,356 @@
+/*
+ memdebug.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define ENABLE_BUFFER_CHECKS
+#define BUFFER_CHECK_SIZE 5
+#define MIN_BUFFER_CHECK_SIZE 2
+
+typedef struct {
+ void *p;
+ int size;
+ char *file;
+ int line;
+ char *comment;
+} MEM_REC;
+
+static GHashTable *data = NULL, *preallocs = NULL;
+static const char *comment = "";
+
+static void add_flow_checks(guchar *p, unsigned long size)
+{
+#ifdef ENABLE_BUFFER_CHECKS
+ int n;
+
+ for (n = 0; n < BUFFER_CHECK_SIZE; n++)
+ p[n] = n ^ 0x7f;
+ for (n = 0; n < BUFFER_CHECK_SIZE; n++)
+ p[size-BUFFER_CHECK_SIZE+n] = n ^ 0x7f;
+#endif
+}
+
+void ig_memcheck_rec(void *key, MEM_REC *rec)
+{
+ guchar *p;
+ int n;
+
+ if (rec->size != INT_MIN){
+ p = rec->p;
+
+ for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++)
+ if (p[n] != (n ^ 0x7f))
+ g_error("buffer underflow, file %s line %d!\n", rec->file, rec->line);
+
+ for (n = 0; n < MIN_BUFFER_CHECK_SIZE; n++)
+ if (p[rec->size-BUFFER_CHECK_SIZE+n] != (n ^ 0x7f))
+ g_error("buffer overflow, file %s line %d!\n", rec->file, rec->line);
+ }
+}
+
+static void mem_check(void)
+{
+#ifdef ENABLE_BUFFER_CHECKS
+ g_hash_table_foreach(data, (GHFunc) ig_memcheck_rec, NULL);
+#endif
+}
+
+static void data_add(void *p, int size, const char *file, int line)
+{
+ MEM_REC *rec;
+
+ if (size <= 0 && size != INT_MIN)
+ g_error("size = %d, file %s line %d", size, file, line);
+
+ if (data == NULL) {
+ data = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ preallocs = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ }
+
+ if (g_hash_table_lookup(data, p) != NULL)
+ g_error("data_add() already malloc()'ed %p (in %s:%d)", p, file, line);
+
+ rec = g_new(MEM_REC, 1);
+ g_hash_table_insert(data, p, rec);
+
+ rec->p = p;
+ rec->size = size;
+ rec->file = (char *) file;
+ rec->line = line;
+ rec->comment = g_strdup(comment);
+
+ if (size == INT_MIN)
+ g_hash_table_insert(preallocs, p-BUFFER_CHECK_SIZE, p);
+ else
+ add_flow_checks(p, size);
+ mem_check();
+}
+
+static void *data_remove(void *p, const char *file, int line)
+{
+ MEM_REC *rec;
+
+ mem_check();
+
+ if (g_hash_table_lookup(preallocs, p) != NULL) {
+ g_hash_table_remove(preallocs, p);
+ p += BUFFER_CHECK_SIZE;
+ }
+
+ rec = g_hash_table_lookup(data, p);
+ if (rec == NULL) {
+ g_warning("data_remove() data %p not found (in %s:%d)", p, file, line);
+ return p+BUFFER_CHECK_SIZE;
+ }
+
+ g_hash_table_remove(data, p);
+ g_free(rec->comment);
+ g_free(rec);
+
+ return p;
+}
+
+void *ig_malloc(int size, const char *file, int line)
+{
+#if 1
+ void *p;
+
+ size += BUFFER_CHECK_SIZE*2;
+ p = g_malloc(size);
+ data_add(p, size, file, line);
+ return p+BUFFER_CHECK_SIZE;
+#else
+ return g_malloc(size);
+#endif
+}
+
+void *ig_malloc0(int size, const char *file, int line)
+{
+#if 1
+ void *p;
+
+ size += BUFFER_CHECK_SIZE*2;
+ p = g_malloc0(size);
+ data_add(p, size, file, line);
+ return p+BUFFER_CHECK_SIZE;
+#else
+ return g_malloc0(size);
+#endif
+}
+
+void *ig_realloc(void *mem, unsigned long size, const char *file, int line)
+{
+#if 1
+ void *p;
+
+ size += BUFFER_CHECK_SIZE*2;
+ mem -= BUFFER_CHECK_SIZE;
+ data_remove(mem, file, line);
+ p = g_realloc(mem, size);
+ data_add(p, size, file, line);
+ return p+BUFFER_CHECK_SIZE;
+#else
+ return g_realloc(mem, size);
+#endif
+}
+
+char *ig_strdup(const char *str, const char *file, int line)
+{
+ void *p;
+
+ if (str == NULL) return NULL;
+
+ p = ig_malloc(strlen(str)+1, file, line);
+ strcpy(p, str);
+
+ return p;
+}
+
+char *ig_strndup(const char *str, int count, const char *file, int line)
+{
+ char *p;
+
+ if (str == NULL) return NULL;
+
+ p = ig_malloc(count+1, file, line);
+ strncpy(p, str, count); p[count] = '\0';
+
+ return p;
+}
+
+char *ig_strconcat(const char *file, int line, const char *str, ...)
+{
+ guint l;
+ va_list args;
+ char *s;
+ char *concat;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ l = 1 + strlen (str);
+ va_start (args, str);
+ s = va_arg (args, char*);
+ while (s)
+ {
+ l += strlen (s);
+ s = va_arg (args, char*);
+ }
+ va_end (args);
+
+ concat = ig_malloc(l, file, line);
+ concat[0] = 0;
+
+ strcat (concat, str);
+ va_start (args, str);
+ s = va_arg (args, char*);
+ while (s)
+ {
+ strcat (concat, s);
+ s = va_arg (args, char*);
+ }
+ va_end (args);
+
+ return concat;
+}
+
+char *ig_strdup_printf(const char *file, int line, const char *format, ...)
+{
+ char *buffer, *p;
+ va_list args;
+
+ va_start (args, format);
+ buffer = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ p = ig_malloc(strlen(buffer)+1, file, line);
+ strcpy(p, buffer);
+ g_free(buffer);
+
+ return p;
+}
+
+char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args)
+{
+ char *buffer, *p;
+
+ buffer = g_strdup_vprintf (format, args);
+
+ p = ig_malloc(strlen(buffer)+1, file, line);
+ strcpy(p, buffer);
+ g_free(buffer);
+
+ return p;
+}
+
+void ig_free(void *p)
+{
+#if 1
+ p -= BUFFER_CHECK_SIZE;
+ p = data_remove(p, "??", 0);
+ if (p != NULL)
+#endif
+ g_free(p);
+}
+
+GString *ig_string_new(const char *file, int line, const char *str)
+{
+ GString *ret;
+
+ ret = g_string_new(str);
+ data_add(ret, INT_MIN, file, line);
+ return ret;
+}
+
+void ig_string_free(const char *file, int line, GString *str, gboolean freeit)
+{
+ data_remove(str, file, line);
+ if (!freeit)
+ data_add(str->str, INT_MIN, file, line);
+
+ g_string_free(str, freeit);
+}
+
+char *ig_strjoinv(const char *file, int line, const char *sepa, char **array)
+{
+ char *ret;
+
+ ret = g_strjoinv(sepa, array);
+ data_add(ret, INT_MIN, file, line);
+ return ret;
+}
+
+void ig_profile_line(void *key, MEM_REC *rec)
+{
+ char *data;
+
+ if (*rec->comment == '\0' &&
+ (strcmp(rec->file, "ig_strdup_printf") == 0 ||
+ strcmp(rec->file, "ig_strdup_vprintf") == 0 ||
+ strcmp(rec->file, "ig_strconcat") == 0 ||
+ strcmp(rec->file, "ig_string_free (free = FALSE)") == 0))
+ data = rec->p + BUFFER_CHECK_SIZE;
+ else
+ data = rec->comment;
+ fprintf(stderr, "%s:%d %d bytes (%s)\n", rec->file, rec->line, rec->size, data);
+}
+
+void ig_mem_profile(void)
+{
+ g_hash_table_foreach(data, (GHFunc) ig_profile_line, NULL);
+ g_hash_table_destroy(data);
+ g_hash_table_destroy(preallocs);
+}
+
+static MEM_REC *largest[10];
+
+void ig_profile_largest(void *key, MEM_REC *rec)
+{
+ int n;
+
+ for (n = 0; n < 10; n++)
+ {
+ if (largest[n] == NULL || rec->size > largest[n]->size)
+ {
+ g_memmove(largest+n+1, largest+n, sizeof(void *)*(9-n));
+ largest[n] = rec;
+ }
+ }
+}
+
+void ig_mem_profile_largest(void)
+{
+ /*int n;*/
+
+ memset(&largest, 0, sizeof(MEM_REC*)*10);
+ /*g_hash_table_foreach(data, (GHFunc) ig_profile_largest, NULL);
+
+ for (n = 0; n < 10 && largest[n] != NULL; n++)
+ {
+ ig_profile_line(NULL, largest[n]);
+ }*/
+}
+
+void ig_set_data(const char *data)
+{
+ comment = data;
+}
diff --git a/src/core/memdebug.h b/src/core/memdebug.h
new file mode 100644
index 00000000..1cf187f1
--- /dev/null
+++ b/src/core/memdebug.h
@@ -0,0 +1,31 @@
+#ifdef MEM_DEBUG
+void ig_mem_profile(void);
+
+void ig_set_data(const char *data);
+
+void *ig_malloc(int size, const char *file, int line);
+void *ig_malloc0(int size, const char *file, int line);
+void *ig_realloc(void *mem, unsigned long size, const char *file, int line);
+char *ig_strdup(const char *str, const char *file, int line);
+char *ig_strndup(const char *str, int count, const char *file, int line);
+char *ig_strconcat(const char *file, int line, const char *str, ...);
+char *ig_strdup_printf(const char *file, int line, const char *format, ...) G_GNUC_PRINTF (3, 4);
+char *ig_strdup_vprintf(const char *file, int line, const char *format, va_list args);
+void ig_free(void *p);
+GString *ig_string_new(const char *file, int line, const char *str);
+void ig_string_free(const char *file, int line, GString *str, int freeit);
+char *ig_strjoinv(const char *file, int line, const char *sepa, char **array);
+
+#define g_malloc(a) ig_malloc(a, __FILE__, __LINE__)
+#define g_malloc0(a) ig_malloc0(a, __FILE__, __LINE__)
+#define g_realloc(a,b) ig_realloc(a, b, __FILE__, __LINE__)
+#define g_strdup(a) ig_strdup(a, __FILE__, __LINE__)
+#define g_strndup(a, b) ig_strndup(a, b, __FILE__, __LINE__)
+#define g_strconcat(a...) ig_strconcat(__FILE__, __LINE__, ##a)
+#define g_strdup_printf(a, b...) ig_strdup_printf(__FILE__, __LINE__, a, ##b)
+#define g_strdup_vprintf(a, b...) ig_strdup_vprintf(__FILE__, __LINE__, a, ##b)
+#define g_free ig_free
+#define g_string_new(a) ig_string_new(__FILE__, __LINE__, a)
+#define g_string_free(a, b) ig_string_free(__FILE__, __LINE__, a, b)
+#define g_strjoinv(a,b) ig_strjoinv(__FILE__, __LINE__, a, b)
+#endif
diff --git a/src/core/misc.c b/src/core/misc.c
new file mode 100644
index 00000000..28aaae84
--- /dev/null
+++ b/src/core/misc.c
@@ -0,0 +1,467 @@
+/*
+ misc.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "misc.h"
+#include "pidwait.h"
+
+#include <errno.h>
+#include <regex.h>
+
+typedef struct {
+ GInputCondition condition;
+ GInputFunction function;
+ gpointer data;
+} IRSSI_INPUT_REC;
+
+static gboolean irssi_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ IRSSI_INPUT_REC *rec = data;
+ GInputCondition icond = 0;
+
+ if (condition & (G_IO_IN | G_IO_PRI))
+ icond |= G_INPUT_READ;
+ if (condition & G_IO_OUT)
+ icond |= G_INPUT_WRITE;
+ if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ icond |= G_INPUT_EXCEPTION;
+
+ if (rec->condition & icond)
+ rec->function(rec->data, g_io_channel_unix_get_fd(source), icond);
+
+ return TRUE;
+}
+
+int g_input_add(int source, GInputCondition condition,
+ GInputFunction function, gpointer data)
+{
+ IRSSI_INPUT_REC *rec;
+ unsigned int result;
+ GIOChannel *channel;
+ GIOCondition cond = 0;
+
+ rec = g_new(IRSSI_INPUT_REC, 1);
+ rec->condition = condition;
+ rec->function = function;
+ rec->data = data;
+
+ if (condition & G_INPUT_READ)
+ cond |= (G_IO_IN | G_IO_PRI);
+ if (condition & G_INPUT_WRITE)
+ cond |= G_IO_OUT;
+ if (condition & G_INPUT_EXCEPTION)
+ cond |= G_IO_ERR|G_IO_HUP|G_IO_NVAL;
+
+ channel = g_io_channel_unix_new (source);
+ result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
+ irssi_io_invoke, rec, g_free);
+ g_io_channel_unref(channel);
+
+ return result;
+}
+
+long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2)
+{
+ long secs, usecs;
+
+ secs = tv1->tv_sec - tv2->tv_sec;
+ usecs = tv1->tv_usec - tv2->tv_usec;
+ if (usecs < 0) {
+ usecs += 1000000;
+ secs--;
+ }
+ usecs = usecs/1000 + secs * 1000;
+
+ return usecs;
+}
+
+int find_substr(const char *list, const char *item)
+{
+ const char *ptr;
+
+ g_return_val_if_fail(list != NULL, FALSE);
+ g_return_val_if_fail(item != NULL, FALSE);
+
+ if (*item == '\0')
+ return FALSE;
+
+ for (;;) {
+ while (isspace((gint) *list)) list++;
+ if (*list == '\0') break;
+
+ ptr = strchr(list, ' ');
+ if (ptr == NULL) ptr = list+strlen(list);
+
+ if (g_strncasecmp(list, item, ptr-list) == 0 && item[ptr-list] == '\0')
+ return TRUE;
+
+ list = ptr;
+ }
+
+ return FALSE;
+}
+
+int strarray_length(char **array)
+{
+ int len;
+
+ g_return_val_if_fail(array != NULL, 0);
+
+ len = 0;
+ while (*array) {
+ len++;
+ array++;
+ }
+ return len;
+}
+
+int strarray_find(char **array, const char *item)
+{
+ char **tmp;
+ int index;
+
+ g_return_val_if_fail(array != NULL, 0);
+ g_return_val_if_fail(item != NULL, 0);
+
+ index = 0;
+ for (tmp = array; *tmp != NULL; tmp++, index++) {
+ if (g_strcasecmp(*tmp, item) == 0)
+ return index;
+ }
+
+ return -1;
+}
+
+int copyfile(const char *src, const char *dest)
+{
+ FILE *fs, *fd;
+ int ret;
+
+ g_return_val_if_fail(src != NULL, FALSE);
+ g_return_val_if_fail(dest != NULL, FALSE);
+
+ fs = fopen(src, "rb");
+ if (fs == NULL) return FALSE;
+
+ ret = FALSE;
+ remove(dest); /* just to be sure there's no link already in dest */
+
+ fd = fopen(dest, "w+");
+ if (fd != NULL) {
+ int len;
+ char buf[1024];
+
+ while ((len = fread(buf, 1, sizeof(buf), fs)) > 0)
+ if (fwrite(buf, 1, len, fd) != len) break;
+ fclose(fd);
+ ret = TRUE;
+ }
+
+ fclose(fs);
+ return ret;
+}
+
+int execute(const char *cmd)
+{
+ char **args;
+ char *prev, *dupcmd;
+ int pid, max, cnt;
+
+ g_return_val_if_fail(cmd != NULL, -1);
+
+ pid = fork();
+ if (pid == -1) return FALSE;
+ if (pid != 0) {
+ pidwait_add(pid);
+ return pid;
+ }
+
+ dupcmd = g_strdup(cmd);
+ max = 5; cnt = 0;
+ args = g_malloc(sizeof(char *)*max);
+ for (prev = dupcmd; ; dupcmd++) {
+ if (*dupcmd == '\0' || *dupcmd == ' ') {
+ args[cnt++] = prev;
+ if (cnt == max) {
+ max += 5;
+ args = g_realloc(args, sizeof(char *)*max);
+ }
+ if (*dupcmd == '\0') break;
+ *dupcmd++ = '\0';
+ prev = dupcmd;
+ }
+ }
+ args[cnt] = NULL;
+
+ execvp(args[0], args);
+ g_free(dupcmd);
+
+ _exit(99);
+ return -1;
+}
+
+GSList *gslist_find_string(GSList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (strcmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+GSList *gslist_find_icase_string(GSList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (g_strcasecmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, void *data)
+{
+ void *ret;
+
+ while (list != NULL) {
+ ret = func(list->data, data);
+ if (ret != NULL) return ret;
+
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+char *gslist_to_string(GSList *list, int offset, const char *delimiter)
+{
+ GString *str;
+ char **data, *ret;
+
+ str = g_string_new(NULL);
+ while (list != NULL) {
+ data = G_STRUCT_MEMBER_P(list->data, offset);
+
+ if (str->len != 0) g_string_append(str, delimiter);
+ g_string_append(str, *data);
+ list = list->next;
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+GList *glist_find_string(GList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (strcmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+GList *glist_find_icase_string(GList *list, const char *key)
+{
+ for (list = list; list != NULL; list = list->next)
+ if (g_strcasecmp(list->data, key) == 0) return list;
+
+ return NULL;
+}
+
+char *stristr(const char *data, const char *key)
+{
+ const char *pos, *max;
+ int keylen, datalen;
+
+ keylen = strlen(key);
+ datalen = strlen(data);
+
+ if (keylen > datalen)
+ return NULL;
+
+ max = data+datalen-keylen;
+ for (pos = data; pos <= max; pos++)
+ if (g_strncasecmp(pos, key, keylen) == 0) return (char *) pos;
+
+ return NULL;
+}
+
+#define isbound(c) \
+ ((unsigned char) (c) < 128 && \
+ (isspace((int) (c)) || ispunct((int) (c))))
+
+char *stristr_full(const char *data, const char *key)
+{
+ const char *pos, *max;
+ int keylen, datalen;
+
+ keylen = strlen(key);
+ datalen = strlen(data);
+
+ if (keylen > datalen)
+ return NULL;
+
+ max = data+datalen-keylen;
+ for (pos = data; pos <= max; pos++) {
+ if (pos > data && !isbound(pos[-1])) continue;
+
+ if (g_strncasecmp(pos, key, keylen) == 0 &&
+ (pos[keylen] == '\0' || isbound(pos[keylen])))
+ return (char *) pos;
+ }
+
+ return NULL;
+}
+
+int regexp_match(const char *str, const char *regexp)
+{
+ regex_t preg;
+ int ret;
+
+ if (regcomp(&preg, regexp, REG_EXTENDED|REG_ICASE|REG_NOSUB) != 0)
+ return 0;
+
+ ret = regexec(&preg, str, 0, NULL, 0);
+ regfree(&preg);
+
+ return ret == 0;
+}
+
+char *convert_home(const char *path)
+{
+ return *path == '~' && (*(path+1) == '/' || *(path+1) == '\0') ?
+ g_strconcat(g_get_home_dir(), path+1, NULL) :
+ g_strdup(path);
+}
+
+int g_istr_equal(gconstpointer v, gconstpointer v2)
+{
+ return g_strcasecmp((const char *) v, (const char *) v2) == 0;
+}
+
+/* a char* hash function from ASU */
+unsigned int g_istr_hash(gconstpointer v)
+{
+ const char *s = (char *) v;
+ unsigned int h = 0, g;
+
+ while (*s != '\0') {
+ h = (h << 4) + toupper(*s);
+ if ((g = h & 0xf0000000)) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ s++;
+ }
+
+ return h /* % M */;
+}
+
+/* Find `mask' from `data', you can use * and ? wildcards. */
+int match_wildcards(const char *cmask, const char *data)
+{
+ char *mask, *newmask, *p1, *p2;
+ int ret;
+
+ newmask = mask = strdup(cmask);
+ for (; *mask != '\0' && *data != '\0'; mask++) {
+ if (*mask == '?' || toupper(*mask) == toupper(*data)) {
+ data++;
+ continue;
+ }
+
+ if (*mask != '*')
+ break;
+
+ while (*mask == '?' || *mask == '*') mask++;
+ if (*mask == '\0') {
+ data += strlen(data);
+ break;
+ }
+
+ p1 = strchr(mask, '*');
+ p2 = strchr(mask, '?');
+ if (p1 == NULL || (p2 < p1 && p2 != NULL)) p1 = p2;
+
+ if (p1 != NULL) *p1 = '\0';
+
+ data = stristr(data, mask);
+ if (data == NULL) break;
+
+ data += strlen(mask);
+ mask += strlen(mask)-1;
+
+ if (p1 != NULL) *p1 = p1 == p2 ? '?' : '*';
+ }
+
+ ret = data != NULL && *data == '\0' && *mask == '\0';
+ free(newmask);
+
+ return ret;
+}
+
+/* Return TRUE if all characters in `str' are numbers.
+ Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char)
+{
+ g_return_val_if_fail(str != NULL, FALSE);
+
+ while (*str != '\0' && *str != end_char) {
+ if (!isdigit(*str)) return FALSE;
+ str++;
+ }
+
+ return TRUE;
+}
+
+/* replace all `from' chars in string to `to' chars. returns `str' */
+char *replace_chars(char *str, char from, char to)
+{
+ char *p;
+
+ for (p = str; *p != '\0'; p++) {
+ if (*p == from) *p = to;
+ }
+ return str;
+}
+
+int octal2dec(int octal)
+{
+ int dec, n;
+
+ dec = 0; n = 1;
+ while (octal != 0) {
+ dec += n*(octal%10);
+ octal /= 10; n *= 8;
+ }
+
+ return dec;
+}
+
+int dec2octal(int decimal)
+{
+ int octal, pos;
+
+ octal = 0; pos = 0;
+ while (decimal > 0) {
+ octal += (decimal & 7)*(pos == 0 ? 1 : pos);
+ decimal /= 8;
+ pos += 10;
+ }
+
+ return octal;
+}
diff --git a/src/core/misc.h b/src/core/misc.h
new file mode 100644
index 00000000..b836e3dd
--- /dev/null
+++ b/src/core/misc.h
@@ -0,0 +1,57 @@
+#ifndef __MISC_H
+#define __MISC_H
+
+/* `str' should be type char[MAX_INT_STRLEN] */
+#define ltoa(str, num) \
+ g_snprintf(str, sizeof(str), "%d", num)
+
+typedef void* (*FOREACH_FIND_FUNC) (void *item, void *data);
+
+long get_timeval_diff(const GTimeVal *tv1, const GTimeVal *tv2);
+
+/* find `item' from a space separated `list' */
+int find_substr(const char *list, const char *item);
+/* return how many items `array' has */
+int strarray_length(char **array);
+/* return index of `item' in `array' or -1 if not found */
+int strarray_find(char **array, const char *item);
+
+int copyfile(const char *src, const char *dest);
+int execute(const char *cmd); /* returns pid or -1 = error */
+
+GSList *gslist_find_string(GSList *list, const char *key);
+GSList *gslist_find_icase_string(GSList *list, const char *key);
+GList *glist_find_string(GList *list, const char *key);
+GList *glist_find_icase_string(GList *list, const char *key);
+
+void *gslist_foreach_find(GSList *list, FOREACH_FIND_FUNC func, void *data);
+char *gslist_to_string(GSList *list, int offset, const char *delimiter);
+
+/* strstr() with case-ignoring */
+char *stristr(const char *data, const char *key);
+/* stristr(), but matches only for full words */
+char *stristr_full(const char *data, const char *key);
+/* easy way to check if regexp matches */
+int regexp_match(const char *str, const char *regexp);
+
+char *convert_home(const char *path);
+
+/* Case-insensitive string hash functions */
+int g_istr_equal(gconstpointer v, gconstpointer v2);
+unsigned int g_istr_hash(gconstpointer v);
+
+/* Find `mask' from `data', you can use * and ? wildcards. */
+int match_wildcards(const char *mask, const char *data);
+
+/* Return TRUE if all characters in `str' are numbers.
+ Stop when `end_char' is found from string. */
+int is_numeric(const char *str, char end_char);
+
+/* replace all `from' chars in string to `to' chars. returns `str' */
+char *replace_chars(char *str, char from, char to);
+
+/* octal <-> decimal conversions */
+int octal2dec(int octal);
+int dec2octal(int decimal);
+
+#endif
diff --git a/src/core/module.h b/src/core/module.h
new file mode 100644
index 00000000..89784389
--- /dev/null
+++ b/src/core/module.h
@@ -0,0 +1,3 @@
+#include "common.h"
+
+#define MODULE_NAME "core"
diff --git a/src/core/modules.c b/src/core/modules.c
new file mode 100644
index 00000000..d850f60d
--- /dev/null
+++ b/src/core/modules.c
@@ -0,0 +1,185 @@
+/*
+ modules.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "modules.h"
+
+static GHashTable *uniqids, *uniqstrids;
+static GHashTable *idlookup, *stridlookup;
+static int next_uniq_id;
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id)
+{
+ GHashTable *ids;
+ gpointer origkey, uniqid;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ids = g_hash_table_lookup(idlookup, module);
+ if (ids == NULL) {
+ /* new module */
+ ids = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ g_hash_table_insert(idlookup, g_strdup(module), ids);
+ }
+
+ if (!g_hash_table_lookup_extended(ids, GINT_TO_POINTER(id), &origkey, &uniqid)) {
+ /* not found */
+ ret = next_uniq_id++;
+ g_hash_table_insert(ids, GINT_TO_POINTER(id), GINT_TO_POINTER(ret));
+ g_hash_table_insert(uniqids, GINT_TO_POINTER(ret), GINT_TO_POINTER(id));
+ } else {
+ ret = GPOINTER_TO_INT(uniqid);
+ }
+
+ return ret;
+}
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id_str(const char *module, const char *id)
+{
+ GHashTable *ids;
+ gpointer origkey, uniqid;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ids = g_hash_table_lookup(stridlookup, module);
+ if (ids == NULL) {
+ /* new module */
+ ids = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
+ g_hash_table_insert(stridlookup, g_strdup(module), ids);
+ }
+
+ if (!g_hash_table_lookup_extended(ids, id, &origkey, &uniqid)) {
+ /* not found */
+ char *saveid;
+
+ saveid = g_strdup(id);
+ ret = next_uniq_id++;
+ g_hash_table_insert(ids, saveid, GINT_TO_POINTER(ret));
+ g_hash_table_insert(uniqstrids, GINT_TO_POINTER(ret), saveid);
+ } else {
+ ret = GPOINTER_TO_INT(uniqid);
+ }
+
+ return ret;
+}
+
+/* returns the original module specific id, -1 = not found */
+int module_find_id(const char *module, int uniqid)
+{
+ GHashTable *ids;
+ gpointer origkey, id;
+ int ret;
+
+ g_return_val_if_fail(module != NULL, -1);
+
+ ret = g_hash_table_lookup_extended(uniqids, GINT_TO_POINTER(uniqid), &origkey, &id) ?
+ GPOINTER_TO_INT(id) : -1;
+
+ if (ret != -1) {
+ /* check that module matches */
+ ids = g_hash_table_lookup(idlookup, module);
+ if (ids == NULL || !g_hash_table_lookup_extended(ids, GINT_TO_POINTER(ret), &origkey, &id))
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/* returns the original module specific id, NULL = not found */
+const char *module_find_id_str(const char *module, int uniqid)
+{
+ GHashTable *ids;
+ gpointer origkey, id;
+ const char *ret;
+
+ g_return_val_if_fail(module != NULL, NULL);
+
+ ret = g_hash_table_lookup_extended(uniqstrids, GINT_TO_POINTER(uniqid),
+ &origkey, &id) ? id : NULL;
+
+ if (ret != NULL) {
+ /* check that module matches */
+ ids = g_hash_table_lookup(stridlookup, module);
+ if (ids == NULL || !g_hash_table_lookup_extended(ids, GINT_TO_POINTER(ret), &origkey, &id))
+ ret = NULL;
+ }
+
+ return ret;
+}
+
+static void gh_uniq_destroy(gpointer key, gpointer value)
+{
+ g_hash_table_remove(uniqids, value);
+}
+
+static void gh_uniq_destroy_str(gpointer key, gpointer value)
+{
+ g_hash_table_remove(uniqstrids, value);
+ g_free(key);
+}
+
+/* Destroy unique IDs from `module'. This function is automatically called
+ when module is destroyed with module's name as the parameter. */
+void module_uniq_destroy(const char *module)
+{
+ GHashTable *ids;
+ gpointer key;
+
+ if (g_hash_table_lookup_extended(idlookup, module, &key, (gpointer *) &ids)) {
+ g_hash_table_remove(idlookup, key);
+ g_free(key);
+
+ g_hash_table_foreach(ids, (GHFunc) gh_uniq_destroy, NULL);
+ g_hash_table_destroy(ids);
+ }
+
+ if (g_hash_table_lookup_extended(stridlookup, module, &key, (gpointer *) &ids)) {
+ g_hash_table_remove(stridlookup, key);
+ g_free(key);
+
+ g_hash_table_foreach(ids, (GHFunc) gh_uniq_destroy_str, NULL);
+ g_hash_table_destroy(ids);
+ }
+}
+
+void modules_init(void)
+{
+ idlookup = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
+ uniqids = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+
+ stridlookup = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
+ uniqstrids = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+ next_uniq_id = 0;
+}
+
+void modules_deinit(void)
+{
+ g_hash_table_foreach(idlookup, (GHFunc) module_uniq_destroy, NULL);
+ g_hash_table_destroy(idlookup);
+ g_hash_table_destroy(uniqids);
+
+ g_hash_table_foreach(stridlookup, (GHFunc) module_uniq_destroy, NULL);
+ g_hash_table_destroy(stridlookup);
+ g_hash_table_destroy(uniqstrids);
+}
diff --git a/src/core/modules.h b/src/core/modules.h
new file mode 100644
index 00000000..f175957b
--- /dev/null
+++ b/src/core/modules.h
@@ -0,0 +1,33 @@
+#ifndef __MODULES_H
+#define __MODULES_H
+
+#define MODULE_DATA_INIT(rec) \
+ (rec)->module_data = g_hash_table_new(g_str_hash, g_str_equal)
+
+#define MODULE_DATA_DEINIT(rec) \
+ g_hash_table_destroy((rec)->module_data)
+
+#define MODULE_DATA_SET(rec, data) \
+ g_hash_table_insert((rec)->module_data, MODULE_NAME, data)
+
+#define MODULE_DATA(rec) \
+ g_hash_table_lookup((rec)->module_data, MODULE_NAME)
+
+/* return unique number across all modules for `id' */
+int module_get_uniq_id(const char *module, int id);
+/* return unique number across all modules for `id'. */
+int module_get_uniq_id_str(const char *module, const char *id);
+
+/* returns the original module specific id, -1 = not found */
+int module_find_id(const char *module, int uniqid);
+/* returns the original module specific id, NULL = not found */
+const char *module_find_id_str(const char *module, int uniqid);
+
+/* Destroy unique IDs from `module'. This function is automatically called
+ when module is destroyed with module's name as the parameter. */
+void module_uniq_destroy(const char *module);
+
+void modules_init(void);
+void modules_deinit(void);
+
+#endif
diff --git a/src/core/net-disconnect.c b/src/core/net-disconnect.c
new file mode 100644
index 00000000..9f95a34f
--- /dev/null
+++ b/src/core/net-disconnect.c
@@ -0,0 +1,155 @@
+/*
+ net-disconnect.c :
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "network.h"
+
+/* when quitting, wait for max. 5 seconds before forcing to close the socket */
+#define MAX_QUIT_CLOSE_WAIT 5
+
+/* wait for max. 2 minutes for other side to close the socket */
+#define MAX_CLOSE_WAIT (60*2)
+
+typedef struct {
+ time_t created;
+ int handle;
+ int tag;
+} NET_DISCONNECT_REC;
+
+static GSList *disconnects;
+
+static int timeout_tag;
+
+static void net_disconnect_remove(NET_DISCONNECT_REC *rec)
+{
+ disconnects = g_slist_remove(disconnects, rec);
+
+ g_source_remove(rec->tag);
+ g_free(rec);
+}
+
+static void sig_disconnect(NET_DISCONNECT_REC *rec)
+{
+ char buf[128];
+ int ret;
+
+ /* check if there's any data waiting in socket */
+ for (;;) {
+ ret = net_receive(rec->handle, buf, sizeof(buf));
+ if (ret <= 0)
+ break;
+ }
+
+ if (ret == -1) {
+ /* socket was closed */
+ net_disconnect_remove(rec);
+ }
+}
+
+static int sig_timeout_disconnect(void)
+{
+ NET_DISCONNECT_REC *rec;
+ GSList *tmp, *next;
+ time_t now;
+ int ret;
+
+ /* check if we've waited enough for sockets to close themselves */
+ now = time(NULL);
+ for (tmp = disconnects; tmp != NULL; tmp = next) {
+ rec = tmp->data;
+ next = tmp->next;
+
+ if (rec->created+MAX_CLOSE_WAIT <= now)
+ sig_disconnect(rec);
+ }
+
+ if (disconnects == NULL) {
+ /* no more sockets in disconnect queue, stop calling this
+ function */
+ timeout_tag = -1;
+ }
+ ret = disconnects != NULL ? 1 : 0;
+
+ return ret;
+}
+
+/* Try to let the other side close the connection, if it still isn't
+ disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(int handle)
+{
+ NET_DISCONNECT_REC *rec;
+
+ rec = g_new(NET_DISCONNECT_REC, 1);
+ disconnects = g_slist_append(disconnects, rec);
+
+ rec->created = time(NULL);
+ rec->handle = handle;
+ rec->tag = g_input_add(handle, G_INPUT_READ, (GInputFunction) sig_disconnect, rec);
+
+ if (timeout_tag == -1)
+ timeout_tag = g_timeout_add(10000, (GSourceFunc) sig_timeout_disconnect, NULL);
+}
+
+void net_disconnect_init(void)
+{
+ disconnects = NULL;
+ timeout_tag = -1;
+}
+
+void net_disconnect_deinit(void)
+{
+ NET_DISCONNECT_REC *rec;
+ time_t now, max;
+ int first;
+ struct timeval tv;
+ fd_set set;
+
+ if (disconnects == NULL)
+ return;
+
+ /* give the sockets a chance to disconnect themselves.. */
+ max = time(NULL)+MAX_QUIT_CLOSE_WAIT;
+ first = 1;
+ while (disconnects != NULL) {
+ rec = disconnects->data;
+
+ now = time(NULL);
+ if (rec->created+MAX_QUIT_CLOSE_WAIT <= now || max <= now) {
+ /* this one has waited enough */
+ net_disconnect_remove(rec);
+ continue;
+ }
+
+ FD_ZERO(&set);
+ FD_SET(rec->handle, &set);
+ tv.tv_sec = first ? 0 : max-now;
+ tv.tv_usec = first ? 100000 : 0;
+ if (select(rec->handle+1, &set, NULL, NULL, &tv) > 0 && FD_ISSET(rec->handle, &set)) {
+ /* data coming .. check if we can close the handle */
+ sig_disconnect(rec);
+ } else if (first) {
+ /* Display the text when we have already waited for a while */
+ printf(_("Please wait, waiting for servers to close connections..\n"));
+ fflush(stdout);
+
+ first = 0;
+ }
+ }
+}
diff --git a/src/core/net-disconnect.h b/src/core/net-disconnect.h
new file mode 100644
index 00000000..a1ca0643
--- /dev/null
+++ b/src/core/net-disconnect.h
@@ -0,0 +1,7 @@
+#ifndef __NET_DISCONNECT_H
+#define __NET_DISCONNECT_H
+
+void net_disconnect_init(void);
+void net_disconnect_deinit(void);
+
+#endif
diff --git a/src/core/net-internal.h b/src/core/net-internal.h
new file mode 100644
index 00000000..79da708e
--- /dev/null
+++ b/src/core/net-internal.h
@@ -0,0 +1,6 @@
+#ifdef HAVE_SOCKS_H
+#include <socks.h>
+#endif
+
+#include <netdb.h>
+#include <arpa/inet.h>
diff --git a/src/core/net-nonblock.c b/src/core/net-nonblock.c
new file mode 100644
index 00000000..b6e9264f
--- /dev/null
+++ b/src/core/net-nonblock.c
@@ -0,0 +1,210 @@
+/*
+ net-nonblock.c : Nonblocking net_connect()
+
+ Copyright (C) 1998-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+
+#include <signal.h>
+
+#include "pidwait.h"
+#include "net-nonblock.h"
+
+typedef struct
+{
+ NET_CALLBACK func;
+ void *data;
+
+ int pipes[2];
+ int port;
+ IPADDR *my_ip;
+ int tag;
+}
+SIMPLE_THREAD_REC;
+
+/* nonblocking gethostbyname(), ip (IPADDR) + error (int, 0 = not error) is
+ written to pipe when found PID of the resolver child is returned */
+int net_gethostname_nonblock(const char *addr, int pipe)
+{
+ RESOLVED_IP_REC rec;
+ const char *errorstr;
+ int pid;
+
+ g_return_val_if_fail(addr != NULL, FALSE);
+
+ pid = fork();
+ if (pid > 0) {
+ /* parent */
+ pidwait_add(pid);
+ return pid;
+ }
+
+ if (pid != 0) {
+ /* failed! */
+ g_warning("net_connect_thread(): fork() failed! Using blocking resolving");
+ }
+
+ /* child */
+ rec.error = net_gethostname(addr, &rec.ip);
+ if (rec.error == 0) {
+ errorstr = NULL;
+ } else {
+ errorstr = net_gethosterror(rec.error);
+ rec.errlen = strlen(errorstr)+1;
+ }
+
+ write(pipe, &rec, sizeof(rec));
+ if (rec.error != 0)
+ write(pipe, errorstr, rec.errlen);
+
+ if (pid == 0)
+ _exit(99);
+
+ /* we used blocking lookup */
+ return 0;
+}
+
+/* get the resolved IP address */
+int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec)
+{
+ time_t maxwait;
+ int len, ret;
+
+ rec->error = -1;
+ rec->errorstr = NULL;
+
+ /* get ip+error - try for max. 1-2 seconds */
+ fcntl(pipe, F_SETFL, O_NONBLOCK);
+
+ maxwait = time(NULL)+2;
+ len = 0;
+ do {
+ ret = read(pipe, (char *) rec+len, sizeof(*rec)-len);
+ if (ret == -1) return -1;
+
+ len += ret;
+ } while (len < sizeof(*rec) && time(NULL) < maxwait);
+
+ if (len < sizeof(*rec))
+ return -1; /* timeout */
+
+ if (rec->error) {
+ /* read error string */
+ rec->errorstr = g_malloc(rec->errlen);
+ len = 0;
+ do {
+ ret = read(pipe, rec->errorstr+len, rec->errlen-len);
+ if (ret == -1) break;
+ len += ret;
+ } while (len < rec->errlen && time(NULL) < maxwait);
+
+ if (len < rec->errlen) {
+ /* just ignore the rest of the error message.. */
+ rec->errorstr[len] = '\0';
+ }
+ }
+
+ return 0;
+}
+
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid)
+{
+ g_return_if_fail(pid > 0);
+
+ kill(pid, SIGKILL);
+}
+
+static void simple_init(SIMPLE_THREAD_REC *rec, int handle)
+{
+ g_return_if_fail(rec != NULL);
+
+ g_source_remove(rec->tag);
+
+ if (net_geterror(handle) != 0) {
+ /* failed */
+ close(handle);
+ handle = -1;
+ }
+
+ rec->func(handle, rec->data);
+ g_free(rec);
+}
+
+static void simple_readpipe(SIMPLE_THREAD_REC *rec, int pipe)
+{
+ RESOLVED_IP_REC iprec;
+ int handle;
+
+ g_return_if_fail(rec != NULL);
+
+ g_source_remove(rec->tag);
+
+ net_gethostbyname_return(pipe, &iprec);
+ g_free_not_null(iprec.errorstr);
+
+ close(rec->pipes[0]);
+ close(rec->pipes[1]);
+
+ handle = iprec.error == -1 ? -1 :
+ net_connect_ip(&iprec.ip, rec->port, rec->my_ip);
+
+ g_free_not_null(rec->my_ip);
+
+ if (handle == -1) {
+ /* failed */
+ rec->func(-1, rec->data);
+ g_free(rec);
+ return;
+ }
+
+ rec->tag = g_input_add(handle, G_INPUT_WRITE,
+ (GInputFunction) simple_init, rec);
+}
+
+/* Connect to server, call func when finished */
+int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, NET_CALLBACK func, void *data)
+{
+ SIMPLE_THREAD_REC *rec;
+ int fd[2];
+
+ g_return_val_if_fail(server != NULL, FALSE);
+ g_return_val_if_fail(func != NULL, FALSE);
+
+ if (pipe(fd) != 0) {
+ g_warning("net_connect_nonblock(): pipe() failed.");
+ return FALSE;
+ }
+
+ /* start nonblocking host name lookup */
+ net_gethostname_nonblock(server, fd[1]);
+
+ rec = g_new0(SIMPLE_THREAD_REC, 1);
+ rec->port = port;
+ if (my_ip != NULL) {
+ rec->my_ip = g_malloc(sizeof(IPADDR));
+ memcpy(rec->my_ip, my_ip, sizeof(IPADDR));
+ }
+ rec->func = func;
+ rec->data = data;
+ rec->pipes[0] = fd[0];
+ rec->pipes[1] = fd[1];
+ rec->tag = g_input_add(fd[0], G_INPUT_READ, (GInputFunction) simple_readpipe, rec);
+
+ return 1;
+}
diff --git a/src/core/net-nonblock.h b/src/core/net-nonblock.h
new file mode 100644
index 00000000..6ae05e82
--- /dev/null
+++ b/src/core/net-nonblock.h
@@ -0,0 +1,26 @@
+#ifndef __NET_NONBLOCK_H
+#define __NET_NONBLOCK_H
+
+#include "network.h"
+
+typedef struct {
+ IPADDR ip; /* resolved ip addres */
+ int error; /* error, 0 = no error, -1 = error: */
+ int errlen; /* error text length */
+ char *errorstr; /* error string - dynamically allocated, you'll
+ need to free() it yourself unless it's NULL */
+} RESOLVED_IP_REC;
+
+typedef void (*NET_CALLBACK) (int, void *);
+
+/* nonblocking gethostbyname(), PID of the resolver child is returned. */
+int net_gethostname_nonblock(const char *addr, int pipe);
+/* get the resolved IP address. returns -1 if some error occured with read() */
+int net_gethostbyname_return(int pipe, RESOLVED_IP_REC *rec);
+
+/* Connect to server, call func when finished */
+int net_connect_nonblock(const char *server, int port, const IPADDR *my_ip, NET_CALLBACK func, void *data);
+/* Kill the resolver child */
+void net_disconnect_nonblock(int pid);
+
+#endif
diff --git a/src/core/network.c b/src/core/network.c
new file mode 100644
index 00000000..d962f35f
--- /dev/null
+++ b/src/core/network.c
@@ -0,0 +1,451 @@
+/*
+ network.c : Network stuff
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "network.h"
+#include "net-internal.h"
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 sin6;
+#endif
+};
+
+/* Cygwin need this, don't know others.. */
+/*#define BLOCKING_SOCKETS 1*/
+
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2)
+{
+ if (ip1->family != ip2->family)
+ return 0;
+
+#ifdef HAVE_IPV6
+ if (ip1->family == AF_INET6)
+ return memcmp(&ip1->addr, &ip2->addr, sizeof(ip1->addr)) == 0;
+#endif
+
+ return memcmp(&ip1->addr, &ip2->addr, 4) == 0;
+}
+
+
+/* copy IP to sockaddr */
+inline void sin_set_ip(union sockaddr_union *so, const IPADDR *ip)
+{
+ so->sin.sin_family = ip->family;
+#ifdef HAVE_IPV6
+ if (ip->family == AF_INET6)
+ memcpy(&so->sin6.sin6_addr, &ip->addr, sizeof(ip->addr.ip6));
+ else
+#endif
+ memcpy(&so->sin.sin_addr, &ip->addr, 4);
+}
+
+inline void sin_get_ip(const union sockaddr_union *so, IPADDR *ip)
+{
+ ip->family = so->sin.sin_family;
+
+#ifdef HAVE_IPV6
+ if (ip->family == AF_INET6)
+ memcpy(&ip->addr, &so->sin6.sin6_addr, sizeof(ip->addr.ip6));
+ else
+#endif
+ memcpy(&ip->addr, &so->sin.sin_addr, 4);
+}
+
+G_INLINE_FUNC void sin_set_port(union sockaddr_union *so, int port)
+{
+#ifdef HAVE_IPV6
+ if (so->sin.sin_family == AF_INET6)
+ so->sin6.sin6_port = htons(port);
+ else
+#endif
+ so->sin.sin_port = htons(port);
+}
+
+G_INLINE_FUNC int sin_get_port(union sockaddr_union *so)
+{
+#ifdef HAVE_IPV6
+ if (so->sin.sin_family == AF_INET6)
+ return ntohs(so->sin6.sin6_port);
+#endif
+ return ntohs(so->sin.sin_port);
+}
+
+/* Connect to socket */
+int net_connect(const char *addr, int port, IPADDR *my_ip)
+{
+ IPADDR ip;
+
+ g_return_val_if_fail(addr != NULL, -1);
+
+ if (net_gethostname(addr, &ip) == -1)
+ return -1;
+
+ return net_connect_ip(&ip, port, my_ip);
+}
+
+/* Connect to socket with ip address */
+int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip)
+{
+ union sockaddr_union so;
+ int handle, ret, opt = 1;
+
+ /* create the socket */
+ memset(&so, 0, sizeof(so));
+ so.sin.sin_family = ip->family;
+ handle = socket(ip->family, SOCK_STREAM, 0);
+
+ if (handle == -1)
+ return -1;
+
+ /* set socket options */
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt));
+ setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt));
+
+ /* set our own address, ignore if bind() fails */
+ if (my_ip != NULL) {
+ sin_set_ip(&so, my_ip);
+ bind(handle, &so.sa, sizeof(so));
+ }
+
+ /* connect */
+ sin_set_ip(&so, ip);
+ sin_set_port(&so, port);
+ ret = connect(handle, &so.sa, sizeof(so));
+
+ if (ret < 0 && errno != EINPROGRESS) {
+ close(handle);
+ return -1;
+ }
+
+ return handle;
+}
+
+/* Disconnect socket */
+void net_disconnect(int handle)
+{
+ g_return_if_fail(handle != -1);
+
+ close(handle);
+}
+
+/* Listen for connections on a socket */
+int net_listen(IPADDR *my_ip, int *port)
+{
+ union sockaddr_union so;
+ int ret, handle, opt = 1;
+ socklen_t len = sizeof(so);
+
+ g_return_val_if_fail(my_ip != NULL, -1);
+ g_return_val_if_fail(port != NULL, -1);
+
+ /* create the socket */
+ memset(&so, 0, sizeof(so));
+ so.sin.sin_family = my_ip->family;
+ handle = socket(my_ip->family, SOCK_STREAM, 0);
+
+ if (handle == -1)
+ return -1;
+
+ /* set socket options */
+ fcntl(handle, F_SETFL, O_NONBLOCK);
+ setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt));
+ setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt));
+
+ /* specify the address/port we want to listen in */
+ sin_set_port(&so, *port);
+ ret = bind(handle, &so.sa, sizeof(so));
+ if (ret < 0) {
+ close(handle);
+ return -1;
+ }
+
+ /* get the actual port we started listen */
+ ret = getsockname(handle, &so.sa, &len);
+ if (ret < 0) {
+ close(handle);
+ return -1;
+ }
+
+ *port = sin_get_port(&so);
+
+ /* start listening */
+ if (listen(handle, 1) < 0)
+ {
+ close(handle);
+ return -1;
+ }
+
+ return handle;
+}
+
+/* Accept a connection on a socket */
+int net_accept(int handle, IPADDR *addr, int *port)
+{
+ union sockaddr_union so;
+ int ret;
+ socklen_t addrlen;
+
+ g_return_val_if_fail(handle != -1, -1);
+ g_return_val_if_fail(addr != NULL, -1);
+ g_return_val_if_fail(port != NULL, -1);
+
+ addrlen = sizeof(so);
+ ret = accept(handle, &so.sa, &addrlen);
+
+ if (ret < 0)
+ return -1;
+
+ sin_get_ip(&so, addr);
+ *port = sin_get_port(&so);
+
+ fcntl(ret, F_SETFL, O_NONBLOCK);
+ return ret;
+}
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(int handle, char *buf, int len)
+{
+#ifdef BLOCKING_SOCKETS
+ fd_set set;
+ struct timeval tv;
+#endif
+ int ret;
+
+ g_return_val_if_fail(handle != -1, -1);
+ g_return_val_if_fail(buf != NULL, -1);
+
+#ifdef BLOCKING_SOCKETS
+ FD_ZERO(&set);
+ FD_SET(handle, &set);
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ if (select(handle+1, &set, NULL, NULL, &tv) <= 0 ||
+ !FD_ISSET(handle, &set)) return 0;
+#endif
+
+ ret = recv(handle, buf, len, 0);
+ if (ret == 0)
+ return -1; /* disconnected */
+
+ if (ret == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
+ return 0; /* no bytes received */
+
+ return ret;
+}
+
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(int handle, const char *data, int len)
+{
+ int n;
+
+ g_return_val_if_fail(handle != -1, -1);
+ g_return_val_if_fail(data != NULL, -1);
+
+ n = send(handle, data, len, 0);
+ if (n == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
+ return 0;
+
+ return n > 0 ? n : -1;
+}
+
+/* Get socket address/port */
+int net_getsockname(int handle, IPADDR *addr, int *port)
+{
+ union sockaddr_union so;
+#ifdef HAVE_IPV6
+ socklen_t len = sizeof(so.sin6);
+#else
+ socklen_t len = sizeof(so.sin);
+#endif
+
+ g_return_val_if_fail(handle != -1, -1);
+ g_return_val_if_fail(addr != NULL, -1);
+
+#ifdef HAVE_IPV6
+ if (getsockname(handle, &so.sin6, &len) == -1)
+#else
+ if (getsockname(handle, &so.sin, &len) == -1)
+#endif
+ return -1;
+
+ sin_get_ip(&so, addr);
+ if (port) *port = sin_get_port(&so);
+
+ return 0;
+}
+
+/* Get IP address for host, returns 0 = ok,
+ others = error code for net_gethosterror() */
+int net_gethostname(const char *addr, IPADDR *ip)
+{
+#ifdef HAVE_IPV6
+ union sockaddr_union *so;
+ struct addrinfo req, *ai;
+ char hbuf[NI_MAXHOST];
+ int host_error;
+#else
+ struct hostent *hp;
+#endif
+
+ g_return_val_if_fail(addr != NULL, -1);
+
+ /* host name */
+#ifdef HAVE_IPV6
+ memset(ip, 0, sizeof(IPADDR));
+ memset(&req, 0, sizeof(struct addrinfo));
+ req.ai_socktype = SOCK_STREAM;
+
+ /* save error to host_error for later use */
+ host_error = getaddrinfo(addr, NULL, &req, &ai);
+ if (host_error != 0)
+ return host_error;
+
+ if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST))
+ return 1;
+
+ so = (union sockaddr_union *) ai->ai_addr;
+ sin_get_ip(so, ip);
+ freeaddrinfo(ai);
+#else
+ hp = gethostbyname(addr);
+ if (hp == NULL) return -1;
+
+ ip->family = AF_INET;
+ memcpy(&ip->addr, hp->h_addr, 4);
+#endif
+
+ return 0;
+}
+
+int net_ip2host(IPADDR *ip, char *host)
+{
+#ifdef HAVE_IPV6
+ if (!inet_ntop(ip->family, &ip->addr, host, MAX_IP_LEN))
+ return -1;
+#else
+ unsigned long ip4;
+
+ ip4 = ntohl(ip->addr.ip.s_addr);
+ sprintf(host, "%lu.%lu.%lu.%lu",
+ (ip4 & 0xff000000) >> 24,
+ (ip4 & 0x00ff0000) >> 16,
+ (ip4 & 0x0000ff00) >> 8,
+ (ip4 & 0x000000ff));
+#endif
+ return 0;
+}
+
+int net_host2ip(const char *host, IPADDR *ip)
+{
+ unsigned long addr;
+
+#ifdef HAVE_IPV6
+ if (strchr(host, ':') != NULL) {
+ /* IPv6 */
+ ip->family = AF_INET6;
+ if (inet_pton(AF_INET6, host, &ip->addr) == 0)
+ return -1;
+ } else
+#endif
+ {
+ /* IPv4 */
+ ip->family = AF_INET;
+#ifdef HAVE_INET_ATON
+ if (inet_aton(host, &ip->addr.ip.s_addr) == 0)
+ return -1;
+#else
+ addr = inet_addr(host);
+ if (addr == INADDR_NONE)
+ return -1;
+
+ memcpy(&ip->addr, &addr, 4);
+#endif
+ }
+
+ return 0;
+}
+
+/* Get socket error */
+int net_geterror(int handle)
+{
+ int data;
+ socklen_t len = sizeof(data);
+
+ if (getsockopt(handle, SOL_SOCKET, SO_ERROR, &data, &len) == -1)
+ return -1;
+
+ return data;
+}
+
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error)
+{
+#ifdef HAVE_IPV6
+ g_return_val_if_fail(error != 0, NULL);
+
+ if (error == 1) {
+ /* getnameinfo() failed ..
+ FIXME: does strerror return the right error message?? */
+ return g_strerror(errno);
+ }
+
+ return gai_strerror(error);
+#else
+ switch (h_errno) {
+ case HOST_NOT_FOUND:
+ return _("Host not found");
+ case NO_ADDRESS:
+ return _("No IP address found for name");
+ case NO_RECOVERY:
+ return _("A non-recovable name server error occurred");
+ case TRY_AGAIN:
+ return _("A temporary error on an authoritative name server");
+ }
+
+ /* unknown error */
+ return NULL;
+#endif
+}
+
+int is_ipv4_address(const char *host)
+{
+ while (*host != '\0') {
+ if (*host != '.' && !isdigit(*host))
+ return 0;
+ host++;
+ }
+
+ return 1;
+}
+
+int is_ipv6_address(const char *host)
+{
+ while (*host != '\0') {
+ if (*host != ':' && !isxdigit(*host))
+ return 0;
+ host++;
+ }
+
+ return 1;
+}
diff --git a/src/core/network.h b/src/core/network.h
new file mode 100644
index 00000000..a206da45
--- /dev/null
+++ b/src/core/network.h
@@ -0,0 +1,71 @@
+#ifndef __NETWORK_H
+#define __NETWORK_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+struct _ipaddr {
+ unsigned short family;
+ union {
+#ifdef HAVE_IPV6
+ struct in6_addr ip6;
+#else
+ struct in_addr ip;
+#endif
+ } addr;
+};
+
+typedef struct _ipaddr IPADDR;
+
+/* maxmimum string length of IP address */
+#ifdef HAVE_IPV6
+# define MAX_IP_LEN INET6_ADDRSTRLEN
+#else
+# define MAX_IP_LEN 20
+#endif
+
+#define is_ipv6_addr(ip) ((ip)->family != AF_INET)
+
+/* returns 1 if IPADDRs are the same */
+int net_ip_compare(IPADDR *ip1, IPADDR *ip2);
+
+/* Connect to socket */
+int net_connect(const char *addr, int port, IPADDR *my_ip);
+/* Connect to socket with ip address */
+int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip);
+/* Disconnect socket */
+void net_disconnect(int handle);
+/* Try to let the other side close the connection, if it still isn't
+ disconnected after certain amount of time, close it ourself */
+void net_disconnect_later(int handle);
+
+/* Listen for connections on a socket */
+int net_listen(IPADDR *my_ip, int *port);
+/* Accept a connection on a socket */
+int net_accept(int handle, IPADDR *addr, int *port);
+
+/* Read data from socket, return number of bytes read, -1 = error */
+int net_receive(int handle, char *buf, int len);
+/* Transmit data, return number of bytes sent, -1 = error */
+int net_transmit(int handle, const char *data, int len);
+
+/* Get IP address for host, returns 0 = ok,
+ others = error code for net_gethosterror() */
+int net_gethostname(const char *addr, IPADDR *ip);
+/* get error of net_gethostname() */
+const char *net_gethosterror(int error);
+
+/* Get socket address/port */
+int net_getsockname(int handle, IPADDR *addr, int *port);
+
+int net_ip2host(IPADDR *ip, char *host);
+int net_host2ip(const char *host, IPADDR *ip);
+
+/* Get socket error */
+int net_geterror(int handle);
+
+int is_ipv4_address(const char *host);
+int is_ipv6_address(const char *host);
+
+#endif
diff --git a/src/core/pidwait.c b/src/core/pidwait.c
new file mode 100644
index 00000000..7f6d77e3
--- /dev/null
+++ b/src/core/pidwait.c
@@ -0,0 +1,74 @@
+/*
+ pidwait.c :
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "modules.h"
+
+#include <sys/wait.h>
+
+static GSList *pids;
+
+static unsigned int childcheck_tag;
+static int signal_pidwait;
+
+/* add a pid to wait list */
+void pidwait_add(int pid)
+{
+ pids = g_slist_append(pids, GINT_TO_POINTER(pid));
+}
+
+/* remove pid from wait list */
+void pidwait_remove(int pid)
+{
+ pids = g_slist_remove(pids, GINT_TO_POINTER(pid));
+}
+
+static int child_check(void)
+{
+ GSList *tmp, *next;
+ int status;
+
+ /* wait for each pid.. */
+ for (tmp = pids; tmp != NULL; tmp = next) {
+ next = tmp->next;
+ if (waitpid(GPOINTER_TO_INT(tmp->data), &status, WNOHANG) > 0) {
+ /* process terminated, remove from list */
+ pids = g_slist_remove(pids, tmp->data);
+ signal_emit_id(signal_pidwait, 1, GPOINTER_TO_INT(tmp->data));
+ }
+ }
+ return 1;
+}
+
+void pidwait_init(void)
+{
+ pids = NULL;
+ childcheck_tag = g_timeout_add(1000, (GSourceFunc) child_check, NULL);
+
+ signal_pidwait = module_get_uniq_id_str("signals", "pidwait");
+}
+
+void pidwait_deinit(void)
+{
+ g_slist_free(pids);
+
+ g_source_remove(childcheck_tag);
+}
diff --git a/src/core/pidwait.h b/src/core/pidwait.h
new file mode 100644
index 00000000..3f6b84cd
--- /dev/null
+++ b/src/core/pidwait.h
@@ -0,0 +1,12 @@
+#ifndef __PIDWAIT_H
+#define __PIDWAIT_H
+
+void pidwait_init(void);
+void pidwait_deinit(void);
+
+/* add a pid to wait list */
+void pidwait_add(int pid);
+/* remove pid from wait list */
+void pidwait_remove(int pid);
+
+#endif
diff --git a/src/core/rawlog.c b/src/core/rawlog.c
new file mode 100644
index 00000000..791e594d
--- /dev/null
+++ b/src/core/rawlog.c
@@ -0,0 +1,167 @@
+/*
+ rawlog.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "rawlog.h"
+#include "modules.h"
+#include "signals.h"
+#include "misc.h"
+
+#include "settings.h"
+#include "common-setup.h"
+
+static int rawlog_lines;
+static int signal_rawlog;
+
+RAWLOG_REC *rawlog_create(void)
+{
+ RAWLOG_REC *rec;
+
+ rec = g_new0(RAWLOG_REC, 1);
+ return rec;
+}
+
+void rawlog_destroy(RAWLOG_REC *rawlog)
+{
+ g_return_if_fail(rawlog != NULL);
+
+ g_slist_foreach(rawlog->lines, (GFunc) g_free, NULL);
+ g_slist_free(rawlog->lines);
+
+ if (rawlog->logging) close(rawlog->file);
+ g_free(rawlog);
+}
+
+/* NOTE! str must be dynamically allocated and must not be freed after! */
+static void rawlog_add(RAWLOG_REC *rawlog, char *str)
+{
+ if (rawlog->nlines < rawlog_lines || rawlog_lines <= 2)
+ rawlog->nlines++;
+ else {
+ g_free(rawlog->lines->data);
+ rawlog->lines = g_slist_remove(rawlog->lines, rawlog->lines->data);
+ }
+
+ if (rawlog->logging) {
+ write(rawlog->file, str, strlen(str));
+ write(rawlog->file, "\n", 1);
+ }
+
+ rawlog->lines = g_slist_append(rawlog->lines, str);
+ signal_emit_id(signal_rawlog, 2, rawlog, str);
+}
+
+void rawlog_input(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf(">> %s", str));
+}
+
+void rawlog_output(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf("<< %s", str));
+}
+
+void rawlog_redirect(RAWLOG_REC *rawlog, const char *str)
+{
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(str != NULL);
+
+ rawlog_add(rawlog, g_strdup_printf("--> %s", str));
+}
+
+static void rawlog_dump(RAWLOG_REC *rawlog, int f)
+{
+ GSList *tmp;
+
+ for (tmp = rawlog->lines; tmp != NULL; tmp = tmp->next) {
+ write(f, tmp->data, strlen((char *) tmp->data));
+ write(f, "\n", 1);
+ }
+}
+
+void rawlog_open(RAWLOG_REC *rawlog, const char *fname)
+{
+ char *path;
+
+ g_return_if_fail(rawlog != NULL);
+ g_return_if_fail(fname != NULL);
+
+ if (rawlog->logging)
+ return;
+
+ path = convert_home(fname);
+ rawlog->file = open(path, O_WRONLY | O_APPEND | O_CREAT, LOG_FILE_CREATE_MODE);
+ g_free(path);
+
+ rawlog_dump(rawlog, rawlog->file);
+ rawlog->logging = rawlog->file != -1;
+}
+
+void rawlog_close(RAWLOG_REC *rawlog)
+{
+ if (rawlog->logging) {
+ close(rawlog->file);
+ rawlog->logging = 0;
+ }
+}
+
+void rawlog_save(RAWLOG_REC *rawlog, const char *fname)
+{
+ char *path;
+ int f;
+
+ path = convert_home(fname);
+ f = open(path, O_WRONLY | O_APPEND | O_CREAT, LOG_FILE_CREATE_MODE);
+ g_free(path);
+
+ rawlog_dump(rawlog, f);
+ close(f);
+}
+
+void rawlog_set_size(int lines)
+{
+ rawlog_lines = lines;
+}
+
+static void read_settings(void)
+{
+ rawlog_set_size(settings_get_int("rawlog_lines"));
+}
+
+void rawlog_init(void)
+{
+ signal_rawlog = module_get_uniq_id_str("signals", "rawlog");
+
+ settings_add_int("history", "rawlog_lines", 200);
+ read_settings();
+
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void rawlog_deinit(void)
+{
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/core/rawlog.h b/src/core/rawlog.h
new file mode 100644
index 00000000..ad1c8b53
--- /dev/null
+++ b/src/core/rawlog.h
@@ -0,0 +1,28 @@
+#ifndef __RAWLOG_H
+#define __RAWLOG_H
+
+typedef struct {
+ int logging;
+ int file;
+
+ int nlines;
+ GSList *lines;
+} RAWLOG_REC;
+
+RAWLOG_REC *rawlog_create(void);
+void rawlog_destroy(RAWLOG_REC *rawlog);
+
+void rawlog_input(RAWLOG_REC *rawlog, const char *str);
+void rawlog_output(RAWLOG_REC *rawlog, const char *str);
+void rawlog_redirect(RAWLOG_REC *rawlog, const char *str);
+
+void rawlog_set_size(int lines);
+
+void rawlog_open(RAWLOG_REC *rawlog, const char *fname);
+void rawlog_close(RAWLOG_REC *rawlog);
+void rawlog_save(RAWLOG_REC *rawlog, const char *fname);
+
+void rawlog_init(void);
+void rawlog_deinit(void);
+
+#endif
diff --git a/src/core/server-redirect.c b/src/core/server-redirect.c
new file mode 100644
index 00000000..792fdb0f
--- /dev/null
+++ b/src/core/server-redirect.c
@@ -0,0 +1,323 @@
+/*
+ server-redirect.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "misc.h"
+
+#include "server.h"
+#include "server-redirect.h"
+
+static int redirect_group;
+
+static void server_eventtable_destroy(char *key, GSList *value)
+{
+ GSList *tmp;
+
+ g_free(key);
+
+ for (tmp = value; tmp != NULL; tmp = tmp->next) {
+ REDIRECT_REC *rec = tmp->data;
+
+ g_free_not_null(rec->arg);
+ g_free(rec->name);
+ g_free(rec);
+ }
+ g_slist_free(value);
+}
+
+static void server_eventgrouptable_destroy(gpointer key, GSList *value)
+{
+ g_slist_foreach(value, (GFunc) g_free, NULL);
+ g_slist_free(value);
+}
+
+static void server_cmdtable_destroy(char *key, REDIRECT_CMD_REC *value)
+{
+ g_free(key);
+
+ g_slist_foreach(value->events, (GFunc) g_free, NULL);
+ g_slist_free(value->events);
+ g_free(value);
+}
+
+static void sig_disconnected(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ if (server->eventtable != NULL) {
+ g_hash_table_foreach(server->eventtable, (GHFunc) server_eventtable_destroy, NULL);
+ g_hash_table_destroy(server->eventtable);
+ }
+
+ g_hash_table_foreach(server->eventgrouptable, (GHFunc) server_eventgrouptable_destroy, NULL);
+ g_hash_table_destroy(server->eventgrouptable);
+
+ if (server->cmdtable != NULL) {
+ g_hash_table_foreach(server->cmdtable, (GHFunc) server_cmdtable_destroy, NULL);
+ g_hash_table_destroy(server->cmdtable);
+ }
+}
+
+void server_redirect_initv(SERVER_REC *server, const char *command, int last, GSList *list)
+{
+ REDIRECT_CMD_REC *rec;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(command != NULL);
+ g_return_if_fail(last > 0);
+
+ if (g_hash_table_lookup(server->cmdtable, command) != NULL) {
+ /* already in hash table. list of events SHOULD be the same... */
+ g_slist_foreach(list, (GFunc) g_free, NULL);
+ g_slist_free(list);
+ return;
+ }
+
+ rec = g_new(REDIRECT_CMD_REC, 1);
+ rec->last = last;
+ rec->events = list;
+ g_hash_table_insert(server->cmdtable, g_strdup(command), rec);
+}
+
+void server_redirect_init(SERVER_REC *server, const char *command, int last, ...)
+{
+ va_list args;
+ GSList *list;
+ char *event;
+
+ va_start(args, last);
+ list = NULL;
+ while ((event = va_arg(args, gchar *)) != NULL)
+ list = g_slist_append(list, g_strdup(event));
+ va_end(args);
+
+ server_redirect_initv(server, command, last, list);
+}
+
+int server_redirect_single_event(SERVER_REC *server, const char *arg, int last, int group,
+ const char *event, const char *signal, int argpos)
+{
+ REDIRECT_REC *rec;
+ GSList *list, *grouplist;
+ char *origkey;
+
+ g_return_val_if_fail(server != NULL, 0);
+ g_return_val_if_fail(event != NULL, 0);
+ g_return_val_if_fail(signal != NULL, 0);
+ g_return_val_if_fail(arg != NULL || argpos == -1, 0);
+
+ if (group == 0) group = ++redirect_group;
+
+ rec = g_new0(REDIRECT_REC, 1);
+ rec->arg = arg == NULL ? NULL : g_strdup(arg);
+ rec->argpos = argpos;
+ rec->name = g_strdup(signal);
+ rec->group = group;
+ rec->last = last;
+
+ if (g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &list))
+ g_hash_table_remove(server->eventtable, origkey);
+ else {
+ list = NULL;
+ origkey = g_strdup(event);
+ }
+
+ grouplist = g_hash_table_lookup(server->eventgrouptable, GINT_TO_POINTER(group));
+ if (grouplist != NULL) g_hash_table_remove(server->eventgrouptable, GINT_TO_POINTER(group));
+
+ list = g_slist_append(list, rec);
+ grouplist = g_slist_append(grouplist, g_strdup(event));
+
+ g_hash_table_insert(server->eventtable, origkey, list);
+ g_hash_table_insert(server->eventgrouptable, GINT_TO_POINTER(group), grouplist);
+
+ return group;
+}
+
+void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...)
+{
+ va_list args;
+ char *event, *signal;
+ int argpos, group;
+
+ g_return_if_fail(server != NULL);
+
+ va_start(args, last);
+
+ group = 0;
+ while ((event = va_arg(args, gchar *)) != NULL) {
+ signal = va_arg(args, gchar *);
+ argpos = va_arg(args, gint);
+
+ group = server_redirect_single_event(server, arg, last > 0, group, event, signal, argpos);
+ last--;
+ }
+
+ va_end(args);
+}
+
+void server_redirect_default(SERVER_REC *server, const char *command)
+{
+ REDIRECT_CMD_REC *cmdrec;
+ REDIRECT_REC *rec;
+ GSList *events, *list, *grouplist;
+ char *event, *origkey;
+ int last;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(command != NULL);
+
+ if (server->cmdtable == NULL)
+ return; /* not connected yet */
+
+ cmdrec = g_hash_table_lookup(server->cmdtable, command);
+ if (cmdrec == NULL) return;
+
+ /* add all events used by command to eventtable and eventgrouptable */
+ redirect_group++; grouplist = NULL; last = cmdrec->last;
+ for (events = cmdrec->events; events != NULL; events = events->next, last--) {
+ event = events->data;
+
+ if (g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &list))
+ g_hash_table_remove(server->eventtable, origkey);
+ else {
+ list = NULL;
+ origkey = g_strdup(event);
+ }
+
+ rec = g_new0(REDIRECT_REC, 1);
+ rec->argpos = -1;
+ rec->name = g_strdup(event);
+ rec->group = redirect_group;
+ rec->last = last > 0;
+
+ grouplist = g_slist_append(grouplist, g_strdup(event));
+ list = g_slist_append(list, rec);
+ g_hash_table_insert(server->eventtable, origkey, list);
+ }
+
+ g_hash_table_insert(server->eventgrouptable, GINT_TO_POINTER(redirect_group), grouplist);
+}
+
+void server_redirect_remove_next(SERVER_REC *server, const char *event, GSList *item)
+{
+ REDIRECT_REC *rec;
+ GSList *grouplist, *list, *events, *tmp;
+ char *origkey;
+ int group;
+
+ g_return_if_fail(server != NULL);
+ g_return_if_fail(event != NULL);
+
+ if (!g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &list))
+ return;
+
+ rec = item == NULL ? list->data : item->data;
+ if (!rec->last) {
+ /* this wasn't last expected event */
+ return;
+ }
+ group = rec->group;
+
+ /* get list of events from this group */
+ grouplist = g_hash_table_lookup(server->eventgrouptable, GINT_TO_POINTER(group));
+
+ /* remove all of them */
+ for (list = grouplist; list != NULL; list = list->next) {
+ char *event = list->data;
+
+ if (!g_hash_table_lookup_extended(server->eventtable, event, (gpointer *) &origkey, (gpointer *) &events)) {
+ g_warning("server_redirect_remove_next() : event in eventgrouptable but not in eventtable");
+ continue;
+ }
+
+ /* remove the right group */
+ for (tmp = events; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+
+ if (rec->group == group)
+ break;
+ }
+
+ if (rec == NULL) {
+ g_warning("server_redirect_remove_next() : event in eventgrouptable but not in eventtable (group)");
+ continue;
+ }
+
+ g_free(event);
+
+ events = g_slist_remove(events, rec);
+ g_free_not_null(rec->arg);
+ g_free(rec->name);
+ g_free(rec);
+
+ /* update hash table */
+ g_hash_table_remove(server->eventtable, origkey);
+ if (events == NULL)
+ g_free(origkey);
+ else
+ g_hash_table_insert(server->eventtable, origkey, events);
+ }
+
+ g_hash_table_remove(server->eventgrouptable, GINT_TO_POINTER(group));
+ g_slist_free(grouplist);
+}
+
+GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, const char *args)
+{
+ REDIRECT_REC *rec;
+ GSList *list;
+ char **arglist;
+ int found;
+
+ list = g_hash_table_lookup(server->eventtable, event);
+
+ for (; list != NULL; list = list->next) {
+ rec = list->data;
+ if (rec->argpos == -1)
+ break;
+
+ if (rec->arg == NULL)
+ continue;
+
+ /* we need to check that the argument is right.. */
+ arglist = g_strsplit(args, " ", -1);
+ found = (strarray_length(arglist) > rec->argpos &&
+ find_substr(rec->arg, arglist[rec->argpos]));
+ g_strfreev(arglist);
+
+ if (found) break;
+ }
+
+ return list;
+}
+
+void servers_redirect_init(void)
+{
+ redirect_group = 0;
+
+ signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
+
+void servers_redirect_deinit(void)
+{
+ signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
+}
diff --git a/src/core/server-redirect.h b/src/core/server-redirect.h
new file mode 100644
index 00000000..977dded1
--- /dev/null
+++ b/src/core/server-redirect.h
@@ -0,0 +1,38 @@
+#ifndef __SERVER_REDIRECT_H
+#define __SERVER_REDIRECT_H
+
+#include "server.h"
+
+typedef struct {
+ int last; /* number of "last" events at the start of the events list */
+ GSList *events; /* char* list of events */
+} REDIRECT_CMD_REC;
+
+typedef struct {
+ char *name; /* event name */
+
+ char *arg; /* argument for event we are expecting or NULL */
+ int argpos; /* argument position */
+
+ int group; /* group of events this belongs to */
+ int last; /* if this event is received, remove all the events in this group */
+}
+REDIRECT_REC;
+
+void server_redirect_init(SERVER_REC *server, const char *command, int last, ...);
+void server_redirect_initv(SERVER_REC *server, const char *command, int last, GSList *list);
+/* ... = char *event1, char *event2, ..., NULL */
+
+void server_redirect_event(SERVER_REC *server, const char *arg, int last, ...);
+/* ... = char *event, char *callback_signal, int argpos, ..., NULL */
+
+int server_redirect_single_event(SERVER_REC *server, const char *arg, int last, int group,
+ const char *event, const char *signal, int argpos);
+void server_redirect_default(SERVER_REC *server, const char *command);
+void server_redirect_remove_next(SERVER_REC *server, const char *event, GSList *item);
+GSList *server_redirect_getqueue(SERVER_REC *server, const char *event, const char *args);
+
+void servers_redirect_init(void);
+void servers_redirect_deinit(void);
+
+#endif
diff --git a/src/core/server.c b/src/core/server.c
new file mode 100644
index 00000000..dcfefc5a
--- /dev/null
+++ b/src/core/server.c
@@ -0,0 +1,273 @@
+/*
+ server.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+
+#include "modules.h"
+#include "signals.h"
+#include "line-split.h"
+#include "net-nonblock.h"
+#include "rawlog.h"
+#include "misc.h"
+#include "server.h"
+#include "server-redirect.h"
+#include "settings.h"
+
+GSList *servers, *lookup_servers;
+
+/* connection to server failed */
+static void server_cant_connect(SERVER_REC *server, const char *msg)
+{
+ g_return_if_fail(server != NULL);
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+
+ signal_emit("server connect failed", 2, server, msg);
+ if (server->connect_tag != -1)
+ g_source_remove(server->connect_tag);
+
+ if (server->connect_pipe[0] != -1) {
+ close(server->connect_pipe[0]);
+ close(server->connect_pipe[1]);
+ }
+
+ MODULE_DATA_DEINIT(server);
+ g_free(server->tag);
+ g_free(server->nick);
+ g_free(server);
+}
+
+/* generate tag from server's address */
+static char *server_create_address_tag(const char *address)
+{
+ const char *start, *end;
+
+ /* try to generate a reasonable server tag */
+ if (g_strncasecmp(address, "irc", 3) == 0 ||
+ g_strncasecmp(address, "chat", 4) == 0) {
+ /* irc-2.cs.hut.fi -> hut, chat.bt.net -> bt */
+ end = strrchr(address, '.');
+ start = end-1;
+ while (start > address && *start != '.') start--;
+ } else {
+ /* efnet.cs.hut.fi -> efnet */
+ end = strchr(address, '.');
+ start = end;
+ }
+
+ if (start == end) start = address; else start++;
+ if (end == NULL) end = address + strlen(address);
+
+ return g_strndup(start, (int) (end-start));
+}
+
+/* create unique tag for server. prefer ircnet's name or
+ generate it from server's address */
+static char *server_create_tag(SERVER_CONNECT_REC *conn)
+{
+ GString *str;
+ char *tag;
+ int num;
+
+ tag = conn->ircnet != NULL ? g_strdup(conn->ircnet) :
+ server_create_address_tag(conn->address);
+
+ /* then just append numbers after tag until unused is found.. */
+ str = g_string_new(tag);
+ for (num = 2; server_find_tag(str->str) != NULL; num++)
+ g_string_sprintf(str, "%s%d", tag, num);
+ g_free(tag);
+
+ tag = str->str;
+ g_string_free(str, FALSE);
+ return tag;
+}
+
+static void server_connect_callback_init(SERVER_REC *server, int handle)
+{
+ int error;
+
+ error = net_geterror(handle);
+ if (error != 0) {
+ server->connection_lost = TRUE;
+ server_cant_connect(server, g_strerror(error));
+ return;
+ }
+
+ lookup_servers = g_slist_remove(lookup_servers, server);
+
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+ server->connect_time = time(NULL);
+ server->rawlog = rawlog_create();
+ servers = g_slist_append(servers, server);
+
+ signal_emit("server connected", 1, server);
+}
+
+static void server_connect_callback_readpipe(SERVER_REC *server, int handle)
+{
+ SERVER_CONNECT_REC *conn;
+ RESOLVED_IP_REC iprec;
+
+ g_source_remove(server->connect_tag);
+ server->connect_tag = -1;
+
+ net_gethostbyname_return(handle, &iprec);
+
+ close(server->connect_pipe[0]);
+ close(server->connect_pipe[1]);
+
+ server->connect_pipe[0] = -1;
+ server->connect_pipe[1] = -1;
+
+ conn = server->connrec;
+ server->handle = iprec.error == -1 ? -1 :
+ net_connect_ip(&iprec.ip, conn->proxy != NULL ?
+ conn->proxy_port : conn->port,
+ conn->own_ip != NULL ? conn->own_ip : NULL);
+ if (server->handle == -1) {
+ /* failed */
+ server->connection_lost = TRUE;
+ server_cant_connect(server,
+ iprec.error != -1 ? g_strerror(errno) : /* connect() failed */
+ (iprec.errorstr != NULL ? iprec.errorstr : "Host lookup failed")); /* gethostbyname() failed */
+ g_free_not_null(iprec.errorstr);
+ return;
+ }
+
+ server->connect_tag = g_input_add(server->handle, G_INPUT_WRITE|G_INPUT_READ,
+ (GInputFunction) server_connect_callback_init, server);
+ signal_emit("server connecting", 2, server, &iprec.ip);
+}
+
+int server_connect(SERVER_REC *server)
+{
+ g_return_val_if_fail(server != NULL, FALSE);
+
+ MODULE_DATA_INIT(server);
+
+ if (pipe(server->connect_pipe) != 0) {
+ g_warning("server_connect(): pipe() failed.");
+ return FALSE;
+ }
+
+ server->tag = server_create_tag(server->connrec);
+ server->handle = -1;
+
+ server->connect_pid =
+ net_gethostname_nonblock(server->connrec->proxy != NULL ?
+ server->connrec->proxy : server->connrec->address,
+ server->connect_pipe[1]);
+
+ server->connect_tag =
+ g_input_add(server->connect_pipe[0], G_INPUT_READ,
+ (GInputFunction) server_connect_callback_readpipe, server);
+
+ lookup_servers = g_slist_append(lookup_servers, server);
+
+ signal_emit("server looking", 1, server);
+ return TRUE;
+}
+
+void server_disconnect(SERVER_REC *server)
+{
+ g_return_if_fail(server != NULL);
+
+ if (server->connect_tag != -1) {
+ /* still connecting to server.. */
+ if (server->connect_pid != -1)
+ net_disconnect_nonblock(server->connect_pid);
+ server_cant_connect(server, NULL);
+ return;
+ }
+
+ servers = g_slist_remove(servers, server);
+
+ signal_emit("server disconnected", 1, server);
+
+ if (server->handle != -1)
+ net_disconnect(server->handle);
+
+ MODULE_DATA_DEINIT(server);
+ rawlog_destroy(server->rawlog);
+ line_split_free(server->buffer);
+ g_free(server->tag);
+ g_free(server->nick);
+ g_free(server);
+}
+
+SERVER_REC *server_find_tag(const char *tag)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(tag != NULL, NULL);
+ if (*tag == '\0') return NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (strcmp(server->tag, tag) == 0)
+ return server;
+ }
+
+ for (tmp = lookup_servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (strcmp(server->tag, tag) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+SERVER_REC *server_find_ircnet(const char *ircnet)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(ircnet != NULL, NULL);
+ if (*ircnet == '\0') return NULL;
+
+ for (tmp = servers; tmp != NULL; tmp = tmp->next) {
+ SERVER_REC *server = tmp->data;
+
+ if (server->connrec->ircnet != NULL &&
+ strcmp(server->connrec->ircnet, ircnet) == 0) return server;
+ }
+
+ return NULL;
+}
+
+void servers_init(void)
+{
+ lookup_servers = servers = NULL;
+
+ servers_redirect_init();
+}
+
+void servers_deinit(void)
+{
+ while (servers != NULL)
+ server_disconnect(servers->data);
+ while (lookup_servers != NULL)
+ server_cant_connect(lookup_servers->data, NULL);
+
+ servers_redirect_deinit();
+}
diff --git a/src/core/server.h b/src/core/server.h
new file mode 100644
index 00000000..ea6ef94e
--- /dev/null
+++ b/src/core/server.h
@@ -0,0 +1,66 @@
+#ifndef __SERVER_H
+#define __SERVER_H
+
+#ifndef __NETWORK_H
+typedef struct _ipaddr IPADDR;
+#endif
+
+/* all strings should be either NULL or dynamically allocated */
+/* address and nick are mandatory, rest are optional */
+typedef struct {
+ /* if we're connecting via proxy, or just NULLs */
+ char *proxy;
+ int proxy_port;
+ char *proxy_string;
+
+ char *address;
+ int port;
+ char *ircnet;
+
+ IPADDR *own_ip;
+} SERVER_CONNECT_REC;
+
+typedef struct {
+ int type; /* server type */
+
+ SERVER_CONNECT_REC *connrec;
+ time_t connect_time; /* connection time */
+
+ char *tag; /* tag name for addressing server */
+ char *nick; /* current nick */
+
+ int connected:1; /* connected to server */
+ int connection_lost:1; /* Connection lost unintentionally */
+
+ int handle; /* socket handle */
+ int readtag; /* input tag */
+
+ /* for net_connect_nonblock() */
+ int connect_pipe[2];
+ int connect_tag;
+ int connect_pid;
+
+ /* For deciding if event should be handled internally */
+ GHashTable *eventtable; /* "event xxx" : GSList* of REDIRECT_RECs */
+ GHashTable *eventgrouptable; /* event group : GSList* of REDIRECT_RECs */
+ GHashTable *cmdtable; /* "command xxx" : REDIRECT_CMD_REC* */
+
+ void *rawlog;
+ void *buffer; /* receive buffer */
+ GHashTable *module_data;
+} SERVER_REC;
+
+extern GSList *servers, *lookup_servers;
+
+/* Connect to server */
+int server_connect(SERVER_REC *server);
+/* Disconnect from server */
+void server_disconnect(SERVER_REC *server);
+
+SERVER_REC *server_find_tag(const char *tag);
+SERVER_REC *server_find_ircnet(const char *ircnet);
+
+void servers_init(void);
+void servers_deinit(void);
+
+#endif
diff --git a/src/core/settings.c b/src/core/settings.c
new file mode 100644
index 00000000..af6e5ee6
--- /dev/null
+++ b/src/core/settings.c
@@ -0,0 +1,336 @@
+/*
+ settings.c : Irssi settings
+
+ Copyright (C) 1999 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "commands.h"
+
+#include "lib-config/iconfig.h"
+#include "settings.h"
+#include "default-config.h"
+
+#include <signal.h>
+
+CONFIG_REC *mainconfig;
+
+static GHashTable *settings;
+static char *last_error_msg;
+
+static const char *settings_get_default_str(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) {
+ g_warning("settings_get_default_str(%s) : unknown setting", key);
+ return NULL;
+ }
+
+ return rec->def;
+}
+
+static int settings_get_default_int(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, -1);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) {
+ g_warning("settings_get_default_int(%s) : unknown setting", key);
+ return -1;
+ }
+
+ return GPOINTER_TO_INT(rec->def);
+}
+
+const char *settings_get_str(const char *key)
+{
+ return iconfig_get_str("settings", key, settings_get_default_str(key));
+}
+
+const int settings_get_int(const char *key)
+{
+ return iconfig_get_int("settings", key, settings_get_default_int(key));
+}
+
+const int settings_get_bool(const char *key)
+{
+ return iconfig_get_bool("settings", key, settings_get_default_int(key));
+}
+
+void settings_add_str(const char *section, const char *key, const char *def)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_if_fail(rec == NULL);
+
+ rec = g_new0(SETTINGS_REC, 1);
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->def = def == NULL ? NULL : g_strdup(def);
+
+ g_hash_table_insert(settings, rec->key, rec);
+}
+
+void settings_add_int(const char *section, const char *key, int def)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_if_fail(rec == NULL);
+
+ rec = g_new0(SETTINGS_REC, 1);
+ rec->type = SETTING_TYPE_INT;
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->def = GINT_TO_POINTER(def);
+
+ g_hash_table_insert(settings, rec->key, rec);
+}
+
+void settings_add_bool(const char *section, const char *key, int def)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(section != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ g_return_if_fail(rec == NULL);
+
+ rec = g_new0(SETTINGS_REC, 1);
+ rec->type = SETTING_TYPE_BOOLEAN;
+ rec->key = g_strdup(key);
+ rec->section = g_strdup(section);
+ rec->def = GINT_TO_POINTER(def);
+
+ g_hash_table_insert(settings, rec->key, rec);
+}
+
+static void settings_destroy(SETTINGS_REC *rec)
+{
+ if (rec->type == SETTING_TYPE_STRING)
+ g_free_not_null(rec->def);
+ g_free(rec->section);
+ g_free(rec->key);
+ g_free(rec);
+}
+
+void settings_remove(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_if_fail(key != NULL);
+
+ rec = g_hash_table_lookup(settings, key);
+ if (rec == NULL) return;
+
+ g_hash_table_remove(settings, key);
+ settings_destroy(rec);
+}
+
+int settings_get_type(const char *key)
+{
+ SETTINGS_REC *rec;
+
+ g_return_val_if_fail(key != NULL, -1);
+
+ rec = g_hash_table_lookup(settings, key);
+ return rec == NULL ? -1 : rec->type;
+}
+
+/* Get the record of the setting */
+SETTINGS_REC *settings_get_record(const char *key)
+{
+ g_return_val_if_fail(key != NULL, NULL);
+
+ return g_hash_table_lookup(settings, key);
+}
+
+static int settings_compare(SETTINGS_REC *v1, SETTINGS_REC *v2)
+{
+ return strcmp(v1->section, v2->section);
+}
+
+static void settings_hash_get(const char *key, SETTINGS_REC *rec, GSList **list)
+{
+ *list = g_slist_insert_sorted(*list, rec, (GCompareFunc) settings_compare);
+}
+
+GSList *settings_get_sorted(void)
+{
+ GSList *list;
+
+ list = NULL;
+ g_hash_table_foreach(settings, (GHFunc) settings_hash_get, &list);
+ return list;
+}
+
+void sig_term(int n)
+{
+ /* if we get SIGTERM after this, just die instead of coming back here. */
+ signal(SIGTERM, SIG_DFL);
+
+ /* quit from all servers too.. */
+ signal_emit("command quit", 1, "");
+
+ /* and die */
+ raise(SIGTERM);
+}
+
+static CONFIG_REC *parse_configfile(const char *fname)
+{
+ CONFIG_REC *config;
+ char *str;
+
+ str = fname != NULL ? g_strdup(fname) :
+ g_strdup_printf("%s/.irssi/config", g_get_home_dir());
+ config = config_open(str, -1);
+ g_free(str);
+
+ if (config == NULL && *fname != '\0')
+ return NULL; /* specified config file not found */
+
+ if (config != NULL)
+ config_parse(config);
+ else {
+ /* user configuration file not found, use the default one
+ from sysconfdir */
+ config = config_open(SYSCONFDIR"/irssi/config", -1);
+ if (config != NULL)
+ config_parse(config);
+ else {
+ /* no configuration file in sysconfdir ..
+ use the build-in configuration */
+ config = config_open(NULL, -1);
+ config_parse_data(config, default_config, "internal");
+ }
+
+ config_change_file_name(config, fname, 0660);
+ }
+
+ return config;
+}
+
+static void sig_print_config_error(void)
+{
+ signal_emit("gui dialog", 2, "error", last_error_msg);
+ signal_remove("irssi init finished", (SIGNAL_FUNC) sig_print_config_error);
+
+ g_free_and_null(last_error_msg);
+}
+
+static void init_configfile(void)
+{
+ struct stat statbuf;
+ char *str;
+
+ str = g_strdup_printf("%s/.irssi", g_get_home_dir());
+ if (stat(str, &statbuf) != 0) {
+ /* ~/.irssi not found, create it. */
+ if (mkdir(str, 0700) != 0)
+ g_error("Couldn't create %s/.irssi directory", g_get_home_dir());
+ }
+ g_free(str);
+
+ mainconfig = parse_configfile(NULL);
+
+ /* any errors? */
+ if (config_last_error(mainconfig) != NULL) {
+ last_error_msg = g_strdup_printf("Ignored errors in configuration file:\n%s",
+ config_last_error(mainconfig));
+ signal_add("irssi init finished", (SIGNAL_FUNC) sig_print_config_error);
+ }
+
+ signal(SIGTERM, sig_term);
+}
+
+static void cmd_rehash(const char *data)
+{
+ CONFIG_REC *tempconfig;
+ char *str, *fname;
+
+ fname = *data != '\0' ? g_strdup(data) :
+ g_strdup_printf("%s/.irssi/config", g_get_home_dir());
+ tempconfig = parse_configfile(fname);
+ g_free(fname);
+
+ if (tempconfig == NULL) {
+ signal_emit("gui dialog", 2, "error", g_strerror(errno));
+ return;
+ }
+
+ if (config_last_error(tempconfig) != NULL) {
+ /* error */
+ str = g_strdup_printf("Errors in configuration file:\n%s",
+ config_last_error(tempconfig));
+ signal_emit("gui dialog", 2, "error", str);
+ g_free(str);
+ config_close(tempconfig);
+ return;
+ }
+
+ config_close(mainconfig);
+ mainconfig = tempconfig;
+
+ signal_emit("setup changed", 0);
+ signal_emit("setup reread", 0);
+}
+
+static void cmd_save(const char *data)
+{
+ config_write(mainconfig, *data == '\0' ? NULL : data, 0660);
+}
+
+void settings_init(void)
+{
+ settings = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
+
+ init_configfile();
+ command_bind("rehash", NULL, (SIGNAL_FUNC) cmd_rehash);
+ command_bind("save", NULL, (SIGNAL_FUNC) cmd_save);
+}
+
+static void settings_hash_free(const char *key, SETTINGS_REC *rec)
+{
+ settings_destroy(rec);
+}
+
+void settings_deinit(void)
+{
+ command_unbind("rehash", (SIGNAL_FUNC) cmd_rehash);
+ command_unbind("save", (SIGNAL_FUNC) cmd_save);
+
+ g_free_not_null(last_error_msg);
+ g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL);
+ g_hash_table_destroy(settings);
+
+ if (mainconfig != NULL) config_close(mainconfig);
+}
diff --git a/src/core/settings.h b/src/core/settings.h
new file mode 100644
index 00000000..9198bba9
--- /dev/null
+++ b/src/core/settings.h
@@ -0,0 +1,56 @@
+#ifndef __SETTINGS_H
+#define __SETTINGS_H
+
+#ifndef __ICONFIG_H
+typedef struct _config_rec CONFIG_REC;
+#endif
+
+enum {
+ SETTING_TYPE_STRING,
+ SETTING_TYPE_INT,
+ SETTING_TYPE_BOOLEAN,
+};
+
+typedef struct {
+ int type;
+ char *key;
+ char *section;
+ void *def;
+} SETTINGS_REC;
+
+/* macros for handling the default Irssi configuration */
+#define iconfig_get_str(a, b, c) config_get_str(mainconfig, a, b,c)
+#define iconfig_get_int(a, b, c) config_get_int(mainconfig, a, b,c)
+#define iconfig_get_bool(a, b, c) config_get_bool(mainconfig, a, b,c)
+#define iconfig_list_find(a, b, c, d) config_list_find(mainconfig, a, b, c, d)
+
+#define iconfig_set_str(a, b, c) config_set_str(mainconfig, a, b,c)
+#define iconfig_set_int(a, b, c) config_set_int(mainconfig, a, b,c)
+#define iconfig_set_bool(a, b, c) config_set_bool(mainconfig, a, b,c)
+
+#define iconfig_node_traverse(a, b) config_node_traverse(mainconfig, a, b)
+
+extern CONFIG_REC *mainconfig;
+
+/* Functions for handling the "settings" node of Irssi configuration */
+const char *settings_get_str(const char *key);
+const int settings_get_int(const char *key);
+const int settings_get_bool(const char *key);
+
+/* Functions to add/remove settings */
+void settings_add_str(const char *section, const char *key, const char *def);
+void settings_add_int(const char *section, const char *key, int def);
+void settings_add_bool(const char *section, const char *key, int def);
+void settings_remove(const char *key);
+
+/* Get the type (SETTING_TYPE_xxx) of `key' */
+int settings_get_type(const char *key);
+/* Get all settings sorted by section. Free the result with g_slist_free() */
+GSList *settings_get_sorted(void);
+/* Get the record of the setting */
+SETTINGS_REC *settings_get_record(const char *key);
+
+void settings_init(void);
+void settings_deinit(void);
+
+#endif
diff --git a/src/core/signals.c b/src/core/signals.c
new file mode 100644
index 00000000..cbd92d45
--- /dev/null
+++ b/src/core/signals.c
@@ -0,0 +1,356 @@
+/*
+ signals.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "../common.h"
+#include "signals.h"
+#include "modules.h"
+
+#define SIGNAL_LISTS 3
+
+typedef struct {
+ int emitting; /* signal is being emitted */
+ int altered; /* some signal functions are marked as NULL */
+ int stop_emit; /* this signal was stopped */
+
+ GPtrArray *modulelist[SIGNAL_LISTS]; /* list of what signals belong
+ to which module */
+ GPtrArray *siglist[SIGNAL_LISTS]; /* signal lists */
+} SIGNAL_REC;
+
+#define signal_is_emitlist_empty(a) \
+ (!(a)->siglist[0] && !(a)->siglist[1] && !(a)->siglist[2])
+
+static GMemChunk *signals_chunk;
+static GHashTable *signals;
+static SIGNAL_REC *first_signal_rec, *last_signal_rec; /* "signal" and "last signal" */
+static SIGNAL_REC *current_emitted_signal;
+
+/* bind a signal */
+void signal_add_to(const char *module, int pos, const char *signal, SIGNAL_FUNC func)
+{
+ SIGNAL_REC *rec;
+ int signal_id;
+
+ g_return_if_fail(signal != NULL);
+ g_return_if_fail(func != NULL);
+ g_return_if_fail(pos >= 0 && pos < SIGNAL_LISTS);
+
+ signal_id = module_get_uniq_id_str("signals", signal);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec == NULL) {
+ rec = g_mem_chunk_alloc0(signals_chunk);
+ g_hash_table_insert(signals, GINT_TO_POINTER(signal_id), rec);
+ }
+
+ if (strcmp(signal, "signal") == 0)
+ first_signal_rec = rec;
+ else if (strcmp(signal, "last signal") == 0)
+ last_signal_rec = rec;
+
+ if (rec->siglist[pos] == NULL) {
+ rec->siglist[pos] = g_ptr_array_new();
+ rec->modulelist[pos] = g_ptr_array_new();
+ }
+
+ g_ptr_array_add(rec->siglist[pos], (void *) func);
+ g_ptr_array_add(rec->modulelist[pos], (void *) module);
+}
+
+/* Destroy the whole signal */
+static void signal_destroy(int signal_id)
+{
+ SIGNAL_REC *rec;
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL) {
+ /* remove whole signal from memory */
+ g_hash_table_remove(signals, GINT_TO_POINTER(signal_id));
+ g_free(rec);
+ }
+
+ if (first_signal_rec == rec)
+ first_signal_rec = NULL;
+ else if (last_signal_rec == rec)
+ last_signal_rec = NULL;
+}
+
+static int signal_list_find(GPtrArray *array, void *data)
+{
+ int n;
+
+ for (n = 0; n < array->len; n++) {
+ if (g_ptr_array_index(array, n) == data)
+ return n;
+ }
+
+ return -1;
+}
+
+static void signal_remove_from_list(SIGNAL_REC *rec, int signal_id, int list, int index)
+{
+ if (rec->emitting) {
+ g_ptr_array_index(rec->siglist[list], index) = NULL;
+ rec->altered = TRUE;
+ } else {
+ g_ptr_array_remove_index(rec->siglist[list], index);
+ g_ptr_array_remove_index(rec->modulelist[list], index);
+ if (signal_is_emitlist_empty(rec))
+ signal_destroy(signal_id);
+ }
+}
+
+/* Remove signal from emit lists */
+static int signal_remove_from_lists(SIGNAL_REC *rec, int signal_id, SIGNAL_FUNC func)
+{
+ int n, index;
+
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ if (rec->siglist[n] == NULL)
+ continue;
+
+ index = signal_list_find(rec->siglist[n], (void *) func);
+ if (index != -1) {
+ /* remove the function from emit list */
+ signal_remove_from_list(rec, signal_id, n, index);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* unbind signal */
+void signal_remove(const char *signal, SIGNAL_FUNC func)
+{
+ SIGNAL_REC *rec;
+ int signal_id, found;
+
+ g_return_if_fail(signal != NULL);
+ g_return_if_fail(func != NULL);
+
+ signal_id = module_get_uniq_id_str("signals", signal);
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ found = rec == NULL ? 0 : signal_remove_from_lists(rec, signal_id, func);
+
+ if (!found) g_warning("signal_remove() : signal \"%s\" isn't grabbed for %p", signal, func);
+}
+
+/* Remove all NULL functions from signal list */
+static void signal_list_clean(SIGNAL_REC *rec)
+{
+ int n, index;
+
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ if (rec->siglist[n] == NULL)
+ continue;
+
+ for (index = rec->siglist[n]->len-1; index >= 0; index--) {
+ if (g_ptr_array_index(rec->siglist[n], index) == NULL) {
+ g_ptr_array_remove_index(rec->siglist[n], index);
+ g_ptr_array_remove_index(rec->modulelist[n], index);
+ }
+ }
+ }
+}
+
+static int signal_emit_real(SIGNAL_REC *rec, gconstpointer *arglist)
+{
+ SIGNAL_REC *prev_emitted_signal;
+ SIGNAL_FUNC func;
+ int n, index, stopped;
+
+ stopped = FALSE;
+ rec->emitting++;
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ /* run signals in emit lists */
+ if (rec->siglist[n] == NULL)
+ continue;
+
+ for (index = rec->siglist[n]->len-1; index >= 0; index--) {
+ func = (SIGNAL_FUNC) g_ptr_array_index(rec->siglist[n], index);
+
+ if (func != NULL) {
+ prev_emitted_signal = current_emitted_signal;
+ current_emitted_signal = rec;
+ func(arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5], arglist[6]);
+ current_emitted_signal = prev_emitted_signal;
+ }
+
+ if (rec->stop_emit) {
+ stopped = TRUE;
+ rec->stop_emit--;
+ n = SIGNAL_LISTS;
+ break;
+ }
+ }
+ }
+ rec->emitting--;
+
+ if (!rec->emitting && rec->altered) {
+ signal_list_clean(rec);
+ rec->altered = FALSE;
+ }
+
+ return stopped;
+}
+
+static int signal_emitv_id(int signal_id, int params, va_list va)
+{
+ gconstpointer arglist[8];
+ SIGNAL_REC *rec;
+ int n;
+
+ g_return_val_if_fail(signal_id >= 0, FALSE);
+ g_return_val_if_fail(params >= 0 && params <= sizeof(arglist)/sizeof(arglist[0]), FALSE);
+
+ arglist[0] = GINT_TO_POINTER(signal_id);
+ for (n = 1; n < 8; n++)
+ arglist[n] = n >= params ? NULL : va_arg(va, gconstpointer);
+
+ /* send "signal" */
+ if (first_signal_rec != NULL) {
+ if (signal_emit_real(first_signal_rec, arglist))
+ return TRUE;
+ }
+
+ rec = g_hash_table_lookup(signals, GINT_TO_POINTER(signal_id));
+ if (rec != NULL && signal_emit_real(rec, arglist+1))
+ return TRUE;
+
+ /* send "last signal" */
+ if (last_signal_rec != NULL) {
+ if (signal_emit_real(last_signal_rec, arglist))
+ return TRUE;
+ }
+
+ return rec != NULL;
+}
+
+int signal_emit(const char *signal, int params, ...)
+{
+ va_list va;
+ int signal_id, ret;
+
+ /* get arguments */
+ signal_id = module_get_uniq_id_str("signals", signal);
+
+ va_start(va, params);
+ ret = signal_emitv_id(signal_id, params, va);
+ va_end(va);
+
+ return ret;
+}
+
+int signal_emit_id(int signal_id, int params, ...)
+{
+ va_list va;
+ int ret;
+
+ /* get arguments */
+ va_start(va, params);
+ ret = signal_emitv_id(signal_id, params, va);
+ va_end(va);
+
+ return ret;
+}
+
+/* stop the current ongoing signal emission */
+void signal_stop(void)
+{
+ SIGNAL_REC *rec;
+
+ rec = current_emitted_signal;
+ if (rec == NULL || rec->emitting <= rec->stop_emit)
+ g_warning("signal_stop() : no signals are being emitted currently");
+ else
+ rec->stop_emit++;
+}
+
+/* stop ongoing signal emission by signal name */
+void signal_stop_by_name(const char *signal)
+{
+ SIGNAL_REC *rec;
+
+ rec = g_hash_table_lookup(signals, signal);
+ if (rec == NULL)
+ g_warning("signal_stop_by_name() : unknown signal \"%s\"", signal);
+ else if (rec->emitting <= rec->stop_emit)
+ g_warning("signal_stop_by_name() : signal \"%s\" not being emitted", signal);
+ else
+ rec->stop_emit++;
+}
+
+static void signal_remove_module(void *signal, SIGNAL_REC *rec, const char *module)
+{
+ int signal_id, list, index;
+
+ signal_id = GPOINTER_TO_INT(signal);
+
+ for (list = 0; list < SIGNAL_LISTS; list++) {
+ for (index = 0; index < rec->modulelist[list]->len; index++) {
+ if (g_strcasecmp(g_ptr_array_index(rec->modulelist[list], index), module) == 0)
+ signal_remove_from_list(rec, signal_id, list, index);
+ }
+ }
+}
+
+/* remove all signals that belong to `module' */
+void signals_remove_module(const char *module)
+{
+ g_return_if_fail(module != NULL);
+
+ g_hash_table_foreach(signals, (GHFunc) signal_remove_module, (void *) module);
+}
+
+void signals_init(void)
+{
+ signals_chunk = g_mem_chunk_new("signals", sizeof(SIGNAL_REC),
+ sizeof(SIGNAL_REC)*200, G_ALLOC_AND_FREE);
+ signals = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
+
+ first_signal_rec = NULL;
+ last_signal_rec = NULL;
+}
+
+static void signal_free(void *key, SIGNAL_REC *rec)
+{
+ int n;
+
+ for (n = 0; n < SIGNAL_LISTS; n++) {
+ if (rec->siglist[n] != NULL) {
+ g_ptr_array_free(rec->siglist[n], TRUE);
+ g_ptr_array_free(rec->modulelist[n], TRUE);
+ }
+ }
+
+ g_mem_chunk_free(signals_chunk, rec);
+ current_emitted_signal = NULL;
+}
+
+void signals_deinit(void)
+{
+ g_hash_table_foreach(signals, (GHFunc) signal_free, NULL);
+ g_hash_table_destroy(signals);
+
+ module_uniq_destroy("signals");
+ g_mem_chunk_destroy(signals_chunk);
+}
diff --git a/src/core/signals.h b/src/core/signals.h
new file mode 100644
index 00000000..613aa245
--- /dev/null
+++ b/src/core/signals.h
@@ -0,0 +1,30 @@
+#ifndef __SIGNAL_H
+#define __SIGNAL_H
+
+typedef void (*SIGNAL_FUNC) (gconstpointer, gconstpointer, gconstpointer, gconstpointer, gconstpointer, gconstpointer, gconstpointer);
+
+void signals_init(void);
+void signals_deinit(void);
+
+/* bind a signal */
+void signal_add_to(const char *module, int pos, const char *signal, SIGNAL_FUNC func);
+#define signal_add(a, b) signal_add_to(MODULE_NAME, 1, a, b)
+#define signal_add_first(a, b) signal_add_to(MODULE_NAME, 0, a, b)
+#define signal_add_last(a, b) signal_add_to(MODULE_NAME, 2, a, b)
+
+/* unbind signal */
+void signal_remove(const char *signal, SIGNAL_FUNC func);
+
+/* emit signal */
+int signal_emit(const char *signal, int params, ...);
+int signal_emit_id(int signal_id, int params, ...);
+
+/* stop the current ongoing signal emission */
+void signal_stop(void);
+/* stop ongoing signal emission by signal name */
+void signal_stop_by_name(const char *signal);
+
+/* remove all signals that belong to `module' */
+void signals_remove_module(const char *module);
+
+#endif
diff --git a/src/core/special-vars.c b/src/core/special-vars.c
new file mode 100644
index 00000000..26788a65
--- /dev/null
+++ b/src/core/special-vars.c
@@ -0,0 +1,635 @@
+/*
+ special-vars.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "module.h"
+#include "signals.h"
+#include "special-vars.h"
+#include "settings.h"
+#include "misc.h"
+#include "irssi-version.h"
+
+#include <sys/utsname.h>
+
+#define ALIGN_RIGHT 0x01
+#define ALIGN_CUT 0x02
+
+static EXPANDO_FUNC char_expandos[256];
+static GHashTable *expandos;
+static time_t client_start_time;
+static SPECIAL_HISTORY_FUNC history_func;
+
+static char *get_argument(char **cmd, char **arglist)
+{
+ GString *str;
+ char *ret;
+ int max, arg, argcount;
+
+ arg = 0;
+ max = -1;
+
+ argcount = strarray_length(arglist);
+
+ if (**cmd == '*') {
+ /* get all arguments */
+ } else if (**cmd == '~') {
+ /* get last argument */
+ arg = max = argcount-1;
+ } else {
+ if (isdigit(**cmd)) {
+ /* first argument */
+ arg = max = (**cmd)-'0';
+ (*cmd)++;
+ }
+
+ if (**cmd == '-') {
+ /* get more than one argument */
+ (*cmd)++;
+ if (!isdigit(**cmd))
+ max = -1; /* get all the rest */
+ else {
+ max = (**cmd)-'0';
+ (*cmd)++;
+ }
+ }
+ (*cmd)--;
+ }
+
+ str = g_string_new(NULL);
+ while (arg < argcount && (arg <= max || max == -1)) {
+ g_string_append(str, arglist[arg]);
+ g_string_append_c(str, ' ');
+ arg++;
+ }
+ if (str->len > 0) g_string_truncate(str, str->len-1);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+static char *get_internal_setting(const char *key, int type, int *free_ret)
+{
+ switch (type) {
+ case SETTING_TYPE_BOOLEAN:
+ return settings_get_bool(key) ? "yes" : "no";
+ case SETTING_TYPE_INT:
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", settings_get_int(key));
+ case SETTING_TYPE_STRING:
+ return (char *) settings_get_str(key);
+ }
+
+ return NULL;
+}
+
+static char *get_long_variable_value(const char *key, void *server, void *item, int *free_ret)
+{
+ EXPANDO_FUNC func;
+ char *ret;
+ int type;
+
+ *free_ret = FALSE;
+
+ /* expando? */
+ func = g_hash_table_lookup(expandos, key);
+ if (func != NULL)
+ return func(server, item, free_ret);
+
+ /* internal setting? */
+ type = settings_get_type(key);
+ if (type != -1)
+ return get_internal_setting(key, type, free_ret);
+
+ /* environment variable? */
+ ret = g_getenv(key);
+ if (ret != NULL) {
+ *free_ret = TRUE;
+ return ret;
+ }
+
+ return NULL;
+}
+
+static char *get_long_variable(char **cmd, void *server, void *item, int *free_ret)
+{
+ char *start, *var, *ret;
+
+ /* get variable name */
+ start = *cmd;
+ while (isalnum((*cmd)[1])) (*cmd)++;
+
+ var = g_strndup(start, (int) (*cmd-start)+1);
+ ret = get_long_variable_value(var, server, item, free_ret);
+ g_free(var);
+ return ret;
+}
+
+/* return the value of the variable found from `cmd' */
+static char *get_variable(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used)
+{
+ if (isdigit(**cmd) || **cmd == '*' || **cmd == '-' || **cmd == '~') {
+ /* argument */
+ *free_ret = TRUE;
+ if (arg_used != NULL) *arg_used = TRUE;
+ return get_argument(cmd, arglist);
+ }
+
+ if (isalpha(**cmd) && isalnum((*cmd)[1])) {
+ /* long variable name.. */
+ return get_long_variable(cmd, server, item, free_ret);
+ }
+
+ /* single character variable. */
+ *free_ret = FALSE;
+ return char_expandos[(int) **cmd] == NULL ? NULL :
+ char_expandos[(int) **cmd](server, item, free_ret);
+}
+
+static char *get_history(char **cmd, void *item, int *free_ret)
+{
+ char *start, *text, *ret;
+
+ /* get variable name */
+ start = ++(*cmd);
+ while (**cmd != '\0' && **cmd != '!') (*cmd)++;
+
+ if (history_func == NULL)
+ ret = NULL;
+ else {
+ text = g_strndup(start, (int) (*cmd-start)+1);
+ ret = history_func(text, item, free_ret);
+ g_free(text);
+ }
+
+ if (**cmd == '\0') (*cmd)--;
+ return ret;
+}
+
+static char *get_special_value(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used)
+{
+ char command, *value, *p;
+ int len;
+
+ if (**cmd == '!') {
+ /* find text from command history */
+ return get_history(cmd, item, free_ret);
+ }
+
+ command = 0;
+ if (**cmd == '#' || **cmd == '@') {
+ command = **cmd;
+ if ((*cmd)[1] != '\0')
+ (*cmd)++;
+ else {
+ /* default to $* */
+ char *temp_cmd = "*";
+
+ *free_ret = TRUE;
+ return get_argument(&temp_cmd, arglist);
+ }
+ }
+
+ value = get_variable(cmd, server, item, arglist, free_ret, arg_used);
+
+ if (command == '#') {
+ /* number of words */
+ if (value == NULL || *value == '\0') {
+ if (value != NULL && *free_ret) {
+ g_free(value);
+ *free_ret = FALSE;
+ }
+ return "0";
+ }
+
+ len = 1;
+ for (p = value; *p != '\0'; p++) {
+ if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
+ len++;
+ }
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", len);
+ }
+
+ if (command == '@') {
+ /* number of characters */
+ if (value == NULL) return "0";
+
+ len = strlen(value);
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return g_strdup_printf("%d", len);
+ }
+
+ return value;
+}
+
+/* get alignment arguments (inside the []) */
+static int get_alignment_args(char **data, int *align, int *flags, char *pad)
+{
+ char *str;
+
+ *align = 0;
+ *flags = ALIGN_CUT;
+ *pad = ' ';
+
+ /* '!' = don't cut, '-' = right padding */
+ str = *data;
+ while (*str != '\0' && *str != ']' && !isdigit(*str)) {
+ if (*str == '!')
+ *flags &= ~ALIGN_CUT;
+ else if (*str == '-')
+ *flags |= ALIGN_RIGHT;
+ str++;
+ }
+ if (!isdigit(*str))
+ return FALSE; /* expecting number */
+
+ /* get the alignment size */
+ while (isdigit(*str)) {
+ *align = (*align) * 10 + (*str-'0');
+ str++;
+ }
+
+ /* get the pad character */
+ while (*str != '\0' && *str != ']') {
+ *pad = *str;
+ str++;
+ }
+
+ if (*str++ != ']') return FALSE;
+
+ *data = str;
+ return TRUE;
+}
+
+/* return the aligned text */
+static char *get_alignment(const char *text, int align, int flags, char pad)
+{
+ GString *str;
+ char *ret;
+
+ g_return_val_if_fail(text != NULL, NULL);
+
+ str = g_string_new(text);
+
+ /* cut */
+ if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
+ g_string_truncate(str, align);
+
+ /* add pad characters */
+ while (str->len < align) {
+ if (flags & ALIGN_RIGHT)
+ g_string_prepend_c(str, pad);
+ else
+ g_string_append_c(str, pad);
+ }
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* Parse and expand text after '$' character. return value has to be
+ g_free()'d if `free_ret' is TRUE. */
+char *parse_special(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used)
+{
+ static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
+ char command, *value;
+
+ char align_pad;
+ int align, align_flags;
+
+ char *nest_value;
+ int brackets, nest_free;
+
+ *free_ret = FALSE;
+
+ command = **cmd; (*cmd)++;
+ switch (command) {
+ case '[':
+ /* alignment */
+ if (!get_alignment_args(cmd, &align, &align_flags, &align_pad) ||
+ **cmd == '\0') {
+ (*cmd)--;
+ return NULL;
+ }
+ break;
+ default:
+ command = 0;
+ (*cmd)--;
+ }
+
+ nest_free = FALSE; nest_value = NULL;
+ if (**cmd == '(') {
+ /* subvariable */
+ int toplevel = nested_orig_cmd == NULL;
+
+ if (toplevel) nested_orig_cmd = cmd;
+ (*cmd)++;
+ if (**cmd != '$') {
+ /* ... */
+ nest_value = *cmd;
+ } else {
+ (*cmd)++;
+ nest_value = parse_special(cmd, server, item, arglist, &nest_free, arg_used);
+ }
+
+ while ((*nested_orig_cmd)[1] != '\0') {
+ (*nested_orig_cmd)++;
+ if (**nested_orig_cmd == ')') break;
+ }
+ cmd = &nest_value;
+
+ if (toplevel) nested_orig_cmd = NULL;
+ }
+
+ if (**cmd != '{')
+ brackets = FALSE;
+ else {
+ /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
+ (*cmd)++;
+ brackets = TRUE;
+ }
+
+ value = get_special_value(cmd, server, item, arglist, free_ret, arg_used);
+ if (**cmd == '\0')
+ g_error("parse_special() : buffer overflow!");
+
+ if (brackets) {
+ while (**cmd != '}' && (*cmd)[1] != '\0')
+ (*cmd)++;
+ }
+
+ if (nest_free) g_free(nest_value);
+
+ if (command == '[') {
+ /* alignment */
+ char *p;
+
+ if (value == NULL) return "";
+
+ p = get_alignment(value, align, align_flags, align_pad);
+ if (*free_ret) g_free(value);
+
+ *free_ret = TRUE;
+ return p;
+ }
+
+ return value;
+}
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, void *server, void *item, const char *data, int *arg_used)
+{
+ char code, **arglist, *ret;
+ GString *str;
+ int need_free;
+
+ g_return_val_if_fail(cmd != NULL, NULL);
+ g_return_val_if_fail(data != NULL, NULL);
+
+ /* create the argument list */
+ arglist = g_strsplit(data, " ", -1);
+
+ if (arg_used != NULL) *arg_used = FALSE;
+ code = 0;
+ str = g_string_new(NULL);
+ while (*cmd != '\0') {
+ if (code == '\\'){
+ g_string_append_c(str, *cmd);
+ code = 0;
+ } else if (code == '$') {
+ char *ret;
+
+ ret = parse_special((char **) &cmd, server, item, arglist, &need_free, arg_used);
+ if (ret != NULL) {
+ g_string_append(str, ret);
+ if (need_free) g_free(ret);
+ }
+ code = 0;
+ } else {
+ if (*cmd == '\\' || *cmd == '$')
+ code = *cmd;
+ else
+ g_string_append_c(str, *cmd);
+ }
+
+ cmd++;
+ }
+ g_strfreev(arglist);
+
+ ret = str->str;
+ g_string_free(str, FALSE);
+ return ret;
+}
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data, void *server, void *item)
+{
+ const char *cmdchars;
+ char *orig, *str, *start, *ret;
+ int arg_used;
+
+ cmdchars = settings_get_str("cmdchars");
+ orig = start = str = g_strdup(cmd);
+ do {
+ if (*str == ';' && (start == str || (str[-1] != '\\' && str[-1] != '$')))
+ *str++ = '\0';
+ else if (*str != '\0') {
+ str++;
+ continue;
+ }
+
+ ret = parse_special_string(start, server, item, data, &arg_used);
+ if (strchr(cmdchars, *ret) == NULL) {
+ /* no command char - let's put it there.. */
+ char *old = ret;
+
+ ret = g_strdup_printf("%c%s", *cmdchars, old);
+ g_free(old);
+ }
+ if (!arg_used && *data != '\0') {
+ /* append the string with all the arguments */
+ char *old = ret;
+
+ ret = g_strconcat(old, " ", data, NULL);
+ g_free(old);
+ }
+ signal_emit("send command", 3, ret, server, item);
+ g_free(ret);
+
+ start = str;
+ } while (*start != '\0');
+
+ g_free(orig);
+}
+
+/* Create expando - overrides any existing ones. */
+void expando_create(const char *key, EXPANDO_FUNC func)
+{
+ gpointer origkey, origvalue;
+
+ g_return_if_fail(key != NULL || *key == '\0');
+ g_return_if_fail(func != NULL);
+
+ if (key[1] == '\0') {
+ /* single character expando */
+ char_expandos[(int) *key] = func;
+ return;
+ }
+
+ if (g_hash_table_lookup_extended(expandos, key, &origkey, &origvalue)) {
+ g_free(origkey);
+ g_hash_table_remove(expandos, key);
+ }
+ g_hash_table_insert(expandos, g_strdup(key), func);
+}
+
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func)
+{
+ gpointer origkey, origvalue;
+
+ g_return_if_fail(key != NULL || *key == '\0');
+ g_return_if_fail(func != NULL);
+
+ if (key[1] == '\0') {
+ /* single character expando */
+ if (char_expandos[(int) *key] == func)
+ char_expandos[(int) *key] = NULL;
+ return;
+ }
+
+ if (g_hash_table_lookup_extended(expandos, key, &origkey, &origvalue)) {
+ g_free(origkey);
+ g_hash_table_remove(expandos, key);
+ }
+}
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func)
+{
+ history_func = func;
+}
+
+/* time client was started, $time() format */
+static char *expando_clientstarted(void *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_strdup_printf("%ld", (long) client_start_time);
+}
+
+/* client version text string */
+static char *expando_version(void *server, void *item, int *free_ret)
+{
+ return IRSSI_VERSION;
+}
+
+/* current value of CMDCHARS */
+static char *expando_cmdchars(void *server, void *item, int *free_ret)
+{
+ return (char *) settings_get_str("cmdchars");
+}
+
+/* client release date (numeric version string) */
+static char *expando_releasedate(void *server, void *item, int *free_ret)
+{
+ return IRSSI_VERSION_DATE;
+}
+
+/* current working directory */
+static char *expando_workdir(void *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return g_get_current_dir();
+}
+
+/* time of day (hh:mm) */
+static char *expando_time(void *server, void *item, int *free_ret)
+{
+ time_t now = time(NULL);
+ struct tm *tm;
+
+ tm = localtime(&now);
+ *free_ret = TRUE;
+ return g_strdup_printf("%02d:%02d", tm->tm_hour, tm->tm_min);
+}
+
+/* a literal '$' */
+static char *expando_dollar(void *server, void *item, int *free_ret)
+{
+ return "$";
+}
+
+/* system name */
+static char *expando_sysname(void *server, void *item, int *free_ret)
+{
+ struct utsname un;
+
+ if (uname(&un) != 0)
+ return NULL;
+
+ *free_ret = TRUE;
+ return g_strdup(un.sysname);
+
+}
+
+/* system release */
+static char *expando_sysrelease(void *server, void *item, int *free_ret)
+{
+ struct utsname un;
+
+ if (uname(&un) != 0)
+ return NULL;
+
+ *free_ret = TRUE;
+ return g_strdup(un.release);
+
+}
+
+void special_vars_init(void)
+{
+ client_start_time = time(NULL);
+
+ memset(char_expandos, 0, sizeof(char_expandos));
+ expandos = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
+ history_func = NULL;
+
+ char_expandos['F'] = expando_clientstarted;
+ char_expandos['J'] = expando_version;
+ char_expandos['K'] = expando_cmdchars;
+ char_expandos['V'] = expando_releasedate;
+ char_expandos['W'] = expando_workdir;
+ char_expandos['Z'] = expando_time;
+ char_expandos['$'] = expando_dollar;
+
+ expando_create("sysname", expando_sysname);
+ expando_create("sysrelease", expando_sysrelease);
+}
+
+void special_vars_deinit(void)
+{
+ expando_destroy("sysname", expando_sysname);
+ expando_destroy("sysrelease", expando_sysrelease);
+
+ g_hash_table_destroy(expandos);
+}
diff --git a/src/core/special-vars.h b/src/core/special-vars.h
new file mode 100644
index 00000000..c40a2fcb
--- /dev/null
+++ b/src/core/special-vars.h
@@ -0,0 +1,27 @@
+#ifndef __SPECIAL_VARS_H
+#define __SPECIAL_VARS_H
+
+typedef char* (*EXPANDO_FUNC) (void *server, void *item, int *free_ret);
+typedef char* (*SPECIAL_HISTORY_FUNC) (const char *text, void *item, int *free_ret);
+
+/* Parse and expand text after '$' character. return value has to be
+ g_free()'d if `free_ret' is TRUE. */
+char *parse_special(char **cmd, void *server, void *item, char **arglist, int *free_ret, int *arg_used);
+
+/* parse the whole string. $ and \ chars are replaced */
+char *parse_special_string(const char *cmd, void *server, void *item, const char *data, int *arg_used);
+
+/* execute the commands in string - commands can be split with ';' */
+void eval_special_string(const char *cmd, const char *data, void *server, void *item);
+
+/* Create expando - overrides any existing ones. */
+void expando_create(const char *key, EXPANDO_FUNC func);
+/* Destroy expando */
+void expando_destroy(const char *key, EXPANDO_FUNC func);
+
+void special_history_func_set(SPECIAL_HISTORY_FUNC func);
+
+void special_vars_init(void);
+void special_vars_deinit(void);
+
+#endif