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