view src/util.c @ 281:5eece4dfd945 noffle

[svn] * src/log.c,src/log.h: Add Log_fatal() for reporting fatal errors and exiting, Log_gdbm_fatal() for the the same but specifically as a GDBM error reporting function, and a new log debug level AUTH for a forthcoming authentication mechanism. * src/database.c,src/group.c: Provide new gdbm error function to all gdbm opens. * src/noffle.c: Add atexit() to always close databases on a program- inspired exit. * src/content.c,src/dynamicstring.c,src/fetchlist.c,src/filter.c, src/itemlist.c,src/log.c,src/log.h,src/over.c,src/protocol.h, src/request.c,src/util.c: Use Log_fatal where appropriate.
author bears
date Fri, 27 Dec 2002 21:48:25 +0000
parents 49b452b667a6
children 4426fde0a72c
line wrap: on
line source

/*
  util.c

  $Id: util.c 413 2002-12-27 21:48:25Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include "configfile.h"
#include "log.h"
#include "portable.h"
#include "util.h"
#include "wildmat.h"

#if defined(UTIL_TEST)
#define	Log_err	printf
#endif

static const char *
nextWhiteSpace( const char *p )
{
    while ( *p && ! isspace( *p ) )
        ++p;
    return p;
}

static const char *
nextNonWhiteSpace( const char *p )
{
    while ( *p && isspace( *p ) )
        ++p;
    return p;
}

const char *
Utl_restOfLn( const char *line, unsigned int token )
{
    unsigned int i;
    const char *p;

    p = line;
    for ( i = 0; i < token; ++i )
    {
        p = nextNonWhiteSpace( p );
        p = nextWhiteSpace( p );
    }
    p = nextNonWhiteSpace( p );
    return p;
}

const char *
Utl_getLn( Str result, const char *pos )
{
    int len = 0;
    const char *p = pos;

    if ( ! p )
        return NULL;
    while ( *p != '\n' )
    {
        if ( *p == '\0' )
        {
            if ( len > 0 )
                Log_err( "Line not terminated by newline: '%s'", pos );
            return NULL;
        }
        *(result++) = *(p++);
        ++len;
        if ( len >= MAXCHAR - 1 )
        {
            *result = '\0';
            Log_err( "Utl_getLn: line too long: %s", result );
            return ++p;
        }
    }
    *result = '\0';
    return ++p;

}

const char *
Utl_ungetLn( const char *str, const char *p )
{
    if ( str == p )
        return FALSE;
    --p;
    if ( *p != '\n' )
    {
        Log_err( "Utl_ungetLn: not at beginning of line" );
        return NULL;
    }
    --p;
    while ( TRUE )
    {
        if ( p == str )
            return p;
        if ( *p == '\n' )
            return p + 1;
        --p;
    }
}

const char *
Utl_getHeaderLn( Str result, const char *p )
{
    const char * res = Utl_getLn( result, p );
    Bool not_too_long_header = TRUE;

    /* Look for followon line if this isn't a blank line. */
    if ( res != NULL && result[ 0 ] != '\0' && ! isspace( result[ 0 ] ) )
	for(;;)
	{
	    Str nextLine;
	    const char *here;

	    here = res;
	    nextLine[ 0 ] = '\0';
	    res = Utl_getLn( nextLine, res );
	    if ( res == NULL || nextLine[ 0 ] == '\0'
		 || ! isspace( nextLine[ 0 ] ) )
	    {
		res = here;
		break;
	    }
	    else
	    {
                if ( not_too_long_header &&
                     ( MAXCHAR > ( strlen( result ) + strlen( nextLine ) + 1 ) ) )
                {
                    Utl_catStr( result, "\n"   );
		    Utl_catStr( result, nextLine );
                }
                else
                {
                    Log_err( "Utl_getHeaderLn: skipped continued header: %s", nextLine );
                    not_too_long_header = FALSE;
                    /* Now let poor little noffle skip the header continuations. */
                    /* We really need to up the size limit of headers much */
                    /* higher than MAXCHAR = 2048. mliss */
                }
	    }
	}

    return res;
}

void
Utl_toLower( Str line )
{
    char *p;

    p = line;
    while ( *p )
    {
        *p = tolower( *p );
        ++p;
    }
}

char *
Utl_stripWhiteSpace( char *line )
{
    char *p;

    while ( isspace( *line ) )
        ++line;
    p = line + strlen( line ) - 1;
    while ( p >= line && isspace( *p ) )
    {
        *p = '\0';
        --p;
    }
    return line;
}

void
Utl_stripComment( char *line )
{
    for ( ; *line != '\0'; line++ )
	if ( *line =='#' )
	{
	    *line = '\0';
	    break;
	}
}

void
Utl_cpyStr( Str dst, const char *src )
{
    dst[ 0 ] = '\0';
    strncat( dst, src, MAXCHAR );
}

void
Utl_cpyStrN( Str dst, const char *src, int n )
{
    if ( n > MAXCHAR )
    	n = MAXCHAR;
    dst[ 0 ] = '\0';
    strncat( dst, src, (size_t)n );
}

void
Utl_catStr( Str dst, const char *src )
{
    strncat( dst, src, MAXCHAR - strlen( dst ) );
}

void
Utl_catStrN( Str dst, const char *src, int n )
{
    size_t un;

    ASSERT( n >= 0 );
    un = (size_t)n;
    if ( un > MAXCHAR - strlen( dst ) )
    	 un = MAXCHAR - strlen( dst );
    strncat( dst, src, un );
}

void
Utl_stamp( Str file )
{
    FILE *f;
    time_t t;
    Str tmpfname;

    snprintf( tmpfname, MAXCHAR, "%s/.#%d.stamp.update",
              Cfg_spoolDir(), (int) getpid() );
    time( &t );
    if ( ! ( f = fopen( tmpfname, "w" ) ) )
    {
        Log_err( "Could not open %s for writing (%s)",
                 tmpfname, strerror( errno ) );
        return;
    }
    fprintf( f, "%lu\n", t );
    if ( fclose( f ) != 0 )
    {
         Log_err( "Error stamping into file %s: %s",
		 tmpfname, strerror( errno ) );

    }
    else
    {
        if ( rename( tmpfname, file ) < 0 )
            Log_err( "Rename of stamp file %s to %s failed: %s",
		     tmpfname, file, strerror( errno ) );
    }
}

Bool
Utl_getStamp( time_t *result, Str file )
{
    FILE *f;

    if ( ! ( f = fopen( file, "r" ) ) )
        return FALSE;
    if ( fscanf( f, "%lu", result ) != 1 )
    {
        Log_err( "File %s corrupted", file );
        fclose( f );
        return FALSE;
    }
    fclose( f );
    return TRUE;
}

static const char *DOTW[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
			      "Sat", NULL };
static const char *MON[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
			     "Aug", "Sep", "Oct", "Nov", "Dec", NULL };

/*
 * Calculate the difference between local time and GMT at GMT time t.
 *
 * This is INN's 'always-works' method. It assumes the time differences
 * is < 24hrs. Sounds reasonable to me. It also assumes it can ignore seconds.
 * Returns localtime - GMT seconds. It will also trash the localtime/
 * gmtime/etc. static buffer.
 *
 * Do some basic caching by returning the previous result if the new request
 * is in the same hour as the previous one.
 */
static int
localTimeDiff( time_t t )
{
    struct tm local, gmt, *tm;
    static time_t resHourStart = 0;
    static int res = 0;

    if ( labs( t - resHourStart ) < 60 )
	return res;

    /* Calculating for new hour */
    resHourStart = ( t / 60 ) * 60;
    
    tm = localtime( &t );
    if ( tm == NULL )
	return 0;
    local = *tm;
    tm = gmtime( &t );
    if ( tm == NULL )
	return 0;
    gmt = *tm;

    res = local.tm_yday - gmt.tm_yday;
    if ( res < -1 )
	res = -1;		/* Year rollover? */
    else if ( res > 1 )
	res = 1;

    res *= 24;
    res += local.tm_hour - gmt.tm_hour;
    res *= 60;
    res += local.tm_min - gmt.tm_min;
    res *= 60;

    return res;
}

time_t
Utl_mktimeGMT( struct tm *t )
{
#if HAVE_TIMEGM
    return timegm( t );
#else
    /* This comment and implmentation is taken from Wget's mktime_from_utc().
     * We could 
     *
     * Converts struct tm to time_t, assuming the data in tm is UTC rather
     * than local timezone.
     *
     * mktime is similar but assumes struct tm, also known as the
     * "broken-down" form of time, is in local time zone.  mktime_from_utc
     * uses mktime to make the conversion understanding that an offset
     * will be introduced by the local time assumption.
     *
     * mktime_from_utc then measures the introduced offset by applying
     * gmtime to the initial result and applying mktime to the resulting
     * "broken-down" form.  The difference between the two mktime results
     * is the measured offset which is then subtracted from the initial
     * mktime result to yield a calendar time which is the value returned.
     *
     * tm_isdst in struct tm is set to 0 to force mktime to introduce a
     * consistent offset (the non DST offset) since tm and tm+o might be
     * on opposite sides of a DST change.
     * 
     * Some implementations of mktime return -1 for the nonexistent
     * localtime hour at the beginning of DST.  In this event, use
     * mktime(tm - 1hr) + 3600.
     * 
     * Schematically
     * mktime(tm)   --> t+o
     * gmtime(t+o)  --> tm+o
     * mktime(tm+o) --> t+2o
     * t+o - (t+2o - t+o) = t
     *
     * Note that glibc contains a function of the same purpose named
     * `timegm' (reverse of gmtime).  But obviously, it is not universally
     * available, and unfortunately it is not straightforwardly
     * extractable for use here.  Perhaps configure should detect timegm
     * and use it where available.
     * 
     * Contributed by Roger Beeman <beeman@cisco.com>, with the help of
     * Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO.
     * Further improved by Roger with assistance from Edward J. Sabol
     * based on input by Jamie Zawinski.
     */
    time_t tl, tb;
    struct tm *tg;

    tl = mktime (t);
    if (tl == -1)
    {
	t->tm_hour--;
	tl = mktime (t);
	if (tl == -1)
	    return -1; /* can't deal with output from strptime */
	tl += 3600;
    }
    tg = gmtime (&tl);
    tg->tm_isdst = 0;
    tb = mktime (tg);
    if (tb == -1)
    {
	tg->tm_hour--;
	tb = mktime (tg);
	if (tb == -1)
	    return -1; /* can't deal with output from gmtime */
	tb += 3600;
    }
    return (tl - (tb - tl));    
#endif    
}

void
Utl_newsDate( time_t t, Str res )
{    
    struct tm *local;
    long tzdiff, hoffset, moffset;

    tzdiff = localTimeDiff( t ) / 60;

    local = localtime( &t );
    if ( local == NULL )
    {
	Utl_cpyStr( res, "** localtime failure **" );
	return;
    }
    
    hoffset = tzdiff / 60;
    moffset = tzdiff % 60;
    if ( moffset < 0 )
	moffset = - moffset;

    sprintf( res, "%s, %d %s %4d %02d:%02d:%02d %+03ld%02ld",
	     DOTW[local->tm_wday], local->tm_mday,
	     MON[local->tm_mon], local->tm_year + 1900,
	     local->tm_hour, local->tm_min, local->tm_sec,
	     hoffset, moffset );
}

time_t
Utl_parseNewsDate( const char *s )
{
    struct tm tm;
    int wday, offset, tzoffset;
    char *p;
    time_t res;

    memset( &tm, 0, sizeof( tm ) );
    wday = -1;
    tm.tm_isdst = -1;
    
    s = nextNonWhiteSpace( s );

    /* Is this the day number, or a weekday? */
    if ( ! isdigit( *s ) )
    {
	if ( strlen( s ) < 4 )
	    return (time_t) -1;
	
	for ( wday = 0; DOTW[ wday ] != NULL; wday++ )
	    if ( strncasecmp( DOTW[ wday ], s, 3 ) == 0 )
		break;

	if( DOTW[ wday ] == NULL || s[3] != ',' )
	    return (time_t) -1;

	s += 4;
    }
    
    /* Get the day number */
    tm.tm_mday = (int) strtol( s, &p, 10 );
    if ( p == s )
	return (time_t) -1;
    s = p;

    /* Look for month name */
    s = nextNonWhiteSpace( s );
    if ( strlen( s ) < 4 )
	return (time_t) -1;
    for ( tm.tm_mon = 0; MON[ tm.tm_mon ] != NULL; tm.tm_mon++ )
	if ( strncasecmp( MON[ tm.tm_mon ], s, 3 ) == 0 )
	    break;

    if ( MON[ tm.tm_mon ] == NULL )
	return (time_t) -1;
    s += 3;

    /* Year next */
    tm.tm_year = (int) strtol( s, &p, 10 );
    if ( p == s || ( tm.tm_year >= 100 && tm.tm_year < 1900  ) )
	return (time_t) -1;
    if ( tm.tm_year >= 1900 )
	tm.tm_year -= 1900;
    s = p;  

    /* Hours */
    tm.tm_hour = (int) strtol( s, &p, 10 );
    if ( p == s || *p != ':' )
	return (time_t) -1;
    s = ++p;

    /* Minutes */
    tm.tm_min = (int) strtol( s, &p, 10 );
    if ( p == s || ( *p != ':' && *p != ' ' ) )
	return (time_t) -1;
    s = p;

    /* Seconds */
    if ( *s == ':' )
    {
	s++;
	tm.tm_sec = (int) strtol( s, &p, 10 );
	if ( p == s )
	    return (time_t) -1;
	s = p;
    }

    /* GMT/UT or timezone offset */
    tzoffset = 0;
    s = nextNonWhiteSpace( s );
    if ( strncasecmp( s, "GMT", 3) == 0 )
	s += 3;
    else if ( strncasecmp( s, "UT", 2 ) == 0 )
	s += 2;
    else
    {
	offset = (int) strtol( s, &p, 10 );
	if ( p == s )
	    return (time_t) -1;
	s = p;
	tzoffset = ( offset / 100 ) * 60 + ( offset % 100 );
    }

    /* Check for following junk */
    if ( *s != '\0' && ! isspace( *s ) )
	return (time_t) -1;

    res = Utl_mktimeGMT( &tm );
    if ( res == (time_t) -1 )
	return res;
    
    if ( wday >= 0 && wday != tm.tm_wday )
	return (time_t) -1;

    /* And now adjust for tzoffset */
    res -= tzoffset * 60;
    
    return res;
}

void
Utl_allocAndCpy( char **dst, const char *src )
{
    size_t len = strlen( src );
    if ( ! ( *dst = malloc( len + 1 ) ) )
        Log_fatal( "Cannot allocate string with length %lu", len );
    memcpy( *dst, src, len + 1 );
}

SignalHandler
Utl_installSignalHandler( int sig, SignalHandler handler )
{
    struct sigaction act, oldAct;

    act.sa_handler = handler;
    sigemptyset( &act.sa_mask );
    act.sa_flags = 0;
    if ( sig != SIGALRM )
        act.sa_flags |= SA_RESTART;
    if ( sigaction( sig, &act, &oldAct ) < 0 )
        return SIG_ERR;
    return oldAct.sa_handler;
}

static Bool
getHostFQDN( Str result )
{
    struct hostent *myHostEnt;
    struct utsname myName;
    
    if ( uname( &myName ) >= 0
         && ( myHostEnt = gethostbyname( myName.nodename ) ) )
    {
        Utl_cpyStr( result, myHostEnt->h_name );
        return TRUE;
    }
    return FALSE;
}

Bool
Utl_getFQDN( Str result )
{
    /* get hostname from Cfg-File */
    Utl_cpyStr( result, Cfg_hostnameMsgId() );
    if ( strlen( result ) != 0 )
	return TRUE;
    return getHostFQDN( result );
}

#if defined(UTIL_TEST)

/* Test code borrowed from wildmat.c. Yep, still uses gets(). */
extern char	*gets();

int
main()
{
    Str line;
    time_t t;

    printf( "Util date tester.  Enter date to test.\n" );    
    printf( "A blank line exits the program.\n" );

    for ( ; ; )
    {
	t = time( NULL );
	Utl_newsDate( t, line );
	printf( "\n(%s) Enter date:  ", line );
	(void) fflush( stdout );
	if ( gets( line ) == NULL || line[0] == '\0' )
	    break;

	t = Utl_parseNewsDate( line );
	if ( t == (time_t) -1 )
	    printf( "Date parse failed\n" );
	else
	{
	    Utl_newsDate( t, line );
	    printf( "Utl_newsDate -> '%s'\n", line );
	}
    }

    exit(0);
    /* NOTREACHED */
}
#endif