/* * vars.c: All the dealing of the irc variables are handled here. * * Copyright (c) 1990 Michael Sandroff. * Copyright (c) 1991, 1992 Troy Rollo. * Copyright (c) 1992-1996 Matthew Green. * Copyright 1993, 2003 EPIC Software Labs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notices, the above paragraph (the one permitting redistribution), * this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The names of the author(s) may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define VARS_C #include "irc.h" #include "alist.h" #include "alias.h" #include "status.h" #include "window.h" #include "lastlog.h" #include "log.h" #include "hook.h" #include "list.h" #include "sedcrypt.h" #include "notify.h" #include "vars.h" #include "input.h" #include "ircaux.h" #include "termx.h" #include "output.h" #include "stack.h" #include "dcc.h" #include "keys.h" #include "timer.h" #include "clock.h" #include "mail.h" #include "reg.h" #include "commands.h" #include "ifcmd.h" #include "ssl.h" /* * About /SETs and BIVs * * A /SET, is a persistent global variable. * A /SET has a type * Boolean -- ON or OFF * String * Integer * Character * Much of the behavior of the client is controlled/configured by /SETs. * * Formally, /SETs are known as Built-In Variables ("BIVs") and you'll see that in * alias.c in the unified symbol table. * * But in this file here, BIVs refer to those /SETs that are hardcoded in the client, * and not /SETs that are created by the user at runtime. BIVs are created when the * client boots. * * Because symbol lookups (by string) are expensive, and because /SETs are used so * extensively, BIVs have an optimization abstraction that lets C code get a /SET in * O(1) time. * * Most of the things in this file are related to BIV /SETs. There is also the /SET * command, which applies to all SETs, builtin or hardcoded. * * You can always do a full name-to-object lookup with bucket_builtin_variables(), * but you can only do a O(1) lookup on BIVs. */ /* * This bucket contains ONLY THE HARDCODED /SETs * This is duplicative of the user-facing /SET list maintained by the symbol table. * The routines that do O(1) mapping of /set's to (IrcVariable) objects * will use this bucket for their lookups. */ Bucket *var_bucket = NULL; static void set_mangle_inbound (void *); static void set_mangle_outbound (void *); static void set_mangle_logfiles (void *); static void set_mangle_display (void *); static void update_all_status_wrapper (void *); static void update_all_windows_wrapper (void *); static void set_wserv_type (void *); static void set_indent (void *); char * make_string_var_bydata_internal (int type, const VARIABLE *data); /* * add_biv -- Create a Hard-coded /SET at client-startup. * * Arguments: * name - The name of the /SET * bucket - 1 to save the /SET in the optimization bucket, 0 if not * type - STR_VAR - the /SET can be set to a string * INT_VAR - The /SET can be set to an integer * CHAR_VAR - The /SET can be set to a single character * BOOL_VAR - The /SET can be set to OFF or ON * func - A C function to call back when the /SET is changed * This is used when you need to do something when the * value is changed, or you need to constrain/QA the value * script - Only NULL is supported. * ... - The initial value of the /SET (usually a #define DEFAULT_ in config.h) * * Return value: * > 0 If bucket == 1, the integer offset in the BIV bucket for this /SET * -1 If bucket == 0, the new /SET was not saved in the bucket. * * It is clear this function was originally intended to support creating user sets, * but that should be done using $symbolctl() instead. * * THIS IS AN INTERNAL FUNCTION - NOBODY SHOULD CALL IT EXCEPT THE BOOTSTRAP CODE * (init_variables_stage1()) */ static int add_biv (const char *name, int bucket, int type, void (*func) (void *), const char *script, ...) { IrcVariable *var; va_list va; int numval; const char *strval; var = (IrcVariable *)new_malloc(sizeof(IrcVariable)); var->type = type; if (script) var->script = malloc_strdup(script); else var->script = NULL; var->func = func; var->data = new_malloc(sizeof(union builtin_variable)); var->orig_data = new_malloc(sizeof(union builtin_variable)); var->pending = 0; va_start(va, script); switch (var->type) { case BOOL_VAR: case CHAR_VAR: case INT_VAR: numval = va_arg(va, int); var->data->integer = numval; var->orig_data->integer = numval; break; case STR_VAR: strval = va_arg(va, char *); if (strval) { var->data->string = malloc_strdup(strval); var->orig_data->string = malloc_strdup(strval); } else { var->data->string = NULL; var->orig_data->string = NULL; } break; } va_end(va); add_builtin_variable_alias(name, var); if (bucket) { add_to_bucket(var_bucket, name, var); return (var_bucket->numitems - 1); } else return -1; } /* * clone_biv -- Create a deep copy of a /SET's object * * Arguments: * old - Ptr to an existing/live (IrcVariable) * * Return value: * A ptr to a new (IrcVariable) that is a deep copy of 'old' * You can pass the return value to unclone_bov() later. * * This is used by /STACK PUSH SET to create the copy of * that gets pushed onto the stack for restoration later. * * This can be called on any /SET */ IrcVariable * clone_biv (IrcVariable *old) { IrcVariable *var; if (!old) return NULL; /* Take THAT! */ var = (IrcVariable *)new_malloc(sizeof(IrcVariable)); var->type = old->type; if (old->script) var->script = malloc_strdup(old->script); else var->script = NULL; var->func = old->func; var->data = new_malloc(sizeof(union builtin_variable)); var->orig_data = new_malloc(sizeof(union builtin_variable)); var->pending = 0; switch (old->type) { case BOOL_VAR: case CHAR_VAR: case INT_VAR: var->data->integer = old->data->integer; var->orig_data->integer = old->orig_data->integer; break; case STR_VAR: if (old->data->string) { var->data->string = malloc_strdup(old->data->string); var->orig_data->string = malloc_strdup(old->orig_data->string); } else { var->data->string = NULL; var->orig_data->string = NULL; } break; } return var; } /* * unclone_biv -- Restore a deep copy of an (IrcVariable) object * * Arguments: * name - The name of a /SET to be restored * clone - Ptr to (IrcVariable) previously returned by clone_biv() * * This is used by /STACK POP SET to restore the copy of * that was previously created by /STACK PUSH SET . * * When this value gets reset, if the /SET has a callback, it is invoked * * This can be called on any /SET */ void unclone_biv (const char *name, IrcVariable *clone) { IrcVariable *var; int i; for (i = 0; i < var_bucket->numitems; i++) { if (!strcmp(name, var_bucket->list[i].name)) { var = (IrcVariable *)var_bucket->list[i].stuff; var->type = clone->type; if (clone->script) { malloc_strcpy(&var->script, clone->script); new_free(&clone->script); } else new_free(&var->script); var->pending = clone->pending; /* * XXX This should be unified with set_variable() somehow. */ switch (clone->type) { case BOOL_VAR: case CHAR_VAR: case INT_VAR: var->data->integer = clone->data->integer; var->orig_data->integer = clone->orig_data->integer; break; case STR_VAR: if (clone->data->string) malloc_strcpy(&var->data->string, clone->data->string); else new_free(&var->data->string); if (clone->orig_data->string) malloc_strcpy(&var->orig_data->string, clone->orig_data->string); else new_free(&var->orig_data->string); new_free(&clone->data->string); new_free(&clone->orig_data->string); break; } /* * XXX I copied this from set_variable(), but this * should be refactored and shared with that function. */ if ((var->func || var->script) && !var->pending) { var->pending++; if (var->func) (var->func)(var->data); if (var->script) { char *s; int owd; s = make_string_var_bydata((const void *)var); owd = swap_window_display(0); call_lambda_command("SET", var->script, s); swap_window_display(owd); new_free(&s); } var->pending--; } new_free(&clone->data); new_free(&clone->orig_data); new_free(&clone); return; } } } /* * is_var_builtin -- Is a /SET a BIV, or is it user-created? * * Arguments: * varname - The name of a /SET * * Return value: * 0 - The named /SET is not a BIV, either: * - because it does not exist, or * - because it is a user-created /SET * 1 - The named /SET is a BIV and must not be deleted * * This is used by $symbolctl() to determine if a /SET can be deleted. * XXX Perhaps this info should be stored in the (IrcVariable) * * This can be called on any /SET. */ int is_var_builtin (const char *varname) { int i; for (i = 0; i < var_bucket->numitems; i++) { if (!my_stricmp(var_bucket->list[i].name, varname)) return 1; } return 0; } #define VAR(x, y, z) x ## _VAR = add_biv( #x, 1, y ## _VAR, z,NULL, DEFAULT_ ## x); /* * init_variables -- Bootstrap the Built In Variables (BIV) /SETs during client bootup * * When the client is bootstraping itself, it needs to create all the built-in-variable * /SETs so things can start using them. This is done before parsing command line * arguments, since you can change /SETs that way * * The default values for /SETs are #defined in include/config.h -- mostly. * For those things not defined in config.h, a hardcoded default value is found here! * * This can only be called once, at client boostrap time. */ void init_variables_stage1 (void) { var_bucket = new_bucket(); VAR(ACCEPT_INVALID_SSL_CERT, BOOL, NULL) VAR(ALLOW_C1_CHARS, BOOL, NULL) VAR(ALWAYS_SPLIT_BIGGEST, BOOL, NULL) VAR(AUTOMARGIN_OVERRIDE, BOOL, set_automargin_override) VAR(BANNER, STR, NULL) VAR(BANNER_EXPAND, BOOL, NULL) VAR(BEEP, BOOL, NULL) VAR(BLANK_LINE_INDICATOR, STR, update_all_windows_wrapper) VAR(BROKEN_AIXTERM, BOOL, NULL) VAR(CHANNEL_NAME_WIDTH, INT, update_all_status_wrapper) #define DEFAULT_CLIENT_INFORMATION IRCII_COMMENT VAR(CLIENT_INFORMATION, STR, NULL) VAR(CLOCK, BOOL, my_set_clock); VAR(CLOCK_24HOUR, BOOL, reset_clock); VAR(CLOCK_FORMAT, STR, set_clock_format); VAR(CLOCK_INTERVAL, INT, set_clock_interval); VAR(CMDCHARS, STR, NULL); VAR(COMMENT_HACK, BOOL, NULL); VAR(CONTINUED_LINE, STR, NULL); VAR(CPU_SAVER_AFTER, INT, set_cpu_saver_after); VAR(CPU_SAVER_EVERY, INT, set_cpu_saver_every); VAR(CURRENT_WINDOW_LEVEL, STR, set_current_window_mask); VAR(DCC_AUTO_SEND_REJECTS, BOOL, NULL); VAR(DCC_CONNECT_TIMEOUT, INT, NULL); VAR(DCC_DEQUOTE_FILENAMES, BOOL, NULL); VAR(DCC_LONG_PATHNAMES, BOOL, NULL); VAR(DCC_SLIDING_WINDOW, INT, NULL); VAR(DCC_STORE_PATH, STR, NULL); VAR(DCC_USE_GATEWAY_ADDR, BOOL, NULL) #define DEFAULT_DEBUG 0 VAR(DEBUG, INT, NULL); #define DEFAULT_DEFAULT_REALNAME NULL VAR(DEFAULT_REALNAME, STR, NULL); #define DEFAULT_DEFAULT_USERNAME NULL VAR(DEFAULT_USERNAME, STR, NULL); VAR(DISPATCH_UNKNOWN_COMMANDS, BOOL, NULL); VAR(DISPLAY, BOOL, NULL); VAR(DO_NOTIFY_IMMEDIATELY, BOOL, NULL); VAR(FIRST_LINE, STR, NULL); VAR(FLOATING_POINT_MATH, BOOL, NULL); VAR(FLOATING_POINT_PRECISION, INT, NULL); VAR(FLOOD_AFTER, INT, NULL); VAR(FLOOD_IGNORE, BOOL, NULL); VAR(FLOOD_MASKUSER, INT, NULL); VAR(FLOOD_RATE, INT, NULL); VAR(FLOOD_RATE_PER, INT, NULL); VAR(FLOOD_USERS, INT, NULL); VAR(FLOOD_WARNING, BOOL, NULL); VAR(HIDE_PRIVATE_CHANNELS, BOOL, update_all_status_wrapper); VAR(HOLD_SLIDER, INT, NULL); VAR(INDENT, BOOL, set_indent); VAR(INPUT_INDICATOR_LEFT, STR, NULL); VAR(INPUT_INDICATOR_RIGHT, STR, NULL); VAR(INPUT_PROMPT, STR, set_input_prompt); VAR(INSERT_MODE, BOOL, update_all_status_wrapper); VAR(KEY_INTERVAL, INT, set_key_interval); VAR(LASTLOG, INT, set_lastlog_size); VAR(LASTLOG_LEVEL, STR, set_lastlog_mask); VAR(LASTLOG_REWRITE, STR, NULL); #define DEFAULT_LOAD_PATH NULL VAR(LOAD_PATH, STR, NULL); VAR(LOG, BOOL, logger); VAR(LOGFILE, STR, set_logfile); #define DEFAULT_LOG_REWRITE NULL VAR(LOG_REWRITE, STR, NULL); VAR(MAIL, INT, set_mail); VAR(MAIL_INTERVAL, INT, set_mail_interval); VAR(MAIL_TYPE, STR, set_mail_type); #define DEFAULT_MANGLE_DISPLAY "NORMALIZE" VAR(MANGLE_DISPLAY, STR, set_mangle_display); #define DEFAULT_MANGLE_INBOUND NULL VAR(MANGLE_INBOUND, STR, set_mangle_inbound); #define DEFAULT_MANGLE_LOGFILES NULL VAR(MANGLE_LOGFILES, STR, set_mangle_logfiles); #define DEFAULT_MANGLE_OUTBOUND NULL VAR(MANGLE_OUTBOUND, STR, set_mangle_outbound); VAR(METRIC_TIME, BOOL, reset_clock); VAR(MIRC_BROKEN_DCC_RESUME, BOOL, NULL); VAR(MODE_STRIPPER, BOOL, NULL); VAR(NEW_SERVER_LASTLOG_LEVEL, STR, set_new_server_lastlog_mask); VAR(NOTIFY, BOOL, set_notify); VAR(NOTIFY_INTERVAL, INT, set_notify_interval); VAR(NOTIFY_LEVEL, STR, set_notify_mask); VAR(NOTIFY_ON_TERMINATION, BOOL, NULL); VAR(NOTIFY_USERHOST_AUTOMATIC, BOOL, NULL); VAR(NO_CONTROL_LOG, BOOL, NULL); /* XXX /set mangle_logfile */ VAR(NO_CTCP_FLOOD, BOOL, NULL); VAR(NO_FAIL_DISCONNECT, BOOL, NULL); VAR(OLD_MATH_PARSER, BOOL, NULL); VAR(OLD_SERVER_LASTLOG_LEVEL, STR, set_old_server_lastlog_mask); #define DEFAULT_OUTPUT_REWRITE NULL VAR(OUTPUT_REWRITE, STR, NULL); VAR(PAD_CHAR, CHAR, NULL); VAR(QUIT_MESSAGE, STR, NULL); VAR(RANDOM_SOURCE, INT, NULL); VAR(SCREEN_OPTIONS, STR, NULL); VAR(SCROLLBACK, INT, set_scrollback_size); VAR(SCROLLBACK_RATIO, INT, NULL); VAR(SCROLL_LINES, INT, set_scroll_lines); VAR(SHELL, STR, NULL); VAR(SHELL_FLAGS, STR, NULL); VAR(SHELL_LIMIT, INT, NULL); VAR(SHOW_CHANNEL_NAMES, BOOL, NULL); VAR(SHOW_NUMERICS, BOOL, NULL); VAR(SHOW_STATUS_ALL, BOOL, update_all_status_wrapper); VAR(SSL_CIPHERS, STR, NULL); VAR(SSL_ROOT_CERTS_LOCATION, STR, set_ssl_root_certs_location); VAR(STATUS_AWAY, STR, build_status); VAR(STATUS_CHANNEL, STR, build_status); VAR(STATUS_CHANOP, STR, build_status); VAR(STATUS_CLOCK, STR, build_status); VAR(STATUS_CPU_SAVER, STR, build_status); #define DEFAULT_STATUS_DOES_EXPANDOS 0 VAR(STATUS_DOES_EXPANDOS, BOOL, NULL); VAR(STATUS_FORMAT, STR, build_status); VAR(STATUS_FORMAT1, STR, build_status); VAR(STATUS_FORMAT2, STR, build_status); VAR(STATUS_HALFOP, STR, build_status); VAR(STATUS_HOLD, STR, build_status); VAR(STATUS_HOLD_LINES, STR, build_status); VAR(STATUS_HOLDMODE, STR, build_status); VAR(STATUS_INSERT, STR, build_status); VAR(STATUS_MAIL, STR, build_status); VAR(STATUS_MODE, STR, build_status); VAR(STATUS_NICKNAME, STR, build_status); VAR(STATUS_NOSWAP, STR, build_status); VAR(STATUS_NOTIFY, STR, build_status); VAR(STATUS_NO_REPEAT, BOOL, build_status); VAR(STATUS_OPER, STR, build_status); VAR(STATUS_OVERWRITE, STR, build_status); VAR(STATUS_PREFIX_WHEN_CURRENT, STR, build_status); VAR(STATUS_PREFIX_WHEN_NOT_CURRENT, STR, build_status); VAR(STATUS_QUERY, STR, build_status); VAR(STATUS_SCROLLBACK, STR, build_status); VAR(STATUS_SEQUENCE_POINT, STR, build_status); VAR(STATUS_SERVER, STR, build_status); VAR(STATUS_SSL_OFF, STR, build_status); VAR(STATUS_SSL_ON, STR, build_status); VAR(STATUS_UMODE, STR, build_status); VAR(STATUS_USER, STR, build_status); VAR(STATUS_USER1, STR, build_status); VAR(STATUS_USER10, STR, build_status); VAR(STATUS_USER11, STR, build_status); VAR(STATUS_USER12, STR, build_status); VAR(STATUS_USER13, STR, build_status); VAR(STATUS_USER14, STR, build_status); VAR(STATUS_USER15, STR, build_status); VAR(STATUS_USER16, STR, build_status); VAR(STATUS_USER17, STR, build_status); VAR(STATUS_USER18, STR, build_status); VAR(STATUS_USER19, STR, build_status); VAR(STATUS_USER2, STR, build_status); VAR(STATUS_USER20, STR, build_status); VAR(STATUS_USER21, STR, build_status); VAR(STATUS_USER22, STR, build_status); VAR(STATUS_USER23, STR, build_status); VAR(STATUS_USER24, STR, build_status); VAR(STATUS_USER25, STR, build_status); VAR(STATUS_USER26, STR, build_status); VAR(STATUS_USER27, STR, build_status); VAR(STATUS_USER28, STR, build_status); VAR(STATUS_USER29, STR, build_status); VAR(STATUS_USER3, STR, build_status); VAR(STATUS_USER30, STR, build_status); VAR(STATUS_USER31, STR, build_status); VAR(STATUS_USER32, STR, build_status); VAR(STATUS_USER33, STR, build_status); VAR(STATUS_USER34, STR, build_status); VAR(STATUS_USER35, STR, build_status); VAR(STATUS_USER36, STR, build_status); VAR(STATUS_USER37, STR, build_status); VAR(STATUS_USER38, STR, build_status); VAR(STATUS_USER39, STR, build_status); VAR(STATUS_USER4, STR, build_status); VAR(STATUS_USER5, STR, build_status); VAR(STATUS_USER6, STR, build_status); VAR(STATUS_USER7, STR, build_status); VAR(STATUS_USER8, STR, build_status); VAR(STATUS_USER9, STR, build_status); VAR(STATUS_VOICE, STR, build_status); VAR(STATUS_WINDOW, STR, build_status); VAR(SUPPRESS_FROM_REMOTE_SERVER, BOOL, NULL); VAR(SWITCH_CHANNELS_BETWEEN_WINDOWS, BOOL, NULL); VAR(TERM_DOES_BRIGHT_BLINK, BOOL, NULL); VAR(TMUX_OPTIONS, STR, NULL); VAR(USER_INFORMATION, STR, NULL); VAR(WORD_BREAK, STR, NULL); #define DEFAULT_WSERV_PATH WSERV_PATH VAR(WSERV_PATH, STR, NULL); VAR(WSERV_TYPE, STR, set_wserv_type); VAR(XTERM, STR, NULL); VAR(XTERM_OPTIONS, STR, NULL); } /* * init_variables_stage2 -- Complete the initialization of all BIV /SETs * * During the early part of the client bootstrap process, we created * the BIV /SETs. But that's all we did, we just created them. * Many /SETs bootstrap themselves through a callback function, which * we haven't called yet. This function goes through and calls the * callback function for every /SET with its initial value. * * This can only be called once, at client boostrap time. */ void init_variables_stage2 (void) { int i; /* * Forcibly init all the variables */ for (i = 0; i < var_bucket->numitems; i++) { IrcVariable *var = (IrcVariable *)var_bucket->list[i].stuff; if (var->func) { /* * We don't call build_status() or update_all_status() here, * because there is still more bootstrap that needs to be * done before the status bar is ready to go. So the bootstrap * code calls build_status() for the first time after we return. */ if (var->func == build_status) continue; if (var->func == update_all_status_wrapper) continue; if (var->func == update_all_windows_wrapper) continue; var->pending++; var->func(var->data); var->pending--; } } } /* * do_boolean -- determine whether a string contains a boolean value and set it * * Arguments: * str - A string that should contain "ON", "OFF", or "TOGGLE" * value - A pointer to an existing boolean value. It should be 0 or 1. * * Return value: * 0 - The operation completed successfully * If str is "ON", then *value is set to 1. * If str is "OFF", then *value is set to 0. * If str is "TOGGLE", then *value is set to the opposte of whatever it is * 1 - The operation failed (str did not contain a value boolean value) * *value is unchanged. * * XXX This is not /SET specific; it belongs somewhere else. */ int do_boolean (char *str, int *value) { upper(str); if (strcmp(str, "ON") == 0) *value = 1; else if (strcmp(str, "OFF") == 0) *value = 0; else if (strcmp(str, "TOGGLE") == 0) { if (*value) *value = 0; else *value = 1; } else return (1); return (0); } /* * show_var_value -- Convert a /SET's value to something human-readable * * Arguments: * name - The name of a /SET * var - Ptr to the /SET's (IrcVariable) object * newval - Whether this /SET is being changed or just read out * * This can be used for any /SET */ static void show_var_value (const char *name, IrcVariable *var, int newval) { char *value; value = make_string_var_bydata((const void *)var); if (!value) value = malloc_strdup(""); say("%s value of %s is %s", newval ? "New" : "Current", name, value); new_free(&value); } /* * set_var_value - A wrapper for changing a BIV /SET * * Arguments: * svv_index - The index of the BIV * value - The value the /SET should be changed to * noisy - 1 if the user should be told, 0 if not. * * You can only call this for a BIV /SET. */ void set_var_value (int svv_index, const char *value, int noisy) { IrcVariable *var; var = (IrcVariable *)var_bucket->list[svv_index].stuff; set_variable(var_bucket->list[svv_index].name, var, value, noisy); } /* * set_variable -- Change a /SET's value * * Arguments: * name - The name of the /SET being changed * var - The (IrcVariable) for that /SET * new_value - The value the /SET should be changed to * noisy - 1 if the user should be told, 0 if not. * * Return value: * 0 - The new value was set successfully. * -1 - Something went wrong; the /SET was not changed. * * You can call this for any /SET. * However, you have already looked up the 'var' yourself. * I recommend bucket_builtin_variables(). */ int set_variable (const char *name, IrcVariable *var, const char *new_value, int noisy) { char *rest; int changed = 0; char *value; int retval = 0; if (new_value) value = LOCAL_COPY(new_value); else value = NULL; switch (var->type) { case BOOL_VAR: { if (value && *value && (value = next_arg(value, &rest))) { if (do_boolean(value, &(var->data->integer))) { say("Value must be either ON, OFF, or TOGGLE"); retval = -1; } else changed = 1; } break; } case CHAR_VAR: { int codepoint; ptrdiff_t offset; if (!value || !*value) { var->data->integer = ' '; changed = 1; break; } if ((codepoint = next_code_point2(value, &offset, 1)) == -1) { say("New value of %s could not be determined", name); retval = -1; break; } if (codepoint_numcolumns(codepoint) != 1) { say("New value of %s must be exactly 1 column wide", name); retval = -1; break; } var->data->integer = codepoint; changed = 1; break; } case INT_VAR: { if (value && *value && (value = next_arg(value, &rest))) { int val; if (!is_number(value)) { say("Value of %s must be numeric!", name); retval = -1; } else if ((val = my_atol(value)) < 0) { say("Value of %s must be a non-negative number", name); retval = -1; } else { var->data->integer = val; changed = 1; } } break; } case STR_VAR: { if (!value) { new_free(&(var->data->string)); changed = 1; } else if (*value) { malloc_strcpy(&(var->data->string), value); changed = 1; } } } if (changed) { if ((var->func || var->script) && !var->pending) { var->pending++; if (var->func) (var->func)(var->data); if (var->script) { char *s; int owd; s = make_string_var_bydata((const void *)var); owd = swap_window_display(0); call_lambda_command("SET", var->script, s); swap_window_display(owd); new_free(&s); } var->pending--; } } if (noisy) show_var_value(name, var, changed); return retval; } /* * setcmd: The SET command sets one of the irc variables. The args * should consist of "variable-name setting", where variable name can be * partial, but non-ambbiguous, and setting depends on the variable being set */ BUILT_IN_COMMAND(setcmd) { char *var = NULL; IrcVariable *thevar; const char *name; int i; Bucket *b = NULL; /* * XXX Ugh. This is a hideous offense of good taste which is * necessary to support set's abominable syntax, particularly * acute with /set continued_line */ while (args && *args && isspace(*args)) args++; var = args; while (args && *args && !isspace(*args)) args++; if (args && *args) *args++ = 0; /* * Show all of the /SET values if no argument was given. */ if (!var || !*var) { b = new_bucket(); bucket_builtin_variables(b, empty_string); for (i = 0; i < b->numitems; i++) show_var_value(b->list[i].name, (IrcVariable *)b->list[i].stuff, 0); free_bucket(&b); return; } /* * We're committed to changing a /SET value here. */ /* * If the name is prefixed with a hyphen, it unsets the value. */ if (*var == '-') { var++; args = NULL; } /* * How many /SET VARiables are matched by what the user typed? */ upper(var); b = new_bucket(); bucket_builtin_variables(b, var); /* * Decide if this is an "exact" match or an "uknonwn" match. */ /* Either exactly correct, or only one possible match */ if (b->numitems == 1 || (b->numitems > 1 && !my_stricmp(var, b->list[0].name))) { thevar = (IrcVariable *)b->list[0].stuff; name = b->list[0].name; } /* Either zero or two-or-more matches */ else { thevar = NULL; name = NULL; } /* * STEP 1 -- We would Allow the user to overrule the /SET via /ON SET. * You can /SET a new value in the /ON SET! */ if (!thevar || !thevar->pending) { if (thevar) thevar->pending++; /* * Offer exactly what the user asked for */ if (!do_hook(SET_LIST, "%s %s", var, args ? args : "")) { free_bucket(&b); if (thevar) thevar->pending--; return; /* Grabbed -- stop. */ } /* * Offer the fully complete name, but only * if that's not what the user asked for! */ if (name && my_stricmp(name, var)) { if (!do_hook(SET_LIST, "%s %s", name, args ? args : "")) { free_bucket(&b); if (thevar) thevar->pending--; return; /* Grabbed -- stop. */ } } if (thevar) thevar->pending--; } /* * STEP 2 -- The user /SET a valid variable, but the user didn't grab it * via an /ON SET above. */ if (thevar) { /* Either set it, or display it, depending on the args */ if (args && !*args) show_var_value(name, thevar, 0); else set_variable(name, thevar, args, 1); free_bucket(&b); return; } /* * If there was no match at all (as opposed to two-or-more), then * also throw /on unknown_set. */ if (b->numitems == 0) { if (do_hook(UNKNOWN_SET_LIST, "%s %s", var, args ? args : "")) if (do_hook(SET_LIST, "set-error No such variable \"%s\"", var)) say("No such variable \"%s\"", var); free_bucket(&b); return; } /* * If there was two-or-more matches (ambiguous), then also throw * /on set to tell the user it was ambiguous. */ else if (b->numitems > 1) { if (do_hook(SET_LIST, "set-error %s is ambiguous", var)) { say("%s is ambiguous", var); for (i = 0; i < b->numitems; i++) show_var_value(b->list[i].name, (IrcVariable *)b->list[i].stuff, 0); } } free_bucket(&b); } /* * get_string_var -- Get the value of a BIV using its index * * Arguments: * var - An integer representing the offset of a BIV * It must be a string typed /SET! * * Return value: * The value of that /SET as an string * * IMPORTANT: This O(1) function only works for /SETs that are hardcoded. * You need to call make_string_var2() to look up user-created /SETs * * IMPORTANT: This does not do any type checking. If you call get_string_var() * against a /SET that is not an integer /SET, you will get garbage! * * IMPORTANT: The caller DOES NOT OWN the return value. YOU MUST NOT * new_free() IT. YOU MUST NOT CHANGE IT. */ const char * get_string_var (int var) { return ((IrcVariable *)var_bucket->list[var].stuff)->data->string; } /* * get_int_var -- Get the value of a BIV using its index * * Arguments: * var - An integer representing the offset of a BIV * It must be an integer typed /SET! * * Return value: * The value of that /SET as an integer * * IMPORTANT: This O(1) function only works for /SETs that are hardcoded. * You need to call make_string_var2() to look up user-created /SETs * * IMPORTANT: This does not do any type checking. If you call get_int_var() * against a /SET that is not an integer /SET, you will get garbage! */ int get_int_var (int var) { return ((IrcVariable *)var_bucket->list[var].stuff)->data->integer; } /* * make_string_var -- Get the current value of any /SET by name * * Arguments: * var_name - The name of a /SET -- Must be in canonical format * * Return Value: * NULL - The requested /SET does not exist, * OR, the /SET does not have a value * _ - A new string containing a human-readable version of the /SET * THE RETURN VALUE IS MALLOC()ed -- THE CALLER MUST NEW_FREE() it. * * * You can call this against any /SET * * Because 'var_name' is looked up literally as a symbol, it must be in * canonical format (dots, not []s) and uppercase. This function will * uppercase what you pass it as a courtesy. */ char * make_string_var (const char *var_name) { char * (*dummy) (void); IrcVariable *thevar = NULL; char *copy; copy = LOCAL_COPY(var_name); upper(copy); get_var_alias(copy, &dummy, &thevar); if (thevar == NULL) return NULL; return make_string_var_bydata((const void *)thevar); } /* * make_string_var2 -- Get the current value of any /SET by name with error code * * Arguments: * var_name - The name of a /SET -- Must be in canonical format * retval - A ptr to a (char *) where we will return the string * * Return Value: * -1 - The requested /SET does not exist * 0 - A new string containing a human-readable version of the /SET * has been placed in *retval. YOU MUST NEW_FREE() THE STRING * PLACED IN *retval. * * You can call this against any /SET * * Because 'var_name' is looked up literally as a symbol, it must be in * canonical format (dots, not []s) and uppercase. This function will * uppercase what you pass it as a courtesy. */ int make_string_var2 (const char *var_name, char **retval) { char * (*dummy) (void); IrcVariable *thevar = NULL; char *copy; copy = LOCAL_COPY(var_name); upper(copy); get_var_alias(copy, &dummy, &thevar); if (thevar == NULL) return -1; *retval = make_string_var_bydata((const void *)thevar); return 0; } /* * make_string_var_bydata -- Make a new string representing the value of an (IrcVariable) * * Arguments: * irc_variable - A ptr to an (IrcVariable) object * * Return value: * A new string containing a human-readable version of the value in irc_variable. * THE RETURN VALUE IS MALLOC()ed -- THE CALLER MUST NEW_FREE() it. * * You can call this against any /SET */ char * make_string_var_bydata (const void *irc_variable) { int type; const VARIABLE *data; char *ret = (char *) 0; /* XXX Not sure what better to do here */ if (!irc_variable) return malloc_strdup(empty_string); type = ((const IrcVariable *)irc_variable)->type; data = (const VARIABLE *)(((const IrcVariable *)irc_variable)->data); ret = make_string_var_bydata_internal(type, data); return ret; } char * make_string_var_by_orig_data (const void *irc_variable) { int type; const VARIABLE *data; char *ret = (char *) 0; /* XXX Not sure what better to do here */ if (!irc_variable) return malloc_strdup(empty_string); type = ((const IrcVariable *)irc_variable)->type; data = (const VARIABLE *)(((const IrcVariable *)irc_variable)->orig_data); ret = make_string_var_bydata_internal(type, data); return ret; } char * make_string_var_bydata_internal (int type, const VARIABLE *data) { char *ret = (char *) 0; /* XXX Not sure what better to do here */ if (!data) return malloc_strdup(empty_string); switch (type) { case STR_VAR: if (data->string) ret = malloc_strdup(data->string); break; case INT_VAR: ret = malloc_strdup(ltoa(data->integer)); break; case BOOL_VAR: if (data->integer) ret = malloc_strdup("ON"); else ret = malloc_strdup("OFF"); break; case CHAR_VAR: { char utf8str[16]; ucs_to_utf8(data->integer, utf8str, sizeof(utf8str)); ret = malloc_strdup((const char *)utf8str); break; } default: panic(1, "make_string_var_bydata_internal: unrecognized type [%d]", type); } return (ret); } /***************************************************************************/ /* * set_mangle_inbound -- update 'inbound_line_mangler' from /SET MANGLE_INBOUND */ static void set_mangle_inbound (void *stuff) { VARIABLE *v; const char *value; char *nv = NULL; v = (VARIABLE *)stuff; value = v->string; inbound_line_mangler = parse_mangle(value, inbound_line_mangler, &nv); malloc_strcpy(&v->string, nv); new_free(&nv); } /* * set_mangle_outbound -- update 'outbound_line_mangler' from /SET MANGLE_OUTBOUND */ static void set_mangle_outbound (void *stuff) { VARIABLE *v; const char *value; char *nv = NULL; v = (VARIABLE *)stuff; value = v->string; outbound_line_mangler = parse_mangle(value, outbound_line_mangler, &nv); malloc_strcpy(&v->string, nv); new_free(&nv); } /* * set_mangle_logfiles -- update 'logfile_line_mangler' from /SET MANGLE_LOGFILES */ static void set_mangle_logfiles (void *stuff) { VARIABLE *v; const char *value; char *nv = NULL; v = (VARIABLE *)stuff; value = v->string; logfile_line_mangler = parse_mangle(value, logfile_line_mangler, &nv); malloc_strcpy(&v->string, nv); new_free(&nv); } /* * set_mangle_display - update 'display_line_mangler' from /SET MANGLE_DISPLAY */ static void set_mangle_display (void *stuff) { VARIABLE *v; const char *value; char *nv = NULL; v = (VARIABLE *)stuff; value = v->string; display_line_mangler = parse_mangle(value, display_line_mangler, &nv); malloc_strcpy(&v->string, nv); new_free(&nv); } /* * update_all_status_wrapper - call update_all_status() from a /SET callback * * Several /SETs change what appears on the status bar, so update_all_status() * recalculates the status bar and updates it */ static void update_all_status_wrapper (void *stuff) { update_all_status(); } /* * update_all_windows_wrapper - call update_all_windows() from a /SET callback */ static void update_all_windows_wrapper (void *stuff) { need_redraw = 1; } /* * set_wserv_type -- callback for /SET WSERV_TYPE * * This constrains the value of /SET WSERV_TYPE to the supported values */ static void set_wserv_type (void *stuff) { VARIABLE *v; const char *s; v = (VARIABLE *)stuff; s = v->string; if (!s) return; /* It's ok */ if (!my_stricmp(s, "SCREEN")) return; /* It's ok */ if (!my_stricmp(s, "XTERM")) return; /* It's ok */ if (!my_stricmp(s, "TMUX")) return; /* It's ok */ say("SET WSERV_TYPE must be either SCREEN, XTERM or TMUX"); new_free(&v->string); } /* * set_indent -- callback for /SET INDENT * * /SET INDENT is a wrapper around /WINDOW INDENT on every window. * * XXX Normally, these kinds of /SETs use the /WINDOW value if the user * set one, and the /SET as a fallback. WHy is this different? */ void set_indent (void *stuff) { VARIABLE *v; int indent; int window = 0; v = (VARIABLE *)stuff; indent = v->integer; while (traverse_all_windows2(&window)) set_window_indent(window, indent); } /***************************************************************************/ #if 0 void help_topics_set (FILE *f) { int i; for (i = 0; i < var_bucket->numitems; i++) fprintf(f, "set %s\n", var_bucket->list[i].name); } #endif