1 /* febootstrap-supermin-helper reimplementation in C.
2 * Copyright (C) 2009-2010 Red Hat Inc.
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.
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.
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.
19 /* This script builds the supermin appliance on the fly each
20 * time the appliance runs.
22 * *NOTE*: This program is designed to be very short-lived, and so we
23 * don't normally bother to free up any memory that we allocate.
24 * That's not completely true - we free up stuff if it's obvious and
25 * easy to free up, and ignore the rest.
43 #include <sys/types.h>
49 #include "filevercmp.h"
51 #include "full-write.h"
55 #include "xvasprintf.h"
57 /* Directory containing candidate kernels. We could make this
58 * configurable at some point.
60 #define KERNELDIR "/boot"
61 #define MODULESDIR "/lib/modules"
63 /* Buffer size used in copy operations throughout. Large for
64 * greatest efficiency.
66 #define BUFFER_SIZE 65536
68 static struct timeval start_t;
69 static int verbose = 0;
71 static void print_timestamped_message (const char *fs, ...);
72 static const char *create_kernel (const char *hostcpu, const char *kernel);
73 static void create_appliance (char **inputs, int nr_inputs, const char *whitelist, const char *modpath, const char *initrd);
75 enum { HELP_OPTION = CHAR_MAX + 1 };
77 static const char *options = "k:vV";
78 static const struct option long_options[] = {
79 { "help", 0, 0, HELP_OPTION },
80 { "kmods", required_argument, 0, 'k' },
81 { "verbose", 0, 0, 'v' },
82 { "version", 0, 0, 'V' },
87 usage (const char *progname)
89 printf ("%s: build the supermin appliance on the fly\n"
92 " %s [-options] inputs [...] whitelist host_cpu kernel initrd\n"
96 "This script is used by febootstrap to build the supermin appliance\n"
97 "(kernel and initrd output files). You should NOT need to run this\n"
98 "program directly except if you are debugging tricky supermin\n"
99 "appliance problems.\n"
101 "NB: The kernel and initrd parameters are OUTPUT parameters. If\n"
102 "those files exist, they are overwritten by the output.\n"
106 " Display this help text and exit.\n"
107 " -k file | --kmods file\n"
108 " Specify kernel module whitelist.\n"
110 " Enable verbose messages (give multiple times for more verbosity).\n"
112 " Display version number and exit.\n",
113 progname, progname, progname, progname);
117 main (int argc, char *argv[])
119 /* First thing: start the clock. */
120 gettimeofday (&start_t, NULL);
122 const char *whitelist = NULL;
124 /* Command line arguments. */
126 int c = getopt_long (argc, argv, options, long_options, NULL);
143 printf (PACKAGE_NAME " " PACKAGE_VERSION "\n");
152 char **inputs = &argv[optind];
153 int nr_inputs = argc - optind - 3;
160 /* See: https://bugzilla.redhat.com/show_bug.cgi?id=558593 */
161 const char *hostcpu = argv[argc-3];
164 const char *kernel = argv[argc-2];
165 const char *initrd = argv[argc-1];
168 print_timestamped_message ("whitelist = %s, "
172 whitelist ? : "(not specified)",
173 hostcpu, kernel, initrd);
175 for (i = 0; i < nr_inputs; ++i)
176 print_timestamped_message ("inputs[%d] = %s", i, inputs[i]);
179 /* Remove the output files if they exist. */
183 /* Create kernel output file. */
185 modpath = create_kernel (hostcpu, kernel);
188 print_timestamped_message ("finished creating kernel");
190 /* Create the appliance. */
191 create_appliance (inputs, nr_inputs, whitelist, modpath, initrd);
194 print_timestamped_message ("finished creating appliance");
199 /* Compute Y - X and return the result in milliseconds.
200 * Approximately the same as this code:
201 * http://www.mpp.mpg.de/~huber/util/timevaldiff.c
204 timeval_diff (const struct timeval *x, const struct timeval *y)
208 msec = (y->tv_sec - x->tv_sec) * 1000;
209 msec += (y->tv_usec - x->tv_usec) / 1000;
214 print_timestamped_message (const char *fs, ...)
217 gettimeofday (&tv, NULL);
224 err = vasprintf (&msg, fs, args);
229 fprintf (stderr, "supermin helper [%05" PRIi64 "ms] %s\n",
230 timeval_diff (&start_t, &tv), msg);
235 static char **read_dir (const char *dir);
236 static char **filter_fnmatch (char **strings, const char *patt, int flags);
237 static char **filter_notmatching_substring (char **strings, const char *sub);
238 static void sort (char **strings, int (*compare) (const void *, const void *));
239 static int isdir (const char *path);
242 reverse_filevercmp (const void *p1, const void *p2)
244 const char *s1 = * (char * const *) p1;
245 const char *s2 = * (char * const *) p2;
247 /* Note, arguments are reversed to achieve a reverse sort. */
248 return filevercmp (s2, s1);
251 /* Create the kernel. This chooses an appropriate kernel and makes a
254 * Look for the most recent kernel named vmlinuz-*.<arch>* which has a
255 * corresponding directory in /lib/modules/. If the architecture is
256 * x86, look for any x86 kernel.
258 * RHEL 5 didn't append the arch to the kernel name, so look for
259 * kernels without arch second.
261 * If no suitable kernel can be found, exit with an error.
263 * This function returns the module path (ie. /lib/modules/<version>).
266 create_kernel (const char *hostcpu, const char *kernel)
268 char **all_files = read_dir (KERNELDIR);
270 /* In original: ls -1dvr /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen */
272 if (hostcpu[0] == 'i' && hostcpu[2] == '8' && hostcpu[3] == '6' &&
274 patt = "vmlinuz-*.i?86*";
276 patt = xasprintf ("vmlinuz-*.%s*", hostcpu);
279 candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
280 candidates = filter_notmatching_substring (candidates, "xen");
282 if (candidates[0] == NULL) {
283 /* In original: ls -1dvr /boot/vmlinuz-* 2>/dev/null | grep -v xen */
285 candidates = filter_fnmatch (all_files, patt, FNM_NOESCAPE);
286 candidates = filter_notmatching_substring (candidates, "xen");
288 if (candidates[0] == NULL)
292 sort (candidates, reverse_filevercmp);
294 /* Choose the first candidate which has a corresponding /lib/modules
298 for (i = 0; candidates[i] != NULL; ++i) {
300 fprintf (stderr, "candidate kernel: " KERNELDIR "/%s\n", candidates[i]);
302 /* Ignore "vmlinuz-" at the beginning of the kernel name. */
303 const char *version = &candidates[i][8];
305 /* /lib/modules/<version> */
306 char *modpath = xasprintf (MODULESDIR "/%s", version);
309 fprintf (stderr, "checking modpath %s is a directory\n", modpath);
311 if (isdir (modpath)) {
313 fprintf (stderr, "picked %s because modpath %s exists\n",
314 candidates[i], modpath);
316 char *tmp = xasprintf (KERNELDIR "/%s", candidates[i]);
319 fprintf (stderr, "creating symlink %s -> %s\n", kernel, tmp);
321 if (symlink (tmp, kernel) == -1)
322 error (EXIT_FAILURE, errno, "symlink kernel");
330 /* Print more diagnostics here than the old script did. */
333 "febootstrap-supermin-helper: failed to find a suitable kernel.\n"
334 "I looked for kernels in " KERNELDIR " and modules in " MODULESDIR
336 "If this is a Xen guest, and you only have Xen domU kernels\n"
337 "installed, try installing a fullvirt kernel (only for\n"
338 "febootstrap use, you shouldn't boot the Xen guest with it).\n");
342 static void iterate_inputs (char **inputs, int nr_inputs);
343 static void iterate_input_directory (const char *dirname, int dirfd);
344 static void write_kernel_modules (const char *whitelist, const char *modpath);
345 static void write_hostfiles (const char *hostfiles_file);
346 static void write_to_fd (const void *buffer, size_t len);
347 static void write_file_to_fd (const char *filename);
348 static void write_file_len_to_fd (const char *filename, size_t len);
349 static void write_padding (size_t len);
350 static char **load_file (const char *filename);
351 static void cpio_append_fts_entry (FTSENT *entry);
352 static void cpio_append_stat (const char *filename, struct stat *);
353 static void cpio_append (const char *filename);
354 static void cpio_append_trailer (void);
356 static int out_fd = -1;
357 static off_t out_offset = 0;
359 /* Create the appliance.
361 * The initrd consists of these components concatenated together:
363 * (1) The base skeleton appliance that we constructed at build time.
364 * format = plain cpio
365 * (2) The host files which match wildcards in *.supermin.hostfiles.
366 * input format = plain text, output format = plain cpio
367 * (3) The modules from modpath which are on the module whitelist.
368 * output format = plain cpio
370 * The original shell script used the external cpio program to create
371 * parts (2) and (3), but we have decided it's going to be faster if
372 * we just write out the data outselves. The reasons are that
373 * external cpio is slow (particularly when used with SELinux because
374 * it does 512 byte reads), and the format that we're writing is
375 * narrow and well understood, because we only care that the Linux
376 * kernel can read it.
378 * This version contains some improvements over the C version written
379 * for libguestfs, in that we can have multiple base images (or
380 * hostfiles) or use a directory to store these files.
383 create_appliance (char **inputs, int nr_inputs,
384 const char *whitelist,
388 out_fd = open (initrd, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0644);
390 error (EXIT_FAILURE, errno, "open: %s", initrd);
393 iterate_inputs (inputs, nr_inputs);
395 /* Kernel modules (3). */
396 write_kernel_modules (whitelist, modpath);
398 cpio_append_trailer ();
400 /* Finish off and close output file. */
401 if (close (out_fd) == -1)
402 error (EXIT_FAILURE, errno, "close: %s", initrd);
405 /* Iterate over the inputs to find out what they are, visiting
406 * directories if specified.
409 iterate_inputs (char **inputs, int nr_inputs)
412 for (i = 0; i < nr_inputs; ++i) {
414 print_timestamped_message ("visiting %s", inputs[i]);
416 int fd = open (inputs[i], O_RDONLY);
418 error (EXIT_FAILURE, errno, "open: %s", inputs[i]);
421 if (fstat (fd, &statbuf) == -1)
422 error (EXIT_FAILURE, errno, "fstat: %s", inputs[i]);
425 if (S_ISDIR (statbuf.st_mode))
426 iterate_input_directory (inputs[i], fd);
427 else if (S_ISREG (statbuf.st_mode)) {
428 /* Is it a cpio file? */
430 if (read (fd, buf, 6) == 6 && memcmp (buf, "070701", 6) == 0)
431 /* Yes, a cpio file. This is a skeleton appliance, case (1). */
432 write_file_to_fd (inputs[i]);
434 /* No, must be hostfiles, case (2). */
435 write_hostfiles (inputs[i]);
438 error (EXIT_FAILURE, 0, "%s: input is not a regular file or directory",
446 iterate_input_directory (const char *dirname, int dirfd)
449 strcpy (path, dirname);
450 size_t len = strlen (dirname);
453 char *inputs[] = { path };
455 DIR *dir = fdopendir (dirfd);
457 error (EXIT_FAILURE, errno, "fdopendir: %s", dirname);
460 while ((errno = 0, d = readdir (dir)) != NULL) {
461 if (d->d_name[0] == '.') /* ignore ., .. and any hidden files. */
464 strcpy (&path[len], d->d_name);
465 iterate_inputs (inputs, 1);
469 error (EXIT_FAILURE, errno, "readdir: %s", dirname);
471 if (closedir (dir) == -1)
472 error (EXIT_FAILURE, errno, "closedir: %s", dirname);
475 /* Copy kernel modules.
477 * Find every file under modpath.
479 * Exclude all *.ko files, *except* ones which match names in
480 * the whitelist (which may contain wildcards). Include all
483 * Add chosen files to the output.
485 * whitelist_file may be NULL, to include ALL kernel modules.
488 write_kernel_modules (const char *whitelist_file, const char *modpath)
490 char **whitelist = NULL;
491 if (whitelist_file != NULL)
492 whitelist = load_file (whitelist_file);
494 char *paths[2] = { (char *) modpath, NULL };
495 FTS *fts = fts_open (paths, FTS_COMFOLLOW|FTS_PHYSICAL, NULL);
497 error (EXIT_FAILURE, errno, "write_kernel_modules: fts_open: %s", modpath);
501 FTSENT *entry = fts_read (fts);
502 if (entry == NULL && errno != 0)
503 error (EXIT_FAILURE, errno, "write_kernel_modules: fts_read: %s", modpath);
507 /* Ignore directories being visited in post-order. */
508 if (entry->fts_info & FTS_DP)
511 /* Is it a *.ko file? */
512 if (entry->fts_namelen >= 3 &&
513 entry->fts_name[entry->fts_namelen-3] == '.' &&
514 entry->fts_name[entry->fts_namelen-2] == 'k' &&
515 entry->fts_name[entry->fts_namelen-1] == 'o') {
517 /* Is it a *.ko file which is on the whitelist? */
519 for (j = 0; whitelist[j] != NULL; ++j) {
521 r = fnmatch (whitelist[j], entry->fts_name, 0);
523 /* It's on the whitelist, so include it. */
525 fprintf (stderr, "including kernel module %s (matches whitelist entry %s)\n",
526 entry->fts_name, whitelist[j]);
527 cpio_append_fts_entry (entry);
529 } else if (r != FNM_NOMATCH)
530 error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
531 whitelist[j], entry->fts_name, 0, r);
533 } else { /* whitelist == NULL, always include */
535 fprintf (stderr, "including kernel module %s\n", entry->fts_name);
536 cpio_append_fts_entry (entry);
539 /* It's some other sort of file, or a directory, always include. */
540 cpio_append_fts_entry (entry);
543 if (fts_close (fts) == -1)
544 error (EXIT_FAILURE, errno, "write_kernel_modules: fts_close: %s", modpath);
547 /* Copy the host files.
549 * Read the list of entries in hostfiles (which may contain
550 * wildcards). Look them up in the filesystem, and add those files
551 * that exist. Ignore any files that don't exist or are not readable.
554 write_hostfiles (const char *hostfiles_file)
556 char **hostfiles = load_file (hostfiles_file);
558 /* Hostfiles list can contain "." before each path - ignore it.
559 * It also contains each directory name before we enter it. But
560 * we don't read that until we see a wildcard for that directory.
563 for (i = 0; hostfiles[i] != NULL; ++i) {
564 char *hostfile = hostfiles[i];
565 if (hostfile[0] == '.')
570 /* Is it a wildcard? */
571 if (strchr (hostfile, '*') || strchr (hostfile, '?')) {
572 char *dirname = xstrdup (hostfile);
573 char *patt = strrchr (dirname, '/');
577 char **files = read_dir (dirname);
578 files = filter_fnmatch (files, patt, FNM_NOESCAPE);
580 /* Add matching files. */
581 for (j = 0; files[j] != NULL; ++j) {
582 char *tmp = xasprintf ("%s/%s", dirname, files[j]);
585 fprintf (stderr, "including host file %s (matches %s)\n", tmp, patt);
592 /* Else does this file/directory/whatever exist? */
593 else if (lstat (hostfile, &statbuf) == 0) {
595 fprintf (stderr, "including host file %s (directly referenced)\n",
598 cpio_append_stat (hostfile, &statbuf);
599 } /* Ignore files that don't exist. */
604 /* Helper functions. */
607 add_string (char ***argv, size_t *n_used, size_t *n_alloc, const char *str)
611 if (*n_used >= *n_alloc)
612 *argv = x2nrealloc (*argv, n_alloc, sizeof (char *));
615 new_str = xstrdup (str);
619 (*argv)[*n_used] = new_str;
625 count_strings (char *const *argv)
629 for (argc = 0; argv[argc] != NULL; ++argc)
640 dir_cache_hash (void const *x, size_t table_size)
642 struct dir_cache const *p = x;
643 return hash_pjw (p->path, table_size);
647 dir_cache_compare (void const *x, void const *y)
649 struct dir_cache const *p = x;
650 struct dir_cache const *q = y;
651 return strcmp (p->path, q->path) == 0;
654 /* Read a directory into a list of strings.
656 * Previously looked up directories are cached and returned quickly,
657 * saving some considerable amount of time compared to reading the
658 * directory over again. However this means you really must not
659 * alter the array of strings that are returned.
661 * Returns an empty list if the directory cannot be opened.
664 read_dir (const char *name)
666 static Hash_table *ht = NULL;
669 ht = hash_initialize (1024, NULL, dir_cache_hash, dir_cache_compare, NULL);
671 struct dir_cache key = { .path = (char *) name };
672 struct dir_cache *p = hash_lookup (ht, &key);
677 size_t n_used = 0, n_alloc = 0;
679 DIR *dir = opendir (name);
681 /* If it fails to open, that's OK, skip to the end. */
688 struct dirent *d = readdir (dir);
691 /* But if it fails here, after opening and potentially reading
692 * part of the directory, that's a proper failure - inform the
695 error (EXIT_FAILURE, errno, "%s", name);
699 add_string (&files, &n_used, &n_alloc, d->d_name);
702 if (closedir (dir) == -1)
703 error (EXIT_FAILURE, errno, "closedir: %s", name);
706 /* NULL-terminate the array. */
707 add_string (&files, &n_used, &n_alloc, NULL);
709 /* Add it to the hash for next time. */
710 p = xmalloc (sizeof *p);
711 p->path = (char *) name;
713 p = hash_insert (ht, p);
719 /* Filter a list of strings and return only those matching the wildcard. */
721 filter_fnmatch (char **strings, const char *patt, int flags)
724 size_t n_used = 0, n_alloc = 0;
727 for (i = 0; strings[i] != NULL; ++i) {
728 r = fnmatch (patt, strings[i], flags);
730 add_string (&out, &n_used, &n_alloc, strings[i]);
731 else if (r != FNM_NOMATCH)
732 error (EXIT_FAILURE, 0, "internal error: fnmatch ('%s', '%s', %d) returned unexpected non-zero value %d\n",
733 patt, strings[i], flags, r);
736 add_string (&out, &n_used, &n_alloc, NULL);
740 /* Filter a list of strings and return only those which DON'T contain sub. */
742 filter_notmatching_substring (char **strings, const char *sub)
745 size_t n_used = 0, n_alloc = 0;
748 for (i = 0; strings[i] != NULL; ++i) {
749 if (strstr (strings[i], sub) == NULL)
750 add_string (&out, &n_used, &n_alloc, strings[i]);
753 add_string (&out, &n_used, &n_alloc, NULL);
757 /* Sort a list of strings, in place, with the comparison function supplied. */
759 sort (char **strings, int (*compare) (const void *, const void *))
761 qsort (strings, count_strings (strings), sizeof (char *), compare);
764 /* Return true iff path exists and is a directory. This version
768 isdir (const char *path)
772 if (stat (path, &statbuf) == -1)
775 return S_ISDIR (statbuf.st_mode);
778 /* Copy contents of buffer to out_fd and keep out_offset correct. */
780 write_to_fd (const void *buffer, size_t len)
782 if (full_write (out_fd, buffer, len) != len)
783 error (EXIT_FAILURE, errno, "write");
787 /* Copy contents of file to out_fd. */
789 write_file_to_fd (const char *filename)
791 char buffer[BUFFER_SIZE];
796 fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
798 fd2 = open (filename, O_RDONLY);
800 error (EXIT_FAILURE, errno, "open: %s", filename);
802 r = read (fd2, buffer, sizeof buffer);
806 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
808 error (EXIT_FAILURE, errno, "read: %s", filename);
810 write_to_fd (buffer, r);
813 if (close (fd2) == -1)
814 error (EXIT_FAILURE, errno, "close: %s", filename);
817 /* Copy file of given length to output, and fail if the file has
821 write_file_len_to_fd (const char *filename, size_t len)
823 char buffer[BUFFER_SIZE];
827 fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
829 int fd2 = open (filename, O_RDONLY);
831 error (EXIT_FAILURE, errno, "open: %s", filename);
833 ssize_t r = read (fd2, buffer, sizeof buffer);
837 if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
839 error (EXIT_FAILURE, errno, "read: %s", filename);
841 write_to_fd (buffer, r);
844 error (EXIT_FAILURE, 0, "write_file_len_to_fd: %s: file has increased in size\n", filename);
847 if (close (fd2) == -1)
848 error (EXIT_FAILURE, errno, "close: %s", filename);
851 error (EXIT_FAILURE, 0, "febootstrap-supermin-helper: write_file_len_to_fd: %s: file has changed size\n", filename);
854 /* Load in a file, returning a list of lines. */
856 load_file (const char *filename)
859 size_t n_used = 0, n_alloc = 0;
862 fp = fopen (filename, "r");
864 error (EXIT_FAILURE, errno, "fopen: %s", filename);
867 while (fgets (line, sizeof line, fp)) {
868 size_t len = strlen (line);
869 if (len > 0 && line[len-1] == '\n')
871 add_string (&lines, &n_used, &n_alloc, line);
874 add_string (&lines, &n_used, &n_alloc, NULL);
878 /* Append the file pointed to by FTSENT to the cpio output. */
880 cpio_append_fts_entry (FTSENT *entry)
882 if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK)
883 cpio_append (entry->fts_path);
885 cpio_append_stat (entry->fts_path, entry->fts_statp);
888 /* Append the file named 'filename' to the cpio output. */
890 cpio_append (const char *filename)
894 if (lstat (filename, &statbuf) == -1)
895 error (EXIT_FAILURE, errno, "lstat: %s", filename);
896 cpio_append_stat (filename, &statbuf);
899 /* Append the file to the cpio output. */
900 #define PADDING(len) ((((len) + 3) & ~3) - (len))
902 #define CPIO_HEADER_LEN (6 + 13*8)
905 cpio_append_stat (const char *filename, struct stat *statbuf)
907 const char *orig_filename = filename;
909 if (*filename == '/')
911 if (*filename == '\0')
915 fprintf (stderr, "cpio_append_stat %s 0%o -> %d\n",
916 orig_filename, statbuf->st_mode, out_fd);
918 /* Regular files and symlinks are the only ones that have a "body"
919 * in this cpio entry.
921 int has_body = S_ISREG (statbuf->st_mode) || S_ISLNK (statbuf->st_mode);
923 size_t len = strlen (filename) + 1;
925 char header[CPIO_HEADER_LEN + 1];
926 snprintf (header, sizeof header,
930 "%08X" "%08X" /* uid, gid */
933 "%08X" /* file length */
934 "%08X" "%08X" /* device holding file major, minor */
935 "%08X" "%08X" /* for specials, device major, minor */
936 "%08X" /* name length (including \0 byte) */
937 "%08X", /* checksum (not used by the kernel) */
938 (unsigned) statbuf->st_ino, statbuf->st_mode,
939 statbuf->st_uid, statbuf->st_gid,
940 (unsigned) statbuf->st_nlink, (unsigned) statbuf->st_mtime,
941 has_body ? (unsigned) statbuf->st_size : 0,
942 major (statbuf->st_dev), minor (statbuf->st_dev),
943 major (statbuf->st_rdev), minor (statbuf->st_rdev),
946 /* Write the header. */
947 write_to_fd (header, CPIO_HEADER_LEN);
949 /* Follow with the filename, and pad it. */
950 write_to_fd (filename, len);
951 size_t padding_len = PADDING (CPIO_HEADER_LEN + len);
952 write_padding (padding_len);
954 /* Follow with the file or symlink content, and pad it. */
956 if (S_ISREG (statbuf->st_mode))
957 write_file_len_to_fd (orig_filename, statbuf->st_size);
958 else if (S_ISLNK (statbuf->st_mode)) {
960 if (readlink (orig_filename, tmp, sizeof tmp) == -1)
961 error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
962 write_to_fd (tmp, statbuf->st_size);
965 padding_len = PADDING (statbuf->st_size);
966 write_padding (padding_len);
972 cpio_append_trailer (void)
975 memset (&statbuf, 0, sizeof statbuf);
976 statbuf.st_nlink = 1;
977 cpio_append_stat ("TRAILER!!!", &statbuf);
979 /* CPIO seems to pad up to the next block boundary, ie. up to
980 * the next 512 bytes.
982 write_padding (((out_offset + 511) & ~511) - out_offset);
983 assert ((out_offset & 511) == 0);
986 /* Write 'len' bytes of zeroes out. */
988 write_padding (size_t len)
990 static const char buffer[512] = { 0 };
993 size_t n = len < sizeof buffer ? len : sizeof buffer;
994 write_to_fd (buffer, n);