/*******************************************************************/
/* slibtool: a strong libtool implementation, written in C */
/* Copyright (C) 2016--2024 SysDeer Technologies, LLC */
/* Released under the Standard MIT License; see COPYING.SLIBTOOL. */
/*******************************************************************/
#include <time.h>
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <slibtool/slibtool.h>
#include <slibtool/slibtool_arbits.h>
#include "slibtool_ar_impl.h"
#include "slibtool_driver_impl.h"
#include "slibtool_errinfo_impl.h"
/* anonymous fun */
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
/* time stamp format specifier */
#define PPRII64 "%"PRIi64
/* file size format specifier */
#define PPRIU64 "%"PRIu64
/* dlopen self/force */
static const char slbt_ar_self_dlunit[] = "@PROGRAM@";
struct armap_buffer_32 {
uint32_t moffset;
const char * symname;
uint32_t baseidx;
};
struct armap_buffer_64 {
uint64_t moffset;
const char * symname;
uint64_t baseidx;
};
static const char ar_signature[] = AR_SIGNATURE;
static int slbt_create_anonymous_archive_ctx(
const struct slbt_driver_ctx * dctx,
size_t size,
struct slbt_archive_ctx ** pctx)
{
struct slbt_archive_ctx_impl * ctx;
if (!(ctx = calloc(1,sizeof(*ctx))))
return SLBT_BUFFER_ERROR(dctx);
ctx->map.map_size = size;
ctx->map.map_addr = mmap(
0,size,
PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS,
-1,0);
if (ctx->map.map_addr == MAP_FAILED) {
free(ctx);
return SLBT_SYSTEM_ERROR(dctx,0);
}
ctx->dctx = dctx;
ctx->actx.map = &ctx->map;
*pctx = &ctx->actx;
return 0;
}
static off_t slbt_armap_write_be_32(unsigned char * mark, uint32_t val)
{
mark[0] = val >> 24;
mark[1] = val >> 16;
mark[2] = val >> 8;
mark[3] = val;
return sizeof(uint32_t);
}
static off_t slbt_armap_write_le_32(unsigned char * mark, uint32_t val)
{
mark[0] = val;
mark[1] = val >> 8;
mark[2] = val >> 16;
mark[3] = val >> 24;
return sizeof(uint32_t);
}
static off_t slbt_armap_write_be_64(unsigned char * mark, uint64_t val)
{
slbt_armap_write_be_32(&mark[0],val >> 32);
slbt_armap_write_be_32(&mark[4],val);
return sizeof(uint64_t);
}
static off_t slbt_armap_write_le_64(unsigned char * mark, uint64_t val)
{
slbt_armap_write_be_32(&mark[0],val);
slbt_armap_write_be_32(&mark[4],val >> 32);
return sizeof(uint64_t);
}
static int slbt_ar_merge_archives_fail(
struct slbt_archive_ctx * arctx,
struct armap_buffer_32 * bsdmap32,
struct armap_buffer_64 * bsdmap64,
int ret)
{
if (bsdmap32)
free(bsdmap32);
if (bsdmap64)
free(bsdmap64);
if (arctx)
slbt_ar_free_archive_ctx(arctx);
return ret;
}
int slbt_ar_merge_archives(
struct slbt_archive_ctx * const arctxv[],
struct slbt_archive_ctx ** arctxm)
{
struct slbt_archive_ctx * const * arctxp;
struct slbt_archive_ctx * arctx;
const struct slbt_driver_ctx * dctx;
const struct slbt_archive_meta * meta;
struct ar_raw_file_header * arhdr;
struct ar_meta_member_info ** memberp;
struct ar_meta_member_info * meminfo;
struct ar_meta_member_info * armap;
struct ar_meta_member_info * arnames;
const struct ar_meta_armap_common_32 * armap32;
const struct ar_meta_armap_common_64 * armap64;
const struct ar_meta_armap_ref_32 * symref32;
const struct ar_meta_armap_ref_64 * symref64;
struct armap_buffer_32 * bsdmap32;
struct armap_buffer_64 * bsdmap64;
struct armap_buffer_32 * bsdsort32;
struct armap_buffer_64 * bsdsort64;
size_t nbytes;
ssize_t nwritten;
uint32_t mapattr;
uint64_t nmembers;
uint64_t nsymrefs;
uint64_t sarmap;
uint64_t sarname;
uint64_t sarchive;
uint64_t smembers;
uint64_t ssymrefs;
uint64_t ssymstrs;
uint64_t snamestrs;
int64_t omembers;
int64_t osymrefs;
int64_t onamestrs;
int64_t omemfixup;
char * base;
unsigned char * ubase;
char * ch;
unsigned char * uch;
char * namebase;
char * namestr;
char * strtbl;
uint64_t idx;
uint64_t mapidx;
uint64_t cmpidx;
off_t (*armap_write_uint32)(
unsigned char *,
uint32_t);
off_t (*armap_write_uint64)(
unsigned char *,
uint64_t);
/* init */
nmembers = nsymrefs = ssymstrs = mapattr = 0;
sarchive = smembers = ssymrefs = snamestrs = 0;
omembers = osymrefs = onamestrs = 0;
if (!arctxv || !arctxv[0])
return -1;
if (!(dctx = slbt_get_archive_ictx(arctxv[0])->dctx))
return -1;
/* determine armap type and size of archive elements */
for (armap=0,arnames=0,arctxp=arctxv; *arctxp; arctxp++) {
if (slbt_get_archive_ictx(*arctxp)->dctx != dctx)
return SLBT_CUSTOM_ERROR(
dctx,
SLBT_ERR_AR_DRIVER_MISMATCH);
meta = (*arctxp)->meta;
armap32 = meta->a_armap_primary.ar_armap_common_32;
armap64 = meta->a_armap_primary.ar_armap_common_64;
for (memberp=meta->a_memberv; memberp && *memberp; memberp++) {
meminfo = *memberp;
switch (meminfo->ar_member_attr) {
case AR_MEMBER_ATTR_ARMAP:
if (armap32) {
if (mapattr == 0) {
armap = meminfo;
mapattr = armap32->ar_armap_attr;
} else if (mapattr != armap32->ar_armap_attr) {
return SLBT_CUSTOM_ERROR(
dctx,
SLBT_ERR_AR_ARMAP_MISMATCH);
}
nsymrefs += armap32->ar_num_of_symbols;
ssymstrs += armap32->ar_size_of_strs;
} else if (armap64) {
if (mapattr == 0) {
armap = meminfo;
mapattr = armap64->ar_armap_attr;
} else if (mapattr != armap64->ar_armap_attr) {
return SLBT_CUSTOM_ERROR(
dctx,
SLBT_ERR_AR_ARMAP_MISMATCH);
}
nsymrefs += armap64->ar_num_of_symbols;
}
break;
case AR_MEMBER_ATTR_LINKINFO:
break;
case AR_MEMBER_ATTR_NAMESTRS:
snamestrs += meminfo->ar_object_size;
if (!arnames)
arnames = meminfo;
break;
default:
smembers += sizeof(struct ar_raw_file_header);
smembers += meminfo->ar_file_header.ar_file_size;
smembers += 1;
smembers |= 1;
smembers ^= 1;
nmembers++;
break;
}
}
}
/* armap size */
if (sarmap = 0, sarname = 0, (mapattr == 0)) {
(void)0;
} else if (mapattr & AR_ARMAP_ATTR_SYSV) {
if (mapattr & (AR_ARMAP_ATTR_LE_32|AR_ARMAP_ATTR_BE_32)) {
sarmap += sizeof(uint32_t);
sarmap += sizeof(uint32_t) * nsymrefs;
} else {
sarmap += sizeof(uint64_t);
sarmap += sizeof(uint64_t) * nsymrefs;
}
} else if (mapattr & AR_ARMAP_ATTR_BSD) {
if (mapattr & (AR_ARMAP_ATTR_LE_32|AR_ARMAP_ATTR_BE_32)) {
sarmap += 2 * sizeof(uint32_t);
sarmap += 2 * sizeof(uint32_t) * nsymrefs;
} else {
sarmap += 2 * sizeof(uint64_t);
sarmap += 2 * sizeof(uint64_t) * nsymrefs;
}
sarname += armap->ar_file_header.ar_file_size;
sarname -= armap->ar_object_size;
} else {
return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_FLOW_ERROR);
}
ssymstrs += 1;
ssymstrs |= 1;
ssymstrs ^= 1;
/* (debugging) */
(void)nmembers;
/* long-names member alignment */
snamestrs += 1;
snamestrs |= 1;
snamestrs ^= 1;
/* archive size */
sarchive = sizeof(struct ar_raw_signature);
sarchive += armap ? sizeof(struct ar_raw_file_header) : 0;
sarchive += arnames ? sizeof(struct ar_raw_file_header) : 0;
sarchive += sarname;
sarchive += sarmap;
sarchive += ssymstrs;
sarchive += snamestrs;
sarchive += smembers;
/* offset from archive base to first public member */
omembers = sarchive;
omembers -= smembers;
/* create in-memory archive */
if (slbt_create_anonymous_archive_ctx(dctx,sarchive,&arctx) < 0)
return SLBT_NESTED_ERROR(dctx);
/* get ready for writing */
base = arctx->map->map_addr;
ubase = arctx->map->map_addr;
/* archive header */
ch = base;
memcpy(ch,ar_signature,sizeof(struct ar_raw_signature));
ch += sizeof(struct ar_raw_signature);
/* armap header */
if (armap) {
arhdr = (struct ar_raw_file_header *)ch;
memcpy(arhdr,armap->ar_member_data,sizeof(*arhdr)+sarname);
nwritten = armap->ar_file_header.ar_time_date_stamp
? sprintf(arhdr->ar_time_date_stamp,PPRII64,time(0))
: 0;
if (nwritten < 0)
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_SYSTEM_ERROR(dctx,0));
for (nbytes=nwritten; nbytes < sizeof(arhdr->ar_time_date_stamp); nbytes++)
arhdr->ar_time_date_stamp[nbytes] = AR_DEC_PADDING;
nwritten = sprintf(
arhdr->ar_file_size,PPRIU64,
sarname + sarmap + ssymstrs);
if (nwritten < 0)
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_SYSTEM_ERROR(dctx,0));
for (nbytes=nwritten; nbytes < sizeof(arhdr->ar_file_size); nbytes++)
arhdr->ar_file_size[nbytes] = AR_DEC_PADDING;
}
/* arnames header (sysv only) */
if (arnames) {
ch = base;
ch += omembers;
ch -= snamestrs;
ch -= sizeof(struct ar_raw_file_header);
namebase = ch;
namebase += sizeof(struct ar_raw_file_header);
memset(namebase,0,snamestrs);
namestr = namebase;
arhdr = (struct ar_raw_file_header *)ch;
memcpy(arhdr,arnames->ar_member_data,sizeof(*arhdr));
nwritten = arnames->ar_file_header.ar_time_date_stamp
? sprintf(arhdr->ar_time_date_stamp,PPRII64,time(0))
: 0;
if (nwritten < 0)
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_SYSTEM_ERROR(dctx,0));
for (nbytes=nwritten; nbytes < sizeof(arhdr->ar_time_date_stamp); nbytes++)
arhdr->ar_time_date_stamp[nbytes] = AR_DEC_PADDING;
nwritten = sprintf(
arhdr->ar_file_size,PPRIU64,
snamestrs);
if (nwritten < 0)
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_SYSTEM_ERROR(dctx,0));
for (nbytes=nwritten; nbytes < sizeof(arhdr->ar_file_size); nbytes++)
arhdr->ar_file_size[nbytes] = AR_DEC_PADDING;
}
/* armap data (preparation) */
armap_write_uint32 = 0;
armap_write_uint64 = 0;
bsdmap32 = 0;
bsdmap64 = 0;
if (mapattr & AR_ARMAP_ATTR_BE_32)
armap_write_uint32 = slbt_armap_write_be_32;
else if (mapattr & AR_ARMAP_ATTR_LE_32)
armap_write_uint32 = slbt_armap_write_le_32;
else if (mapattr & AR_ARMAP_ATTR_BE_64)
armap_write_uint64 = slbt_armap_write_be_64;
else if (mapattr & AR_ARMAP_ATTR_LE_64)
armap_write_uint64 = slbt_armap_write_le_64;
uch = ubase;
uch += sizeof(struct ar_raw_signature);
uch += sizeof(struct ar_raw_file_header);
uch += sarname;
if (mapattr & AR_ARMAP_ATTR_SYSV) {
if (armap_write_uint32) {
uch += armap_write_uint32(uch,nsymrefs);
ch = base;
ch += uch - ubase;
ch += sizeof(uint32_t) * nsymrefs;
} else {
uch += armap_write_uint64(uch,nsymrefs);
ch = base;
ch += uch - ubase;
ch += sizeof(uint64_t) * nsymrefs;
}
} else if (mapattr & AR_ARMAP_ATTR_BSD) {
strtbl = base;
strtbl += omembers;
strtbl -= ssymstrs;
memset(strtbl,0,ssymstrs);
if (armap_write_uint32) {
if (!(bsdmap32 = calloc(2*nsymrefs,sizeof(struct armap_buffer_32))))
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_SYSTEM_ERROR(dctx,0));
bsdsort32 = &bsdmap32[nsymrefs];
} else {
if (!(bsdmap64 = calloc(2*nsymrefs,sizeof(struct armap_buffer_64))))
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_SYSTEM_ERROR(dctx,0));
bsdsort64 = &bsdmap64[nsymrefs];
}
}
/* main iteration (armap data, long-names, public members) */
for (mapidx=0,arctxp=arctxv; *arctxp; arctxp++) {
meta = (*arctxp)->meta;
armap32 = meta->a_armap_primary.ar_armap_common_32;
armap64 = meta->a_armap_primary.ar_armap_common_64;
if ((memberp = meta->a_memberv)) {
for (omemfixup=0; *memberp && !omemfixup; memberp++) {
meminfo = *memberp;
switch (meminfo->ar_member_attr) {
case AR_MEMBER_ATTR_ARMAP:
case AR_MEMBER_ATTR_LINKINFO:
case AR_MEMBER_ATTR_NAMESTRS:
break;
default:
omemfixup = (int64_t)meminfo->ar_member_data;
omemfixup -= (int64_t)meta->r_archive.map_addr;
break;
}
}
}
for (memberp=meta->a_memberv; memberp && *memberp; memberp++) {
meminfo = *memberp;
switch (meminfo->ar_member_attr) {
case AR_MEMBER_ATTR_ARMAP:
if (armap32 && (mapattr & AR_ARMAP_ATTR_SYSV)) {
symref32 = armap32->ar_symrefs;
for (idx=0; idx<armap32->ar_num_of_symbols; idx++) {
uch += armap_write_uint32(
uch,
symref32[idx].ar_member_offset + omembers - omemfixup);
strcpy(ch,&armap32->ar_string_table[symref32[idx].ar_name_offset]);
ch += strlen(ch);
ch++;
}
} else if (armap64 && (mapattr & AR_ARMAP_ATTR_SYSV)) {
symref64 = armap64->ar_symrefs;
for (idx=0; idx<armap64->ar_num_of_symbols; idx++) {
uch += armap_write_uint64(
uch,
symref64[idx].ar_member_offset + omembers - omemfixup);
strcpy(ch,&armap64->ar_string_table[symref64[idx].ar_name_offset]);
ch += strlen(ch);
ch++;
}
} else if (armap32 && (mapattr & AR_ARMAP_ATTR_BSD)) {
symref32 = armap32->ar_symrefs;
for (idx=0; idx<armap32->ar_num_of_symbols; idx++) {
bsdmap32[mapidx].moffset = symref32[idx].ar_member_offset;
bsdmap32[mapidx].moffset += omembers - omemfixup;
bsdmap32[mapidx].symname = armap32->ar_string_table;
bsdmap32[mapidx].symname += symref32[idx].ar_name_offset;
mapidx++;
}
} else if (armap64 && (mapattr & AR_ARMAP_ATTR_BSD)) {
symref64 = armap64->ar_symrefs;
for (idx=0; idx<armap64->ar_num_of_symbols; idx++) {
bsdmap64[mapidx].moffset = symref64[idx].ar_member_offset;
bsdmap64[mapidx].moffset += omembers - omemfixup;
bsdmap64[mapidx].symname = armap64->ar_string_table;
bsdmap64[mapidx].symname += symref64[idx].ar_name_offset;
mapidx++;
}
}
break;
case AR_MEMBER_ATTR_LINKINFO:
break;
case AR_MEMBER_ATTR_NAMESTRS:
break;
default:
arhdr = meminfo->ar_member_data;
memcpy(
&base[omembers],arhdr,
sizeof(*arhdr) + meminfo->ar_file_header.ar_file_size);
if (meminfo->ar_file_header.ar_header_attr & AR_HEADER_ATTR_SYSV) {
if (meminfo->ar_file_header.ar_header_attr & AR_HEADER_ATTR_NAME_REF) {
nwritten = sprintf(
&base[omembers],"/"PPRII64,
namestr - namebase);
if (nwritten < 0)
SLBT_SYSTEM_ERROR(dctx,0);
for (nbytes=nwritten; nbytes < sizeof(arhdr->ar_file_id); nbytes++)
base[omembers + nbytes] = AR_DEC_PADDING;
strcpy(namestr,meminfo->ar_file_header.ar_member_name);
namestr += strlen(namestr);
*namestr++ = '/';
*namestr++ = AR_OBJ_PADDING;
}
}
omembers += sizeof(*arhdr);
omembers += meminfo->ar_file_header.ar_file_size;
omembers += 1;
omembers |= 1;
omembers ^= 1;
break;
}
}
}
/* bsd variant: also sort the string table (because we can:=)) */
if (bsdmap32) {
for (mapidx=0; mapidx<nsymrefs; mapidx++)
for (cmpidx=0; cmpidx<nsymrefs; cmpidx++)
if (strcmp(bsdmap32[cmpidx].symname,bsdmap32[mapidx].symname) < 0)
bsdmap32[mapidx].baseidx++;
/* a symbol might be present in more than one member; */
/* see whether ar_member_offset has already been set) */
for (mapidx=0,ch=strtbl; mapidx<nsymrefs; mapidx++) {
idx = bsdmap32[mapidx].baseidx;
for (; bsdsort32[idx].moffset; )
idx++;
bsdsort32[idx].moffset = bsdmap32[mapidx].moffset;
bsdsort32[idx].symname = bsdmap32[mapidx].symname;
}
uch += armap_write_uint32(uch,2*sizeof(uint32_t)*nsymrefs);
for (mapidx=0,ch=strtbl; mapidx<nsymrefs; mapidx++) {
uch += armap_write_uint32(uch,ch-strtbl);
uch += armap_write_uint32(uch,bsdsort32[mapidx].moffset);
strcpy(ch,bsdsort32[mapidx].symname);
ch += strlen(ch);
ch++;
}
uch += armap_write_uint32(uch,ssymstrs);
free(bsdmap32);
} else if (bsdmap64) {
for (mapidx=0; mapidx<nsymrefs; mapidx++)
for (cmpidx=0; cmpidx<nsymrefs; cmpidx++)
if (strcmp(bsdmap64[cmpidx].symname,bsdmap64[mapidx].symname) < 0)
bsdmap64[mapidx].baseidx++;
/* a symbol might be present in more than one member; */
/* see whether ar_member_offset has already been set) */
for (mapidx=0,ch=strtbl; mapidx<nsymrefs; mapidx++) {
idx = bsdmap64[mapidx].baseidx;
for (; bsdsort64[idx].moffset; )
idx++;
bsdsort64[idx].moffset = bsdmap64[mapidx].moffset;
bsdsort64[idx].symname = bsdmap64[mapidx].symname;
}
uch += armap_write_uint64(uch,2*sizeof(uint64_t)*nsymrefs);
for (mapidx=0,ch=strtbl; mapidx<nsymrefs; mapidx++) {
uch += armap_write_uint64(uch,ch-strtbl);
uch += armap_write_uint64(uch,bsdsort64[mapidx].moffset);
strcpy(ch,bsdsort64[mapidx].symname);
ch += strlen(ch);
ch++;
}
uch += armap_write_uint64(uch,ssymstrs);
free(bsdmap64);
}
struct slbt_archive_ctx_impl * ictx;
ictx = slbt_get_archive_ictx(arctx);
if (slbt_ar_get_archive_meta(dctx,arctx->map,&ictx->meta) < 0)
return slbt_ar_merge_archives_fail(
arctx,0,0,
SLBT_NESTED_ERROR(dctx));
ictx->actx.meta = ictx->meta;
*arctxm = arctx;
return 0;
}
int slbt_ar_get_varchive_ctx(
const struct slbt_driver_ctx * dctx,
struct slbt_archive_ctx ** pctx)
{
struct slbt_archive_ctx * ctx;
struct slbt_archive_ctx_impl * ictx;
void * base;
size_t size;
size = sizeof(struct ar_raw_signature);
if (slbt_create_anonymous_archive_ctx(dctx,size,&ctx) < 0)
return SLBT_NESTED_ERROR(dctx);
ictx = slbt_get_archive_ictx(ctx);
base = ctx->map->map_addr;
memcpy(base,ar_signature,size);
if (slbt_ar_get_archive_meta(dctx,ctx->map,&ictx->meta) < 0) {
slbt_ar_free_archive_ctx(ctx);
return SLBT_NESTED_ERROR(dctx);
}
ictx->path = slbt_ar_self_dlunit;
ictx->actx.meta = ictx->meta;
ictx->actx.path = &ictx->path;
*pctx = ctx;
return 0;
}