/*
* nm.c -- drive nm
*/
#include <u.h>
#include <libc.h>
#include <ar.h>
#include <bio.h>
#include <mach.h>

enum{
       CHUNK   =       256     /* must be power of 2 */
};

char    *errs;                  /* exit status */
char    *filename;              /* current file */
char    symname[]="__.SYMDEF";  /* table of contents file name */
int     multifile;              /* processing multiple files */
int     aflag;
int     gflag;
int     hflag;
int     nflag;
int     sflag;
int     uflag;
int     Tflag;

Sym     **fnames;               /* file path translation table */
Sym     **symptr;
int     nsym;
Biobuf  bout;

int     cmp(void*, void*);
void    error(char*, ...);
void    execsyms(int);
void    psym(Sym*, void*);
void    printsyms(Sym**, long);
void    doar(Biobuf*);
void    dofile(Biobuf*);
void    zenter(Sym*);

void
usage(void)
{
       fprint(2, "usage: nm [-aghnsTu] file ...\n");
       exits("usage");
}

void
main(int argc, char *argv[])
{
       int i;
       Biobuf  *bin;

       Binit(&bout, 1, OWRITE);
       argv0 = argv[0];
       ARGBEGIN {
       default:        usage();
       case 'a':       aflag = 1; break;
       case 'g':       gflag = 1; break;
       case 'h':       hflag = 1; break;
       case 'n':       nflag = 1; break;
       case 's':       sflag = 1; break;
       case 'u':       uflag = 1; break;
       case 'T':       Tflag = 1; break;
       } ARGEND
       if (argc == 0)
               usage();
       if (argc > 1)
               multifile++;
       for(i=0; i<argc; i++){
               filename = argv[i];
               bin = Bopen(filename, OREAD);
               if(bin == 0){
                       error("cannot open %s", filename);
                       continue;
               }
               if (isar(bin))
                       doar(bin);
               else{
                       Bseek(bin, 0, 0);
                       dofile(bin);
               }
               Bterm(bin);
       }
       exits(errs);
}

/*
* read an archive file,
* processing the symbols for each intermediate file in it.
*/
void
doar(Biobuf *bp)
{
       int offset, size, obj;
       char membername[SARNAME];

       multifile = 1;
       for (offset = Boffset(bp);;offset += size) {
               size = nextar(bp, offset, membername);
               if (size < 0) {
                       error("phase error on ar header %ld", offset);
                       return;
               }
               if (size == 0)
                       return;
               if (strcmp(membername, symname) == 0)
                       continue;
               obj = objtype(bp, 0);
               if (obj < 0) {
                       error("inconsistent file %s in %s",
                                       membername, filename);
                       return;
               }
               if (!readar(bp, obj, offset+size, 1)) {
                       error("invalid symbol reference in file %s",
                                       membername);
                       return;
               }
               filename = membername;
               nsym=0;
               objtraverse(psym, 0);
               printsyms(symptr, nsym);
       }
}

/*
* process symbols in a file
*/
void
dofile(Biobuf *bp)
{
       int obj;

       obj = objtype(bp, 0);
       if (obj < 0)
               execsyms(Bfildes(bp));
       else
       if (readobj(bp, obj)) {
               nsym = 0;
               objtraverse(psym, 0);
               printsyms(symptr, nsym);
       }
}

/*
* comparison routine for sorting the symbol table
*      this screws up on 'z' records when aflag == 1
*/
int
cmp(void *vs, void *vt)
{
       Sym **s, **t;

       s = vs;
       t = vt;
       if(nflag)
               if((*s)->value < (*t)->value)
                       return -1;
               else
                       return (*s)->value > (*t)->value;
       return strcmp((*s)->name, (*t)->name);
}
/*
* enter a symbol in the table of filename elements
*/
void
zenter(Sym *s)
{
       static int maxf = 0;

       if (s->value > maxf) {
               maxf = (s->value+CHUNK-1) &~ (CHUNK-1);
               fnames = realloc(fnames, (maxf+1)*sizeof(*fnames));
               if(fnames == 0) {
                       error("out of memory", argv0);
                       exits("memory");
               }
       }
       fnames[s->value] = s;
}

/*
* get the symbol table from an executable file, if it has one
*/
void
execsyms(int fd)
{
       Fhdr f;
       Sym *s;
       long n;

       seek(fd, 0, 0);
       if (crackhdr(fd, &f) == 0) {
               error("Can't read header for %s", filename);
               return;
       }
       if (syminit(fd, &f) < 0)
               return;
       s = symbase(&n);
       nsym = 0;
       while(n--)
               psym(s++, 0);

       printsyms(symptr, nsym);
}

void
psym(Sym *s, void* p)
{
       USED(p);
       switch(s->type) {
       case 'T':
       case 'L':
       case 'D':
       case 'B':
               if (uflag)
                       return;
               if (!aflag && ((s->name[0] == '.' || s->name[0] == '$')))
                       return;
               break;
       case 'b':
       case 'd':
       case 'l':
       case 't':
               if (uflag || gflag)
                       return;
               if (!aflag && ((s->name[0] == '.' || s->name[0] == '$')))
                       return;
               break;
       case 'U':
               if (gflag)
                       return;
               break;
       case 'Z':
               if (!aflag)
                       return;
               break;
       case 'm':
       case 'f':       /* we only see a 'z' when the following is true*/
               if(!aflag || uflag || gflag)
                       return;
               if (strcmp(s->name, ".frame"))
                       zenter(s);
               break;
       case 'a':
       case 'p':
       case 'z':
       default:
               if(!aflag || uflag || gflag)
                       return;
               break;
       }
       symptr = realloc(symptr, (nsym+1)*sizeof(Sym*));
       if (symptr == 0) {
               error("out of memory");
               exits("memory");
       }
       symptr[nsym++] = s;
}

void
printsyms(Sym **symptr, long nsym)
{
       int i, wid;
       Sym *s;
       char *cp;
       char path[512];

       if(!sflag)
               qsort(symptr, nsym, sizeof(*symptr), cmp);

       wid = 0;
       for (i=0; i<nsym; i++) {
               s = symptr[i];
               if (s->value && wid == 0)
                       wid = 8;
               else if (s->value >= 0x100000000LL && wid == 8)
                       wid = 16;
       }
       for (i=0; i<nsym; i++) {
               s = symptr[i];
               if (multifile && !hflag)
                       Bprint(&bout, "%s:", filename);
               if (s->type == 'z') {
                       fileelem(fnames, (uchar *) s->name, path, 512);
                       cp = path;
               } else
                       cp = s->name;
               if (Tflag)
                       Bprint(&bout, "%8ux ", s->sig);
               if (s->value || s->type == 'a' || s->type == 'p')
                       Bprint(&bout, "%*llux ", wid, s->value);
               else
                       Bprint(&bout, "%*s ", wid, "");
               Bprint(&bout, "%c %s\n", s->type, cp);
       }
}

void
error(char *fmt, ...)
{
       Fmt f;
       char buf[128];
       va_list arg;

       fmtfdinit(&f, 2, buf, sizeof buf);
       fmtprint(&f, "%s: ", argv0);
       va_start(arg, fmt);
       fmtvprint(&f, fmt, arg);
       va_end(arg);
       fmtprint(&f, "\n");
       fmtfdflush(&f);
       errs = "errors";
}