blob: 9aa5bd5a1a145832f8fa176e455f7cfe52a4bbb8 [file] [log] [blame]
/*
* 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"
#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.
*/
int rd_copy_image(int ffd)
{
int dfd = open("/dev/ram0", O_RDWR);
char buffer[BUF_SIZE];
struct stat st;
off_t bytes;
int rv = -1;
if ( ffd < 0 || dfd < 0 )
goto barf;
if ( fstat(ffd, &st) || !S_ISREG(st.st_mode) ||
(bytes = st.st_size) == 0 )
goto barf;
while ( bytes ) {
ssize_t blocksize = ((bytes-1) & ~(BUF_SIZE-1))+1;
off_t offset = bytes-blocksize;
if ( pread(ffd, buffer, blocksize, offset) != blocksize ||
pwrite(dfd, buffer, blocksize, offset) != blocksize )
goto barf;
ftruncate(ffd, offset); /* Free up memory */
bytes = offset;
}
rv = 0; /* Done! */
barf:
close(ffd);
close(dfd);
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;
if ( !mount_block(ramdisk_name, "/", NULL, MS_VERBOSE, NULL) )
return -errno;
/* Write the current "real root device" out to procfs */
fp = fopen("/proc/sys/kernel/real-root-dev", "w");
fprintf(fp, "%lu", (unsigned long)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 ) {
while ( waitpid(pid, NULL, 0) != pid );
} 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);
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);
}
}
return 0;
}
int initrd_load(int argc, char *argv[], dev_t root_dev)
{
int initrd_fd;
initrd_fd = open("/initrd.image", O_RDWR);
if ( initrd_fd < 0 )
return 0;
create_dev("/dev/ram0", Root_RAM0, NULL);
if ( rd_copy_image(initrd_fd) ||
unlink("/initrd.image") )
return 0;
if ( root_dev != Root_RAM0 ) {
run_linuxrc(argc, argv, root_dev);
return 1;
}
return 0;
}