Goto sanos source index
//
// ar.c
//
// Library archive utility
//
// Copyright (C) 2011 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#define AR_MAGIC "!<arch>\012"
struct ar_header {
char ar_name[16]; // Name of this member
char ar_date[12]; // File mtime
char ar_uid[6]; // Owner uid; printed as decimal
char ar_gid[6]; // Owner gid; printed as decimal
char ar_mode[8]; // File mode, printed as octal
char ar_size[10]; // File size, printed as decimal
char ar_fmag[2]; // Should contain ARFMAG
};
//
// ELF file header
//
#define ELF_MAGIC "\177ELF"
#define EI_NIDENT (16)
#define ET_NONE 0 // No file type
#define ET_REL 1 // Relocatable file
#define ET_EXEC 2 // Executable file
#define ET_DYN 3 // Shared object file
#define ET_CORE 4 // Core file
struct elf_header {
uint8_t e_ident[EI_NIDENT]; // Magic number and other info
uint16_t e_type; // Object file type
uint16_t e_machine; // Architecture
uint32_t e_version; // Object file version
uint32_t e_entry; // Entry point virtual address
uint32_t e_phoff; // Program header table file offset
uint32_t e_shoff; // Section header table file offset
uint32_t e_flags; // Processor-specific flags
uint16_t e_ehsize; // ELF header size in bytes
uint16_t e_phentsize; // Program header table entry size
uint16_t e_phnum; // Program header table entry count
uint16_t e_shentsize; // Section header table entry size
uint16_t e_shnum; // Section header table entry count
uint16_t e_shstrndx; // Section header string table index
};
//
// ELF section header
//
//
#define SHT_NULL 0 // Section header table entry unused
#define SHT_PROGBITS 1 // Program data
#define SHT_SYMTAB 2 // Symbol table
#define SHT_STRTAB 3 // String table
#define SHT_RELA 4 // Relocation entries with addends
#define SHT_HASH 5 // Symbol hash table
#define SHT_DYNAMIC 6 // Dynamic linking information
#define SHT_NOTE 7 // Notes
#define SHT_NOBITS 8 // Program space with no data (bss)
#define SHT_REL 9 // Relocation entries, no addends
#define SHT_SHLIB 10 // Reserved
#define SHT_DYNSYM 11 // Dynamic linker symbol table
struct elf_section_header {
uint32_t sh_name; // Section name (string table index)
uint32_t sh_type; // Section type
uint32_t sh_flags; // Section flags
uint32_t sh_addr; // Section virtual addr at execution
uint32_t sh_offset; // Section file offset
uint32_t sh_size; // Section size in bytes
uint32_t sh_link; // Link to another section
uint32_t sh_info; // Additional section information
uint32_t sh_addralign; // Section alignment
uint32_t sh_entsize; // Entry size if section holds table
};
//
// ELF symbol table entry
//
struct elf_symbol {
uint32_t st_name; // Symbol name (string table index)
uint32_t st_value; // Symbol value
uint32_t st_size; // Symbol size
uint8_t st_info; // Symbol type and binding
uint8_t st_other; // No defined meaning, 0
uint16_t st_shndx; // Section index
};
//
// Archive state information
//
struct entry {
struct ar_header header; // Header for archive entry
char *contents; // File contents
int size; // Size of input file
int offset; // Position in archive
struct entry *next; // Next entry in archive
};
struct symbol {
char *name; // Symbol name
struct entry *entry; // Entry in the archive the symbol belongs to
struct symbol *next; // Next symbol
};
struct archive {
struct entry *first_entry; // First archive entry
struct entry *last_entry; // Last archive entry
struct symbol *first_symbol; // First archive entry
struct symbol *last_symbol; // Last archive entry
struct entry *symtab; // Entry for symbol table
};
int make_symtab = 0;
int merge_archives = 0;
void *load_data(int fd, int offset, int size) {
void *data;
data = malloc(size);
if (!data) return NULL;
if (lseek(fd, offset, SEEK_SET) != offset) {
free(data);
return NULL;
}
if (read(fd, data, size) != size) {
free(data);
return NULL;
}
return data;
}
struct entry *add_new_entry(struct archive *ar) {
struct entry *e = (struct entry *) malloc(sizeof(struct entry));
memset(e, 0, sizeof(struct entry));
memset(&e->header, ' ', sizeof(struct ar_header));
if (ar->last_entry) ar->last_entry->next = e;
if (!ar->first_entry) ar->first_entry = e;
ar->last_entry = e;
return e;
}
struct symbol *add_new_symbol(struct archive *ar, struct entry *e, char *name) {
struct symbol *s = (struct symbol *) malloc(sizeof(struct symbol));
s->name = strdup(name);
s->entry = e;
s->next = NULL;
if (ar->last_symbol) ar->last_symbol->next = s;
if (!ar->first_symbol) ar->first_symbol = s;
ar->last_symbol = s;
return s;
}
struct symbol *find_symbol(struct archive *ar, char *name) {
struct symbol *s = ar->first_symbol;
while (s) {
if (strcmp(s->name, name) == 0) return s;
s = s->next;
}
return NULL;
}
void init_archive(struct archive *ar) {
memset(ar, 0, sizeof(struct archive));
if (make_symtab) ar->symtab = add_new_entry(ar);
}
void free_archive(struct archive *ar) {
struct entry *e;
struct symbol *s;
e = ar->first_entry;
while (e) {
struct entry *next = e->next;
if (e->contents) free(e->contents);
free(e);
e = next;
}
s = ar->first_symbol;
while (s) {
struct symbol *next = s->next;
if (s->name) free(s->name);
free(s);
s = next;
}
}
int add_symbols(struct archive *ar, struct entry *e) {
struct elf_header *hdr;
struct elf_section_header *sections, *s;
struct elf_symbol *symtab, *sym;
char *strtab;
char *name;
int i, j, num_syms, type, bind;
// The ELF header is at the begining of the file.
hdr = (struct elf_header *) e->contents;
// Check that this is an ELF object file.
if (memcmp(hdr->e_ident, ELF_MAGIC, 4) != 0) return -1;
if (hdr->e_type != ET_REL) return -1;
// Get sections
sections = (struct elf_section_header *) (e->contents + hdr->e_shoff);
// Get symbols
for (i = 1; i < hdr->e_shnum; i++) {
s = §ions[i];
if (s->sh_type == SHT_SYMTAB) {
num_syms = s->sh_size / sizeof(struct elf_symbol);
symtab = (struct elf_symbol *) (e->contents + s->sh_offset);
s = §ions[s->sh_link];
strtab = e->contents + s->sh_offset;
for (j = 0; j < num_syms; j++) {
sym = &symtab[j];
name = strtab + sym->st_name;
type = sym->st_info & 0x0f;
bind = sym->st_info >> 4;
//printf("symbol %s type=%d bind=%d section=%d\n", name, type, bind, sym->st_shndx);
// Only keep defined symbols.
if (sym->st_shndx == 0) continue;
// Only keep global symbols.
if (bind != 1) continue;
add_new_symbol(ar, e, name);
}
}
}
return 0;
}
void fill_header(struct ar_header *hdr, char *filename, struct stat *st) {
char *p, *name;
int len;
char buf[32];
// Set the file name
name = filename;
p = filename;
while (*p) {
if (*p == '/' || *p == '\\') {
name = ++p;
} else {
p++;
}
}
len = strlen(name);
if (len > 15) len = 15;
memcpy(hdr->ar_name, name, len);
hdr->ar_name[len] = '/';
// Set date.
sprintf(buf, "%d", st->st_mtime);
memcpy(hdr->ar_date, buf, strlen(buf));
// Set owner uid and gid.
sprintf(buf, "%d", st->st_uid);
memcpy(hdr->ar_uid, buf, strlen(buf));
sprintf(buf, "%d", st->st_gid);
memcpy(hdr->ar_gid, buf, strlen(buf));
// Set file mode and size.
sprintf(buf, "%o", st->st_mode);
memcpy(hdr->ar_mode, buf, strlen(buf));
sprintf(buf, "%d", st->st_size);
memcpy(hdr->ar_size, buf, strlen(buf));
// Set magic marker
hdr->ar_fmag[0] = '`';
hdr->ar_fmag[1] = '\n';
}
int read_file(struct archive *ar, int fd, int offset, int size, char *filename) {
struct entry *e;
struct stat st;
// Add new entry to archive.
e = add_new_entry(ar);
e->size = size;
e->contents = load_data(fd, offset, size);
if (e->contents == NULL) return -1;
// Get file information
if (fstat(fd, &st) < 0) return -1;
// Fill out archive entry header
fill_header(&e->header, filename, &st);
// Add symbols from ELF object file
if (make_symtab && size >= 4 && memcmp(e->contents, ELF_MAGIC, 4) == 0) {
if (add_symbols(ar, e) < 0) return -1;
}
return 0;
}
int read_archive_file(struct archive *ar, int fd) {
struct ar_header hdr;
char name[17];
char sizebuf[11];
int len, i, offset, size;
struct entry *e;
for (;;) {
// Read next header from input archive
len = read(fd, &hdr, sizeof(hdr));
if (len == 0) break;
if (len != sizeof(hdr)) {
fprintf(stderr, "invalid archive");
return -1;
}
// Get entry name and length from header
memcpy(name, hdr.ar_name, sizeof(hdr.ar_name));
for(i = sizeof(hdr.ar_name) - 1; i >= 0; i--) {
if (name[i] != ' ') break;
}
name[i + 1] = '\0';
memcpy(sizebuf, hdr.ar_size, sizeof(hdr.ar_size));
sizebuf[sizeof(hdr.ar_size)] = '\0';
size = strtol(sizebuf, NULL, 0);
// Skip the symbol table
offset = lseek(fd, 0, SEEK_CUR);
if (strcmp(name, "/") != 0) {
// Add new entry to archive.
e = add_new_entry(ar);
memcpy(&e->header, &hdr, sizeof(hdr));
e->size = size;
e->contents = load_data(fd, offset, size);
if (e->contents == NULL) return -1;
// Add symbols from ELF object file
if (make_symtab && size >= 4 && memcmp(e->contents, ELF_MAGIC, 4) == 0) {
if (add_symbols(ar, e) < 0) return -1;
}
}
size = (size + 1) & ~1; // align to even
lseek(fd, offset + size, SEEK_SET);
}
return 0;
}
void build_symbol_table(struct archive *ar) {
struct symbol *s;
struct entry *e;
int size, num_syms, offset;
uint32_t *index;
char *symtab;
char *strtab;
struct stat st;
// Compute size of symbol table
size = 4;
num_syms = 0;
s = ar->first_symbol;
while (s) {
size += strlen(s->name) + 1 + 4;
num_syms++;
s = s->next;
}
ar->symtab->size = size;
// Compute offsets for all entries
offset = 8;
e = ar->first_entry;
while (e) {
e->offset = offset;
offset += sizeof(struct ar_header) + e->size;
if (offset & 1) offset++;
e = e->next;
}
// Build symbol table
symtab = ar->symtab->contents = malloc(ar->symtab->size);
*((uint32_t *) symtab) = htonl(num_syms);
index = (uint32_t *) (symtab + 4);
strtab = symtab + 4 * num_syms + 4;
s = ar->first_symbol;
while (s) {
*index++ = htonl(s->entry->offset);
strcpy(strtab, s->name);
strtab += strlen(s->name) + 1;
s = s->next;
}
// Setup archive header for symbol table
memset(&st, 0, sizeof(struct stat));
st.st_mode = 0666;
st.st_mtime = time(NULL);
st.st_size = ar->symtab->size;
fill_header(&ar->symtab->header, "", &st);
}
int write_archive(struct archive *ar, int fd) {
struct entry *e;
int size;
int offset;
// Write header
size = write(fd, AR_MAGIC, 8);
offset = 8;
// Write entries.
e = ar->first_entry;
while (e) {
size += write(fd, &e->header, sizeof(struct ar_header));
size += write(fd, e->contents, e->size);
offset += sizeof(struct ar_header) + e->size;
if (offset & 1) {
size += write(fd, "\0", 1);
offset++;
}
if (size != offset) break;
e = e->next;
}
return offset == size ? 0 : -1;
}
void usage() {
fprintf(stderr, "usage: ar [options] <archive> <files>\n\n");
fprintf(stderr, " -s Add symbol table to archive.\n");
fprintf(stderr, " -m Add individual entries in input archives.\n");
}
int main(int argc, char *argv[]) {
int c, fd;
char *archive_filename;
char magic[8];
struct archive ar;
// Parse command line options
while ((c = getopt(argc, argv, "ms")) != EOF) {
switch (c) {
case 'm':
merge_archives = 1;
break;
case 's':
make_symtab = 1;
break;
default:
usage();
return 1;
}
}
if (optind == argc) {
usage();
return 1;
}
archive_filename = argv[optind++];
// Read all the input object files
init_archive(&ar);
while (optind < argc) {
// Open next input file
char *input_file = argv[optind++];
fd = open(input_file, O_RDONLY | O_BINARY);
if (fd < 0) {
perror(input_file);
free_archive(&ar);
return 1;
}
// Determine file type
if (read(fd, magic, 8) != 8) {
fprintf(stderr, "%s: Invalid input file\n", input_file);
close(fd);
free_archive(&ar);
return 1;
}
// Add input file to archive
if (merge_archives && memcmp(magic, AR_MAGIC, 8) == 0) {
if (read_archive_file(&ar, fd) < 0) {
fprintf(stderr, "%s: Error reading archive file\n", input_file);
close(fd);
free_archive(&ar);
return 1;
}
} else {
if (read_file(&ar, fd, 0, lseek(fd, 0, SEEK_END), input_file) < 0) {
fprintf(stderr, "%s: Error reading file\n", input_file);
close(fd);
free_archive(&ar);
return 1;
}
}
close(fd);
}
// Open output archive
fd = open(archive_filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666);
if (fd < 0) {
perror(archive_filename);
free_archive(&ar);
return 1;
}
// Build symbol table.
if (make_symtab) build_symbol_table(&ar);
// Write archive.
if (write_archive(&ar, fd) < 0) {
perror(archive_filename);
free_archive(&ar);
close(fd);
return 1;
}
close(fd);
free_archive(&ar);
return 0;
}