有没有一个命令行工具,将采取象征性的errno,如EINVAL并打印相应的字符串,无效的参数?
我想避免在系统上找到EINVAL值为22,然后使用$perror 22.
理想情况下我可以写一些类似的东西
$errorcommand EINVAL
Invalid argument
$
AFAIK,没有一个标准的工具来完成这项工作.在一个层面上,写一个并不是特别困难 – 最糟糕的部分是找到正确的文件来解析(通常是但并不总是/usr/include/sys/errno.h),然后采取从那个数据来做映射的名字到数字.我没有找到一个使用枚举值而不是#define值的系统,但这可能只是时间问题.是否产生由令牌号(EINTR等),令牌名称(“EINTR”等)和错误消息(“中断系统调用”等)组成的三元组,或是否仅使用数字并命名并将其留给“strerror()”来提供消息.
原文链接:https://www.f2er.com/bash/383549.html正如我所说,这不是特别困难.我已经有一个名为“errno”的程序,接受纯数值并打印出相应的错误信息:
$errno 1:10 20 1: Operation not permitted 2: No such file or directory 3: No such process 4: Interrupted system call 5: Input/output error 6: Device not configured 7: Argument list too long 8: Exec format error 9: Bad file descriptor 10: No child processes 20: Not a directory $
我已经写了一个Perl脚本,并且黑客程序也处理了符号错误号:
$errno 1:4 EINTR ENOTDIR 1 (EPERM): Operation not permitted 2 (ENOENT): No such file or directory 3 (ESRCH): No such process 4 (EINTR): Interrupted system call EINTR (4): Interrupted system call ENOTDIR (20): Not a directory $
它不处理符号错误号的范围(为读者练习).
generrno.pl
#!/usr/bin/perl -w # # @(#)$Id: generrno.pl,v 1.1 2010/02/07 18:39:18 jleffler Exp jleffler $ # # Generate table of error number constants from given file(s) use strict; my %symlist; my $maxsymlen = 0; my $maxmsglen = 0; while (<>) { next unless m%^\s*#\s*define\s+(E[A-Z0-9a-z]+)\s+(\d+)\s*/\*\s*([A-Za-z].*\S)\s*\*/%; $symlist{$1} = { number => $2,message => $3 }; $maxsymlen = length($1) if length($1) > $maxsymlen; $maxmsglen = length($3) if length($3) > $maxmsglen; } my $format = sprintf " { %%-%ds %%-%ds %%-5s %%-%ds },\n",$maxsymlen + 3,$maxsymlen + 1,$maxmsglen + 2; foreach my $key (sort keys %symlist) { my $name = qq{"$key",}; my $symbol = qq{$key,}; my $number = qq{$symlist{$key}->{number},}; my $message = qq{"$symlist{$key}->{message}"}; printf $format,$name,$symbol,$number,$message; }
errno.c
/* @(#)File: $RCSfile: errno.c,v $ @(#)Version: $Revision: 2.2 $ @(#)Last changed: $Date: 2010/02/07 19:22:37 $ @(#)Purpose: Print messages corresponding to errno values or name @(#)Author: J Leffler @(#)Copyright: (C) JLSS 2003,2005,2008,2010 */ /*TABSTOP=4*/ #define MAIN_PROGRAM /* Need O/S specific messages as well as POSIX messages */ //#if __STDC_VERSION__ >= 199901L //#define _XOPEN_SOURCE 600 //#else //#define _XOPEN_SOURCE 500 //#endif /* __STDC_VERSION__ */ #include <ctype.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* getopt() on MacOS X 10.2 */ #include "stderr.h" #include "range.h" typedef struct err_info { const char *errsym; /* Error symbol - "EINTR" */ int errnum; /* Error number - EINTR */ int errdef; /* Error define - 4 */ const char *errmsg; /* Error message - Interrupted system call */ } err_info; /* ** Generate generrno.h using: ** perl generrno.pl /usr/include/sys/errno.h > generrno.h ** NB: list must be sorted alphabetically on symbol name */ static const err_info err_msgs[] = { #include "generrno.h" }; static const char usestr[] = "[-qV] [--] lo[:hi] ..."; #define DIM(x) (sizeof(x)/sizeof(*(x))) static const err_info *err_nums[DIM(err_msgs)]; #ifndef lint /* Prevent over-aggressive optimizers from eliminating ID string */ const char jlss_id_errno_c[] = "@(#)$Id: errno.c,v 2.2 2010/02/07 19:22:37 jleffler Exp $"; #endif /* lint */ static int cmp_err_number(const void *v1,const void *v2) { int e1 = (*((const err_info * const *)v1))->errnum; int e2 = (*((const err_info * const *)v2))->errnum; return(e1 - e2); } static void map_numbers(void) { int i; for (i = 0; i < DIM(err_msgs); i++) err_nums[i] = &err_msgs[i]; qsort(err_nums,DIM(err_nums),sizeof(*err_nums),cmp_err_number); } static const char *err_symbol(int num) { const char *sym = "<UNKNOWN>"; err_info lookfor = { 0,num,0 }; err_info *lookptr = &lookfor; const err_info **found = bsearch(&lookptr,err_nums,cmp_err_number); if (found != 0) sym = (*found)->errsym; return(sym); } static int cmp_err_symbol(const void *v1,const void *v2) { const char *s1 = ((const err_info *)v1)->errsym; const char *s2 = ((const err_info *)v2)->errsym; return(strcmp(s1,s2)); } static int pr_string_errno(const char *arg,int qflag) { int estat = EXIT_SUCCESS; err_info lookfor = { arg,0 }; const err_info *found = bsearch(&lookfor,err_msgs,DIM(err_msgs),sizeof(*err_msgs),cmp_err_symbol); if (found == 0) { err_remark("unrecognized symbol %s\n",arg); estat = EXIT_FAILURE; } else if (qflag == 0) printf("%s (%d): %s\n",arg,found->errnum,found->errmsg); return(estat); } static int pr_number_errno(const char *arg,int qflag) { int estat = EXIT_SUCCESS; long lo; long hi; const char *endp; long msg; endp = numeric_range(arg,&lo,&hi); if (endp == arg) err_remark("Invalid range specified (%s) - should be lo[:hi]\n",arg); else if (*endp != '\0') err_remark("Non-numeric character (%c) after range '%s'\n",(isprint((unsigned char)*endp) ? *endp : '?'),arg); else { for (msg = lo; msg <= hi; msg++) { char *msgtxt = strerror(msg); if (msgtxt == 0) { err_remark("no message for errno = %ld\n",msg); estat = EXIT_FAILURE; } else if (qflag == 0) printf("%ld (%s): %s\n",msg,err_symbol(msg),msgtxt); } } return(estat); } static int pr_errno(char *arg,int qflag) { int estat; if (isalpha(*arg)) estat = pr_string_errno(arg,qflag); else estat = pr_number_errno(arg,qflag); return(estat); } int main(int argc,char **argv) { int i; int opt; int nstat; int estat = EXIT_SUCCESS; int qflag = 0; int nflag = 0; err_setarg0(argv[0]); map_numbers(); while ((opt = getopt(argc,argv,"qV0:1:2:3:4:5:6:7:8:9:")) != EOF) { switch (opt) { case 'q': qflag = 1; break; case 'V': err_version("ERRNO","$Revision: 2.2 $($Date: 2010/02/07 19:22:37 $)"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* GETOPT() is probably not the right tool for this job! */ nstat = pr_errno(optarg-2,qflag); if (estat == EXIT_SUCCESS) estat = nstat; nflag = 1; break; default: err_usage(usestr); break; } } if (optind >= argc && nflag == 0) err_usage(usestr); for (i = optind; i < argc; i++) { nstat = pr_errno(argv[i],qflag); if (estat == EXIT_SUCCESS) estat = nstat; } return(estat); }
代码需要一些支持文件 – stderr.h,range.h,range2.c和stderrmin.c(一个更简单的版本的stderr.c我通常使用,它具有额外的铃声和口哨处理syslog和写入文件描述符代替的文件指针.)
stderr.h
/* @(#)File: $RCSfile: stderr.h,v $ @(#)Version: $Revision: 9.2 $ @(#)Last changed: $Date: 2009/03/06 06:52:26 $ @(#)Purpose: Header file for standard error functions @(#)Author: J Leffler @(#)Copyright: (C) JLSS 1989-93,1996-99,2003,2005-09 @(#)Product: :PRODUCT: */ #ifndef STDERR_H #define STDERR_H #ifdef MAIN_PROGRAM #ifndef lint /* Prevent over-aggressive optimizers from eliminating ID string */ const char jlss_id_stderr_h[] = "@(#)$Id: stderr.h,v 9.2 2009/03/06 06:52:26 jleffler Exp $"; #endif /* lint */ #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include <stdio.h> #include <stdarg.h> #ifdef __GNUC__ #define PRINTFLIKE(n,m) __attribute__((format(printf,n,m))) #define NORETURN() __attribute__((noreturn)) #else #define PRINTFLIKE(n,m) /* If only */ #define NORETURN() /* If only */ #endif /* __GNUC__ */ /* -- Definitions for error handling */ enum { ERR_STAT = 1 }; /* Default exit status */ enum { ERR_DEFAULT = 0x0000 }; /* Default flag */ enum { ERR_NOFLUSH = 0x0001 }; /* Do not flush open files */ enum { ERR_EXIT = 0x0004 }; /* Exit -- do not return */ enum { ERR_ABORT = 0x0008 }; /* Abort -- do not return */ enum { ERR_STAMP = 0x0020 }; /* Timestamp messages */ enum { ERR_NOARG0 = 0x0040 }; /* Do not print arg0 prefix */ enum { ERR_PID = 0x0080 }; /* Include pid=nnnnn info */ enum { ERR_ERRNO = 0x0100 }; /* Include system error */ #ifdef USE_STDERR_SYSLOG /* Definitions related to using syslog */ enum { ERR_LOG_EMERG = 0x01000 }; /* system is unusable */ enum { ERR_LOG_ALERT = 0x02000 }; /* action must be taken immediately */ enum { ERR_LOG_CRIT = 0x04000 }; /* critical conditions */ enum { ERR_LOG_ERR = 0x08000 }; /* error conditions */ enum { ERR_LOG_WARNING = 0x10000 }; /* warning conditions */ enum { ERR_LOG_NOTICE = 0x20000 }; /* normal but signification condition */ enum { ERR_LOG_INFO = 0x40000 }; /* informational */ enum { ERR_LOG_DEBUG = 0x80000 }; /* debug-level messages */ enum { ERR_LOG_LEVEL_HI = ERR_LOG_EMERG|ERR_LOG_ALERT|ERR_LOG_CRIT|ERR_LOG_ERR }; enum { ERR_LOG_LEVEL_LO = ERR_LOG_WARNING|ERR_LOG_NOTICE|ERR_LOG_INFO|ERR_LOG_DEBUG }; enum { ERR_LOG_LEVEL = ERR_LOG_LEVEL_HI|ERR_LOG_LEVEL_LO }; #endif /* USE_STDERR_SYSLOG */ /* -- Standard combinations of flags */ enum { ERR_REM = ERR_DEFAULT }; enum { ERR_ERR = ERR_EXIT }; enum { ERR_ABT = ERR_ABORT }; enum { ERR_LOG = ERR_STAMP|ERR_PID }; enum { ERR_SYSREM = ERR_REM|ERR_ERRNO }; enum { ERR_SYSERR = ERR_ERR|ERR_ERRNO }; /* -- Maximum recorded length of argv[0]; extra is truncated */ enum { ERR_MAXLEN_ARGV0 = 63 }; /* -- Global definitions */ extern const char err_format1[]; /* "%s\n" - for one string argument */ extern const char err_format2[]; /* "%s %s\n" - for two string arguments */ extern const char *err_getarg0(void); extern void err_setarg0(const char *argv0); extern FILE *err_stderr(FILE *fp); extern const char *err_rcs_string(const char *s,char *buffer,size_t buflen); extern void err_abort(const char *format,...) PRINTFLIKE(1,2) NORETURN(); extern void err_error(const char *format,2) NORETURN(); extern void err_error1(const char *s1) NORETURN(); extern void err_error2(const char *s1,const char *s2) NORETURN(); extern void err_help(const char *use_str,const char *hlp_str) NORETURN(); extern void err_helplist(const char *use_str,const char * const *help_list) NORETURN(); extern void err_internal(const char *function,const char *msg) NORETURN(); extern void err_logmsg(FILE *fp,int flags,int estat,const char *format,...) PRINTFLIKE(4,5); extern void err_print(int flags,va_list args); extern void err_printversion(const char *program,const char *verinfo); extern void err_remark(const char *format,2); extern void err_remark1(const char *s1); extern void err_remark2(const char *s1,const char *s2); extern void err_report(int flags,...) PRINTFLIKE(3,4); extern void err_syserr(const char *format,2) NORETURN(); extern void err_syserr1(const char *s1) NORETURN(); extern void err_syserr2(const char *s1,const char *s2) NORETURN(); extern void err_sysrem(const char *format,2); extern void err_sysrem1(const char *s1); extern void err_sysrem2(const char *s1,const char *s2); extern void err_usage(const char *usestr) NORETURN(); extern void err_version(const char *program,const char *verinfo) NORETURN(); extern int err_getlogopts(void); /* Get default log options */ extern int err_setlogopts(int new_opts); /* Set default log options */ #ifdef USE_STDERR_FILEDESC extern int err_use_fd(int fd); /* Use file descriptor */ #endif /* USE_STDERR_FILEDESC */ #ifdef USE_STDERR_SYSLOG /* In case of doubt,use zero for both logopts and facility */ extern int err_use_syslog(int logopts,int facility); /* Configure/use syslog() */ #endif /* USE_STDERR_SYSLOG */ /* ** JL 2003-07-31: Security Note. ** Question: given that err_remark("abc\n") and err_remark1("abc") ** produce the same output,when should you use err_remark1() ** instead of err_remark()? ** Answer 1: trivia - when you can't put the newline in the string. ** Answer 2: security - when the argument contains user input and could,** therefore,contain conversion specifiers,etc. The code in ** err_remark() does not (and cannot) verify that you have ** passed correct arguments for the conversion specifiers in ** the format string. ** Answer 3: inertia - when migrating code that uses remark(). ** ** Generalizing: when you use a function that has 'const char *format' ** in the prototype above,make sure your code is fully in charge of the ** format string to avoid security lapses. Do not allow the user to ** provide that string unless you stringently check it beforehand. */ #endif /* STDERR_H */
range.h
/* @(#)File: $RCSfile: range.h,v $ @(#)Version: $Revision: 1.8 $ @(#)Last changed: $Date: 2008/02/11 07:39:36 $ @(#)Purpose: Declaration of range parsing functions @(#)Author: J Leffler @(#)Copyright: (C) JLSS 1997,2007-08 @(#)Product: :PRODUCT: */ /*TABSTOP=4*/ #ifndef RANGE_H #define RANGE_H #ifdef MAIN_PROGRAM #ifndef lint /* Prevent over-aggressive optimizers from eliminating ID string */ const char jlss_id_range_h[] = "@(#)$Id: range.h,v 1.8 2008/02/11 07:39:36 jleffler Exp $"; #endif /* lint */ #endif /* MAIN_PROGRAM */ /* ** parse_range(): parse range of non-negative numbers. ** ** Given a string,parse_range() returns the lo and hi values corresponding ** to the range specified by the string. For example: ** Input: Low High ** 23 23 23 ** 23-25 23 25 ** 23- 23 0 ** -23 0 23 ** Any delimiter other than '-' before or after a number terminates the ** scan,but commas are skipped. Returns pointer to character after ** last character parsed (which may or may not be '\0') if successful. ** Otherwise,returns null. ** ** Idiomatic use: ** ** const char *ptr = source_string; ** const char *nxt; ** while ((nxt = parse_range(ptr,&hi)) != 0) ** { ** if (nxt == ptr) ** err_error("invalid range string (%s)\n",source_string); ** use_range(lo,hi); ** ptr = nxt; ** } */ extern const char *parse_range(const char *str,long *lo,long *hi); /* ** numeric_range(): parse range of numbers,positive or negative. ** ** Input: Low High ** 23 23 23 ** -23 -23 -23 ** 23:25 23 25 ** 23..25 23 25 ** -23..-25 -25 -23 ** -23..25 -23 25 ** 23..-25 -25 23 ** Returns pointer to '\0' at end of string if OK,sets *lo and *hi,** and guarantees *lo <= *hi. ** Otherwise,returns pointer to start of string and does not set *lo or *hi. ** ** Idiomatic use: ** ** const char *ptr = source_string; ** const char *nxt; ** while ((nxt = numeric_range(ptr,hi); ** ptr = nxt; ** } */ extern const char *numeric_range(const char *str,long *hi); #endif /* RANGE_H */
range2.c
/* @(#)File: $RCSfile: range2.c,v $ @(#)Version: $Revision: 1.8 $ @(#)Last changed: $Date: 2008/02/11 08:44:50 $ @(#)Purpose: Decode string into range of integers. @(#)Author: J Leffler @(#)Copyright: (C) JLSS 1997,2002,2007-08 @(#)Product: :PRODUCT: */ /*TABSTOP=4*/ /* ** Parse number ranges,dealing with positive and negative values,** and ranges separated by either colon or double-dot. ** ** Input: Low High ** 23 23 23 ** -23 -23 -23 ** 23:25 23 25 ** 23..25 23 25 ** -23..-25 -25 -23 ** -23..25 -23 25 ** 23..-25 -25 23 ** -23..+25 -23 25 ** Any other delimiter after number (or before number) terminates ** input. NB: a leading colon (or dot) is not a valid range. If ** there is a format error,the returned pointer points to the ** start of the string (and lo and hi are unchanged). If there is ** no error,then the returned pointer points to the ASCII NUL at ** the end of the string. */ #include "range.h" #include <stdlib.h> #ifndef lint /* Prevent over-aggressive optimizers from eliminating ID string */ const char jlss_id_range2_c[] = "@(#)$Id: range2.c,v 1.8 2008/02/11 08:44:50 jleffler Exp $"; #endif /* lint */ /* ** Parse numeric range. ** Return pointer to trailing '\0' if OK,else pointer to input string */ const char *numeric_range(const char *str,long *hi) { const char *s = str; char *t; long l; long h; l = strtol(s,&t,10); if (*t == '\0') { /* Just one number */ *lo = *hi = l; return(t); } if (*t == ':') t += 1; else if (t[0] == '.' && t[1] == '.') t += 2; else { /* Format error */ return(str); } h = strtol(t,10); if (*t != '\0') { /* Format error */ return(str); } if (h < l) { long x = h; h = l; l = x; } *lo = l; *hi = h; return(t); } #ifdef TEST #include <stdio.h> #include "stderr.h" int main(int argc,char **argv) { int i; long lo; long hi; const char *t; const char *s; err_setarg0(argv[0]); if (argc <= 1) err_usage("range [...]"); for (i = 1; i < argc; i++) { t = argv[i]; if (t != 0 && *t != '\0') { printf("Parse: %15s (addr = 0x%08lX) ",t,(unsigned long)t); fflush(stdout); s = numeric_range(t,&hi); printf("Range: %2ld -> %2ld (addr = 0x%08lX; trailer = <<%s>>)\n",lo,hi,(unsigned long)s,s); fflush(stdout); } } return(0); } #endif /* TEST */
stderrmin.c
这是约400行,而不是约700行.是的,这个程序是过度的;我不会在这个程序中使用它.
/* @(#)File: $RCSfile: stderrmin.c,v $ @(#)Version: $Revision: 9.6 $ @(#)Last changed: $Date: 2009/03/02 20:27:38 $ @(#)Purpose: Minimal implementation of error reporting routines @(#)Author: J Leffler @(#)Copyright: (C) JLSS 1988-91,2001,2005-09 @(#)Product: :PRODUCT: */ /*TABSTOP=4*/ #undef STDERR_EXTENDED #include "stderr.h" #include <assert.h> #include <ctype.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <time.h> #ifdef HAVE_UNISTD_H #include <unistd.h> #else extern int getpid(void); #endif /* HAVE_UNISTD_H */ enum { MAX_MSGLEN = 2048 }; /* Global format strings */ const char err_format1[] = "%s\n"; const char err_format2[] = "%s %s\n"; static const char def_format[] = "%Y-%m-%d %H:%M:%S"; static const char *tm_format = def_format; static char arg0[ERR_MAXLEN_ARGV0+1] = "**undefined**"; /* Permitted default error flags */ enum { ERR_logoPTS = ERR_NOFLUSH | ERR_EXIT | ERR_ABORT | ERR_STAMP | ERR_NOARG0 | ERR_PID | ERR_ERRNO }; static int err_flags = 0; /* Default error flags (ERR_STAMP,ERR_PID,etc) */ static FILE *errout = 0; /* ** err_???_print() functions are named systematically,and are all static. ** ** err_[ev][crx][fn]_print(): ** -- e takes ellipsis argument ** -- v takes va_list argument ** -- c conditionally exits ** -- r returns (no exit) ** -- x exits (no return) ** -- f takes file pointer ** -- n no file pointer (use errout) ** ** NB: no-return and printf-like can only be attached to declarations,not definitions. */ static void err_vxf_print(FILE *fp,va_list args) NORETURN(); static void err_vxn_print(int flags,va_list args) NORETURN(); static void err_exn_print(int flags,...) NORETURN() PRINTFLIKE(3,4); static void err_terminate(int flags,int estat) NORETURN(); #ifndef lint /* Prevent over-aggressive optimizers from eliminating ID string */ const char jlss_id_stderrmin_c[] = "@(#)$Id: stderrmin.c,v 9.6 2009/03/02 20:27:38 jleffler Exp $"; #endif /* lint */ /* ** Set default log options,returning old value. ** Setting ERR_EXIT and ERR_ABORT is permitted but not recommended. */ int err_setlogopts(int new_opts) { int old_opts = err_flags; err_flags = new_opts & ERR_logoPTS; return(old_opts); } /* Return default log options */ int err_getlogopts(void) { return(err_flags); } /* Change the definition of 'stderr',reporting on the old one too */ /* NB: using err_stderr((FILE *)0) simply reports the current 'stderr' */ FILE *(err_stderr)(FILE *newerr) { FILE *old; if (errout == 0) errout = stderr; old = errout; if (newerr != 0) errout = newerr; return(old); } /* Return stored basename of command */ const char *(err_getarg0)(void) { return(arg0); } /* Store basename of command,excluding trailing slashes */ void (err_setarg0)(const char *argv0) { /* Ignore three pathological program names -- NULL,"/" and "" */ if (argv0 != 0 && *argv0 != '\0' && (*argv0 != '/' || *(argv0 + 1) != '\0')) { const char *cp; size_t nbytes = sizeof(arg0) - 1; if ((cp = strrchr(argv0,'/')) == 0) { /* Basename of file only */ cp = argv0; } else if (*(cp + 1) != '\0') { /* Regular pathname containing slashes but not trailing slashes */ cp++; } else { /* Skip backwards over trailing slashes */ const char *ep = cp; while (ep > argv0 && *ep == '/') ep--; /* Skip backwards over non-slashes */ cp = ep; while (cp > argv0 && *cp != '/') cp--; assert(ep >= cp); cp++; nbytes = (size_t)(ep - cp) + 1; if (nbytes > sizeof(arg0) - 1) nbytes = sizeof(arg0) - 1; } strncpy(arg0,cp,nbytes); arg0[nbytes] = '\0'; } } const char *(err_rcs_string)(const char *s2,size_t buflen) { const char *src = s2; char *dst = buffer; char *end = buffer + buflen - 1; /* ** Bother RCS! We've probably been given something like: ** "$Revision: 9.6 $($Date: 2009/03/02 20:27:38 $)" ** We only want to emit "7.5 (2001/08/11 06:25:48)". ** Skip the components between '$' and ': ',copy up to ' $',** repeating as necessary. And we have to test for overflow! ** Also work with the unexpanded forms of keywords ($Keyword$). ** Never needed this with SCCS! */ while (*src != '\0' && dst < end) { while (*src != '\0' && *src != '$') { *dst++ = *src++; if (dst >= end) break; } if (*src == '$') src++; while (*src != '\0' && *src != ':' && *src != '$') src++; if (*src == '\0') break; if (*src == '$') { /* Unexpanded keyword '$Keyword$' notation */ src++; continue; } if (*src == ':') src++; if (*src == ' ') src++; while (*src != '\0' && *src != '$') { /* Map / in 2009/02/15 to dash */ /* Heuristic - maps slashes surrounded by digits to dashes */ char c = *src++; if (c == '/' && isdigit(*src) && isdigit(*(src-2))) c = '-'; *dst++ = c; if (dst >= end) break; } if (*src == '$') { if (*(dst-1) == ' ') dst--; src++; } } *dst = '\0'; return(buffer); } /* Format a time string for now (using ISO8601 format) */ /* Allow for future settable time format with tm_format */ static char *err_time(char *buffer,size_t buflen) { time_t now; struct tm *tp; now = time((time_t *)0); tp = localtime(&now); strftime(buffer,buflen,tm_format,tp); return(buffer); } /* Most fundamental (and flexible) error message printing routine - always returns */ static