#include <ar.h>
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ohash.h>
#include "defines.h"
#include "buf.h"
#include "dir.h"
#include "direxpand.h"
#include "arch.h"
#include "var.h"
#include "targ.h"
#include "memory.h"
#include "gnode.h"
#include "timestamp.h"
#include "lst.h"
#ifdef TARGET_MACHINE
#undef MACHINE
#define MACHINE TARGET_MACHINE
#endif
#ifdef TARGET_MACHINE_ARCH
#undef MACHINE_ARCH
#define MACHINE_ARCH TARGET_MACHINE_ARCH
#endif
#ifdef TARGET_MACHINE_CPU
#undef MACHINE_CPU
#define MACHINE_CPU TARGET_MACHINE_CPU
#endif
static struct ohash archives;
typedef struct Arch_ {
struct ohash members;
char name[1];
} Arch;
#define AR_NAME_SIZE (sizeof(((struct ar_hdr *)0)->ar_name))
#define AR_DATE_SIZE (sizeof(((struct ar_hdr *)0)->ar_date))
struct arch_member {
struct timespec mtime;
char date[AR_DATE_SIZE+1];
char name[1];
};
static struct ohash_info members_info = {
offsetof(struct arch_member, name), NULL,
hash_calloc, hash_free, element_alloc
};
static struct ohash_info arch_info = {
offsetof(Arch, name), NULL, hash_calloc, hash_free, element_alloc
};
static struct arch_member *new_arch_member(struct ar_hdr *, const char *);
static struct timespec mtime_of_member(struct arch_member *);
static long field2long(const char *, size_t);
static Arch *read_archive(const char *, const char *);
static struct timespec ArchMTimeMember(const char *, const char *, bool);
static FILE *ArchFindMember(const char *, const char *, struct ar_hdr *, const char *);
static void ArchTouch(const char *, const char *);
#if defined(__svr4__) || defined(__SVR4) || \
(defined(__OpenBSD__) && defined(__ELF__))
#define SVR4ARCHIVES
#endif
static bool parse_archive(Buffer, const char **, Lst, SymTable *);
static void add_archive_node(Lst, const char *);
struct SVR4namelist {
char *fnametab;
size_t fnamesize;
};
#ifdef SVR4ARCHIVES
static const char *svr4list = "Archive list";
static char *ArchSVR4Entry(struct SVR4namelist *, const char *, size_t, FILE *);
#endif
static struct arch_member *
new_arch_member(struct ar_hdr *hdr, const char *name)
{
const char *end = NULL;
struct arch_member *n;
n = ohash_create_entry(&members_info, name, &end);
memcpy(n->date, &(hdr->ar_date), AR_DATE_SIZE);
n->date[AR_DATE_SIZE] = '\0';
ts_set_out_of_date(n->mtime);
return n;
}
static struct timespec
mtime_of_member(struct arch_member *m)
{
if (is_out_of_date(m->mtime))
ts_set_from_time_t((time_t) strtoll(m->date, NULL, 10),
m->mtime);
return m->mtime;
}
bool
Arch_ParseArchive(const char **line, Lst nodes, SymTable *ctxt)
{
bool result;
static BUFFER expand;
Buf_Reinit(&expand, MAKE_BSIZE);
result = parse_archive(&expand, line, nodes, ctxt);
return result;
}
static void
add_archive_node(Lst nodes, const char *name)
{
GNode *gn;
gn = Targ_FindNode(name, TARG_CREATE);
gn->type |= OP_ARCHV;
Lst_AtEnd(nodes, gn);
}
static bool
parse_archive(Buffer expand, const char **linePtr, Lst nodeLst, SymTable *ctxt)
{
const char *cp;
const char *lib;
const char *elib;
const char *member;
const char *emember;
bool subst_lib;
lib = *linePtr;
subst_lib = false;
for (cp = lib; *cp != '(' && *cp != '\0';) {
if (*cp == '$') {
if (!Var_ParseSkip(&cp, ctxt))
return false;
subst_lib = true;
} else
cp++;
}
elib = cp;
if (subst_lib) {
lib = Var_Substi(lib, elib, ctxt, true);
elib = lib + strlen(lib);
}
if (*cp == '\0') {
printf("Unclosed parenthesis in archive specification\n");
return false;
}
cp++;
for (;;) {
bool subst_member = false;
while (ISSPACE(*cp))
cp++;
member = cp;
while (*cp != '\0' && *cp != ')' && !ISSPACE(*cp)) {
if (*cp == '$') {
if (!Var_ParseSkip(&cp, ctxt))
return false;
subst_member = true;
} else
cp++;
}
if (*cp == '\0' || ISSPACE(*cp)) {
printf("No closing parenthesis in archive specification\n");
return false;
}
if (cp == member)
break;
emember = cp;
if (subst_member) {
const char *oldMemberName = member;
const char *result;
member = Var_Substi(member, emember, ctxt, true);
Buf_Addi(expand, lib, elib);
Buf_AddChar(expand, '(');
Buf_AddString(expand, member);
Buf_AddChar(expand, ')');
result = Buf_Retrieve(expand);
if (strchr(member, '$') &&
memcmp(member, oldMemberName,
emember - oldMemberName) == 0) {
add_archive_node(nodeLst, result);
} else if (!Arch_ParseArchive(&result, nodeLst, ctxt))
return false;
Buf_Reset(expand);
} else if (Dir_HasWildcardsi(member, emember)) {
LIST members;
char *m;
Lst_Init(&members);
Dir_Expandi(member, emember, defaultPath, &members);
while ((m = Lst_DeQueue(&members)) != NULL) {
Buf_Addi(expand, lib, elib);
Buf_AddChar(expand, '(');
Buf_AddString(expand, m);
Buf_AddChar(expand, ')');
free(m);
add_archive_node(nodeLst, Buf_Retrieve(expand));
Buf_Reset(expand);
}
} else {
Buf_Addi(expand, lib, elib);
Buf_AddChar(expand, '(');
Buf_Addi(expand, member, emember);
Buf_AddChar(expand, ')');
add_archive_node(nodeLst, Buf_Retrieve(expand));
Buf_Reset(expand);
}
if (subst_member)
free((char *)member);
}
if (subst_lib)
free((char *)lib);
do {
cp++;
} while (ISSPACE(*cp));
*linePtr = cp;
return true;
}
static long
field2long(const char *field, size_t length)
{
static char enough[32];
assert(length < sizeof(enough));
memcpy(enough, field, length);
enough[length] = '\0';
return strtol(enough, NULL, 10);
}
static Arch *
read_archive(const char *archive, const char *earchive)
{
FILE *arch;
char magic[SARMAG];
Arch *ar;
struct SVR4namelist list;
list.fnametab = NULL;
arch = fopen(archive, "r");
if (arch == NULL)
return NULL;
if ((fread(magic, SARMAG, 1, arch) != 1) ||
(strncmp(magic, ARMAG, SARMAG) != 0)) {
fclose(arch);
return NULL;
}
ar = ohash_create_entry(&arch_info, archive, &earchive);
ohash_init(&ar->members, 8, &members_info);
for (;;) {
size_t n;
struct ar_hdr arHeader;
off_t size;
char buffer[PATH_MAX];
char *memberName;
char *cp;
memberName = buffer;
n = fread(&arHeader, 1, sizeof(struct ar_hdr), arch);
if (n == 0 && feof(arch)) {
free(list.fnametab);
fclose(arch);
return ar;
}
if (n < sizeof(struct ar_hdr))
break;
if (memcmp(arHeader.ar_fmag, ARFMAG, sizeof(arHeader.ar_fmag))
!= 0) {
break;
} else {
size = (off_t) field2long(arHeader.ar_size,
sizeof(arHeader.ar_size));
(void)memcpy(memberName, arHeader.ar_name,
AR_NAME_SIZE);
for (cp = memberName + AR_NAME_SIZE - 1; *cp == ' ';)
cp--;
cp[1] = '\0';
#ifdef SVR4ARCHIVES
if (memberName[0] == '/') {
memberName = ArchSVR4Entry(&list, memberName,
size, arch);
if (memberName == NULL)
break;
else if (memberName == svr4list)
continue;
}
else {
if (cp[0] == '/')
cp[0] = '\0';
}
#endif
#ifdef AR_EFMT1
if (memcmp(memberName, AR_EFMT1, sizeof(AR_EFMT1) - 1)
== 0 && ISDIGIT(memberName[sizeof(AR_EFMT1) - 1])) {
int elen = atoi(memberName +
sizeof(AR_EFMT1)-1);
if (elen <= 0 || elen >= PATH_MAX)
break;
memberName = buffer;
if (fread(memberName, elen, 1, arch) != 1)
break;
memberName[elen] = '\0';
if (fseek(arch, -elen, SEEK_CUR) != 0)
break;
if (DEBUG(ARCH) || DEBUG(MAKE))
printf("ArchStat: Extended format entry for %s\n",
memberName);
}
#endif
ohash_insert(&ar->members,
ohash_qlookup(&ar->members, memberName),
new_arch_member(&arHeader, memberName));
}
if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0)
break;
}
fclose(arch);
ohash_delete(&ar->members);
free(list.fnametab);
free(ar);
return NULL;
}
static struct timespec
ArchMTimeMember(
const char *archive,
const char *member,
bool hash)
{
FILE *arch;
Arch *ar;
unsigned int slot;
const char *end = NULL;
const char *cp;
struct timespec result;
ts_set_out_of_date(result);
cp = strrchr(member, '/');
if (cp != NULL)
member = cp + 1;
slot = ohash_qlookupi(&archives, archive, &end);
ar = ohash_find(&archives, slot);
if (ar == NULL) {
if (!hash) {
struct ar_hdr arHeader;
arch = ArchFindMember(archive, member, &arHeader, "r");
if (arch != NULL) {
fclose(arch);
ts_set_from_time_t(
(time_t)strtol(arHeader.ar_date, NULL, 10),
result);
}
return result;
}
ar = read_archive(archive, end);
if (ar != NULL)
ohash_insert(&archives, slot, ar);
}
if (ar != NULL) {
struct arch_member *he;
end = NULL;
he = ohash_find(&ar->members, ohash_qlookupi(&ar->members,
member, &end));
if (he != NULL)
return mtime_of_member(he);
else {
if ((size_t)(end - member) > AR_NAME_SIZE) {
end = member + AR_NAME_SIZE;
he = ohash_find(&ar->members,
ohash_qlookupi(&ar->members, member, &end));
if (he != NULL)
return mtime_of_member(he);
}
}
}
return result;
}
#ifdef SVR4ARCHIVES
static char *
ArchSVR4Entry(struct SVR4namelist *l, const char *name, size_t size, FILE *arch)
{
#define ARLONGNAMES1 "/"
#define ARLONGNAMES2 "ARFILENAMES"
size_t entry;
char *ptr, *eptr;
assert(name[0] == '/');
name++;
if (memcmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
memcmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
if (l->fnametab != NULL) {
if (DEBUG(ARCH))
printf("Attempted to redefine an SVR4 name table\n");
return NULL;
}
l->fnametab = emalloc(size);
l->fnamesize = size;
if (fread(l->fnametab, size, 1, arch) != 1) {
if (DEBUG(ARCH))
printf("Reading an SVR4 name table failed\n");
return NULL;
}
eptr = l->fnametab + size;
for (entry = 0, ptr = l->fnametab; ptr < eptr; ptr++)
switch (*ptr) {
case '/':
entry++;
*ptr = '\0';
break;
case '\n':
break;
default:
break;
}
if (DEBUG(ARCH))
printf("Found svr4 archive name table with %zu entries\n",
entry);
return (char *)svr4list;
}
if (*name == ' ' || *name == '\0')
return NULL;
entry = (size_t) strtol(name, &eptr, 0);
if ((*eptr != ' ' && *eptr != '\0') || eptr == name) {
if (DEBUG(ARCH))
printf("Could not parse SVR4 name /%s\n", name);
return NULL;
}
if (entry >= l->fnamesize) {
if (DEBUG(ARCH))
printf("SVR4 entry offset /%s is greater than %zu\n",
name, l->fnamesize);
return NULL;
}
if (DEBUG(ARCH))
printf("Replaced /%s with %s\n", name, l->fnametab + entry);
return l->fnametab + entry;
}
#endif
static FILE *
ArchFindMember(
const char *archive,
const char *member,
struct ar_hdr *arHeaderPtr,
const char *mode)
{
FILE * arch;
char *cp;
char magic[SARMAG];
size_t length;
struct SVR4namelist list;
list.fnametab = NULL;
arch = fopen(archive, mode);
if (arch == NULL)
return NULL;
if (fread(magic, SARMAG, 1, arch) != 1 ||
strncmp(magic, ARMAG, SARMAG) != 0) {
fclose(arch);
return NULL;
}
cp = strrchr(member, '/');
if (cp != NULL)
member = cp + 1;
length = strlen(member);
if (length >= AR_NAME_SIZE)
length = AR_NAME_SIZE;
while (fread(arHeaderPtr, sizeof(struct ar_hdr), 1, arch) == 1) {
off_t size;
char *memberName;
if (memcmp(arHeaderPtr->ar_fmag, ARFMAG,
sizeof(arHeaderPtr->ar_fmag) ) != 0)
break;
memberName = arHeaderPtr->ar_name;
if (memcmp(member, memberName, length) == 0) {
#ifdef SVR4ARCHIVES
if (length < sizeof(arHeaderPtr->ar_name) &&
memberName[length] == '/')
length++;
#endif
if (length == sizeof(arHeaderPtr->ar_name) ||
memberName[length] == ' ') {
free(list.fnametab);
return arch;
}
}
size = (off_t) field2long(arHeaderPtr->ar_size,
sizeof(arHeaderPtr->ar_size));
#ifdef SVR4ARCHIVES
if (memberName[0] == '/') {
memberName = ArchSVR4Entry(&list, arHeaderPtr->ar_name,
size, arch);
if (memberName == NULL)
break;
else if (memberName == svr4list)
continue;
if (strcmp(memberName, member) == 0) {
free(list.fnametab);
return arch;
}
}
#endif
#ifdef AR_EFMT1
if (memcmp(memberName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
ISDIGIT(memberName[sizeof(AR_EFMT1) - 1])) {
char ename[PATH_MAX];
int elength = atoi(memberName + sizeof(AR_EFMT1)-1);
if (elength <= 0 || elength >= PATH_MAX)
break;
if (fread(ename, elength, 1, arch) != 1)
break;
if (fseek(arch, -elength, SEEK_CUR) != 0)
break;
ename[elength] = '\0';
if (DEBUG(ARCH) || DEBUG(MAKE))
printf("ArchFind: Extended format entry for %s\n", ename);
if (strcmp(ename, member) == 0) {
free(list.fnametab);
return arch;
}
}
#endif
if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0)
break;
}
#ifdef SVRARCHIVES
free(list.fnametab);
#endif
fclose(arch);
return NULL;
}
static void
ArchTouch(const char *archive, const char *member)
{
FILE *arch;
struct ar_hdr arHeader;
arch = ArchFindMember(archive, member, &arHeader, "r+");
if (arch != NULL) {
char temp[sizeof(arHeader.ar_date)+1];
snprintf(temp, sizeof(temp), "%-12lld", (long long)time(NULL));
memcpy(arHeader.ar_date, temp, sizeof(arHeader.ar_date));
if (fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR) == 0)
(void)fwrite(&arHeader, sizeof(struct ar_hdr), 1, arch);
fclose(arch);
}
}
void
Arch_Touch(GNode *gn)
{
ArchTouch(Var(ARCHIVE_INDEX, gn), Var(MEMBER_INDEX, gn));
}
struct timespec
Arch_MTime(GNode *gn)
{
gn->mtime = ArchMTimeMember(Var(ARCHIVE_INDEX, gn),
Var(MEMBER_INDEX, gn), true);
return gn->mtime;
}
struct timespec
Arch_MemMTime(GNode *gn)
{
LstNode ln;
for (ln = Lst_First(&gn->parents); ln != NULL; ln = Lst_Adv(ln)) {
GNode *pgn;
char *nameStart;
char *nameEnd;
pgn = Lst_Datum(ln);
if (pgn->type & OP_ARCHV) {
if ((nameStart = strchr(pgn->name, '(') ) != NULL) {
nameStart++;
nameEnd = strchr(nameStart, ')');
} else
nameEnd = NULL;
if (pgn->must_make && nameEnd != NULL &&
strncmp(nameStart, gn->name, nameEnd - nameStart)
== 0 && gn->name[nameEnd-nameStart] == '\0')
gn->mtime = Arch_MTime(pgn);
} else if (pgn->must_make) {
ts_set_out_of_date(gn->mtime);
break;
}
}
return gn->mtime;
}
void
Arch_Init(void)
{
ohash_init(&archives, 4, &arch_info);
}