changeset 1087:b73a61542297 draft

I've finally gotten 'cpio' into a shape where it could be useable. This version can archive and extract directories, sockets, FIFOs, devices, symlinks, and regular files. Supported options are -iot, -H FMT (which is a dummy right now). It only writes newc, and could read newc or newcrc. This does NOT implement -d, which essentially is equivalent to mkdir -p $(dirname $FILE) for every file that needs it. Hard links are not supported, though it would be easy to add them given a hash table or something like that. I also have not implemented the "<n> blocks" output on stderr. If desired, I can add it pretty simply. There is one assumption this makes: that the mode of a file, as mode_t, is bitwise equivalent to the mode as defined for the cpio format. This is true of Linux, but is not mandated by POSIX. If it is compiled for a system where that is false, the archives will not be portable.
author Isaac Dunham <ibid.ag@gmail.com>
date Mon, 14 Oct 2013 11:15:22 -0500
parents 162bb45b0387
children 4948a942de49
files toys/pending/cpio.c
diffstat 1 files changed, 240 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/toys/pending/cpio.c	Mon Oct 14 11:15:22 2013 -0500
@@ -0,0 +1,240 @@
+/* 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.
+USE_CPIO(NEWTOY(cpio, "H:iot", TOYFLAG_BIN))
+
+config CPIO
+  bool "cpio"
+  default n
+  help
+    usage: cpio { -i | -o | -t } [-H fmt] 
+
+    copy files into and out of an archive
+    -i  extract from archive into file system (stdin is an archive)
+    -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)
+    -H fmt   Write archive in specified format:
+    newc  SVR4 new character format (default)
+*/
+#define FOR_cpio
+#include "toys.h"
+
+GLOBALS(
+char * fmt;
+)
+
+/* Iterate through a list of files, read from stdin.
+ * No users need rw.
+ */
+void loopfiles_stdin(void (*function)(int fd, char *name))
+{
+  int fd;
+  char *name = toybuf;
+
+  while (name != NULL){
+    memset(toybuf, 0, sizeof(toybuf));
+    name = fgets(toybuf, sizeof(toybuf) - 1, stdin);
+    
+    if (name != NULL) {
+      if (toybuf[strlen(name) - 1] == '\n' ) { 
+        toybuf[strlen(name) - 1 ] = '\0';
+	fd = open(name, O_RDONLY);
+	if (fd > 0) {
+          function(fd, name);
+	  close(fd);
+	}
+	errno = 0;
+      }
+    }
+  }
+}
+
+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)
+{
+  char ahdr[sizeof(struct newc_header) + 1];
+  struct newc_header *hdr = (struct newc_header *)ahdr;
+  size_t out = 0;
+  unsigned int n = 0x00000000, nlen = strlen(name) + 1;
+
+  memset(hdr, '0', sizeof(struct newc_header));
+  if (S_ISDIR(buf.st_mode) || S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode)
+     || S_ISFIFO(buf.st_mode) || S_ISSOCK(buf.st_mode)) 
+    buf.st_size = 0;
+  snprintf((char *)(hdr), sizeof(struct newc_header)+1, 
+          "070701%08X%08X" "%08X%08X"
+	  "%08X%08X%08X"
+	   "%08X%08X" "%08X%08X%08X00000000",
+	   (unsigned int)(buf.st_ino), buf.st_mode, buf.st_uid, buf.st_gid, 
+	   buf.st_nlink, (uint32_t)(buf.st_mtime), (uint32_t)(buf.st_size), 
+	   major(buf.st_dev), minor(buf.st_dev),
+           major(buf.st_rdev), minor(buf.st_rdev), nlen);
+  write(1, hdr, sizeof(struct newc_header));
+  write(1, name, nlen);
+  if ((nlen + 2) % 4) write(1, &n, 4 - ((nlen + 2) % 4)); 
+  if (S_ISLNK(buf.st_mode)) {
+    ssize_t llen = readlink(name, toybuf, sizeof(toybuf) - 1);
+    if (llen > 0) {
+      toybuf[llen] = '\0';
+      write(1, toybuf, buf.st_size);
+    }
+  } else if (buf.st_size) {
+    for (; (lseek(fd, 0, SEEK_CUR) < (uint32_t)(buf.st_size));) {
+      out = read(fd, toybuf, sizeof(toybuf));
+      if (out > 0) write(1, toybuf, out);
+    if (errno || out < sizeof(toybuf)) break;
+    }
+  }
+  if (buf.st_size % 4) write(1, &n, 4 - (buf.st_size % 4));
+}
+
+void write_cpio_call(int fd, char *name)
+{
+  struct stat buf;
+  if (lstat(name, &buf) == -1) return;
+  write_cpio_member(fd, name, buf);
+}
+
+//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;
+  dev_t dev = 0;
+
+  xreadall(fd, &hdr, sizeof(struct newc_header));
+  nsize = htou(hdr.c_namesize);
+  name = xmalloc(nsize);
+  xreadall(fd, name, 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 (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)
+{
+  switch (toys.optflags & (FLAG_i | FLAG_o | FLAG_t)) {
+    case FLAG_o:
+      loopfiles_stdin(write_cpio_call);
+      write(1, "07070100000000000000000000000000000000000000010000000000000000"
+      "000000000000000000000000000000000000000B00000000TRAILER!!!\0\0\0", 124);
+      break;
+    case FLAG_i:
+      read_cpio_archive(0, 1);
+    case FLAG_t:
+    case (FLAG_t | FLAG_i):
+      read_cpio_archive(0, 2);
+      break;
+  default: 
+  error_exit("must use one of -iot");
+  }
+}