From: Richard Jones Date: Fri, 20 Aug 2010 18:23:03 +0000 (+0100) Subject: Implement ext2 output module. X-Git-Tag: 2.8~2 X-Git-Url: http://git.annexia.org/?a=commitdiff_plain;h=89e336ee166be538e376d288fb2b3fbbffd66d4c;p=febootstrap.git Implement ext2 output module. --- diff --git a/.gitignore b/.gitignore index 310aa6e..748667f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ febootstrap-supermin-helper febootstrap*.8 febootstrap*.txt febootstrap-*.tar.gz +helper/init lib/alloca.h lib/arg-nonnull.h lib/c++defs.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..9b9f39c 100644 --- a/configure.ac +++ b/configure.ac @@ -58,6 +58,29 @@ 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.]) + +old_LIBS="$LIBS" +AC_CHECK_LIB([com_err],[error_message],[],[ + AC_MSG_FAILURE([com_err library not found (part of e2fsprogs)]) +]) +LIBS="$old_LIBS" + +old_LIBS="$LIBS" +AC_CHECK_LIB([ext2fs],[ext2fs_file_open2],[],[ + AC_MSG_FAILURE([libext2fs library not found (part of e2fsprogs)]) +]) +LIBS="$old_LIBS" + +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 3af1d64..96b5581 100644 --- a/helper/Makefile.am +++ b/helper/Makefile.am @@ -25,11 +25,29 @@ febootstrap_supermin_helper_SOURCES = \ appliance.c \ cpio.c \ ext2.c \ + ext2cpio.c \ + ext2initrd.c \ + ext2internal.h \ kernel.c \ main.c \ utils.c febootstrap_supermin_helper_CFLAGS = -Wall -I../lib -febootstrap_supermin_helper_LDADD = $(LTLIBINTL) -L../lib -lgnu +febootstrap_supermin_helper_LDADD = \ + ext2init.o -lext2fs -lcom_err $(LTLIBINTL) -L../lib -lgnu + +# init "script" used by ext2 initrd. +noinst_PROGRAMS = init +init_SOURCES = init.c +init_CFLAGS = -static +init_LDFLAGS = -all-static + +# http://www.doof.me.uk/2010/05/07/cute-objcopy-hack/ +ELF_DEFAULT_ARCH = $(shell $(srcdir)/elf-default-arch) +ext2init.o: init + strip --strip-all $< + @file $< | grep -isq static || \ + (echo "*** error: init is not staticly linked"; exit 1) + objcopy -I binary -B i386 -O $(ELF_DEFAULT_ARCH) $< $@ man_MANS = \ febootstrap-supermin-helper.8 @@ -50,4 +68,5 @@ endif EXTRA_DIST = \ febootstrap-supermin-helper.8 febootstrap-supermin-helper.txt \ - febootstrap-supermin-helper.pod + febootstrap-supermin-helper.pod \ + elf-default-arch diff --git a/helper/elf-default-arch b/helper/elf-default-arch new file mode 100755 index 0000000..0adc351 --- /dev/null +++ b/helper/elf-default-arch @@ -0,0 +1,27 @@ +#!/bin/sh - +# Copyright (C) 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. + +# Calculate the default ELF object architecture for this format. +# There doesn't seem to be an easy way to derive this from binutils, +# so instead we hard code it. + +case $(uname -m) in + i[3456]86) echo elf32-i386 ;; + x86_64) echo elf64-x86-64 ;; + *) + echo "This architecture is not recognized. Please update helper/elf-default-arch." +esac diff --git a/helper/ext2.c b/helper/ext2.c index 2900221..9d60da9 100644 --- a/helper/ext2.c +++ b/helper/ext2.c @@ -21,26 +21,461 @@ #include #include #include +#include +#include +#include +#include #include #include #include "error.h" #include "fts_.h" +#include "xvasprintf.h" #include "helper.h" +#include "ext2internal.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 (); + + /* Make the initrd. */ + ext2_make_initrd (modpath, initrd); + + /* Make the appliance sparse image. */ + 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 >= 2 ? "" : "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 a symlink. */ + else if (S_ISLNK (statbuf->st_mode)) { + 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_SYMLINK, &ino); + + char buf[PATH_MAX+1]; + ssize_t r = readlink (orig_filename, buf, sizeof buf); + if (r == -1) + error (EXIT_FAILURE, errno, "readlink: %s", orig_filename); + ext2_write_file (ino, buf, r); + } + /* 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..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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/helper/ext2initrd.c b/helper/ext2initrd.c new file mode 100644 index 0000000..c981232 --- /dev/null +++ b/helper/ext2initrd.c @@ -0,0 +1,263 @@ +/* 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. + */ + +/* ext2 requires a small initrd in order to boot. This builds it. */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "full-write.h" +#include "xalloc.h" +#include "xvasprintf.h" + +#include "helper.h" +#include "ext2internal.h" + +static void read_module_deps (const char *modpath); +static void free_module_deps (void); +static const char *get_module_dep (const char *); + +/* The init binary. */ +extern char _binary_init_start, _binary_init_end, _binary_init_size; + +/* The list of modules (wildcards) we consider for inclusion in the + * mini initrd. Only what is needed in order to find a device with an + * ext2 filesystem on it. + */ +static const char *kmods[] = { + "ext2.ko", + "virtio*.ko", + "ide*.ko", + "libata*.ko", + "piix*.ko", + "scsi_transport_spi.ko", + "scsi_mod.ko", + "sd_mod.ko", + "sym53c8xx.ko", + "ata_piix.ko", + "sr_mod.ko", + "mbcache.ko", + "crc*.ko", + "libcrc*.ko", + NULL +}; + +void +ext2_make_initrd (const char *modpath, const char *initrd) +{ + char dir[] = "/tmp/ext2initrdXXXXXX"; + if (mkdtemp (dir) == NULL) + error (EXIT_FAILURE, errno, "mkdtemp"); + + char *cmd; + int r; + + /* Copy kernel modules into tmpdir. */ + size_t n = strlen (modpath) + strlen (dir) + 64; + size_t i; + for (i = 0; kmods[i] != NULL; ++i) + n += strlen (kmods[i]) + 16; + cmd = malloc (n); + sprintf (cmd, "find '%s' ", modpath); + for (i = 0; kmods[i] != NULL; ++i) { + if (i > 0) strcat (cmd, "-o "); + strcat (cmd, "-name '"); + strcat (cmd, kmods[i]); + strcat (cmd, "' "); + } + strcat (cmd, "| xargs cp -t "); + strcat (cmd, dir); + if (verbose >= 2) fprintf (stderr, "%s\n", cmd); + r = system (cmd); + if (r == -1 || WEXITSTATUS (r) != 0) + error (EXIT_FAILURE, 0, "ext2_make_initrd: copy kmods failed"); + free (cmd); + + /* The above command effectively gives us the final list of modules. + * Calculate dependencies from modpath/modules.dep and write that + * into the output. + */ + read_module_deps (modpath); + + cmd = xasprintf ("tsort > %s/modules", dir); + if (verbose >= 2) fprintf (stderr, "%s\n", cmd); + FILE *pp = popen (cmd, "w"); + if (pp == NULL) + error (EXIT_FAILURE, errno, "tsort: failed to create modules list"); + + DIR *dr = opendir (dir); + if (dr == NULL) + error (EXIT_FAILURE, errno, "opendir: %s", dir); + + struct dirent *d; + while ((errno = 0, d = readdir (dr)) != NULL) { + size_t n = strlen (d->d_name); + if (n >= 3 && + d->d_name[n-3] == '.' && + d->d_name[n-2] == 'k' && + d->d_name[n-1] == 'o') { + const char *dep = get_module_dep (d->d_name); + if (dep) + /* Reversed so that tsort will print the final list in the + * order that it has to be loaded. + */ + fprintf (pp, "%s %s\n", dep, d->d_name); + else + /* No dependencies, just make it depend on itself so that + * tsort prints it. + */ + fprintf (pp, "%s %s\n", d->d_name, d->d_name); + } + } + if (errno) + error (EXIT_FAILURE, errno, "readdir: %s", dir); + + if (closedir (dr) == -1) + error (EXIT_FAILURE, errno, "closedir: %s", dir); + + if (pclose (pp) == -1) + error (EXIT_FAILURE, errno, "pclose: %s", cmd); + + free (cmd); + free_module_deps (); + + /* Copy in insmod static binary. */ + cmd = xasprintf ("cp /sbin/insmod.static %s", dir); + if (verbose >= 2) fprintf (stderr, "%s\n", cmd); + r = system (cmd); + if (r == -1 || WEXITSTATUS (r) != 0) + error (EXIT_FAILURE, 0, + "ext2_make_initrd: copy /sbin/insmod.static failed"); + free (cmd); + + /* Copy in the init program, linked into this program as a data blob. */ + char *init = xasprintf ("%s/init", dir); + int fd = open (init, O_WRONLY|O_TRUNC|O_CREAT|O_NOCTTY, 0755); + if (fd == -1) + error (EXIT_FAILURE, errno, "open: %s", init); + + n = (size_t) &_binary_init_size; + if (full_write (fd, &_binary_init_start, n) != n) + error (EXIT_FAILURE, errno, "write: %s", init); + + if (close (fd) == -1) + error (EXIT_FAILURE, errno, "close: %s", init); + + free (init); + + /* Build the cpio file. */ + cmd = xasprintf ("(cd %s && (echo . ; ls -1)" + " | cpio --quiet -o -H newc) > '%s'", + dir, initrd); + if (verbose >= 2) fprintf (stderr, "%s\n", cmd); + r = system (cmd); + if (r == -1 || WEXITSTATUS (r) != 0) + error (EXIT_FAILURE, 0, "ext2_make_initrd: cpio failed"); + free (cmd); + + /* Construction of 'dir' above ensures this is safe. */ + cmd = xasprintf ("rm -rf %s", dir); + if (verbose >= 2) fprintf (stderr, "%s\n", cmd); + system (cmd); + free (cmd); +} + +/* Module dependencies. */ +struct moddep { + struct moddep *next; + char *name; + char *dep; +}; +struct moddep *moddeps = NULL; + +static void add_module_dep (const char *name, const char *dep); + +static void +free_module_deps (void) +{ + /* Short-lived program, don't bother to free it. */ + moddeps = NULL; +} + +/* Read modules.dep into internal structure. */ +static void +read_module_deps (const char *modpath) +{ + free_module_deps (); + + char *filename = xasprintf ("%s/modules.dep", modpath); + FILE *fp = fopen (filename, "r"); + if (fp == NULL) + error (EXIT_FAILURE, errno, "open: %s", modpath); + + char *line = NULL; + size_t llen = 0; + ssize_t len; + while ((len = getline (&line, &llen, fp)) != -1) { + if (len > 0 && line[len-1] == '\n') + line[--len] = '\0'; + + char *name = strtok (line, ": "); + if (!name) continue; + + /* Only want the module basename, but keep the ".ko" extension. */ + char *p = strrchr (name, '/'); + if (p) name = p+1; + + char *dep; + while ((dep = strtok (NULL, " ")) != NULL) { + p = strrchr (dep, '/'); + if (p) dep = p+1; + + add_module_dep (name, dep); + } + } + + free (line); + fclose (fp); +} + +/* Module 'name' requires 'dep' to be loaded first. */ +static void +add_module_dep (const char *name, const char *dep) +{ + struct moddep *m = xmalloc (sizeof *m); + m->next = moddeps; + moddeps = m; + m->name = xstrdup (name); + m->dep = xstrdup (dep); +} + +static const char * +get_module_dep (const char *name) +{ + struct moddep *m; + + for (m = moddeps; m; m = m->next) + if (strcmp (m->name, name) == 0) + return m->dep; + + return NULL; +} diff --git a/helper/ext2internal.h b/helper/ext2internal.h new file mode 100644 index 0000000..7efadb5 --- /dev/null +++ b/helper/ext2internal.h @@ -0,0 +1,45 @@ +/* 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 parts of the ext2 plugin. */ + +#ifndef FEBOOTSTRAP_SUPERMIN_EXT2INTERNAL_H +#define FEBOOTSTRAP_SUPERMIN_EXT2INTERNAL_H + +/* Inlining is broken in the ext2fs header file. Disable it by + * defining the following: + */ +#define NO_INLINE_FUNCS +#include + +/* ext2.c */ +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_clean_path (ext2_ino_t dir_ino, const char *dirname, const char *basename, int isdir); + +/* ext2cpio.c */ +extern void ext2_cpio_file (const char *cpio_file); + +/* ext2initrd.c */ +extern void ext2_make_initrd (const char *modpath, const char *initrd); + +#endif /* FEBOOTSTRAP_SUPERMIN_EXT2INTERNAL_H */ diff --git a/helper/init.c b/helper/init.c new file mode 100644 index 0000000..5dd05cf --- /dev/null +++ b/helper/init.c @@ -0,0 +1,195 @@ +/* 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 very minimal init "script" goes in the mini-initrd used to + * boot the ext2-based appliance. Note we have no shell, so we cannot + * use system(3) to run external commands. In fact, we don't have + * very much at all, except this program, insmod.static, and some + * kernel modules. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Leave this enabled for now. When we get more confident in the boot + * process we can turn this off or make it configurable. + */ +#define verbose 1 + +static void print_uptime (void); +static void insmod (const char *filename); + +static char line[1024]; + +void +main () +{ + print_uptime (); + fprintf (stderr, "febootstrap: ext2 mini initrd starting up\n"); + + /* Create some fixed directories. */ + mkdir ("/dev", 0755); + mkdir ("/root", 0755); + mkdir ("/sys", 0755); + + /* Mount /sys. */ + if (verbose) + fprintf (stderr, "febootstrap: mounting /sys\n"); + if (mount ("sysfs", "/sys", "sysfs", 0, "") == -1) { + perror ("mount: /sys"); + exit (EXIT_FAILURE); + } + + FILE *fp = fopen ("/modules", "r"); + if (fp == NULL) { + perror ("fopen: /modules"); + exit (EXIT_FAILURE); + } + while (fgets (line, sizeof line, fp)) { + size_t n = strlen (line); + if (n > 0 && line[n-1] == '\n') + line[--n] = '\0'; + insmod (line); + } + fclose (fp); + + /* Look for the ext2 filesystem device. It's always the last + * one that was added. + * XXX More than 25 devices? + */ + char path[] = "/sys/block/xdx/dev"; + char class[3] = { 'v', 's', 'h' }; + size_t i, j; + fp = NULL; + for (i = 0; i < sizeof class; ++i) { + for (j = 'z'; j >= 'a'; --j) { + path[11] = class[i]; + path[13] = j; + fp = fopen (path, "r"); + if (fp != NULL) + goto found; + } + } + fprintf (stderr, + "febootstrap: no ext2 root device found\n" + "Please include FULL verbose output in your bug report.\n"); + exit (EXIT_FAILURE); + + found: + if (verbose) + fprintf (stderr, "febootstrap: picked %s as root device\n", path); + + fgets (line, sizeof line, fp); + int major = atoi (line); + char *p = line + strcspn (line, ":") + 1; + int minor = atoi (p); + + fclose (fp); + if (umount ("/sys") == -1) { + perror ("umount: /sys"); + exit (EXIT_FAILURE); + } + + if (verbose) + fprintf (stderr, "febootstrap: creating /dev/root as block special %d:%d\n", + major, minor); + + if (mknod ("/dev/root", S_IFBLK|0700, makedev (major, minor)) == -1) { + perror ("mknod: /dev/root"); + exit (EXIT_FAILURE); + } + + /* Mount new root and chroot to it. */ + if (verbose) + fprintf (stderr, "febootstrap: mounting new root on /root\n"); + if (mount ("/dev/root", "/root", "ext2", MS_NOATIME, "") == -1) { + perror ("mount: /root"); + exit (EXIT_FAILURE); + } + + /* Note that pivot_root won't work. See the note in + * Documentation/filesystems/ramfs-rootfs-initramfs.txt + * We could remove the old initramfs files, but let's not bother. + */ + if (verbose) + fprintf (stderr, "febootstrap: chroot\n"); + + if (chroot ("/root") == -1) { + perror ("chroot: /root"); + exit (EXIT_FAILURE); + } + + chdir ("/"); + + /* Run /init from ext2 filesystem. */ + print_uptime (); + execl ("/init", "init", NULL); + perror ("execl: /init"); + exit (EXIT_FAILURE); +} + +static void +insmod (const char *filename) +{ + if (verbose) + fprintf (stderr, "febootstrap: insmod %s\n", filename); + + pid_t pid = fork (); + if (pid == -1) { + perror ("insmod: fork"); + exit (EXIT_FAILURE); + } + + if (pid == 0) { /* Child. */ + execl ("/insmod.static", "insmod.static", filename, NULL); + perror ("insmod: execl"); + _exit (EXIT_FAILURE); + } + + /* Parent. */ + int status; + if (wait (&status) == -1 || + WEXITSTATUS (status) != 0) + perror ("insmod: wait"); + /* but ignore the error, some will be because the device is not found */ +} + +/* Print contents of /proc/uptime. */ +static void +print_uptime (void) +{ + FILE *fp = fopen ("/proc/uptime", "r"); + if (fp == NULL) { + perror ("/proc/uptime"); + return; + } + + fgets (line, sizeof line, fp); + fclose (fp); + + fprintf (stderr, "febootstrap: uptime: %s", line); +} diff --git a/helper/main.c b/helper/main.c index d7dd25a..b4359b6 100644 --- a/helper/main.c +++ b/helper/main.c @@ -72,7 +72,7 @@ usage (const char *progname) "Options:\n" " --help\n" " Display this help text and exit.\n" - " -f cpio | --format cpio\n" + " -f cpio|ext2 | --format cpio|ext2\n" " Specify output format (default: cpio).\n" " -k file | --kmods file\n" " Specify kernel module whitelist.\n" @@ -136,7 +136,7 @@ main (int argc, char *argv[]) nr_outputs = 3; /* kernel, initrd, appliance */ } else { - fprintf (stderr, "%s: incorrect output format (-f): must be cpio\n", + fprintf (stderr, "%s: incorrect output format (-f): must be cpio|ext2\n", argv[0]); exit (EXIT_FAILURE); }