summaryrefslogtreecommitdiff
path: root/utils/mcpu-archive.c
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /utils/mcpu-archive.c
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'utils/mcpu-archive.c')
-rw-r--r--utils/mcpu-archive.c1025
1 files changed, 1025 insertions, 0 deletions
diff --git a/utils/mcpu-archive.c b/utils/mcpu-archive.c
new file mode 100644
index 0000000..e10dd5c
--- /dev/null
+++ b/utils/mcpu-archive.c
@@ -0,0 +1,1025 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "mcpu-archive.h"
+#include "mcpu-asm.h"
+#include "mcpu-melf.h"
+#include "mcpu-object.h"
+
+
+#define MCPU_AR_MAGIC "!<arch>\n"
+#define MCPU_AR_MAGIC_SIZE 8U
+#define MCPU_AR_HEADER_SIZE 60U
+
+
+typedef struct mcpu_ar_raw_header
+{
+ char name[16];
+ char date[12];
+ char uid[6];
+ char gid[6];
+ char mode[8];
+ char size[10];
+ char fmag[2];
+} mcpu_ar_raw_header;
+
+
+static int
+add_size( size_t a, size_t b, size_t *result )
+{
+ if( b > SIZE_MAX - a )
+ return( -1 );
+ *result = a + b;
+ return( 0 );
+
+} /* End of add_size() */
+
+
+static int
+mul_size( size_t a, size_t b, size_t *result )
+{
+ if( a && b > SIZE_MAX / a )
+ return( -1 );
+ *result = a * b;
+ return( 0 );
+
+} /* End of mul_size() */
+
+
+static char *
+copy_string( const char *s, size_t n )
+{
+ char *p = (char *)malloc( n + 1 );
+
+ if( !p )
+ return( NULL );
+ memcpy( p, s, n );
+ p[n] = 0;
+ return( p );
+
+} /* End of copy_string() */
+
+
+const char *
+mcpu_archive_basename( const char *path )
+{
+ const char *p;
+
+ if( !path )
+ return( NULL );
+ p = strrchr( path, '/' );
+ return( p ? p + 1 : path );
+
+} /* End of mcpu_archive_basename() */
+
+
+static int
+valid_member_name( const char *name )
+{
+ const unsigned char *p = (const unsigned char *)name;
+
+ if( !name || !*name || !strcmp( name, "." ) || !strcmp( name, ".." ) )
+ return( 0 );
+
+ while( *p )
+ {
+ if( *p == '/' || *p == '\n' || *p == '\r' || *p == 0x7f )
+ return( 0 );
+ ++p;
+ }
+ return( 1 );
+
+} /* End of valid_member_name() */
+
+
+void
+mcpu_archive_member_free( mcpu_archive_member *member )
+{
+ size_t i;
+
+ if( !member )
+ return;
+
+ free( member->name );
+ free( member->data );
+ for( i = 0; i < member->symbol_count; ++i )
+ free( member->symbols[i] );
+ free( member->symbols );
+ memset( member, 0, sizeof( *member ) );
+
+} /* End of mcpu_archive_member_free() */
+
+
+void
+mcpu_archive_init( mcpu_archive *archive )
+{
+ if( archive )
+ memset( archive, 0, sizeof( *archive ) );
+
+} /* End of mcpu_archive_init() */
+
+
+void
+mcpu_archive_free( mcpu_archive *archive )
+{
+ size_t i;
+
+ if( !archive )
+ return;
+
+ for( i = 0; i < archive->member_count; ++i )
+ mcpu_archive_member_free( &archive->members[i] );
+ free( archive->members );
+ memset( archive, 0, sizeof( *archive ) );
+
+} /* End of mcpu_archive_free() */
+
+
+static int
+member_add_symbol( mcpu_archive_member *member, const char *name,
+ char *err, size_t errsz )
+{
+ char **q;
+ char *s;
+
+ q = (char **)realloc( member->symbols,
+ (member->symbol_count + 1) * sizeof( *q ) );
+ if( !q )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ s = strdup( name );
+ if( !s )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ member->symbols = q;
+ member->symbols[member->symbol_count++] = s;
+ return( 0 );
+
+} /* End of member_add_symbol() */
+
+
+static int
+member_collect_symbols( const char *path, mcpu_archive_member *member,
+ char *err, size_t errsz )
+{
+ cpu_asm_object object;
+ size_t i;
+
+ memset( &object, 0, sizeof( object ) );
+ if( cpu_object_read( path, &object, err, errsz ) )
+ return( -1 );
+
+ for( i = 0; i < object.symbol_count; ++i )
+ {
+ const cpu_asm_symbol *s = &object.symbols[i];
+
+ if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL )
+ continue;
+ if( member_add_symbol( member, s->name, err, errsz ) )
+ {
+ cpu_asm_object_free( &object );
+ return( -1 );
+ }
+ }
+
+ cpu_asm_object_free( &object );
+ return( 0 );
+
+} /* End of member_collect_symbols() */
+
+
+
+
+
+static int
+member_collect_symbols_data( const unsigned char *data, size_t size,
+ mcpu_archive_member *member,
+ char *err, size_t errsz )
+{
+ cpu_asm_object object;
+ size_t i;
+
+ memset( &object, 0, sizeof( object ) );
+ if( cpu_object_read_data(data, size, &object, err, errsz) )
+ return( -1 );
+
+ for( i = 0; i < object.symbol_count; ++i )
+ {
+ const cpu_asm_symbol *s = &object.symbols[i];
+
+ if( s->section == CPU_OBJ_SEC_UNDEF || s->binding == CPU_OBJ_BIND_LOCAL )
+ continue;
+ if( member_add_symbol(member, s->name, err, errsz) )
+ {
+ cpu_asm_object_free( &object );
+ return( -1 );
+ }
+ }
+
+ cpu_asm_object_free( &object );
+ return( 0 );
+
+} /* End of member_collect_symbols_data() */
+
+
+int
+mcpu_archive_member_load( const char *path, mcpu_archive_member *member,
+ char *err, size_t errsz )
+{
+ const char *name;
+ unsigned char *data = NULL;
+ size_t size = 0;
+
+ if( !path || !member )
+ return( -1 );
+ memset( member, 0, sizeof( *member ) );
+
+ name = mcpu_archive_basename( path );
+ if( !valid_member_name( name ) )
+ {
+ snprintf( err, errsz, "invalid archive member name: %s",
+ name ? name : "<null>" );
+ return( -1 );
+ }
+
+ if( cpu_melf_readall( path, &data, &size, err, errsz ) )
+ return( -1 );
+
+ member->name = strdup( name );
+ member->data = data;
+ member->size = size;
+ if( !member->name )
+ {
+ snprintf( err, errsz, "out of memory" );
+ mcpu_archive_member_free( member );
+ return( -1 );
+ }
+
+ if( member_collect_symbols( path, member, err, errsz ) )
+ {
+ mcpu_archive_member_free( member );
+ return( -1 );
+ }
+ return( 0 );
+
+} /* End of mcpu_archive_member_load() */
+
+
+int
+mcpu_archive_append( mcpu_archive *archive, mcpu_archive_member *member,
+ char *err, size_t errsz )
+{
+ mcpu_archive_member *q;
+
+ if( !archive || !member )
+ return( -1 );
+
+ q = (mcpu_archive_member *)realloc( archive->members,
+ (archive->member_count + 1) * sizeof( *q ) );
+ if( !q )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ archive->members = q;
+ archive->members[archive->member_count++] = *member;
+ memset( member, 0, sizeof( *member ) );
+ return( 0 );
+
+} /* End of mcpu_archive_append() */
+
+
+static int
+parse_decimal_field( const char *p, size_t n, size_t *value )
+{
+ size_t i = 0;
+ size_t v = 0;
+ int any = 0;
+
+ while( i < n && p[i] == ' ' )
+ ++i;
+ while( i < n && p[i] != ' ' )
+ {
+ unsigned d;
+
+ if( p[i] < '0' || p[i] > '9' )
+ return( -1 );
+ d = (unsigned)(p[i] - '0');
+ if( v > (SIZE_MAX - d) / 10 )
+ return( -1 );
+ v = v * 10 + d;
+ any = 1;
+ ++i;
+ }
+ while( i < n )
+ {
+ if( p[i] != ' ' )
+ return( -1 );
+ ++i;
+ }
+
+ if( !any )
+ return( -1 );
+ *value = v;
+ return( 0 );
+
+} /* End of parse_decimal_field() */
+
+
+static int
+raw_name( const mcpu_ar_raw_header *h, char *name, size_t namesz )
+{
+ size_t n = sizeof( h->name );
+
+ while( n && h->name[n - 1] == ' ' )
+ --n;
+ if( n + 1 > namesz )
+ return( -1 );
+ memcpy( name, h->name, n );
+ name[n] = 0;
+ return( 0 );
+
+} /* End of raw_name() */
+
+
+static int
+long_name_at( const unsigned char *tab, size_t tabsize, size_t off,
+ char **name, char *err, size_t errsz )
+{
+ size_t end;
+
+ if( off >= tabsize )
+ {
+ snprintf( err, errsz, "bad GNU archive long-name offset" );
+ return( -1 );
+ }
+
+ end = off;
+ while( end < tabsize && tab[end] != '\n' && tab[end] != 0 )
+ ++end;
+ if( end == tabsize )
+ {
+ snprintf( err, errsz, "unterminated GNU archive long name" );
+ return( -1 );
+ }
+
+ if( end > off && tab[end - 1] == '/' )
+ --end;
+ *name = copy_string( (const char *)tab + off, end - off );
+ if( !*name )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+
+} /* End of long_name_at() */
+
+
+static int
+read_member_name( const mcpu_ar_raw_header *h,
+ const unsigned char *payload, size_t payload_size,
+ const unsigned char *longtab, size_t longtab_size,
+ char **name, size_t *name_prefix,
+ int *special, char *err, size_t errsz )
+{
+ char raw[17];
+ size_t n;
+
+ *name = NULL;
+ *name_prefix = 0;
+ *special = 0;
+
+ if( raw_name( h, raw, sizeof( raw ) ) )
+ {
+ snprintf( err, errsz, "bad archive member name" );
+ return( -1 );
+ }
+
+ if( !strcmp( raw, "/" ) || !strcmp( raw, "/SYM64/" ) )
+ {
+ *special = 1;
+ return( 0 );
+ }
+ if( !strcmp( raw, "//" ) )
+ {
+ *special = 2;
+ return( 0 );
+ }
+
+ if( !strncmp( raw, "#1/", 3 ) )
+ {
+ if( parse_decimal_field( raw + 3, strlen( raw + 3 ), &n ) ||
+ n == 0 || n > payload_size )
+ {
+ snprintf( err, errsz, "bad BSD archive extended name" );
+ return( -1 );
+ }
+ *name = copy_string( (const char *)payload, n );
+ if( !*name )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ *name_prefix = n;
+ }
+ else if( raw[0] == '/' && raw[1] >= '0' && raw[1] <= '9' )
+ {
+ size_t off;
+
+ if( !longtab || parse_decimal_field( raw + 1, strlen( raw + 1 ), &off ) ||
+ long_name_at( longtab, longtab_size, off, name, err, errsz ) )
+ return( -1 );
+ }
+ else
+ {
+ n = strlen( raw );
+ if( n && raw[n - 1] == '/' )
+ --n;
+ *name = copy_string( raw, n );
+ if( !*name )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ }
+
+ if( !valid_member_name( *name ) )
+ {
+ snprintf( err, errsz, "invalid archive member name: %s", *name );
+ free( *name );
+ *name = NULL;
+ return( -1 );
+ }
+ return( 0 );
+
+} /* End of read_member_name() */
+
+
+int
+mcpu_archive_read( const char *path, mcpu_archive *archive,
+ char *err, size_t errsz )
+{
+ unsigned char *file = NULL;
+ size_t file_size = 0;
+ size_t off;
+ const unsigned char *longtab = NULL;
+ size_t longtab_size = 0;
+ int rc = -1;
+
+ if( !path || !archive )
+ return( -1 );
+ mcpu_archive_init( archive );
+
+ if( cpu_melf_readall( path, &file, &file_size, err, errsz ) )
+ return( -1 );
+ if( file_size < MCPU_AR_MAGIC_SIZE ||
+ memcmp( file, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) )
+ {
+ snprintf( err, errsz, "not a UNIX archive" );
+ goto out;
+ }
+
+ off = MCPU_AR_MAGIC_SIZE;
+ while( off < file_size )
+ {
+ const mcpu_ar_raw_header *h;
+ size_t stored_size;
+ size_t next;
+ const unsigned char *payload;
+ char *name = NULL;
+ size_t name_prefix = 0;
+ int special = 0;
+ mcpu_archive_member member;
+
+ memset( &member, 0, sizeof( member ) );
+
+ if( file_size - off < MCPU_AR_HEADER_SIZE )
+ {
+ snprintf( err, errsz, "truncated archive member header" );
+ goto out;
+ }
+ h = (const mcpu_ar_raw_header *)(file + off);
+ if( h->fmag[0] != '`' || h->fmag[1] != '\n' ||
+ parse_decimal_field( h->size, sizeof( h->size ), &stored_size ) )
+ {
+ snprintf( err, errsz, "bad archive member header" );
+ goto out;
+ }
+
+ if( stored_size > file_size - off - MCPU_AR_HEADER_SIZE )
+ {
+ snprintf( err, errsz, "truncated archive member" );
+ goto out;
+ }
+ payload = file + off + MCPU_AR_HEADER_SIZE;
+
+ if( read_member_name( h, payload, stored_size, longtab, longtab_size,
+ &name, &name_prefix, &special, err, errsz ) )
+ goto out;
+
+ if( special == 1 )
+ archive->had_symbol_index = 1;
+ else if( special == 2 )
+ {
+ longtab = payload;
+ longtab_size = stored_size;
+ }
+ else
+ {
+ if( name_prefix > stored_size )
+ {
+ free( name );
+ snprintf( err, errsz, "bad archive extended member size" );
+ goto out;
+ }
+
+ member.name = name;
+ member.size = stored_size - name_prefix;
+ member.data = (unsigned char *)malloc( member.size ? member.size : 1 );
+ if( !member.data )
+ {
+ snprintf( err, errsz, "out of memory" );
+ mcpu_archive_member_free( &member );
+ goto out;
+ }
+ if( member.size )
+ memcpy( member.data, payload + name_prefix, member.size );
+
+ if( member_collect_symbols_data( member.data, member.size, &member,
+ err, errsz ) ||
+ mcpu_archive_append( archive, &member, err, errsz ) )
+ {
+ mcpu_archive_member_free( &member );
+ goto out;
+ }
+ }
+
+ if( add_size( off, MCPU_AR_HEADER_SIZE, &next ) ||
+ add_size( next, stored_size, &next ) )
+ {
+ snprintf( err, errsz, "archive offset overflow" );
+ goto out;
+ }
+ if( stored_size & 1 )
+ {
+ if( next >= file_size || file[next] != '\n' )
+ {
+ snprintf( err, errsz, "bad archive member padding" );
+ goto out;
+ }
+ ++next;
+ }
+ off = next;
+ }
+
+ rc = 0;
+
+out:
+ free( file );
+ if( rc )
+ mcpu_archive_free( archive );
+ return( rc );
+
+} /* End of mcpu_archive_read() */
+
+
+static int
+short_member_name( const char *name )
+{
+ const unsigned char *p = (const unsigned char *)name;
+ size_t n = strlen( name );
+
+ if( n == 0 || n > 15 )
+ return( 0 );
+ while( *p )
+ {
+ if( *p == ' ' || *p == '/' || *p == '\t' )
+ return( 0 );
+ ++p;
+ }
+ return( 1 );
+
+} /* End of short_member_name() */
+
+
+static int
+member_stored_size( const mcpu_archive_member *member, size_t *size )
+{
+ size_t n = short_member_name( member->name ) ? 0 : strlen( member->name );
+
+ return( add_size( n, member->size, size ) );
+
+} /* End of member_stored_size() */
+
+
+static int
+format_field( char *dst, size_t n, const char *text )
+{
+ size_t len = strlen( text );
+
+ if( len > n )
+ return( -1 );
+ memcpy( dst, text, len );
+ memset( dst + len, ' ', n - len );
+ return( 0 );
+
+} /* End of format_field() */
+
+
+static int
+format_size_field( char *dst, size_t n, size_t value )
+{
+ char text[32];
+
+ snprintf( text, sizeof( text ), "%zu", value );
+ return( format_field( dst, n, text ) );
+
+} /* End of format_size_field() */
+
+
+static int
+make_header( mcpu_ar_raw_header *h, const char *name, size_t stored_size,
+ int special )
+{
+ char namebuf[32];
+
+ memset( h, ' ', sizeof( *h ) );
+
+ if( special )
+ {
+ if( format_field( h->name, sizeof( h->name ), name ) )
+ return( -1 );
+ }
+ else if( short_member_name( name ) )
+ {
+ snprintf( namebuf, sizeof( namebuf ), "%s/", name );
+ if( format_field( h->name, sizeof( h->name ), namebuf ) )
+ return( -1 );
+ }
+ else
+ {
+ snprintf( namebuf, sizeof( namebuf ), "#1/%zu", strlen( name ) );
+ if( format_field( h->name, sizeof( h->name ), namebuf ) )
+ return( -1 );
+ }
+
+ if( format_field( h->date, sizeof( h->date ), "0" ) ||
+ format_field( h->uid, sizeof( h->uid ), "0" ) ||
+ format_field( h->gid, sizeof( h->gid ), "0" ) ||
+ format_field( h->mode, sizeof( h->mode ), special ? "0" : "644" ) ||
+ format_size_field( h->size, sizeof( h->size ), stored_size ) )
+ return( -1 );
+
+ h->fmag[0] = '`';
+ h->fmag[1] = '\n';
+ return( 0 );
+
+} /* End of make_header() */
+
+
+static int
+write_bytes( FILE *fp, const void *data, size_t size )
+{
+ return( size && fwrite( data, 1, size, fp ) != size ? -1 : 0 );
+
+} /* End of write_bytes() */
+
+
+static void
+put_be32( unsigned char *p, uint32_t v )
+{
+ p[0] = (unsigned char)(v >> 24);
+ p[1] = (unsigned char)(v >> 16);
+ p[2] = (unsigned char)(v >> 8);
+ p[3] = (unsigned char)v;
+
+} /* End of put_be32() */
+
+
+static int
+symbol_index_size( const mcpu_archive *archive, size_t *count,
+ size_t *payload_size, char *err, size_t errsz )
+{
+ size_t i, j;
+ size_t c = 0;
+ size_t names = 0;
+ size_t offsets;
+ size_t total;
+
+ for( i = 0; i < archive->member_count; ++i )
+ {
+ for( j = 0; j < archive->members[i].symbol_count; ++j )
+ {
+ size_t n = strlen( archive->members[i].symbols[j] ) + 1;
+
+ if( add_size( names, n, &names ) || c == SIZE_MAX )
+ {
+ snprintf( err, errsz, "archive symbol index is too large" );
+ return( -1 );
+ }
+ ++c;
+ }
+ }
+
+ if( c > UINT32_MAX || mul_size( c, 4, &offsets ) ||
+ add_size( 4, offsets, &total ) || add_size( total, names, &total ) )
+ {
+ snprintf( err, errsz, "archive symbol index is too large" );
+ return( -1 );
+ }
+
+ *count = c;
+ *payload_size = total;
+ return( 0 );
+
+} /* End of symbol_index_size() */
+
+
+static int
+compute_member_offsets( const mcpu_archive *archive, int write_index,
+ size_t index_size, uint32_t **offsets,
+ char *err, size_t errsz )
+{
+ uint32_t *v;
+ size_t off = MCPU_AR_MAGIC_SIZE;
+ size_t i;
+
+ v = (uint32_t *)calloc( archive->member_count ? archive->member_count : 1,
+ sizeof( *v ) );
+ if( !v )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ if( write_index )
+ {
+ if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) ||
+ add_size( off, index_size, &off ) )
+ {
+ snprintf( err, errsz, "archive is too large" );
+ free( v );
+ return( -1 );
+ }
+ if( index_size & 1 )
+ {
+ if( off == SIZE_MAX )
+ {
+ snprintf( err, errsz, "archive is too large" );
+ free( v );
+ return( -1 );
+ }
+ ++off;
+ }
+ }
+
+ for( i = 0; i < archive->member_count; ++i )
+ {
+ size_t stored;
+
+ if( off > UINT32_MAX || member_stored_size( &archive->members[i], &stored ) )
+ {
+ snprintf( err, errsz, "archive is too large for a 32-bit symbol index" );
+ free( v );
+ return( -1 );
+ }
+ v[i] = (uint32_t)off;
+
+ if( add_size( off, MCPU_AR_HEADER_SIZE, &off ) ||
+ add_size( off, stored, &off ) )
+ {
+ snprintf( err, errsz, "archive is too large" );
+ free( v );
+ return( -1 );
+ }
+ if( stored & 1 )
+ {
+ if( off == SIZE_MAX )
+ {
+ snprintf( err, errsz, "archive is too large" );
+ free( v );
+ return( -1 );
+ }
+ ++off;
+ }
+ }
+
+ *offsets = v;
+ return( 0 );
+
+} /* End of compute_member_offsets() */
+
+
+static int
+write_armap( FILE *fp, const mcpu_archive *archive,
+ const uint32_t *member_offsets, size_t count,
+ size_t payload_size, char *err, size_t errsz )
+{
+ mcpu_ar_raw_header h;
+ unsigned char *payload;
+ size_t pos;
+ size_t i, j, k = 0;
+
+ if( make_header( &h, "/", payload_size, 1 ) )
+ {
+ snprintf( err, errsz, "archive symbol index header is too large" );
+ return( -1 );
+ }
+
+ payload = (unsigned char *)malloc( payload_size ? payload_size : 1 );
+ if( !payload )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ put_be32( payload, (uint32_t)count );
+ pos = 4;
+ for( i = 0; i < archive->member_count; ++i )
+ for( j = 0; j < archive->members[i].symbol_count; ++j )
+ {
+ put_be32( payload + pos, member_offsets[i] );
+ pos += 4;
+ ++k;
+ }
+
+ for( i = 0; i < archive->member_count; ++i )
+ for( j = 0; j < archive->members[i].symbol_count; ++j )
+ {
+ size_t n = strlen( archive->members[i].symbols[j] ) + 1;
+ memcpy( payload + pos, archive->members[i].symbols[j], n );
+ pos += n;
+ }
+
+ if( k != count || pos != payload_size ||
+ write_bytes( fp, &h, sizeof( h ) ) ||
+ write_bytes( fp, payload, payload_size ) ||
+ (payload_size & 1 && fputc( '\n', fp ) == EOF) )
+ {
+ snprintf( err, errsz, "cannot write archive symbol index: %s",
+ strerror( errno ? errno : EIO ) );
+ free( payload );
+ return( -1 );
+ }
+
+ free( payload );
+ return( 0 );
+
+} /* End of write_symbol_index() */
+
+
+static int
+write_member( FILE *fp, const mcpu_archive_member *member,
+ char *err, size_t errsz )
+{
+ mcpu_ar_raw_header h;
+ size_t stored;
+ int shortname = short_member_name( member->name );
+
+ if( member_stored_size( member, &stored ) ||
+ make_header( &h, member->name, stored, 0 ) )
+ {
+ snprintf( err, errsz, "archive member is too large: %s", member->name );
+ return( -1 );
+ }
+
+ if( write_bytes( fp, &h, sizeof( h ) ) ||
+ (!shortname && write_bytes( fp, member->name, strlen( member->name ) )) ||
+ write_bytes( fp, member->data, member->size ) ||
+ (stored & 1 && fputc( '\n', fp ) == EOF) )
+ {
+ snprintf( err, errsz, "cannot write archive member %s: %s",
+ member->name, strerror( errno ? errno : EIO ) );
+ return( -1 );
+ }
+ return( 0 );
+
+} /* End of write_member() */
+
+
+int
+mcpu_archive_write( const char *path, const mcpu_archive *archive,
+ int write_symbol_index, char *err, size_t errsz )
+{
+ char *tmp;
+ size_t tmpsz;
+ int fd = -1;
+ FILE *fp = NULL;
+ size_t symbol_count = 0;
+ size_t index_size = 0;
+ uint32_t *member_offsets = NULL;
+ size_t i;
+ int rc = -1;
+
+ if( !path || !archive )
+ return( -1 );
+
+ if( write_symbol_index &&
+ symbol_index_size( archive, &symbol_count, &index_size, err, errsz ) )
+ return( -1 );
+ if( write_symbol_index &&
+ compute_member_offsets( archive, 1, index_size,
+ &member_offsets, err, errsz ) )
+ return( -1 );
+
+ if( add_size( strlen( path ), sizeof( ".tmp.XXXXXX" ), &tmpsz ) )
+ {
+ snprintf( err, errsz, "archive pathname is too long" );
+ goto out;
+ }
+ tmp = (char *)malloc( tmpsz );
+ if( !tmp )
+ {
+ snprintf( err, errsz, "out of memory" );
+ goto out;
+ }
+ snprintf( tmp, tmpsz, "%s.tmp.XXXXXX", path );
+
+ fd = mkstemp( tmp );
+ if( fd < 0 )
+ {
+ snprintf( err, errsz, "cannot create archive temporary file: %s",
+ strerror( errno ) );
+ free( tmp );
+ goto out;
+ }
+ fp = fdopen( fd, "wb" );
+ if( !fp )
+ {
+ snprintf( err, errsz, "cannot open archive temporary stream: %s",
+ strerror( errno ) );
+ close( fd );
+ fd = -1;
+ unlink( tmp );
+ free( tmp );
+ goto out;
+ }
+ fd = -1;
+
+ if( write_bytes( fp, MCPU_AR_MAGIC, MCPU_AR_MAGIC_SIZE ) ||
+ (write_symbol_index &&
+ write_armap( fp, archive, member_offsets, symbol_count,
+ index_size, err, errsz )) )
+ goto write_fail;
+
+ for( i = 0; i < archive->member_count; ++i )
+ if( write_member( fp, &archive->members[i], err, errsz ) )
+ goto write_fail;
+
+ if( fflush( fp ) || fsync( fileno( fp ) ) || fclose( fp ) )
+ {
+ fp = NULL;
+ snprintf( err, errsz, "cannot finalize archive: %s", strerror( errno ) );
+ unlink( tmp );
+ free( tmp );
+ goto out;
+ }
+ fp = NULL;
+
+ if( rename( tmp, path ) )
+ {
+ snprintf( err, errsz, "cannot install archive %s: %s",
+ path, strerror( errno ) );
+ unlink( tmp );
+ free( tmp );
+ goto out;
+ }
+ free( tmp );
+ rc = 0;
+ goto out;
+
+write_fail:
+ if( !err[0] )
+ snprintf( err, errsz, "cannot write archive: %s", strerror( errno ) );
+ fclose( fp );
+ fp = NULL;
+ unlink( tmp );
+ free( tmp );
+
+out:
+ if( fp )
+ fclose( fp );
+ if( fd >= 0 )
+ close( fd );
+ free( member_offsets );
+ return( rc );
+
+} /* End of mcpu_archive_write() */