/* @(#) implementation of utility functions for udpxy * * Copyright 2008-2011 Pavel V. Cherenkov (pcherenkov@gmail.com) * * This file is part of udpxy. * * udpxy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * udpxy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with udpxy. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "uopt.h" #include "mtrace.h" #include "osdef.h" extern const char COMPILE_MODE[]; extern const char VERSION[]; extern const int BUILDNUM; extern const char BUILD_TYPE[]; extern const int PATCH; static char s_sysinfo [80] = "\0"; extern struct udpxy_opt g_uopt; /* write buffer to a file * */ ssize_t save_buffer( const void* buf, size_t len, const char* filename ) { int fd, rc; ssize_t nw, left; const char* p; assert( buf && len && filename ); rc = 0; fd = creat( filename, (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) ); if( -1 == fd ) { perror("creat"); return 1; } for( p = (const char*)buf, nw = 0, left = len; left > 0; ) { nw = write( fd, p, left ); if( nw <= 0 ) { nw = 0; if( EINTR != errno) { perror("write"); rc = -1; break; } } left -= nw; p += nw; } (void)close(fd); return (rc ? -1 : ((ssize_t)len - left)); } /* read text file into a buffer * */ ssize_t txtf_read (const char* fpath, char* dst, size_t maxlen, FILE* log) { int rc = 0, fd = -1; ssize_t n = 0; ssize_t left = maxlen - 1; char *p = dst; assert (fpath && dst && maxlen); fd = open (fpath, O_RDONLY, 0); if (-1 == fd) { mperror (log, errno, "%s open %s", __func__, fpath); return -1; } while (left > 0) { n = read (fd, p, maxlen - 1); if (!n) break; if (n < 0) { n = 0; rc = errno; if (EINTR != rc) { mperror (log, errno, "%s read %s", __func__, fpath); break; } rc = 0; } left -= (size_t)n; p += n; } if (!rc) *p = '\0'; if (-1 == close (fd)) { mperror (log, errno, "%s close %s", __func__, fpath); } return (rc ? -1 : ((ssize_t)maxlen - left - 1)); } /* make current process run as a daemon */ int daemonize(int options, FILE* log) { pid_t pid; int rc = 0, fh = -1; assert( log ); if( (pid = fork()) < 0 ) { mperror( log, errno, "%s: fork", __func__); return -1; } else if( 0 != pid ) { exit(0); } do { if( -1 == (rc = setsid()) ) { mperror( log, errno, "%s: setsid", __func__); break; } if( -1 == (rc = chdir("/")) ) { mperror( log, errno, "%s: chdir", __func__ ); break; } (void) umask(0); if( !(options & DZ_STDIO_OPEN) ) { for( fh = 0; fh < 3; ++fh ) if( -1 == (rc = close(fh)) ) { mperror( log, errno, "%s: close", __func__); break; } } if( SIG_ERR == signal(SIGHUP, SIG_IGN) ) { mperror( log, errno, "%s: signal", __func__ ); rc = 2; break; } } while(0); if( 0 != rc ) return rc; /* child exits to avoid session leader's re-acquiring * control terminal */ if( (pid = fork()) < 0 ) { mperror( log, errno, "%s: fork", __func__); return -1; } else if( 0 != pid ) exit(0); return 0; } /* multiplex error output to custom log * and syslog */ void mperror( FILE* fp, int err, const char* format, ... ) { char buf[ 256 ] = { '\0' }; va_list ap; int n = 0; assert(format); va_start( ap, format ); n = vsnprintf( buf, sizeof(buf) - 1, format, ap ); va_end( ap ); if( n <= 0 || n >= ((int)sizeof(buf) - 1) ) return; snprintf( buf + n, sizeof(buf) - n - 1, ": %s", strerror(err) ); syslog( LOG_ERR | LOG_LOCAL0, "%s", buf ); if( fp ) (void) tmfprintf( fp, "%s\n", buf ); return; } /* write-lock on a file handle */ static int wlock_file( int fd ) { struct flock lck; lck.l_type = F_WRLCK; lck.l_start = 0; lck.l_whence = SEEK_SET; lck.l_len = 0; return fcntl( fd, F_SETLK, &lck ); } /* create and lock file with process's ID */ int make_pidfile( const char* fpath, pid_t pid, FILE* log ) { int fd = -1, rc = 0, n = -1; ssize_t nwr = -1; #define LLONG_MAX_DIGITS 21 char buf[ LLONG_MAX_DIGITS + 1 ]; mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; assert( (NULL != fpath) && pid ); errno = 0; do { fd = open( fpath, O_CREAT | O_WRONLY | O_NOCTTY, fmode ); if( -1 == fd ) { mperror(log, errno, "make_pidfile - open"); rc = EXIT_FAILURE; break; } rc = wlock_file( fd ); if( 0 != rc ) { if( (EACCES == errno) || (EAGAIN == errno) ) { (void) fprintf( stderr, "File [%s] is locked " "(another instance of daemon must be running)\n", fpath ); exit(EXIT_FAILURE); } mperror(log, errno, "wlock_file"); break; } rc = ftruncate( fd, 0 ); if( 0 != rc ) { mperror(log, errno, "make_pidfile - ftruncate"); break; } n = snprintf( buf, sizeof(buf) - 1, "%d", pid ); if( n < 0 ) { mperror(log, errno, "make_pidfile - snprintf"); rc = EXIT_FAILURE; break; } nwr = write( fd, buf, n ); if( (ssize_t)n != nwr ) { mperror( log, errno, "make_pidfile - write"); rc = EXIT_FAILURE; break; } } while(0); if( (0 != rc) && (fd > 0) ) { (void)close( fd ); } return rc; } /* write path to application's pidfile into the the buffer * (fail if destination directory is not writable) */ int set_pidfile( const char* appname, int port, char* buf, size_t len ) { int n = -1; assert( appname && buf && len ); if( -1 == access(PIDFILE_DIR, W_OK ) ) return -1; n = snprintf( buf, len, "%s/%s%d.pid", PIDFILE_DIR, appname, port ); if( n < 0 ) return EXIT_FAILURE; buf[ len - 1 ] = '\0'; return 0; } int would_block(int err) { return (EAGAIN == err) || (EWOULDBLOCK == err); } int no_fault(int err) { return (EPIPE == err) || (ECONNRESET == err) || would_block(err); } /* write buffer to designated socket/file */ ssize_t write_buf( int fd, const char* data, const ssize_t len, FILE* log ) { ssize_t n = 0, nwr = 0, error = IO_ERR; int err = 0; for( n = 0; errno = 0, n < len ; ) { nwr = write( fd, &(data[n]), len - n ); if( nwr <= 0 ) { err = errno; if( EINTR == err ) { TRACE( (void)tmfprintf( log, "%s interrupted\n", __func__ ) ); continue; } else { if( would_block(err) ) error = IO_BLK; break; } } n += nwr; if( nwr != len ) { if( NULL != log ) { TRACE( (void)tmfprintf( log, "Fragment written %s[%ld:%ld]/[%ld] bytes\n", (len > n ? "P" : "F"), (long)nwr, (long)n, (long)len ) ); } } } if( nwr <= 0 ) { if( log ) { if (IO_BLK == error) (void)tmfprintf( log, "%s: socket time-out on write", __func__); else if( !no_fault(err) || g_uopt.is_verbose ) mperror( log, errno, "%s: write", __func__ ); } return error; } return n; } /* read data chunk of designated size (or less) into buffer * (will *NOT* attempt to re-read if read less than expected * w/o interruption) */ ssize_t read_buf( int fd, char* data, const ssize_t len, FILE* log ) { ssize_t n = 0, nrd = 0, err = 0; for( n = 0; errno = 0, n < len ; ) { nrd = read( fd, &(data[n]), len - n ); if( nrd <= 0 ) { err = errno; if( EINTR == err ) { TRACE( (void)tmfprintf( log, "%s interrupted\n", __func__ ) ); errno = 0; continue; } else { break; } } n += nrd; /* if( nrd != len ) { if( NULL != log ) { TRACE( (void)tmfprintf( log, "Fragment read [%ld]/[%ld] bytes\n", (long)nrd, (long)len ) ); } } */ /* we only read as much as we can read at once (uninterrupted) */ break; } if( nrd < 0 ) { if( log ) { if( would_block(err) ) (void)tmfprintf( log, "%s: socket time-out on read", __func__); else if( !no_fault(err) || g_uopt.is_verbose ) mperror( log, errno, "%s: read", __func__ ); } } return n; } /* output hex dump of a memory fragment */ void hex_dump( const char* msg, const char* data, size_t len, FILE* log ) { u_int i = 0; assert( data && (len > (size_t)0) && log ); if( msg ) (void)fprintf( log, "%s: ", msg ); for( i = 0; i < len; ++i ) (void)fprintf( log, "%02x ", data[i] & 0xFF ); (void) fputs("\n", log); return; } /* check for expected size, complain if not matched */ int sizecheck( const char* msg, ssize_t expct, ssize_t len, FILE* log, const char* func ) { assert( msg && log && func ); if( expct == len ) return 0; (void) (msg && log && func); /* NOP to eliminate warnings */ TRACE( (void)tmfprintf( log, "%s: %s - read only [%ld] " "bytes out of [%ld]\n", func, msg, (long)len, (long)expct ) ); return 1; } /* check for a potential buffer overrun by * evaluating target buffer and the portion * of that buffer to be accessed */ int buf_overrun( const char* buf, size_t buflen, size_t offset, size_t dlen, FILE* log ) { const char* tgt = buf + offset + dlen; if( tgt > (buf + buflen) ) { if( NULL != log ) { TRACE( (void)tmfprintf( log, "+++ BUFFER OVERRUN at: " "buf=[%p], size=[%lu] - intending to access [%p]: " "offset=[%lu], data_length=[%lu]\n", buf, (u_long)buflen, tgt, (u_long)offset, (u_long)dlen) ); } return 1; } return 0; } /* create timestamp string in YYYY-mm-dd HH24:MI:SS.MSEC from struct timeval */ int mk_tvstamp( const struct timeval* tv, char* buf, size_t* len, int32_t flags ) { const char tmfmt_TZ[] = "%Y-%m-%d %H:%M:%S.%%06ld %Z"; char tfmt_ms[ 80 ] = { '\0' }; int n = 0; struct tm src_tm, *p_tm; time_t clock; assert( tv && buf && len ); clock = tv->tv_sec; p_tm = (flags & TVSTAMP_GMT) ? gmtime_r( &clock, &src_tm ) : localtime_r( &clock, &src_tm ); if( NULL == p_tm ) { perror("gmtime_r/localtime_r"); return errno; } n = strftime( tfmt_ms, sizeof(tfmt_ms) - 1, tmfmt_TZ, &src_tm ); if( 0 == n ) { perror( "strftime" ); return errno; } n = snprintf( buf, *len, tfmt_ms, (long)tv->tv_usec ); if( 0 == n ) { perror( "snprintf" ); return errno; } *len = (size_t)n; return 0; } /* write timestamp-prepended formatted message to file */ int tmfprintf( FILE* stream, const char* format, ... ) { va_list ap; int n = -1, total = 0, rc = 0, NO_RESET = 0; char tstamp[ 80 ] = {'\0'}; size_t ts_len = sizeof(tstamp) - 1; struct timeval tv_now; const char* pidstr = get_pidstr( NO_RESET, NULL ); (void)gettimeofday( &tv_now, NULL ); errno = 0; do { rc = mk_tvstamp( &tv_now, tstamp, &ts_len, 0 ); if( 0 != rc ) break; n = fprintf( stream, "%s\t%s\t", tstamp, pidstr ); if( n <= 0 ) break; total += n; va_start( ap, format ); n = vfprintf( stream, format, ap ); va_end( ap ); if( n <= 0 ) break; total += n; } while(0); if( n <= 0 ) { perror( "fprintf/vfprintf" ); return -1; } return (0 != rc) ? -1 : total; } /* write timestamp-prepended message to file */ int tmfputs( const char* s, FILE* stream ) { int n = -1, rc = 0, NO_RESET = 0; char tstamp[ 80 ] = {'\0'}; size_t ts_len = sizeof(tstamp) - 1; struct timeval tv_now; const char* pidstr = get_pidstr( NO_RESET, NULL ); (void)gettimeofday( &tv_now, NULL ); errno = 0; do { rc = mk_tvstamp( &tv_now, tstamp, &ts_len, 0 ); if( 0 != rc ) break; if( (n = fputs( tstamp, stream )) < 0 || (n = fputs( "\t", stream )) < 0 || (n = fputs( pidstr, stream )) < 0 || (n = fputs( "\t", stream )) < 0 ) break; if( (n = fputs( s, stream )) < 0 ) break; } while(0); if( n < 0 && errno ) { perror( "fputs" ); } return (0 != rc) ? -1 : n; } /* print out command-line */ void printcmdln( FILE* stream, const char* msg, int argc, char* const argv[] ) { int i = 0; assert( stream ); if( msg ) (void)tmfprintf( stream, "%s: ", msg ); else (void)tmfputs( "", stream ); for( i = 0; i < argc; ++i ) (void)fprintf( stream, "%s ", argv[i] ); (void)fputc( '\n', stream ); } /* convert timespec to time_t * where * timespec format: [+|-]dd:hh24:mi.ss * * @return 0 if success, n>0 if errno > 0, * n < 0 otherwise (see ERR_ codes) */ int a2time( const char* str, time_t* t, time_t from ) { int field[ 4 ] = {0}; const size_t field_LEN = sizeof(field)/sizeof(field[0]); int is_offset = 0; int i_sec, i_min, i_hour, i_day; int n = 0, fi = 0, i = 0, new_field = 0, has_seconds = 0; struct tm stm; time_t tgt_time = (time_t)-1, offset_tm = (time_t)0; time_t now = from; static const int ERR_HSEC = -2; static const int ERR_FIELDLEN = -3; static const int ERR_NONDIGIT = -4; assert( str ); /* check if timespec is an offset (to current time) */ n = 0; if( ('-' == str[n]) || ('+' == str[n]) ) { is_offset = ('-' == str[n]) ? -1 : 1; ++n; } /* read every field into an array element */ for( fi = 0; (size_t)fi < field_LEN; ++n ) { if( '\0' == str[n] ) break; /* seconds has to be the last field in timespec * if seconds field has already been processed * it an error */ if( (':' == str[n]) && has_seconds ) return ERR_HSEC; /* delimiter - move on to the next timespec field */ if( ':' == str[n] || ('.' == str[n]) ) { new_field = 1; if( '.' == str[n] ) has_seconds = 1; continue; } if( !isdigit( str[n] ) ) return ERR_NONDIGIT; if( new_field ) { new_field = 0; ++fi; } field[ fi ] *= 10; field[ fi ] += (str[n] - '0'); } if( (fi <= 0) || (size_t)fi >= field_LEN ) return ERR_FIELDLEN; /* last field is seconds, the one before is hours, etc. */ i = fi; i_sec = has_seconds ? i-- : -1; i_min = i--; i_hour = i--; i_day = i--; if( NULL == localtime_r( &now, &stm ) ) { return errno; } /* (void) fprintf( stderr, "sec[%d], min[%d], hour[%d], day[%d]\n", stm.tm_sec, stm.tm_min, stm.tm_hour, stm.tm_mday ); (void) fprintf( stderr, "i_sec[%d], i_min[%d], i_hour[%d], i_day[%d]\n", i_sec, i_min, i_hour, i_day ); */ if( !is_offset ) { /* hours and days default to current value */ if( i_hour >= 0 ) stm.tm_hour = field[ i_hour ]; if( i_day >= 0 ) stm.tm_mday = field[ i_day ]; stm.tm_sec = (i_sec < 0) ? 0 : field[ i_sec ]; stm.tm_min = (i_min < 0) ? 0 : field[ i_min ]; /* (void) fprintf( stderr, "sec[%d], min[%d], hour[%d], day[%d]\n", stm.tm_sec, stm.tm_min, stm.tm_hour, stm.tm_mday ); */ tgt_time = mktime( &stm ); } else { /* (void) fprintf( stderr, "sec[%d], min[%d], hour[%d], day[%d]\n", (i_sec < 0 ? 0 : field[i_sec]), (i_min < 0 ? 0 : field[i_min]), (i_hour < 0 ? 0 : field[i_hour]),(i_day < 0 ? 0 : field[i_day]) ); */ offset_tm = ( (i_sec < 0 ? 0 : field[ i_sec ]) + 60 * (i_min < 0 ? 0 : field[ i_min ]) + 3600 * (i_hour < 0 ? 0 : field[ i_hour ]) + (3600*24) * (i_day < 0 ? 0 : field[ i_day ]) ); tgt_time = now + is_offset * offset_tm; } if( NULL != t ) *t = tgt_time; return ((time_t)-1 == tgt_time) ? -1 : 0; } /* convert ASCII size spec (positive) into numeric value * size spec format: * num[modifier], where num is an ASCII representation of * any positive integer and * modifier ::= [Kb|K|Mb|M|Gb|G] */ static int a2double( const char* str, double* pval ) { int64_t mult = 1; const char* p = NULL; double dval = 0.0; size_t numsz = 0; static const int ERR_OVERRUN = -3; static const int ERR_NUMFMT = -4; static const int ERR_BADMULT = -5; #define MAX_SZLEN 64 char buf[ MAX_SZLEN ] = {0}; assert( str ); /* skip to the first */ for( p = str; *p && !isalpha(*p); ++p ); if( '\0' != *p ) { /* there is a modifier, calculate multiplication * factor */ if( 0 == strncasecmp( p, "Kb", MAX_SZLEN ) || 0 == strncasecmp( p, "K", MAX_SZLEN )) mult = 1024; else if( 0 == strncasecmp( p, "Mb", MAX_SZLEN ) || 0 == strncasecmp( p, "M", MAX_SZLEN ) ) mult = (1024 * 1024); else if( 0 == strncasecmp( p, "Gb", MAX_SZLEN ) || 0 == strncasecmp( p, "G", MAX_SZLEN ) ) mult = (1024 * 1024 * 1024); else return ERR_BADMULT; numsz = p - str; if( numsz >= (size_t)MAX_SZLEN ) return ERR_OVERRUN; (void) memcpy( buf, str, numsz ); /*(void)fprintf( stderr, "buf=[%s]\n", buf);*/ errno = 0; dval = strtod( buf, (char**)NULL ); if( errno ) return ERR_NUMFMT; /* apply the modifier-induced multiplication factor */ dval *= mult; } else { errno = 0; dval = strtod( str, (char**)NULL ); if( errno ) return ERR_NUMFMT; } /*fprintf( stderr, "dval = [%f]\n", dval );*/ if( NULL != pval ) *pval = dval; return 0; } int a2size( const char* str, ssize_t* pval ) { double dval = 0.0; int rc = 0; static const int ERR_OVFLW = -2; if( 0 != (rc = a2double( str, &dval )) ) return rc; if( dval > LONG_MAX || dval < LONG_MIN ) return ERR_OVFLW; if( NULL != pval ) { *pval = (ssize_t)dval; } return rc; } int a2int64( const char* str, int64_t* pval ) { /* NB: use LLONG_MAX and LLONG_MIN recommended when * compiling with C99 compliance; * * we still (yet) complie under C89, so LLONGMAX64, * LLONGMIN64 are introduced instead to avoid C99-related * warnings */ # define LLONGMAX64 9223372036854775807.0 # define LLONGMIN64 (-LLONGMAX64 - 1.0) double dval = 0.0; int rc = 0; static const int ERR_OVFLW = -2; if( 0 != (rc = a2double( str, &dval )) ) return rc; if( dval > LLONGMAX64 || dval < LLONGMIN64 ) return ERR_OVFLW; if( NULL != pval ) { *pval = (int64_t)dval; } return rc; } /* returns asctime w/o CR character at the end */ const char* Zasctime( const struct tm* tm ) { size_t n = 0; #define ASCBUF_SZ 64 static char buf[ ASCBUF_SZ ], *p = NULL; buf[0] = '\0'; p = asctime_r( tm, buf ); if( NULL == p ) return p; buf[ ASCBUF_SZ - 1 ] = '\0'; n = strlen( buf ); if( (n > (size_t)1) && ('\n' == buf[ n - 1 ]) ) { buf[ n - 1 ] = '\0'; } return buf; } /* adjust nice value if needed */ int set_nice( int val, FILE* log ) { int newval = 0; assert( log ); if( 0 != val ) { errno = 0; newval = nice( val ); if( (-1 == newval) && (0 != errno) ) { mperror( log, errno, "%s: nice", __func__ ); return -1; } else { TRACE( (void)tmfprintf( log, "Nice value incremented by %d\n", val) ); } } return 0; } /* check and report a deviation in values: n_was and n_is are not supposed * to differ more than by delta */ void check_fragments( const char* action, ssize_t total, ssize_t n_was, ssize_t n_is, ssize_t delta, FILE* log ) { if( NULL == action ) return; if( (n_is < (n_was - delta)) || (n_is > (n_was + delta)) ) { (void)tmfprintf( log, "%s [%ld] bytes out of [%ld], last=[%ld]\n", action, (long)n_is, (long)total, (long)n_was); } } /* retrieve UNIX time value from given environment * variable, otherwise return default */ time_t get_timeval( const char* envar, const time_t deflt ) { char *str = NULL, *eptr = NULL; time_t tval = -1; assert( envar ); if( NULL == (str = getenv( envar )) ) return deflt; errno = 0; tval = (time_t) strtol( str, &eptr, 10 ); if( errno ) return deflt; if ( eptr && (0 == *eptr) ) return tval; return deflt; } /* retrieve flag value as 1 = true, 0 = false * from an environment variable set in the form * of 0|1|'true'|'false'|'yes'|'no' */ int get_flagval( const char* envar, const int deflt ) { long lval = (long)deflt; char *str = NULL, *eptr = NULL; size_t i = 0; static char* ON_sym[] = { "true", "yes", "on" }; static char* OFF_sym[] = { "false", "no", "off" }; assert( envar ); str = getenv( envar ); if ( NULL == str ) return deflt; errno = 0; lval = strtol( str, &eptr, 10 ); if( errno ) return deflt; if ( eptr && (0 == *eptr) ) return lval; for( i = 0; i < sizeof(ON_sym)/sizeof(ON_sym[0]); ++i ) if( 0 == strcasecmp(str, ON_sym[i]) ) return 1; for( i = 0; i < sizeof(OFF_sym)/sizeof(OFF_sym[0]); ++i ) if( 0 == strcasecmp(str, OFF_sym[i]) ) return 0; return deflt; } /* retrieve SSIZE value from given environment * variable, otherwise return default */ ssize_t get_sizeval( const char* envar, const ssize_t deflt ) { char *str = NULL, *eptr = NULL; long lval = -1; assert( envar ); if( NULL == (str = getenv( envar )) ) return deflt; errno = 0; lval = strtol( str, &eptr, 10 ); if( errno ) return deflt; if ( eptr && (0 == *eptr) ) return lval; return deflt; } /* retrieve/reset string representation of pid */ const char* get_pidstr( int reset, const char* pfx ) { static char pidstr[ 24 ]; static pid_t pid = 0; if( 1 == reset || (0 == pid) ) { pid = getpid(); if (pfx) { (void) snprintf (pidstr, sizeof(pidstr)-1, "%s(%d)", pfx, pid); } else { (void) snprintf (pidstr, sizeof(pidstr)-1, "%d", pid ); } pidstr [sizeof(pidstr)-1] = '\0'; } return pidstr; } /* retrieve system info string */ const char* get_sysinfo (int* perr) { struct utsname uts; int rc = 0; if (s_sysinfo[0]) return s_sysinfo; (void) memset (&uts, 0, sizeof(uts)); errno = 0; rc = uname (&uts); if (perr) *perr = errno; if (0 == rc) { s_sysinfo [sizeof(s_sysinfo)-1] = '\0'; (void) snprintf (s_sysinfo, sizeof(s_sysinfo)-1, "%s %s %s", uts.sysname, uts.release, uts.machine); } return s_sysinfo; } void mk_app_info(const char *appname, char *info, size_t infolen) { assert(info); if ('\0' == *info) { (void) snprintf(info, infolen, "%s %s-%d.%d (%s) %s [%s]", appname, VERSION, BUILDNUM, PATCH, BUILD_TYPE, COMPILE_MODE, get_sysinfo(NULL) ); } } /* __EOF__ */