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