1 /* $Id$
2 Written 1999 by Tobias Ernst and released do the Public Domain.
3 This file is part of NLTOOLS, the nodelist processor of the Husky fidonet
4 software project.
5
6 Tool to apply a Nodediff upon a Nodelist.
7 References: FTS-0005, FTS-5000
8 */
9
10 #include <stdio.h>
11 #include <string.h>
12 #include <assert.h>
13 #include <ctype.h>
14 #include <stdlib.h>
15
16 #include <huskylib/huskylib.h>
17 #include "crc16.h"
18 #include "version.h"
19
20 /* It is no problem if a line grows larger than BUFSZ. This code is
21 intelligent. :-)
22 */
23 #define BUFSZ 128
24
25 unsigned short actualcrc;
26
27 char *versionStr;
28
29 /* A state machine to analyse the very first line of a nodelist for day
30 number and an optional CRC value. */
31
32 enum
33 { SCANDASH, SCANFDIGIT, DAYVAL, SCANCOLON, SCANNDIGIT, CRCVAL, AFTERCRCVAL, FINISH };
34 enum
35 { COPY, ADD, DEL, END, ILL };
36
analyze_first_line(FILE * f,unsigned short * crcnum,int * has_crc,unsigned short * daynum)37 int analyze_first_line( FILE * f, unsigned short *crcnum, int *has_crc, unsigned short *daynum )
38 {
39 char c = 0;
40 int state = SCANDASH;
41 unsigned short crc, day, hcrc, result = 1;
42 crc = day = hcrc = 0;
43
44 while( state != FINISH )
45 {
46 if( fread( &c, 1, 1, f ) != 1 )
47 {
48 result = 0;
49 state = FINISH;
50 continue;
51 }
52
53 if( c == '\n' )
54 {
55 hcrc = 1;
56 if( state < CRCVAL )
57 {
58 hcrc = 0;
59 }
60 if( state < DAYVAL )
61 {
62 result = 0; /* analysis failed */
63 }
64 state = FINISH;
65 }
66 else if( isdigit( c ) )
67 {
68 switch ( state )
69 {
70 case SCANFDIGIT:
71 day = c - '0';
72 state = DAYVAL;
73 break;
74 case SCANNDIGIT:
75 crc = c - '0';
76 state = CRCVAL;
77 break;
78 case DAYVAL:
79 day = day * 10U + ( c - '0' );
80 break;
81 case CRCVAL:
82 crc = crc * 10U + ( c - '0' );
83 break;
84 default:;
85 }
86 }
87 else if( c == '-' )
88 {
89 switch ( state )
90 {
91 case SCANDASH:
92 state = SCANFDIGIT;
93 break;
94 default:;
95 }
96 }
97 else if( c == ':' )
98 {
99 switch ( state )
100 {
101 case SCANCOLON:
102 state = SCANNDIGIT;
103 break;
104 default:;
105 }
106 }
107 else
108 {
109 switch ( state )
110 {
111 case CRCVAL:
112 state = AFTERCRCVAL;
113 break;
114 case DAYVAL:
115 state = SCANCOLON;
116 break;
117 default:;
118 }
119 }
120 }
121
122 *crcnum = crc;
123 *daynum = day;
124 *has_crc = hcrc;
125 return result;
126 }
127
128
129 /* Copy a line from in to out, no matter how long it is, and register it
130 with the CRC checker. */
131
passline(FILE * from,FILE * to)132 int passline( FILE * from, FILE * to )
133 {
134 char buf1[BUFSZ];
135 size_t l;
136
137 do
138 {
139 if( fgets( buf1, BUFSZ, from ) == NULL )
140 return -1;
141 if( fputs( buf1, to ) == EOF )
142 return -1;
143 l = strlen( buf1 );
144 crc16_process( &actualcrc, ( unsigned char * ) buf1, l );
145 if( !l )
146 return -1;
147 }
148 while( buf1[l - 1] != '\n' );
149 return 0;
150 }
151
152 /* Skip a line. */
153
skipline(FILE * from)154 int skipline( FILE * from )
155 {
156 char buf1[BUFSZ];
157 size_t l;
158
159 do
160 {
161 if( fgets( buf1, BUFSZ, from ) == NULL )
162 return -1;
163 l = strlen( buf1 );
164 if( !l )
165 return -1;
166 }
167 while( buf1[l - 1] != '\n' );
168 return 0;
169 }
170
171 /* Compare two lines. */
172
compareline(FILE * from1,FILE * from2)173 int compareline( FILE * from1, FILE * from2 )
174 {
175 char buf1[BUFSZ], buf2[BUFSZ];
176 size_t l;
177
178 do
179 {
180 if( fgets( buf1, BUFSZ, from1 ) == NULL )
181 return -1;
182 if( fgets( buf2, BUFSZ, from2 ) == NULL )
183 return -1;
184 l = strlen( buf1 );
185 if( !l )
186 return -1;
187 if( memcmp( buf1, buf2, l ) )
188 return -2;
189 }
190 while( buf1[l - 1] != '\n' );
191 return 0;
192 }
193
194 /* Analyse a Nodediff command */
195
196
readcommand(FILE * f,int * argument)197 int readcommand( FILE * f, int *argument )
198 {
199 char buffer[64];
200 int cmd, arg;
201
202 if( fgets( buffer, 64, f ) == NULL )
203 return END;
204
205 if( *buffer == '\0' || *buffer == 0x1A )
206 return END;
207
208 switch ( *buffer )
209 {
210 case 'A':
211 cmd = ADD;
212 break;
213 case 'C':
214 cmd = COPY;
215 break;
216 case 'D':
217 cmd = DEL;
218 break;
219 default:
220 return ILL;
221 }
222
223 arg = atoi( buffer + 1 );
224 if( arg < 1 )
225 return ILL;
226
227 *argument = arg;
228 return cmd;
229 }
230
231 /* Construct the name of the new nodelist from the old name plus the
232 extension of the nodediff file */
233
construct_new_filename(char * listname,char * diffname)234 char *construct_new_filename( char *listname, char *diffname )
235 {
236 int l, m;
237 char *tempname;
238
239 /* Sanity checks on the given filenames */
240 if( ( l = strlen( listname ) ) < 5 )
241 {
242 fprintf( stderr, "%s is not a valid nodelist filename.\n", listname );
243 return NULL;
244 }
245 if( listname[l - 4] != '.' || !isdigit( listname[l - 3] ) ||
246 !isdigit( listname[l - 2] ) || !isdigit( listname[l - 1] ) )
247 {
248 fprintf( stderr, "%s is not a valid nodelist filename.\n", listname );
249 return NULL;
250 }
251 if( ( m = strlen( diffname ) ) < 5 )
252 {
253 fprintf( stderr, "%s is not a valid nodediff filename.\n", listname );
254 return NULL;
255 }
256 if( diffname[m - 4] != '.' || !isdigit( diffname[m - 3] ) ||
257 !isdigit( diffname[m - 2] ) || !isdigit( diffname[m - 1] ) )
258 {
259 fprintf( stderr, "%s is not a valid nodediff filename.\n", listname );
260 return NULL;
261 }
262
263 /* test for misuse like "nldiff -n nodelist.365 nodediff.365 */
264 if( !strcmp( diffname + m - 4, listname + l - 4 ) )
265 {
266 fprintf( stderr, "%s does not seem to apply to %s.\n", diffname, listname );
267 return NULL;
268 }
269
270 /* Construct filename of new nodelist */
271 if( ( tempname = malloc( strlen( listname ) + 1 ) ) == NULL )
272 {
273 fprintf( stderr, "Out of memory.\n" );
274 return NULL;
275 }
276 strcpy( tempname, listname );
277 strcpy( tempname + l - 4, diffname + m - 4 );
278
279 return tempname;
280 }
281
282 /* Parse command line arguments */
283
284 enum
285 { REMOVE_NODELIST = 1, REMOVE_NODEDIFF = 2 };
286
parse_args(int argc,char ** argv,char ** listname,char ** diffname,int * pflags)287 int parse_args( int argc, char **argv, char **listname, char **diffname, int *pflags )
288 {
289 char *args[2];
290 int i, j = 0;
291 int flags;
292
293 flags = 0;
294
295 for( i = 1; i < argc; i++ )
296 {
297 if( argv[i][0] == '-' )
298 {
299 switch ( argv[i][1] )
300 {
301 case 'n':
302 flags = flags | REMOVE_NODELIST;
303 break;
304 case 'd':
305 flags = flags | REMOVE_NODEDIFF;
306 break;
307 default:
308 return 0;
309 }
310 if( argv[i][2] )
311 {
312 return 0;
313 }
314 continue;
315 }
316
317 if( j < 2 )
318 {
319 args[j++] = argv[i];
320 }
321 }
322
323 if( j == 2 )
324 {
325 *listname = args[0];
326 *diffname = args[1];
327 *pflags = flags;
328 return 1;
329 }
330 return 0;
331 }
332
333 /* Usage help */
334
usage(void)335 void usage( void )
336 {
337 versionStr = GenVersionStr( "nldiff", VER_MAJOR, VER_MINOR, VER_PATCH, VER_BRANCH, cvs_date );
338 fprintf( stderr, "%s\n", versionStr );
339 fprintf( stderr, "Usage:\n"
340 " nldiff [-n] [-d] LISTNAME.DNR DIFFNAME.DNR\n\n"
341 " The -n option causes the original Nodelist to be deleted if the new\n"
342 " nodelist could be successfully generated.\n"
343 " The -d option causes the Nodediff file to be deleted if the new\n"
344 " nodelist could be successfully generated.\n\n"
345 "Example:\n"
346 " nldiff -n NODELIST.253 NODEDIFF.260\n\n"
347 "Remarks:\n"
348 " If you want a tool that automatically updates your nodelist\n"
349 " without you having to manually specify the day number file\n"
350 " extensions, use \"nlupdate\". It will call nldiff internally.\n" );
351 }
352
353 /* Main program */
354
main(int argc,char ** argv)355 int main( int argc, char **argv )
356 {
357 FILE *fn = NULL, *fd = NULL, *fo = NULL;
358 char *listname = NULL, *diffname = NULL, *tempname = NULL;
359 int cmd, arg, i, hascrc, rv, expnewday;
360 unsigned short crc, newday;
361 int crci = 0;
362 int flags = 0;
363
364 /* parse the command line */
365 if( !parse_args( argc, argv, &listname, &diffname, &flags ) )
366 {
367 usage( );
368 return 8;
369 }
370
371 /* construct the filename of the new nodelist */
372 if( ( tempname = construct_new_filename( listname, diffname ) ) == NULL )
373 return 8;
374 expnewday = atoi( tempname + strlen( tempname ) - 3 );
375
376 /* open the files */
377 if( ( fn = fopen( listname, "rb" ) ) == NULL )
378 {
379 fprintf( stderr, "Cannot open %s.\n", listname );
380 goto abnormal;
381 }
382 if( ( fd = fopen( diffname, "rb" ) ) == NULL )
383 {
384 fprintf( stderr, "Cannot open %s.\n", diffname );
385 goto abnormal;
386 }
387 if( ( fo = fopen( tempname, "w+b" ) ) == NULL )
388 {
389 fprintf( stderr, "Cannot create %s.\n", tempname );
390 goto abnormal;
391 }
392
393 /* Test if the diff's first line matches the list's first line */
394 switch ( compareline( fn, fd ) )
395 {
396 case -2:
397 fprintf( stderr, "%s does not seem to apply to %s.\n", diffname, listname );
398 goto abnormal;
399 case -1:
400 fprintf( stderr, "I/O error.\n" );
401 goto abnormal;
402 default:; /* match! */
403 }
404 if( fseek( fn, 0L, SEEK_SET ) )
405 {
406 fprintf( stderr, "File seek error.\n" );
407 goto abnormal;
408 }
409
410 /* Interpret the commands in the Diff file */
411 do
412 {
413 cmd = readcommand( fd, &arg );
414 switch ( cmd )
415 {
416 case COPY:
417 for( i = 0; i < arg; i++ )
418 {
419 if( passline( fn, fo ) )
420 {
421 fprintf( stderr, "Copy failed (%d / %d)\n", i, arg );
422 goto abnormal;
423 }
424 if( !crci )
425 {
426 crc16_init( &actualcrc );
427 crci = 1;
428 }
429 }
430
431 break;
432 case ADD:
433 for( i = 0; i < arg; i++ )
434 {
435 if( passline( fd, fo ) )
436 {
437 fprintf( stderr, "Add failed (%d / %d)\n", i, arg );
438 goto abnormal;
439 }
440 if( !crci )
441 {
442 crc16_init( &actualcrc );
443 crci = 1;
444 }
445 }
446 break;
447 case DEL:
448 for( i = 0; i < arg; i++ )
449 if( skipline( fn ) )
450 {
451 fprintf( stderr, "Delete failed (%d / %d)\n", i, arg );
452 goto abnormal;
453 }
454 break;
455 case END:
456 break;
457 default:
458 fprintf( stderr, "Illegal command encountered.\n" );
459 goto abnormal;
460 }
461 }
462 while( cmd != END );
463 fputc( 0x1A, fo );
464
465 /* Now determine the actual day number and the expected CRC of the newly
466 written nodelist file */
467 if( ( fseek( fo, 0L, SEEK_SET ) ) || ( !analyze_first_line( fo, &crc, &hascrc, &newday ) ) )
468 {
469 fprintf( stderr, "New file is not a valid nodelist.\n" );
470 goto abnormal;
471 }
472
473 /* Compare the CRC values */
474 crc16_finalize( &actualcrc );
475 if( hascrc && actualcrc != crc )
476 {
477 fprintf( stderr, "New file does not pass CRC test.\n" );
478 goto abnormal;
479 }
480 if( newday != expnewday )
481 {
482 fprintf( stderr, "New day number and diff file name do not match.\n" );
483 goto abnormal;
484 }
485
486 fclose( fn );
487 fclose( fd );
488 fclose( fo );
489 rv = 0;
490
491 /* Delete files that are not needed any more, if the user requested it. */
492 if( ( flags & REMOVE_NODELIST ) && remove( listname ) )
493 {
494 fprintf( stderr, "Cannot remove old nodelist file %s.\n", listname );
495 rv = 8;
496 }
497 if( ( flags & REMOVE_NODEDIFF ) && remove( diffname ) )
498 {
499 fprintf( stderr, "Cannot remove old nodediff file %s.\n", diffname );
500 rv = 8;
501 }
502
503 return rv;
504
505 abnormal:
506 fprintf( stderr, "Processing aborted.\n" );
507 if( fn != NULL )
508 fclose( fn );
509 if( fd != NULL )
510 fclose( fd );
511 if( fo != NULL )
512 fclose( fo );
513 if( tempname != NULL )
514 {
515 if( fo != NULL )
516 remove( tempname );
517 free( tempname );
518 }
519 return 8;
520 }
521