Add TODO file.
[febootstrap.git] / helper / main.c
1 /* febootstrap-supermin-helper reimplementation in C.
2  * Copyright (C) 2009-2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <limits.h>
28 #include <sys/types.h>
29 #include <sys/time.h>
30 #include <assert.h>
31 #include <grp.h>
32 #include <pwd.h>
33
34 #include "error.h"
35 #include "xstrtol.h"
36
37 #include "helper.h"
38
39 struct timeval start_t;
40 int verbose = 0;
41
42 enum { HELP_OPTION = CHAR_MAX + 1 };
43
44 static const char *options = "f:g:k:u:vV";
45 static const struct option long_options[] = {
46   { "help", 0, 0, HELP_OPTION },
47   { "format", required_argument, 0, 'f' },
48   { "group", 0, 0, 'g' },
49   { "kmods", required_argument, 0, 'k' },
50   { "user", 0, 0, 'u' },
51   { "verbose", 0, 0, 'v' },
52   { "version", 0, 0, 'V' },
53   { 0, 0, 0, 0 }
54 };
55
56 static void
57 usage (FILE *f, const char *progname)
58 {
59   fprintf (f,
60           "%s: build the supermin appliance on the fly\n"
61           "\n"
62           "Usage:\n"
63           "  %s [-options] inputs [...] host_cpu kernel initrd\n"
64           "  %s -f ext2 inputs [...] host_cpu kernel initrd appliance\n"
65           "  %s -f checksum inputs [...] host_cpu\n"
66           "  %s --help\n"
67           "  %s --version\n"
68           "\n"
69           "This script is used by febootstrap to build the supermin appliance\n"
70           "(kernel and initrd output files).  You should NOT need to run this\n"
71           "program directly except if you are debugging tricky supermin\n"
72           "appliance problems.\n"
73           "\n"
74           "NB: The kernel and initrd parameters are OUTPUT parameters.  If\n"
75           "those files exist, they are overwritten by the output.\n"
76           "\n"
77           "Options:\n"
78           "  --help\n"
79           "       Display this help text and exit.\n"
80           "  -f cpio|ext2|checksum | --format cpio|ext2|checksum\n"
81           "       Specify output format (default: cpio).\n"
82           "  -u user\n"
83           "       The user name or uid the appliance will run as. Use of this\n"
84           "       option requires root privileges.\n"
85           "  -g group\n"
86           "       The group name or gid the appliance will run as. Use of\n"
87           "       this option requires root privileges.\n"
88           "  -k file | --kmods file\n"
89           "       Specify kernel module whitelist.\n"
90           "  --verbose | -v\n"
91           "       Enable verbose messages (give multiple times for more verbosity).\n"
92           "  --version | -V\n"
93           "       Display version number and exit.\n",
94           progname, progname, progname, progname, progname, progname);
95 }
96
97 static uid_t
98 parseuser (const char *id, const char *progname)
99 {
100
101   struct passwd *pwd;
102
103   errno = 0;
104   pwd = getpwnam (id);
105
106   if (NULL == pwd) {
107     if (errno != 0) {
108       fprintf (stderr, "Error looking up user: %m\n");
109       exit (EXIT_FAILURE);
110     }
111
112     long val;
113     int err = xstrtol (id, NULL, 10, &val, "");
114     if (err != LONGINT_OK) {
115         fprintf (stderr, "%s is not a valid user name or uid\n", id);
116         usage (stderr, progname);
117         exit (EXIT_FAILURE);
118     }
119
120     return (uid_t) val;
121   }
122
123   return pwd->pw_uid;
124 }
125
126 static gid_t
127 parsegroup (const char *id, const char *progname)
128 {
129
130   struct group *grp;
131
132   errno = 0;
133   grp = getgrnam (id);
134
135   if (NULL == grp) {
136     if (errno != 0) {
137       fprintf (stderr, "Error looking up group: %m\n");
138       exit (EXIT_FAILURE);
139     }
140
141     long val;
142     int err = xstrtol (id, NULL, 10, &val, "");
143     if (err != LONGINT_OK) {
144         fprintf (stderr, "%s is not a valid group name or gid\n", id);
145         usage (stderr, progname);
146         exit (EXIT_FAILURE);
147     }
148
149     return (gid_t) val;
150   }
151
152   return grp->gr_gid;
153 }
154
155 int
156 main (int argc, char *argv[])
157 {
158   /* First thing: start the clock. */
159   gettimeofday (&start_t, NULL);
160
161   const char *format = "cpio";
162   const char *whitelist = NULL;
163
164   uid_t euid = geteuid ();
165   gid_t egid = getegid ();
166
167   /* Command line arguments. */
168   for (;;) {
169     int c = getopt_long (argc, argv, options, long_options, NULL);
170     if (c == -1) break;
171
172     switch (c) {
173     case HELP_OPTION:
174       usage (stdout, argv[0]);
175       exit (EXIT_SUCCESS);
176
177     case 'f':
178       format = optarg;
179       break;
180
181     case 'u':
182       euid = parseuser (optarg, argv[0]);
183       break;
184
185     case 'g':
186       egid = parsegroup (optarg, argv[0]);
187       break;
188
189     case 'k':
190       whitelist = optarg;
191       break;
192
193     case 'v':
194       verbose++;
195       break;
196
197     case 'V':
198       printf (PACKAGE_NAME " " PACKAGE_VERSION "\n");
199       exit (EXIT_SUCCESS);
200
201     default:
202       usage (stderr, argv[0]);
203       exit (EXIT_FAILURE);
204     }
205   }
206
207   /* We need to set the real, not effective, uid here to work round a
208    * misfeature in bash. bash will automatically reset euid to uid when
209    * invoked. As shell is used in places by febootstrap-supermin-helper, this
210    * results in code running with varying privilege. */
211   uid_t uid = getuid ();
212   gid_t gid = getgid ();
213
214   if (uid != euid || gid != egid) {
215     if (uid != 0) {
216       fprintf (stderr, "The -u and -g options require root privileges.\n");
217       usage (stderr, argv[0]);
218       exit (EXIT_FAILURE);
219     }
220
221     /* Need to become root first because setgid and setuid require it */
222     if (seteuid (0) == -1) {
223         perror ("seteuid");
224         exit (EXIT_FAILURE);
225     }
226
227     /* Set gid and uid to command-line parameters */
228     if (setgid (egid) == -1) {
229       perror ("setgid");
230       exit (EXIT_FAILURE);
231     }
232     if (setuid (euid) == -1) {
233       perror ("setuid");
234       exit (EXIT_FAILURE);
235     }
236   }
237
238   /* Select the correct writer module. */
239   struct writer *writer;
240   int nr_outputs;
241
242   if (strcmp (format, "cpio") == 0) {
243     writer = &cpio_writer;
244     nr_outputs = 2;             /* kernel and appliance (== initrd) */
245   }
246   else if (strcmp (format, "ext2") == 0) {
247     writer = &ext2_writer;
248     nr_outputs = 3;             /* kernel, initrd, appliance */
249   }
250   else if (strcmp (format, "checksum") == 0) {
251     writer = &checksum_writer;
252     nr_outputs = 0;             /* (none) */
253   }
254   else {
255     fprintf (stderr,
256              "%s: incorrect output format (-f): must be cpio|ext2|checksum\n",
257              argv[0]);
258     exit (EXIT_FAILURE);
259   }
260
261   /* [optind .. optind+nr_inputs-1] hostcpu [argc-nr_outputs-1 .. argc-1]
262    * <----     nr_inputs      ---->    1    <----    nr_outputs     ---->
263    */
264   char **inputs = &argv[optind];
265   int nr_inputs = argc - nr_outputs - 1 - optind;
266   char **outputs = &argv[optind+nr_inputs+1];
267   /*assert (outputs [nr_outputs] == NULL);
268     assert (inputs [nr_inputs + 1 + nr_outputs] == NULL);*/
269
270   if (nr_inputs < 1) {
271     fprintf (stderr, "%s: not enough files specified on the command line\n",
272              argv[0]);
273     exit (EXIT_FAILURE);
274   }
275
276   /* See: https://bugzilla.redhat.com/show_bug.cgi?id=558593 */
277   const char *hostcpu = outputs[-1];
278
279   /* Output files. */
280   const char *kernel = NULL, *initrd = NULL, *appliance = NULL;
281   if (nr_outputs > 0)
282     kernel = outputs[0];
283   if (nr_outputs > 1)
284     initrd = appliance = outputs[1];
285   if (nr_outputs > 2)
286     appliance = outputs[2];
287
288   if (verbose) {
289     print_timestamped_message ("whitelist = %s, "
290                                "host_cpu = %s, "
291                                "kernel = %s, "
292                                "initrd = %s, "
293                                "appliance = %s",
294                                whitelist ? : "(not specified)",
295                                hostcpu, kernel, initrd, appliance);
296     int i;
297     for (i = 0; i < nr_inputs; ++i)
298       print_timestamped_message ("inputs[%d] = %s", i, inputs[i]);
299   }
300
301   /* Remove the output files if they exist. */
302   if (kernel)
303     unlink (kernel);
304   if (initrd)
305     unlink (initrd);
306   if (appliance && initrd != appliance)
307     unlink (appliance);
308
309   /* Create kernel output file. */
310   const char *modpath = create_kernel (hostcpu, kernel);
311
312   if (verbose)
313     print_timestamped_message ("finished creating kernel");
314
315   /* Create the appliance. */
316   create_appliance (hostcpu, inputs, nr_inputs, whitelist, modpath,
317                     initrd, appliance, writer);
318
319   if (verbose)
320     print_timestamped_message ("finished creating appliance");
321
322   exit (EXIT_SUCCESS);
323 }