/************************************************************************* * TinyFugue - programmable mud client * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys * * TinyFugue (aka "tf") is protected under the terms of the GNU * General Public License. See the file "COPYING" for details. ************************************************************************/ static const char RCSid[] = "$Id: util.c,v 35004.150 2007/01/13 23:12:39 kkeys Exp $"; /* * TF utilities. * * String utilities * Command line option parser * Time parser/formatter * Mail checker */ #include "tfconfig.h" #if HAVE_LOCALE_H # include #endif #include #include #include #include "port.h" #include "tf.h" #include "util.h" #include "pattern.h" /* for tfio.h */ #include "search.h" /* for tfio.h */ #include "tfio.h" #include "output.h" /* fix_screen() */ #include "tty.h" /* reset_tty() */ #include "signals.h" /* core() */ #include "variable.h" #include "parse.h" /* for expression in nextopt() numeric option */ typedef struct mail_info_s { /* mail file information */ char *name; /* file name */ int flag; /* new mail? */ int error; /* error */ time_t mtime; /* file modification time */ long size; /* file size */ struct mail_info_s *next; } mail_info_t; mail_info_t *maillist = NULL; const struct timeval tvzero = { 0, 0 }; /* zero (useful in tvcmp()) */ struct timeval mail_update = { 0, 0 }; /* next mail update (0==immediately) */ int mail_count = 0; char tf_ctype[0x100]; const int feature_locale = HAVE_SETLOCALE - 0; const int feature_subsecond = HAVE_GETTIMEOFDAY - 0; const int feature_ftime = HAVE_STRFTIME - 0; const int feature_TZ = HAVE_TZSET - 0; char current_opt = '\0'; AUTO_BUFFER(featurestr); struct feature features[] = { { "256colors", &feature_256colors, }, { "core", &feature_core, }, { "float", &feature_float }, { "ftime", &feature_ftime }, { "history", &feature_history }, { "IPv6", &feature_IPv6 }, { "locale", &feature_locale }, { "MCCPv1", &feature_MCCPv1 }, { "MCCPv2", &feature_MCCPv2 }, { "process", &feature_process }, { "SOCKS", &feature_SOCKS }, { "ssl", &feature_ssl }, { "subsecond", &feature_subsecond }, { "TZ", &feature_TZ, }, { NULL, NULL } }; static void free_maillist(void); #if !STDC_HEADERS int lcase(x) char x; { return is_upper(x) ? tolower(x) : x; } int ucase(x) char x; { return is_lower(x) ? toupper(x) : x; } #endif void init_util1(void) { int i; const struct feature *f; for (i = 0; i < 0x100; i++) { tf_ctype[i] = 0; } tf_ctype['+'] |= IS_UNARY | IS_ADDITIVE | IS_ASSIGNPFX; tf_ctype['-'] |= IS_UNARY | IS_ADDITIVE | IS_ASSIGNPFX; tf_ctype['!'] |= IS_UNARY; tf_ctype['*'] |= IS_MULT | IS_ASSIGNPFX; tf_ctype['/'] |= IS_MULT | IS_ASSIGNPFX; tf_ctype[':'] |= IS_ASSIGNPFX; /* tf_ctype['.'] |= IS_ADDITIVE; */ /* doesn't work right */ tf_ctype['"'] |= IS_QUOTE; tf_ctype['`'] |= IS_QUOTE; tf_ctype['\''] |= IS_QUOTE; tf_ctype['/'] |= IS_STATMETA; tf_ctype['%'] |= IS_STATMETA; tf_ctype['$'] |= IS_STATMETA; tf_ctype[')'] |= IS_STATMETA; tf_ctype['\n'] |= IS_STATMETA; tf_ctype['\\'] |= IS_STATMETA | IS_STATEND; tf_ctype[';'] |= IS_STATEND; tf_ctype['|'] |= IS_STATEND; tf_ctype['b'] |= IS_KEYSTART; /* break */ tf_ctype['d'] |= IS_KEYSTART; /* do, done */ tf_ctype['e'] |= IS_KEYSTART; /* else, elseif, endif, exit */ tf_ctype['i'] |= IS_KEYSTART; /* if */ tf_ctype['l'] |= IS_KEYSTART; /* let */ tf_ctype['r'] |= IS_KEYSTART; /* return, result */ tf_ctype['s'] |= IS_KEYSTART; /* set */ tf_ctype['t'] |= IS_KEYSTART; /* then, test */ tf_ctype['w'] |= IS_KEYSTART; /* while */ tf_ctype['B'] |= IS_KEYSTART; /* BREAK */ tf_ctype['D'] |= IS_KEYSTART; /* DO, DONE */ tf_ctype['E'] |= IS_KEYSTART; /* ELSE, ELSEIF, ENDIF, EXIT */ tf_ctype['I'] |= IS_KEYSTART; /* IF */ tf_ctype['L'] |= IS_KEYSTART; /* LET */ tf_ctype['R'] |= IS_KEYSTART; /* RETURN, RESULT */ tf_ctype['S'] |= IS_KEYSTART; /* SET */ tf_ctype['T'] |= IS_KEYSTART; /* THEN, TEST */ tf_ctype['W'] |= IS_KEYSTART; /* WHILE */ Stringtrunc(featurestr, 0); for (f = features; f->name; f++) { Stringadd(featurestr, *f->flag ? '+' : '-'); Stringcat(featurestr, f->name); if (f[1].name) Stringadd(featurestr, ' '); } } /* Convert ascii string to printable string with "^X" forms. */ /* Returns pointer to static area; copy if needed. */ const conString *ascii_to_print(const char *str) { STATIC_BUFFER(buffer); char c; for (Stringtrunc(buffer, 0); *str; str++) { c = unmapchar(*str); if (c == '^' || c == '\\') { Stringadd(Stringadd(buffer, '\\'), c); } else if (is_print(c)) { Stringadd(buffer, c); } else if (is_cntrl(c)) { Stringadd(Stringadd(buffer, '^'), CTRL(c)); } else { Sprintf(buffer, "\\0x%2x", c); } } return CS(buffer); } /* Convert a printable string containing "^X" and "\nnn" to real ascii. */ /* "^@" and "\0" are mapped to '\200'. */ /* If dest is NULL, returns pointer to static area; copy if needed. */ const conString *print_to_ascii(String *dest, const char *src) { STATIC_BUFFER(buf); if (!dest) { dest = buf; Stringtrunc(dest, 0); } while (*src) { if (*src == '^') { Stringadd(dest, *++src ? mapchar(CTRL(*src)) : '^'); if (*src) src++; } else if (*src == '\\' && is_digit(*++src)) { char c; c = strtochr(src, (char**)&src); Stringadd(dest, mapchar(c)); } else Stringadd(dest, *src++); } return CS(dest); } /* String handlers * These are heavily used functions, so speed is favored over simplicity. */ int enum2int(const char *str, long val, conString *vec, const char *msg) { int i; STATIC_BUFFER(buf); const char *end, *prefix = "", *comma = ", ", *elide = " ... "; for (i = 0; vec[i].data; ++i) { if (str && cstrcmp(str, vec[i].data) == 0) return i; } if (str) { if (is_digit(*str)) { val = strtoint(str, &end); if (*end) val = -1; } else { val = -1; } } if (val >= 0 && val < i) return val; Stringcpy(buf, "Valid values are: "); for (i = 0; vec[i].data; ++i) { if (vec[i].attrs & F_GAG) { prefix = elide; } else { Sappendf(buf, "%s%S (%d)", prefix, &vec[i], i); prefix = comma; } } if (str) eprintf("Invalid %s value \"%s\". %S", msg, str, buf); else eprintf("Invalid %s value %d. %S", msg, val, buf); return -1; } #if 0 /* not used */ /* case-insensitive strchr() */ char *cstrchr(register const char *s, register int c) { for (c = lcase(c); *s; s++) if (lcase(*s) == c) return (char *)s; return (c) ? NULL : (char *)s; } #endif /* c may be escaped by preceeding it with e */ char *estrchr(register const char *s, register int c, register int e) { while (*s) { if (*s == c) return (char *)s; if (*s == e) { if (*++s) s++; } else s++; } return NULL; } #ifdef sun # if !HAVE_INDEX /* Workaround for some buggy Solaris 2.x systems, where libtermcap calls index() * and rindex(), but they're not defined in libc. */ #undef index char *index(const char *s, char c) { return strchr(s, c); } #undef rindex char *rindex(const char *s, char c) { return strrchr(s, c); } # endif /* HAVE_INDEX */ #endif /* sun */ #ifndef cstrcmp /* case-insensitive strcmp() */ int cstrcmp(register const char *s, register const char *t) { register int diff = 0; while ((*s || *t) && !(diff = lcase(*s) - lcase(*t))) s++, t++; return diff; } #endif /* case-insensitive strncmp() */ int cstrncmp(register const char *s, register const char *t, size_t n) { register int diff = 0; while (n && *s && !(diff = lcase(*s) - lcase(*t))) s++, t++, n--; return n ? diff : 0; } /* like strcmp(), but allows NULL arguments. A NULL arg is equal to * another NULL arg, but not to any non-NULL arg. */ int nullstrcmp(const char *s, const char *t) { return (!s && !t) ? 0 : (!s && t) ? -257 : (s && !t) ? 257 : strcmp(s, t); } /* case-insensitive nullstrcmp() */ int nullcstrcmp(const char *s, const char *t) { return (!s && !t) ? 0 : (!s && t) ? -257 : (s && !t) ? 257 : cstrcmp(s, t); } /* numarg * Converts argument to a nonnegative integer. Returns -1 for failure. * The *str pointer will be advanced to beginning of next word. */ int numarg(const char **str) { int result; if (is_digit(**str)) { result = strtoint(*str, str); } else { eprintf("invalid or missing numeric argument"); result = -1; while (**str && !is_space(**str)) ++*str; } while (is_space(**str)) ++*str; return result; } /* stringarg * Returns pointer to first space-delimited word in *str. *str is advanced * to next word. If end != NULL, *end will get pointer to end of first word; * otherwise, word will be nul terminated. */ char *stringarg(char **str, const char **end) { char *start; while (is_space(**str)) ++*str; for (start = *str; (**str && !is_space(**str)); ++*str) ; if (end) *end = *str; else if (**str) *((*str)++) = '\0'; if (**str) while (is_space(**str)) ++*str; return start; } int stringliteral(String *dest, const char **str) { char quote; if (dest->len > 0) Stringtrunc(dest, 0); quote = **str; for (++*str; **str && **str != quote; ++*str) { if (**str == '\\') { if ((*str)[1] == quote || (*str)[1] == '\\') { ++*str; #if 0 } else if ((*str)[1] == '\n') { /* XXX handle backslash-newline */ #endif } else if ((*str)[1] && pedantic) { wprintf("the only legal escapes within this quoted " "string are \\\\ and \\%c. \\\\%c is the correct way to " "write a literal \\%c inside a quoted string.", quote, (*str)[1], (*str)[1]); } } #if 0 /* 4.0 alpha (or earlier) - why? */ Stringadd(dest, is_space(**str) ? ' ' : **str); #else /* 5.0 - wanted to allow ^M, ^J, etc in fake_recv() */ Stringadd(dest, **str); #endif } if (!**str) { Sprintf(dest, "unmatched %c", quote); return 0; } ++*str; if (!dest->data) Stringtrunc(dest, 0); /* make sure data is allocated */ return 1; } /* remove leading and trailing spaces. Modifies s and *s */ char *stripstr(char *s) { char *end; while (is_space(*s)) s++; if (*s) { for (end = s + strlen(s) - 1; is_space(*end); end--); *++end = '\0'; } return s; } /* General command option parser startopt should be called before nextopt. args is the argument list to be parsed, opts is a string containing a series of codes describing valid options. A letter or digit indicates a valid option character. A '-' indicates that no option is expected; the argument should directly follow the dash. A punctuation character following an option code indicates that it takes an argument: ':' string '#' integer '@' time of the form "-h:m[:s[.f]]" or "-s[.f]". The '-' option code must always take an argument. String arguments may be omitted. nextopt returns the next option character. The new offset is returned in *offp. If option takes a string argument, a pointer to it is returned in *arg; an integer argument is returned in *num; a time argument is returned in *tvp. If end of options is reached, nextopt returns '\0'. " - " or " -- " marks the end of options, and is consumed. "\0", "=", or a word not beggining with "-" marks the end of options, and is not consumed. If an invalid option is encountered, an error message is printed and '?' is returned. Option Syntax Rules: All options must be preceded by '-'. Options may be grouped after a single '-'. There must be no space between an option and its argument. String option-arguments may be quoted. Quotes in the arg must be escaped. All options must precede operands. A '--' or '-' with no option may be used to mark the end of the options. */ static const conString *optstr; static const char *options; static int inword; void startopt(const conString *args, const char *opts) { optstr = args; options = opts; inword = 0; } char nextopt(const char **arg, ValueUnion *uval, int *type, int *offp) { char *q, opt; const char *end; STATIC_BUFFER(buffer); current_opt = '\0'; if (!inword) { while (is_space(optstr->data[*offp])) (*offp)++; if (optstr->data[*offp] != '-') { return '\0'; } else { if (optstr->data[++(*offp)] == '-') { (*offp)++; if (optstr->data[*offp] && !is_space(optstr->data[*offp])) { eprintf("invalid option syntax: --%c", optstr->data[*offp]); goto nextopt_error; } } if (!optstr->data[*offp] || is_space(optstr->data[*offp])) { while (is_space(optstr->data[*offp])) ++(*offp); return '\0'; } } } else if (optstr->data[*offp] == '=') { /* '=' ends, but isn't consumed, for stuff like: /def -t"foo"=bar */ return '\0'; } current_opt = opt = optstr->data[*offp]; if ((is_digit(opt) || opt == ':') && (q = strchr(options, '-'))) { /* valid time/number option */ opt = '-'; } else if ((opt != '@' && opt != ':' && opt != '#') && (q = strchr(options, opt))) { /* valid letter/punctuation option */ ++*offp; } else { /* invalid option */ int dash=1; const char *p; STATIC_BUFFER(helpbuf); Stringtrunc(helpbuf, 0); if (opt != '?') eprintf("invalid option"); /* eprintf() shows current_opt */ for (p = options; *p; p++) { if (dash || is_punct(p[1])) Stringcat(helpbuf, " -"); if (*p != '-') Stringadd(helpbuf, *p); dash=0; if (p[1] == ':') { Stringcat(helpbuf,""); p++; dash=1; } if (p[1] == '#') { Stringcat(helpbuf,""); p++; dash=1; } if (p[1] == '@') { Stringcat(helpbuf,"