diff options
author | Timo Sirainen <cras@irssi.org> | 2000-04-26 08:03:38 +0000 |
---|---|---|
committer | cras <cras@dbcabf3a-b0e7-0310-adc4-f8d773084564> | 2000-04-26 08:03:38 +0000 |
commit | c95034c6de1bf72536595e1e3431d8ec64b9880e (patch) | |
tree | e51aa4528257ed8aa9d53640649519f299aaf0c7 /src/core | |
parent | d01b094151705d433bc43cae9eeb304e6f110a17 (diff) | |
download | irssi-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')
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 |