blob: d2efc596c759bfa7c83ae64734e948af4378f722 [file] [log] [blame]
/*
* 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 */
}
}