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