1 /*
2   request.c
3 
4   Collection of articles that are marked for download.
5 
6   $Id: request.c,v 1.8 2002/12/27 21:48:25 bears Exp $
7 */
8 
9 #if HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12 
13 #include <stdio.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <assert.h>
21 #include "configfile.h"
22 #include "log.h"
23 #include "portable.h"
24 #include "request.h"
25 #include "util.h"
26 
27 
28 /* This struct keeps record of the message IDs that are to be fetched from
29    one particular host. Several of these are chained together via the
30    "next" pointer, if we have several servers.
31 */
32 
33 struct Reqserv;
34 typedef struct Reqserv Reqserv;
35 
36 struct Reqserv {
37     char*    serv;                /* Server the messages are to be requested
38 				     from */
39     char**   reql;                /* List of message IDs of requested
40 				     messages. Some entries (that have been
41 				     deleted) may be NULL */
42     int      reql_length;         /* Number of string pointers in reql,
43 				     including NULL entries */
44     int      reql_capacity;       /* maximum number of string pointers reql
45 				     can hold */
46     Bool     dirty;               /* whether the request list needs to be
47 				     rewritten to disk */
48     Reqserv* next;                /* next Reqserv in list */
49     time_t   mtime;               /* last modification time of request file */
50 };
51 
52 /* List of servers */
53 static Reqserv* reqserv = 0;
54 
55 /* sanity check */
56 static Bool is_open = FALSE;
57 
58 /* for Req_first/Req_next */
59 static char** iterator = 0;
60 static char** iterator_end = 0;
61 
62 
63 /* local functions */
64 static Reqserv* newReqserv      ( const char* serv );
65 static Bool     getReqserv      ( const char* serv, Reqserv** rsz );
66 static void     fileRequest     ( Str file, const char *serv );
67 static char**   searchMsgId     ( const Reqserv * rs, const char *msgId );
68 static Bool     storeMsgId      ( Reqserv* rs, const char* msgId );
69 static Bool     readRequestfile ( const char* serv, Reqserv** rsz );
70 static time_t   get_mtime       ( const char* serv );
71 
72 /* read modification time of request file */
73 static time_t
get_mtime(const char * serv)74 get_mtime( const char* serv )
75 {
76     Str filename;
77     struct stat stat1;
78 
79     fileRequest( filename, serv );
80     stat( filename, &stat1 );
81     return stat1.st_mtime;
82 }
83 
84 
85 /* create new Reqserv and queue it */
86 static Reqserv*
newReqserv(const char * serv)87 newReqserv( const char* serv )
88 {
89     Reqserv *rs;
90 
91     rs = malloc( sizeof( Reqserv ) );
92     if ( rs == NULL )
93 	Log_fatal( "Malloc of Reqserv failed." );
94 
95     Utl_allocAndCpy( &(rs->serv), serv );
96     rs->reql = 0;
97     rs->reql_length = 0;
98     rs->reql_capacity = 0;
99     rs->next = reqserv;
100     rs->dirty = FALSE;
101     rs->mtime = 0;
102     reqserv = rs;
103     return rs;
104 }
105 
106 
107 /* get Reqserv for given server, and save it in "rsz". Load from file as
108    necessary. Return TRUE on success. Otherwise log errors and return
109    FALSE. (details in errno)
110 */
111 static Bool
getReqserv(const char * serv,Reqserv ** rsz)112 getReqserv( const char* serv, Reqserv** rsz )
113 {
114     Reqserv* rs;
115 
116     for ( rs = reqserv; rs; rs = rs->next )
117 	if ( !strcmp( serv, rs->serv ) )
118 	{
119 	    *rsz = rs;
120 	    return TRUE;
121 	}
122     return readRequestfile( serv, rsz );
123 }
124 
125 
126 /* Delete Reqserv from cache, if not up-to-date */
127 static void
cleanupReqserv(void)128 cleanupReqserv( void )
129 {
130     Reqserv *rs, *prev, *next;
131 
132     rs = reqserv;
133     prev = NULL;
134     while ( rs != NULL )
135     {
136 	ASSERT( ! rs->dirty );
137 	next = rs->next;
138 	if ( get_mtime( rs->serv ) != rs->mtime )
139 	{
140 	    if ( prev != NULL )
141 		prev->next = next;
142 	    else
143 		reqserv = next;
144 	    free( rs->serv );
145 	    rs->serv = NULL;
146 	    free( rs->reql );
147 	    rs->reql = NULL;
148 	    free( rs );
149 	}
150 	prev = rs;
151 	rs = next;
152     }
153 }
154 
155 /* Save name of file storing requests from server "serv" in "file" */
156 static void
fileRequest(Str file,const char * serv)157 fileRequest( Str file, const char *serv )
158 {
159     snprintf( file, MAXCHAR, "%s/requested/%s", Cfg_spoolDir(), serv);
160 }
161 
162 
163 /* Search for msgid in Reqserv. Return pointer to list entry. Return 0 if
164    list does not contain msgid. */
165 static char**
searchMsgId(const Reqserv * rs,const char * msgId)166 searchMsgId( const Reqserv * rs, const char *msgId )
167 {
168     char** rz;
169     ASSERT( rs != 0 );
170 
171     if ( !rs->reql )
172 	return 0;
173 
174     for ( rz = rs->reql; rz < rs->reql + rs->reql_length; rz++ )
175 	if ( *rz && !strcmp( *rz, msgId ) )
176 	    return rz;
177 
178     return 0;
179 }
180 
181 
182 Bool
Req_contains(const char * serv,const char * msgId)183 Req_contains( const char *serv, const char *msgId )
184 {
185     Reqserv *rs;
186     ASSERT( is_open );
187     if (getReqserv( serv, &rs ) == FALSE)
188 	return FALSE;
189     return ( searchMsgId( rs, msgId ) != NULL );
190 }
191 
192 
193 static Bool
storeMsgId(Reqserv * rs,const char * msgId)194 storeMsgId( Reqserv* rs, const char* msgId )
195 {
196     char *msgid;
197 
198     if ( searchMsgId( rs, msgId ) )
199 	/* already recorded */
200 	return FALSE;
201 
202     Utl_allocAndCpy( &msgid, msgId );
203 
204     if ( rs->reql_length >= rs->reql_capacity )
205     {
206 	int c1 = rs->reql_capacity * 2 + 10;
207 	rs->reql = ( char ** ) realloc( rs->reql, c1 * sizeof( char * ) );
208 	if ( rs->reql == NULL )
209 	    Log_fatal( "Could not realloc requests." );
210 	rs->reql_capacity = c1;
211     }
212 
213     *( rs->reql + rs->reql_length++ ) = msgid;
214     rs->dirty = TRUE;
215     return TRUE;
216 }
217 
218 static Bool
appRequest(Reqserv * rs,const char * msgId)219 appRequest( Reqserv* rs, const char *msgId )
220 {
221     Str filename;
222     FILE*  file;
223 
224     fileRequest(filename, rs->serv);
225     Log_dbg( LOG_DBG_REQUESTS, "appending to request file %s", filename );
226 
227     if (Log_check((file = fopen(filename, "a")) != 0,
228 		  "could not open %s for appending: %s",
229 		  filename, strerror(errno)))
230 	return FALSE;
231 
232     if (Log_check(    fputs(msgId, file) != EOF
233 		      &&  fputs("\n", file) != EOF,
234 		      "write error: %s", strerror(errno)))
235 	return FALSE;
236 
237     if (Log_check(fclose(file) != EOF,
238 		  "could not close %s properly: %s\n",
239 		  filename, strerror(errno)))
240 	return FALSE;
241 
242     return TRUE;
243 }
244 
245 
246 /* Add request for message "msgIg" from server "serv". Return TRUE iff
247    successful.
248 */
249 Bool
Req_add(const char * serv,const char * msgId)250 Req_add( const char *serv, const char *msgId )
251 {
252     Reqserv* rs;
253     ASSERT( is_open );
254     Log_dbg( LOG_DBG_REQUESTS, "Marking %s on %s for download", msgId, serv );
255 
256     if ( ! getReqserv( serv, &rs ) )
257 	return FALSE;
258     if ( ! storeMsgId(rs, msgId) ) /* already recorded */
259 	return TRUE;
260     return appRequest(rs, msgId);
261 }
262 
263 static Bool
readLn(Str line,FILE * f)264 readLn( Str line, FILE* f )
265 {
266     size_t len;
267 
268     if ( ! fgets( line, MAXCHAR, f ) )
269         return FALSE;
270     len = strlen( line );
271     if ( line[ len - 1 ] == '\n' )
272         line[ len - 1 ] = '\0';
273     return TRUE;
274 }
275 
276 /* Read request file into new, non-queued Reqserv. Save new Reqserv in
277    "rsz" and return TRUE on success. Returns FALSE on failure, see errno.
278    If the file doesn't exist, an empty Reqserv is returned.
279 */
280 static Bool
readRequestfile(const char * serv,Reqserv ** rsz)281 readRequestfile( const char* serv, Reqserv** rsz )
282 {
283     Str           filename;
284     Str           line;
285     FILE*         file;
286     Reqserv*      rs;
287 
288     fileRequest( filename, serv );
289     Log_dbg( LOG_DBG_REQUESTS, "reading request file %s", filename );
290 
291     file = fopen( filename, "r" );
292     if ( !file && ( errno == ENOENT ) )
293     {
294 	*rsz = newReqserv( serv );
295 	(*rsz)->mtime = get_mtime( serv );
296 	return TRUE;
297     }
298     if ( Log_check( file != 0,
299 		    "could not open %s for reading: %s",
300 		    filename, strerror( errno ) ) )
301 	return FALSE;
302 
303     rs = *rsz = newReqserv( serv );
304 
305     while( readLn( line, file ) )
306     {
307 	char *line1 = Utl_stripWhiteSpace( line );
308 	if ( *line1 )
309 	    storeMsgId( rs, line1 );
310     }
311 
312     rs->dirty = FALSE;
313 
314     if ( Log_check( fclose( file ) != EOF,
315 		    "could not close %s properly: %s\n",
316 		    filename, strerror( errno ) ) )
317 	return FALSE;
318 
319     return TRUE;
320 }
321 
322 
323 /* Write out request file for given Reqserv. Return TRUE on success. If an
324    I/O error occurs, it is logged, and FALSE is returned.
325 */
326 static Bool
writeRequestfile(Reqserv * rs)327 writeRequestfile( Reqserv* rs )
328 {
329     Str    filename;
330     FILE*  file;
331     char** z;
332 
333     fileRequest( filename, rs->serv );
334     Log_dbg( LOG_DBG_REQUESTS, "writing request file %s", filename );
335 
336     if ( Log_check( ( file = fopen( filename, "w" ) ) != 0,
337 		   "could not open %s for writing: %s",
338 		   filename, strerror( errno ) ) )
339 	return FALSE;
340 
341     if ( rs->reql )
342 	for ( z = rs->reql; z < rs->reql+rs->reql_length; z++ )
343 	    if ( *z )
344 	    {
345 		if ( Log_check( fputs( *z, file ) != EOF
346 				&& fputs( "\n", file ) != EOF,
347 				"write error: %s", strerror( errno ) ) )
348 		    return FALSE;
349 	    }
350 
351     if ( Log_check( fclose( file ) != EOF,
352 		    "could not close %s properly: %s\n",
353 		     filename, strerror( errno ) ) )
354 	return FALSE;
355 
356     rs->dirty = FALSE;
357     rs->mtime = get_mtime( rs->serv );
358 
359     return TRUE;
360 }
361 
362 
363 void
Req_remove(const char * serv,const char * msgId)364 Req_remove( const char *serv, const char *msgId )
365 {
366     Reqserv* rs;
367     char** z;
368 
369     ASSERT( is_open );
370     Log_dbg( LOG_DBG_REQUESTS, "Req_remove(\"%s\", \"%s\")", serv, msgId );
371 
372     if ( !getReqserv(serv, &rs) )
373         return;
374 
375     z = searchMsgId( rs, msgId );
376     if ( ! z )
377         return;
378 
379     free( *z );
380     *z = 0;
381     rs->dirty = TRUE;
382 }
383 
384 
385 Bool
Req_first(const char * serv,Str msgId)386 Req_first( const char *serv, Str msgId )
387 {
388     Reqserv* rs;
389 
390     ASSERT( is_open );
391     ASSERT( !iterator && !iterator_end );
392 
393     if ( !getReqserv( serv, &rs ) )
394 	return FALSE;
395 
396     if ( !rs->reql )
397 	return FALSE;
398 
399     iterator = rs->reql - 1;
400     iterator_end = rs->reql + rs->reql_length;
401 
402     return Req_next( msgId );
403 }
404 
405 
406 Bool
Req_next(Str msgId)407 Req_next( Str msgId )
408 {
409     ASSERT( is_open );
410     ASSERT( iterator && iterator_end );
411 
412     if ( iterator >= iterator_end )
413 	return FALSE;
414     iterator++;
415 
416     while ( iterator < iterator_end )
417     {
418 	if ( !*iterator )
419 	    iterator++;
420 	else
421 	{
422 	    Utl_cpyStr( msgId, *iterator );
423 	    return TRUE;
424 	}
425     }
426 
427     iterator = iterator_end = 0;
428     return FALSE;
429 }
430 
431 
432 /* Get exclusive access to all request files. Maybe we already have had it,
433    and the cache is outdated. So we delete request files, which have
434    changed recently, from cache. These files will be reread on demand.
435 */
436 Bool
Req_open(void)437 Req_open( void )
438 {
439     Log_dbg( LOG_DBG_REQUESTS, "opening request database" );
440     ASSERT( !is_open );
441 
442     cleanupReqserv();
443     is_open = TRUE;
444     return TRUE;
445 }
446 
447 
448 /* Do not occupy the request files any longer. Write any changes to disk.
449    Return TRUE on success, FALSE if an IO error occurs. */
450 void
Req_close(void)451 Req_close(void)
452 {
453     Bool ret = TRUE;
454     Reqserv* rs;
455     Log_dbg( LOG_DBG_REQUESTS,
456 	     "closing request database, writing changes to disk" );
457     ASSERT( is_open );
458 
459     for ( rs = reqserv; rs; rs = rs->next )
460     {
461 	if ( rs->dirty )
462 	{
463 	    if ( !writeRequestfile( rs ) )
464 		ret = FALSE;
465 	}
466     }
467 
468     is_open = FALSE;
469 }
470