| /* |
| * Handle initrd, thus putting the backwards into backwards compatible |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <sys/mount.h> |
| #include <sys/ioctl.h> |
| #include <sys/wait.h> |
| #include "do_mounts.h" |
| #include "kinit.h" |
| #include "xpio.h" |
| |
| #define BUF_SIZE 65536 /* Should be a power of 2 */ |
| |
| /* |
| * Copy the initrd to /dev/ram0, copy from the end to the beginning |
| * to avoid taking 2x the memory. |
| */ |
| static int rd_copy_uncompressed(int ffd, int dfd) |
| { |
| char buffer[BUF_SIZE]; |
| off_t bytes; |
| struct stat st; |
| |
| dprintf("kinit: uncompressed initrd\n"); |
| |
| if (ffd < 0 || fstat(ffd, &st) || !S_ISREG(st.st_mode) || |
| (bytes = st.st_size) == 0) |
| return -1; |
| |
| while (bytes) { |
| ssize_t blocksize = ((bytes - 1) & (BUF_SIZE - 1)) + 1; |
| off_t offset = bytes - blocksize; |
| |
| dprintf("kinit: copying %zd bytes at offset %llu\n", |
| blocksize, offset); |
| |
| if (xpread(ffd, buffer, blocksize, offset) != blocksize || |
| xpwrite(dfd, buffer, blocksize, offset) != blocksize) |
| return -1; |
| |
| ftruncate(ffd, offset); /* Free up memory */ |
| bytes = offset; |
| } |
| return 0; |
| } |
| |
| static int rd_copy_image(const char *path) |
| { |
| int ffd = open(path, O_RDONLY); |
| int rv = -1; |
| unsigned char gzip_magic[2]; |
| |
| if (ffd < 0) |
| goto barf; |
| |
| if (xpread(ffd, gzip_magic, 2, 0) == 2 && |
| gzip_magic[0] == 037 && gzip_magic[1] == 0213) { |
| FILE *wfd = fopen("/dev/ram0", "w"); |
| if (!wfd) |
| goto barf; |
| rv = load_ramdisk_compressed(path, wfd, 0); |
| fclose(wfd); |
| } else { |
| int dfd = open("/dev/ram0", O_WRONLY); |
| if (dfd < 0) |
| goto barf; |
| rv = rd_copy_uncompressed(ffd, dfd); |
| close(dfd); |
| } |
| |
| barf: |
| if (ffd >= 0) |
| close(ffd); |
| return rv; |
| } |
| |
| /* |
| * Run /linuxrc, for emulation of old-style initrd |
| */ |
| static int run_linuxrc(int argc, char *argv[], dev_t root_dev) |
| { |
| int root_fd, old_fd; |
| pid_t pid; |
| long realroot = Root_RAM0; |
| const char *ramdisk_name = "/dev/ram0"; |
| FILE *fp; |
| |
| dprintf("kinit: mounting initrd\n"); |
| mkdir("/root", 0700); |
| if (!mount_block(ramdisk_name, "/root", NULL, MS_VERBOSE, NULL)) |
| return -errno; |
| |
| /* Write the current "real root device" out to procfs */ |
| dprintf("kinit: real_root_dev = %#x\n", root_dev); |
| fp = fopen("/proc/sys/kernel/real-root-dev", "w"); |
| fprintf(fp, "%u", root_dev); |
| fclose(fp); |
| |
| mkdir("/old", 0700); |
| root_fd = open_cloexec("/", O_RDONLY | O_DIRECTORY, 0); |
| old_fd = open_cloexec("/old", O_RDONLY | O_DIRECTORY, 0); |
| |
| if (root_fd < 0 || old_fd < 0) |
| return -errno; |
| |
| if (chdir("/root") || |
| mount(".", "/", NULL, MS_MOVE, NULL) || chroot(".")) |
| return -errno; |
| |
| pid = vfork(); |
| if (pid == 0) { |
| setsid(); |
| /* Looks like linuxrc doesn't get the init environment |
| or parameters. Weird, but so is the whole linuxrc bit. */ |
| execl("/linuxrc", "linuxrc", NULL); |
| _exit(255); |
| } else if (pid > 0) { |
| dprintf("kinit: Waiting for linuxrc to complete...\n"); |
| while (waitpid(pid, NULL, 0) != pid) |
| ; |
| dprintf("kinit: linuxrc done\n"); |
| } else { |
| return -errno; |
| } |
| |
| if (fchdir(old_fd) || |
| mount("/", ".", NULL, MS_MOVE, NULL) || |
| fchdir(root_fd) || chroot(".")) |
| return -errno; |
| |
| close(root_fd); |
| close(old_fd); |
| |
| getintfile("/proc/sys/kernel/real-root-dev", &realroot); |
| |
| /* If realroot is Root_RAM0, then the initrd did any necessary work */ |
| if (realroot == Root_RAM0) { |
| if (mount("/old", "/root", NULL, MS_MOVE, NULL)) |
| return -errno; |
| } else { |
| mount_root(argc, argv, (dev_t) realroot, NULL); |
| |
| /* If /root/initrd exists, move the initrd there, otherwise discard */ |
| if (!mount("/old", "/root/initrd", NULL, MS_MOVE, NULL)) { |
| /* We're good */ |
| } else { |
| int olddev = open(ramdisk_name, O_RDWR); |
| umount2("/old", MNT_DETACH); |
| if (olddev < 0 || |
| ioctl(olddev, BLKFLSBUF, (long)0) || |
| close(olddev)) { |
| fprintf(stderr, |
| "%s: Cannot flush initrd contents\n", |
| progname); |
| } |
| } |
| } |
| |
| rmdir("/old"); |
| return 0; |
| } |
| |
| int initrd_load(int argc, char *argv[], dev_t root_dev) |
| { |
| if (access("/initrd.image", R_OK)) |
| return 0; /* No initrd */ |
| |
| dprintf("kinit: initrd found\n"); |
| |
| create_dev("/dev/ram0", Root_RAM0); |
| |
| if (rd_copy_image("/initrd.image") || unlink("/initrd.image")) { |
| fprintf(stderr, "%s: initrd installation failed (too big?)\n", |
| progname); |
| return 0; /* Failed to copy initrd */ |
| } |
| |
| dprintf("kinit: initrd copied\n"); |
| |
| if (root_dev == Root_MULTI) { |
| dprintf("kinit: skipping linuxrc: incompatible with multiple roots\n"); |
| /* Mounting initrd as ordinary root */ |
| return 0; |
| } |
| |
| if (root_dev != Root_RAM0) { |
| int err; |
| dprintf("kinit: running linuxrc\n"); |
| err = run_linuxrc(argc, argv, root_dev); |
| if (err) |
| fprintf(stderr, "%s: running linuxrc: %s\n", progname, |
| strerror(-err)); |
| return 1; /* initrd is root, or run_linuxrc took care of it */ |
| } else { |
| dprintf("kinit: permament (or pivoting) initrd, not running linuxrc\n"); |
| return 0; /* Mounting initrd as ordinary root */ |
| } |
| } |