| /* |
| * kinit/initrd.c |
| * |
| * 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; |
| |
| DEBUG(("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; |
| |
| DEBUG(("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; |
| |
| DEBUG(("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 */ |
| DEBUG(("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 = fork(); |
| 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 ) { |
| DEBUG(("kinit: Waiting for linuxrc to complete...\n")); |
| while ( waitpid(pid, NULL, 0) != pid ) |
| ; |
| DEBUG(("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 */ |
| |
| DEBUG(("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 */ |
| } |
| |
| DEBUG(("kinit: initrd copied\n")); |
| |
| if (root_dev != Root_RAM0) { |
| int err; |
| DEBUG(("kinit: running linuxrc\n")); |
| err = run_linuxrc(argc, argv, root_dev); |
| if (err) |
| fprintf(stderr, "%s: running linuxrc: %s\n", progname, strerror(-err)); |
| } else { |
| DEBUG(("kinit: permament (or pivoting) initrd, not running linuxrc\n")); |
| } |
| |
| return 1; /* initrd is root, or run_linuxrc took care of it */ |
| } |