>From 5ed9c376904ca312d8c38a9dbd0bd8716f58ca7d Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Thu, 19 Aug 2010 15:55:01 +0100 Subject: [PATCH 8/8] Add ext2 output module. --- README | 4 + configure.ac | 19 +++ helper/Makefile.am | 2 + helper/ext2.c | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++- helper/ext2cpio.c | 358 +++++++++++++++++++++++++++++++++++++++++++ helper/ext2cpio.h | 42 +++++ 6 files changed, 848 insertions(+), 6 deletions(-) create mode 100644 helper/ext2cpio.c create mode 100644 helper/ext2cpio.h diff --git a/README b/README index b8df88e..5383dcb 100644 --- a/README +++ b/README @@ -38,6 +38,10 @@ Requirements qemu - If you want to test-run your systems. + libext2fs + /sbin/mke2fs + - These are part of e2fsprogs. + Building and installing ----------------------- diff --git a/configure.ac b/configure.ac index 1a94700..c91190a 100644 --- a/configure.ac +++ b/configure.ac @@ -58,6 +58,25 @@ if test "x$YUM" = "xno" ; then AC_MSG_FAILURE([yum program not found]) fi +AC_PATH_PROG([MKE2FS],[mke2fs],[no]) +if test "x$MKE2FS" = "xno" ; then + AC_MSG_FAILURE([mke2fs program not found (is /sbin in your current path?)]) +fi +AC_DEFINE_UNQUOTED([MKE2FS],["$MKE2FS"], + [Full path to the mke2fs program.]) + +AC_CHECK_LIB([com_err],[error_message],[],[ + AC_MSG_FAILURE([com_err library not found (part of e2fsprogs)]) +]) + +AC_CHECK_LIB([ext2fs],[ext2fs_file_open2],[],[ + AC_MSG_FAILURE([libext2fs library not found (part of e2fsprogs)]) +]) + +AC_CHECK_HEADER([ext2fs/ext2fs.h],[],[ + AC_MSG_FAILURE([Header not found (part of e2fsprogs)]) +]) + AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile lib/Makefile helper/Makefile examples/Makefile]) AC_OUTPUT diff --git a/helper/Makefile.am b/helper/Makefile.am index 5c5966c..c43f906 100644 --- a/helper/Makefile.am +++ b/helper/Makefile.am @@ -26,6 +26,8 @@ febootstrap_supermin_helper_SOURCES = \ appliance.c \ cpio.c \ ext2.c \ + ext2cpio.c \ + ext2cpio.h \ kernel.c \ main.c \ utils.c diff --git a/helper/ext2.c b/helper/ext2.c index 2900221..f2ad92c 100644 --- a/helper/ext2.c +++ b/helper/ext2.c @@ -21,26 +21,443 @@ #include #include #include +#include +#include +#include +#include #include #include #include "error.h" #include "fts_.h" +#include "xvasprintf.h" #include "helper.h" +#include "ext2cpio.h" + +ext2_filsys fs; + +/* The ext2 image that we build always has a fixed size, and we 'hope' + * that the files fit in (otherwise we'll get an error). Note that + * the file is sparsely allocated. + * + * The downside of allocating a very large initial disk is that the + * fixed overhead of ext2 is larger (since ext2 calculates it based on + * the size of the disk). For a 1GB disk the overhead is + * approximately 16MB. + * + * In future, make this configurable, or determine it from the input + * files (XXX). + */ +#define APPLIANCE_SIZE (1024*1024*1024) static void ext2_start (const char *appliance, const char *modpath, const char *initrd) { - abort (); + initialize_ext2_error_table (); + + int fd = open (appliance, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0644); + if (fd == -1) + error (EXIT_FAILURE, errno, "open: %s", appliance); + + if (lseek (fd, APPLIANCE_SIZE - 1, SEEK_SET) == -1) + error (EXIT_FAILURE, errno, "lseek"); + + char c = 0; + if (write (fd, &c, 1) != 1) + error (EXIT_FAILURE, errno, "write"); + + if (close (fd) == -1) + error (EXIT_FAILURE, errno, "close"); + + /* Run mke2fs on the file. + * XXX Quoting, but this string doesn't come from an untrusted source. + */ + char *cmd = xasprintf ("%s -t ext2 -F%s '%s'", + MKE2FS, + verbose ? "" : "q", + appliance); + int r = system (cmd); + if (r == -1 || WEXITSTATUS (r) != 0) + error (EXIT_FAILURE, 0, "%s: failed", cmd); + free (cmd); + + if (verbose) + print_timestamped_message ("finished mke2fs"); + + /* Open the filesystem. */ + errcode_t err = + ext2fs_open (appliance, EXT2_FLAG_RW, 0, 0, unix_io_manager, &fs); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_open: %s", error_message (err)); + + /* Bitmaps are not loaded by default, so load them. ext2fs_close will + * write out any changes. + */ + err = ext2fs_read_bitmaps (fs); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_read_bitmaps: %s", error_message (err)); +} + +static void +ext2_end (void) +{ + /* Write out changes and close. */ + errcode_t err = ext2fs_close (fs); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_close: %s", error_message (err)); +} + +void +ext2_mkdir (ext2_ino_t dir_ino, const char *dirname, const char *basename, + mode_t mode, uid_t uid, gid_t gid, + time_t ctime, time_t atime, time_t mtime) +{ + errcode_t err; + + mode = LINUX_S_IFDIR | (mode & 0777); + + /* Does the directory exist? This is legitimate: we just skip + * this case. + */ + ext2_ino_t ino; + err = ext2fs_namei (fs, EXT2_ROOT_INO, dir_ino, basename, &ino); + if (err == 0) + return; /* skip */ + + /* Otherwise, create it. */ + err = ext2fs_new_inode (fs, dir_ino, mode, 0, &ino); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_new_inode: %s", error_message (err)); + + err = ext2fs_mkdir (fs, dir_ino, ino, basename); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_mkdir: %s/%s: %s", + dirname, basename, error_message (err)); + + /* Copy the final permissions, UID etc. to the inode. */ + struct ext2_inode inode; + err = ext2fs_read_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err)); + inode.i_mode = mode; + inode.i_uid = uid; + inode.i_gid = gid; + inode.i_ctime = ctime; + inode.i_atime = atime; + inode.i_mtime = mtime; + err = ext2fs_write_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err)); +} + +void +ext2_empty_inode (ext2_ino_t dir_ino, const char *dirname, const char *basename, + mode_t mode, uid_t uid, gid_t gid, + time_t ctime, time_t atime, time_t mtime, + int major, int minor, int dir_ft, ext2_ino_t *ino_ret) +{ + errcode_t err; + struct ext2_inode inode; + ext2_ino_t ino; + + err = ext2fs_new_inode (fs, dir_ino, mode, 0, &ino); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_new_inode: %s", error_message (err)); + + memset (&inode, 0, sizeof inode); + inode.i_mode = mode; + inode.i_uid = uid; + inode.i_gid = gid; + inode.i_blocks = 0; + inode.i_links_count = 1; + inode.i_ctime = ctime; + inode.i_atime = atime; + inode.i_mtime = mtime; + inode.i_size = 0; + inode.i_block[0] = (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); + + err = ext2fs_write_new_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err)); + + ext2_link (dir_ino, basename, ino, dir_ft); + + ext2fs_inode_alloc_stats2 (fs, ino, 1, 0); + + if (ino_ret) + *ino_ret = ino; +} + +/* You must create the file first with ext2_empty_inode. */ +void +ext2_write_file (ext2_ino_t ino, const char *buf, size_t size) +{ + errcode_t err; + ext2_file_t file; + err = ext2fs_file_open2 (fs, ino, NULL, EXT2_FILE_WRITE, &file); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_file_open2: %s", error_message (err)); + + /* ext2fs_file_write cannot deal with partial writes. You have + * to write the entire file in a single call. + */ + unsigned int written; + err = ext2fs_file_write (file, buf, size, &written); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_file_write: %s", error_message (err)); + if ((size_t) written != size) + error (EXIT_FAILURE, 0, + "ext2fs_file_write: size = %zu != written = %u\n", + size, written); + + err = ext2fs_file_flush (file); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_file_flush: %s", error_message (err)); + err = ext2fs_file_close (file); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_file_close: %s", error_message (err)); + + /* Update the true size in the inode. */ + struct ext2_inode inode; + err = ext2fs_read_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err)); + inode.i_size = size; + err = ext2fs_write_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err)); +} + +/* This is just a wrapper around ext2fs_link which calls + * ext2fs_expand_dir as necessary if the directory fills up. See + * definition of expand_dir in the sources of debugfs. + */ +void +ext2_link (ext2_ino_t dir_ino, const char *basename, ext2_ino_t ino, int dir_ft) +{ + errcode_t err; + + again: + err = ext2fs_link (fs, dir_ino, basename, ino, dir_ft); + + if (err == EXT2_ET_DIR_NO_SPACE) { + err = ext2fs_expand_dir (fs, dir_ino); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2_link: ext2fs_expand_dir: %s: %s", + basename, error_message (err)); + goto again; + } + + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_link: %s: %s", + basename, error_message (err)); +} + +static int +release_block (ext2_filsys fs, blk_t *blocknr, + int blockcnt, void *private) +{ + blk_t block; + + block = *blocknr; + ext2fs_block_alloc_stats (fs, block, -1); + return 0; +} + +/* unlink or rmdir path, if it exists. */ +void +ext2_clean_path (ext2_ino_t dir_ino, + const char *dirname, const char *basename, + int isdir) +{ + errcode_t err; + + ext2_ino_t ino; + err = ext2fs_lookup (fs, dir_ino, basename, strlen (basename), + NULL, &ino); + if (err == EXT2_ET_FILE_NOT_FOUND) + return; + + if (!isdir) { + struct ext2_inode inode; + err = ext2fs_read_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_read_inode: %s", error_message (err)); + inode.i_links_count--; + err = ext2fs_write_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err)); + + err = ext2fs_unlink (fs, dir_ino, basename, 0, 0); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_unlink_inode: %s", error_message (err)); + + if (inode.i_links_count == 0) { + inode.i_dtime = time (NULL); + err = ext2fs_write_inode (fs, ino, &inode); + if (err != 0) + error (EXIT_FAILURE, 0, "ext2fs_write_inode: %s", error_message (err)); + + if (ext2fs_inode_has_valid_blocks (&inode)) + ext2fs_block_iterate (fs, ino, BLOCK_FLAG_READ_ONLY, NULL, + release_block, NULL); + + ext2fs_inode_alloc_stats2 (fs, ino, -1, isdir); + } + } + /* else it's a directory, what to do? XXX */ +} + +/* Read in the whole file into memory. Check the size is still 'size'. */ +static char * +read_whole_file (const char *filename, size_t size) +{ + char *buf = malloc (size); + if (buf == NULL) + error (EXIT_FAILURE, errno, "malloc"); + + int fd = open (filename, O_RDONLY); + if (fd == -1) + error (EXIT_FAILURE, errno, "open: %s", filename); + + size_t n = 0; + char *p = buf; + + while (n < size) { + ssize_t r = read (fd, p, size - n); + if (r == -1) + error (EXIT_FAILURE, errno, "read: %s", filename); + if (r == 0) + error (EXIT_FAILURE, 0, + "error: file has changed size unexpectedly: %s", filename); + n += r; + p += r; + } + + if (close (fd) == -1) + error (EXIT_FAILURE, errno, "close: %s", filename); + + return buf; +} + +/* Add a file (or directory etc) from the host. */ +static void +ext2_file_stat (const char *orig_filename, const struct stat *statbuf) +{ + errcode_t err; + + if (verbose >= 2) + fprintf (stderr, "ext2_file_stat %s 0%o\n", + orig_filename, statbuf->st_mode); + + /* Sanity check the path. These rules are always true for the paths + * passed to us here from the appliance layer. The assertions just + * verify that the rules haven't changed. + */ + size_t n = strlen (orig_filename); + assert (n <= PATH_MAX); + assert (n > 0); + assert (orig_filename[0] == '/'); /* always absolute path */ + assert (n == 1 || orig_filename[n-1] != '/'); /* no trailing slash */ + + /* Don't make the root directory, it always exists. This simplifies + * the code that follows. + */ + if (n == 1) return; + + const char *dirname, *basename; + const char *p = strrchr (orig_filename, '/'); + ext2_ino_t dir_ino; + if (orig_filename == p) { /* "/foo" */ + dirname = "/"; + basename = orig_filename+1; + dir_ino = EXT2_ROOT_INO; + } else { /* "/foo/bar" */ + dirname = strndup (orig_filename, p-orig_filename); + basename = p+1; + + /* 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)); + } + + ext2_clean_path (dir_ino, dirname, basename, S_ISDIR (statbuf->st_mode)); + + int dir_ft; + + /* Create regular file. */ + if (S_ISREG (statbuf->st_mode)) { + /* XXX Hard links get duplicated here. */ + ext2_ino_t ino; + ext2_empty_inode (dir_ino, dirname, basename, + statbuf->st_mode, statbuf->st_uid, statbuf->st_gid, + statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime, + 0, 0, EXT2_FT_REG_FILE, &ino); + + if (statbuf->st_size > 0) { + char *buf = read_whole_file (orig_filename, statbuf->st_size); + ext2_write_file (ino, buf, statbuf->st_size); + free (buf); + } + } + /* Create directory. */ + else if (S_ISDIR (statbuf->st_mode)) + ext2_mkdir (dir_ino, dirname, basename, + statbuf->st_mode, statbuf->st_uid, statbuf->st_gid, + statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime); + /* Create a special file. */ + else if (S_ISBLK (statbuf->st_mode)) { + dir_ft = EXT2_FT_BLKDEV; + goto make_special; + } + else if (S_ISCHR (statbuf->st_mode)) { + dir_ft = EXT2_FT_CHRDEV; + goto make_special; + } else if (S_ISFIFO (statbuf->st_mode)) { + dir_ft = EXT2_FT_FIFO; + goto make_special; + } else if (S_ISSOCK (statbuf->st_mode)) { + dir_ft = EXT2_FT_SOCK; + make_special: + ext2_empty_inode (dir_ino, dirname, basename, + statbuf->st_mode, statbuf->st_uid, statbuf->st_gid, + statbuf->st_ctime, statbuf->st_atime, statbuf->st_mtime, + major (statbuf->st_rdev), minor (statbuf->st_rdev), + dir_ft, NULL); + } +} + +static void +ext2_file (const char *filename) +{ + struct stat statbuf; + + if (lstat (filename, &statbuf) == -1) + error (EXIT_FAILURE, errno, "lstat: %s", filename); + ext2_file_stat (filename, &statbuf); +} + +/* In theory this could be optimized to avoid a namei lookup, but + * it probably wouldn't make much difference. + */ +static void +ext2_fts_entry (FTSENT *entry) +{ + if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK) + ext2_file (entry->fts_path); + else + ext2_file_stat (entry->fts_path, entry->fts_statp); } struct writer ext2_writer = { .wr_start = ext2_start, - /* .wr_end = , - .wr_file = , - .wr_file_stat = , - .wr_fts_entry = , - .wr_cpio_file = , */ + .wr_end = ext2_end, + .wr_file = ext2_file, + .wr_file_stat = ext2_file_stat, + .wr_fts_entry = ext2_fts_entry, + .wr_cpio_file = ext2_cpio_file, }; diff --git a/helper/ext2cpio.c b/helper/ext2cpio.c new file mode 100644 index 0000000..65b8bcb --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" + +#include "helper.h" +#include "ext2cpio.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; +} diff --git a/helper/ext2cpio.h b/helper/ext2cpio.h new file mode 100644 index 0000000..570e414 --- /dev/null +++ b/helper/ext2cpio.h @@ -0,0 +1,42 @@ +/* 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. + */ + +/* This is a private interface used between the two parts of the + * ext2 plugin. + */ + +#ifndef FEBOOTSTRAP_SUPERMIN_EXT2CPIO_H +#define FEBOOTSTRAP_SUPERMIN_EXT2CPIO_H + +/* Inlining is broken in the ext2fs header file. Disable it by + * defining the following: + */ +#define NO_INLINE_FUNCS + +#include + +extern ext2_filsys fs; + +extern void ext2_mkdir (ext2_ino_t dir_ino, const char *dirname, const char *basename, mode_t mode, uid_t uid, gid_t gid, time_t ctime, time_t atime, time_t mtime); +extern void ext2_empty_inode (ext2_ino_t dir_ino, const char *dirname, const char *basename, mode_t mode, uid_t uid, gid_t gid, time_t ctime, time_t atime, time_t mtime, int major, int minor, int dir_ft, ext2_ino_t *ino_ret); +extern void ext2_write_file (ext2_ino_t ino, const char *buf, size_t size); +extern void ext2_link (ext2_ino_t dir_ino, const char *basename, ext2_ino_t ino, int dir_ft); +extern void ext2_cpio_file (const char *cpio_file); +extern void ext2_clean_path (ext2_ino_t dir_ino, const char *dirname, const char *basename, int isdir); + +#endif /* FEBOOTSTRAP_SUPERMIN_EXT2CPIO_H */ -- 1.7.1