Mercurial > noffle
view noffle.c @ 12:43631b72021f noffle
[svn] Fixed bug: multiple line headers of posted articles were truncated
author | enz |
---|---|
date | Sat, 15 Apr 2000 11:09:20 +0100 |
parents | 6deb8bb73f2a |
children | 526a4c34ee2e |
line wrap: on
line source
/* noffle.c Main program. Implements specified actions, but running as server, which is done by Serv_run(), declared in server.h. Locking policy: lock access to databases while noffle is running, but not as server. If noffle runs as server, locking is performed while executing NNTP commands, but temporarily released if no new command is received for some seconds (to allow multiple clients connect at the same time). $Id: noffle.c 15 2000-04-11 06:36:57Z enz $ */ #include <errno.h> #include <getopt.h> #include <signal.h> #include <sys/resource.h> #include <syslog.h> #include <unistd.h> #include "client.h" #include "common.h" #include "content.h" #include "config.h" #include "database.h" #include "fetch.h" #include "fetchlist.h" #include "group.h" #include "log.h" #include "online.h" #include "over.h" #include "pseudo.h" #include "util.h" #include "server.h" #include "request.h" #include "lock.h" struct Noffle { Bool queryGrps; Bool queryDsc; Bool queryTimes; Bool interactive; } noffle = { FALSE, FALSE, FALSE, TRUE }; static void doArt( const char *msgId ) { const char *id; if ( strcmp( msgId, "all" ) == 0 ) { if ( ! Db_first( &id ) ) fprintf( stderr, "Database empty.\n" ); else do { printf( "%s\n%s" "========================================" "======================================\n", Db_header( id ), Db_body( id ) ); } while ( Db_next( &id ) ); } else { if ( ! Db_contains( msgId ) ) fprintf( stderr, "Not in database.\n" ); else printf( "%s\n%s", Db_header( msgId ), Db_body( msgId ) ); } } /* List articles requested from one particular server */ static void listRequested1( const char* serv ) { Str msgid; if ( ! Req_first( serv, msgid ) ) return; do printf( "%s %s\n", msgid, serv ); while ( Req_next( msgid ) ); } /* List requested articles. List for all servers if serv = "all" or serv = NULL. */ void doRequested( const char *arg ) { Str serv; if ( ! arg || ! strcmp( arg, "all" ) ) { Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) listRequested1( serv ); } else listRequested1( arg ); } static void doDb( void ) { const char *msgId; if ( ! Db_first( &msgId ) ) fprintf( stderr, "Database empty.\n" ); else do printf( "%s\n", msgId ); while ( Db_next( &msgId ) ); } static void doFetch( void ) { Str serv; Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( Fetch_init( serv ) ) { Fetch_postArts(); Fetch_getNewGrps(); /* Get overviews of new articles and store IDs of new articles that are to be fetched becase of FULL or THREAD mode in the request database. */ Fetch_updateGrps(); /* get requested articles */ Fetch_getReq_(); Fetch_close(); } } static void doQuery( void ) { Str serv; Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( Fetch_init( serv ) ) { if ( noffle.queryGrps ) Client_getGrps(); if ( noffle.queryDsc ) Client_getDsc(); if ( noffle.queryTimes ) Client_getCreationTimes(); Fetch_close(); } } /* Expire all overviews not in database */ static void expireContents( void ) { const Over *ov; int i; int cntDel, cntLeft; Str grp; Bool autoUnsubscribe; int autoUnsubscribeDays; time_t now = time( NULL ), maxAge = 0; const char *msgId; autoUnsubscribe = Cfg_autoUnsubscribe(); autoUnsubscribeDays = Cfg_autoUnsubscribeDays(); maxAge = Cfg_autoUnsubscribeDays() * 24 * 3600; if ( ! Cont_firstGrp( grp ) ) return; Log_inf( "Expiring overviews not in database" ); do { if ( ! Grp_exists( grp ) ) Log_err( "Overview file for unknown group %s exists", grp ); else { cntDel = cntLeft = 0; Cont_read( grp ); for ( i = Cont_first(); i <= Cont_last(); ++i ) if ( ( ov = Cont_get( i ) ) ) { msgId = Ov_msgId( ov ); if ( ! Db_contains( msgId ) ) { Cont_delete( i ); ++cntDel; } else ++cntLeft; } if ( autoUnsubscribe && difftime( now, Grp_lastAccess( grp ) ) > maxAge ) { Log_ntc( "Auto-unsubscribing from %s after %d " "days without access", grp, autoUnsubscribeDays ); Pseudo_autoUnsubscribed( grp, autoUnsubscribeDays ); Fetchlist_read(); Fetchlist_remove( grp ); Fetchlist_write(); } Cont_write(); Grp_setFirstLast( grp, Cont_first(), Cont_last() ); Log_inf( "%ld overviews deleted from group %s, %ld left (%ld-%ld)", cntDel, grp, cntLeft, Grp_first( grp ), Grp_last( grp ) ); } } while ( Cont_nextGrp( grp ) ); } static void doExpire( unsigned int days ) { Db_close(); Db_expire( days ); if ( ! Db_open() ) return; expireContents(); } static void doList( void ) { FetchMode mode; int i, size; const char *name, *modeStr = ""; Fetchlist_read(); size = Fetchlist_size(); if ( size == 0 ) fprintf( stderr, "Fetch list is empty.\n" ); else for ( i = 0; i < size; ++i ) { Fetchlist_element( &name, &mode, i ); switch ( mode ) { case FULL: modeStr = "full"; break; case THREAD: modeStr = "thread"; break; case OVER: modeStr = "over"; break; } printf( "%s %s %s\n", name, Grp_serv( name ), modeStr ); } } static void doGrps( void ) { const char *g; Str dateLastAccess, dateCreated; time_t lastAccess, created; if ( Grp_firstGrp( &g ) ) do { lastAccess = Grp_lastAccess( g ); created = Grp_created( g ); ASSERT( lastAccess >= 0 ); ASSERT( created >= 0 ); strftime( dateLastAccess, MAXCHAR, "%Y-%m-%d %H:%M:%S", localtime( &lastAccess ) ); strftime( dateCreated, MAXCHAR, "%Y-%m-%d %H:%M:%S", localtime( &created ) ); printf( "%s\t%s\t%i\t%i\t%i\t%s\t%s\t%s\n", g, Grp_serv( g ), Grp_first( g ), Grp_last( g ), Grp_rmtNext( g ), dateCreated, dateLastAccess, Grp_dsc( g ) ); } while ( Grp_nextGrp( &g ) ); } static Bool doSubscribe( const char *name, FetchMode mode ) { if ( ! Grp_exists( name ) ) { fprintf( stderr, "%s is not available at remote servers.\n", name ); return FALSE; } Fetchlist_read(); if ( Fetchlist_add( name, mode ) ) printf( "Adding %s to fetch list in %s mode.\n", name, mode == FULL ? "full" : mode == THREAD ? "thread" : "overview" ); else printf( "%s is already in fetch list. Mode is now: %s.\n", name, mode == FULL ? "full" : mode == THREAD ? "thread" : "overview" ); if ( ! Fetchlist_write() ) fprintf( stderr, "Could not save fetchlist.\n" ); return TRUE; } static void doUnsubscribe( const char *name ) { Fetchlist_read(); if ( ! Fetchlist_remove( name ) ) printf( "%s is not in fetch list.\n", name ); else printf( "%s removed from fetch list.\n", name ); if ( ! Fetchlist_write() ) fprintf( stderr, "Could not save fetchlist.\n" ); } static void printUsage( void ) { static const char *msg = "Usage: noffle <option>\n" "Option is one of the following:\n" " -a | --article <msg id>|all Show article(s) in database\n" " -d | --database Show content of article database\n" " -e | --expire <n> Expire articles older than <n> days\n" " -f | --fetch Get newsfeed from server/post articles\n" " -g | --groups Show all groups available at server\n" " -h | --help Show this text\n" " -l | --list List groups on fetch list\n" " -n | --online Switch to online mode\n" " -o | --offline Switch to offline mode\n" " -q | --query groups Get group list from server\n" " -q | --query desc Get group descriptions from server\n" " -q | --query times Get group creation times from server\n" " -r | --server Run as server on stdin/stdout\n" " -R | --requested List articles marked for download\n" " -s | --subscribe-over <grp> Add group to fetch list (overview)\n" " -S | --subscribe-full <grp> Add group to fetch list (full)\n" " -t | --subscribe-thread <grp> Add group to fetch list (thread)\n" " -u | --unsubscribe <grp> Remove group from fetch list\n" " -v | --version Print version\n"; fprintf( stderr, "%s", msg ); } /* Allow core files: Change core limit and change working directory to spool directory, where news has write permissions. */ static void enableCorefiles() { struct rlimit lim; if ( getrlimit( RLIMIT_CORE, &lim ) != 0 ) { Log_err( "Cannot get system core limit: %s", strerror( errno ) ); return; } lim.rlim_cur = lim.rlim_max; if ( setrlimit( RLIMIT_CORE, &lim ) != 0 ) { Log_err( "Cannot set system core limit: %s", strerror( errno ) ); return; } Log_dbg( "Core limit set to %i", lim.rlim_max ); if ( chdir( Cfg_spoolDir() ) != 0 ) { Log_err( "Cannot change to directory '%s'", Cfg_spoolDir() ); return; } Log_dbg( "Changed to directory '%s'", Cfg_spoolDir() ); } static Bool initNoffle( Bool interactive ) { Log_init( "noffle", interactive, LOG_NEWS ); Cfg_read(); Log_dbg( "NOFFLE version %s", Cfg_version() ); noffle.interactive = interactive; if ( interactive ) if ( ! Lock_openDatabases() ) return FALSE; if ( ! interactive ) enableCorefiles(); return TRUE; } static void closeNoffle( void ) { if ( noffle.interactive ) Lock_closeDatabases(); } static void bugReport( int sig ) { Log_err( "Received SIGSEGV. Please submit a bug report" ); signal( SIGSEGV, SIG_DFL ); raise( sig ); } static void logSignal( int sig ) { const char *name; Bool err = TRUE; switch ( sig ) { case SIGABRT: name = "SIGABRT"; break; case SIGFPE: name = "SIGFPE"; break; case SIGILL: name = "SIGILL"; break; case SIGINT: name = "SIGINT"; break; case SIGTERM: name = "SIGTERM"; break; case SIGPIPE: name = "SIGPIPE"; err = FALSE; break; default: name = "?"; break; } if ( err ) Log_err( "Received signal %i (%s). Aborting.", sig, name ); else Log_inf( "Received signal %i (%s). Aborting.", sig, name ); signal( sig, SIG_DFL ); raise( sig ); } int main ( int argc, char **argv ) { int c, result; struct option longOptions[] = { { "article", required_argument, NULL, 'a' }, { "database", no_argument, NULL, 'd' }, { "expire", required_argument, NULL, 'e' }, { "fetch", no_argument, NULL, 'f' }, { "groups", no_argument, NULL, 'g' }, { "help", no_argument, NULL, 'h' }, { "list", no_argument, NULL, 'l' }, { "offline", no_argument, NULL, 'o' }, { "online", no_argument, NULL, 'n' }, { "query", required_argument, NULL, 'q' }, { "server", no_argument, NULL, 'r' }, { "requested", no_argument, NULL, 'R' }, { "subscribe-over", required_argument, NULL, 's' }, { "subscribe-full", required_argument, NULL, 'S' }, { "subscribe-thread", required_argument, NULL, 't' }, { "unsubscribe", required_argument, NULL, 'u' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; signal( SIGSEGV, bugReport ); signal( SIGABRT, logSignal ); signal( SIGFPE, logSignal ); signal( SIGILL, logSignal ); signal( SIGINT, logSignal ); signal( SIGTERM, logSignal ); signal( SIGPIPE, logSignal ); c = getopt_long( argc, argv, "a:de:fghlonq:rRs:S:t:u:v", longOptions, NULL ); if ( ! initNoffle( c != 'r' ) ) return EXIT_FAILURE; result = EXIT_SUCCESS; switch ( c ) { case 0: /* Options that set a flag. */ break; case 'a': if ( ! optarg ) { fprintf( stderr, "Option -a needs argument.\n" ); result = EXIT_FAILURE; } else doArt( optarg ); break; case 'd': doDb(); break; case 'e': { unsigned int days; if ( ! optarg || sscanf( optarg, "%u", &days ) != 1 ) { fprintf( stderr, "Bad argument: -e %s\n", optarg ); result = EXIT_FAILURE; } else doExpire( days ); } break; case 'f': doFetch(); break; case 'g': doGrps(); break; case -1: case 'h': printUsage(); break; case 'l': doList(); break; case 'n': if ( Online_true() ) fprintf( stderr, "NOFFLE is already online\n" ); else Online_set( TRUE ); break; case 'o': if ( ! Online_true() ) fprintf( stderr, "NOFFLE is already offline\n" ); else Online_set( FALSE ); break; case 'q': if ( ! optarg ) { fprintf( stderr, "Option -q needs argument.\n" ); result = EXIT_FAILURE; } else { if ( strcmp( optarg, "groups" ) == 0 ) noffle.queryGrps = TRUE; else if ( strcmp( optarg, "desc" ) == 0 ) noffle.queryDsc = TRUE; else if ( strcmp( optarg, "times" ) == 0 ) noffle.queryTimes = TRUE; else { fprintf( stderr, "Unknown argument -q %s\n", optarg ); result = EXIT_FAILURE; } doQuery(); } break; case 'r': Log_inf( "Starting as server" ); Serv_run(); break; case 'R': doRequested( optarg ); break; case 's': if ( ! optarg ) { fprintf( stderr, "Option -s needs argument.\n" ); result = EXIT_FAILURE; } else result = doSubscribe( optarg, OVER ); break; case 'S': if ( ! optarg ) { fprintf( stderr, "Option -S needs argument.\n" ); result = EXIT_FAILURE; } else doSubscribe( optarg, FULL ); break; case 't': if ( ! optarg ) { fprintf( stderr, "Option -t needs argument.\n" ); result = EXIT_FAILURE; } else result = doSubscribe( optarg, THREAD ); break; case 'u': if ( ! optarg ) { fprintf( stderr, "Option -u needs argument.\n" ); result = EXIT_FAILURE; } else doUnsubscribe( optarg ); break; case '?': /* Error message already printed by getopt_long */ result = EXIT_FAILURE; break; case 'v': printf( "NNTP server NOFFLE, version %s.\n", Cfg_version() ); break; default: abort(); /* Never reached */ } closeNoffle(); return result; }