Mercurial > noffle
diff src/server.c @ 127:3c71e28c8eef noffle
[svn] Release-1-0 mergedocs/NOTES
author | bears |
---|---|
date | Tue, 25 Jul 2000 13:14:54 +0100 |
parents | f50cc311e29a |
children | 8b9366fc1361 |
line wrap: on
line diff
--- a/src/server.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/server.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* server.c - $Id: server.c 167 2000-06-30 07:10:45Z enz $ + $Id: server.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -52,9 +52,11 @@ struct { Bool running; + time_t lastServerOpen; int artPtr; Str grp; /* selected group, "" if none */ -} server = { FALSE, 0, "" }; + Bool readAlarmFlag; +} server = { FALSE, 0L, 0, "", FALSE }; typedef struct Cmd { @@ -87,6 +89,9 @@ static Bool notImplemented( char *arg, const Cmd *cmd ); static void putStat( unsigned int stat, const char *fmt, ... ); +static void closeServer( void ); +static Bool initServer( void ); + Cmd commands[] = { { "article", "ARTICLE [msg-id|n]", &doArt }, @@ -107,8 +112,8 @@ { "quit", "QUIT", &doQuit }, { "slave", "SLAVE (ignored)", &doSlave }, { "stat", "STAT [msg-id|n]", &doStat }, - { "xhdr", "XHDR over-field [m[-[n]]]", &doXhdr }, - { "xpat", "XPAT over-field m[-[n]] pat", &doXpat }, + { "xhdr", "XHDR over-field [msg-id|m[-[n]]]", &doXhdr }, + { "xpat", "XPAT over-field msg-id|m[-[n]] pat", &doXpat }, { "xover", "XOVER [m[-[n]]]", &doXOver } }; @@ -202,10 +207,66 @@ putStat( STAT_SYNTAX_ERR, "Syntax error. Usage: %s", cmd->syntax ); } -static Bool -getLn( Str line ) +static void +readAlarm( int sig ) +{ + UNUSED( sig ); + + server.readAlarmFlag = TRUE; + return; +} + +static sig_t +installSignalHandler( int sig, sig_t handler ) { - return Prt_getLn( line, stdin ); + 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; +} + +/* + * Returns: < 0 on error, 0 on timeout, > 0 on success. + * If timeout is zero, wait indefinitely. + */ +static int +waitCmdLn( Str line, int timeoutSeconds ) +{ + sig_t oldHandler; + int r; + + ASSERT( timeoutSeconds >= 0 ); + + /* Special case - no timeout. */ + if ( timeoutSeconds == 0 ) + { + r = Prt_getLn( line, stdin ); + return ( r ) ? 1 : -1; + } + + server.readAlarmFlag = FALSE; + oldHandler = installSignalHandler( SIGALRM, readAlarm ); + if ( oldHandler == SIG_ERR ) + { + Log_err( "client.c:connectWithTimeout: signal failed." ); + return -1; + } + if ( alarm( ( unsigned int ) timeoutSeconds ) != 0 ) + Log_err( "server.c:waitCmdLn: Alarm was already set." ); + r = Prt_getLn( line, stdin ); + alarm( 0 ); + installSignalHandler( SIGALRM, oldHandler ); + if ( server.readAlarmFlag ) + return 0; + else if ( r ) + return 1; + return -1; } static Bool @@ -636,9 +697,6 @@ { Str line; const char *g; - FILE *f; - sig_t lastHandler; - int ret; putStat( STAT_GRPS_FOLLOW, "Groups" ); fflush( stdout ); @@ -651,42 +709,15 @@ } else { - lastHandler = signal( SIGPIPE, SIG_IGN ); - f = popen( "sort", "w" ); - if ( f == NULL ) - { - Log_err( "Cannot open pipe to 'sort'" ); - if ( Grp_firstGrp( &g ) ) - do - if ( Wld_match( g, pat ) ) - { - (*printProc)( line, g ); - if ( ! Prt_putTxtLn( line, stdout ) ) - Log_err( "Writing to stdout failed." ); - } - while ( Grp_nextGrp( &g ) ); - } - else - { - if ( Grp_firstGrp( &g ) ) - do - if ( Wld_match( g, pat ) ) - { - (*printProc)( line, g ); - if ( ! Prt_putTxtLn( line, f ) ) - { - Log_err( "Writing to 'sort' pipe failed." ); - break; - } - } - while ( Grp_nextGrp( &g ) ); - ret = pclose( f ); - if ( ret != EXIT_SUCCESS ) - Log_err( "sort command returned %d", ret ); - fflush( stdout ); - Log_dbg( "[S FLUSH]" ); - signal( SIGPIPE, lastHandler ); - } + if ( Grp_firstGrp( &g ) ) + do + if ( Wld_match( g, pat ) ) + { + (*printProc)( line, g ); + if ( ! Prt_putTxtLn( line, stdout ) ) + Log_err( "Writing to stdout failed." ); + } + while ( Grp_nextGrp( &g ) ); } putEndOfTxt(); } @@ -813,6 +844,14 @@ else { changeToGrp( arg ); + + /* + * The output may take some time, so release the lock + * while outputting (all the required data is in RAM + * at this point). + */ + closeServer(); + first = Cont_first(); last = Cont_last(); putStat( STAT_GRP_SELECTED, "Article list" ); @@ -980,10 +1019,24 @@ fflush( stdout ); Log_dbg( "[S FLUSH]" ); s = new_DynStr( 10000 ); + + + /* + * The article may take some time coming in, so release the + * lock while collecting it. + */ + closeServer(); + err = FALSE; while ( ! err && getTxtLn( line, &err ) ) DynStr_appLn( s, line ); + if ( ! initServer() ) + { + del_DynStr( s ); + return FALSE; + } + if ( ! err && Post_open( DynStr_str( s ) ) && Post_post() ) @@ -1033,46 +1086,104 @@ ++(*numb); } +enum XhdrType { SUBJ, FROM, DATE, MSG_ID, REF, BYTES, LINES, XREF, UNKNOWN }; + +static enum XhdrType +whatXhdrField( const char * fieldname ) +{ + Str name; + + Utl_cpyStr( name, fieldname ); + Utl_toLower( name ); + if ( strcmp( name, "subject" ) == 0 ) + return SUBJ; + else if ( strcmp( name, "from" ) == 0 ) + return FROM; + else if ( strcmp( name, "date" ) == 0 ) + return DATE; + else if ( strcmp( name, "message-id" ) == 0 ) + return MSG_ID; + else if ( strcmp( name, "references" ) == 0 ) + return REF; + else if ( strcmp( name, "bytes" ) == 0 ) + return BYTES; + else if ( strcmp( name, "lines" ) == 0 ) + return LINES; + else if ( strcmp( name, "xref" ) == 0 ) + return XREF; + else + return UNKNOWN; +} + +static void +getXhdrField( enum XhdrType what, const Over * ov, Str res ) +{ + const char * msgId; + Str host; + + switch ( what ) + { + case SUBJ: + Utl_cpyStr( res, Ov_subj( ov ) ); + break; + case FROM: + Utl_cpyStr( res, Ov_from( ov ) ); + break; + case DATE: + Utl_cpyStr( res, Ov_date( ov ) ); + break; + case MSG_ID: + Utl_cpyStr( res, Ov_msgId( ov ) ); + break; + case REF: + Utl_cpyStr( res, Ov_ref( ov ) ); + break; + case BYTES: + snprintf( res, MAXCHAR, "%d", Ov_bytes( ov ) ); + break; + case LINES: + snprintf( res, MAXCHAR, "%d", Ov_lines( ov ) ); + break; + case XREF: + msgId = Ov_msgId( ov ); + /* + * Gen info messages don't have an Xref header. When INN is asked + * for a header that doesn't exist in a message, it reports the + * header value as '(none)', so do the same. + */ + if ( Pseudo_isGeneralInfo( msgId ) ) + Utl_cpyStr( res, "none" ); + else + { + gethostname( host, MAXCHAR ); + snprintf( res, MAXCHAR, "%s %s", host, Db_xref( msgId ) ); + } + break; + default: + ASSERT( FALSE ); + } +} + /* Note this only handles a subset of headers. But they are all the headers any newsreader should need to work properly. - + That last sentence will at some stage be proved wrong. */ static Bool doXhdr( char *arg, const Cmd *cmd ) { - int first, last, i, n, numb; - enum { SUBJ, FROM, DATE, MSG_ID, REF, BYTES, LINES, XREF } what; - const char *p, *msgId; - const Over *ov; + enum XhdrType what; + const char *p; Str whatStr; - if ( ! testGrpSelected() ) - return TRUE; if ( sscanf( arg, "%s", whatStr ) != 1 ) { putSyntax( cmd ); return TRUE; } - Utl_toLower( whatStr ); - if ( strcmp( whatStr, "subject" ) == 0 ) - what = SUBJ; - else if ( strcmp( whatStr, "from" ) == 0 ) - what = FROM; - else if ( strcmp( whatStr, "date" ) == 0 ) - what = DATE; - else if ( strcmp( whatStr, "message-id" ) == 0 ) - what = MSG_ID; - else if ( strcmp( whatStr, "references" ) == 0 ) - what = REF; - else if ( strcmp( whatStr, "bytes" ) == 0 ) - what = BYTES; - else if ( strcmp( whatStr, "lines" ) == 0 ) - what = LINES; - else if ( strcmp( whatStr, "xref" ) == 0 ) - what = XREF; - else + what = whatXhdrField( whatStr ); + if ( what == UNKNOWN ) { putStat( STAT_HEAD_FOLLOWS, "Unknown header (empty list follows)" ); putEndOfTxt(); @@ -1081,131 +1192,112 @@ p = Utl_restOfLn( arg, 1 ); if ( p[ 0 ] == '<' ) { - first = last = Cont_find( p ); - if ( first < 0 ) - numb = 0; + Over * ov; + Str field; + + /* Argument is message ID */ + ov = Db_over( p ); + if ( ov == NULL ) + { + putStat( STAT_NO_SUCH_ID, "No such article" ); + return TRUE; + } + putStat( STAT_HEAD_FOLLOWS, "%s header %s", whatStr, p ) ; + getXhdrField( what, ov, field ); + putTxtLn( "%s %s", p, field ); + del_Over( ov ); } else + { + const Over * ov; + int first, last, i, n, numb; + Str field; + + /* Argument is article no. or range */ + if ( ! testGrpSelected() ) + return TRUE; parseRange( p, &first, &last, &numb ); - if ( numb == 0 ) - putStat( STAT_NO_ART_SELECTED, "No articles selected" ); - else - { + if ( numb == 0 ) + { + putStat( STAT_NO_ART_SELECTED, "No articles selected" ); + return TRUE; + } putStat( STAT_HEAD_FOLLOWS, "%s header %lu-%lu", - whatStr, first, last ) ; + whatStr, first, last ) ; for ( i = first; i <= last; ++i ) if ( ( ov = Cont_get( i ) ) ) { n = Ov_numb( ov ); - switch ( what ) - { - case SUBJ: - putTxtLn( "%lu %s", n, Ov_subj( ov ) ); - break; - case FROM: - putTxtLn( "%lu %s", n, Ov_from( ov ) ); - break; - case DATE: - putTxtLn( "%lu %s", n, Ov_date( ov ) ); - break; - case MSG_ID: - putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); - break; - case REF: - putTxtLn( "%lu %s", n, Ov_ref( ov ) ); - break; - case BYTES: - putTxtLn( "%lu %d", n, Ov_bytes( ov ) ); - break; - case LINES: - putTxtLn( "%lu %d", n, Ov_lines( ov ) ); - break; - case XREF: - msgId = Ov_msgId( ov ); - if ( Pseudo_isGeneralInfo( msgId ) ) - putTxtLn( "%lu %s:%lu", n, server.grp, n ); - else - putTxtLn( "%lu %s", n, Db_xref( msgId ) ); - break; - default: - ASSERT( FALSE ); - } + getXhdrField( what, ov, field ); + putTxtLn( "%lu %s", n, field ); } - putEndOfTxt(); } + putEndOfTxt(); return TRUE; } static Bool doXpat( char *arg, const Cmd *cmd ) { - int first, last, i, n; - enum { SUBJ, FROM, DATE, MSG_ID, REF } what; - const Over *ov; - Str whatStr, pat; + enum XhdrType what; + Str whatStr, articles, pat; - if ( ! testGrpSelected() ) - return TRUE; - if ( sscanf( arg, "%s %d-%d %s", whatStr, &first, &last, pat ) != 4 ) + if ( sscanf( arg, "%s %s %s", whatStr, articles, pat ) != 3 ) { - if ( sscanf( arg, "%s %d- %s", whatStr, &first, pat ) == 3 ) - last = Cont_last(); - else if ( sscanf( arg, "%s %d %s", whatStr, &first, pat ) == 3 ) - last = first; - else - { - putSyntax( cmd ); - return TRUE; - } + putSyntax( cmd ); + return TRUE; } - Utl_toLower( whatStr ); - if ( strcmp( whatStr, "subject" ) == 0 ) - what = SUBJ; - else if ( strcmp( whatStr, "from" ) == 0 ) - what = FROM; - else if ( strcmp( whatStr, "date" ) == 0 ) - what = DATE; - else if ( strcmp( whatStr, "message-id" ) == 0 ) - what = MSG_ID; - else if ( strcmp( whatStr, "references" ) == 0 ) - what = REF; - else + what = whatXhdrField( whatStr ); + if ( what == UNKNOWN ) { - putStat( STAT_HEAD_FOLLOWS, "invalid header (empty list follows)" ); + putStat( STAT_HEAD_FOLLOWS, "Unknown header (empty list follows)" ); putEndOfTxt(); return TRUE; } - putStat( STAT_HEAD_FOLLOWS, "header" ) ; - for ( i = first; i <= last; ++i ) - if ( ( ov = Cont_get( i ) ) ) - { - n = Ov_numb( ov ); - switch ( what ) + if ( articles[ 0 ] == '<' ) + { + Over * ov; + Str field; + + /* Argument is message ID */ + ov = Db_over( articles ); + if ( ov == NULL ) + { + putStat( STAT_NO_SUCH_ID, "No such article" ); + return TRUE; + } + putStat( STAT_HEAD_FOLLOWS, "%s header %s", whatStr, articles ) ; + getXhdrField( what, ov, field ); + if ( Wld_match( field, pat ) ) + putTxtLn( "%s %s", articles, field ); + del_Over( ov ); + } + else + { + const Over * ov; + Str field; + int first, last, i, n, numb; + + /* Argument is article no. or range */ + if ( ! testGrpSelected() ) + return TRUE; + parseRange( articles, &first, &last, &numb ); + if ( numb == 0 ) + { + putStat( STAT_NO_ART_SELECTED, "No articles selected" ); + return TRUE; + } + putStat( STAT_HEAD_FOLLOWS, "%s header %lu-%lu", + whatStr, first, last ) ; + for ( i = first; i <= last; ++i ) + if ( ( ov = Cont_get( i ) ) ) { - case SUBJ: - if ( Wld_match( Ov_subj( ov ), pat ) ) - putTxtLn( "%lu %s", n, Ov_subj( ov ) ); - break; - case FROM: - if ( Wld_match( Ov_from( ov ), pat ) ) - putTxtLn( "%lu %s", n, Ov_from( ov ) ); - break; - case DATE: - if ( Wld_match( Ov_date( ov ), pat ) ) - putTxtLn( "%lu %s", n, Ov_date( ov ) ); - break; - case MSG_ID: - if ( Wld_match( Ov_msgId( ov ), pat ) ) - putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); - break; - case REF: - if ( Wld_match( Ov_ref( ov ), pat ) ) - putTxtLn( "%lu %s", n, Ov_ref( ov ) ); - break; - default: - ASSERT( FALSE ); + n = Ov_numb( ov ); + getXhdrField( what, ov, field ); + if ( Wld_match( field, pat ) ) + putTxtLn( "%lu %s", n, field ); } - } + } putEndOfTxt(); return TRUE; } @@ -1258,6 +1350,15 @@ if ( ! testGrpSelected() ) return TRUE; + Grp_setLastAccess( server.grp, time( NULL ) ); + + /* + * All the info we require is now in RAM, and we may generate + * lots of output (consider XOVER 2-3999), so release the lock + * while responding. + */ + closeServer(); + parseRange( arg, &first, &last, &n ); if ( n == 0 ) first = last = server.artPtr; @@ -1332,6 +1433,7 @@ if ( ! Lock_openDatabases() ) return FALSE; server.running = TRUE; + server.lastServerOpen = time( NULL ); return TRUE; } @@ -1349,45 +1451,50 @@ Bool done; int r; Str line; - struct timeval timeOut; - fd_set readSet; putWelcome(); done = FALSE; while ( ! done ) { - FD_ZERO( &readSet ); - FD_SET( STDIN_FILENO, &readSet ); - /* Never hold lock more than 5 seconds (empirically good value, - avoids to close/open databases, if clients sends several - commands, but releases the lock often enough, for allowing - multiple persons to read news at the same time) */ - timeOut.tv_sec = 5; - timeOut.tv_usec = 0; - r = select( STDIN_FILENO + 1, &readSet, NULL, NULL, &timeOut ); + /* + * If we've had the lock for more than 2 seconds, + * force it to be released. Otherwise, if we have + * the lock but have had it for < 2 secs, + * wait for a command line for a maximum + * of 2 seconds. This is all an attempt at striking a + * balance between efficient processing of commands + * and hogging the lock. + */ + if ( server.running ) + { + time_t now; + + now = time( NULL ); + if ( difftime( now, server.lastServerOpen ) > 2.0 ) + closeServer(); + } + r = waitCmdLn( line, ( server.running ) ? 2 : 0 ); if ( r < 0 ) + { + Log_inf( "Client disconnected. Terminating." ); done = TRUE; + } else if ( r == 0 ) - { - if ( server.running ) - closeServer(); - } - else /* ( r > 0 ) */ - { - if ( ! server.running ) - { + { + if ( server.running ) + closeServer(); + } + else /* ( r > 0 ) */ + { + if ( ! server.running ) + { if ( ! initServer() ) { putFatal( "Cannot init server" ); done = TRUE; } } - if ( ! getLn( line ) ) - { - Log_inf( "Client disconnected. Terminating." ); - done = TRUE; - } - else if ( ! parseAndExecute( line ) ) + if ( ! parseAndExecute( line ) ) done = TRUE; } }