view src/fetch.c @ 180:09ca6eb5c7ff noffle

[svn] * TODO,src/client.c,src/client.h,src/fetch.c,src/fetch.h,src/noffle.c: Improve error checking during fetches. A fetch is now aborted immediately if the connection times out or if an unexpected response arrives. This should fix problems with articles appearing in the wrong group, and possibly other mysterious happenings.
author bears
date Wed, 09 May 2001 12:33:43 +0100
parents 22b81617d427
children 9854ea5f295f
line wrap: on
line source

/*
  fetch.c

  $Id: fetch.c 279 2001-05-09 11:33:43Z bears $
*/

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

#include <stdio.h>
#include "fetch.h"
#include <errno.h>

#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 <signal.h>
#include "client.h"
#include "configfile.h"
#include "content.h"
#include "dynamicstring.h"
#include "fetchlist.h"
#include "request.h"
#include "group.h"
#include "lock.h"
#include "log.h"
#include "outgoing.h"
#include "protocol.h"
#include "pseudo.h"
#include "util.h"
#include "portable.h"

#define	MAX_ARTICLE_CMDS_QUEUED		20

struct Fetch
{
    Bool ready;
    Str serv;
} fetch = { FALSE, "" };

static Bool
connectToServ( const char *name )
{
    Log_inf( "Fetch from '%s'", name );
    if ( ! Client_connect( name ) )
    {
        Log_err( "Could not connect to %s", name );
        return FALSE;
    }
    return TRUE;
}

Bool
Fetch_getNewGrps( void )
{
    time_t t;
    Str file;

    ASSERT( fetch.ready );
    snprintf( file, MAXCHAR, "%s/lastupdate.%s",
	      Cfg_spoolDir(), fetch.serv );
    if ( ! Utl_getStamp( &t, file ) )
    {
        Log_err( "Cannot read %s. Please run noffle --query groups", file );
        return FALSE;
    }
    Log_inf( "Updating groupinfo" );
    return Client_getNewgrps( &t );
}

/* Databases open on entry, closed on exit. */
static Bool
fetchNewArts( const char *name, FetchMode mode )
{
    int next, first, last, refetch;

    if ( ! Client_changeToGrp( name ) )
    {
        Log_err( "Could not change to group %s", name );
	if ( Lock_gotLock() )
	    Lock_closeDatabases();
        return FALSE;
    }
    Client_rmtFirstLast( &first, &last );
    Cont_read( name );
    next = Grp_rmtNext( name );
    if ( next == GRP_RMT_NEXT_NOT_SUBSCRIBED )
	next = first;
    if ( next == last + 1 )
    {
        Log_inf( "No new articles in %s", name );
        Cont_write();
        Grp_setFirstLast( name, Cont_first(), Cont_last() );
	Lock_closeDatabases();
        return TRUE;
    }
    if ( first == 0 && last == 0 )
    {
        Log_inf( "No articles in %s", name );
        Cont_write();
        Grp_setFirstLast( name, Cont_first(), Cont_last() );
	Lock_closeDatabases();
        return TRUE;
    }
    if ( next > last + 1 )
    {
    	refetch = last - Cfg_maxFetch() + 1;
    	if ( refetch < 0 ) refetch = 1;
        Log_err( "Article number inconsistent (%s rmt=%lu-%lu, next=%lu). Refetching from %lu",
                 name, first, last, next, refetch );
        Pseudo_cntInconsistent( name, first, last, next, refetch );
        first = refetch;
    }
    else if ( next < first )
    {
        Log_inf( "Missing articles (%s first=%lu next=%lu)",
                 name, first, next );
        Pseudo_missArts( name, first, next );
    }
    else
        first = next;
    if ( last - first > Cfg_maxFetch() )
    {
        Log_ntc( "Cutting number of overviews to %lu", Cfg_maxFetch() );
        first = last - Cfg_maxFetch() + 1;
    }
    Log_inf( "Getting remote overviews %lu-%lu for group %s",
             first, last, name );
    Lock_closeDatabases();
    return Client_getOver( name, first, last, mode );
}

Bool
Fetch_getNewArts( const char *name, FetchMode mode )
{
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }
    return fetchNewArts( name, mode );
}

Bool
Fetch_updateGrps( void )
{
    FetchMode mode;
    int i, size;
    const char *name;

    ASSERT( fetch.ready );
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }
    Fetchlist_read();
    size = Fetchlist_size();
    for ( i = 0; i < size; ++i )
    {
        Fetchlist_element( &name, &mode, i );
        if ( strcmp( Grp_server( name ), fetch.serv ) == 0 )
	{
            if ( ! fetchNewArts( name, mode ) )
		return FALSE;
	    if ( ! Lock_openDatabases() )
	    {
		Log_err( "Could not open message base" );
		return FALSE;
	    }
	}
    }
    Lock_closeDatabases();
    return TRUE;
}

static Bool
fetchMessageList( const char *list, int *artcnt, int artmax )
{
    const char *p;
    Str msgId;

    ASSERT( Lock_gotLock() );
    if ( ! Client_retrieveArtList( list, artcnt, artmax ) )
	return FALSE;
    p = list;
    while ( ( p = Utl_getLn( msgId, p ) ) )
        Req_remove( fetch.serv, msgId );
    return TRUE;
}

Bool
Fetch_getReq_( void )
{
    Str msgId;
    DynStr *list;
    DynStr *fetchList;
    const char *p;
    int count = 0, artcnt = 0, artmax = 0;
    Bool res;

    ASSERT( fetch.ready );
    Log_dbg( "Retrieving articles marked for download" );
    list = new_DynStr( 10000 );
    fetchList = new_DynStr( 1000 );
    if ( list == NULL || fetchList == NULL )
    {
	if ( list != NULL )
	    del_DynStr( list );
        Log_err( "Out of memory in Fetch_get_Req_");
	return FALSE;
    }

    /*
     * Get all waiting message IDs for this server. We copy into a master
     * list as the requests file will be closed and re-opened during the
     * fetch and the position therein will be lost.
     */
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }

    if ( Req_first( fetch.serv, msgId ) )
    {
        do
        {
            DynStr_appLn( list, msgId );
	    artmax++;
        }
        while ( Req_next( msgId ) );
	Log_inf( "%d TOTAL messages to download", artmax);
    }

    /* Retrieve in groups of up to size MAX_ARTICLE_CMDS_QUEUED. */
    p = DynStr_str( list );
    res = TRUE;
    while ( res && ( p = Utl_getLn( msgId, p ) ) != NULL )
    {
	DynStr_appLn( fetchList, msgId );
	if ( ++count % MAX_ARTICLE_CMDS_QUEUED == 0 )
	{
	    res = fetchMessageList( DynStr_str( fetchList ), &artcnt, artmax );
	    DynStr_clear( fetchList );
	}
    }
    res = res && fetchMessageList( DynStr_str( fetchList ), &artcnt, artmax );

    del_DynStr( fetchList );
    del_DynStr( list );
    Lock_closeDatabases();
    return res;
}

static void
returnArticleToSender( const char *sender, const char *reason,
                       const char *article )
{
    int ret;
    Str cmd;
    FILE *f;
    sig_t lastHandler;

    Log_err( "Return article to '%s' by mail", sender );
    snprintf( cmd, MAXCHAR, "%s -t -oi", SENDMAILPROG);
    lastHandler = signal( SIGPIPE, SIG_IGN );
    f = popen( cmd, "w" );
    if ( f == NULL )
        Log_err( "Invocation of '%s' failed (%s)", cmd, strerror( errno ) );
    else
    {
        fprintf( f,
                 "To: %s\n"
                 "Subject: [ NOFFLE: Posting failed ]\n"
                 "\n"
                 "\t[ NOFFLE: POSTING OF ARTICLE FAILED ]\n"
                 "\n"
                 "\t[ The posting of your article failed. ]\n"
                 "\t[ Reason of failure at remote server: ]\n"
                 "\n"
                 "\t[ %s ]\n"
                 "\n"
                 "\t[ Full article text has been appended. ]\n"
                 "\n"
                 "%s"
                 ".\n",
                 sender,
                 reason, article );
        ret = pclose( f );
        if ( ret != EXIT_SUCCESS )
            Log_err( "'%s' exit value %d", cmd, ret );
        signal( SIGPIPE, lastHandler );
    }
}

Bool
Fetch_postArts( void )
{
    DynStr *s;
    Str msgId, errStr, sender;
    const char *txt;
    Bool res;

    res = TRUE;
    s = new_DynStr( 10000 );
    if ( Out_first( fetch.serv, msgId, s ) )
    {
        Log_inf( "Posting articles" );
        do
        {
            txt = DynStr_str( s );
	    if ( ! Client_postArt( msgId, txt, errStr ) )
	    {
		res = FALSE;
		break;
	    }
	    
	    /*
	     * OK, no server communication SNAFU during post. Now, do we
	     * get an error response? If so, try to return article to sender.
	     */
	    Out_remove( fetch.serv, msgId );
	    if ( errStr[0] != '\0' )
	    {
		Utl_cpyStr( sender, Cfg_mailTo() );
		if ( strcmp( sender, "" ) == 0
		     && ! Prt_searchHeader( txt, "SENDER", sender )
		     && ! Prt_searchHeader( txt, "X-NOFFLE-X-SENDER",
					    sender ) /* see server.c */
		     && ! Prt_searchHeader( txt, "FROM", sender ) )
		    Log_err( "Article %s has no From/Sender/X-Sender field",
			     msgId );
		else
		    returnArticleToSender( sender, errStr, txt );
	    }
        }
        while ( Out_next( msgId, s ) );
    }
    del_DynStr( s );
    return res;
}

Bool
Fetch_init( const char *serv )
{
    Lock_closeDatabases();
    if ( ! connectToServ( serv ) )
    {
        Lock_openDatabases();
        return FALSE;
    }
    Utl_cpyStr( fetch.serv, serv );
    fetch.ready = TRUE;
    return TRUE;
}

void
Fetch_close()
{
    Client_disconnect();
    fetch.ready = FALSE;
    Log_inf( "Fetch from '%s' finished", fetch.serv );
    Lock_openDatabases();
}