Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 1 | /* cpio.c - a basic cpio |
| 2 | * |
| 3 | * Written 2013 AD by Isaac Dunham; this code is placed under the |
| 4 | * same license as toybox or as CC0, at your option. |
Isaac Dunham | 59272f2 | 2013-11-16 10:37:49 -0600 | [diff] [blame] | 5 | * |
| 6 | * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/cpio.html |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 7 | * and http://pubs.opengroup.org/onlinepubs/7908799/xcu/cpio.html |
Isaac Dunham | 59272f2 | 2013-11-16 10:37:49 -0600 | [diff] [blame] | 8 | * |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 9 | * Yes, that's SUSv2, the newer standards removed it around the time RPM |
| 10 | * and initramfs started heavily using this archive format. |
| 11 | * |
| 12 | * Modern cpio expanded header to 110 bytes (first field 6 bytes, rest are 8). |
| 13 | * In order: magic ino mode uid gid nlink mtime filesize devmajor devminor |
| 14 | * rdevmajor rdevminor namesize check |
Isaac Dunham | 59272f2 | 2013-11-16 10:37:49 -0600 | [diff] [blame] | 15 | |
Rob Landley | 392e127 | 2014-03-26 06:07:06 -0500 | [diff] [blame] | 16 | USE_CPIO(NEWTOY(cpio, "duH:i|t|F:v(verbose)o|[!io][!ot]", TOYFLAG_BIN)) |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 17 | |
| 18 | config CPIO |
| 19 | bool "cpio" |
Rob Landley | a2d5581 | 2014-03-25 07:35:56 -0500 | [diff] [blame] | 20 | default y |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 21 | help |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 22 | usage: cpio -{o|t|i} [-v] [--verbose] [-F FILE] [ignored: -du -H newc] |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 23 | |
Rob Landley | bab79fe | 2014-03-13 19:42:42 -0500 | [diff] [blame] | 24 | copy files into and out of a "newc" format cpio archive |
| 25 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 26 | -F FILE use archive FILE instead of stdin/stdout |
| 27 | -i extract from archive into file system (stdin=archive) |
| 28 | -o create archive (stdin=list of files, stdout=archive) |
| 29 | -t test files (list only, stdin=archive, stdout=list of files) |
| 30 | -v verbose (list files during create/extract) |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 31 | */ |
Rob Landley | 83d3a0b | 2013-12-01 14:34:05 -0600 | [diff] [blame] | 32 | |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 33 | #define FOR_cpio |
| 34 | #include "toys.h" |
| 35 | |
| 36 | GLOBALS( |
Rob Landley | 83d3a0b | 2013-12-01 14:34:05 -0600 | [diff] [blame] | 37 | char *archive; |
| 38 | char *fmt; |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 39 | ) |
| 40 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 41 | // Read strings, tail padded to 4 byte alignment. Argument "align" is amount |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 42 | // by which start of string isn't aligned (usually 0, but header is 110 bytes |
| 43 | // which is 2 bytes off because the first field wasn't expanded from 6 to 8). |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 44 | static char *strpad(int fd, unsigned len, unsigned align) |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 45 | { |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 46 | char *str; |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 47 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 48 | align = (align + len) & 3; |
| 49 | if (align) len += (4-align); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 50 | xreadall(fd, str = xmalloc(len+1), len); |
| 51 | str[len]=0; // redundant, in case archive is bad |
Rob Landley | 9ea9911 | 2014-03-15 15:41:09 -0500 | [diff] [blame] | 52 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 53 | return str; |
Rob Landley | bab79fe | 2014-03-13 19:42:42 -0500 | [diff] [blame] | 54 | } |
| 55 | |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 56 | //convert hex to uint; mostly to allow using bits of non-terminated strings |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 57 | unsigned x8u(char *hex) |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 58 | { |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 59 | unsigned val, inpos = 8, outpos; |
| 60 | char pattern[6]; |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 61 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 62 | while (*hex == '0') { |
| 63 | hex++; |
| 64 | if (!--inpos) return 0; |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 65 | } |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 66 | // Because scanf gratuitously treats %*X differently than printf does. |
| 67 | sprintf(pattern, "%%%dX%%n", inpos); |
| 68 | sscanf(hex, pattern, &val, &outpos); |
| 69 | if (inpos != outpos) error_exit("bad header"); |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 70 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 71 | return val; |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 72 | } |
| 73 | |
| 74 | void cpio_main(void) |
| 75 | { |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 76 | int afd; |
Rob Landley | 9ea9911 | 2014-03-15 15:41:09 -0500 | [diff] [blame] | 77 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 78 | // Subtle bit: FLAG_o is 1 so we can just use it to select stdin/stdout. |
| 79 | |
| 80 | afd = toys.optflags & FLAG_o; |
Isaac Dunham | 59272f2 | 2013-11-16 10:37:49 -0600 | [diff] [blame] | 81 | if (TT.archive) { |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 82 | int perm = (toys.optflags & FLAG_o) ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY; |
| 83 | |
| 84 | afd = xcreate(TT.archive, perm, 0644); |
Isaac Dunham | 59272f2 | 2013-11-16 10:37:49 -0600 | [diff] [blame] | 85 | } |
| 86 | |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 87 | // read cpio archive |
| 88 | |
| 89 | if (toys.optflags & (FLAG_i|FLAG_t)) for (;;) { |
| 90 | char *name, *tofree, *data; |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 91 | unsigned size, mode, uid, gid, timestamp; |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 92 | int test = toys.optflags & FLAG_t, err = 0; |
| 93 | |
| 94 | // Read header and name. |
| 95 | xreadall(afd, toybuf, 110); |
| 96 | tofree = name = strpad(afd, x8u(toybuf+94), 110); |
| 97 | if (!strcmp("TRAILER!!!", name)) break; |
| 98 | |
| 99 | // If you want to extract absolute paths, "cd /" and run cpio. |
| 100 | while (*name == '/') name++; |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 101 | // TODO: remove .. entries |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 102 | |
| 103 | size = x8u(toybuf+54); |
| 104 | mode = x8u(toybuf+14); |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 105 | uid = x8u(toybuf+30); |
| 106 | gid = x8u(toybuf+38); |
| 107 | timestamp = x8u(toybuf+46); // unsigned 32 bit, so year 2100 problem |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 108 | |
| 109 | if (toys.optflags & (FLAG_t|FLAG_v)) puts(name); |
| 110 | |
| 111 | if (!test && strrchr(name, '/') && mkpathat(AT_FDCWD, name, 0, 2)) { |
| 112 | perror_msg("mkpath '%s'", name); |
| 113 | test++; |
| 114 | } |
| 115 | |
| 116 | // Consume entire record even if it couldn't create file, so we're |
| 117 | // properly aligned with next file. |
| 118 | |
| 119 | if (S_ISDIR(mode)) { |
| 120 | if (!test) err = mkdir(name, mode); |
| 121 | } else if (S_ISLNK(mode)) { |
| 122 | data = strpad(afd, size, 0); |
| 123 | if (!test) err = symlink(data, name); |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 124 | // Can't get a filehandle to a symlink, so do special chown |
| 125 | if (!err && !getpid()) err = lchown(name, uid, gid); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 126 | } else if (S_ISREG(mode)) { |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 127 | int fd = test ? 0 : open(name, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW, mode); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 128 | |
| 129 | // If write fails, we still need to read/discard data to continue with |
| 130 | // archive. Since doing so overwrites errno, report error now |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 131 | if (fd < 0) { |
| 132 | perror_msg("create %s", name); |
| 133 | test++; |
| 134 | } |
| 135 | |
| 136 | data = toybuf; |
| 137 | while (size) { |
| 138 | if (size < sizeof(toybuf)) data = strpad(afd, size, 0); |
| 139 | else xreadall(afd, toybuf, sizeof(toybuf)); |
| 140 | if (!test) xwrite(fd, data, data == toybuf ? sizeof(toybuf) : size); |
| 141 | if (data != toybuf) { |
| 142 | free(data); |
| 143 | break; |
| 144 | } |
| 145 | size -= sizeof(toybuf); |
| 146 | } |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 147 | |
| 148 | if (!test) { |
| 149 | // set owner, restore dropped suid bit |
| 150 | if (!getpid()) { |
| 151 | err = fchown(fd, uid, gid); |
| 152 | if (!err) err = fchmod(fd, mode); |
| 153 | } |
| 154 | close(fd); |
| 155 | } |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 156 | } else if (!test) |
| 157 | err = mknod(name, mode, makedev(x8u(toybuf+62), x8u(toybuf+70))); |
| 158 | |
Rob Landley | c54fdc9 | 2014-04-29 06:03:17 -0500 | [diff] [blame] | 159 | // Set ownership and timestamp. |
| 160 | if (!test && !err) { |
| 161 | // Creading dir/dev doesn't give us a filehandle, we have to refer to it |
| 162 | // by name to chown/utime, but how do we know it's the same item? |
| 163 | // Check that we at least have the right type of entity open, and do |
| 164 | // NOT restore dropped suid bit in this case. |
| 165 | if (!S_ISREG(mode) && !S_ISLNK(mode) && !getpid()) { |
| 166 | int fd = open(name, O_WRONLY|O_NOFOLLOW); |
| 167 | struct stat st; |
| 168 | |
| 169 | if (fd != -1 && !fstat(fd, &st) && (st.st_mode&S_IFMT) == mode) |
| 170 | err = fchown(fd, uid, gid); |
| 171 | else err = 1; |
| 172 | |
| 173 | close(fd); |
| 174 | } |
| 175 | |
| 176 | // set timestamp |
| 177 | if (!err) { |
| 178 | struct timespec times[2]; |
| 179 | |
| 180 | memset(times, 0, sizeof(struct timespec)*2); |
| 181 | times[0].tv_sec = times[1].tv_sec = timestamp; |
| 182 | err = utimensat(AT_FDCWD, name, times, AT_SYMLINK_NOFOLLOW); |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | if (err) perror_msg("'%s'", name); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 187 | free(tofree); |
| 188 | |
| 189 | // Output cpio archive |
| 190 | |
| 191 | } else { |
| 192 | char *name = 0; |
| 193 | size_t size = 0; |
| 194 | |
| 195 | for (;;) { |
| 196 | struct stat st; |
Rob Landley | 392e127 | 2014-03-26 06:07:06 -0500 | [diff] [blame] | 197 | unsigned nlen, error = 0, zero = 0; |
Rob Landley | 6d796b6 | 2014-03-25 07:24:50 -0500 | [diff] [blame] | 198 | int len, fd = -1; |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 199 | ssize_t llen; |
| 200 | |
| 201 | len = getline(&name, &size, stdin); |
| 202 | if (len<1) break; |
| 203 | if (name[len-1] == '\n') name[--len] = 0; |
Rob Landley | 392e127 | 2014-03-26 06:07:06 -0500 | [diff] [blame] | 204 | nlen = len+1; |
Rob Landley | 6d796b6 | 2014-03-25 07:24:50 -0500 | [diff] [blame] | 205 | if (lstat(name, &st) |
| 206 | || (S_ISREG(st.st_mode) && (fd = open(name, O_RDONLY))<0)) |
| 207 | { |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 208 | perror_msg("%s", name); |
| 209 | continue; |
| 210 | } |
| 211 | |
| 212 | if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) st.st_size = 0; |
| 213 | if (st.st_size >> 32) perror_msg("skipping >2G file '%s'", name); |
| 214 | else { |
| 215 | llen = sprintf(toybuf, |
| 216 | "070701%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X", |
| 217 | (int)st.st_ino, st.st_mode, st.st_uid, st.st_gid, (int)st.st_nlink, |
| 218 | (int)st.st_mtime, (int)st.st_size, major(st.st_dev), |
| 219 | minor(st.st_dev), major(st.st_rdev), minor(st.st_rdev), nlen, 0); |
| 220 | xwrite(afd, toybuf, llen); |
| 221 | xwrite(afd, name, nlen); |
| 222 | |
| 223 | // NUL Pad header up to 4 multiple bytes. |
| 224 | llen = (llen + nlen) & 3; |
| 225 | if (llen) xwrite(afd, &zero, 4-llen); |
| 226 | |
| 227 | // Write out body for symlink or regular file |
| 228 | llen = st.st_size; |
| 229 | if (S_ISLNK(st.st_mode)) { |
| 230 | if (readlink(name, toybuf, sizeof(toybuf)-1) == llen) |
| 231 | xwrite(afd, toybuf, llen); |
| 232 | else perror_msg("readlink '%s'", name); |
| 233 | } else while (llen) { |
| 234 | nlen = llen > sizeof(toybuf) ? sizeof(toybuf) : llen; |
Rob Landley | 6d796b6 | 2014-03-25 07:24:50 -0500 | [diff] [blame] | 235 | llen -= nlen; |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 236 | // If read fails, write anyway (already wrote size in header) |
| 237 | if (nlen != readall(fd, toybuf, nlen)) |
| 238 | if (!error++) perror_msg("bad read from file '%s'", name); |
| 239 | xwrite(afd, toybuf, nlen); |
| 240 | } |
| 241 | llen = st.st_size & 3; |
Rob Landley | 6d796b6 | 2014-03-25 07:24:50 -0500 | [diff] [blame] | 242 | if (llen) write(afd, &zero, 4-llen); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 243 | } |
| 244 | close(fd); |
| 245 | } |
| 246 | free(name); |
| 247 | |
Rob Landley | 392e127 | 2014-03-26 06:07:06 -0500 | [diff] [blame] | 248 | memset(toybuf, 0, sizeof(toybuf)); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 249 | xwrite(afd, toybuf, |
Rob Landley | 392e127 | 2014-03-26 06:07:06 -0500 | [diff] [blame] | 250 | sprintf(toybuf, "070701%040X%056X%08XTRAILER!!!", 1, 0x0b, 0)+4); |
Rob Landley | dba5a37 | 2014-03-24 08:19:21 -0500 | [diff] [blame] | 251 | } |
Isaac Dunham | c50057e | 2013-10-14 11:15:22 -0500 | [diff] [blame] | 252 | } |