diff options
Diffstat (limited to 'helper/ext2cpio.c')
-rw-r--r-- | helper/ext2cpio.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/helper/ext2cpio.c b/helper/ext2cpio.c new file mode 100644 index 0000000..7690553 --- /dev/null +++ b/helper/ext2cpio.c @@ -0,0 +1,358 @@ +/* febootstrap-supermin-helper reimplementation in C. + * Copyright (C) 2009-2010 Red Hat 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 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <sys/stat.h> +#include <assert.h> + +#include "error.h" + +#include "helper.h" +#include "ext2internal.h" + +/* This function must unpack the cpio file and add the files it + * contains to the ext2 filesystem. Essentially this is doing the + * same thing as the kernel init/initramfs.c code. Note that we + * assume that the cpio is uncompressed newc format and can't/won't + * deal with anything else. All this cpio parsing code is copied to + * some extent from init/initramfs.c in the kernel. + */ +#define N_ALIGN(len) ((((len) + 1) & ~3) + 2) + +static unsigned long cpio_ino, nlink; +static mode_t mode; +static unsigned long body_len, name_len; +static uid_t uid; +static gid_t gid; +static time_t mtime; +static int dev_major, dev_minor, rdev_major, rdev_minor; +static loff_t curr, next_header; +static FILE *fp; + +static void parse_header (char *s); +static int parse_next_entry (void); +static void skip_to_next_header (void); +static void read_file (void); +static char *read_whole_body (void); +static ext2_ino_t maybe_link (void); +static void add_link (ext2_ino_t real_ino); +static void clear_links (void); + +void +ext2_cpio_file (const char *cpio_file) +{ + fp = fopen (cpio_file, "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, "open: %s", cpio_file); + + curr = 0; + while (parse_next_entry ()) + ; + + fclose (fp); +} + +static int +parse_next_entry (void) +{ + clearerr (fp); + + char header[110]; + + /* Skip padding and synchronize with the next header. */ + again: + if (fread (&header[0], 4, 1, fp) != 1) { + if (feof (fp)) + return 0; + error (EXIT_FAILURE, errno, "read failure reading cpio file"); + } + curr += 4; + if (memcmp (header, "\0\0\0\0", 4) == 0) + goto again; + + /* Read the rest of the header field. */ + if (fread (&header[4], sizeof header - 4, 1, fp) != 1) + error (EXIT_FAILURE, errno, "read failure reading cpio file"); + curr += sizeof header - 4; + + if (verbose >= 2) + fprintf (stderr, "cpio header %s\n", header); + + if (memcmp (header, "070707", 6) == 0) + error (EXIT_FAILURE, 0, "incorrect cpio method: use -H newc option"); + if (memcmp (header, "070701", 6) != 0) + error (EXIT_FAILURE, 0, "input is not a cpio file"); + + parse_header (header); + + next_header = curr + N_ALIGN(name_len) + body_len; + next_header = (next_header + 3) & ~3; + if (name_len <= 0 || name_len > PATH_MAX) + skip_to_next_header (); + else if (S_ISLNK (mode)) { + if (body_len <= 0 || body_len > PATH_MAX) + skip_to_next_header (); + else + read_file (); + } + else if (!S_ISREG (mode) && body_len > 0) + skip_to_next_header (); /* only regular files have bodies */ + else + read_file (); /* could be file, directory, block special, ... */ + + return 1; +} + +static void +parse_header (char *s) +{ + unsigned long parsed[12]; + char buf[9]; + int i; + + buf[8] = '\0'; + for (i = 0, s += 6; i < 12; i++, s += 8) { + memcpy (buf, s, 8); + parsed[i] = strtoul (buf, NULL, 16); + } + cpio_ino = parsed[0]; /* fake inode number from cpio file */ + mode = parsed[1]; + uid = parsed[2]; + gid = parsed[3]; + nlink = parsed[4]; + mtime = parsed[5]; + body_len = parsed[6]; + dev_major = parsed[7]; + dev_minor = parsed[8]; + rdev_major = parsed[9]; + rdev_minor = parsed[10]; + name_len = parsed[11]; +} + +static void +skip_to_next_header (void) +{ + char buf[65536]; + + while (curr < next_header) { + size_t bytes = (size_t) (next_header - curr); + if (bytes > sizeof buf) + bytes = sizeof buf; + size_t r = fread (buf, 1, bytes, fp); + if (r == 0) + error (EXIT_FAILURE, errno, "error or unexpected end of cpio file"); + curr += r; + } +} + +/* Read any sort of file. The body will only be present for + * regular files and symlinks. + */ +static void +read_file (void) +{ + errcode_t err; + int dir_ft; + char name[N_ALIGN(name_len)+1]; /* asserted above this is <= PATH_MAX */ + + if (fread (name, N_ALIGN(name_len), 1, fp) != 1) + error (EXIT_FAILURE, errno, "read failure reading name field in cpio file"); + curr += N_ALIGN(name_len); + + name[name_len] = '\0'; + + if (verbose >= 2) + fprintf (stderr, "ext2 read_file %s %o\n", name, mode); + + if (strcmp (name, "TRAILER!!!") == 0) { + clear_links (); + goto skip; + } + + /* The name will be something like "bin/ls" or "./bin/ls". It won't + * (ever?) be an absolute path. Skip leading parts, and if it refers + * to the root directory just skip it entirely. + */ + char *dirname = name, *basename; + if (*dirname == '.') + dirname++; + if (*dirname == '/') + dirname++; + if (*dirname == '\0') + goto skip; + + ext2_ino_t dir_ino; + basename = strrchr (dirname, '/'); + if (basename == NULL) { + basename = dirname; + dir_ino = EXT2_ROOT_INO; + } else { + *basename++ = '\0'; + + /* Look up the parent directory. */ + err = ext2fs_namei (fs, EXT2_ROOT_INO, EXT2_ROOT_INO, dirname, &dir_ino); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2: parent directory not found: %s: %s", + dirname, error_message (err)); + } + + if (verbose >= 2) + fprintf (stderr, "ext2 read_file dirname %s basename %s\n", + dirname, basename); + + ext2_clean_path (dir_ino, dirname, basename, S_ISDIR (mode)); + + /* Create a regular file. */ + if (S_ISREG (mode)) { + ext2_ino_t ml = maybe_link (); + ext2_ino_t ino; + if (ml <= 1) { + ext2_empty_inode (dir_ino, dirname, basename, + mode, uid, gid, mtime, mtime, mtime, + 0, 0, EXT2_FT_REG_FILE, &ino); + if (ml == 1) + add_link (ino); + } + else /* ml >= 2 */ { + /* It's a hard link back to a previous file. */ + ino = ml; + ext2_link (dir_ino, basename, ino, EXT2_FT_REG_FILE); + } + + if (body_len) { + char *buf = read_whole_body (); + ext2_write_file (ino, buf, body_len); + free (buf); + } + } + /* Create a symlink. */ + else if (S_ISLNK (mode)) { + ext2_ino_t ino; + ext2_empty_inode (dir_ino, dirname, basename, + mode, uid, gid, mtime, mtime, mtime, + 0, 0, EXT2_FT_SYMLINK, &ino); + + char *buf = read_whole_body (); + ext2_write_file (ino, buf, body_len); + free (buf); + } + /* Create a directory. */ + else if (S_ISDIR (mode)) { + ext2_mkdir (dir_ino, dirname, basename, + mode, uid, gid, mtime, mtime, mtime); + } + /* Create a special file. */ + else if (S_ISBLK (mode)) { + dir_ft = EXT2_FT_BLKDEV; + goto make_special; + } + else if (S_ISCHR (mode)) { + dir_ft = EXT2_FT_CHRDEV; + goto make_special; + } else if (S_ISFIFO (mode)) { + dir_ft = EXT2_FT_FIFO; + goto make_special; + } else if (S_ISSOCK (mode)) { + dir_ft = EXT2_FT_SOCK; + make_special: + /* Just like the kernel, we ignore special files with nlink > 1. */ + if (maybe_link () == 0) + ext2_empty_inode (dir_ino, dirname, basename, + mode, uid, gid, mtime, mtime, mtime, + rdev_major, rdev_minor, dir_ft, NULL); + } + + skip: + skip_to_next_header (); +} + +static char * +read_whole_body (void) +{ + char *buf = malloc (body_len); + if (buf == NULL) + error (EXIT_FAILURE, errno, "malloc"); + + size_t r = fread (buf, body_len, 1, fp); + if (r != 1) + error (EXIT_FAILURE, errno, "read failure reading body in cpio file"); + curr += body_len; + + return buf; +} + +struct links { + struct links *next; + unsigned long cpio_ino; /* fake ino from cpio file */ + int minor; + int major; + ext2_ino_t real_ino; /* real inode number on ext2 filesystem */ +}; +static struct links *links_head = NULL; + +/* If it's a hard link, return the linked inode number in the real + * ext2 filesystem. + * + * Returns: 0 = not a hard link + * 1 = possible unresolved hard link + * inode number = resolved hard link to this inode + */ +static ext2_ino_t +maybe_link (void) +{ + if (nlink >= 2) { + struct links *p; + for (p = links_head; p; p = p->next) { + if (p->cpio_ino != cpio_ino) + continue; + if (p->minor != dev_minor) + continue; + if (p->major != dev_major) + continue; + return p->real_ino; + } + return 1; + } + + return 0; +} + +static void +add_link (ext2_ino_t real_ino) +{ + struct links *p = malloc (sizeof (*p)); + p->cpio_ino = cpio_ino; + p->minor = dev_minor; + p->major = dev_major; + p->real_ino = real_ino; +} + +static void +clear_links (void) +{ + /* Don't bother to free the linked list in this short-lived program. */ + links_head = NULL; +} |