| /* |
| ** commands.c - POSIX 1003.2 "ar" command |
| ** |
| ** This isn't a pure POSIX 1003.2 ar; it only manipulates Metrowerks |
| ** Library files, not general-purpose POSIX 1003.2 format archives. |
| ** |
| ** Dec. 14, 1997 Chris Herborth (chrish@kagi.com) |
| ** |
| ** This code is donated to the PUBLIC DOMAIN. You can use, abuse, modify, |
| ** redistribute, steal, or otherwise manipulate this code. No restrictions |
| ** at all. If you laugh at this code, you can't use it. |
| ** |
| ** This "ar" was implemented using IEEE Std 1003.2-1992 as the basis for |
| ** the interface, and Metrowerk's published docs detailing their library |
| ** format. Look inside for clues about how reality differs from MW's |
| ** documentation on BeOS... |
| */ |
| |
| #include <support/Errors.h> |
| #include <support/byteorder.h> |
| #ifndef NO_DEBUG |
| #include <assert.h> |
| #define ASSERT(cond) assert(cond) |
| #else |
| #define ASSERT(cond) ((void)0) |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <utime.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| |
| #include "mwlib.h" |
| #include "commands.h" |
| |
| #ifndef FALSE |
| #define FALSE (0) |
| #endif |
| #ifndef TRUE |
| #define TRUE (!FALSE) |
| #endif |
| |
| static const char *rcs_version_id = "$Id$"; |
| |
| /* ---------------------------------------------------------------------- |
| ** Local functions |
| ** |
| ** do_match() - find the index of the file, if it's in the archive; return |
| ** TRUE if found, else FALSE |
| **/ |
| static int do_match( MWLib *lib, const char *file, int *idx ); |
| |
| static int do_match( MWLib *lib, const char *file, int *idx ) |
| { |
| int which = 0; |
| char *name_ptr; |
| |
| ASSERT( lib != NULL ); |
| ASSERT( file != NULL ); |
| ASSERT( idx != NULL ); |
| |
| /* Skip over the path, if any, so we can compare just the file name. |
| */ |
| name_ptr = strrchr( file, '/' ); |
| if( name_ptr == NULL ) { |
| name_ptr = (char *)file; |
| } else { |
| name_ptr++; |
| } |
| |
| for( which = 0; which < lib->header.num_objects; which++ ) { |
| if( !strcmp( name_ptr, lib->names[which] ) ) { |
| *idx = which; |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| /* ---------------------------------------------------------------------- |
| ** Delete an archive member. |
| ** |
| ** This isn't really optimal; you could make a more efficient version |
| ** using a linked list instead of arrays for the data. This was easier |
| ** to write, and speed shouldn't be that big a deal here... you're not |
| ** likely to be dealing with thousands of files. |
| */ |
| static status_t delete_lib_entry( MWLib *lib, int idx, int verbose ); |
| |
| status_t do_delete( const char *archive_name, char **files, int verbose ) |
| { |
| status_t retval = B_OK; |
| MWLib lib; |
| int idx = 0; |
| int which = 0; |
| |
| ASSERT( archive_name != NULL ); |
| |
| if( files == NULL ) { |
| fprintf( stderr, "ar: %s, nothing to do\n", archive_name ); |
| return B_ERROR; |
| } |
| |
| retval = load_MW_lib( &lib, archive_name ); |
| if( retval != B_OK ) { |
| switch( retval ) { |
| case B_FILE_NOT_FOUND: |
| fprintf( stderr, "ar: %s, file not found\n", archive_name ); |
| return retval; |
| break; |
| |
| default: |
| return retval; |
| break; |
| } |
| } |
| |
| /* Delete the specified files. |
| */ |
| for( idx = 0; files[idx] != NULL; idx++ ) { |
| if( do_match( &lib, files[idx], &which ) ) { |
| retval = delete_lib_entry( &lib, which, verbose ); |
| } |
| which = 0; |
| } |
| |
| /* Write the new file. |
| */ |
| retval = write_MW_lib( &lib, archive_name ); |
| |
| return retval; |
| } |
| |
| static status_t delete_lib_entry( MWLib *lib, int which, int verbose ) |
| { |
| uint32 new_num; |
| MWLibFile *new_files = NULL; |
| char **new_names = NULL; |
| char **new_data = NULL; |
| int ctr = 0; |
| int idx = 0; |
| |
| ASSERT( lib != NULL ); |
| ASSERT( which <= lib->header.num_objects ); |
| |
| new_num = lib->header.num_objects - 1; |
| |
| new_files = (MWLibFile *)malloc( new_num * ( sizeof( MWLibFile ) ) ); |
| new_names = (char **)malloc( new_num * ( sizeof( char * ) ) ); |
| new_data = (char **)malloc( new_num * ( sizeof( char * ) ) ); |
| if( new_files == NULL || new_names == NULL || new_data == NULL ) { |
| return B_NO_MEMORY; |
| } |
| |
| /* Copy the contents of the old lib to the new lib, skipping the one |
| ** we want to delete. |
| */ |
| for( ctr = 0; ctr < lib->header.num_objects; ctr++ ) { |
| if( ctr != which ) { |
| memcpy( &(new_files[idx]), &(lib->files[ctr]), |
| sizeof( MWLibFile ) ); |
| new_names[idx] = lib->names[ctr]; |
| new_data[idx] = lib->data[ctr]; |
| |
| idx++; |
| } else { |
| /* Free up the name and data. |
| */ |
| if( verbose ) { |
| printf( "d - %s\n", lib->names[ctr] ); |
| } |
| |
| free( lib->names[idx] ); |
| lib->names[idx] = NULL; |
| free( lib->data[idx] ); |
| lib->data[idx] = NULL; |
| } |
| } |
| |
| /* Free up the old lib's data. |
| */ |
| free( lib->files ); |
| free( lib->names ); |
| free( lib->data ); |
| |
| lib->files = new_files; |
| lib->names = new_names; |
| lib->data = new_data; |
| |
| lib->header.num_objects = new_num; |
| |
| return B_OK; |
| } |
| |
| /* ---------------------------------------------------------------------- |
| ** Print an archive member to stdout. |
| */ |
| static status_t print_lib_entry( MWLib *lib, int idx, int verbose ); |
| |
| status_t do_print( const char *archive_name, char **files, int verbose ) |
| { |
| status_t retval = B_OK; |
| MWLib lib; |
| int idx = 0; |
| |
| ASSERT( archive_name != NULL ); |
| |
| retval = load_MW_lib( &lib, archive_name ); |
| if( retval != B_OK ) { |
| switch( retval ) { |
| case B_FILE_NOT_FOUND: |
| fprintf( stderr, "ar: %s, file not found\n", archive_name ); |
| return retval; |
| break; |
| |
| default: |
| return retval; |
| break; |
| } |
| } |
| |
| if( files == NULL ) { |
| /* Then we print the entire archive. |
| */ |
| for( idx = 0; idx < lib.header.num_objects; idx++ ) { |
| retval = print_lib_entry( &lib, idx, verbose ); |
| } |
| } else { |
| /* Then we print the specified files. |
| */ |
| int which = 0; |
| |
| for( idx = 0; files[idx] != NULL; idx++ ) { |
| if( do_match( &lib, files[idx], &which ) ) { |
| retval = print_lib_entry( &lib, which, verbose ); |
| } |
| which = 0; |
| } |
| } |
| |
| return retval; |
| } |
| |
| static status_t print_lib_entry( MWLib *lib, int idx, int verbose ) |
| { |
| int recs; |
| |
| ASSERT( lib != NULL ); |
| |
| if( verbose ) { |
| printf( "\n<%s>\n\n", lib->names[idx] ); |
| } |
| |
| recs = fwrite( lib->data[idx], lib->files[idx].object_size, 1, stdout ); |
| fflush( stdout ); |
| if( recs != 1 ) { |
| fprintf( stderr, "error printing %s, %s\n", lib->names[idx], |
| strerror( errno ) ); |
| return B_OK; |
| } |
| |
| return B_OK; |
| } |
| |
| /* ---------------------------------------------------------------------- |
| ** Add/replace/update files in an archive. |
| */ |
| static status_t add_lib_entry( MWLib *lib, const char *filename, int verbose ); |
| static status_t replace_lib_entry( MWLib *lib, const char *filename, |
| int idx, int verbose ); |
| static status_t load_lib_file( const char *filename, |
| char **data, MWLibFile *info ); |
| |
| static status_t load_lib_file( const char *filename, |
| char **data, MWLibFile *info ) |
| { |
| status_t retval = B_OK; |
| struct stat s; |
| FILE *fp; |
| uint32 recs; |
| |
| ASSERT( filename != NULL ); |
| ASSERT( info != NULL ); |
| |
| /* Initialize the info area. |
| */ |
| info->m_time = (time_t)0; /* Only this... */ |
| info->off_filename = 0; |
| info->off_fullpath = 0; |
| info->off_object = 0; |
| info->object_size = 0; /* ... and this will actually be updated. */ |
| |
| /* stat() the file to get the info we need. |
| */ |
| retval = stat( filename, &s ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: can't stat %s, %s\n", filename, |
| strerror( errno ) ); |
| return B_FILE_NOT_FOUND; |
| } |
| |
| /* Possible errors here; if you have an object that's larger |
| ** than a size_t can hold (malloc() can only allocate a size_t size, |
| ** not a full off_t)... |
| */ |
| if( s.st_size > (off_t)ULONG_MAX ) { |
| fprintf( stderr, "ar: %s is too large!\n", filename ); |
| return B_NO_MEMORY; |
| } |
| |
| /* Allocate enough memory to hold the file data. |
| */ |
| *data = (char *)malloc( (size_t)s.st_size ); |
| if( *data == NULL ) { |
| fprintf( stderr, "ar: can't allocate memory for %s\n", filename ); |
| return B_NO_MEMORY; |
| } |
| |
| /* Read the file's data. |
| */ |
| fp = fopen( filename, "r" ); |
| if( fp == NULL ) { |
| fprintf( stderr, "ar: can't open %s, %s\n", filename, |
| strerror( errno ) ); |
| retval = B_FILE_NOT_FOUND; |
| goto free_data_return; |
| } |
| |
| recs = fread( *data, (size_t)s.st_size, 1, fp ); |
| if( recs != 1 ) { |
| fprintf( stderr, "ar: can't read %s, %s\n", filename, |
| strerror( errno ) ); |
| retval = B_IO_ERROR; |
| goto close_fp_return; |
| } |
| |
| fclose( fp ); |
| |
| /* Now that all the stuff that can fail has succeeded, fill in the info |
| ** we need. |
| */ |
| info->m_time = s.st_mtime; |
| info->object_size = (uint32)s.st_size; |
| |
| return B_OK; |
| |
| /* How we should return if an error occurred. |
| */ |
| close_fp_return: |
| fclose( fp ); |
| |
| free_data_return: |
| free( *data ); |
| *data = NULL; |
| |
| return retval; |
| } |
| |
| status_t do_replace( const char *archive_name, char **files, int verbose, |
| int create, int update ) |
| { |
| status_t retval = B_OK; |
| MWLib lib; |
| int idx = 0; |
| int which = 0; |
| |
| ASSERT( archive_name != NULL ); |
| |
| memset( &lib, 0, sizeof( MWLib ) ); |
| |
| if( files == NULL ) { |
| fprintf( stderr, "ar: %s, nothing to do\n", archive_name ); |
| return B_ERROR; |
| } |
| |
| retval = load_MW_lib( &lib, archive_name ); |
| if( retval != B_OK ) { |
| switch( retval ) { |
| case B_FILE_NOT_FOUND: |
| lib.header.magicword = 'MWOB'; |
| lib.header.magicproc = 'PPC '; |
| lib.header.magicflags = 0; |
| lib.header.version = 1; |
| |
| if( lib.files != NULL ) { |
| free( lib.files ); |
| lib.files = NULL; |
| } |
| |
| if( lib.names != NULL ) { |
| free( lib.names ); |
| lib.names = NULL; |
| } |
| |
| if( lib.data != NULL ) { |
| lib.data = NULL; |
| } |
| |
| if( !create ) { |
| fprintf( stderr, "ar: creating %s\n", archive_name ); |
| } |
| |
| if( update ) { |
| fprintf( stderr, "ar: nothing to do for %s\n", archive_name ); |
| return retval; |
| } |
| break; |
| |
| default: |
| return retval; |
| break; |
| } |
| } |
| |
| for( idx = 0; files[idx] != NULL; idx++ ) { |
| if( do_match( &lib, files[idx], &which ) ) { |
| /* Then the file exists, and we need to replace it or update it. |
| */ |
| if( update ) { |
| /* Compare m_times |
| ** then replace this entry |
| */ |
| struct stat s; |
| |
| retval = stat( files[idx], &s ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: can't stat %s, %s\n", files[idx], |
| strerror( errno ) ); |
| } |
| |
| if( s.st_mtime >= lib.files[which].m_time ) { |
| retval = replace_lib_entry( &lib, files[idx], which, |
| verbose ); |
| } else { |
| fprintf( stderr, "ar: a newer %s is already in %s\n", |
| files[idx], archive_name ); |
| } |
| } else { |
| /* replace this entry |
| */ |
| retval = replace_lib_entry( &lib, files[idx], which, verbose ); |
| } |
| } else { |
| /* add this entry |
| */ |
| retval = add_lib_entry( &lib, files[idx], verbose ); |
| } |
| } |
| |
| /* Write the new file. |
| */ |
| retval = write_MW_lib( &lib, archive_name ); |
| |
| return retval; |
| } |
| |
| static status_t add_lib_entry( MWLib *lib, const char *filename, int verbose ) |
| { |
| status_t retval = B_OK; |
| uint32 new_num_objects; |
| uint32 idx; |
| |
| ASSERT( lib != NULL ); |
| ASSERT( filename != NULL ); |
| |
| /* Find out how many objects we'll have after we add this one. |
| */ |
| new_num_objects = lib->header.num_objects + 1; |
| idx = lib->header.num_objects; |
| |
| /* Attempt to reallocate the MWLib's buffers. If one of these fails, |
| ** we could leak a little memory, but it shouldn't be a big deal in |
| ** a short-lived app like this. |
| */ |
| lib->files = (MWLibFile *)realloc( lib->files, |
| sizeof(MWLibFile) * new_num_objects ); |
| lib->names = (char **)realloc( lib->names, |
| sizeof(char *) * new_num_objects ); |
| lib->data = (char **)realloc( lib->data, |
| sizeof(char *) * new_num_objects ); |
| if( lib->files == NULL || lib->names == NULL || lib->data == NULL ) { |
| fprintf( stderr, "ar: can't allocate memory to add %s\n", filename ); |
| return B_NO_MEMORY; |
| } |
| |
| /* Load the file's data and info into the MWLib structure. |
| */ |
| retval = load_lib_file( filename, &(lib->data[idx]), &(lib->files[idx]) ); |
| if( retval != B_OK ) { |
| fprintf( stderr, "ar: error adding %s, %s\n", filename, |
| strerror( errno ) ); |
| |
| return retval; |
| } |
| |
| /* Save a copy of the filename. This is where we leak |
| ** sizeof(MWLibFile) + 2 * sizeof(char *) bytes because we don't |
| ** shrink lib->files, lib->names, and lib->data. |
| */ |
| lib->names[idx] = strdup( filename ); |
| if( lib->names == NULL ) { |
| fprintf( stderr, "ar: error allocating memory for filename\n" ); |
| |
| return B_NO_MEMORY; |
| } |
| |
| /* Now that everything's OK, we can update the MWLib header. |
| */ |
| lib->header.num_objects++; |
| |
| /* Give a little feedback. |
| */ |
| if( verbose ) { |
| printf( "a - %s\n", filename ); |
| } |
| |
| return B_OK; |
| } |
| |
| static status_t replace_lib_entry( MWLib *lib, const char *filename, |
| int idx, int verbose ) |
| { |
| char *buff; |
| MWLibFile info; |
| char *dup_name; |
| |
| status_t retval = B_OK; |
| |
| ASSERT( lib != NULL ); |
| ASSERT( filename != NULL ); |
| ASSERT( idx <= lib->header.num_objects ); |
| |
| /* Load the file's data and info into the MWLib structure. |
| ** |
| ** We'll do it safely so we don't end up writing a bogus library in |
| ** the event of failure. |
| */ |
| retval = load_lib_file( filename, &buff, &info ); |
| if( retval != B_OK ) { |
| fprintf( stderr, "ar: error adding %s, %s\n", filename, |
| strerror( errno ) ); |
| |
| return retval; |
| } |
| |
| /* Attempt to allocate memory for a duplicate of the file name. |
| */ |
| dup_name = strdup( filename ); |
| if( dup_name == NULL ) { |
| fprintf( stderr, "ar: unable to allocate memory for filename\n", |
| filename ); |
| |
| free( buff ); |
| |
| return B_NO_MEMORY; |
| } |
| |
| /* All is well, so let's update the MWLib object appropriately. |
| */ |
| lib->files[idx].m_time = info.m_time; |
| lib->files[idx].off_filename = 0; |
| lib->files[idx].off_fullpath = 0; |
| lib->files[idx].off_object = 0; |
| lib->files[idx].object_size = info.object_size; |
| |
| lib->data[idx] = buff; |
| |
| free( lib->names[idx] ); |
| lib->names[idx] = dup_name; |
| |
| /* Give a little feedback. |
| */ |
| if( verbose ) { |
| printf( "r - %s\n", filename ); |
| } |
| |
| return B_OK; |
| } |
| |
| /* ---------------------------------------------------------------------- |
| ** Print the table for an archive. |
| */ |
| static status_t table_lib_entry( MWLib *lib, int idx, int verbose ); |
| |
| status_t do_table( const char *archive_name, char **files, int verbose ) |
| { |
| status_t retval = B_OK; |
| MWLib lib; |
| int idx = 0; |
| |
| ASSERT( archive_name != NULL ); |
| |
| retval = load_MW_lib( &lib, archive_name ); |
| if( retval != B_OK ) { |
| switch( retval ) { |
| case B_FILE_NOT_FOUND: |
| fprintf( stderr, "ar: %s, file not found\n", archive_name ); |
| return retval; |
| break; |
| |
| default: |
| return retval; |
| break; |
| } |
| } |
| |
| if( files == NULL ) { |
| /* Then we print the table for the entire archive. |
| */ |
| for( idx = 0; idx < lib.header.num_objects; idx++ ) { |
| retval = table_lib_entry( &lib, idx, verbose ); |
| } |
| } else { |
| /* Then we print the table for the specified files. |
| */ |
| int which = 0; |
| |
| for( idx = 0; files[idx] != NULL; idx++ ) { |
| if( do_match( &lib, files[idx], &which ) ) { |
| retval = table_lib_entry( &lib, which, verbose ); |
| } |
| which = 0; |
| } |
| } |
| |
| return retval; |
| } |
| |
| static status_t table_lib_entry( MWLib *lib, int idx, int verbose ) |
| { |
| struct tm *t; |
| char month_buff[4]; |
| |
| ASSERT( lib != NULL ); |
| |
| if( verbose ) { |
| t = localtime( &(lib->files[idx].m_time) ); |
| if( t == NULL ) { |
| fprintf( stderr, "localtime() failed, %s\n", |
| strerror( errno ) ); |
| return B_OK; |
| } |
| |
| if( strftime( month_buff, sizeof( month_buff ), |
| "%b", t ) == 0 ) { |
| /* TODO: error message */ |
| fprintf( stderr, "strftime() failed, %s\n", |
| strerror( errno ) ); |
| return B_OK; |
| } |
| |
| /* I wish POSIX allowed for a nicer format; even using tabs |
| * between some entries would be better. |
| */ |
| printf( "%s %u/%u %u %s %d %d:%d %d %s\n", |
| "-rw-r--r--", /* simulated mode */ |
| getuid(), getgid(), /* simulated uid & gid */ |
| lib->files[idx].object_size, |
| month_buff, /* abbreviated month */ |
| t->tm_mon, /* day of month */ |
| t->tm_hour, /* hour */ |
| t->tm_min, /* minute */ |
| t->tm_year, /* year */ |
| lib->names[idx] ); |
| } else { |
| printf( "%s\n", lib->names[idx] ); |
| } |
| |
| return B_OK; |
| } |
| |
| /* ---------------------------------------------------------------------- |
| ** Extract one or more files from the archive. |
| */ |
| static status_t extract_lib_entry( MWLib *lib, int idx, int verbose ); |
| |
| status_t do_extract( const char *archive_name, char **files, int verbose ) |
| { |
| status_t retval = B_OK; |
| MWLib lib; |
| int idx = 0; |
| |
| ASSERT( archive_name != NULL ); |
| |
| retval = load_MW_lib( &lib, archive_name ); |
| if( retval != B_OK ) { |
| switch( retval ) { |
| case B_FILE_NOT_FOUND: |
| fprintf( stderr, "ar: %s, file not found\n", archive_name ); |
| return retval; |
| break; |
| |
| default: |
| return retval; |
| break; |
| } |
| } |
| |
| if( files == NULL ) { |
| /* Then we extract all the files. |
| */ |
| for( idx = 0; idx < lib.header.num_objects; idx++ ) { |
| retval = extract_lib_entry( &lib, idx, verbose ); |
| } |
| } else { |
| /* Then we extract the specified files. |
| */ |
| int which = 0; |
| |
| for( idx = 0; files[idx] != NULL; idx++ ) { |
| if( do_match( &lib, files[idx], &which ) ) { |
| retval = extract_lib_entry( &lib, which, verbose ); |
| } |
| which = 0; |
| } |
| } |
| |
| return retval; |
| } |
| |
| static status_t extract_lib_entry( MWLib *lib, int idx, int verbose ) |
| { |
| FILE *fp; |
| int recs; |
| status_t retval = B_OK; |
| struct stat s; |
| mode_t mode_bits = 0666; /* TODO: use user's umask() instead */ |
| |
| ASSERT( lib != NULL ); |
| |
| /* Delete the file if it already exists. |
| */ |
| retval = access( lib->names[idx], F_OK ); |
| if( retval == 0 ) { |
| retval = stat( lib->names[idx], &s ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: can't stat %s, %s\n", lib->names[idx], |
| strerror( errno ) ); |
| } else { |
| mode_bits = s.st_mode; |
| } |
| retval = unlink( lib->names[idx] ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: can't unlink %s, %s\n", lib->names[idx], |
| strerror( retval ) ); |
| return B_OK; |
| } |
| } |
| |
| /* Write the file. |
| */ |
| if( verbose ) { |
| printf( "x - %s\n", lib->names[idx] ); |
| } |
| |
| fp = fopen( lib->names[idx], "w" ); |
| if( fp == NULL ) { |
| fprintf( stderr, "ar: can't open %s for write, %s\n", lib->names[idx], |
| strerror( errno ) ); |
| return B_OK; |
| } |
| |
| recs = fwrite( lib->data[idx], lib->files[idx].object_size, 1, fp ); |
| if( recs != 1 ) { |
| fprintf( stderr, "error writing %s, %s\n", lib->names[idx], |
| strerror( errno ) ); |
| } |
| |
| retval = fclose( fp ); |
| |
| /* Set the newly extracted file's modification time to the time |
| ** stored in the archive. |
| */ |
| retval = stat( lib->names[idx], &s ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: can't stat %s, %s\n", lib->names[idx], |
| strerror( errno ) ); |
| } else { |
| struct utimbuf new_times; |
| |
| new_times.actime = s.st_atime; |
| new_times.modtime = lib->files[idx].m_time; |
| |
| retval = utime( lib->names[idx], &new_times ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: can't set modification time for %s, %s\n", |
| lib->names[idx], strerror( retval ) ); |
| } |
| } |
| |
| /* Set the newly extracted file's mode. |
| */ |
| retval = chmod( lib->names[idx], mode_bits ); |
| if( retval != 0 ) { |
| fprintf( stderr, "ar: unable to change file mode for %s, %s\n", |
| lib->names[idx], strerror( errno ) ); |
| } |
| |
| /* Set the newly extracted file's type. |
| */ |
| setfiletype( lib->names[idx], "application/x-mw-library" ); |
| |
| return B_OK; |
| } |