diff options
Diffstat (limited to 'usr/utils/cpio.c')
-rw-r--r-- | usr/utils/cpio.c | 1079 |
1 files changed, 1079 insertions, 0 deletions
diff --git a/usr/utils/cpio.c b/usr/utils/cpio.c new file mode 100644 index 0000000..9b0b6ae --- /dev/null +++ b/usr/utils/cpio.c @@ -0,0 +1,1079 @@ +/* copyin.c - extract or list a cpio archive + Copyright (C) 1990,1991,1992,2001,2002,2003,2004 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <malloc.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <utime.h> +#include <fnmatch.h> + +# ifndef DIRECTORY_SEPARATOR +# define DIRECTORY_SEPARATOR '/' +# endif + +# ifndef ISSLASH +# define ISSLASH(C) ((C) == DIRECTORY_SEPARATOR) +# endif + +/* Return 1 if an array of N objects, each of size S, cannot exist due + to size arithmetic overflow. S must be positive and N must be + nonnegative. This is a macro, not an inline function, so that it + works correctly even when SIZE_MAX < N. + + By gnulib convention, SIZE_MAX represents overflow in size + calculations, so the conservative dividend to use here is + SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value. + However, malloc (SIZE_MAX) fails on all known hosts where + sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for + exactly-SIZE_MAX allocations on such hosts; this avoids a test and + branch when S is known to be 1. */ +# define xalloc_oversized(n, s) \ + ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n)) + +#define DISK_IO_BLOCK_SIZE (512) + +char *progname = NULL; + +/* If true, print a . for each file processed. (-V) */ +char dot_flag = false; + +/* Input and output buffers. */ +char *input_buffer, *output_buffer; + +/* The size of the input buffer. */ +long input_buffer_size; + +/* Current locations in `input_buffer' and `output_buffer'. */ +char *in_buff, *out_buff; + +/* Current number of bytes stored at `input_buff' and `output_buff'. */ +long input_size, output_size; + +/* Block size value, initially 512. -B sets to 5120. */ +int io_block_size = 512; + +struct new_cpio_header { + unsigned short c_magic; + union { + struct { + unsigned long c_ino; + unsigned long c_mode; + unsigned long c_uid; + unsigned long c_gid; + unsigned long c_nlink; + unsigned long c_mtime; + unsigned long c_filesize; + long c_dev_maj; + long c_dev_min; + long c_rdev_maj; + long c_rdev_min; + unsigned long c_namesize; + unsigned long c_chksum; + }; + unsigned long c_hdr[13]; + }; + char *c_name; + char *c_tar_linkname; +}; + +/* Total number of bytes read and written for all files. + * Now that many tape drives hold more than 4Gb we need more than 32 + * bits to hold input_bytes and output_bytes. + */ +long long input_bytes, output_bytes; + +/* Allocate N bytes of memory dynamically, with error checking. */ + +static void *xmalloc(size_t n) +{ + void *p; + if (xalloc_oversized(n, 1) || (!(p = malloc(n)) && n != 0)) { + fprintf(stderr, "%s: memory exhausted\n", progname); + exit(1); + } + return p; +/* return xnmalloc_inline (n, 1); */ +} + +/* Clone STRING. */ + +static char *xstrdup(char const *string) +{ + size_t s = strlen(string) + 1; + return memcpy(xmalloc(s), string, s); +/* return xmemdup_inline (string, strlen (string) + 1); */ +} + +/* Copy NUM_BYTES of buffer `in_buff' into IN_BUF. + `in_buff' may be partly full. + When `in_buff' is exhausted, refill it from file descriptor IN_DES. */ + +static void tape_fill_input_buffer(int in_des, int num_bytes) +{ + in_buff = input_buffer; + num_bytes = (num_bytes < io_block_size) ? num_bytes : io_block_size; + input_size = read(in_des, input_buffer, num_bytes); + if (input_size < 0) { + fprintf(stderr, "%s: read error: %s\n", progname, + strerror(errno)); + exit(1); + } + if (input_size == 0) { + fprintf(stderr, "%s: premature end of file\n", progname); + exit(1); + } + input_bytes += input_size; +} + +/* Write `output_size' bytes of `output_buffer' to file + descriptor OUT_DES and reset `output_size' and `out_buff'. + If `swapping_halfwords' or `swapping_bytes' is set, + do the appropriate swapping first. Our callers have + to make sure to only set these flags if `output_size' + is appropriate (a multiple of 4 for `swapping_halfwords', + 2 for `swapping_bytes'). The fact that DISK_IO_BLOCK_SIZE + must always be a multiple of 4 helps us (and our callers) + insure this. */ + +static void disk_empty_output_buffer(int out_des) +{ + int bytes_written; + + bytes_written = write(out_des, output_buffer, output_size); + + if (bytes_written != output_size) { + fprintf(stderr, "%s: write error: %s\n", + progname, strerror(errno)); + exit(1); + } + output_bytes += output_size; + out_buff = output_buffer; + output_size = 0; +} + +/* Copy NUM_BYTES of buffer IN_BUF to `out_buff', which may be partly full. + When `out_buff' fills up, flush it to file descriptor OUT_DES. */ + +static void disk_buffered_write(char *in_buf, int out_des, long num_bytes) +{ + register long bytes_left = num_bytes; /* Bytes needing to be copied. */ + register long space_left; /* Room left in output buffer. */ + + while (bytes_left > 0) { + space_left = DISK_IO_BLOCK_SIZE - output_size; + if (space_left == 0) + disk_empty_output_buffer(out_des); + else { + if (bytes_left < space_left) + space_left = bytes_left; + memmove(out_buff, in_buf, (unsigned)space_left); + out_buff += space_left; + output_size += space_left; + in_buf += space_left; + bytes_left -= space_left; + } + } +} + +/* Copy a file using the input and output buffers, which may start out + partly full. After the copy, the files are not closed nor the last + block flushed to output, and the input buffer may still be partly + full. If `crc_i_flag' is set, add each byte to `crc'. + IN_DES is the file descriptor for input; + OUT_DES is the file descriptor for output; + NUM_BYTES is the number of bytes to copy. */ + +static void copy_files_tape_to_disk(int in_des, int out_des, long num_bytes) +{ + long size; + + while (num_bytes > 0) { + if (input_size == 0) + tape_fill_input_buffer(in_des, io_block_size); + size = (input_size < num_bytes) ? input_size : num_bytes; + disk_buffered_write(in_buff, out_des, size); + num_bytes -= size; + input_size -= size; + in_buff += size; + } +} + +/* if IN_BUF is NULL, Skip the next NUM_BYTES bytes of file descriptor IN_DES. */ +static void tape_buffered_read(char *in_buf, int in_des, long num_bytes) +{ + register long bytes_left = num_bytes; /* Bytes needing to be copied. */ + register long space_left; /* Bytes to copy from input buffer. */ + + while (bytes_left > 0) { + if (input_size == 0) + tape_fill_input_buffer(in_des, io_block_size); + if (bytes_left < input_size) + space_left = bytes_left; + else + space_left = input_size; + if (in_buf != NULL) { + memmove(in_buf, in_buff, (unsigned)space_left); + in_buf += space_left; + } + in_buff += space_left; + input_size -= space_left; + bytes_left -= space_left; + } +} + +/* Skip the next NUM_BYTES bytes of file descriptor IN_DES. */ +#define tape_toss_input(in_des,num_bytes) \ +(tape_buffered_read(NULL,(in_des),(num_bytes))) + +struct deferment { + struct deferment *next; + struct new_cpio_header header; +}; + +static struct deferment *create_deferment(struct new_cpio_header *file_hdr) +{ + struct deferment *d; + d = (struct deferment *)xmalloc(sizeof(struct deferment)); + d->header = *file_hdr; + d->header.c_name = (char *)xmalloc(strlen(file_hdr->c_name) + 1); + strcpy(d->header.c_name, file_hdr->c_name); + return d; +} + +static void free_deferment(struct deferment *d) +{ + free(d->header.c_name); + free(d); +} + +static int link_to_name(char *link_name, char *link_target) +{ + int res = link(link_target, link_name); + return res; +} + +struct inode_val { + unsigned long inode; + unsigned long major_num; + unsigned long minor_num; + char *file_name; +}; + +/* Inode hash table. Allocated by first call to add_inode. */ +static struct inode_val **hash_table = NULL; + +/* Size of current hash table. Initial size is 47. (47 = 2*22 + 3) */ +static int hash_size = 22; + +/* Number of elements in current hash table. */ +static int hash_num; + +/* Do the hash insert. Used in normal inserts and resizing the hash + table. It is guaranteed that there is room to insert the item. + NEW_VALUE is the pointer to the previously allocated inode, file + name association record. */ + +static void hash_insert(struct inode_val *new_value) +{ + int start; /* Home position for the value. */ + int temp; /* Used for rehashing. */ + + /* Hash function is node number modulo the table size. */ + start = new_value->inode % hash_size; + + /* Do the initial look into the table. */ + if (hash_table[start] == NULL) { + hash_table[start] = new_value; + return; + } + + /* If we get to here, the home position is full with a different inode + record. Do a linear search for the first NULL pointer and insert + the new item there. */ + temp = (start + 1) % hash_size; + while (hash_table[temp] != NULL) + temp = (temp + 1) % hash_size; + + /* Insert at the NULL. */ + hash_table[temp] = new_value; +} + +/* Associate FILE_NAME with the inode NODE_NUM. (Insert into hash table.) */ + +static void +add_inode(unsigned long node_num, char *file_name, unsigned long major_num, + unsigned long minor_num) +{ + struct inode_val *temp; + + /* Create new inode record. */ + temp = (struct inode_val *)xmalloc(sizeof(struct inode_val)); + temp->inode = node_num; + temp->major_num = major_num; + temp->minor_num = minor_num; + temp->file_name = xstrdup(file_name); + + /* Do we have to increase the size of (or initially allocate) + the hash table? */ + if (hash_num == hash_size || hash_table == NULL) { + struct inode_val **old_table; /* Pointer to old table. */ + int i; /* Index for re-insert loop. */ + + /* Save old table. */ + old_table = hash_table; + if (old_table == NULL) + hash_num = 0; + + /* Calculate new size of table and allocate it. + Sequence of table sizes is 47, 97, 197, 397, 797, 1597, 3197, 6397 ... + where 3197 and most of the sizes after 6397 are not prime. The other + numbers listed are prime. */ + hash_size = 2 * hash_size + 3; + hash_table = (struct inode_val **) + xmalloc(hash_size * sizeof(struct inode_val *)); + memset(hash_table, 0, hash_size * sizeof(struct inode_val *)); + + /* Insert the values from the old table into the new table. */ + for (i = 0; i < hash_num; i++) + hash_insert(old_table[i]); + + free(old_table); + } + + /* Insert the new record and increment the count of elements in the + hash table. */ + hash_insert(temp); + hash_num++; +} + +static char *find_inode_file(unsigned long node_num, unsigned long major_num, + unsigned long minor_num) +{ + int start; /* Initial hash location. */ + int temp; /* Rehash search variable. */ + + if (hash_table != NULL) { + /* Hash function is node number modulo the table size. */ + start = node_num % hash_size; + + /* Initial look into the table. */ + if (hash_table[start] == NULL) + return NULL; + if (hash_table[start]->inode == node_num + && hash_table[start]->major_num == major_num + && hash_table[start]->minor_num == minor_num) + return hash_table[start]->file_name; + + /* The home position is full with a different inode record. + Do a linear search terminated by a NULL pointer. */ + for (temp = (start + 1) % hash_size; + hash_table[temp] != NULL && temp != start; + temp = (temp + 1) % hash_size) { + if (hash_table[temp]->inode == node_num + && hash_table[start]->major_num == major_num + && hash_table[start]->minor_num == minor_num) + return hash_table[temp]->file_name; + } + } + return NULL; +} + +/* Try and create a hard link from FILE_NAME to another file + with the given major/minor device number and inode. If no other + file with the same major/minor/inode numbers is known, add this file + to the list of known files and associated major/minor/inode numbers + and return -1. If another file with the same major/minor/inode + numbers is found, try and create another link to it using + link_to_name, and return 0 for success and -1 for failure. */ + +static int +link_to_maj_min_ino(char *file_name, int st_dev_maj, int st_dev_min, int st_ino) +{ + int link_res; + char *link_name; + link_res = -1; + /* Is the file a link to a previously copied file? */ + link_name = find_inode_file(st_ino, st_dev_maj, st_dev_min); + if (link_name == NULL) + add_inode(st_ino, file_name, st_dev_maj, st_dev_min); + else + link_res = link_to_name(file_name, link_name); + return link_res; +} + +static void copyin_regular_file(struct new_cpio_header *file_hdr, + int in_file_des); + +static void warn_junk_bytes(long bytes_skipped) +{ + fprintf(stderr, "%s: warning: skipped %ld byte(s) of junk\n", + progname, bytes_skipped); +} + +/* Skip the padding on IN_FILE_DES after a header or file, + up to the next header. + The number of bytes skipped is based on OFFSET -- the current offset + from the last start of a header (or file) -- and the current + header type. */ + +static void tape_skip_padding(int in_file_des, int offset) +{ + int pad; + pad = (4 - (offset % 4)) % 4; + + if (pad != 0) + tape_toss_input(in_file_des, pad); +} + +static int +try_existing_file(struct new_cpio_header *file_hdr, int in_file_des, + int *existing_dir) +{ + struct stat file_stat; + + *existing_dir = false; + if (lstat(file_hdr->c_name, &file_stat) == 0) { + if (S_ISDIR(file_stat.st_mode) + && ((file_hdr->c_mode & S_IFMT) == S_IFDIR)) { + /* If there is already a directory there that + we are trying to create, don't complain about + it. */ + *existing_dir = true; + return 0; + } else if (S_ISDIR(file_stat.st_mode) + ? rmdir(file_hdr->c_name) + : unlink(file_hdr->c_name)) { + fprintf(stderr, "%s: cannot remove current %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + tape_toss_input(in_file_des, file_hdr->c_filesize); + tape_skip_padding(in_file_des, file_hdr->c_filesize); + return -1; /* Go to the next file. */ + } + } + return 0; +} + +/* The newc and crc formats store multiply linked copies of the same file + in the archive only once. The actual data is attached to the last link + in the archive, and the other links all have a filesize of 0. When a + file in the archive has multiple links and a filesize of 0, its data is + probably "attatched" to another file in the archive, so we can't create + it right away. We have to "defer" creating it until we have created + the file that has the data "attatched" to it. We keep a list of the + "defered" links on deferments. */ + +struct deferment *deferments = NULL; + +/* Add a file header to the deferments list. For now they all just + go on one list, although we could optimize this if necessary. */ + +static void defer_copyin(struct new_cpio_header *file_hdr) +{ + struct deferment *d; + d = create_deferment(file_hdr); + d->next = deferments; + deferments = d; + return; +} + +/* We just created a file that (probably) has some other links to it + which have been defered. Go through all of the links on the deferments + list and create any which are links to this file. */ + +static void create_defered_links(struct new_cpio_header *file_hdr) +{ + struct deferment *d; + struct deferment *d_prev; + int ino; + int maj; + int min; + int link_res; + ino = file_hdr->c_ino; + maj = file_hdr->c_dev_maj; + min = file_hdr->c_dev_min; + d = deferments; + d_prev = NULL; + while (d != NULL) { + if ((d->header.c_ino == ino) && (d->header.c_dev_maj == maj) + && (d->header.c_dev_min == min)) { + struct deferment *d_free; + link_res = + link_to_name(d->header.c_name, file_hdr->c_name); + if (link_res < 0) { + fprintf(stderr, + "%s: cannot link %s to %s: %s\n", + progname, d->header.c_name, + file_hdr->c_name, strerror(errno)); + } + if (d_prev != NULL) + d_prev->next = d->next; + else + deferments = d->next; + d_free = d; + d = d->next; + free_deferment(d_free); + } else { + d_prev = d; + d = d->next; + } + } +} + +/* If we had a multiply linked file that really was empty then we would + have defered all of its links, since we never found any with data + "attached", and they will still be on the deferment list even when + we are done reading the whole archive. Write out all of these + empty links that are still on the deferments list. */ + +static void create_final_defers(void) +{ + struct deferment *d; + int link_res; + int out_file_des; + struct utimbuf times; /* For setting file times. */ + /* Initialize this in case it has members we don't know to set. */ + memset(×, 0, sizeof(struct utimbuf)); + + for (d = deferments; d != NULL; d = d->next) { + /* Debian hack: A line, which could cause an endless loop, was + removed (97/1/2). It was reported by Ronald F. Guilmette to + the upstream maintainers. -BEM */ + /* Debian hack: This was reported by Horst Knobloch. This bug has + been reported to "bug-gnu-utils@prep.ai.mit.edu". (99/1/6) -BEM + */ + link_res = link_to_maj_min_ino(d->header.c_name, + d->header.c_dev_maj, + d->header.c_dev_min, + d->header.c_ino); + if (link_res == 0) { + continue; + } + out_file_des = open(d->header.c_name, O_CREAT | O_WRONLY, 0600); + if (out_file_des < 0) { + fprintf(stderr, "%s: open %s: %s\n", + progname, d->header.c_name, strerror(errno)); + continue; + } + + /* File is now copied; set attributes. */ + if ((fchown(out_file_des, d->header.c_uid, d->header.c_gid) < 0) + && errno != EPERM) + fprintf(stderr, "%s: fchown %s: %s\n", + progname, d->header.c_name, strerror(errno)); + /* chown may have turned off some permissions we wanted. */ + if (fchmod(out_file_des, (int)d->header.c_mode) < 0) + fprintf(stderr, "%s: fchmod %s: %s\n", + progname, d->header.c_name, strerror(errno)); + + if (close(out_file_des) < 0) + fprintf(stderr, "%s: close %s: %s\n", + progname, d->header.c_name, strerror(errno)); + + } +} + +static void +copyin_regular_file(struct new_cpio_header *file_hdr, int in_file_des) +{ + int out_file_des; /* Output file descriptor. */ + + /* Can the current file be linked to a previously copied file? */ + if (file_hdr->c_nlink > 1) { + int link_res; + if (file_hdr->c_filesize == 0) { + /* The newc and crc formats store multiply linked copies + of the same file in the archive only once. The + actual data is attached to the last link in the + archive, and the other links all have a filesize + of 0. Since this file has multiple links and a + filesize of 0, its data is probably attatched to + another file in the archive. Save the link, and + process it later when we get the actual data. We + can't just create it with length 0 and add the + data later, in case the file is readonly. We still + lose if its parent directory is readonly (and we aren't + running as root), but there's nothing we can do about + that. */ + defer_copyin(file_hdr); + tape_toss_input(in_file_des, file_hdr->c_filesize); + tape_skip_padding(in_file_des, file_hdr->c_filesize); + return; + } + /* If the file has data (filesize != 0), then presumably + any other links have already been defer_copyin'ed(), + but GNU cpio version 2.0-2.2 didn't do that, so we + still have to check for links here (and also in case + the archive was created and later appeneded to). */ + /* Debian hack: (97/1/2) This was reported by Ronald + F. Guilmette to the upstream maintainers. -BEM */ + link_res = link_to_maj_min_ino(file_hdr->c_name, + file_hdr->c_dev_maj, + file_hdr->c_dev_min, + file_hdr->c_ino); + if (link_res == 0) { + tape_toss_input(in_file_des, file_hdr->c_filesize); + tape_skip_padding(in_file_des, file_hdr->c_filesize); + return; + } + } + + /* If not linked, copy the contents of the file. */ + out_file_des = open(file_hdr->c_name, O_CREAT | O_WRONLY, 0600); + + if (out_file_des < 0) { + fprintf(stderr, "%s: open %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + tape_toss_input(in_file_des, file_hdr->c_filesize); + tape_skip_padding(in_file_des, file_hdr->c_filesize); + return; + } + + copy_files_tape_to_disk(in_file_des, out_file_des, + file_hdr->c_filesize); + disk_empty_output_buffer(out_file_des); + + if (close(out_file_des) < 0) + fprintf(stderr, "%s: close %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + + /* File is now copied; set attributes. */ + if ((chown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0) + && errno != EPERM) + fprintf(stderr, "%s: chown %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + + /* chown may have turned off some permissions we wanted. */ + if (chmod(file_hdr->c_name, (int)file_hdr->c_mode) < 0) + fprintf(stderr, "%s: chmod %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + + tape_skip_padding(in_file_des, file_hdr->c_filesize); + if (file_hdr->c_nlink > 1) { + /* (see comment above for how the newc and crc formats + store multiple links). Now that we have the data + for this file, create any other links to it which + we defered. */ + create_defered_links(file_hdr); + } +} + +/* In general, we can't use the builtin `basename' function if available, + since it has different meanings in different environments. + In some environments the builtin `basename' modifies its argument. + + Return the address of the last file name component of NAME. If + NAME has no file name components because it is all slashes, return + NAME if it is empty, the address of its last slash otherwise. */ + +static char *base_name(char const *name) +{ + char const *base = name; + char const *p; + + for (p = base; *p; p++) { + if (ISSLASH(*p)) { + /* Treat multiple adjacent slashes like a single slash. */ + do + p++; + while (ISSLASH(*p)); + + /* If the file name ends in slash, use the trailing slash as + the basename if no non-slashes have been found. */ + if (!*p) { + if (ISSLASH(*base)) + base = p - 1; + break; + } + + /* *P is a non-slash preceded by a slash. */ + base = p; + } + } + + return (char *)base; +} + +/* Return the length of of the basename NAME. Typically NAME is the + value returned by base_name. Act like strlen (NAME), except omit + redundant trailing slashes. */ + +static size_t base_len(char const *name) +{ + size_t len; + + for (len = strlen(name); 1 < len && ISSLASH(name[len - 1]); len--) + continue; + + return len; +} + +/* Remove trailing slashes from PATH. + Return true if a trailing slash was removed. + This is useful when using filename completion from a shell that + adds a "/" after directory names (such as tcsh and bash), because + the Unix rename and rmdir system calls return an "Invalid argument" error + when given a path that ends in "/" (except for the root directory). */ + +static bool strip_trailing_slashes(char *path) +{ + char *base = base_name(path); + char *base_lim = base + base_len(base); + bool had_slash = (*base_lim != '\0'); + *base_lim = '\0'; + return had_slash; +} + +static void copyin_directory(struct new_cpio_header *file_hdr, int existing_dir) +{ + int res; /* Result of various function calls. */ + + /* Strip any trailing `/'s off the filename; tar puts + them on. We might as well do it here in case anybody + else does too, since they cause strange things to happen. */ + strip_trailing_slashes(file_hdr->c_name); + + /* Ignore the current directory. It must already exist, + and we don't want to change its permission, ownership + or time. */ + if (file_hdr->c_name[0] == '.' && file_hdr->c_name[1] == '\0') { + return; + } + + if (!existing_dir) + { + res = mkdir(file_hdr->c_name, file_hdr->c_mode); + } else + res = 0; + if (res < 0) { + /* In some odd cases where the file_hdr->c_name includes `.', + the directory may have actually been created by + create_all_directories(), so the mkdir will fail + because the directory exists. If that's the case, + don't complain about it. */ + struct stat file_stat; + if ((errno != EEXIST) || + (lstat(file_hdr->c_name, &file_stat) != 0) || + !(S_ISDIR(file_stat.st_mode))) { + fprintf(stderr, "%s: lstat %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + return; + } + } + if ((chown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0) + && errno != EPERM) + fprintf(stderr, "%s: chown %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + /* chown may have turned off some permissions we wanted. */ + if (chmod(file_hdr->c_name, (int)file_hdr->c_mode) < 0) + fprintf(stderr, "%s: chmod %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); +} + +static void copyin_device(struct new_cpio_header *file_hdr) +{ + int res; /* Result of various function calls. */ + + if (file_hdr->c_nlink > 1) { + int link_res; + /* Debian hack: This was reported by Horst + Knobloch. This bug has been reported to + "bug-gnu-utils@prep.ai.mit.edu". (99/1/6) -BEM */ + link_res = link_to_maj_min_ino(file_hdr->c_name, + file_hdr->c_dev_maj, + file_hdr->c_dev_min, + file_hdr->c_ino); + if (link_res == 0) { + return; + } + } + + res = mknod(file_hdr->c_name, file_hdr->c_mode, + makedev(file_hdr->c_rdev_maj, file_hdr->c_rdev_min)); + if (res < 0) { + fprintf(stderr, "%s: mknod %s: %s\n", progname, + file_hdr->c_name, strerror(errno)); + return; + } + if ((chown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0) + && errno != EPERM) + fprintf(stderr, "%s: chown %s: %s\n", progname, + file_hdr->c_name, strerror(errno)); + /* chown may have turned off some permissions we wanted. */ + if (chmod(file_hdr->c_name, file_hdr->c_mode) < 0) + fprintf(stderr, "%s: chmod %s: %s\n", progname, + file_hdr->c_name, strerror(errno)); +} + +static void copyin_link(struct new_cpio_header *file_hdr, int in_file_des) +{ + char *link_name = NULL; /* Name of hard and symbolic links. */ + int res; /* Result of various function calls. */ + + link_name = (char *)xmalloc(file_hdr->c_filesize + 1); + link_name[file_hdr->c_filesize] = '\0'; + tape_buffered_read(link_name, in_file_des, file_hdr->c_filesize); + tape_skip_padding(in_file_des, file_hdr->c_filesize); + + res = symlink(link_name, file_hdr->c_name); + if (res < 0) { + fprintf(stderr, "%s: symlink %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + free(link_name); + return; + } + if ((lchown(file_hdr->c_name, file_hdr->c_uid, file_hdr->c_gid) < 0) + && errno != EPERM) { + fprintf(stderr, "%s: lchown %s: %s\n", + progname, file_hdr->c_name, strerror(errno)); + } + free(link_name); +} + +static void copyin_file(struct new_cpio_header *file_hdr, int in_file_des) +{ + int existing_dir; + + if (try_existing_file(file_hdr, in_file_des, &existing_dir) < 0) + return; + + /* Do the real copy or link. */ + switch (file_hdr->c_mode & S_IFMT) { + case S_IFREG: + copyin_regular_file(file_hdr, in_file_des); + break; + + case S_IFDIR: + copyin_directory(file_hdr, existing_dir); + break; + + case S_IFCHR: + case S_IFBLK: + case S_IFSOCK: + case S_IFIFO: + copyin_device(file_hdr); + break; + + case S_IFLNK: + copyin_link(file_hdr, in_file_des); + break; + + default: + fprintf(stderr, "%s: %s: unknown file type\n", + progname, file_hdr->c_name); + tape_toss_input(in_file_des, file_hdr->c_filesize); + tape_skip_padding(in_file_des, file_hdr->c_filesize); + } +} + +/* Fill in FILE_HDR by reading a new-format ASCII format cpio header from + file descriptor IN_DES, except for the magic number, which is + already filled in. */ + +static void read_in_new_ascii(struct new_cpio_header *file_hdr, int in_des) +{ + char ascii_header[13*8], *ah, hexbuf[9]; + int i; + + tape_buffered_read(ascii_header, in_des, 13*8); + ah = ascii_header; + hexbuf[8] = '\0'; + for (i = 0; i < 13; i++) { + memcpy(hexbuf, ah, 8); + file_hdr->c_hdr[i] = strtoul(hexbuf, NULL, 16); + ah += 8; + } + + /* Sizes > LONG_MAX can currently result in integer overflow + in various places. Fail if name is too large. */ + if (file_hdr->c_namesize > LONG_MAX) { + fprintf(stderr, "%s: name size out of range\n", + progname); + exit(1); + } + + /* Read file name from input. */ + free(file_hdr->c_name); + file_hdr->c_name = (char *)xmalloc(file_hdr->c_namesize); + tape_buffered_read(file_hdr->c_name, in_des, + (long)file_hdr->c_namesize); + + /* In SVR4 ASCII format, the amount of space allocated for the header + is rounded up to the next long-word, so we might need to drop + 1-3 bytes. */ + tape_skip_padding(in_des, file_hdr->c_namesize + 110); + + /* Fail if file is too large. We could check this earlier + but it's helpful to report the name. */ + if (file_hdr->c_filesize > LONG_MAX) { + fprintf(stderr, "%s: %s: file size out of range\n", + progname, file_hdr->c_name); + exit(1); + } +} + +/* Return 16-bit integer I with the bytes swapped. */ +#define swab_short(i) ((((i) << 8) & 0xff00) | (((i) >> 8) & 0x00ff)) + +/* Read the header, including the name of the file, from file + descriptor IN_DES into FILE_HDR. */ + +static void read_in_header(struct new_cpio_header *file_hdr, int in_des) +{ + long bytes_skipped = 0; /* Bytes of junk found before magic number. */ + + /* Search for a valid magic number. */ + + file_hdr->c_tar_linkname = NULL; + + tape_buffered_read((char *)file_hdr, in_des, 6L); + while (1) { + if (!strncmp((char *)file_hdr, "070702", 6) + || !strncmp((char *)file_hdr, "070701", 6)) + { + if (bytes_skipped > 0) + warn_junk_bytes(bytes_skipped); + + read_in_new_ascii(file_hdr, in_des); + break; + } + bytes_skipped++; + memmove((char *)file_hdr, (char *)file_hdr + 1, 5); + tape_buffered_read((char *)file_hdr + 5, in_des, 1L); + } +} + +/* Read the collection from standard input and create files + in the file system. */ + +static void process_copy_in(void) +{ + char done = false; /* True if trailer reached. */ + struct new_cpio_header file_hdr; /* Output header information. */ + int in_file_des; /* Input file descriptor. */ + + /* Initialize the copy in. */ + file_hdr.c_name = NULL; + + /* only from stdin */ + in_file_des = 0; + + /* While there is more input in the collection, process the input. */ + while (!done) { + /* Start processing the next file by reading the header. */ + read_in_header(&file_hdr, in_file_des); + + /* Is this the header for the TRAILER file? */ + if (strcmp("TRAILER!!!", file_hdr.c_name) == 0) { + done = true; + break; + } + + /* Copy the input file into the directory structure. */ + + copyin_file(&file_hdr, in_file_des); + + if (dot_flag) + fputc('.', stderr); + } + + if (dot_flag) + fputc('\n', stderr); + + create_final_defers(); + +} + +/* Initialize the input and output buffers to their proper size and + initialize all variables associated with the input and output + buffers. */ + +static void initialize_buffers(void) +{ + int in_buf_size, out_buf_size; + + /* Make sure the input buffer can always hold 2 blocks and that it + is big enough to hold 1 tar record (512 bytes) even if it + is not aligned on a block boundary. The extra buffer space + is needed by process_copyin and peek_in_buf to automatically + figure out what kind of archive it is reading. */ + if (io_block_size >= 512) + in_buf_size = 2 * io_block_size; + else + in_buf_size = 1024; + out_buf_size = DISK_IO_BLOCK_SIZE; + + input_buffer = (char *)xmalloc(in_buf_size); + in_buff = input_buffer; + input_buffer_size = in_buf_size; + input_size = 0; + input_bytes = 0; + + output_buffer = (char *)xmalloc(out_buf_size); + out_buff = output_buffer; + output_size = 0; + output_bytes = 0; + +} + +int main(int argc, char *argv[]) +{ + int c; + int extract_flag = false; + + progname = argv[0]; + + do { + c = getopt(argc, argv, "iV"); + if (c == EOF) + break; + switch (c) { + case 'V': + dot_flag = true; + break; + + case 'i': + extract_flag = true; + break; + case '?': + fprintf(stderr, + "%s: not implemented or invalid option -%c\n", + progname, optopt); + exit(1); + + } + } while (1); + + if (extract_flag) { + initialize_buffers(); + + process_copy_in(); + } else { + fprintf(stderr, "Usage: %s [-V] -i [< archive]\n", progname); + exit(1); + } + + return 0; +} |