// -*- c-basic-offset: 2; related-file-name: "../include/click/archive.hh" -*-
/*
 * archive.{cc,hh} -- deal with `ar' files
 * Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>

#include <click/glue.hh>
#include <click/archive.hh>
#include <click/error.hh>
#include <click/confparse.hh>
#include <click/straccum.hh>

/* `ar' file format:

   "!<arch>\n"
8    first header block
68   first file data
68+n second header block
     etc.
   
   header block format:
   
 0    char ar_name[16];		// Member file name, sometimes / terminated.
16    char ar_date[12];		// File date, decimal seconds since Epoch. 
28    char ar_uid[6],
34         ar_gid[6];		// User and group IDs, in ASCII decimal. 
40    char ar_mode[8];		// File mode, in ASCII octal. 
48    char ar_size[10];		// File size, in ASCII decimal. 
58    char ar_fmag[2];		// Always contains ARFMAG == "`\n".
*/

CLICK_DECLS

static int
read_uint(const char *data, int max_len,
	  const char *type, ErrorHandler *errh, int base = 10)
{
  char buf[17];
  char *end;
  
  assert(max_len <= 16);
  memcpy(buf, data, max_len);
  buf[max_len] = 0;
  
  int result = strtol(buf, &end, base);
  if (end == buf)
    result = -1;
  if (*end && !isspace(*end))
    errh->warning("bad %s in archive", type);
  return result;
}

int
separate_ar_string(const String &s, Vector<ArchiveElement> &v,
		   ErrorHandler *errh)
{
  if (!errh)
    errh = ErrorHandler::silent_handler();
  
  if (s.length() <= 8 || memcmp(s.data(), "!<arch>\n", 8) != 0)
    return errh->error("not an archive");
  
  const char *data = s.data();
  int len = s.length();
  int p = 8;

  ArchiveElement longname_ae;

  // loop over sections
  while (p+60 <= len) {
    
    // check magic number
    if (data[p+58] != '`' || data[p+59] != '\n')
      return errh->error("bad archive: missing header magic number");
    
    int size;
    
    if (data[p+0] == '/' && data[p+1] == '/' && isspace(data[p+2])) {
      // GNUlike long name section
      if (longname_ae.data)
	errh->error("two long name sections in archive");
      
      size = read_uint(data+p+48, 10, "size", errh);
      if (size < 0 || p+60+size > len)
	return errh->error("truncated archive");

      longname_ae.data = s.substring(p+60, size);
      
    } else {

      ArchiveElement ae;
      
      // read name
      int bsd_longname = 0;
      int j;
      if (data[p] == '/' && data[p+1] >= '0' && data[p+1] <= '9') {
	int offset = read_uint(data+p+1, 15, "long name", errh);
	if (!longname_ae.data || offset < 0 || offset >= longname_ae.data.length())
	  errh->error("bad long name in archive");
	else {
	  const char *ndata = longname_ae.data.data();
	  int nlen = longname_ae.data.length();
	  for (j = offset; j < nlen && ndata[j] != '/' && !isspace(ndata[j]);
	       j++)
	    /* nada */;
	  ae.name = longname_ae.data.substring(offset, j - offset);
	}
      } else if (data[p+0] == '#' && data[p+1] == '1' && data[p+2] == '/'
		 && data[p+3] >= '0' && data[p+3] <= '9') {
	bsd_longname = read_uint(data+p+3, 13, "long name", errh);
      } else {
	for (j = 0; j < 16 && data[p+j] != '/' && !isspace(data[p+j]); j++)
	  /* nada */;
	ae.name = s.substring(p, j);
      }

      // read date, uid, gid, mode, size
      ae.date = read_uint(data+p+16, 12, "date", errh);
      ae.uid = read_uint(data+p+28, 6, "uid", errh);
      ae.gid = read_uint(data+p+34, 6, "gid", errh);
      ae.mode = read_uint(data+p+40, 8, "mode", errh, 8);
      size = read_uint(data+p+48, 10, "size", errh);
      if (size < 0 || p+60+size > len)
	return errh->error("truncated archive, %d bytes short", p+60+size - len);

      // set data
      if (bsd_longname > 0) {
	if (size < bsd_longname)
	  return errh->error("bad long name in archive");
	ae.name = s.substring(p+60, bsd_longname);
	ae.data = s.substring(p+60+bsd_longname, size - bsd_longname);
      } else
	ae.data = s.substring(p+60, size);

      // append archive element
      v.push_back(ae);
    }
    
    p += 60 + size;
    if (size % 2 == 1)		// objects in archive are even # of bytes
      p++;
  }
  
  if (p != len)
    return errh->error("truncated archive");
  else
    return 0;
}

String
create_ar_string(const Vector<ArchiveElement> &v, ErrorHandler *errh)
{
  if (!errh)
    errh = ErrorHandler::silent_handler();
  
  StringAccum sa;
  int want_size = 8;
  sa << "!<arch>\n";
  for (int i = 0; i < v.size(); i++) {
    const ArchiveElement &ae = v[i];
    if (ae.dead())
      continue;

    // check name
    const char *ndata = ae.name.data();
    int nlen = ae.name.length();
    bool must_longname = false;
    for (int i = 0; i < nlen; i++)
      if (ndata[i] == '/') {
	errh->error("archive element name `%s' contains slash", ae.name.c_str());
	nlen = i;
	break;
      } else if (isspace(ndata[i]))
	must_longname = true;

    // write name, or nameish thing
    if ((nlen >= 3 && ndata[0] == '#' && ndata[1] == '1' && ndata[2] == '/')
	|| must_longname || nlen > 14) {
      if (char *x = sa.extend(16, 1))
	sprintf(x, "#1/%-13u", (unsigned)nlen);
      must_longname = true;
    } else
      if (char *x = sa.extend(16, 1))
	sprintf(x, "%-16.*s", nlen, ndata);

    // write other data
    int wrote_size = ae.data.length() + (must_longname ? nlen : 0);
    if (char *x = sa.extend(12 + 6 + 6 + 8 + 10 + 2, 1))
      sprintf(x, "%-12u%-6u%-6u%-8o%-10u`\n", (unsigned)ae.date,
	      (unsigned)ae.uid, (unsigned)ae.gid, (unsigned)ae.mode,
	      (unsigned)wrote_size);

    if (must_longname)
      sa.append(ndata, nlen);
    sa << ae.data;

    want_size += 60 + wrote_size;
    if (wrote_size % 2 == 1) {
      // extend by an extra newline to keep object lengths even
      sa << '\n';
      want_size++;
    }
  }

  // check for memory errors
  if (want_size != sa.length()) {
    errh->error("out of memory");
    return String();
  } else
    return sa.take_string();
}


// generate Vector template instance
#include <click/vector.cc>
template class Vector<ArchiveElement>;
CLICK_ENDDECLS


syntax highlighted by Code2HTML, v. 0.9.1