/*-
 * arch.c --
 *	Functions to manipulate libraries, archives and their members.
 *
 * Copyright (c) 1988, 1989 by the Regents of the University of California
 * Copyright (c) 1988, 1989 by Adam de Boor
 * Copyright (c) 1989 by Berkeley Softworks
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any non-commercial purpose
 * and without fee is hereby granted, provided that the above copyright
 * notice appears in all copies.  The University of California,
 * Berkeley Softworks and Adam de Boor make no representations about
 * the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 *	Once again, cacheing/hashing comes into play in the manipulation
 * of archives. The first time an archive is referenced, all of its members'
 * headers are read and hashed and the archive closed again. All hashed
 * archives are kept on a list which is searched each time an archive member
 * is referenced.
 *
 * The interface to this module is:
 *	Arch_ParseArchive   	Given an archive specification, return a list
 *	    	  	    	of GNode's, one for each member in the spec.
 *	    	  	    	FAILURE is returned if the specification is
 *	    	  	    	invalid for some reason.
 *
 *	Arch_FindSymbol   	Expand archive symbol lookup into member
 *	    	  	    	names.
 *
 *	Arch_Touch	    	Alter the modification time of the archive
 *	    	  	    	member described by the given node to be
 *	    	  	    	the current time.
 *
 *	Arch_TouchLib	    	Update the modification time of the library
 *	    	  	    	described by the given node. This is special
 *	    	  	    	because it also updates the modification time
 *	    	  	    	of the library's table of contents.
 *
 *	Arch_MTime	    	Find the modification time of a member of
 *	    	  	    	an archive *in the archive*. The time is also
 *	    	  	    	placed in the member's GNode. Returns the
 *	    	  	    	modification time.
 *
 *	Arch_MemMTime	    	Find the modification time of a member of
 *	    	  	    	an archive. Called when the member doesn't
 *	    	  	    	already exist. Looks in the archive for the
 *	    	  	    	modification time. Returns the modification
 *	    	  	    	time.
 *
 *	Arch_FindLib	    	Search for a library along a path. The
 *	    	  	    	library name in the GNode should be in
 *	    	  	    	-l<name> format.
 *
 *	Arch_LibOODate	    	Special function to decide if a library node
 *	    	  	    	is out-of-date.
 *
 *	Arch_Init 	    	Initialize this module.
 */
#ifndef lint
static char *rcsid = "$Id: arch.c,v 1.30 1999/09/16 13:57:33 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <stdio.h>
#include    <string.h>
#include    <sys/types.h>
#include    <sys/time.h>
#include    <ctype.h>
#include    <netinet/in.h>    /* for ntohl() */
#include    <ar.h>
#if !defined(SYSV) && !defined(AR_SYMPREF) /*MIPS*/
# include    <ranlib.h>
#endif
#include    "make.h"
#include    "hash.h"

/*
 * If the system doesn't define struct ranlib, do it here so we can
 * read old archive on newer systems.
 */
#if defined(SYSV) && !defined(AR_SYMPREF) /*MIPS*/
struct	ranlib {
	union {
		off_t	ran_strx;	/* string table index of */
		char	*ran_name;	/* symbol defined by */
	} ran_un;
	off_t	ran_off;		/* library member at this offset */
};
#endif /* has no <ranlib.h> */

/*
 * Similarly for MIPS archive stuff
 */
#ifndef AR_SYMPREF
#define AR_SYMPREF      "__________E"	/* common unique prefix */
#define AR_SYMPREFLEN   11		/* length of prefix */
#define AR_IOUTOFDATE   14		/* index for out of date char */
#define AR_OUTOFDATE    'X'		/* out of date char */
#define AR_ISSYMDEF(x) (!strncmp(x, AR_SYMPREF, AR_SYMPREFLEN))
#define AR_ISOUTOFDATE(x) ((x)[AR_IOUTOFDATE] == AR_OUTOFDATE)
#endif /* !AR_SYMPREF */

static Lst	  archives;   /* Lst of archives we've already examined */

typedef unsigned int	u_int32;		/* unsigned integer used in archive
					 * symbol tables (32 bit even on 64 bit
				         * machines) */
typedef struct Arch {
    char	  *name;      /* Name of archive */
    Hash_Table	  members;    /* All the members of the archive described
			       * by <name, struct ar_hdr *> key/value pairs */
    int	  	  type;       /* Type of archive -- a combination of flags */
#define ARCH_OLD	0x00      /* old-style archive */
#define ARCH_COMMON	0x01      /* SYSV Common Archive format */
#define ARCH_LONGNAM	0x02      /* SVR4-style long name indirection */
#define ARCH_NEWBSD	0x04      /* 4.4BSD style long name extension */
#define ARCH_GNU	0x08      /* GNU style long name indirection */
#define ARCH_BAD	0x80      /* Format error detected by ArchScan() */
} Arch;

#define ARCH_TOC	"/"   /* Format-independent hash key for the table
			       * of contents member (a string that cannot
			       * be a real filename). */
#define ARCH_ISTOC(x)	(strncmp(x, LIBTOC, sizeof(LIBTOC)-1) == 0 && \
                   	 (x)[sizeof(LIBTOC)-1] == ' ')
			      /* ranlib archive TOC 
			       * Note: the above test handles NeXT's
			       * `__.SYMDEF SORTED' (and possibly other
			       * extensions to `__.SYMDEF'.
			       */
#define ARCH_ISCOMTOC(x) (strncmp(x, "/ ", 2)==0)
			      /* Common archive TOC */
#define ARCH_ISCOMNAMES(x) (strncmp(x, "//", 2)==0)
			      /* Common archive long name section */
#define ARCH_ISGNUNAMES(x) (strncmp(x, "ARFILENAMES/", 12)==0)
			      /* GNU binutils long name section */
#define ARCH_ISLONGNAME(x) (x[0] == '/' && isdigit(x[1]))
			      /* Common archive long name reference */
#define ARCH_ISBSDLONGNAME(x) (strncmp(x, "#1/", 3)==0)
			      /* 4.4BSD style long name reference */
#define ARCH_ISGNULONGNAME(x) (x[0] == ' ' && isdigit(x[1]))
			      /* GNU binutils long name reference */

static FILE *ArchFindMember();

/*-
 *-----------------------------------------------------------------------
 * ArchScan --
 *	Sequentially process an archive's header structures.
 *
 * Results:
 *	The string name of the next archive member.  NULL is returned
 *	to indicate that the end of the archive has been reached or
 *	a format error was found.
 *	The buffer pointed to by arhPtr is fill with the header info
 *	for the member.
 *	The transient contents of *typePtr is updated to keep track of
 *	the archive format. An error is additionally indicated by
 *	setting *typePtr to ARCH_BAD.
 *	A NULL file pointer causes freeing of resources associated
 *	with the previous scan.
 *
 * Side Effects:
 *	The file pointer is advanced to the beginning of the next
 *	header.
 *
 *-----------------------------------------------------------------------
 */
static char *
ArchScan(fp, arhPtr, typePtr)
    FILE *fp;			/* File pointer into archive */
    struct ar_hdr *arhPtr;	/* Pointer to header info returned */
    int *typePtr;		/* Pointer to format type (transient) */
{
    static char   copy[256];	/* edited ar_name */
    static char	  *longNames = NULL;
				/* Table of long names */
    static int	  longSize;	/* ... and its size */

    if (fp == (FILE *)NULL) {
        if (DEBUG(ARCH)) {
	    Debug("\n");
	}
	if (longNames) {
	    free(longNames);
	}
	return (NULL);
    } else {
	char *memName;	      /* Member name returned */
	long size;	      /* Size of current member */
again:
        if (fread ((char *)arhPtr, sizeof (struct ar_hdr), 1, fp) != 1) {
	    return (NULL);
        }

	if (strncmp (arhPtr->ar_fmag, ARFMAG, sizeof (arhPtr->ar_fmag)) != 0) {
	    /*
	     * The header is bogus, so the archive is bad
	     * and there's no way we can recover...
	     */
	    if (DEBUG(ARCH)) {
		Debug("bad header\n");
	    }
	    *typePtr = ARCH_BAD;
	    return (NULL);
	} else if (ARCH_ISCOMNAMES(arhPtr->ar_name) ||
		   ARCH_ISGNUNAMES(arhPtr->ar_name))
	{
	    /*
	     * Read the long names section into memory for later reference
	     */
	    longSize = atoi(arhPtr->ar_size);
	    longNames = emalloc(longSize);
	    if (fread (longNames, 1, longSize, fp) != longSize) {
		if (DEBUG(ARCH)) {
		    Debug("bad long names (%d bytes)\n", longSize);
		}
	        *typePtr = ARCH_BAD;
		return (NULL);
	    }
	    if (longSize&1) {
	        fseek (fp, 1L, 1);
	    }
	    if (DEBUG(ARCH)) {
		Debug("long names (%d bytes)...", longSize);
	    }
	    if (ARCH_ISCOMNAMES(arhPtr->ar_name)) {
		    *typePtr |= ARCH_COMMON;
	    } else {
		    *typePtr |= ARCH_GNU;
	    }
	    *typePtr |= ARCH_LONGNAM;
	    goto again;
	} else if (ARCH_ISLONGNAME(arhPtr->ar_name) ||
		   ARCH_ISGNULONGNAME(arhPtr->ar_name))
	{
	    char *cp;
	    int idx = atoi(arhPtr->ar_name + 1);

	    if (!longNames || idx >= longSize) {
		if (DEBUG(ARCH)) {
		    Debug("bad long name index %d\n", idx);
		}
	        *typePtr = ARCH_BAD;
		return (NULL);
	    }
	    
	    /* 
	     * Find the delimiting / in the long names table
	     * XXX: As a result of multiple scans the delimiter may have
	     * already been replaced by \0.
	     */
	    for (cp = longNames + idx;
	         (cp < longNames + longSize) && *cp &&
			(*cp != ((*typePtr & ARCH_GNU) ? '\n' : '/'));
		 cp++);
	    if (cp == longNames + longSize) {
		if (DEBUG(ARCH)) {
		    Debug("missing / from long name #%d\n", idx);
		}
	        *typePtr = ARCH_BAD;
		return (NULL);
	    }
	    /*
	     * We have the name -- copy from the table.
	     * XXX: since each index is looked up only once it doesn't hurt
	     * modifying the table itself, replacing the final slash with \0.
	     */
	    *cp = '\0';
	    memName = longNames + idx;
	    if (DEBUG(ARCH)) {
		Debug("%s (long name #%d)...", memName, idx);
	    }
	} else if (ARCH_ISCOMTOC(arhPtr->ar_name)) {
	    if (DEBUG(ARCH)) {
		Debug("common format t.o.c...");
	    }
	    memName = ARCH_TOC;
	    *typePtr |= ARCH_COMMON;
	} else if (ARCH_ISTOC(arhPtr->ar_name)) {
	    if (DEBUG(ARCH)) {
		Debug("%s t.o.c...", LIBTOC);
	    }
	    memName = ARCH_TOC;
	} else if (AR_ISSYMDEF(arhPtr->ar_name)) {
	    if (DEBUG(ARCH)) {
		Debug("mips format t.o.c...");
	    }
	    memName = ARCH_TOC;
	} else if (ARCH_ISBSDLONGNAME(arhPtr->ar_name)) {
	    int namelen = atoi(arhPtr->ar_name + 3);

	    if (!(*typePtr & ARCH_NEWBSD) && DEBUG(ARCH)) {
		Debug("4.4BSD archive...");
	    }
	    *typePtr |= ARCH_NEWBSD;

	    /*
	     * The long name string follows the header, so we are already
	     * positioned correctly.
	     */
            if ((namelen <= 0) || (namelen >= sizeof(copy)) ||
		fread (copy, 1, namelen, fp) != namelen)
	    {
		if (DEBUG(ARCH)) {
		    Debug("couldn't read long name (length %d)\n", namelen);
		}
		*typePtr = ARCH_BAD;
		return (NULL);
	    }
	    copy[namelen] = '\0';
	    memName = copy;

	    if (DEBUG(ARCH)) {
		Debug("%s...", memName);
	    }
	} else {
	    static char	copy[sizeof(arhPtr->ar_name)+1]; /* edited ar_name */
	    char *cp;

	    /*
	     * Extract the name by scanning trailing blanks.
	     */
	    memcpy(copy, arhPtr->ar_name, sizeof(arhPtr->ar_name));
	    memName = copy;
	    /*
	     * One indication of new-style BSD archives is that they
	     * may use all of the 16 bytes in ar_name for the name,
	     * not leaving a space at the end.
	     */
	    if (memName[sizeof(arhPtr->ar_name) - 1] != ' ') {
		if (!(*typePtr & ARCH_NEWBSD) && DEBUG(ARCH)) {
		    Debug("4.4BSD archive...");
		}
		*typePtr |= ARCH_NEWBSD;
	    }
	    for (cp = &memName[sizeof(arhPtr->ar_name) - 1];
	         *cp == ' ';
		 cp--);
	    if (cp >= memName && *cp == '/') {
		if (!(*typePtr & ARCH_COMMON) && DEBUG(ARCH)) {
		    Debug("common archive...");
		}
		*typePtr |= ARCH_COMMON;
		cp--;
	    }
		
	    cp[1] = '\0';

	    if (DEBUG(ARCH)) {
		Debug("%s...", memName);
	    }
	}

	/*
	 * We need to advance the stream's pointer to the start of the
	 * next header. Files are padded with newlines to an even-byte
	 * boundary.  Because of the 4.4BSD long names extension (which
	 * may add an odd number of bytes to the member size) the padding
	 * depends not only on the size of the member, but also on the
	 * current position in the file.
	 */
	size = atoi(arhPtr->ar_size);
	fseek (fp, (ftell(fp) + size + 1)&~1L, 0);

	return (memName);
    }
}

/*-
 *-----------------------------------------------------------------------
 * Arch_FindSymbol --
 *	Given a library and a symbol reference, find the object member in
 *	which the symbol is defined.
 *
 * Results:
 *	The member name, or NULL if not found.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
char *
Arch_FindSymbol (archive, symbol)
    char	   *archive;    /* Path to the archive */
    char	   *symbol;     /* Symbol name */
{
    FILE	    *arch;	/* Stream open to archive */
    struct ar_hdr   arh;      	/* Header describing table of contents */
    char	    *lastc;	/* Pointer to last character in symbol */

    /*
     * Check for the proper `(symbol)' syntax.  If this is not
     * a symbol reference return the argument unchanged.
     */
    if (*symbol != '(') {
	return (symbol);
    }

    lastc = symbol + strlen(symbol) - 1;
    if (*lastc != ')') {
	return (symbol);
    }

    symbol = symbol +1;
    *lastc = '\0';

    arch = ArchFindMember (archive, ARCH_TOC, &arh, "r");

    if (arch == (FILE *) NULL) {
	Error ("Archive %s has no table of contents", archive);
	*lastc = ')';
	return (NULL);
    } else {
	char		*result = NULL;
	u_int32	        nSymbols;	/* No. of symbols defined in archive */
	long		offset;
	long		tocPos;
	int		tocSize;
	int		arType = ARCH_OLD;

	tocSize = atoi(arh.ar_size);
	tocPos = ftell(arch) + sizeof(struct ar_hdr);

	/*
	 * Skip header and get no. of symbols in TOC
	 */
	if (fseek (arch, (long)sizeof(struct ar_hdr), 1) < 0 ||
	    fread ((char *)&nSymbols, sizeof(nSymbols), 1, arch) != 1)
	{
	    Error ("Archive %s has corrupted table of contents", archive);
	    goto cleanup;
	}

	if (ARCH_ISCOMTOC(arh.ar_name)) {
	    u_int32	*offsets;	/* offset table */
	    char	*strTable = NULL; /* String table */
	    long	strTableLen;
	    char	*cp;
	    int	        i;

	    nSymbols = ntohl(nSymbols);

	    /*
	     * Slurp in the offset table
	     */
	    offsets = (u_int32 *)emalloc(nSymbols * sizeof(u_int32));
	    if (fread ((char *)offsets, sizeof(u_int32), nSymbols, arch)
	         != nSymbols)
	    {
		Error ("Archive %s has corrupted table of contents", archive);
		free (offsets);
		goto cleanup;
	    }

	    strTableLen = tocSize - sizeof (nSymbols) * (nSymbols + 1);
	    strTable = emalloc(strTableLen);
	    if (fread ((char *)strTable, 1, strTableLen, arch) != strTableLen) {
		Error ("Archive %s has corrupted table of contents", archive);
		free (offsets);
		free (strTable);
		goto cleanup;
	    }

	    /*
	     * Search string table for symbol
	     */
	    for (i = 0, cp = strTable; i < nSymbols; i++, cp += strlen(cp)+1) {
		if (strcmp(symbol, cp) == 0)
		    break;
	    }
	    offset = (i == nSymbols) ? -1 : ntohl(offsets[i]);

	    free (offsets);
	    free (strTable);
	} else {
	    struct ranlib *ranlibs = NULL;	/* Array of ranlib structs */
	    char	*strTable = NULL;	/* String table */
	    u_int32	strTableLen;
	    int	        i;

	    /*
	     * MIPS archives give the total number of entries, otherwise
	     * total size.
	     */
	    if (!AR_ISSYMDEF(arh.ar_name))
	    {
		nSymbols /= sizeof(struct ranlib);
	    }

	    /*
	     * Slurp in the index and the string table.
	     */
	    ranlibs = (struct ranlib *)
	                emalloc(nSymbols * sizeof(struct ranlib));
	    if (fread ((char *)ranlibs, sizeof(struct ranlib), nSymbols, arch)
		    != nSymbols)
	    {
		Error ("Archive %s has corrupted table of contents", archive);
		free (ranlibs);
		goto cleanup;
	    }

	    if (fread ((char *)&strTableLen, sizeof(strTableLen), 1, arch)
		 != 1)
	    {
		Error ("Archive %s has corrupted table of contents", archive);
		goto cleanup;
	    }

	    strTable = emalloc(strTableLen);
	    if (fread ((char *)strTable, 1, strTableLen, arch) != strTableLen) {
		Error ("Archive %s has corrupted table of contents", archive);
		free (ranlibs);
		free (strTable);
		goto cleanup;
	    }

	    for (i = 0; i < nSymbols; i++) {
		char *s = &strTable[ranlibs[i].ran_un.ran_strx];

		if (DEBUG(ARCH)) {
		    Debug ("%s: symbol #%d: %s at offset %ld\n",
			   archive, i, s, ranlibs[i].ran_off);
		}
		if (*s == '_') s++;
		if (ranlibs[i].ran_off > 0 && strcmp(symbol, s) == 0) {
		    break;
		}
	    }
	    offset = (i == nSymbols) ? -1 : ranlibs[i].ran_off;

	    free (ranlibs);
	    free (strTable);
	}

	if (offset == -1) {
	    Error ("No symbol %s in archive %s", symbol, archive);
	    goto cleanup;
	}

	/*
	 * We'd like to use ArchScan to get the member name for this offset.
	 * To resolve any long name indirections we have to scan the long names
	 * section as well.  We assume it follows the TOC, if at all present.
	 */
	if ((offset != ((tocPos + tocSize + 1)&~1L)) &&
	    (fseek (arch, (tocPos + tocSize + 1)&~1L, 0) >= 0))
	{
	    (void)ArchScan(arch, &arh, &arType);
	}

	/*
	 * Now we can seek to the previously determined offset and scan that
	 * member, returning us the member name.
	 */
	if (fseek (arch, offset, 0) < 0 ||
	    (result = ArchScan(arch, &arh, &arType)) == NULL)
	{
	    Error ("Member for symbol %s in archive %s corrupted",
		   symbol, archive);
	    goto cleanup;
	}
	result = Str_New(result);

    cleanup:
	ArchScan((FILE *)NULL, &arh, &arType);
	fclose (arch);
	*lastc = ')';
	return (result);
    }
}

/*-
 *-----------------------------------------------------------------------
 * Arch_ParseArchive --
 *	Parse the archive specification in the given line and find/create
 *	the nodes for the specified archive members, placing their nodes
 *	on the given list.
 *
 * Results:
 *	SUCCESS if it was a valid specification. The linePtr is updated
 *	to point to the first non-space after the archive spec. The
 *	nodes for the members are placed on the given list.
 *
 * Side Effects:
 *	Some nodes may be created. The given list is extended.
 *
 *-----------------------------------------------------------------------
 */
ReturnStatus
Arch_ParseArchive (linePtr, nodeLst, ctxt)
    char	    **linePtr;      /* Pointer to start of specification */
    Lst	    	    nodeLst;   	    /* Lst on which to place the nodes */
    GNode   	    *ctxt;  	    /* Context in which to expand variables */
{
    register char   *cp;	    /* Pointer into line */
    GNode	    *gn;     	    /* New node */
    char	    *libName;  	    /* Library-part of specification */
    char	    *memName;  	    /* Member-part of specification */
    char	    *nameBuf;	    /* temporary place for node name */
    char	    saveChar;  	    /* Ending delimiter of member-name */
    Boolean 	    subLibName;	    /* TRUE if libName should have/had
				     * variable substitution performed on it */

    libName = *linePtr;
    
    subLibName = FALSE;

    for (cp = libName; *cp != '(' && *cp != '\0'; cp++) {
	if (*cp == '$') {
	    /*
	     * Variable spec, so call the Var module to parse the puppy
	     * so we can safely advance beyond it...
	     */
	    int 	length;
	    Boolean	freeIt;
	    char	*result;
	    
	    result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
	    if (result == var_Error) {
		return(FAILURE);
	    } else {
		subLibName = TRUE;
	    }
	    
	    if (freeIt) {
		free(result);
	    }
	    cp += length-1;
	}
    }

    *cp++ = '\0';
    if (subLibName) {
	libName = Var_Subst(libName, ctxt, TRUE);
    }
    

    while (1) {
	/*
	 * First skip to the start of the member's name, mark that
	 * place and skip to the end of it (either white-space or
	 * a close paren).
	 */
	Boolean	doSubst = FALSE; /* TRUE if need to substitute in memName */
	Boolean symName;	 /* TRUE if scanning a symbol name in parens */

	while (*cp != '\0' && *cp != ')' && isspace (*cp)) {
	    cp++;
	}
	memName = cp;
	symName = *cp == '(';
	while (*cp != '\0' && (symName || *cp != ')') && !isspace (*cp)) {
	    if (*cp == '$') {
		/*
		 * Variable spec, so call the Var module to parse the puppy
		 * so we can safely advance beyond it...
		 */
		int 	length;
		Boolean	freeIt;
		char	*result;

		result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
		if (result == var_Error) {
		    return(FAILURE);
		} else {
		    doSubst = TRUE;
		}

		if (freeIt) {
		    free(result);
		}
		cp += length;
	    } else if (*cp == ')') {
		/*
	 	 * End of symbol spec -- this terminates the member spec, too.
		 */
		symName = FALSE;
		cp++;
		break;
	    } else {
		cp++;
	    }
	}

	/*
	 * If the specification ends without a closing parenthesis,
	 * chances are there's something wrong (like a missing backslash),
	 * so it's better to return failure than allow such things to happen
	 */
	if (*cp == '\0') {
	    Error ("No closing parenthesis in archive specification");
	    return (FAILURE);
	}

	/*
	 * If we didn't move anywhere, we must be done
	 */
	if (cp == memName) {
	    break;
	}

	saveChar = *cp;
	*cp = '\0';

	/*
	 * XXX: This should be taken care of intelligently by
	 * SuffExpandChildren, both for the archive and the member portions.
	 */
	/*
	 * If member contains variables, try and substitute for them.
	 * This will slow down archive specs with dynamic sources, of course,
	 * since we'll be (non-)substituting them three times, but them's
	 * the breaks -- we need to do this since SuffExpandChildren calls
	 * us, otherwise we could assume the thing would be taken care of
	 * later.
	 */
	if (doSubst) {
	    char    *buf;
	    char    *sacrifice;
	    char    *oldMemName = memName;
	    
	    memName = Var_Subst(memName, ctxt, TRUE);

	    /*
	     * Now form an archive spec and recurse to deal with nested
	     * variables and multi-word variable values.... The results
	     * are just placed at the end of the nodeLst we're returning.
	     */
	    buf=sacrifice=(char *)emalloc(strlen(memName)+strlen(libName)+3);

	    sprintf(buf, "%s(%s)", libName, memName);

	    if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
		/*
		 * Must contain dynamic sources, so we can't deal with it now.
		 * Just create an ARCHV node for the thing and let
		 * SuffExpandChildren handle it...
		 */
		gn = Targ_FindNode(buf, TARG_CREATE);

		if (gn == NILGNODE) {
		    free(buf);
		    return(FAILURE);
		} else {
		    gn->type |= OP_ARCHV;
		    (void)Lst_AtEnd(nodeLst, (ClientData)gn);
		}
	    } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) {
		/*
		 * Error in nested call -- free buffer and return FAILURE
		 * ourselves.
		 */
		free(buf);
		return(FAILURE);
	    }
	    /*
	     * Free buffer and continue with our work.
	     */
	    free(buf);
	} else if (Dir_HasWildcards(memName)) {
	    Lst	  members = Lst_Init(FALSE);
	    char  *member;

	    Dir_Expand(memName, dirSearchPath, members);
	    while (!Lst_IsEmpty(members)) {
		member = (char *)Lst_DeQueue(members);
		
		nameBuf = emalloc(strlen(libName) + strlen(member) + 3);
		sprintf(nameBuf, "%s(%s)", libName, member);
		free(member);
		gn = Targ_FindNode (nameBuf, TARG_CREATE);
		free(nameBuf);
		if (gn == NILGNODE) {
		    return (FAILURE);
		} else {
		    /*
		     * We've found the node, but have to make sure the rest of
		     * the world knows it's an archive member, without having
		     * to constantly check for parentheses, so we type the
		     * thing with the OP_ARCHV bit before we place it on the
		     * end of the provided list.
		     * Clear the OP_LIB bit that might have been set if the
		     * thing starts with -l ...
		     */
		    gn->type &= ~OP_LIB;
		    gn->type |= OP_ARCHV;
		    (void) Lst_AtEnd (nodeLst, (ClientData)gn);
		}
	    }
	    Lst_Destroy(members, NOFREE);
	} else {
	    nameBuf = emalloc(strlen(libName) + strlen(memName) + 3);
	    sprintf(nameBuf, "%s(%s)", libName, memName);
	    gn = Targ_FindNode (nameBuf, TARG_CREATE);
	    free(nameBuf);
	    if (gn == NILGNODE) {
		return (FAILURE);
	    } else {
		/*
		 * We've found the node, but have to make sure the rest of the
		 * world knows it's an archive member, without having to
		 * constantly check for parentheses, so we type the thing with
		 * the OP_ARCHV bit before we place it on the end of the
		 * provided list.
		 * Clear the OP_LIB bit that might have been set if the thing
		 * starts with -l ...
		 */
		gn->type &= ~OP_LIB;
		gn->type |= OP_ARCHV;
		(void) Lst_AtEnd (nodeLst, (ClientData)gn);
	    }
	}
	if (doSubst) {
	    free(memName);
	}
	
	*cp = saveChar;
    }

    /*
     * If substituted libName, free it now, since we need it no longer.
     */
    if (subLibName) {
	free(libName);
    }

    /*
     * We promised the pointer would be set up at the next non-space, so
     * we must advance cp there before setting *linePtr... (note that on
     * entrance to the loop, cp is guaranteed to point at a ')')
     */
    do {
	cp++;
    } while (*cp != '\0' && isspace (*cp));

    *linePtr = cp;
    return (SUCCESS);
}

/*-
 *-----------------------------------------------------------------------
 * ArchTruncateName --
 *	Truncate a member name string to the maximum length supported
 *	by the archive format.
 *
 * Results:
 *	The possibly truncated string (in a static buffer).
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static char*
ArchTruncateName (member, type)
    char	  *member;	   /* Member name */
    int		  type;		   /* Archive type */
{
    static char truncated[16];	   /* the result of truncation */

    if (type&(ARCH_LONGNAM|ARCH_NEWBSD)) {
	/*
	 * Archive supports long names -- no truncation needed
	 */
	return (member);
    } else if (type&ARCH_COMMON) {
	/*
	 * Common archive format can contain member names up to 14 chars
	 */
	strncpy(truncated, member, 14);
	truncated[14] = '\0';
	return (truncated);
    } else {
	/*
	 * Old archive format can contain member names up to 15 chars
	 */
	strncpy(truncated, member, 15);
	truncated[15] = '\0';
	return (truncated);
    }
}

/*-
 *-----------------------------------------------------------------------
 * ArchFindArchive --
 *	See if the given archive is the one we are looking for. Called
 *	From ArchStatMember and ArchFindMember via Lst_Find.
 *
 * Results:
 *	0 if it is, non-zero if it isn't.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static int
ArchFindArchive (ar, archName)
    Arch	  *ar;	      	  /* Current list element */
    char	  *archName;  	  /* Name we want */
{
    return (strcmp (archName, ar->name));
}

/*-
 *-----------------------------------------------------------------------
 * ArchStatMember --
 *	Locate a member of an archive, given the path of the archive and
 *	the path of the desired member.
 *
 * Results:
 *	A pointer to the current struct ar_hdr structure for the member. Note
 *	That no position is returned, so this is not useful for touching
 *	archive members. This is mostly because we have no assurances that
 *	The archive will remain constant after we read all the headers, so
 *	there's not much point in remembering the position...
 *
 * Side Effects:
 *
 *-----------------------------------------------------------------------
 */
static struct ar_hdr *
ArchStatMember (archive, member, hash)
    char	  *archive;   /* Path to the archive */
    char	  *member;    /* Name of member. If it is a path, only the
			       * last component is used. */
    Boolean	  hash;	      /* TRUE if archive should be hashed if not
    			       * already so. */
{
    FILE *	  arch;	      /* Stream to archive */
    char	  *cp;	      /* Useful character pointer */
    char	  magic[SARMAG];
    LstNode	  ln;	      /* Lst member containing archive descriptor */
    Arch	  *ar;	      /* Archive descriptor */
    Hash_Entry	  *he;	      /* Entry containing member's description */
    struct ar_hdr arh;        /* archive-member header for reading archive */
    char	  *memName;   /* Current member name while hashing. */

    /*
     * Because of space constraints and similar things, files are archived
     * using their final path components, not the entire thing, so we need
     * to point 'member' to the final component, if there is one, to make
     * the comparisons easier...
     */
    if (strcmp(member, ARCH_TOC) != 0) {
	cp = strrchr (member, '/');
	if (cp != (char *) NULL) {
	    member = cp + 1;
	}
    }

    ln = Lst_Find (archives, (ClientData) archive, ArchFindArchive);
    if (ln != NILLNODE) {
	ar = (Arch *) Lst_Datum (ln);

	he = Hash_FindEntry (&ar->members,
	                     (Address) ArchTruncateName(member, ar->type));

	if (he != (Hash_Entry *) NULL) {
	    return ((struct ar_hdr *) Hash_GetValue (he));
	} else {
	    return ((struct ar_hdr *) NULL);
	}
    }

    if (!hash) {
	/*
	 * Caller doesn't want the thing hashed, just use ArchFindMember
	 * to read the header for the member out and close down the stream
	 * again. Since the archive is not to be hashed, we assume there's
	 * no need to allocate extra room for the header we're returning,
	 * so just declare it static.
	 */
	 static struct ar_hdr	sarh;

	 arch = ArchFindMember(archive, member, &sarh, "r");

	 if (arch == (FILE *)NULL) {
	    return ((struct ar_hdr *)NULL);
	} else {
	    fclose(arch);
	    return (&sarh);
	}
    }

    /*
     * We don't have this archive on the list yet, so we want to find out
     * everything that's in it and cache it so we can get at it quickly.
     */
    arch = fopen (archive, "r");
    if (arch == (FILE *) NULL) {
	return ((struct ar_hdr *) NULL);
    }
    
    /*
     * We use the ARMAG string to make sure this is an archive we
     * can handle...
     */
    if ((fread (magic, SARMAG, 1, arch) != 1) ||
    	(strncmp (magic, ARMAG, SARMAG) != 0)) {
	    fclose (arch);
	    return ((struct ar_hdr *) NULL);
    }

    ar = (Arch *) emalloc (sizeof (Arch));
    ar->name = Str_New (archive);
    ar->type = ARCH_OLD;
    Hash_InitTable (&ar->members, -1, HASH_STRING_KEYS);
    
    if (DEBUG(ARCH)) {
	Debug("ArchStatMember: scanning %s...", archive);
    }
    while (memName = ArchScan(arch, &arh, &ar->type)) {
	he = Hash_CreateEntry (&ar->members, memName, (Boolean *)NULL);
	Hash_SetValue (he, (ClientData)emalloc (sizeof (struct ar_hdr)));
	memcpy (Hash_GetValue (he), &arh, sizeof (struct ar_hdr));
    }
    ArchScan((FILE *)NULL, &arh, &ar->type);
    fclose (arch);

    if (ar->type == ARCH_BAD) {
	Hash_DeleteTable (&ar->members);
	free ((Address)ar);
	return ((struct ar_hdr *) NULL);
    }

    (void) Lst_AtEnd (archives, (ClientData) ar);

    /*
     * Now that the archive has been read and cached, we can look into
     * the hash table to find the desired member's header.
     */
    he = Hash_FindEntry (&ar->members,
                         (Address) ArchTruncateName(member, ar->type));

    if (he != (Hash_Entry *) NULL) {
	return ((struct ar_hdr *) Hash_GetValue (he));
    } else {
	return ((struct ar_hdr *) NULL);
    }
}

/*-
 *-----------------------------------------------------------------------
 * ArchFindMember --
 *	Locate a member of an archive, given the path of the archive and
 *	the path of the desired member. If the archive is to be modified,
 *	the mode should be "r+", if not, it should be "r".
 *
 * Results:
 *	An FILE *, opened for reading and writing, positioned at the
 *	start of the member's struct ar_hdr, or NULL if the member was
 *	nonexistent. The current struct ar_hdr for member.
 *
 * Side Effects:
 *	The passed struct ar_hdr structure is filled in.
 *
 *-----------------------------------------------------------------------
 */
static FILE *
ArchFindMember (archive, member, arhPtr, mode)
    char	  *archive;   /* Path to the archive */
    char	  *member;    /* Name of member. If it is a path, only the
			       * last component is used. */
    struct ar_hdr *arhPtr;    /* Pointer to header structure to be filled in */
    char	  *mode;      /* The mode for opening the stream */
{
    FILE *	  arch;	      /* Stream to archive */
    char	  *cp;	      /* Useful character pointer */
    char	  magic[SARMAG];
    int		  arType;     /* Archive format */
    char	  *memName;   /* Current member name for comparison */
    long	  lastPos;    /* File position of last header */

    arch = fopen (archive, mode);
    if (arch == (FILE *) NULL) {
	return ((FILE *) NULL);
    }
    
    /*
     * We use the ARMAG string to make sure this is an archive we
     * can handle...
     */
    if ((fread (magic, SARMAG, 1, arch) != 1) ||
    	(strncmp (magic, ARMAG, SARMAG) != 0))
    {
	fclose (arch);
	return ((FILE *) NULL);
    }

    /*
     * Start out assuming an old-stype archive
     */
    arType = ARCH_OLD;

    /*
     * Because of space constraints and similar things, files are archived
     * using their final path components, not the entire thing, so we need
     * to point 'member' to the final component, if there is one, to make
     * the comparisons easier...
     */
    if (strcmp(member, ARCH_TOC) != 0) {
	cp = strrchr (member, '/');
	if (cp != (char *) NULL) {
	    member = cp + 1;
	}
    }
    
    if (DEBUG(ARCH)) {
	Debug("ArchFindMember: scanning %s for %s...",
              archive, strcmp(member, ARCH_TOC) == 0 ? "t.o.c" : member);
    }
    lastPos = ftell(arch);
    while (memName = ArchScan(arch, arhPtr, &arType)) {
	/*
	 * Check requested and actual member names for equality,
	 * modulo truncation of the latter.
	 */
	if (strcmp(memName, ArchTruncateName(member, arType)) == 0) {
	    ArchScan((FILE *)NULL, arhPtr, &arType);
	    /*
	     * To make life easier, we reposition the file at the start
	     * of the header we just read before we return the stream.
	     * In a more general situation, it might be better to leave
	     * the file at the actual member, rather than its header, but
	     * not here...
	     */
	    fseek (arch, lastPos, 0);
	    return (arch);
	}
	lastPos = ftell(arch);
    }

    /*
     * We've looked everywhere, but the member is not to be found. Close the
     * archive and return NULL -- an error.
     */
    fclose (arch);
    ArchScan((FILE *)NULL, arhPtr, &arType);
    return ((FILE *) NULL);
}

/*-
 *-----------------------------------------------------------------------
 * Arch_Touch --
 *	Touch a member of an archive.
 *
 * Results:
 *	The 'time' field of the member's header is updated.
 *
 * Side Effects:
 *	The modification time of the entire archive is also changed.
 *	For a library, this could necessitate the re-ranlib'ing of the
 *	whole thing.
 *
 *-----------------------------------------------------------------------
 */
void
Arch_Touch (gn)
    GNode	  *gn;	  /* Node of member to touch */
{
    FILE *	  arch;	  /* Stream open to archive, positioned properly */
    struct ar_hdr arh;	  /* Current header describing member */

    arch = ArchFindMember(Var_Value (ARCHIVE, gn),
			  Var_Value (MEMBER, gn),
			  &arh, "r+");
    sprintf(arh.ar_date, "%-12d", now);

    if (arch != (FILE *) NULL) {
	(void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch);
	fclose (arch);
    }
}

/*-
 *-----------------------------------------------------------------------
 * Arch_TouchLib --
 *	Given a node which represents a library, touch the thing, making
 *	sure that the table of contents also is touched.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Both the modification time of the library and of the table-of-
 *	contents member are set to 'now'.
 *
 *-----------------------------------------------------------------------
 */
void
Arch_TouchLib (gn)
    GNode	    *gn;      	/* The node of the library to touch */
{
    FILE *	    arch;	/* Stream open to archive */
    struct ar_hdr   arh;      	/* Header describing table of contents */
    struct timeval  times[2];	/* Times for utimes() call */

    arch = ArchFindMember (gn->path, ARCH_TOC, &arh, "r+");
    sprintf(arh.ar_date, "%-12d", now);
    if (AR_ISSYMDEF(arh.ar_name)) {
	/*
	 * Make sure we clear the out-of-date flag in the symbol table name.
	 */
	arh.ar_name[AR_IOUTOFDATE] = '_';
    }

    if (arch != (FILE *) NULL) {
	(void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch);
	fclose (arch);

	times[0].tv_sec = times[1].tv_sec = now;
	times[0].tv_usec = times[1].tv_usec = 0;
	utimes(gn->path, times);
    }
}

/*-
 *-----------------------------------------------------------------------
 * Arch_MTime --
 *	Return the modification time of a member of an archive.
 *
 * Results:
 *	The modification time (seconds).
 *
 * Side Effects:
 *	The mtime field of the given node is filled in with the value
 *	returned by the function.
 *
 *-----------------------------------------------------------------------
 */
int
Arch_MTime (gn)
    GNode	  *gn;	      /* Node describing archive member */
{
    struct ar_hdr *arhPtr;    /* Header of desired member */
    int		  modTime;    /* Modification time as an integer */

    arhPtr = ArchStatMember (Var_Value (ARCHIVE, gn),
			     Var_Value (MEMBER, gn),
			     TRUE);
    if (arhPtr != (struct ar_hdr *) NULL) {
	modTime = atoi(arhPtr->ar_date);
    } else {
	modTime = 0;
    }

    gn->mtime = modTime;
    return (modTime);
}

/*-
 *-----------------------------------------------------------------------
 * Arch_MemMTime --
 *	Given a non-existent archive member's node, get its modification
 *	time from its archived form, if it exists.
 *
 * Results:
 *	The modification time.
 *
 * Side Effects:
 *	The mtime field is filled in.
 *
 *-----------------------------------------------------------------------
 */
int
Arch_MemMTime (gn)
    GNode   	  *gn;
{
    LstNode 	  ln;
    GNode   	  *pgn;

    if (Lst_Open (gn->parents) != SUCCESS) {
	gn->mtime = 0;
	return (0);
    }
    while ((ln = Lst_Next (gn->parents)) != NILLNODE) {
	pgn = (GNode *) Lst_Datum (ln);

	if (pgn->type & OP_ARCHV) {
	    /*
	     * If the parent is an archive specification and is being made
	     * and its member's name matches the name of the node we were
	     * given, record the modification time of the parent in the
	     * child. We keep searching its parents in case some other
	     * parent requires this child to exist...
	     */
	    if (pgn->make &&
		pgn->path &&
		strcmp(pgn->path, gn->name) == 0)
	    {
		 gn->mtime = Arch_MTime(pgn);
	    }
	} else if (pgn->make) {
	    /*
	     * Something which isn't a library depends on the existence of
	     * this target, so it needs to exist.
	     */
	    gn->mtime = 0;
	    break;
	}
    }

    Lst_Close (gn->parents);

    return (gn->mtime);
}

/*-
 *-----------------------------------------------------------------------
 * Arch_FindLib --
 *	Search for a library along the given search path using the
 *	suffix given.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The node's 'path' field is set to the found path (including the
 *	actual file name, not -l...). If the system can handle the -L
 *	flag when linking (or we cannot find the library), we assume that
 *	the user has placed the .LIBRARIES variable in the final linking
 *	command (or the linker will know where to find it) and set the
 *	TARGET variable for this node to be the node's name. Otherwise,
 *	we set the TARGET variable to be the full path of the library,
 *	as returned by Dir_FindFile.
 *
 *-----------------------------------------------------------------------
 */
void
Arch_FindLib (gn, path, suffix)
    GNode	    *gn;	      /* Node of library to find */
    Lst	    	    path;	      /* Search path */
    char	    *suffix;	      /* Suffix to append to lib name */
{
    char	    *libName;   /* file name for archive */
    char	    *libRoot;

    libRoot = gn->name + sizeof(LIBFLAG) - 1;
    libName = (char *)emalloc(strlen (libRoot) +
			      sizeof(LIBPREFIX) - 1 +
			      strlen(suffix) + 1);
    sprintf(libName, "%s%s%s", LIBPREFIX, libRoot, suffix);

    gn->path = Dir_FindFile (libName, path);

    free (libName);

#ifdef LIBRARIES
    Var_Set (TARGET, gn->name, gn);
#else
    Var_Set (TARGET, gn->path == (char *) NULL ? gn->name : gn->path, gn);
#endif /* LIBRARIES */
}

/*-
 *-----------------------------------------------------------------------
 * Arch_LibOODate --
 *	Decide if a node with the OP_LIB attribute is out-of-date. Called
 *	from Make_OODate to make its life easier.
 *
 *	There are several ways for a library to be out-of-date that are
 *	not available to ordinary files. In addition, there are ways
 *	that are open to regular files that are not available to
 *	libraries. A library that is specified using -l and is only
 *	used as a source is never considered out-of-date by itself
 *	(presumably it is only mentioned to be included in a linker
 *	command line). This does not preclude the library's modification
 *	time from making its parent be out-of-date.
 *	A library will be considered out-of-date for any of these reasons,
 *	given that it is a target on a dependency line somewhere:
 *	    Its modification time is less than that of one of its
 *	    	  sources (gn->mtime < gn->cmtime).
 *	    It doesn't exist yet (gn->mtime == 0).
 *	    Its modification time is greater than the time at which the
 *	    	  make began (i.e. it's been modified in the course
 *	    	  of the make, probably by archiving).
 *	    Its modification time doesn't agree with the modification
 *	    	  time of its TOC member (i.e. its table of contents
 *	    	  is out-of-date).
 *
 *
 * Results:
 *	TRUE if the library is out-of-date. FALSE otherwise.
 *
 * Side Effects:
 *	The library will be hashed if it hasn't been already.
 *
 *-----------------------------------------------------------------------
 */
Boolean
Arch_LibOODate (gn)
    GNode   	  *gn;  	/* The library's graph node */
{
    Boolean 	  oodate;
    
    if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children) &&
	strncmp(gn->name, LIBFLAG, sizeof(LIBFLAG) - 1) == 0)
    {
	if (DEBUG(ARCH) || DEBUG(MAKE)) {
	    Debug ("-l type ...");
	}
	oodate = FALSE;
    } else if ((gn->mtime == 0) || 
	       (gn->mtime > now) ||
	       (gn->mtime < gn->cmtime))
    {
	oodate = TRUE;
    } else {
	struct ar_hdr  	*arhPtr;    /* Header for __.SYMDEF */
	int 	  	modTimeTOC; /* The table-of-contents's mod time */

	arhPtr = ArchStatMember (gn->path, ARCH_TOC, FALSE);

	if (arhPtr != (struct ar_hdr *)NULL) {
	    modTimeTOC = atoi(arhPtr->ar_date);

	    if (DEBUG(ARCH) || DEBUG(MAKE)) {
		Debug ("t.o.c modified %s...", Targ_FmtTime(modTimeTOC));
	    }

	    if (ARCH_ISCOMTOC(arhPtr->ar_name)) {
		/*
		 * Common archive TOC is never out of date:
		 * ar always updates it (or so we hope).
		 */
		oodate = FALSE;
	    } else if (AR_ISSYMDEF(arhPtr->ar_name)) {
		/*
		 * MIPS archive: check the OUTOFDATE flag in the name
		 */
		oodate = AR_ISOUTOFDATE(arhPtr->ar_name);
	    } else {
		/*
		 * Make sure the archive's file modification time does not
		 * exceed the TOC's date.
		 */
		oodate = (gn->mtime > modTimeTOC);
	    }
	} else {
	    LstNode 	  ln;

	    /*
	     * A library w/o a table of contents is considered up-to-date
	     * (barring file modification times) only if it depends on
	     * one of its members, since this is most likely a case for
	     * ranlib.
	     */
	    if (DEBUG(ARCH) || DEBUG(MAKE)) {
		Debug ("No t.o.c....");
	    }
	    oodate = FALSE;

	    if (Lst_Open (gn->children) == SUCCESS) {
		while ((ln = Lst_Next (gn->children)) != NILLNODE) {
		    if (((GNode *) Lst_Datum (ln))->type & OP_ARCHV) {
			oodate = TRUE;
			break;
		    }
		}
		Lst_Close (gn->children);
	    }

	    if (oodate && (DEBUG(ARCH) || DEBUG(MAKE))) {
		Debug ("but with member deps....");
	    }
	}
    }
    return (oodate);
}

/*-
 *-----------------------------------------------------------------------
 * Arch_Init --
 *	Initialize things for this module.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The 'archives' list is initialized.
 *
 *-----------------------------------------------------------------------
 */
void
Arch_Init ()
{
    archives = Lst_Init (FALSE);
}
