/*************************************************************************
* 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 <locale.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#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,"<string>"); p++; dash=1; }
if (p[1] == '#') { Stringcat(helpbuf,"<integer>"); p++; dash=1; }
if (p[1] == '@') { Stringcat(helpbuf,"<time>"); p++; dash=1; }
}
current_opt = '\0'; /* so it doesn't appear in "options:" message */
eprintf("options:%S", helpbuf);
goto nextopt_error;
}
q++;
/* option takes a string or numeric_expression argument */
if (*q == ':' || *q == '#') {
Stringtrunc(buffer, 0);
if (is_quote(optstr->data[*offp])) {
end = optstr->data + *offp;
if (!stringliteral(buffer, &end)) {
eprintf("%S", buffer);
goto nextopt_error;
}
*offp = end - optstr->data;
} else {
while (optstr->data[*offp] && !is_space(optstr->data[*offp]))
Stringadd(buffer, optstr->data[(*offp)++]);
}
if (arg)
*arg = buffer->data;
if (type) *type = TYPE_STR;
/* option takes a numeric argument expression */
if (*q == '#') {
Value *val = expr_value(buffer->data);
if (!val) {
goto nextopt_error;
}
uval->ival = valint(val);
freeval(val);
if (type) *type = TYPE_INT;
}
/* option takes a dtime argument */
} else if (*q == '@') {
Value val[1];
if (!parsenumber(optstr->data + *offp, &end, TYPE_DTIME, val)) {
eprintf("%s", strerror(errno));
goto nextopt_error;
}
uval->tval = val->u.tval;
*offp = end - optstr->data;
if (type) *type = TYPE_DTIME;
/* option takes no argument */
} else {
if (arg) *arg = NULL;
if (type) *type = 0;
}
inword = (optstr->data[*offp] && !is_space(optstr->data[*offp]));
return opt;
nextopt_error:
current_opt = '\0';
return '?';
}
#if HAVE_TZSET
int ch_timezone(Var *var)
{
tzset();
return 1;
}
#endif
int ch_locale(Var *var)
{
#if HAVE_SETLOCALE
const char *lang;
#define tf_setlocale(cat, name, value) \
do { \
lang = setlocale(cat, value); \
if (lang) { \
eprintf("%s category set to \"%s\" locale.", name, lang); \
} else { \
eprintf("Invalid locale for %s.", name); \
} \
} while (0)
tf_setlocale(LC_CTYPE, "LC_CTYPE", "");
tf_setlocale(LC_TIME, "LC_TIME", "");
/* XXX memory leak: old re_tables can't be freed if any compiled regexps
* use it. But we don't expect the user to change locales very often
* (typically, locale is set at startup, and then never changed).
*/
reset_pattern_locale();
return 1;
#else
eprintf("Locale support is unavailable.");
return 1;
#endif /* HAVE_SETLOCALE */
}
int ch_maildelay(Var *var)
{
mail_update = tvzero;
return 1;
}
static mail_info_t *add_mail_file(mail_info_t *newlist, char *name)
{
mail_info_t *info, **oldp;
for (oldp = &maillist; *oldp; oldp = &(*oldp)->next) {
if (strcmp(name, (*oldp)->name) == 0) { /* already in maillist */
FREE(name);
break;
}
}
if (*oldp) {
info = *oldp;
*oldp = (*oldp)->next;
} else {
info = XMALLOC(sizeof(mail_info_t));
info->name = name;
info->mtime = -2;
info->size = -2;
info->flag = 0;
info->error = 0;
}
info->next = newlist;
return info;
}
int ch_mailfile(Var *var)
{
const char *path, *end;
char *name, *p;
mail_info_t *newlist = NULL;
if (TFMAILPATH && *TFMAILPATH) {
path = TFMAILPATH;
while (*path) {
while (is_space(*path)) path++;
if (!*path) break;
end = estrchr(path, ' ', '\\');
if (!end) end = path + strlen(path);
p = name = XMALLOC(end-path+1);
while (path < end) {
if (*path == '\\') path++;
*p++ = *path++;
}
*p = '\0';
newlist = add_mail_file(newlist, name);
}
} else {
newlist = add_mail_file(newlist, STRDUP(MAIL));
}
free_maillist();
maillist = newlist;
ch_maildelay(NULL);
return 1;
}
static void free_maillist(void)
{
mail_info_t *info;
while (maillist) {
info = maillist;
maillist = maillist->next;
FREE(info->name);
FREE(info);
}
}
void init_util2(void)
{
String *path;
const char *name;
ch_locale(NULL);
if (MAIL || TFMAILPATH) { /* was imported from environment */
ch_mailfile(NULL);
#ifdef MAILDIR
} else if ((name = getvar("LOGNAME")) || (name = getvar("USER"))) {
(path = Stringnew(NULL, -1, 0))->links++;
Sprintf(path, "%s/%s", MAILDIR, name);
set_str_var_by_name("MAIL", path);
Stringfree(path);
#endif
} else {
wprintf("Can't figure out name of mail file.");
}
}
#if USE_DMALLOC
void free_util(void)
{
Stringfree(featurestr);
free_maillist();
}
#endif
/* check_mail()
* Enables the "(Mail)" indicator iff there is unread mail.
* Calls the MAIL hook iff there is new mail.
*
* Logic:
* If the file exists and is not empty, we follow this chart:
*
* || first | new | mtime same | mtime changed
* Timestamps || test | file | as last test | since last test
* ================++=========+=========+==============+=================
* mtime < atime || 0,mail | 0 | 0 | 0
* mtime == atime || 0,mail | 1,hook% | same | 0
* mtime > atime || 0,mail | 1,hook | 1 | 1,hook
*
* "0" or "1" means turn the "(Mail)" indicator off or on; "same" means
* leave it in the same state it was in before the check.
* "mail" means print a message. "hook" means call the MAIL hook.
*
* [%]Problem:
* If the file is created between checks, and mtime==atime, there is
* no way to tell if the mail has been read. If shell() or suspend()
* is used to read mail, we can avoid this situation by checking mail
* before and after shell() and suspend(). There is no way to avoid
* it if mail is read in an unrelated process (e.g, another window).
*
* Note that mail readers can write to the mail file, causing a change
* in size, a change in mtime, and mtime==atime.
*/
void check_mail(void)
{
struct stat buf;
static int depth = 0;
int old_mail_count = mail_count;
mail_info_t *info;
if (depth) return; /* don't allow recursion */
depth++;
if (!maillist || tvcmp(&maildelay, &tvzero) <= 0) {
if (mail_count) {
mail_count = 0;
update_status_field(NULL, STAT_MAIL);
}
depth--;
return;
}
mail_count = 0;
for (info = maillist; info; info = info->next) {
if (stat(expand_filename(info->name), &buf) == 0) {
errno = (buf.st_mode & S_IFDIR) ? EISDIR : 0;
}
if (errno) {
/* Error, or file does not exist. */
if (errno == ENOENT) {
info->error = 0;
} else if (info->error != errno) {
eprintf("%s: %s", info->name, strerror(errno));
info->error = errno;
}
info->mtime = -1;
info->size = -1;
info->flag = 0;
continue;
} else if (buf.st_size == 0) {
/* There is no mail (the file exists, but is empty). */
info->flag = 0;
} else if (info->size == -2) {
/* There is mail, but this is the first time we've checked.
* There is no way to tell if it has been read; assume it has. */
info->flag = 0;
oprintf("%% You have mail in %s", info->name); /* not "new" */
} else if (buf.st_mtime > buf.st_atime) {
/* There is unread mail. */
info->flag = 1; mail_count++;
if (buf.st_mtime > info->mtime)
do_hook(H_MAIL, "%% You have new mail in %s", "%s", info->name);
} else if (info->size < 0 && buf.st_mtime == buf.st_atime) {
/* File did not exist last time; assume new mail is unread. */
info->flag = 1; mail_count++;
do_hook(H_MAIL, "%% You have new mail in %s", "%s", info->name);
} else if (buf.st_mtime > info->mtime || buf.st_mtime < buf.st_atime) {
/* Mail has been read. */
info->flag = 0;
} else if (info->flag >= 0) {
/* There has been no change since the last check. */
mail_count += info->flag;
}
info->error = 0;
info->mtime = buf.st_mtime;
info->size = buf.st_size;
}
if (mail_count != old_mail_count)
update_status_field(NULL, STAT_MAIL);
depth--;
}
/* Converts a string to a numeric Value.
* <typeset> is a bitmap of the types that are allowed.
* *<endp> will get a pointer to the first character past the number.
* If val is NULL, parsenumver() will allocate a Value.
* Returns pointer to value for success, NULL for error.
* Format Type
* ------ ----
* h:m[:s[.f]] DTIME
* i[.f]Ee FLOAT
* i[.f] DTIME, DECIMAL, or FLOAT (depending typeset and length of <f>).
* i INT
*/
Value *parsenumber(const char *str, const char **caller_endp, int typeset,
Value *val)
{
int neg = 0, allocated, digits;
char *endp;
if ((allocated = !val))
val = newval();
#if NO_FLOAT
typeset &= ~TYPE_FLOAT;
#endif
for ( ; *str == '+' || *str == '-'; str++) {
if (*str == '-') neg = !neg;
}
if (typeset & TYPE_INT) {
val->type = TYPE_INT;
errno = 0;
val->u.ival = strtolong(str, &endp);
if ((typeset & (TYPE_DTIME | TYPE_DECIMAL)) && (*endp == '.' || *endp == ':'))
goto parse_time;
if (typeset & TYPE_FLOAT && (*endp == '.' || lcase(*endp) == 'e'))
goto parse_float;
if (val->u.ival == LONG_MAX && errno) {
if (typeset & TYPE_FLOAT) goto parse_float;
eprintf("integer value too large");
goto fail;
}
if (neg) val->u.ival = -val->u.ival;
goto success;
}
parse_time:
if (typeset & (TYPE_DTIME | TYPE_DECIMAL)) {
val->type = (typeset & TYPE_DECIMAL) ? TYPE_DECIMAL : TYPE_DTIME;
errno = 0;
val->u.tval.tv_usec = 0;
val->u.tval.tv_sec = strtolong(str, &endp);
if (*endp == ':') {
val->type |= TYPE_HMS;
if (val->u.tval.tv_sec > 596523) /* will wrap when *3600 */
goto time_overflow;
val->u.tval.tv_sec *= 3600;
endp++;
if (is_digit(*endp)) {
long fieldval;
fieldval = strtolong(endp, &endp);
if (fieldval > 35791394) /* will wrap when *60 */
goto time_overflow;
fieldval *= 60;
val->u.tval.tv_sec += fieldval;
if (val->u.tval.tv_sec < 0) /* wrapped */
goto time_overflow;
if (*endp == ':') {
++endp;
if (is_digit(*endp)) {
fieldval = strtolong(endp, &endp);
if (fieldval == LONG_MAX && errno) /* wrapped */
goto time_overflow;
val->u.tval.tv_sec += fieldval;
if (val->u.tval.tv_sec < 0) /* wrapped */
goto time_overflow;
}
}
}
if (*endp == '.') {
endp++;
for (digits=0; digits < 6 && is_digit(*endp); digits++, endp++)
val->u.tval.tv_usec = 10*val->u.tval.tv_usec + (*endp-'0');
while (digits++ < 6)
val->u.tval.tv_usec *= 10;
while (is_digit(*endp))
endp++;
}
} else if (*endp == '.') {
if (val->u.tval.tv_sec == LONG_MAX && errno) {
if (typeset & TYPE_FLOAT) goto parse_float;
goto time_overflow;
}
endp++;
for (digits = 0; digits < 6 && is_digit(*endp); digits++, endp++)
val->u.tval.tv_usec = 10 * val->u.tval.tv_usec + (*endp - '0');
if ((is_digit(*endp) || lcase(*endp) == 'e') &&
(typeset & TYPE_FLOAT))
goto parse_float;
while (digits++ < 6)
val->u.tval.tv_usec *= 10;
while (is_digit(*endp))
endp++;
} else {
if (val->u.tval.tv_sec == LONG_MAX && errno) {
if (typeset & TYPE_FLOAT) goto parse_float;
goto time_overflow;
}
}
if (neg) {
val->u.tval.tv_sec = -val->u.tval.tv_sec;
val->u.tval.tv_usec = -val->u.tval.tv_usec;
}
goto success;
time_overflow:
errno = ERANGE;
eprintf("time value too large");
goto fail;
}
parse_float:
val->type = TYPE_FLOAT;
#if NO_FLOAT
eprintf("floating point numeric values are not enabled.");
goto fail;
#else
val->u.fval = strtod(str, &endp);
if (val->u.fval == HUGE_VAL && errno == ERANGE) {
eprintf("numeric value too large");
goto fail;
}
if (neg) val->u.fval = -val->u.fval;
goto success;
#endif
success:
if (caller_endp) *caller_endp = endp;
val->count = 1;
val->name = NULL;
val->sval = NULL;
return val;
fail:
if (allocated)
freeval(val);
return NULL;
}
#if 0
int strtotime(struct timeval *tvp, const char *str, char **endp)
{
Value val[1];
if (!parsenumber(str, endp, TYPE_DTIME | TYPE_INT, val))
return -1;
if (val->type & TYPE_DTIME) {
*tvp = val->u.tval;
return 1;
} else {
tvp->tv_sec = val->u.ival;
tvp->tv_usec;
return 0;
}
}
#endif
/* Converts a time-of-day to an absolute time within the last 24h.
* BUG: doesn't handle switches to/from daylight savings time. (?)
*/
void abstime(struct timeval *tvp)
{
struct tm *tm;
time_t now;
time(&now);
tm = localtime(&now);
tm->tm_hour = tvp->tv_sec / 3600;
tm->tm_min = (tvp->tv_sec / 60) % 60;
tm->tm_sec = tvp->tv_sec % 60;
tm->tm_isdst = -1;
tvp->tv_sec = mktime(tm);
if (tvp->tv_sec > now)
tvp->tv_sec -= 24 * 60 * 60;
/* don't touch tvp->tv_usec */
}
static inline void normalize_time(struct timeval *tvp)
{
while (tvp->tv_usec < (tvp->tv_sec > 0 ? 0 : -999999)) {
tvp->tv_sec--;
tvp->tv_usec += 1000000;
}
while (tvp->tv_usec > (tvp->tv_sec < 0 ? 0 : 999999)) {
tvp->tv_sec++;
tvp->tv_usec -= 1000000;
}
}
/* a = b - c */
void tvsub(struct timeval *a, const struct timeval *b, const struct timeval *c)
{
a->tv_sec = b->tv_sec - c->tv_sec;
a->tv_usec = b->tv_usec - c->tv_usec;
normalize_time(a);
}
/* a = b + c */
void tvadd(struct timeval *a, const struct timeval *b, const struct timeval *c)
{
a->tv_sec = b->tv_sec + c->tv_sec;
a->tv_usec = b->tv_usec + c->tv_usec;
normalize_time(a);
}
void append_usec(String *buf, long usec, int truncflag)
{
#if HAVE_GETTIMEOFDAY
Sappendf(buf, ".%06ld", usec);
if (truncflag) {
int i;
for (i = 1; i < 6; i++)
if (buf->data[buf->len - i] != '0') break;
Stringtrunc(buf, buf->len - i + 1);
}
#endif /* HAVE_GETTIMEOFDAY */
}
/* appends a formatted time string to buf.
* If fmt is NULL, trailing zeros are omitted.
*/
void tftime(String *buf, const conString *fmt, const struct timeval *intv)
{
STATIC_STRING(defaultfmt, "%c", 0);
STATIC_STRING(Ffmt, "%Y-%m-%d", 0);
STATIC_STRING(Tfmt, "%H:%M:%S", 0);
struct timeval tv = *intv;
if (!fmt || strcmp(fmt->data, "@") == 0) {
if (tv.tv_sec < 0 || tv.tv_usec < 0) {
tv.tv_usec = -tv.tv_usec;
if (tv.tv_sec == 0) Stringadd(buf, '-');
/* if (sec < 0), Sprintf will take care of the '-'.
* We shouldn't, because negating sec could cause overflow. */
}
Sappendf(buf, "%ld", tv.tv_sec);
append_usec(buf, tv.tv_usec, !fmt);
} else {
#if HAVE_STRFTIME
int i, start, oldlen;
static char fmtbuf[4] = "%?";
time_t adj_sec, adj_usec;
if (!*fmt->data) fmt = defaultfmt;
adj_sec = tv.tv_sec;
adj_usec = tv.tv_usec;
if (tv.tv_usec < 0) { adj_sec -= 1; adj_usec += 1000000; }
for (start = i = 0; i < fmt->len; i++) {
if (fmt->data[i] != '%') {
/* do nothing */
} else {
SStringoncat(buf, fmt, start, i - start);
oldlen = buf->len;
++i;
if (fmt->data[i] == '@') {
tftime(buf, NULL, &tv);
} else if (fmt->data[i] == '.') {
Sappendf(buf, "%06ld", adj_usec);
} else if (fmt->data[i] == 's') {
Sappendf(buf, "%ld", adj_sec);
} else if (fmt->data[i] == 'F') {
tftime(buf, Ffmt, &tv);
} else if (fmt->data[i] == 'T') {
tftime(buf, Tfmt, &tv);
} else {
struct tm *tm;
int j = 1;
tm = localtime(&adj_sec);
fmtbuf[j] = fmt->data[i];
if (fmtbuf[j] == 'E' || fmtbuf[j] == 'O')
fmtbuf[++j] = fmt->data[++i];
fmtbuf[++j] = '\0';
Stringtrunc(buf, buf->len + 32);
buf->len += strftime(buf->data + buf->len, 32, fmtbuf, tm);
}
if (buf->charattrs || fmt->charattrs)
extend_charattrs(buf, oldlen,
fmt->charattrs ? fmt->charattrs[i] : 0);
start = i+1;
}
}
SStringoncat(buf, fmt, start, i - start);
#else
char *str = ctime(&tv.tv_sec);
Stringcat(buf, str, strlen(str)-1); /* -1 removes ctime()'s '\n' */
#endif /* HAVE_STRFTIME */
}
}
void die(const char *why, int err)
{
fix_screen();
reset_tty();
if ((errno = err)) perror(why);
else {
fputs(why, stderr);
fputc('\n', stderr);
}
exit(1);
}