OSDN Git Service

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