Mercurial > noffle
changeset 127:3c71e28c8eef noffle
[svn] Release-1-0 mergedocs/NOTES
author | bears |
---|---|
date | Tue, 25 Jul 2000 13:14:54 +0100 |
parents | 7c7a7c96d35b |
children | 8897b7e3b108 |
files | ChangeLog NEWS docs/NOTES docs/noffle.1 src/client.c src/client.h src/database.c src/database.h src/fetch.c src/group.c src/group.h src/lock.c src/lock.h src/noffle.c src/protocol.c src/server.c src/util.c |
diffstat | 17 files changed, 843 insertions(+), 445 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Tue Jul 25 13:12:50 2000 +0100 +++ b/ChangeLog Tue Jul 25 13:14:54 2000 +0100 @@ -2,6 +2,44 @@ NOFFLE ChangeLog ------------------------------------------------------------------------------- +Mon 24 Jul 20:30:05 BST 2000 Jim Hague <jim.hague@acm.org> + + * src/database.h,src/database.c,src/server.c: Redo XHDR and + XPAT to handle 'XHDR <msgid>' and 'XPAT <msgid>'. Common up + code shared by XHDR and XPAT. + * src/server.c,packages/redhat/noffle.spec: Remove pipe through + sort(1) in printGroups() and dependency on textutil(RedHat)/ + textutils(SuSE) packages in RPM. The RPM now works on RedHat + and derivatives and SuSE. + * src/server.c: Release lock while collecting POST article and while + reporting XOVER x-y results. + * src/server.c: Update group 'last accessed' timestamp when XOVER read. + This for smooth working with 'trn' and other newsreaders that only + read the XOVER unless an article read. I have newsgroups I want to scan + but only infrequently read an article - I don't want them getting + unsubscribed. + * src/server.c: Change server read timeout to 2 secs, release the lock + if we'v held it for 2 secs so we don't starve anybody else, and + if reading a command when we don't hold the lock don't use timeout. + * src/util.c: Fix Utl_newsDate and Utl_parseNewsDateto work properly + in all timezones. + * src/lock.h,src/lock.c,src/noffle.c: Add 'fetch' lock to main lock, + add Lock_gotLock() so we can test the main lock state (currently used + only in ASSERTs). Fetch lock can be obtained WAIT or NOWAIT - latter + fails immediately if lock not available. + * src/client.c,src/fetch.c,src/noffle.c: Change lock handling during a fetch. + Begin by obtaining 'fetch' lock - if busy, exit fetch immediately with + error message. Then during fetch release the lock except when processing + received data; in cases when incoming data is multi-line, collect all + data first and then process. The aim is to ensure the lock isn't held + with a network operation is in progress. I may carry on and extend this + to all server operations as well (so a slow client can't hog the lock). + +Sat Jun 22 2000 Markus Enzenberger <markus.enzenberger@t-online.de> + + * doc/NOTES: Removed section about GNUS hanging. It was caused + by the "select" bug in Noffle. + Sat Jul 22 2000 Markus Enzenberger <markus.enzenberger@t-online.de> * request.c: Applied patch from M.Nalis: @@ -16,13 +54,48 @@ Wed Jul 19 2000 Markus Enzenberger <markus.enzenberger@t-online.de> - * Do not acquire global lock for prniting help + * Do not acquire global lock for printing help + +Mon Jul 17 11:19:06 BST 2000 Jim Hague <jim.hague@acm.org> + + * src/client.h,src/client.c,src/noffle.c,docs/noffle.1: Remove + '--query times' option. Creation time should always be time of + creation on local server. + * src/group.h,src/group.c: Remove Grp_setCreated(). + +Fri Jul 14 15:20:14 BST 2000 Jim Hague <jim.hague@acm.org> + + * src/client.c,src/fetch.c,src/noffle.c: The groupinfo.lastupdate file + was being used to (a) indicate the time of the last addition to group + information, and (b) the last access to a server. With more than one + server specified, (a) meant the groupinfo.lastupdate file was updated + when new groups on the server (or a new fetch of the server group list) + were processed. When server 2 came to use the file for (b) (to determine + the time to specify when sending the NEWGROUPS command), the time was + already set by server 1 and so server 2 would not see new groups. Fix + this by creating lastupdate.<server> files for use (b). + * src/client.c,src/group.c: Set initial group creation time to the current + time. Previously it was set to 0, so new groups were not reported to a + NEWGROUPS request unless 'noffle --query times' was run - even then, + local groups would never be reported. Setting the creation time to the + creation time on the local server makes things work properly. NB - the + group creation time should always be reported as the creation time on + the server; must fix this and remove '--query times'. This change will + require 'noffle --query groups' to be re-run to create the server + lastupdate.<server> files. Fri Jul 14 2000 Matija Nalis <mnalis-sf@voyager.hr> * Added counter for --fetch so one can see how much it is until the end of the transfer. +Mon Jul 03 12:05:50 BST 2000 Jim Hague <jim.hague@acm.org> + + * src/database.h,src/database.c,src/server.c: Fix 'XHDR <msgId>' and + add 'XPAT <msgId>'. 'XHDR <msgId>' previously only worked if + msgId was a message in the current group. My brain was really + in neutral when I did that. + Fri Jun 30 2000 Markus Enzenberger <markus.enzenberger@t-online.de> * src/server.c: Leave online mode, if the connection to a remote server @@ -55,6 +128,12 @@ * src/protocol.c: Fix bug in Prt_genMsgId that caused duplicate message IDs to be generated for posts in the same second. +Sat Jun 22 2000 Markus Enzenberger <markus.enzenberger@t-online.de> + + * src/server.c, src/protocol.c: Fixed a critical bug. "select" cannot + be used with buffered stdio. This caused Noffle to hang with some + readers (like tin). + Mon Jun 19 22:43:38 BST 2000 Jim Hague <jim.hague@acm.org> * src/util.c, src/database.c: Fix header line reading bug.
--- a/NEWS Tue Jul 25 13:12:50 2000 +0100 +++ b/NEWS Tue Jul 25 13:14:54 2000 +0100 @@ -5,6 +5,11 @@ Current development version: ---------------------------- + * Fix bug with time of last server access for setups with multiple servers. + You must re-run 'noffle --query groups'. + * Remove '--query times'. + * Fix date printing bug. Now works in all(?) timezones. + * Fix bug with 'XHDR <msgId>'. Add 'XPAT <msgId>'. * Allow post to local moderated group if 'Approved:' header present. * Added '--post' command line option. * Add 'path-header' and Path: addition to posted articles. @@ -12,6 +17,7 @@ add new 'auto-subscribe-mode <group pattern> <mode>' option. * Observe Expires: and Supersedes: headers. * Add getgroups and omitgroups options. + * Fixed a bug that caused noffle to hang with some readers (like tin). 1.0pre6 -------
--- a/docs/NOTES Tue Jul 25 13:12:50 2000 +0100 +++ b/docs/NOTES Tue Jul 25 13:14:54 2000 +0100 @@ -176,16 +176,6 @@ 1.8 Emacs Gnus -------------- -Some versions of Gnus freeze up when retrieving active groups. Since -NOFFLE's log files in DEBUG mode show nothing unusual, I believe that -this is a bug in Gnus. Sometimes it helps to remove all ".newsrc" and -similar files on ones home directory and restarting Gnus. - -[ Alternatively, set option Gnus/Gnus Start/Gnus Init File from the ] -[ default ~/.newsrc to, say, ~/.gnus-newsrc. The hang I experienced ] -[ seems to be due to problems reading an existing .newsrc, nothing ] -[ to do with NOFFLE at all. ] - Here is a proposal for changing some key-bindings. ;; Customising Gnus for use with the NOFFLE news server
--- a/docs/noffle.1 Tue Jul 25 13:12:50 2000 +0100 +++ b/docs/noffle.1 Tue Jul 25 13:14:54 2000 +0100 @@ -1,5 +1,5 @@ .TH noffle 1 -.\" $Id: noffle.1 165 2000-06-25 18:42:10Z bears $ +.\" $Id: noffle.1 183 2000-07-25 12:14:54Z bears $ .SH NAME noffle \- Usenet package optimized for dialup connections. @@ -51,7 +51,7 @@ \-p | \-\-post .br .B noffle -\-q | \-\-query groups|desc|times +\-q | \-\-query groups|desc .br .B noffle \-R | \-\-requested @@ -233,7 +233,7 @@ have an 'Approved:' header to be posted. .TP -.B \-q, \-\-query groups|desc|times +.B \-q, \-\-query groups|desc Query information about all groups from the remote server and merge it to the .B groupinfo @@ -246,8 +246,6 @@ (resets all local article counters), .B desc retrieves all newsgroup descriptions, -.B times -retrieves the creation times of the newsgroups. .TP .B \-r, \-\-server
--- a/src/client.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/client.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* client.c - $Id: client.c 177 2000-07-22 06:21:11Z enz $ + $Id: client.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -25,6 +25,7 @@ #include "dynamicstring.h" #include "group.h" #include "itemlist.h" +#include "lock.h" #include "log.h" #include "over.h" #include "protocol.h" @@ -269,6 +270,29 @@ return ( r >= 0 ); } +static DynStr * +collectTxt( void ) +{ + DynStr *res; + Str line; + Bool err; + + res = new_DynStr(2048); + if ( res == NULL ) + return NULL; + + while ( getTxtLn( line, &err ) && ! err ) + DynStr_appLn( res, line ); + + if ( err ) + { + del_DynStr( res ); + return NULL; + } + else + return res; +} + Bool Client_connect( const char *serv ) { @@ -280,6 +304,7 @@ Str host, s; struct sockaddr_in sIn; + ASSERT( client.in == NULL && client.out == NULL ); client.auth = FALSE; Utl_cpyStr( s, serv ); pStart = Utl_stripWhiteSpace( s ); @@ -328,6 +353,7 @@ if ( client.out != NULL ) fclose( client.out ); close( sock ); + client.in = client.out = NULL; break; } stat = getStat(); @@ -352,6 +378,7 @@ fclose( client.in ); fclose( client.out ); close( sock ); + client.in = client.out = NULL; } } return FALSE; @@ -418,14 +445,19 @@ } static void -processGrps( Bool noServerPattern ) +processGrps( const char *lines, Bool noServerPattern ) { char postAllow; - Bool err; int first, last; Str grp, line, file; - - while ( getTxtLn( line, &err ) && ! err ) + Bool groupupdate; + + ASSERT( ! Lock_gotLock() ); + if ( ! Lock_openDatabases() ) + return; + + groupupdate = FALSE; + while ( ( lines = Utl_getLn( line, lines) ) != NULL ) { if ( sscanf( line, "%s %d %d %c", grp, &last, &first, &postAllow ) != 4 ) @@ -454,6 +486,7 @@ Grp_setFirstLast( grp, 1, 0 ); Grp_setServ( grp, client.serv ); Grp_setPostAllow( grp, postAllow ); + groupupdate = TRUE; } else { @@ -464,18 +497,24 @@ Grp_setServ( grp, client.serv ); Grp_setRmtNext( grp, first ); Grp_setPostAllow( grp, postAllow ); + groupupdate = TRUE; } else Log_dbg( "Group %s is already fetched from %s", - grp, Grp_server( grp ) ); - + grp, Grp_server( grp ) ); } } - if ( ! err ) + + snprintf( file, MAXCHAR, "%s/lastupdate.%s", + Cfg_spoolDir(), client.serv ); + Utl_stamp( file ); + if ( groupupdate ) { - snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); - Utl_stamp( file ); + snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", + Cfg_spoolDir() ); + Utl_stamp( file ); } + Lock_closeDatabases(); } void @@ -493,6 +532,7 @@ { Str cmd; int stat; + DynStr *response; Utl_cpyStr( cmd, "LIST ACTIVE" ); if ( pattern[ 0 ] != '\0' ) @@ -517,7 +557,13 @@ Log_err( "%s failed: %s", cmd, client.lastStat ); return FALSE; } - processGrps( *noServerPattern ); + + response = collectTxt(); + if ( response == NULL ) + return FALSE; + + processGrps( DynStr_str( response ), *noServerPattern ); + del_DynStr( response ); return TRUE; } @@ -551,10 +597,12 @@ static Bool doGetDsc( const char *pattern, Bool *noServerPattern ) { - Bool err; Str name, line, dsc, cmd; int stat; + DynStr *response; + const char *lines; + ASSERT( ! Lock_gotLock() ); Utl_cpyStr( cmd, "LIST NEWSGROUPS" ); if ( pattern[ 0 ] != '\0' ) { @@ -578,7 +626,16 @@ Log_err( "%s failed: %s", cmd, client.lastStat ); return FALSE; } - while ( getTxtLn( line, &err ) && ! err ) + + response = collectTxt(); + if ( response == NULL ) + return FALSE; + + if ( ! Lock_openDatabases() ) + return FALSE; + + lines = DynStr_str( response ); + while ( ( lines = Utl_getLn( line, lines) ) != NULL ) { if ( sscanf( line, "%s", name ) != 1 ) { @@ -594,6 +651,8 @@ Grp_setDsc( name, dsc ); } } + Lock_closeDatabases(); + del_DynStr( response ); return TRUE; } @@ -624,87 +683,12 @@ return res; } -static Bool -doGetCreationTimes( const char *pattern, Bool *noServerPattern ) -{ - Bool err; - Str name, line, cmd; - time_t t; - int stat; - - Utl_cpyStr( cmd, "LIST ACTIVE.TIMES" ); - if ( pattern[ 0 ] != '\0' ) - { - Utl_catStr( cmd, " " ); - Utl_catStr( cmd, pattern ); - } - - *noServerPattern = FALSE; - if ( ! putCmd( cmd ) ) - return FALSE; - stat = getStat(); - if ( pattern[ 0 ] != '\0' && stat != STAT_GRPS_FOLLOW ) - { - *noServerPattern= TRUE; - if ( ! putCmd( "LIST ACTIVE.TIMES" ) ) - return FALSE; - stat = getStat(); - } - if ( stat != STAT_GRPS_FOLLOW ) - { - Log_err( "%s failed: %s", cmd, client.lastStat ); - return FALSE; - } - while ( getTxtLn( line, &err ) && ! err ) - { - if ( sscanf( line, "%s %ld", name, &t ) != 2 ) - { - Log_err( "Unknown reply to LIST ACTIVE.TIMES: %s", line ); - continue; - } - if ( *noServerPattern && ! isGetGroup( name ) ) - continue; - if ( Grp_exists( name ) ) - { - Log_inf( "Creation time of %s: %ld", name, t ); - Grp_setCreated( name, t ); - } - } - return TRUE; -} - -Bool -Client_getCreationTimes( void ) -{ - GroupEnum *ge; - const char *pattern; - Bool doneOne, noServerPattern, res; - - Log_inf( "Querying group creation times" ); - - doneOne = FALSE; - res = TRUE; - ge = new_GetGrEn( client.serv ); - while ( res && ( pattern = GrEn_next( ge ) ) != NULL ) - { - res = doGetCreationTimes( pattern, &noServerPattern ); - doneOne = TRUE; - if ( noServerPattern ) - break; - } - - if ( ! doneOne ) - res = doGetCreationTimes( "", &noServerPattern ); - - del_GrEn( ge ); - return res; -} - Bool Client_getNewgrps( const time_t *lastTime ) { Str s; const char *p; + DynStr *response; ASSERT( *lastTime > 0 ); strftime( s, MAXCHAR, "%Y%m%d %H%M00", gmtime( lastTime ) ); @@ -721,7 +705,13 @@ Log_err( "NEWGROUPS command failed: %s", client.lastStat ); return FALSE; } - processGrps( TRUE ); + + response = collectTxt(); + if ( response == NULL ) + return FALSE; + + processGrps( DynStr_str( response ), TRUE ); + del_DynStr( response ); return TRUE; } @@ -816,6 +806,7 @@ double threadFollowTime, secPerDay, maxTime, timeSinceLastAccess; ItemList *itl; + ASSERT( Lock_gotLock() ); Log_dbg( "Checking references '%s' for thread mode", ref ); result = FALSE; itl = new_Itl( ref, " \t" ); @@ -862,6 +853,7 @@ const char *msgId, *p, *xref; int n; + ASSERT( Lock_gotLock() ); msgId = Ov_msgId( ov ); if ( Pseudo_isGeneralInfo( msgId ) ) Log_dbg( "Skipping general info '%s'", msgId ); @@ -900,15 +892,17 @@ } Bool -Client_getOver( int rmtFirst, int rmtLast, FetchMode mode ) +Client_getOver( const char *grp, int rmtFirst, int rmtLast, FetchMode mode ) { - Bool err; - size_t bytes, lines; + size_t nbytes, nlines; int rmtNumb, oldLast, cntMarked; Over *ov; Str line, subj, from, date, msgId, ref; + DynStr *response; + const char *lines; - ASSERT( strcmp( client.grp, "" ) != 0 ); + ASSERT( ! Lock_gotLock() ); + ASSERT( strcmp( grp, "" ) != 0 ); if ( ! putCmd( "XOVER %lu-%lu", rmtFirst, rmtLast ) ) return FALSE; if ( getStat() != STAT_OVERS_FOLLOW ) @@ -917,18 +911,27 @@ return FALSE; } Log_dbg( "Requesting overview for remote %lu-%lu", rmtFirst, rmtLast ); + + response = collectTxt(); + if ( response == NULL ) + return FALSE; + + if ( ! Lock_openDatabases() ) + return FALSE; + Cont_read( grp ); oldLast = Cont_last(); cntMarked = 0; - while ( getTxtLn( line, &err ) && ! err ) + lines = DynStr_str( response ); + while ( ( lines = Utl_getLn( line, lines) ) != NULL ) { if ( ! parseOvLn( line, &rmtNumb, subj, from, date, msgId, ref, - &bytes, &lines ) ) + &nbytes, &nlines ) ) Log_err( "Bad overview line: %s", line ); else if ( Cont_find( msgId ) >= 0 ) Log_inf( "Already have '%s'", msgId ); else { - ov = new_Over( subj, from, date, msgId, ref, bytes, lines ); + ov = new_Over( subj, from, date, msgId, ref, nbytes, nlines ); Cont_app( ov ); prepareEntry( ov ); if ( mode == FULL || ( mode == THREAD && needsMark( ref ) ) ) @@ -942,7 +945,11 @@ if ( oldLast != Cont_last() ) Log_inf( "Added %s %lu-%lu", client.grp, oldLast + 1, Cont_last() ); Log_inf( "%u articles marked for download in %s", cntMarked, client.grp ); - return err; + Cont_write(); + Grp_setFirstLast( grp, Cont_first(), Cont_last() ); + Lock_closeDatabases(); + del_DynStr( response ); + return TRUE; } static void @@ -950,10 +957,14 @@ { int status; + ASSERT( ! Lock_gotLock() ); Log_err( "Retrieving of %s failed: %s", msgId, reason ); + if ( ! Lock_openDatabases() ) + return; status = Db_status( msgId ); Pseudo_retrievingFailed( msgId, reason ); Db_setStatus( msgId, status | DB_RETRIEVING_FAILED ); + Lock_closeDatabases(); } static Bool @@ -961,17 +972,24 @@ { Bool err; DynStr *s = NULL; - Str line; + ASSERT( ! Lock_gotLock() ); Log_inf( "[%d/%d] Retrieving %s", artcnt, artmax, msgId ); - s = new_DynStr( 5000 ); - while ( getTxtLn( line, &err ) && ! err ) - DynStr_appLn( s, line ); - if ( ! err ) + err = TRUE; + + s = collectTxt(); + if ( s != NULL ) { const char *txt; - + txt = DynStr_str( s ); + if ( ! Lock_openDatabases() ) + { + del_DynStr( s ); + retrievingFailed( msgId, "Can't open message base" ); + return FALSE; + } + err = ! Db_storeArt( msgId, txt ); if ( ! err ) { @@ -990,16 +1008,18 @@ del_Itl( ids ); } } + Lock_closeDatabases(); + del_DynStr( s ); } else retrievingFailed( msgId, "Connection broke down" ); - del_DynStr( s ); return ! err; } void Client_retrieveArt( const char *msgId ) { + ASSERT( Lock_gotLock() ); if ( ! Db_contains( msgId ) ) { Log_err( "Article '%s' not prepared in database. Skipping.", msgId ); @@ -1010,6 +1030,8 @@ Log_inf( "Article '%s' already retrieved. Skipping.", msgId ); return; } + + Lock_closeDatabases(); if ( ! putCmd( "ARTICLE %s", msgId ) ) retrievingFailed( msgId, "Connection broke down" ); else if ( getStat() != STAT_ART_FOLLOWS ) @@ -1025,6 +1047,7 @@ DynStr *s; const char *p; + ASSERT( Lock_gotLock() ); Log_inf( "Retrieving article list" ); s = new_DynStr( (int)strlen( list ) ); p = list; @@ -1042,8 +1065,11 @@ } else DynStr_appLn( s, msgId ); + + Lock_closeDatabases(); fflush( client.out ); Log_dbg( "[S FLUSH]" ); + p = DynStr_str( s ); while ( ( p = Utl_getLn( msgId, p ) ) ) { @@ -1053,6 +1079,7 @@ break; } del_DynStr( s ); + Lock_openDatabases(); } Bool @@ -1061,6 +1088,7 @@ unsigned int stat; int estimatedNumb, first, last; + ASSERT( Lock_gotLock() ); if ( ! Grp_exists( name ) ) return FALSE; if ( ! putCmd( "GROUP %s", name ) ) @@ -1082,6 +1110,7 @@ void Client_rmtFirstLast( int *first, int *last ) { + ASSERT( Lock_gotLock() ); *first = client.rmtFirst; *last = client.rmtLast; }
--- a/src/client.h Tue Jul 25 13:12:50 2000 +0100 +++ b/src/client.h Tue Jul 25 13:14:54 2000 +0100 @@ -3,7 +3,7 @@ Noffle acting as client to other NNTP-servers - $Id: client.h 174 2000-07-19 07:02:45Z enz $ + $Id: client.h 183 2000-07-25 12:14:54Z bears $ */ #ifndef CLIENT_H @@ -42,9 +42,6 @@ Client_getDsc( void ); Bool -Client_getCreationTimes( void ); - -Bool Client_getNewgrps( const time_t *lastTime ); /* @@ -60,7 +57,7 @@ or THREAD mode, store IDs in request database. */ Bool -Client_getOver( int rmtFirst, int rmtLast, FetchMode mode ); +Client_getOver( const char *grp, int rmtFirst, int rmtLast, FetchMode mode ); /* Retrieve full article text and store it into database.
--- a/src/database.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/database.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* database.c - $Id: database.c 149 2000-06-19 21:56:12Z bears $ + $Id: database.c 183 2000-07-25 12:14:54Z bears $ Uses GNU gdbm library. Using Berkeley db (included in libc6) was cumbersome. It is based on Berkeley db 1.85, which has severe bugs @@ -515,6 +515,15 @@ return db.date; } +Over * +Db_over( const char *msgId ) +{ + if ( ! loadArt( msgId ) ) + return NULL; + return new_Over( db.subj, db.from, db.date, msgId, + db.ref, db.bytes, db.lines ); +} + Bool Db_contains( const char *msgId ) {
--- a/src/database.h Tue Jul 25 13:12:50 2000 +0100 +++ b/src/database.h Tue Jul 25 13:14:54 2000 +0100 @@ -3,7 +3,7 @@ Article database. - $Id: database.h 67 2000-05-12 17:19:38Z enz $ + $Id: database.h 183 2000-07-25 12:14:54Z bears $ */ #ifndef DB_H @@ -90,6 +90,10 @@ const char * Db_xref( const char *msgId ); +/* Overview - need to del_Over result when finished with */ +Over * +Db_over( const char *msgId ); + Bool Db_contains( const char *msgId );
--- a/src/fetch.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/fetch.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* fetch.c - $Id: fetch.c 174 2000-07-19 07:02:45Z enz $ + $Id: fetch.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -31,6 +31,7 @@ #include "fetchlist.h" #include "request.h" #include "group.h" +#include "lock.h" #include "log.h" #include "outgoing.h" #include "protocol.h" @@ -38,6 +39,8 @@ #include "util.h" #include "portable.h" +#define MAX_ARTICLE_CMDS_QUEUED 20 + struct Fetch { Bool ready; @@ -63,7 +66,8 @@ Str file; ASSERT( fetch.ready ); - snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); + 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 ); @@ -71,21 +75,22 @@ } Log_inf( "Updating groupinfo" ); Client_getNewgrps( &t ); - Utl_stamp( file ); } -void -Fetch_getNewArts( const char *name, FetchMode mode ) +/* Databases open on entry, closed on exit. */ +static void +fetchNewArts( const char *name, FetchMode mode ) { int next, first, last; if ( ! Client_changeToGrp( name ) ) { Log_err( "Could not change to group %s", name ); + Lock_closeDatabases(); return; } + Client_rmtFirstLast( &first, &last ); Cont_read( name ); - Client_rmtFirstLast( &first, &last ); next = Grp_rmtNext( name ); if ( next == GRP_RMT_NEXT_NOT_SUBSCRIBED ) next = first; @@ -94,6 +99,7 @@ Log_inf( "No new articles in %s", name ); Cont_write(); Grp_setFirstLast( name, Cont_first(), Cont_last() ); + Lock_closeDatabases(); return; } if ( first == 0 && last == 0 ) @@ -101,6 +107,7 @@ Log_inf( "No articles in %s", name ); Cont_write(); Grp_setFirstLast( name, Cont_first(), Cont_last() ); + Lock_closeDatabases(); return; } if ( next > last + 1 ) @@ -124,9 +131,19 @@ } Log_inf( "Getting remote overviews %lu-%lu for group %s", first, last, name ); - Client_getOver( first, last, mode ); - Cont_write(); - Grp_setFirstLast( name, Cont_first(), Cont_last() ); + Lock_closeDatabases(); + Client_getOver( name, first, last, mode ); +} + +void +Fetch_getNewArts( const char *name, FetchMode mode ) +{ + if ( ! Lock_openDatabases() ) + { + Log_err( "Could not open message base" ); + return; + } + fetchNewArts( name, mode ); } void @@ -134,17 +151,43 @@ { FetchMode mode; int i, size; - const char* name; + const char *name; ASSERT( fetch.ready ); + if ( ! Lock_openDatabases() ) + { + Log_err( "Could not open message base" ); + return; + } Fetchlist_read(); size = Fetchlist_size(); for ( i = 0; i < size; ++i ) { Fetchlist_element( &name, &mode, i ); if ( strcmp( Grp_server( name ), fetch.serv ) == 0 ) - Fetch_getNewArts( name, mode ); + { + fetchNewArts( name, mode ); + if ( ! Lock_openDatabases() ) + { + Log_err( "Could not open message base" ); + return; + } + } } + Lock_closeDatabases(); +} + +static void +fetchMessageList( const char *list, int *artcnt, int artmax ) +{ + const char *p; + Str msgId; + + ASSERT( Lock_gotLock() ); + Client_retrieveArtList( list, artcnt, artmax ); + p = list; + while ( ( p = Utl_getLn( msgId, p ) ) ) + Req_remove( fetch.serv, msgId ); } void @@ -152,33 +195,60 @@ { Str msgId; DynStr *list; + DynStr *fetchList; const char *p; int count = 0, artcnt = 0, artmax = 0; ASSERT( fetch.ready ); Log_dbg( "Retrieving articles marked for download" ); list = new_DynStr( 10000 ); - if ( Req_first( fetch.serv, msgId ) ) do { artmax++; } while ( Req_next( msgId ) ); - Log_inf( "%d TOTAL messages to download", artmax); + 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; + } + + /* + * 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; + } + if ( Req_first( fetch.serv, msgId ) ) + { do { DynStr_appLn( list, msgId ); - if ( ++count % 20 == 0 ) /* Send max. 20 ARTICLE cmds at once */ - { - p = DynStr_str( list ); - Client_retrieveArtList( p, &artcnt, artmax ); - while ( ( p = Utl_getLn( msgId, p ) ) ) - Req_remove( fetch.serv, msgId ); - DynStr_clear( list ); - } + 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 ); - Client_retrieveArtList( p, &artcnt, artmax ); - while ( ( p = Utl_getLn( msgId, p ) ) ) - Req_remove( fetch.serv, msgId ); + while ( ( p = Utl_getLn( msgId, p ) ) != NULL ) + { + DynStr_appLn( fetchList, msgId ); + if ( ++count % MAX_ARTICLE_CMDS_QUEUED == 0 ) + { + fetchMessageList( DynStr_str( fetchList ), &artcnt, artmax ); + DynStr_clear( fetchList ); + } + } + fetchMessageList( DynStr_str( fetchList ), &artcnt, artmax ); + + del_DynStr( fetchList ); del_DynStr( list ); + Lock_closeDatabases(); } static void @@ -256,6 +326,7 @@ Bool Fetch_init( const char *serv ) { + Lock_closeDatabases(); if ( ! connectToServ( serv ) ) return FALSE; Utl_cpyStr( fetch.serv, serv ); @@ -269,4 +340,5 @@ Client_disconnect(); fetch.ready = FALSE; Log_inf( "Fetch from '%s' finished", fetch.serv ); + Lock_openDatabases(); }
--- a/src/group.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/group.c Tue Jul 25 13:14:54 2000 +0100 @@ -7,7 +7,7 @@ loadGrp() and saveGrp(). This is done transparently. Access to the groups database is done by group name, by the functions defined in group.h. - $Id: group.c 135 2000-06-05 08:57:05Z bears $ + $Id: group.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -178,7 +178,7 @@ grp.entry.first = 1; grp.entry.last = 0; grp.entry.rmtNext = GRP_RMT_NEXT_NOT_SUBSCRIBED; - grp.entry.created = 0; + grp.entry.created = time( NULL ); grp.entry.lastAccess = 0; grp.postAllow = 'y'; saveGrp(); @@ -295,16 +295,6 @@ } void -Grp_setCreated( const char *name, time_t value ) -{ - if ( loadGrp( name ) ) - { - grp.entry.created = value; - saveGrp(); - } -} - -void Grp_setRmtNext( const char *name, int value ) { if ( loadGrp( name ) )
--- a/src/group.h Tue Jul 25 13:12:50 2000 +0100 +++ b/src/group.h Tue Jul 25 13:14:54 2000 +0100 @@ -3,7 +3,7 @@ Groups database - $Id: group.h 135 2000-06-05 08:57:05Z bears $ + $Id: group.h 183 2000-07-25 12:14:54Z bears $ */ #ifndef GRP_H @@ -100,9 +100,6 @@ Grp_setServ( const char *name, const char *value ); void -Grp_setCreated( const char *name, time_t value ); - -void Grp_setRmtNext( const char *name, int value ); void
--- a/src/lock.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/lock.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* lock.c - $Id: lock.c 60 2000-05-09 22:28:38Z uh1763 $ + $Id: lock.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -37,62 +37,79 @@ struct Lock { + const char *name; int lockFd; Str lockFile; -} lock = { -1, "" }; +}; +struct Lock globalLock = { "global", -1, "" }; +struct Lock fetchLock = { "fetch", -1, "" }; -#ifdef DEBUG +/* Check the global lock held. */ static Bool -testLock( void ) +gotLock( struct Lock *lock ) { - return ( lock.lockFd != -1 ); + return ( lock->lockFd != -1 ); } -#endif static Bool -waitLock( void ) +waitLock( struct Lock *lock, enum LockRequestWait wait ) { int fd; struct flock l; - ASSERT( ! testLock() ); - Log_dbg( "Waiting for lock ..." ); - snprintf( lock.lockFile, MAXCHAR, "%s/lock/global", Cfg_spoolDir() ); - if ( ( fd = open( lock.lockFile, O_WRONLY | O_CREAT, 0644 ) ) < 0 ) + ASSERT( ! gotLock( lock ) ); + Log_dbg( "Waiting for lock %s ...", lock->name ); + if ( lock->lockFile[ 0 ] == '\0' ) + snprintf( lock->lockFile, MAXCHAR, "%s/lock/%s", + Cfg_spoolDir(), lock->name ); + if ( ( fd = open( lock->lockFile, O_WRONLY | O_CREAT, 0644 ) ) < 0 ) { - Log_err( "Cannot open %s (%s)", lock.lockFile, strerror( errno ) ); + Log_err( "Cannot open %s (%s)", lock->lockFile, strerror( errno ) ); return FALSE; } l.l_type = F_WRLCK; l.l_start = 0; l.l_whence = SEEK_SET; l.l_len = 0; - if ( fcntl( fd, F_SETLKW, &l ) < 0 ) + if ( wait == LOCK_WAIT ) { - Log_err( "Cannot lock %s: %s", lock.lockFile, strerror( errno ) ); - return FALSE; + if ( fcntl( fd, F_SETLKW, &l ) < 0 ) + { + Log_err( "Cannot lock %s: %s", lock->lockFile, strerror( errno ) ); + close( lock->lockFd ); + return FALSE; + } } - lock.lockFd = fd; + else + { + if ( fcntl( fd, F_SETLK, &l ) < 0 ) + { + close( lock->lockFd ); + return FALSE; + } + } + + lock->lockFd = fd; Log_dbg( "Lock successful" ); return TRUE; } static void -releaseLock( void ) +releaseLock( struct Lock *lock ) { struct flock l; - ASSERT( testLock() ); + ASSERT( gotLock( lock ) ); l.l_type = F_UNLCK; l.l_start = 0; l.l_whence = SEEK_SET; l.l_len = 0; - if ( fcntl( lock.lockFd, F_SETLK, &l ) < 0 ) - Log_err( "Cannot release %s: %s", lock.lockFile, + if ( fcntl( lock->lockFd, F_SETLK, &l ) < 0 ) + Log_err( "Cannot release %s: %s", lock->lockFile, strerror( errno ) ); - close( lock.lockFd ); - lock.lockFd = -1; + close( lock->lockFd ); + lock->lockFd = -1; Log_dbg( "Releasing lock" ); } @@ -101,34 +118,34 @@ Bool Lock_openDatabases( void ) { - if ( ! waitLock() ) + if ( ! waitLock( &globalLock, LOCK_WAIT ) ) { - Log_err( "Could not get write lock" ); - return FALSE; + Log_err( "Could not get write lock" ); + return FALSE; } - if ( ! Db_open() ) + if ( ! Db_open() ) { - Log_err( "Could not open database" ); - releaseLock(); - return FALSE; + Log_err( "Could not open database" ); + releaseLock( &globalLock ); + return FALSE; } - if ( ! Grp_open() ) + if ( ! Grp_open() ) { - Log_err( "Could not open groupinfo" ); - Db_close(); - releaseLock(); - return FALSE; + Log_err( "Could not open groupinfo" ); + Db_close(); + releaseLock( &globalLock ); + return FALSE; } - if ( ! Req_open() ) + if ( ! Req_open() ) { - Log_err( "Could not initialize request database" ); - Grp_close(); - Db_close(); - releaseLock(); - return FALSE; + Log_err( "Could not initialize request database" ); + Grp_close(); + Db_close(); + releaseLock( &globalLock ); + return FALSE; } - return TRUE; + return TRUE; } @@ -136,8 +153,37 @@ void Lock_closeDatabases( void ) { - Grp_close(); - Db_close(); - Req_close(); - releaseLock(); + Grp_close(); + Db_close(); + Req_close(); + releaseLock( &globalLock ); +} + +/* Check the global lock held. */ +Bool +Lock_gotLock( void ) +{ + return gotLock( &globalLock ); } + +/* Get fetch lock. */ +Bool +Lock_getFetchLock( enum LockRequestWait wait ) +{ + return waitLock( &fetchLock, wait ); +} + +/* Release fetch lock. */ +void +Lock_releaseFetchLock( void ) +{ + releaseLock( &fetchLock ); +} + +/* Check the fetch lock held. */ +Bool +Lock_fetchLock( void ) +{ + return gotLock( &fetchLock ); +} +
--- a/src/lock.h Tue Jul 25 13:12:50 2000 +0100 +++ b/src/lock.h Tue Jul 25 13:14:54 2000 +0100 @@ -5,7 +5,7 @@ articla database, groups database, outgoing articles database, requests database. Handles global lock. - $Id: lock.h 51 2000-05-05 23:49:38Z uh1763 $ + $Id: lock.h 183 2000-07-25 12:14:54Z bears $ */ #ifndef LOCK_H @@ -17,6 +17,8 @@ #include "common.h" +enum LockRequestWait { LOCK_WAIT, LOCK_NOWAIT }; + /* Open all databases and set global lock. */ Bool Lock_openDatabases( void ); @@ -25,4 +27,20 @@ void Lock_closeDatabases( void ); +/* Check the global lock held. */ +Bool +Lock_gotLock( void ); + +/* Get fetch lock. */ +Bool +Lock_getFetchLock( enum LockRequestWait wait ); + +/* Release fetch lock. */ +void +Lock_releaseFetchLock( void ); + +/* Check the fetch lock held. */ +Bool +Lock_fetchLock( void ); + #endif
--- a/src/noffle.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/noffle.c Tue Jul 25 13:14:54 2000 +0100 @@ -10,7 +10,7 @@ received for some seconds (to allow multiple clients connect at the same time). - $Id: noffle.c 176 2000-07-19 19:47:41Z enz $ + $Id: noffle.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -156,6 +156,12 @@ { Str serv; + if ( ! Lock_getFetchLock( LOCK_NOWAIT ) ) + { + Log_err( "Another 'noffle --fetch' is in progress" ); + return; + } + Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( Fetch_init( serv ) ) @@ -174,6 +180,8 @@ Fetch_close(); } + + Lock_releaseFetchLock(); } static Bool @@ -215,8 +223,6 @@ Client_getGrps(); if ( noffle.queryDsc ) Client_getDsc(); - if ( noffle.queryTimes ) - Client_getCreationTimes(); Fetch_close(); } } @@ -306,6 +312,9 @@ Grp_create( name ); Grp_setLocal( name ); printf( "New local group '%s' created.\n", name ); + + snprintf( grp, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); + Utl_stamp( grp ); } } @@ -532,7 +541,6 @@ " -p | --post Post article on stdin\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" @@ -772,8 +780,6 @@ 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 );
--- a/src/protocol.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/protocol.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* protocol.c - $Id: protocol.c 165 2000-06-25 18:42:10Z bears $ + $Id: protocol.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -32,7 +32,7 @@ */ if ( ! fgets( line, MAXCHAR, f ) ) { - Log_dbg( "Prt_getLine failed" ); + Log_dbg( "Prt_getLn failed" ); return FALSE; } len = strlen( line );
--- 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; } }
--- a/src/util.c Tue Jul 25 13:12:50 2000 +0100 +++ b/src/util.c Tue Jul 25 13:14:54 2000 +0100 @@ -1,7 +1,7 @@ /* util.c - $Id: util.c 149 2000-06-19 21:56:12Z bears $ + $Id: util.c 183 2000-07-25 12:14:54Z bears $ */ #if HAVE_CONFIG_H @@ -265,31 +265,75 @@ static const char *MON[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL }; +/* + * Calculate the difference between local time and GMT. This is INN's + * 'always-works' method. It assumes the time differences is < 24hrs. + * Sounds reasonable to me. It also assumes it can ignore seconds. + * Returns GMT - localtime minutes. It will also trash the localtime/ + * gmtime/etc. static buffer. + */ +static int +tzDiff( void ) +{ + time_t now; + struct tm local, gmt, *tm; + static time_t nextCalc = 0; + static int res = 0; + + now = time( NULL ); + if ( now < nextCalc ) + return res; + + tm = localtime( &now ); + if ( tm == NULL ) + return 0; + local = *tm; + tm = gmtime( &now ); + if ( tm == NULL ) + return 0; + gmt = *tm; + + res = gmt.tm_yday - local.tm_yday; + if ( res < -1 ) + res = -1; /* Year rollover? */ + else if ( res > 1 ) + res = 1; + + res *= 24; + res += gmt.tm_hour - local.tm_hour; + res *= 60; + res += gmt.tm_min - local.tm_min; + + /* Need to recalc at start of next hour */ + nextCalc = now + ( 60 - local.tm_sec ) + 60 * ( 59 - local.tm_min ); + + return res; +} + void Utl_newsDate( time_t t, Str res ) { - struct tm local, localAsGmt; - time_t tlocalAsGmt; - int tzdiff, hoffset, moffset; + struct tm *local; + long tzdiff, hoffset, moffset; + + tzdiff = - tzDiff(); - local = *localtime( &t ); - memset( &localAsGmt, 0, sizeof( localAsGmt ) ); - localAsGmt.tm_sec = local.tm_sec; - localAsGmt.tm_min = local.tm_min; - localAsGmt.tm_hour = local.tm_hour; - localAsGmt.tm_mday = local.tm_mday; - localAsGmt.tm_mon = local.tm_mon; - localAsGmt.tm_year = local.tm_year; - tlocalAsGmt = mktime( &localAsGmt ); - tzdiff = (int) ( ( (long) difftime( tlocalAsGmt, t ) ) / 60 ); + local = localtime( &t ); + if ( local == NULL ) + { + Utl_cpyStr( res, "** localtime failure **" ); + return; + } + hoffset = tzdiff / 60; moffset = tzdiff % 60; - if ( moffset < 0 ) moffset = -moffset; + if ( moffset < 0 ) + moffset = - moffset; - sprintf( res, "%s, %d %s %4d %02d:%02d:%02d %+03d%02d", - DOTW[local.tm_wday], local.tm_mday, - MON[local.tm_mon], local.tm_year + 1900, - local.tm_hour, local.tm_min, local.tm_sec, + sprintf( res, "%s, %d %s %4d %02d:%02d:%02d %+03ld%02ld", + DOTW[local->tm_wday], local->tm_mday, + MON[local->tm_mon], local->tm_year + 1900, + local->tm_hour, local->tm_min, local->tm_sec, hoffset, moffset ); } @@ -297,12 +341,13 @@ Utl_parseNewsDate( const char *s ) { struct tm tm; - int wday, tzoffset, hoffset, moffset; + int wday, offset, tzoffset; char *p; time_t res; memset( &tm, 0, sizeof( tm ) ); wday = -1; + tm.tm_isdst = -1; s = nextNonWhiteSpace( s ); @@ -380,8 +425,9 @@ s += 2; else { - tzoffset = (int) strtol( s, &p, 10 ); + offset = (int) strtol( s, &p, 10 ); s = p; + tzoffset = ( offset / 100 ) * 60 + ( offset % 100 ); } /* Check for following junk */ @@ -395,9 +441,12 @@ if ( wday >= 0 && wday != tm.tm_wday ) return (time_t) -1; - moffset = tzoffset % 100; - hoffset = tzoffset / 100; - res = res - ( ( hoffset * 60 + moffset ) * 60 ); + /* Remove local time diff from res to give time as if GMT */ + res -= tzDiff() * 60; + + /* And now adjust for tzoffset */ + res -= tzoffset * 60; + return res; } @@ -429,7 +478,9 @@ for ( ; ; ) { - printf( "\nEnter date: " ); + t = time( NULL ); + Utl_newsDate( t, line ); + printf( "\n(%s) Enter date: ", line ); (void) fflush( stdout ); if ( gets( line ) == NULL || line[0] == '\0' ) break; @@ -440,8 +491,7 @@ else { Utl_newsDate( t, line ); - printf( "Utl_newsDate -> '%s'\nctime() -> '%s'\n", - line, ctime( &t ) ); + printf( "Utl_newsDate -> '%s'\n", line ); } }