diff src/client.c @ 188:f1bacee93ca6 noffle

[svn] * src/client.c,src/client.h,src/fetch.c,src/noffle.c,src/server.c: robustness - instead of retruning simple Passed/Failed statuses from connection functions, return an integer status instead. This allows Noffle to distinguish between a connection failure, an unrecoverable protocol error and a recoverable problem. As a concrete instance, Noffle will no longer abort the connection if a group is removed from the upstream server. Also beef up error detection a bit. * src/content.c: instead of overwriting the existing content file(s) when updating - which leaves a window where Noffle is vulnerable to failure which will leave the content file corrupted (yes, it happened to me), write a new content file and rename it over the old file only when it has been written and closed with no errors reported.
author bears
date Wed, 12 Sep 2001 21:33:44 +0100
parents 166008a80f03
children 28488e0e3630
line wrap: on
line diff
--- a/src/client.c	Sat Sep 01 16:57:45 2001 +0100
+++ b/src/client.c	Wed Sep 12 21:33:44 2001 +0100
@@ -1,7 +1,7 @@
 /*
   client.c
 
-  $Id: client.c 306 2001-09-01 15:57:45Z enz $
+  $Id: client.c 307 2001-09-12 20:33:44Z bears $
 */
 
 #if HAVE_CONFIG_H
@@ -209,7 +209,7 @@
     Str lastCmd;
 
     if ( ! getLn( client.lastStat ) )
-        result = STAT_PROGRAM_FAULT;
+        result = STAT_CONNECTION_LOST;
     else if ( sscanf( client.lastStat, "%d", &result ) != 1 )
     {
         Log_err( "Invalid server status: %s", client.lastStat );
@@ -520,7 +520,7 @@
     client.in = client.out = NULL;
 }
 
-static Bool
+static int
 doGetGrps( const char *pattern, Bool *noServerPattern )
 {
     Str cmd;
@@ -536,10 +536,10 @@
 
     *noServerPattern = FALSE;
     if ( ! putCmd( cmd ) )
-        return FALSE;
+        return STAT_CONNECTION_LOST;
     stat = getStat();
-    if ( stat == STAT_PROGRAM_FAULT )
-	return FALSE;
+    if ( IS_FATAL( stat ) )
+        return stat;
 
     /*
      * Try LIST instead of LIST ACTIVE in case server doesn't
@@ -549,37 +549,38 @@
     {
 	*noServerPattern = TRUE;
 	if ( ! putCmd( "LIST" ) )
-	    return FALSE;
+	    return STAT_CONNECTION_LOST;
 	stat = getStat();
     }    
     if ( stat != STAT_GRPS_FOLLOW )    
     {
 	Log_err( "%s failed: %s", cmd, client.lastStat );
-	return FALSE;
+	return stat;
     }
 
     response = collectTxt();
     if ( response == NULL )
-	return FALSE;
+	return STAT_CONNECTION_LOST;
     
     processGrps( DynStr_str( response ), *noServerPattern );
     del_DynStr( response );
-    return TRUE;
+    return STAT_OK;
 }
 
-Bool
+int
 Client_getGrps( void )
 {
     GroupEnum *ge;
     const char *pattern;
-    Bool doneOne, noServerPattern, res;
+    Bool doneOne, noServerPattern;
+    int res;
 
     Log_inf( "Getting groups" );
 
     doneOne = FALSE;
-    res = TRUE;
+    res = STAT_OK;
     ge = new_GetGrEn( client.serv );
-    while ( res && ( pattern = GrEn_next( ge ) ) != NULL )
+    while ( res == STAT_OK && ( pattern = GrEn_next( ge ) ) != NULL )
     {
 	res = doGetGrps( pattern, &noServerPattern );
 	doneOne = TRUE;
@@ -594,7 +595,7 @@
     return res;
 }
 
-static Bool
+static int
 doGetDsc( const char *pattern, Bool *noServerPattern )
 {
     Str name, line, dsc, cmd;
@@ -613,40 +614,40 @@
 
     *noServerPattern = FALSE;
     if ( ! putCmd( cmd ) )
-        return FALSE;
+        return STAT_CONNECTION_LOST;
     stat = getStat();
-    if ( stat == STAT_PROGRAM_FAULT )
-	return FALSE;
+    if ( IS_FATAL( stat ) )
+        return stat;
 
     /* Try without pattern in case server doesn't support patterns. */
     if ( pattern[ 0 ] != '\0' && stat != STAT_GRPS_FOLLOW )
     {
 	*noServerPattern = TRUE;
 	if ( !putCmd( "LIST NEWSGROUPS" ) )
-	     return FALSE;
+	     return STAT_CONNECTION_LOST;
 	stat = getStat();
     }
     if ( stat != STAT_GRPS_FOLLOW )
     {
         Log_err( "%s failed: %s", cmd, client.lastStat );
-        return FALSE;
+        return stat;
     }
 
     response = collectTxt();
     if ( response == NULL )
-	return FALSE;
+	return STAT_CONNECTION_LOST;
     
     if ( ! Lock_openDatabases() )
-	return FALSE;
+	return STAT_NEWSBASE_FATAL;
     
     lines = DynStr_str( response );
-    result = TRUE;
+    result = STAT_OK;
     while ( ( lines = Utl_getLn( line, lines) ) != NULL )
     {
         if ( sscanf( line, "%s", name ) != 1 )
         {
             Log_err( "Unknown reply to LIST NEWSGROUPS: %s", line );
-	    result = FALSE;
+	    result = STAT_PROGRAM_FAULT;
 	    break;
         }
 	if ( *noServerPattern && ! isGetGroup( name ) )
@@ -663,19 +664,20 @@
     return result;
 }
 
-Bool
+int
 Client_getDsc( void )
 {
     GroupEnum *ge;
     const char *pattern;
-    Bool doneOne, noServerPattern, res;
+    Bool doneOne, noServerPattern;
+    int res;
 
     Log_inf( "Querying group descriptions" );
 
     doneOne = FALSE;
-    res = TRUE;
+    res = STAT_OK;
     ge = new_GetGrEn( client.serv );
-    while ( res && ( pattern = GrEn_next( ge ) ) != NULL )
+    while ( res == STAT_OK && ( pattern = GrEn_next( ge ) ) != NULL )
     {
 	res = doGetDsc( pattern, &noServerPattern );
 	doneOne = TRUE;
@@ -683,14 +685,14 @@
 	    break;
     }
 
-    if ( res && ! doneOne )
+    if ( ! doneOne )
 	res = doGetDsc( "", &noServerPattern );
 
     del_GrEn( ge );
     return res;
 }
 
-Bool
+int
 Client_getNewgrps( const time_t *lastTime )
 {
     Str s;
@@ -707,31 +709,21 @@
     */
     p = s + 2;
     if ( ! putCmd( "NEWGROUPS %s GMT", p ) )
-        return FALSE;
+        return STAT_CONNECTION_LOST;
     stat = getStat();
     if ( stat != STAT_NEW_GRP_FOLLOW )
     {
         Log_err( "NEWGROUPS command failed: %s", client.lastStat );
-
-	/*
-	 * If NEWGROUPS fails, it isn't necessarily fatal. You can do
-	 * a periodic noffle --query groups to refresh your group list.
-	 * So only return failure here if the status indicates the link
-	 * itself failed.
-	 *
-	 * In particular, older versions of NNTPcache have a Y2K bug that
-	 * stops NEWGROUPS working.
-	 */
-        return ( stat != STAT_PROGRAM_FAULT );
+        return stat;
     }
 
     response = collectTxt();
     if ( response == NULL )
-	return FALSE;
+	return STAT_CONNECTION_LOST;
     
     processGrps( DynStr_str( response ), TRUE );
     del_DynStr( response );
-    return TRUE;
+    return STAT_OK;
 }
 
 static const char *
@@ -918,7 +910,7 @@
     }
 }
 
-Bool
+int
 Client_getOver( const char *grp, int rmtFirst, int rmtLast, FetchMode mode )
 {
     size_t nbytes, nlines;
@@ -929,6 +921,7 @@
     const char *lines, *groupLines;
     char *p;
     FilterAction action;
+    int stat;
 
     ASSERT( ! Lock_gotLock() );
     ASSERT( strcmp( grp, "" ) != 0 );
@@ -937,11 +930,12 @@
     if ( Flt_getNewsgroups() )
     {
 	if ( ! putCmd( "XHDR Newsgroups %lu-%lu", rmtFirst, rmtLast ) )
-	    return FALSE;
-	if ( getStat() != STAT_HEAD_FOLLOWS )
+	    return STAT_CONNECTION_LOST;
+	stat = getStat();
+	if ( stat != STAT_HEAD_FOLLOWS )
 	{
 	    Log_err( "XHDR command failed: %s", client.lastStat );
-	    return FALSE;
+	    return stat;
 	}
 
 	Log_dbg( LOG_DBG_FETCH,
@@ -950,7 +944,7 @@
 
 	newsgroups = collectTxt();
 	if ( newsgroups == NULL )
-	    return FALSE;
+	    return STAT_CONNECTION_LOST;
 	
 	groupLines = DynStr_str( newsgroups );
     }
@@ -963,14 +957,15 @@
     if ( ! putCmd( "XOVER %lu-%lu", rmtFirst, rmtLast ) )
     {
 	del_DynStr( newsgroups );
-        return FALSE;
+        return STAT_CONNECTION_LOST;
     }
     
-    if ( getStat() != STAT_OVERS_FOLLOW )
+    stat = getStat();
+    if ( stat != STAT_OVERS_FOLLOW )
     {
 	del_DynStr( newsgroups );
         Log_err( "XOVER command failed: %s", client.lastStat );
-        return FALSE;
+        return stat;
     }
     Log_dbg( LOG_DBG_FETCH,
 	     "Requesting overview for remote %lu-%lu",
@@ -980,14 +975,14 @@
     if ( response == NULL )
     {
 	del_DynStr( newsgroups );
-	return FALSE;
+	return STAT_CONNECTION_LOST;
     }
 
     if ( ! Lock_openDatabases() )
     {
 	del_DynStr( newsgroups );
 	del_DynStr( response );
-	return FALSE;
+	return STAT_NEWSBASE_FATAL;
     }
     
     Cont_read( grp );
@@ -1041,7 +1036,7 @@
     Lock_closeDatabases();
     del_DynStr( response );
     del_DynStr( newsgroups );
-    return TRUE;
+    return STAT_OK;
 }
 
 static void
@@ -1057,9 +1052,10 @@
     Pseudo_retrievingFailed( msgId, reason );
     Db_setStatus( msgId, status | DB_RETRIEVING_FAILED );
     Lock_closeDatabases();
+    return;
 }
 
-static Bool
+static int
 retrieveAndStoreArt( const char *msgId, int artcnt, int artmax )
 {
     Bool err;
@@ -1079,7 +1075,7 @@
 	{
 	    del_DynStr( s );
 	    retrievingFailed( msgId, "Can't open message base" );
-	    return FALSE;
+	    return STAT_NEWSBASE_FATAL;
 	}
 	
         err = ! Db_storeArt( msgId, txt );
@@ -1104,61 +1100,74 @@
 	del_DynStr( s );
     }
     else
+    {
         retrievingFailed( msgId, "Connection broke down" );
-    return ! err;
+	return STAT_CONNECTION_LOST;
+    }
+    return err ? STAT_NEWSBASE_FATAL : STAT_OK;
 }
 
-Bool
+int
 Client_retrieveArt( const char *msgId )
 {
-    Bool res = FALSE;
+    int res;
     
     ASSERT( Lock_gotLock() );
     if ( ! Db_contains( msgId ) )
     {
         Log_err( "Article '%s' not prepared in database. Skipping.", msgId );
-        return TRUE;
+        return STAT_PROGRAM_FAULT;
     }
     if ( ! ( Db_status( msgId ) & DB_NOT_DOWNLOADED ) )
     {
         Log_inf( "Article '%s' already retrieved. Skipping.", msgId );
-        return TRUE;
+        return STAT_OK;
     }
 
     Lock_closeDatabases();
     if ( ! putCmd( "ARTICLE %s", msgId ) )
+    {
         retrievingFailed( msgId, "Connection broke down" );
-    else if ( getStat() != STAT_ART_FOLLOWS )
+	res = STAT_CONNECTION_LOST;
+    }
+    else if ( ( res = getStat() ) != STAT_ART_FOLLOWS )
         retrievingFailed( msgId, client.lastStat );
     else
         res = retrieveAndStoreArt( msgId, 0, 0 );
-    Lock_openDatabases();
+    if ( ! Lock_openDatabases() )
+	res = STAT_NEWSBASE_FATAL;
     return res;
 }
 
-Bool
+int
 Client_retrieveArtList( const char *list, int *artcnt, int artmax )
 {
     Str msgId;
     DynStr *s;
     const char *p;
-    Bool res;
+    int res, msgStat;
     
     ASSERT( Lock_gotLock() );
     Log_inf( "Retrieving article list" );
     s = new_DynStr( (int)strlen( list ) );
     p = list;
+    res = STAT_OK;
     while ( ( p = Utl_getLn( msgId, p ) ) )
         if ( ! Db_contains( msgId ) )
-            Log_err( "[%d/%d] Skipping retrieving of %s (not prepared in database)",
+	{
+            Log_err( "[%d/%d] Skipping retrieving of %s "
+		     "(not prepared in database)",
                      ++(*artcnt), artmax, msgId );
+	    res = STAT_PROGRAM_FAULT;
+	}
         else if ( ! ( Db_status( msgId ) & DB_NOT_DOWNLOADED ) )
-            Log_inf( "[%d/%d] Skipping %s (already retrieved)", ++(*artcnt), artmax, msgId );
+            Log_inf( "[%d/%d] Skipping %s (already retrieved)",
+		     ++(*artcnt), artmax, msgId );
         else if ( ! putCmdNoFlush( "ARTICLE %s", msgId ) )
         {
             retrievingFailed( msgId, "Connection broke down" );
             del_DynStr( s );
-            return FALSE;
+            return STAT_CONNECTION_LOST;
         }
         else
             DynStr_appLn( s, msgId );
@@ -1167,55 +1176,58 @@
     fflush( client.out );
     Log_dbg( LOG_DBG_PROTOCOL, "[S FLUSH]" );
     
+    /*
+     * We got something. Try to process all messages and return the
+     * 'worst' error encountered (note we may have already hit a
+     * STAT_PROGRAM_FAULT).
+     */
     p = DynStr_str( s );
-    res = TRUE;
-    while ( res && ( p = Utl_getLn( msgId, p ) ) )
+    while ( ! IS_FATAL( res ) && ( p = Utl_getLn( msgId, p ) ) )
     {
-	switch( getStat() )
-	{
-	case STAT_ART_FOLLOWS:
-	    res = retrieveAndStoreArt( msgId, ++(*artcnt), artmax );
-	    break;
-
-	case STAT_PROGRAM_FAULT:
-	    res = FALSE;
-	    /* Fall through */
-
-	default:
+	msgStat = getStat();
+	if ( msgStat == STAT_ART_FOLLOWS )
+	    msgStat = retrieveAndStoreArt( msgId, ++(*artcnt), artmax );
+	else
             retrievingFailed( msgId, client.lastStat );
-	    break;
-	}
+	    
+	if ( res == STAT_OK || ( ! IS_FATAL( res ) && IS_FATAL( msgStat ) ) )
+	    res = msgStat;
     }
     del_DynStr( s );
-    Lock_openDatabases();
+    if ( ! Lock_openDatabases() && ! IS_FATAL( res ) )
+	res = STAT_NEWSBASE_FATAL;
     return res;
 }
 
-Bool
+int
 Client_changeToGrp( const char* name )
 {
     unsigned int stat;
-    int estimatedNumb, first, last;
-    Bool res;
+    int estimatedNumb, first, last, res;
 
     ASSERT( Lock_gotLock() );
     if ( ! Grp_exists( name ) )
-        return FALSE;
+        return STAT_NEWSBASE_FATAL;
     Lock_closeDatabases();
-    res = putCmd( "GROUP %s", name );
-    res = res && ( getStat() == STAT_GRP_SELECTED );
-    if ( ! Lock_openDatabases() || ! res )
-        return FALSE;
+    stat = STAT_OK;
+    if ( ! putCmd( "GROUP %s", name ) )
+	res = STAT_CONNECTION_LOST;
+    if ( stat == STAT_OK )
+	stat = getStat();
+    if ( ! Lock_openDatabases() )
+	return STAT_NEWSBASE_FATAL;
+    if ( stat != STAT_GRP_SELECTED )
+	return stat;
     if ( sscanf( client.lastStat, "%u %d %d %d",
                  &stat, &estimatedNumb, &first, &last ) != 4 )
     {
         Log_err( "Bad server response to GROUP: %s", client.lastStat );
-        return FALSE;
+        return STAT_PROGRAM_FAULT;
     }
     Utl_cpyStr( client.grp, name );
     client.rmtFirst = first;
     client.rmtLast = last;
-    return TRUE;
+    return STAT_OK;
 }
 
 void
@@ -1226,7 +1238,7 @@
     *last = client.rmtLast;
 }
 
-Bool
+int
 Client_postArt( const char *msgId, const char *artTxt, Str errStr )
 {
     int stat;
@@ -1234,27 +1246,27 @@
     errStr[0] = '\0';
     
     if ( ! putCmd( "POST" ) )
-        return FALSE;
+        return STAT_CONNECTION_LOST;
     stat = getStat();
-    if ( stat == STAT_PROGRAM_FAULT )
-	return FALSE;
+    if ( IS_FATAL( stat ) )
+	return stat;
     else if ( stat != STAT_SEND_ART )
     {
         Log_err( "Posting of %s not allowed: %s", msgId, client.lastStat );
         strcpy( errStr, client.lastStat );
-        return TRUE;
+        return stat;
     }
     putTxtBuf( artTxt );
     putEndOfTxt();
     stat = getStat();
-    if ( stat == STAT_PROGRAM_FAULT )
-	return FALSE;
+    if ( IS_FATAL( stat ) )
+	return stat;
     else if ( stat != STAT_POST_OK )
     {
         Log_err( "Posting of %s failed: %s", msgId, client.lastStat );
         strcpy( errStr, client.lastStat );
-        return TRUE;
+        return stat;
     }
     Log_inf( "Posted %s (Status: %s)", msgId, client.lastStat );
-    return TRUE;
+    return STAT_OK;
 }