Rewrite febootstrap as a general supermin appliance building tool.
[febootstrap.git] / helper / main.c
index f5a06cc..b769bc7 100644 (file)
 #include <sys/types.h>
 #include <sys/time.h>
 #include <assert.h>
+#include <grp.h>
+#include <pwd.h>
 
 #include "error.h"
+#include "xstrtol.h"
 
 #include "helper.h"
 
 struct timeval start_t;
 int verbose = 0;
 
-static const char *format = "cpio";
-
 enum { HELP_OPTION = CHAR_MAX + 1 };
 
-static const char *options = "f:k:vV";
+static const char *options = "f:g:k:u:vV";
 static const struct option long_options[] = {
   { "help", 0, 0, HELP_OPTION },
   { "format", required_argument, 0, 'f' },
+  { "group", 0, 0, 'g' },
   { "kmods", required_argument, 0, 'k' },
+  { "user", 0, 0, 'u' },
   { "verbose", 0, 0, 'v' },
   { "version", 0, 0, 'V' },
   { 0, 0, 0, 0 }
 };
 
 static void
-usage (const char *progname)
+usage (FILE *f, const char *progname)
 {
-  printf ("%s: build the supermin appliance on the fly\n"
+  fprintf (f,
+          "%s: build the supermin appliance on the fly\n"
           "\n"
           "Usage:\n"
-          "  %s [-options] inputs [...] whitelist host_cpu kernel initrd\n"
+          "  %s [-options] inputs [...] host_cpu kernel initrd\n"
+          "  %s -f ext2 inputs [...] host_cpu kernel initrd appliance\n"
+          "  %s -f checksum inputs [...] host_cpu\n"
           "  %s --help\n"
           "  %s --version\n"
           "\n"
@@ -71,15 +77,79 @@ usage (const char *progname)
           "Options:\n"
           "  --help\n"
           "       Display this help text and exit.\n"
-          "  -f cpio | --format cpio\n"
+          "  -f cpio|ext2|checksum | --format cpio|ext2|checksum\n"
           "       Specify output format (default: cpio).\n"
+          "  -u user\n"
+          "       The user name or uid the appliance will run as. Use of this\n"
+          "       option requires root privileges.\n"
+          "  -g group\n"
+          "       The group name or gid the appliance will run as. Use of\n"
+          "       this option requires root privileges.\n"
           "  -k file | --kmods file\n"
           "       Specify kernel module whitelist.\n"
           "  --verbose | -v\n"
           "       Enable verbose messages (give multiple times for more verbosity).\n"
           "  --version | -V\n"
           "       Display version number and exit.\n",
-          progname, progname, progname, progname);
+          progname, progname, progname, progname, progname, progname);
+}
+
+static uid_t
+parseuser (const char *id, const char *progname)
+{
+
+  struct passwd *pwd;
+
+  errno = 0;
+  pwd = getpwnam (id);
+
+  if (NULL == pwd) {
+    if (errno != 0) {
+      fprintf (stderr, "Error looking up user: %m\n");
+      exit (EXIT_FAILURE);
+    }
+
+    long val;
+    int err = xstrtol (id, NULL, 10, &val, "");
+    if (err != LONGINT_OK) {
+        fprintf (stderr, "%s is not a valid user name or uid\n", id);
+        usage (stderr, progname);
+        exit (EXIT_FAILURE);
+    }
+
+    return (uid_t) val;
+  }
+
+  return pwd->pw_uid;
+}
+
+static gid_t
+parsegroup (const char *id, const char *progname)
+{
+
+  struct group *grp;
+
+  errno = 0;
+  grp = getgrnam (id);
+
+  if (NULL == grp) {
+    if (errno != 0) {
+      fprintf (stderr, "Error looking up group: %m\n");
+      exit (EXIT_FAILURE);
+    }
+
+    long val;
+    int err = xstrtol (id, NULL, 10, &val, "");
+    if (err != LONGINT_OK) {
+        fprintf (stderr, "%s is not a valid group name or gid\n", id);
+        usage (stderr, progname);
+        exit (EXIT_FAILURE);
+    }
+
+    return (gid_t) val;
+  }
+
+  return grp->gr_gid;
 }
 
 int
@@ -88,8 +158,12 @@ main (int argc, char *argv[])
   /* First thing: start the clock. */
   gettimeofday (&start_t, NULL);
 
+  const char *format = "cpio";
   const char *whitelist = NULL;
 
+  uid_t euid = geteuid ();
+  gid_t egid = getegid ();
+
   /* Command line arguments. */
   for (;;) {
     int c = getopt_long (argc, argv, options, long_options, NULL);
@@ -97,13 +171,21 @@ main (int argc, char *argv[])
 
     switch (c) {
     case HELP_OPTION:
-      usage (argv[0]);
+      usage (stdout, argv[0]);
       exit (EXIT_SUCCESS);
 
     case 'f':
       format = optarg;
       break;
 
+    case 'u':
+      euid = parseuser (optarg, argv[0]);
+      break;
+
+    case 'g':
+      egid = parsegroup (optarg, argv[0]);
+      break;
+
     case 'k':
       whitelist = optarg;
       break;
@@ -117,62 +199,122 @@ main (int argc, char *argv[])
       exit (EXIT_SUCCESS);
 
     default:
-      usage (argv[0]);
+      usage (stderr, argv[0]);
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  /* We need to set the real, not effective, uid here to work round a
+   * misfeature in bash. bash will automatically reset euid to uid when
+   * invoked. As shell is used in places by febootstrap-supermin-helper, this
+   * results in code running with varying privilege. */
+  uid_t uid = getuid ();
+  gid_t gid = getgid ();
+
+  if (uid != euid || gid != egid) {
+    if (uid != 0) {
+      fprintf (stderr, "The -u and -g options require root privileges.\n");
+      usage (stderr, argv[0]);
+      exit (EXIT_FAILURE);
+    }
+
+    /* Need to become root first because setgid and setuid require it */
+    if (seteuid (0) == -1) {
+        perror ("seteuid");
+        exit (EXIT_FAILURE);
+    }
+
+    /* Set gid and uid to command-line parameters */
+    if (setgid (egid) == -1) {
+      perror ("setgid");
+      exit (EXIT_FAILURE);
+    }
+    if (setuid (euid) == -1) {
+      perror ("setuid");
       exit (EXIT_FAILURE);
     }
   }
 
   /* Select the correct writer module. */
   struct writer *writer;
+  int nr_outputs;
 
-  if (strcmp (format, "cpio") == 0)
+  if (strcmp (format, "cpio") == 0) {
     writer = &cpio_writer;
+    nr_outputs = 2;             /* kernel and appliance (== initrd) */
+  }
+  else if (strcmp (format, "ext2") == 0) {
+    writer = &ext2_writer;
+    nr_outputs = 3;             /* kernel, initrd, appliance */
+  }
+  else if (strcmp (format, "checksum") == 0) {
+    writer = &checksum_writer;
+    nr_outputs = 0;             /* (none) */
+  }
   else {
-    fprintf (stderr, "%s: incorrect output format (-f): must be cpio\n",
+    fprintf (stderr,
+             "%s: incorrect output format (-f): must be cpio|ext2|checksum\n",
              argv[0]);
     exit (EXIT_FAILURE);
   }
 
+  /* [optind .. optind+nr_inputs-1] hostcpu [argc-nr_outputs-1 .. argc-1]
+   * <----     nr_inputs      ---->    1    <----    nr_outputs     ---->
+   */
   char **inputs = &argv[optind];
-  int nr_inputs = argc - optind - 3;
+  int nr_inputs = argc - nr_outputs - 1 - optind;
+  char **outputs = &argv[optind+nr_inputs+1];
+  /*assert (outputs [nr_outputs] == NULL);
+    assert (inputs [nr_inputs + 1 + nr_outputs] == NULL);*/
 
   if (nr_inputs < 1) {
-    usage (argv[0]);
+    fprintf (stderr, "%s: not enough files specified on the command line\n",
+             argv[0]);
     exit (EXIT_FAILURE);
   }
 
   /* See: https://bugzilla.redhat.com/show_bug.cgi?id=558593 */
-  const char *hostcpu = argv[argc-3];
+  const char *hostcpu = outputs[-1];
 
   /* Output files. */
-  const char *kernel = argv[argc-2];
-  const char *appliance = argv[argc-1];
+  const char *kernel = NULL, *initrd = NULL, *appliance = NULL;
+  if (nr_outputs > 0)
+    kernel = outputs[0];
+  if (nr_outputs > 1)
+    initrd = appliance = outputs[1];
+  if (nr_outputs > 2)
+    appliance = outputs[2];
 
   if (verbose) {
     print_timestamped_message ("whitelist = %s, "
                                "host_cpu = %s, "
                                "kernel = %s, "
+                               "initrd = %s, "
                                "appliance = %s",
                                whitelist ? : "(not specified)",
-                               hostcpu, kernel, appliance);
+                               hostcpu, kernel, initrd, appliance);
     int i;
     for (i = 0; i < nr_inputs; ++i)
       print_timestamped_message ("inputs[%d] = %s", i, inputs[i]);
   }
 
   /* Remove the output files if they exist. */
-  unlink (kernel);
-  unlink (appliance);
+  if (kernel)
+    unlink (kernel);
+  if (initrd)
+    unlink (initrd);
+  if (appliance && initrd != appliance)
+    unlink (appliance);
 
   /* Create kernel output file. */
-  const char *modpath;
-  modpath = create_kernel (hostcpu, kernel);
+  const char *modpath = create_kernel (hostcpu, kernel);
 
   if (verbose)
     print_timestamped_message ("finished creating kernel");
 
   /* Create the appliance. */
-  create_appliance (inputs, nr_inputs, whitelist, modpath, appliance, writer);
+  create_appliance (hostcpu, inputs, nr_inputs, whitelist, modpath,
+                    initrd, appliance, writer);
 
   if (verbose)
     print_timestamped_message ("finished creating appliance");