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;
         }
     }