blob: f9304d064fd6fb9df50f3c34802ff6141f456cd7 [file] [log] [blame]
Guido van Rossumd8eb2111998-08-04 17:57:28 +00001/*
2** commands.c - POSIX 1003.2 "ar" command
3**
4** This isn't a pure POSIX 1003.2 ar; it only manipulates Metrowerks
5** Library files, not general-purpose POSIX 1003.2 format archives.
6**
7** Dec. 14, 1997 Chris Herborth (chrish@kagi.com)
8**
9** This code is donated to the PUBLIC DOMAIN. You can use, abuse, modify,
10** redistribute, steal, or otherwise manipulate this code. No restrictions
11** at all. If you laugh at this code, you can't use it.
12**
13** This "ar" was implemented using IEEE Std 1003.2-1992 as the basis for
14** the interface, and Metrowerk's published docs detailing their library
15** format. Look inside for clues about how reality differs from MW's
16** documentation on BeOS...
17*/
18
19#include <support/Errors.h>
20#include <support/byteorder.h>
21#ifndef NO_DEBUG
22#include <assert.h>
23#define ASSERT(cond) assert(cond)
24#else
25#define ASSERT(cond) ((void)0)
26#endif
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <unistd.h>
31#include <limits.h>
32#include <string.h>
33#include <utime.h>
34#include <errno.h>
35#include <sys/stat.h>
36
37#include "mwlib.h"
38#include "commands.h"
39
40#ifndef FALSE
41#define FALSE (0)
42#endif
43#ifndef TRUE
44#define TRUE (!FALSE)
45#endif
46
47static const char *rcs_version_id = "$Id$";
48
49/* ----------------------------------------------------------------------
50** Local functions
51**
52** do_match() - find the index of the file, if it's in the archive; return
53** TRUE if found, else FALSE
54**/
55static int do_match( MWLib *lib, const char *file, int *idx );
56
57static int do_match( MWLib *lib, const char *file, int *idx )
58{
59 int which = 0;
60 char *name_ptr;
61
62 ASSERT( lib != NULL );
63 ASSERT( file != NULL );
64 ASSERT( idx != NULL );
65
66 /* Skip over the path, if any, so we can compare just the file name.
67 */
68 name_ptr = strrchr( file, '/' );
69 if( name_ptr == NULL ) {
70 name_ptr = (char *)file;
71 } else {
72 name_ptr++;
73 }
74
75 for( which = 0; which < lib->header.num_objects; which++ ) {
76 if( !strcmp( name_ptr, lib->names[which] ) ) {
77 *idx = which;
78 return TRUE;
79 }
80 }
81
82 return FALSE;
83}
84
85/* ----------------------------------------------------------------------
86** Delete an archive member.
87**
88** This isn't really optimal; you could make a more efficient version
89** using a linked list instead of arrays for the data. This was easier
90** to write, and speed shouldn't be that big a deal here... you're not
91** likely to be dealing with thousands of files.
92*/
93static status_t delete_lib_entry( MWLib *lib, int idx, int verbose );
94
95status_t do_delete( const char *archive_name, char **files, int verbose )
96{
97 status_t retval = B_OK;
98 MWLib lib;
99 int idx = 0;
100 int which = 0;
101
102 ASSERT( archive_name != NULL );
103
104 if( files == NULL ) {
105 fprintf( stderr, "ar: %s, nothing to do\n", archive_name );
106 return B_ERROR;
107 }
108
109 retval = load_MW_lib( &lib, archive_name );
110 if( retval != B_OK ) {
111 switch( retval ) {
112 case B_FILE_NOT_FOUND:
113 fprintf( stderr, "ar: %s, file not found\n", archive_name );
114 return retval;
115 break;
116
117 default:
118 return retval;
119 break;
120 }
121 }
122
123 /* Delete the specified files.
124 */
125 for( idx = 0; files[idx] != NULL; idx++ ) {
126 if( do_match( &lib, files[idx], &which ) ) {
127 retval = delete_lib_entry( &lib, which, verbose );
128 }
129 which = 0;
130 }
131
132 /* Write the new file.
133 */
134 retval = write_MW_lib( &lib, archive_name );
135
136 return retval;
137}
138
139static status_t delete_lib_entry( MWLib *lib, int which, int verbose )
140{
141 uint32 new_num;
142 MWLibFile *new_files = NULL;
143 char **new_names = NULL;
144 char **new_data = NULL;
145 int ctr = 0;
146 int idx = 0;
147
148 ASSERT( lib != NULL );
149 ASSERT( which <= lib->header.num_objects );
150
151 new_num = lib->header.num_objects - 1;
152
153 new_files = (MWLibFile *)malloc( new_num * ( sizeof( MWLibFile ) ) );
154 new_names = (char **)malloc( new_num * ( sizeof( char * ) ) );
155 new_data = (char **)malloc( new_num * ( sizeof( char * ) ) );
156 if( new_files == NULL || new_names == NULL || new_data == NULL ) {
157 return B_NO_MEMORY;
158 }
159
160 /* Copy the contents of the old lib to the new lib, skipping the one
161 ** we want to delete.
162 */
163 for( ctr = 0; ctr < lib->header.num_objects; ctr++ ) {
164 if( ctr != which ) {
165 memcpy( &(new_files[idx]), &(lib->files[ctr]),
166 sizeof( MWLibFile ) );
167 new_names[idx] = lib->names[ctr];
168 new_data[idx] = lib->data[ctr];
169
170 idx++;
171 } else {
172 /* Free up the name and data.
173 */
174 if( verbose ) {
175 printf( "d - %s\n", lib->names[ctr] );
176 }
177
178 free( lib->names[idx] );
179 lib->names[idx] = NULL;
180 free( lib->data[idx] );
181 lib->data[idx] = NULL;
182 }
183 }
184
185 /* Free up the old lib's data.
186 */
187 free( lib->files );
188 free( lib->names );
189 free( lib->data );
190
191 lib->files = new_files;
192 lib->names = new_names;
193 lib->data = new_data;
194
195 lib->header.num_objects = new_num;
196
197 return B_OK;
198}
199
200/* ----------------------------------------------------------------------
201** Print an archive member to stdout.
202*/
203static status_t print_lib_entry( MWLib *lib, int idx, int verbose );
204
205status_t do_print( const char *archive_name, char **files, int verbose )
206{
207 status_t retval = B_OK;
208 MWLib lib;
209 int idx = 0;
210
211 ASSERT( archive_name != NULL );
212
213 retval = load_MW_lib( &lib, archive_name );
214 if( retval != B_OK ) {
215 switch( retval ) {
216 case B_FILE_NOT_FOUND:
217 fprintf( stderr, "ar: %s, file not found\n", archive_name );
218 return retval;
219 break;
220
221 default:
222 return retval;
223 break;
224 }
225 }
226
227 if( files == NULL ) {
228 /* Then we print the entire archive.
229 */
230 for( idx = 0; idx < lib.header.num_objects; idx++ ) {
231 retval = print_lib_entry( &lib, idx, verbose );
232 }
233 } else {
234 /* Then we print the specified files.
235 */
236 int which = 0;
237
238 for( idx = 0; files[idx] != NULL; idx++ ) {
239 if( do_match( &lib, files[idx], &which ) ) {
240 retval = print_lib_entry( &lib, which, verbose );
241 }
242 which = 0;
243 }
244 }
245
246 return retval;
247}
248
249static status_t print_lib_entry( MWLib *lib, int idx, int verbose )
250{
251 int recs;
252
253 ASSERT( lib != NULL );
254
255 if( verbose ) {
256 printf( "\n<%s>\n\n", lib->names[idx] );
257 }
258
259 recs = fwrite( lib->data[idx], lib->files[idx].object_size, 1, stdout );
260 fflush( stdout );
261 if( recs != 1 ) {
262 fprintf( stderr, "error printing %s, %s\n", lib->names[idx],
263 strerror( errno ) );
264 return B_OK;
265 }
266
267 return B_OK;
268}
269
270/* ----------------------------------------------------------------------
271** Add/replace/update files in an archive.
272*/
273static status_t add_lib_entry( MWLib *lib, const char *filename, int verbose );
274static status_t replace_lib_entry( MWLib *lib, const char *filename,
275 int idx, int verbose );
276static status_t load_lib_file( const char *filename,
277 char **data, MWLibFile *info );
278
279static status_t load_lib_file( const char *filename,
280 char **data, MWLibFile *info )
281{
282 status_t retval = B_OK;
283 struct stat s;
284 FILE *fp;
285 uint32 recs;
286
287 ASSERT( filename != NULL );
288 ASSERT( info != NULL );
289
290 /* Initialize the info area.
291 */
292 info->m_time = (time_t)0; /* Only this... */
293 info->off_filename = 0;
294 info->off_fullpath = 0;
295 info->off_object = 0;
296 info->object_size = 0; /* ... and this will actually be updated. */
297
298 /* stat() the file to get the info we need.
299 */
300 retval = stat( filename, &s );
301 if( retval != 0 ) {
302 fprintf( stderr, "ar: can't stat %s, %s\n", filename,
303 strerror( errno ) );
304 return B_FILE_NOT_FOUND;
305 }
306
307 /* Possible errors here; if you have an object that's larger
308 ** than a size_t can hold (malloc() can only allocate a size_t size,
309 ** not a full off_t)...
310 */
311 if( s.st_size > (off_t)ULONG_MAX ) {
312 fprintf( stderr, "ar: %s is too large!\n", filename );
313 return B_NO_MEMORY;
314 }
315
316 /* Allocate enough memory to hold the file data.
317 */
318 *data = (char *)malloc( (size_t)s.st_size );
319 if( *data == NULL ) {
320 fprintf( stderr, "ar: can't allocate memory for %s\n", filename );
321 return B_NO_MEMORY;
322 }
323
324 /* Read the file's data.
325 */
326 fp = fopen( filename, "r" );
327 if( fp == NULL ) {
328 fprintf( stderr, "ar: can't open %s, %s\n", filename,
329 strerror( errno ) );
330 retval = B_FILE_NOT_FOUND;
331 goto free_data_return;
332 }
333
334 recs = fread( *data, (size_t)s.st_size, 1, fp );
335 if( recs != 1 ) {
336 fprintf( stderr, "ar: can't read %s, %s\n", filename,
337 strerror( errno ) );
338 retval = B_IO_ERROR;
339 goto close_fp_return;
340 }
341
342 fclose( fp );
343
344 /* Now that all the stuff that can fail has succeeded, fill in the info
345 ** we need.
346 */
347 info->m_time = s.st_mtime;
348 info->object_size = (uint32)s.st_size;
349
350 return B_OK;
351
352 /* How we should return if an error occurred.
353 */
354close_fp_return:
355 fclose( fp );
356
357free_data_return:
358 free( *data );
359 *data = NULL;
360
361 return retval;
362}
363
364status_t do_replace( const char *archive_name, char **files, int verbose,
365 int create, int update )
366{
367 status_t retval = B_OK;
368 MWLib lib;
369 int idx = 0;
370 int which = 0;
371
372 ASSERT( archive_name != NULL );
373
374 memset( &lib, 0, sizeof( MWLib ) );
375
376 if( files == NULL ) {
377 fprintf( stderr, "ar: %s, nothing to do\n", archive_name );
378 return B_ERROR;
379 }
380
381 retval = load_MW_lib( &lib, archive_name );
382 if( retval != B_OK ) {
383 switch( retval ) {
384 case B_FILE_NOT_FOUND:
385 lib.header.magicword = 'MWOB';
386 lib.header.magicproc = 'PPC ';
387 lib.header.magicflags = 0;
388 lib.header.version = 1;
389
390 if( lib.files != NULL ) {
391 free( lib.files );
392 lib.files = NULL;
393 }
394
395 if( lib.names != NULL ) {
396 free( lib.names );
397 lib.names = NULL;
398 }
399
400 if( lib.data != NULL ) {
401 lib.data = NULL;
402 }
403
404 if( !create ) {
405 fprintf( stderr, "ar: creating %s\n", archive_name );
406 }
407
408 if( update ) {
409 fprintf( stderr, "ar: nothing to do for %s\n", archive_name );
410 return retval;
411 }
412 break;
413
414 default:
415 return retval;
416 break;
417 }
418 }
419
420 for( idx = 0; files[idx] != NULL; idx++ ) {
421 if( do_match( &lib, files[idx], &which ) ) {
422 /* Then the file exists, and we need to replace it or update it.
423 */
424 if( update ) {
425 /* Compare m_times
426 ** then replace this entry
427 */
428 struct stat s;
429
430 retval = stat( files[idx], &s );
431 if( retval != 0 ) {
432 fprintf( stderr, "ar: can't stat %s, %s\n", files[idx],
433 strerror( errno ) );
434 }
435
436 if( s.st_mtime >= lib.files[which].m_time ) {
437 retval = replace_lib_entry( &lib, files[idx], which,
438 verbose );
439 } else {
440 fprintf( stderr, "ar: a newer %s is already in %s\n",
441 files[idx], archive_name );
442 }
443 } else {
444 /* replace this entry
445 */
446 retval = replace_lib_entry( &lib, files[idx], which, verbose );
447 }
448 } else {
449 /* add this entry
450 */
451 retval = add_lib_entry( &lib, files[idx], verbose );
452 }
453 }
454
455 /* Write the new file.
456 */
457 retval = write_MW_lib( &lib, archive_name );
458
459 return retval;
460}
461
462static status_t add_lib_entry( MWLib *lib, const char *filename, int verbose )
463{
464 status_t retval = B_OK;
465 uint32 new_num_objects;
466 uint32 idx;
467
468 ASSERT( lib != NULL );
469 ASSERT( filename != NULL );
470
471 /* Find out how many objects we'll have after we add this one.
472 */
473 new_num_objects = lib->header.num_objects + 1;
474 idx = lib->header.num_objects;
475
476 /* Attempt to reallocate the MWLib's buffers. If one of these fails,
477 ** we could leak a little memory, but it shouldn't be a big deal in
478 ** a short-lived app like this.
479 */
480 lib->files = (MWLibFile *)realloc( lib->files,
481 sizeof(MWLibFile) * new_num_objects );
482 lib->names = (char **)realloc( lib->names,
483 sizeof(char *) * new_num_objects );
484 lib->data = (char **)realloc( lib->data,
485 sizeof(char *) * new_num_objects );
486 if( lib->files == NULL || lib->names == NULL || lib->data == NULL ) {
487 fprintf( stderr, "ar: can't allocate memory to add %s\n", filename );
488 return B_NO_MEMORY;
489 }
490
491 /* Load the file's data and info into the MWLib structure.
492 */
493 retval = load_lib_file( filename, &(lib->data[idx]), &(lib->files[idx]) );
494 if( retval != B_OK ) {
495 fprintf( stderr, "ar: error adding %s, %s\n", filename,
496 strerror( errno ) );
497
498 return retval;
499 }
500
501 /* Save a copy of the filename. This is where we leak
502 ** sizeof(MWLibFile) + 2 * sizeof(char *) bytes because we don't
503 ** shrink lib->files, lib->names, and lib->data.
504 */
505 lib->names[idx] = strdup( filename );
506 if( lib->names == NULL ) {
507 fprintf( stderr, "ar: error allocating memory for filename\n" );
508
509 return B_NO_MEMORY;
510 }
511
512 /* Now that everything's OK, we can update the MWLib header.
513 */
514 lib->header.num_objects++;
515
516 /* Give a little feedback.
517 */
518 if( verbose ) {
519 printf( "a - %s\n", filename );
520 }
521
522 return B_OK;
523}
524
525static status_t replace_lib_entry( MWLib *lib, const char *filename,
526 int idx, int verbose )
527{
528 char *buff;
529 MWLibFile info;
530 char *dup_name;
531
532 status_t retval = B_OK;
533
534 ASSERT( lib != NULL );
535 ASSERT( filename != NULL );
536 ASSERT( idx <= lib->header.num_objects );
537
538 /* Load the file's data and info into the MWLib structure.
539 **
540 ** We'll do it safely so we don't end up writing a bogus library in
541 ** the event of failure.
542 */
543 retval = load_lib_file( filename, &buff, &info );
544 if( retval != B_OK ) {
545 fprintf( stderr, "ar: error adding %s, %s\n", filename,
546 strerror( errno ) );
547
548 return retval;
549 }
550
551 /* Attempt to allocate memory for a duplicate of the file name.
552 */
553 dup_name = strdup( filename );
554 if( dup_name == NULL ) {
555 fprintf( stderr, "ar: unable to allocate memory for filename\n",
556 filename );
557
558 free( buff );
559
560 return B_NO_MEMORY;
561 }
562
563 /* All is well, so let's update the MWLib object appropriately.
564 */
565 lib->files[idx].m_time = info.m_time;
566 lib->files[idx].off_filename = 0;
567 lib->files[idx].off_fullpath = 0;
568 lib->files[idx].off_object = 0;
569 lib->files[idx].object_size = info.object_size;
570
571 lib->data[idx] = buff;
572
573 free( lib->names[idx] );
574 lib->names[idx] = dup_name;
575
576 /* Give a little feedback.
577 */
578 if( verbose ) {
579 printf( "r - %s\n", filename );
580 }
581
582 return B_OK;
583}
584
585/* ----------------------------------------------------------------------
586** Print the table for an archive.
587*/
588static status_t table_lib_entry( MWLib *lib, int idx, int verbose );
589
590status_t do_table( const char *archive_name, char **files, int verbose )
591{
592 status_t retval = B_OK;
593 MWLib lib;
594 int idx = 0;
595
596 ASSERT( archive_name != NULL );
597
598 retval = load_MW_lib( &lib, archive_name );
599 if( retval != B_OK ) {
600 switch( retval ) {
601 case B_FILE_NOT_FOUND:
602 fprintf( stderr, "ar: %s, file not found\n", archive_name );
603 return retval;
604 break;
605
606 default:
607 return retval;
608 break;
609 }
610 }
611
612 if( files == NULL ) {
613 /* Then we print the table for the entire archive.
614 */
615 for( idx = 0; idx < lib.header.num_objects; idx++ ) {
616 retval = table_lib_entry( &lib, idx, verbose );
617 }
618 } else {
619 /* Then we print the table for the specified files.
620 */
621 int which = 0;
622
623 for( idx = 0; files[idx] != NULL; idx++ ) {
624 if( do_match( &lib, files[idx], &which ) ) {
625 retval = table_lib_entry( &lib, which, verbose );
626 }
627 which = 0;
628 }
629 }
630
631 return retval;
632}
633
634static status_t table_lib_entry( MWLib *lib, int idx, int verbose )
635{
636 struct tm *t;
637 char month_buff[4];
638
639 ASSERT( lib != NULL );
640
641 if( verbose ) {
642 t = localtime( &(lib->files[idx].m_time) );
643 if( t == NULL ) {
644 fprintf( stderr, "localtime() failed, %s\n",
645 strerror( errno ) );
646 return B_OK;
647 }
648
649 if( strftime( month_buff, sizeof( month_buff ),
650 "%b", t ) == 0 ) {
651 /* TODO: error message */
652 fprintf( stderr, "strftime() failed, %s\n",
653 strerror( errno ) );
654 return B_OK;
655 }
656
657 /* I wish POSIX allowed for a nicer format; even using tabs
658 * between some entries would be better.
659 */
660 printf( "%s %u/%u %u %s %d %d:%d %d %s\n",
661 "-rw-r--r--", /* simulated mode */
662 getuid(), getgid(), /* simulated uid & gid */
663 lib->files[idx].object_size,
664 month_buff, /* abbreviated month */
665 t->tm_mon, /* day of month */
666 t->tm_hour, /* hour */
667 t->tm_min, /* minute */
668 t->tm_year, /* year */
669 lib->names[idx] );
670 } else {
671 printf( "%s\n", lib->names[idx] );
672 }
673
674 return B_OK;
675}
676
677/* ----------------------------------------------------------------------
678** Extract one or more files from the archive.
679*/
680static status_t extract_lib_entry( MWLib *lib, int idx, int verbose );
681
682status_t do_extract( const char *archive_name, char **files, int verbose )
683{
684 status_t retval = B_OK;
685 MWLib lib;
686 int idx = 0;
687
688 ASSERT( archive_name != NULL );
689
690 retval = load_MW_lib( &lib, archive_name );
691 if( retval != B_OK ) {
692 switch( retval ) {
693 case B_FILE_NOT_FOUND:
694 fprintf( stderr, "ar: %s, file not found\n", archive_name );
695 return retval;
696 break;
697
698 default:
699 return retval;
700 break;
701 }
702 }
703
704 if( files == NULL ) {
705 /* Then we extract all the files.
706 */
707 for( idx = 0; idx < lib.header.num_objects; idx++ ) {
708 retval = extract_lib_entry( &lib, idx, verbose );
709 }
710 } else {
711 /* Then we extract the specified files.
712 */
713 int which = 0;
714
715 for( idx = 0; files[idx] != NULL; idx++ ) {
716 if( do_match( &lib, files[idx], &which ) ) {
717 retval = extract_lib_entry( &lib, which, verbose );
718 }
719 which = 0;
720 }
721 }
722
723 return retval;
724}
725
726static status_t extract_lib_entry( MWLib *lib, int idx, int verbose )
727{
728 FILE *fp;
729 int recs;
730 status_t retval = B_OK;
731 struct stat s;
732 mode_t mode_bits = 0666; /* TODO: use user's umask() instead */
733
734 ASSERT( lib != NULL );
735
736 /* Delete the file if it already exists.
737 */
738 retval = access( lib->names[idx], F_OK );
739 if( retval == 0 ) {
740 retval = stat( lib->names[idx], &s );
741 if( retval != 0 ) {
742 fprintf( stderr, "ar: can't stat %s, %s\n", lib->names[idx],
743 strerror( errno ) );
744 } else {
745 mode_bits = s.st_mode;
746 }
747 retval = unlink( lib->names[idx] );
748 if( retval != 0 ) {
749 fprintf( stderr, "ar: can't unlink %s, %s\n", lib->names[idx],
750 strerror( retval ) );
751 return B_OK;
752 }
753 }
754
755 /* Write the file.
756 */
757 if( verbose ) {
758 printf( "x - %s\n", lib->names[idx] );
759 }
760
761 fp = fopen( lib->names[idx], "w" );
762 if( fp == NULL ) {
763 fprintf( stderr, "ar: can't open %s for write, %s\n", lib->names[idx],
764 strerror( errno ) );
765 return B_OK;
766 }
767
768 recs = fwrite( lib->data[idx], lib->files[idx].object_size, 1, fp );
769 if( recs != 1 ) {
770 fprintf( stderr, "error writing %s, %s\n", lib->names[idx],
771 strerror( errno ) );
772 }
773
774 retval = fclose( fp );
775
776 /* Set the newly extracted file's modification time to the time
777 ** stored in the archive.
778 */
779 retval = stat( lib->names[idx], &s );
780 if( retval != 0 ) {
781 fprintf( stderr, "ar: can't stat %s, %s\n", lib->names[idx],
782 strerror( errno ) );
783 } else {
784 struct utimbuf new_times;
785
786 new_times.actime = s.st_atime;
787 new_times.modtime = lib->files[idx].m_time;
788
789 retval = utime( lib->names[idx], &new_times );
790 if( retval != 0 ) {
791 fprintf( stderr, "ar: can't set modification time for %s, %s\n",
792 lib->names[idx], strerror( retval ) );
793 }
794 }
795
796 /* Set the newly extracted file's mode.
797 */
798 retval = chmod( lib->names[idx], mode_bits );
799 if( retval != 0 ) {
800 fprintf( stderr, "ar: unable to change file mode for %s, %s\n",
801 lib->names[idx], strerror( errno ) );
802 }
803
804 /* Set the newly extracted file's type.
805 */
806 setfiletype( lib->names[idx], "application/x-mw-library" );
807
808 return B_OK;
809}