1 /*
2   content.c
3 
4   $Id: content.c,v 1.21 2003/05/22 08:27:02 bears Exp $
5 */
6 
7 #if HAVE_CONFIG_H
8 #include <config.h>
9 #endif
10 
11 #include <stdio.h>
12 #include <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18 #include "common.h"
19 #include "configfile.h"
20 #include "content.h"
21 #include "group.h"
22 #include "log.h"
23 #include "over.h"
24 #include "pseudo.h"
25 #include "util.h"
26 #include "portable.h"
27 
28 struct
29 {
30     DIR *dir;           /* Directory for browsing through all
31                            groups */
32     int vecFirst;	/* First article number in vector */
33     int first;		/* First live article number */
34     int last;		/* Last article number */
35     int size;           /* Number of overviews. */
36     int max;            /* Size of elem. */
37     Over **elem;        /* Ptr to array with ptrs to overviews.
38                            NULL entries for non-existing article numbers
39                            in group. */
40     Str name;
41     Str file;
42     Bool dirty;		/* Needs writing? */
43 } cont = { NULL, 1, 1, 0, 0, 0, NULL, "", "", FALSE };
44 
45 void
Cont_app(Over * ov)46 Cont_app( Over *ov )
47 {
48     if ( cont.max < cont.size + 1 )
49     {
50         if ( ! ( cont.elem = realloc( cont.elem,
51                                       ( cont.max + 500 )
52                                       * sizeof( cont.elem[ 0 ] ) ) ) )
53             Log_fatal( "Could not realloc overview list" );
54         cont.max += 500;
55     }
56     ASSERT( cont.vecFirst > 0 );
57     if ( ov )
58         Ov_setNumb( ov, cont.vecFirst + cont.size );
59     cont.elem[ cont.size++ ] = ov;
60     cont.last = cont.vecFirst + cont.size - 1;
61     cont.dirty = TRUE;
62 }
63 
64 Bool
Cont_validNumb(int n)65 Cont_validNumb( int n )
66 {
67     int ofs = n - cont.vecFirst;
68 
69     return ( n != 0 && n >= cont.first && n <= cont.last
70              && ofs >= 0 && ofs < cont.size
71 	     && cont.elem[ ofs ] != NULL );
72 }
73 
74 void
Cont_delete(int n)75 Cont_delete( int n )
76 {
77     Over **ov;
78 
79     if ( ! Cont_validNumb( n ) )
80         return;
81     ov = &cont.elem[ n - cont.vecFirst ];
82     free( *ov );
83     *ov = NULL;
84     cont.dirty = TRUE;
85 }
86 
87 /* Remove all overviews from content. */
88 static void
clearCont(void)89 clearCont( void )
90 {
91     int i;
92 
93     for ( i = 0; i < cont.size; ++i )
94     {
95 	if ( cont.elem[ i ] != NULL )
96 	    del_Over( cont.elem[ i ] );
97 	cont.elem[ i ] = NULL;
98     }
99     cont.size = 0;
100 }
101 
102 static void
setupEmpty(const char * name)103 setupEmpty( const char *name )
104 {
105     cont.last = Grp_last( name );
106     cont.first = cont.vecFirst = cont.last + 1;
107     ASSERT( cont.first > 0 );
108 }
109 
110 /* Extend content list to size "cnt" and append NULL entries. */
111 static void
extendCont(int cnt)112 extendCont( int cnt )
113 {
114     int i, n;
115 
116     if ( cont.size < cnt )
117     {
118         n = cnt - cont.size;
119         for ( i = 0; i < n; ++i )
120             Cont_app( NULL );
121     }
122 }
123 
124 /* Discard all cached overviews, and read in the overviews of a new group
125    from its overviews file. */
126 void
Cont_read(const char * name)127 Cont_read( const char *name )
128 {
129     FILE *f;
130     Over *ov;
131     int numb;
132     Str line;
133 
134     /* Delete old overviews and make room for new ones. */
135     cont.vecFirst = 0;
136     cont.first = 0;
137     cont.last = 0;
138     Utl_cpyStr( cont.name, name );
139     clearCont();
140 
141     /* read overviews from overview file and store them in the overviews
142        list */
143     snprintf( cont.file, MAXCHAR, "%s/overview/%s", Cfg_spoolDir(), name );
144     f = fopen( cont.file, "r" );
145     if ( ! f )
146     {
147         Log_dbg( LOG_DBG_NEWSBASE, "No group overview file: %s", cont.file );
148 	setupEmpty( name );
149         return;
150     }
151     Log_dbg( LOG_DBG_NEWSBASE, "Reading %s", cont.file );
152     while ( fgets( line, MAXCHAR, f ) )
153     {
154         if ( ! ( ov = Ov_read( line ) ) )
155         {
156             Log_err( "Overview corrupted in %s: %s", name, line );
157             continue;
158         }
159         numb = Ov_numb( ov );
160         if ( numb < cont.first )
161         {
162             Log_err( "Wrong ordering in %s: %s", name, line );
163             continue;
164         }
165         if ( cont.first == 0 )
166             cont.first = cont.vecFirst = numb;
167         cont.last = numb;
168         extendCont( numb - cont.first + 1 );
169         cont.elem[ numb - cont.first ] = ov;
170     }
171     fclose( f );
172 
173 
174     if ( cont.first == 0 )
175 	setupEmpty( name );		/* Corrupt overview file recovery */
176     else
177     {
178 	int grpLast;
179 
180 	/*
181 	  Check for end article(s) being cancelled. Need to ensure we
182 	  don't re-use end article number.
183 	 */
184 	grpLast = Grp_last( name );
185 	if ( cont.last < grpLast )
186 	    extendCont( grpLast - cont.first + 1 );
187     }
188 }
189 
190 Bool
Cont_write(void)191 Cont_write( void )
192 {
193     Bool anythingWritten;
194     int i;
195     FILE *f;
196     const Over *ov, *ov_next;
197     Str tmpfname;
198     Bool writeErr;
199     int first;
200 
201     /* If nowt has changed, do nowt. */
202     if ( ! cont.dirty )
203 	return TRUE;
204 
205     /* Save the overview to temporary file in same dir. */
206     /* old tmpfnames will be expired at noffle.c:expireContents() */
207     snprintf( tmpfname, MAXCHAR, "%s/overview/.#%d.%s",
208 	      Cfg_spoolDir(), (int) getpid(), cont.name );
209     if ( ! ( f = fopen( tmpfname, "w" ) ) )
210     {
211         Log_err( "Could not open %s for writing", tmpfname );
212         return FALSE;
213     }
214     Log_dbg( LOG_DBG_NEWSBASE, "Writing %s (%lu)", tmpfname, cont.size );
215     anythingWritten = FALSE;
216     first = -1;
217     writeErr = FALSE;
218 
219     for ( i = 0; i < cont.size; ++i )
220     {
221 	ov = cont.elem[ i ];
222         if ( ov )
223         {
224 	    if ( i + 1 < cont.size )
225 		ov_next = cont.elem[ i + 1 ];
226 	    else
227 		ov_next = NULL;
228 
229 	    /*
230 	      Preserve gen info if it is followed immediately by an
231 	      article with the next number. In practice, this means
232 	      that when in auto-subscribed mode, the gen info will
233 	      remain until the 'group now subscribed' message is
234 	      expired.
235 	     */
236             if ( ! Pseudo_isGeneralInfo( Ov_msgId( ov ) )
237 		 || ( ov_next != NULL &&
238 		      Ov_numb( ov_next ) - Ov_numb( ov ) == 1 ) )
239             {
240                 anythingWritten = TRUE;
241                 if ( ! Ov_write( ov, f ) )
242                 {
243                     Log_err( "Writing of overview line to %s failed: %s",
244 			     tmpfname, strerror( errno ) );
245 		    writeErr = TRUE;
246                     break;
247                 }
248                 else
249 		{
250 		    if  ( first < 0 )
251 			first = cont.vecFirst + i;
252 		}
253             }
254         }
255     }
256     if ( fclose( f ) != 0 )
257     {
258 	Log_err( "Close of content file %s failed: %s",
259 		 tmpfname, strerror( errno ) );
260 	writeErr = TRUE;
261     }
262 
263     if ( writeErr )
264     {
265         /* Write error - leave everything as at present */
266         return FALSE;
267     }
268 
269     /*
270       If empty, remove the overview file and set first to one
271       beyond last to flag said emptiness.
272      */
273     if ( ! anythingWritten )
274     {
275 	if ( unlink( tmpfname ) < 0 )
276 	    Log_err( "Unlink of %s failed: %s", tmpfname, strerror( errno ) );
277 	if ( unlink( cont.file ) < 0 )
278         {
279 	    Log_err( "Unlink of %s failed: %s", cont.file, strerror( errno ) );
280             return FALSE;
281         }
282 	else
283 	{
284 	    cont.dirty = FALSE;
285 	    cont.first = cont.last + 1;
286 	}
287     }
288     else
289     {
290 	if ( rename( tmpfname, cont.file ) < 0 )
291         {
292 	    Log_err( "Rename of content file %s to %s failed: %s",
293 		     tmpfname, cont.file, strerror( errno ) );
294             return FALSE;
295         }
296 	else
297         {
298             ASSERT( first != -1 );
299 	    cont.dirty = FALSE;
300             cont.first = first;
301         }
302     }
303 
304     return TRUE;
305 }
306 
307 const Over *
Cont_get(int numb)308 Cont_get( int numb )
309 {
310     if ( ! Cont_validNumb( numb ) )
311         return NULL;
312     return cont.elem[ numb - cont.vecFirst ];
313 }
314 
315 int
Cont_first(void)316 Cont_first( void ) { return cont.first; }
317 
318 int
Cont_last(void)319 Cont_last( void ) { return cont.last; }
320 
321 int
Cont_find(const char * msgId)322 Cont_find( const char *msgId )
323 {
324     int i;
325     const Over *ov;
326 
327     for ( i = 0; i < cont.size; i++ )
328     {
329         if ( ( ov = cont.elem[ i ] )
330 	     && strcmp( Ov_msgId( ov ), msgId ) ==  0 )
331 	    return i + cont.vecFirst;
332     }
333 
334     return -1;
335 }
336 
337 const char *
Cont_grp(void)338 Cont_grp( void ) { return cont.name; }
339 
340 Bool
Cont_nextGrp(Str result)341 Cont_nextGrp( Str result )
342 {
343     struct dirent *d;
344 
345     ASSERT( cont.dir );
346     if ( ! ( d = readdir( cont.dir ) ) )
347     {
348 	closedir( cont.dir );
349         cont.dir = NULL;
350         return FALSE;
351     }
352     if ( ! d->d_name )
353         return FALSE;
354     if ( d->d_name[0] == '.' )
355     {
356         Str tmpfname;
357 
358 	/*
359 	 * If it is '.' or '..', skip.
360 	 * If it starts '.#', treat as a temporary file that didn't
361 	 * get deleted for some reason and flag an error and delete it.
362 	 */
363 	switch( d->d_name[1] )
364 	{
365 	case '\0':
366 	    break;
367 
368 	case '#':
369             snprintf( tmpfname, MAXCHAR, "%s/overview/%s",
370               Cfg_spoolDir(), d->d_name );
371 	    Log_err( "Bad temporary file %s - deleting.",
372 		      tmpfname );
373 	    if ( unlink( tmpfname ) < 0 )
374 		Log_err( "Unlink of %s failed: %s",
375 			 tmpfname, strerror(errno) );
376 	    break;
377 
378 	case '.':
379 	    if ( d->d_name[2] == '\0' )
380 		break;
381 	    /* Otherwise fall through - filename starting "..". */
382 
383 	default:
384 	    Log_err( "Unknown file %s in %s/overview - please delete",
385 		     d->d_name, Cfg_spoolDir() );
386 	    break;
387 	}
388 	return Cont_nextGrp( result );
389     }
390     Utl_cpyStr( result, d->d_name );
391     result[ MAXCHAR - 1 ] = '\0';
392     return TRUE;
393 }
394 
395 Bool
Cont_firstGrp(Str result)396 Cont_firstGrp( Str result )
397 {
398     Str name;
399 
400     snprintf( name, MAXCHAR, "%s/overview", Cfg_spoolDir() );
401     if ( ! ( cont.dir = opendir( name ) ) )
402     {
403         Log_err( "Cannot open %s", name );
404         return FALSE;
405     }
406     return Cont_nextGrp( result );
407 }
408 
409 Bool
Cont_exists(const char * grp)410 Cont_exists( const char *grp )
411 {
412     Str fname;
413 
414     /* Do we have a content/overview file for this group? */
415     snprintf( fname, MAXCHAR, "%s/overview/%s", Cfg_spoolDir(), grp );
416     return ( access( fname, R_OK ) == 0 );
417 }
418 
419