view toys/pending/cpio.c @ 1223:a7d5b93111a5 draft

Next round of cpio cleanup.
author Rob Landley <rob@landley.net>
date Sat, 15 Mar 2014 15:41:09 -0500
parents bb9d19136eeb
children 903acf85bd1d
line wrap: on
line source

/* cpio.c - a basic cpio
 *
 * Written 2013 AD by Isaac Dunham; this code is placed under the 
 * same license as toybox or as CC0, at your option.
 *
 * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cpio.html
 *
 * http://pubs.opengroup.org/onlinepubs/7908799/xcu/cpio.html
 * (Yes, that's SUSv2, the newer standards removed it around the time RPM
 * and initramfs started heavily using this archive format. Go figure.)

USE_CPIO(NEWTOY(cpio, "H:di|o|t|uF:[!iot][!dot][!uot]", TOYFLAG_BIN))

config CPIO
  bool "cpio"
  default n
  help
    usage: cpio {-o|-t|-i[du]} [-H FMT] [-F ARCHIVE]

    copy files into and out of a "newc" format cpio archive

    Actions:
    -o	create archive (stdin is a list of files, stdout is an archive)
    -t	list files (stdin is an archive, stdout is a list of files)
    -i	extract from archive into file system (stdin is an archive)

    Extract options:
    -d	create leading directories when extracting an archive
    -u	always overwrite files (default)

    Other options:
    -H FMT	archive format (ignored, only newc supported)
    -F ARCHIVE	read from or write to ARCHIVE file
*/

#define FOR_cpio
#include "toys.h"

GLOBALS(
  char *archive;
  char *fmt;

  int outfd;
)

// 110 bytes
struct newc_header {
  char    c_magic[6];
  char    c_ino[8];
  char    c_mode[8];
  char    c_uid[8];
  char    c_gid[8];
  char    c_nlink[8];
  char    c_mtime[8];
  char    c_filesize[8];
  char    c_devmajor[8];
  char    c_devminor[8];
  char    c_rdevmajor[8];
  char    c_rdevminor[8];
  char    c_namesize[8];
  char    c_check[8];
};

void write_cpio_member(int fd, char *name, struct stat buf)
{
  unsigned nlen = strlen(name)+1, error = 0, zero = 0;
  ssize_t llen;

  if (!S_ISREG(buf.st_mode) && !S_ISLNK(buf.st_mode)) buf.st_size = 0;
  else if (buf.st_size >> 32) {
    perror_msg("skipping >2G file '%s'", name);
    return;
  }

  llen = sprintf(toybuf,
    "070701%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X",
    (int)(buf.st_ino), buf.st_mode, buf.st_uid, buf.st_gid, (int)buf.st_nlink,
    (int)(buf.st_mtime), (int)(buf.st_size), major(buf.st_dev),
    minor(buf.st_dev), major(buf.st_rdev), minor(buf.st_rdev), nlen, 0);
  xwrite(TT.outfd, toybuf, llen);
  xwrite(TT.outfd, name, nlen);

  // NUL Pad header up to 4 multiple bytes.
  llen = (llen + nlen) & 3;
  if (llen) xwrite(TT.outfd, &zero, 4-llen); 

  // Write out body for symlink or regular file
  llen = buf.st_size;
  if (S_ISLNK(buf.st_mode)) {
    if (readlink(name, toybuf, sizeof(toybuf)-1) == llen)
      xwrite(TT.outfd, toybuf, llen);
    else perror_msg("readlink '%s'", name);
  } else while (llen) {
    nlen = llen > sizeof(toybuf) ? sizeof(toybuf) : llen;
    // If read fails, write anyway (we already wrote size in the header).
    if (nlen != readall(fd, toybuf, nlen))
      if (!error++) perror_msg("bad read from file '%s'", name);
    xwrite(TT.outfd, toybuf, nlen);
  }
  llen = buf.st_size & 3;
  if (nlen) write(TT.outfd, &zero, 4-llen);
}

// Iterate through a list of files read from stdin. No users need rw.
void loopfiles_stdin(void)
{
  char *name = 0;
  size_t size = 0;

  for (;;) {
    struct stat st;
    int len, fd;

    len = getline(&name, &size, stdin);
    if (!name) break;
    if (name[len-1] == '\n') name[--len] = 0;
    if (lstat(name, &st) || (fd = open(name, O_RDONLY))<0)
      perror_msg("%s", name);
    else {
      write_cpio_member(fd, name, st);
      close(fd);
    }
  }
  free(name);
}

//convert hex to uint; mostly to allow using bits of non-terminated strings
unsigned int htou(char * hex)
{
  unsigned int ret = 0, i = 0;

  for (;(i < 8 && hex[i]);) {
     ret *= 16;
     switch(hex[i]) { 
     case '0':
       break;
     case '1': 
     case '2': 
     case '3': 
     case '4': 
     case '5': 
     case '6': 
     case '7': 
     case '8': 
     case '9': 
       ret += hex[i] - '1' + 1;
       break;
     case 'A': 
     case 'B': 
     case 'C': 
     case 'D': 
     case 'E': 
     case 'F': 
       ret += hex[i] - 'A' + 10;
       break;
     }
     i++;
  }
  return ret;
}

// Read one cpio record. Returns 0 for last record, 1 for "continue".
int read_cpio_member(int fd, int how)
{
  uint32_t nsize, fsize;
  mode_t mode = 0;
  int pad, ofd = 0; 
  struct newc_header hdr;
  char *name, *lastdir;
  dev_t dev = 0;

  xreadall(fd, &hdr, sizeof(struct newc_header));
  nsize = htou(hdr.c_namesize);
  xreadall(fd, name = xmalloc(nsize), nsize);
  if (!strcmp("TRAILER!!!", name)) return 0;
  fsize = htou(hdr.c_filesize);
  mode += htou(hdr.c_mode);
  pad = 4 - ((nsize + 2) % 4); // 2 == sizeof(struct newc_header) % 4
  if (pad < 4) xreadall(fd, toybuf, pad);
  pad = 4 - (fsize % 4);

  if ((toys.optflags&FLAG_d) && (lastdir = strrchr(name, '/')))
    if (mkpathat(AT_FDCWD, name, 0, 2)) perror_msg("mkpath '%s'", name);

  if (how & 1) {
    if (S_ISDIR(mode)) ofd = mkdir(name, mode);
    else if (S_ISLNK(mode)) {
      memset(toybuf, 0, sizeof(toybuf));
      if (fsize < sizeof(toybuf)) {
        pad = readall(fd, toybuf, fsize); 
	if (pad < fsize) error_exit("short archive");
	pad = 4 - (fsize % 4);
	fsize = 0;
        if (symlink(toybuf, name)) {
	  perror_msg("could not write link %s", name);
	  toys.exitval |= 1;
	}
      } else {
        perror_msg("link too long: %s", name);
	toys.exitval |= 1;
      }
    } else if (S_ISBLK(mode)||S_ISCHR(mode)||S_ISFIFO(mode)||S_ISSOCK(mode)) {
      dev = makedev(htou(hdr.c_rdevmajor),htou(hdr.c_rdevminor));
      ofd = mknod(name, mode, dev);
    } else ofd = creat(name, mode);
    if (ofd == -1) {
      error_msg("could not create %s", name);
      toys.exitval |= 1;
    }
  }
  errno = 0;
  if (how & 2) puts(name);
  while (fsize) {
    int i;
    memset(toybuf, 0, sizeof(toybuf));
    i = readall(fd, toybuf, (fsize>sizeof(toybuf)) ? sizeof(toybuf) : fsize);
    if (i < 1) error_exit("archive too short");
    if (ofd > 0) writeall(ofd, toybuf, i);
    fsize -= i;
  }
  if (pad < 4) xreadall(fd, toybuf, pad);
  return 1;
}

void read_cpio_archive(int fd, int how)
{
  for(;;) if (!read_cpio_member(fd, how)) return;
}

void cpio_main(void)
{
  TT.outfd = 1;

  if (TT.archive) {
    if (toys.optflags & (FLAG_i|FLAG_t)) {
      xclose(0);
      xopen(TT.archive, O_RDONLY);
    } else if (toys.optflags & FLAG_o) {
      xclose(1);
      xcreate(TT.archive, O_CREAT|O_WRONLY|O_TRUNC, 0644);
    }
  }

  if (toys.optflags & FLAG_t) read_cpio_archive(0, 2);
  else if (toys.optflags & FLAG_i) read_cpio_archive(0, 1);
  else if (toys.optflags & FLAG_o) {
      loopfiles_stdin();
      write(1, "07070100000000000000000000000000000000000000010000000000000000"
      "000000000000000000000000000000000000000B00000000TRAILER!!!\0\0\0", 124);
  } else error_exit("must use one of -iot");
}