[Libguestfs] [PATCH febootstrap 8/8 INCOMPLETE] Add ext2 output module.

Richard W.M. Jones rjones at redhat.com
Fri Aug 20 10:41:46 UTC 2010


-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming blog: http://rwmj.wordpress.com
Fedora now supports 80 OCaml packages (the OPEN alternative to F#)
http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
-------------- next part --------------
>From 5ed9c376904ca312d8c38a9dbd0bd8716f58ca7d Mon Sep 17 00:00:00 2001
From: Richard Jones <rjones at redhat.com>
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 <ext2fs/ext2fs.h> 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 <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 "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 <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 "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 <ext2fs/ext2fs.h>
+
+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



More information about the Libguestfs mailing list