File: appliance.c

package info (click to toggle)
libguestfs 1%3A1.44.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 118,932 kB
  • sloc: ansic: 458,017; ml: 51,424; sh: 13,191; java: 9,578; makefile: 7,931; cs: 6,328; haskell: 5,674; python: 3,871; perl: 3,528; erlang: 2,446; xml: 1,347; ruby: 350; pascal: 257; javascript: 157; lex: 135; yacc: 128; cpp: 10
file content (387 lines) | stat: -rw-r--r-- 12,068 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/* libguestfs
 * Copyright (C) 2010-2020 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/**
 * This file deals with building the libguestfs appliance.
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libintl.h>

#include "ignore-value.h"

#include "guestfs.h"
#include "guestfs-internal.h"

struct appliance_files {
  char *kernel;
  char *initrd;
  char *image;
};

/* Old-style appliance is going to be obsoleted. */
static const char kernel_name[] = "vmlinuz." host_cpu;
static const char initrd_name[] = "initramfs." host_cpu ".img";

static int search_appliance (guestfs_h *g, struct appliance_files *appliance);
static int dir_contains_file (guestfs_h *g, const char *dir, const char *file);
static int dir_contains_files (guestfs_h *g, const char *dir, ...);
static int contains_old_style_appliance (guestfs_h *g, const char *path, void *data);
static int contains_fixed_appliance (guestfs_h *g, const char *path, void *data);
static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, struct appliance_files *appliance);
static int run_supermin_build (guestfs_h *g, const char *lockfile, const char *appliancedir, const char *supermin_path);

/**
 * Locate or build the appliance.
 *
 * This function locates or builds the appliance as necessary,
 * handling the supermin appliance, caching of supermin-built
 * appliances, or using either a fixed or old-style appliance.
 *
 * The return value is C<0> = good, C<-1> = error.  Returned in
 * C<appliance.kernel> will be the name of the kernel to use,
 * C<appliance.initrd> the name of the initrd, C<appliance.image> the name of
 * the ext2 root filesystem.  C<appliance.image> can be C<NULL>, meaning that
 * we are using an old-style (non-ext2) appliance.  All three strings must be
 * freed by the caller.  However the referenced files themselves must I<not> be
 * deleted.
 *
 * The process is as follows:
 *
 * =over 4
 *
 * =item 1.
 *
 * Look in C<path> which contains a supermin appliance skeleton.  If no element
 * has this, skip straight to step 3.
 *
 * =item 2.
 *
 * Call C<supermin --build> to build the full appliance (if it needs
 * to be rebuilt).  If this is successful, return the full appliance.
 *
 * =item 3.
 *
 * Check C<path>, looking for a fixed appliance.  If one is found, return it.
 *
 * =item 4.
 *
 * Check C<path>, looking for an old-style appliance.  If one is found,
 * return it.
 *
 * =back
 *
 * The supermin appliance cache directory lives in
 * F<$TMPDIR/.guestfs-$UID/> and consists of up to four files:
 *
 *   $TMPDIR/.guestfs-$UID/lock            - the supermin lock file
 *   $TMPDIR/.guestfs-$UID/appliance.d/kernel - the kernel
 *   $TMPDIR/.guestfs-$UID/appliance.d/initrd - the supermin initrd
 *   $TMPDIR/.guestfs-$UID/appliance.d/root   - the appliance
 *
 * Multiple instances of libguestfs with the same UID may be racing to
 * create an appliance.  However (since supermin E<ge> 5) supermin
 * provides a I<--lock> flag and atomic update of the F<appliance.d>
 * subdirectory.
 */
int
guestfs_int_build_appliance (guestfs_h *g,
			     char **kernel_rtn,
			     char **initrd_rtn,
			     char **appliance_rtn)
{

  struct appliance_files appliance;

  memset (&appliance, 0, sizeof appliance);

  if (search_appliance (g, &appliance) != 1)
    return -1;

  /* Don't assign these until we know we're going to succeed, to avoid
   * the caller double-freeing (RHBZ#983218).
   */
  *kernel_rtn = appliance.kernel;
  *initrd_rtn = appliance.initrd;
  *appliance_rtn = appliance.image;
  return 0;
}

/**
 * Check C<path>, looking for one of appliances: supermin appliance,
 * fixed appliance or old-style appliance.  If one of the fixed appliances is
 * found, return it.  If the supermin appliance skeleton is found, build and
 * return appliance.
 *
 * Return values:
 *
 *   1 = appliance is found, returns C<appliance>,
 *   0 = appliance not found,
 *  -1 = error which aborts the launch process.
 */
static int
locate_or_build_appliance (guestfs_h *g,
                           struct appliance_files *appliance,
                           const char *path)
{
  int r;

  /* Step (1). */
  r = contains_supermin_appliance(g, path, NULL);
  if (r == 1) {
    /* Step (2): build supermin appliance. */
    r = build_supermin_appliance (g, path, appliance);
    return r < 0 ? r : 1;
  }

  /* Step (3). */
  r = contains_fixed_appliance (g, path, NULL);
  if (r == 1) {
    appliance->kernel = safe_asprintf (g, "%s/kernel", path);
    appliance->initrd = safe_asprintf (g, "%s/initrd", path);
    appliance->image = safe_asprintf(g, "%s/root", path);
    return 1;
  }

  /* Step (4). */
  r = contains_old_style_appliance (g, path, NULL);
  if (r == 1) {
    appliance->kernel = safe_asprintf(g, "%s/%s", path, kernel_name);
    appliance->initrd = safe_asprintf(g, "%s/%s", path, initrd_name);
    appliance->image = NULL;
    return 1;
  }

  return 0;
}


/**
 * Search elements of C<g-E<gt>path>, returning the first C<appliance> element
 * which matches the predicate function C<locate_or_build_appliance>.
 *
 * Return values:
 *
 *   1 = a path element matched, returns C<appliance>,
 *   0 = no path element matched,
 *  -1 = error which aborts the launch process.
 */
static int
search_appliance (guestfs_h *g, struct appliance_files *appliance)
{
  const char *pelem = g->path;

  /* Note that if g->path is an empty string, we want to check the
   * current directory (for backwards compatibility with
   * libguestfs < 1.5.4).
   */
  do {
    size_t len = strcspn (pelem, PATH_SEPARATOR);
    /* Empty element or "." means current directory. */
    CLEANUP_FREE char *path = (len == 0) ? safe_strdup (g, ".") :
                                           safe_strndup (g, pelem, len);
    int r = locate_or_build_appliance (g, appliance, path);
    if (r != 0)
      return r;       /* error or predicate matched */

    if (pelem[len] == PATH_SEPARATOR[0])
      pelem += len + 1;
    else
      pelem += len;
  } while (*pelem);

  /* Predicate didn't match on any path element. */
  error (g, _("cannot find any suitable libguestfs supermin, fixed or old-style appliance on LIBGUESTFS_PATH (search path: %s)"),
         g->path);
  return 0;
}

static int
contains_old_style_appliance (guestfs_h *g, const char *path, void *data)
{
  return dir_contains_files (g, path, kernel_name, initrd_name, NULL);
}

static int
contains_fixed_appliance (guestfs_h *g, const char *path, void *data)
{
  return dir_contains_files (g, path,
                             "README.fixed",
                             "kernel", "initrd", "root", NULL);
}

static int
contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
{
  return dir_contains_files (g, path,
                             "supermin.d/base.tar.gz",
                             "supermin.d/packages", NULL);
}

/**
 * Build supermin appliance from C<supermin_path> to
 * F<$TMPDIR/.guestfs-$UID>.
 *
 * Returns: C<0> = built or C<-1> = error (aborts launch).
 */
static int
build_supermin_appliance (guestfs_h *g,
                          const char *supermin_path,
                          struct appliance_files *appliance)
{
  CLEANUP_FREE char *cachedir = NULL, *lockfile = NULL, *appliancedir = NULL;

  cachedir = guestfs_int_lazy_make_supermin_appliance_dir (g);
  if (cachedir == NULL)
    return -1;

  appliancedir = safe_asprintf (g, "%s/appliance.d", cachedir);
  lockfile = safe_asprintf (g, "%s/lock", cachedir);

  debug (g, "begin building supermin appliance");

  /* Build the appliance if it needs to be built. */
  debug (g, "run supermin");

  if (run_supermin_build (g, lockfile, appliancedir, supermin_path) == -1)
    return -1;

  debug (g, "finished building supermin appliance");

  /* Return the appliance filenames. */
  appliance->kernel = safe_asprintf (g, "%s/kernel", appliancedir);
  appliance->initrd = safe_asprintf (g, "%s/initrd", appliancedir);
  appliance->image = safe_asprintf (g, "%s/root", appliancedir);

  /* Touch the files so they don't get deleted (as they are in /var/tmp). */
  (void) utimes (appliance->kernel, NULL);
  (void) utimes (appliance->initrd, NULL);

  /* Checking backend != "uml" is a big hack.  UML encodes the mtime
   * of the original backing file (in this case, the appliance) in the
   * COW file, and checks it when adding it to the VM.  If there are
   * multiple threads running and one touches the appliance here, it
   * will disturb the mtime and UML will give an error.
   *
   * We can get rid of this hack as soon as UML fixes the
   * ubdN=cow,original parsing bug, since we won't need to run
   * uml_mkcow separately, so there is no possible race.
   *
   * XXX
   */
  if (STRNEQ (g->backend, "uml"))
    (void) utimes (appliance->image, NULL);

  return 0;
}

/**
 * Run C<supermin --build> and tell it to generate the appliance.
 */
static int
run_supermin_build (guestfs_h *g,
                    const char *lockfile,
                    const char *appliancedir,
                    const char *supermin_path)
{
  CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g);
  int r;
#if 0                           /* not supported in supermin 5 yet XXX */
  const uid_t uid = getuid ();
  const uid_t euid = geteuid ();
  const gid_t gid = getgid ();
  const gid_t egid = getegid ();
  const int pass_u_g_args = uid != euid || gid != egid;
#endif

  guestfs_int_cmd_add_arg (cmd, SUPERMIN);
  guestfs_int_cmd_add_arg (cmd, "--build");
  if (g->verbose)
    guestfs_int_cmd_add_arg (cmd, "--verbose");
  guestfs_int_cmd_add_arg (cmd, "--if-newer");
  guestfs_int_cmd_add_arg (cmd, "--lock");
  guestfs_int_cmd_add_arg (cmd, lockfile);
#if 0
  if (pass_u_g_args) {
    guestfs_int_cmd_add_arg (cmd, "-u");
    guestfs_int_cmd_add_arg_format (cmd, "%d", euid);
    guestfs_int_cmd_add_arg (cmd, "-g");
    guestfs_int_cmd_add_arg_format (cmd, "%d", egid);
  }
#endif
  guestfs_int_cmd_add_arg (cmd, "--copy-kernel");
  guestfs_int_cmd_add_arg (cmd, "-f");
  guestfs_int_cmd_add_arg (cmd, "ext2");
  guestfs_int_cmd_add_arg (cmd, "--host-cpu");
  guestfs_int_cmd_add_arg (cmd, host_cpu);
  guestfs_int_cmd_add_arg_format (cmd, "%s/supermin.d", supermin_path);
  guestfs_int_cmd_add_arg (cmd, "-o");
  guestfs_int_cmd_add_arg (cmd, appliancedir);

  r = guestfs_int_cmd_run (cmd);
  if (r == -1)
    return -1;
  if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
    guestfs_int_external_command_failed (g, r, SUPERMIN, NULL);
    return -1;
  }

  return 0;
}

/**
 * Returns true iff C<file> is contained in C<dir>.
 */
static int
dir_contains_file (guestfs_h *g, const char *dir, const char *file)
{
  CLEANUP_FREE char *path = NULL;

  path = safe_asprintf (g, "%s/%s", dir, file);
  return access (path, F_OK) == 0;
}

/**
 * Returns true iff every listed file is contained in C<dir>.
 */
static int
dir_contains_files (guestfs_h *g, const char *dir, ...)
{
  va_list args;
  const char *file;

  va_start (args, dir);
  while ((file = va_arg (args, const char *)) != NULL) {
    if (!dir_contains_file (g, dir, file)) {
      va_end (args);
      return 0;
    }
  }
  va_end (args);
  return 1;
}