/* $VER: ar archive.c V0.1 (31.01.98)
 *
 * This file is part of ar, a portable archive maintanance
 * utility for normal and BSD-style archives.
 * Copyright (c) 1999  Frank Wille
 *
 * ar is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-99 by Volker Barthelmann.
 * ar may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v0.1  (31.01.99) phx
 *       First working version, which only supports 'q' (quick append)
 *       and 't' (table of contents), reads and writes normals and
 *       BSD-style archives. Symbol table will not be created!
 * v0.0  (29.01.99) phx
 *       File created.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ar.h"
#include "archive.h"
#include "errors.h"


static const char *armag = ARMAG;
static const char *arfmag = ARFMAG;
static const char *symtab_name = "/ ";
static const char *longnames_name = "// ";


extern size_t filesize(FILE *,char *);
extern void getfstat(struct ArObject *,char *name);
extern char *filepart(char *);

static void make_long_names(struct Archive *);
static struct ArObject *allocArObject(unsigned long);
static bool parse_header(struct Archive *,struct ArObject *,
                         struct ArHeader *);



struct Archive *ar_create(char *archive_name)
/* create a new, empty achive */
{
  struct Archive *ar;

  if (ar = malloc(sizeof(struct Archive))) {
    memset(ar,0,sizeof(struct Archive));
    initlist(&ar->l);
    ar->name = archive_name;
  }
  return (ar);
}


struct Archive *ar_read(char *archive_name)
/* open archive and read all objects */
{
  struct Archive *ar = NULL;
  struct ArObject *obj;
  uint8 *data;
  FILE *fh;
  char buf[sizeof(struct ArHeader)];  /* assuming sizeof ArHeader > SARMAG */
  unsigned long size;

  if (fh = fopen(archive_name,"rb")) {
    /* first check for "!<arch>\n" id */  
    if (fread(buf,1,SARMAG,fh) == SARMAG) {
      if (!strncmp(buf,armag,SARMAG)) {
        if (ar = ar_create(archive_name)) {

          /* read all archive members */
          while (fread(buf,1,sizeof(struct ArHeader),fh) ==
                 sizeof(struct ArHeader)) {
            if (sscanf(((struct ArHeader *)buf)->ar_size,"%lu",&size) != 1) {
              error(EMALFORMARHDR,ar->name,buf);
              break;
            }

            if (obj = allocArObject(size)) {
              /* add next member to ar-list */
              size = (size + 1) & ~1;   /* 16bit alignment */
              if (fread(obj->data,1,size,fh) == size) {
                if (parse_header(ar,obj,(struct ArHeader *)buf)) {
                  if (obj!=ar->symbol_tab && obj!=ar->long_names) {
                    addtail(&ar->l,&obj->n);
                    ar->entries++;
                  }
                }
                else
                  break;
              }
              else {
                error(EARMEMBREADERR,ar->name,buf);
                break;
              }
            }
            else {
              error(EOUTOFMEM);
              break;
            }
          }
        }
        else
          error(EOUTOFMEM);
      }
      else
        error(EFORMATERR,archive_name);
    }
    else
      error(EREADERR,archive_name);
    fclose(fh);
  }
  return (ar);
}


struct Archive *ar_readorcreate(char *name,uint32 mod)
{
  struct Archive *ar;

  if (ar = ar_read(name))
    return (ar);
  if (!(mod & AR_CREATIGN))
    error(ECREATARC,name);
  return (ar_create(name));
}


struct ArObject *ar_newmemb(char *name)
/* read a new archive member from file */
{
  struct ArObject *obj = NULL;
  FILE *fh;
  unsigned long size,xxx;

  if (fh = fopen(name,"rb")) {
    size = (unsigned long)filesize(fh,name);
    if (obj = allocArObject(size)) {
      if (fread(obj->data,1,size,fh) == size) {
        obj->name = filepart(name);
        getfstat(obj,name);  /* get time, mode, uid, gid */
      }
      else {
        obj = NULL;
        error(EREADERR,name);
      }
    }
    fclose(fh);
  }
  else
    error(ENOFILEORDIR,name);
  return (obj);
}


void ar_write(struct Archive *ar,uint32 mod)
/* generate symbol table, write archive in normal or BSD format */
/* @@@ FIXME currently the symbol table is not created! */
{
  FILE *fh;

  if (!(mod & AR_TRUNC) && !(mod & AR_BSD))
    make_long_names(ar);

  if (fh = fopen(ar->name,"wb")) {
    struct ArObject *obj = (struct ArObject *)ar->l.first;
    struct ArObject *next;
    char namebuf[20];
    unsigned long offset = 0;
    bool bsdname;
    int len;
    int maxlen = (mod & AR_BSD) ? 16 : 15;

    fprintf(fh,armag);

    /* write archive members */
    while (next = (struct ArObject *)obj->n.next) {
      /* write file name or reference to namebuf */
      len = strlen(obj->name);
      bsdname = FALSE;
      if ((mod & AR_BSD) && !(mod & AR_TRUNC) && len>16) {
        bsdname = TRUE;
        sprintf(namebuf,"#1/%d",len);
      }
      else if (!(mod & AR_BSD) && !(mod & AR_TRUNC) && len>15) {
        sprintf(namebuf,"/%lu",offset);
        offset += len+2;
      }
      else if (mod & AR_BSD) {
        memcpy(namebuf,obj->name,len<16?len+1:16);
        namebuf[16] = '\0';
      }
      else
        sprintf(namebuf,"%.15s/",obj->name);

      if ((mod & AR_TRUNC) && len > maxlen) {
        /* warning: file name was truncated */
        char buf[20];

        strcpy(buf,namebuf);
        buf[maxlen] = '\0';
        error(ETRUNCWARN,obj->name,buf);
      }

      /* write header */
      fprintf(fh,"%-16.16s%-12ld%-6u%-6u%-8o%-10lu%2s",
              namebuf,obj->time,obj->uid,obj->gid,obj->mode,
              bsdname?obj->size+len:obj->size,arfmag);

      if (bsdname)  /* long name first */
        fwrite(obj->name,1,len,fh);

      /* write data */
      fwrite(obj->data,1,obj->size,fh);

      /* alignment */
      if (bsdname)
        obj->size += len;
      if (obj->size & 1)
        fprintf(fh,"\n");

      obj = next;
    }
  }
  else
    error(ECANTWRITE);
}


static void make_long_names(struct Archive *ar)
/* if archive member with a name > 15 chars are present, */
/* allocate a long-names object for them */
{
  struct ArObject *obj = (struct ArObject *)ar->l.first;
  struct ArObject *next,*lnobj;
  int l;
  uint32 size = 0;
  char *p;

  while (next = (struct ArObject *)obj->n.next) {
    if ((l = strlen(obj->name)) > 15)
      size += l+2;
    obj = next;
  }
  if (size) {
    /* create long-names object and copy file names */
    if (lnobj = allocArObject(size)) {
      lnobj->name = "/";
      p = (char *)lnobj->data;
      obj = (struct ArObject *)ar->l.first;
      while (next = (struct ArObject *)obj->n.next) {
        if ((l = strlen(obj->name)) > 15) {
          memcpy(p,obj->name,l);
          p += l;
          *p++ = '/';
          *p++ = '\n';
        }
        obj = next;
      }
      if (size & 1)
        *p = '\n';
      addhead(&ar->l,&lnobj->n);
      ar->entries++;
    }
  }
}


static struct ArObject *allocArObject(unsigned long size)
{
  struct ArObject *obj;
  uint8 *data;

  if ((obj = malloc(sizeof(struct ArObject))) &&
      (data = malloc((size+1)&~1))) {
    obj->data = data;
    obj->size = size;
  }
  else
    error(EOUTOFMEM);
  return (obj);
}


static bool parse_header(struct Archive *ar,struct ArObject *obj,
                         struct ArHeader *hdr)
/* check header and store its informations into ArObject */
{
  if (!strncmp(hdr->ar_fmag,arfmag,2)) {
    obj->time = (time_t)strtoul(hdr->ar_time,NULL,10);
    obj->uid = (uint16)strtoul(hdr->ar_uid,NULL,10);
    obj->gid = (uint16)strtoul(hdr->ar_gid,NULL,10);
    obj->mode = (uint16)strtoul(hdr->ar_mode,NULL,8);  /* octal */

    if (!strncmp(hdr->ar_name,symtab_name,2) ||   /* normal sym table */
        !strncmp(hdr->ar_name,"__.SYMDEF ",10)) { /* BSD symbol table */
      ar->symbol_tab = obj;
      obj->name = (char *)symtab_name;
      return (TRUE);
    }

    else if (!strncmp(hdr->ar_name,longnames_name,3)) { /* long names */
      ar->long_names = obj;
      obj->name = (char *)longnames_name;
      return (TRUE);
    }

    else if (ar->long_names && hdr->ar_name[0]=='/' &&
             hdr->ar_name[1]>='0' && hdr->ar_name[1]<='9') {
      /* get name from long-names object */
      unsigned long offset,len;
      char *p,*s,*d;

      if (sscanf(&hdr->ar_name[1],"%lu",&offset) == 1) {
        for (s = p = (char *)ar->long_names->data+offset,
             d = (char *)ar->long_names->data + ar->long_names->size;
             p<d; p++)
          if (*p=='/' || *p=='\n')
            break;
        len = p - s;
        if (d = malloc(len+1)) {
          memcpy(d,s,len);
          d[len] = '\0';
          obj->name = d;
          return (TRUE);
        }
        else
          error(EOUTOFMEM);
      }
      else
        error(EMALFORMARHDR,ar->name,obj->name);
      return (FALSE);
    }

    else if (!strncmp(hdr->ar_name,"#1/",3)) {
      /* read BSD extended name format #1 */
      unsigned long len;
      char *d;

      if (sscanf(&hdr->ar_name[3],"%lu",&len) == 1) {
        if (len <= obj->size) {
          if (d = malloc(len+1)) {
            memcpy(d,obj->data,len);
            d[len] = '\0';
            obj->name = d;
            /* set real beginning of object data */
            obj->data += len;
            obj->size -= len;
            if (len & 1) {
              /* data block must begin at an even address */
              memmove(obj->data+1,obj->data,obj->size);
              obj->data++;
            }
            return (TRUE);
          }
          else {
            error(EOUTOFMEM);
            return (FALSE);
          }
        }
      }
      error(EMALFORMARHDR,ar->name,obj->name);
      return (FALSE);
    }

    else {
      /* standard name, maximum of 15 chars */
      char *p = &hdr->ar_name[16];
      int len;

      for (len=16; len>0; len--)
        if (*(--p) != ' ')
          break;
      if (len && *p=='/')   /* normal name termination, else BSD */
        --len;
      if (p = malloc(len+1)) {
        memcpy(p,hdr->ar_name,len);
        p[len] = '\0';
        obj->name = p;
        return (TRUE);
      }
      else
        error(EOUTOFMEM);
      return (FALSE);
    }
  }

  error(EMALFORMARHDR,ar->name,obj->name);
  return (FALSE);
}
