diff options
Diffstat (limited to 'usr/kinit/do_mounts.c')
-rw-r--r-- | usr/kinit/do_mounts.c | 533 |
1 files changed, 533 insertions, 0 deletions
diff --git a/usr/kinit/do_mounts.c b/usr/kinit/do_mounts.c new file mode 100644 index 0000000..b648299 --- /dev/null +++ b/usr/kinit/do_mounts.c @@ -0,0 +1,533 @@ +#include <errno.h> +#include <fcntl.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> +#include <mntent.h> + +#include "do_mounts.h" +#include "kinit.h" +#include "fstype.h" +#include "zlib.h" + +#ifndef MS_RELATIME +# define MS_RELATIME (1<<21) /* Update atime relative to mtime/ctime. */ +#endif + +#ifndef MS_STRICTATIME +# define MS_STRICTATIME (1<<24) /* Always perform atime updates */ +#endif + +/* + * The following mount option parsing was stolen from + * + * usr/utils/mount_opts.c + * + * and adapted to add some later mount flags. + */ +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +struct mount_opts { + const char str[16]; + unsigned long rwmask; + unsigned long rwset; + unsigned long rwnoset; +}; + +struct extra_opts { + char *str; + char *end; + int used_size; + int alloc_size; +}; + +/* + * These options define the function of "mount(2)". + */ +#define MS_TYPE (MS_REMOUNT|MS_BIND|MS_MOVE) + + +/* These must be in alphabetic order! */ +static const struct mount_opts options[] = { + /* name mask set noset */ + {"async", MS_SYNCHRONOUS, 0, MS_SYNCHRONOUS}, + {"atime", MS_NOATIME, 0, MS_NOATIME}, + {"bind", MS_TYPE, MS_BIND, 0,}, + {"dev", MS_NODEV, 0, MS_NODEV}, + {"diratime", MS_NODIRATIME, 0, MS_NODIRATIME}, + {"dirsync", MS_DIRSYNC, MS_DIRSYNC, 0}, + {"exec", MS_NOEXEC, 0, MS_NOEXEC}, + {"move", MS_TYPE, MS_MOVE, 0}, + {"nodev", MS_NODEV, MS_NODEV, 0}, + {"noexec", MS_NOEXEC, MS_NOEXEC, 0}, + {"nosuid", MS_NOSUID, MS_NOSUID, 0}, + {"recurse", MS_REC, MS_REC, 0}, + {"relatime", MS_RELATIME, MS_RELATIME, 0}, + {"remount", MS_TYPE, MS_REMOUNT, 0}, + {"ro", MS_RDONLY, MS_RDONLY, 0}, + {"rw", MS_RDONLY, 0, MS_RDONLY}, + {"strictatime", MS_STRICTATIME, MS_STRICTATIME, 0}, + {"suid", MS_NOSUID, 0, MS_NOSUID}, + {"sync", MS_SYNCHRONOUS, MS_SYNCHRONOUS, 0}, + {"verbose", MS_VERBOSE, MS_VERBOSE, 0}, +}; + +/* + * Append 's' to 'extra->str'. 's' is a mount option that can't be turned into + * a flag. Return 0 on success, -1 on error. + */ +static int add_extra_option(struct extra_opts *extra, char *s) +{ + int len = strlen(s); + int newlen = extra->used_size + len; + + if (extra->str) + len++; /* +1 for ',' */ + + if (newlen >= extra->alloc_size) { + char *new; + + new = realloc(extra->str, newlen + 1); /* +1 for NUL */ + if (!new) { + if (extra->str) + free(extra->str); + return -1; + } + + extra->str = new; + extra->end = extra->str + extra->used_size; + extra->alloc_size = newlen; + } + + if (extra->used_size) { + *extra->end = ','; + extra->end++; + } + strcpy(extra->end, s); + extra->used_size += len; + + return 0; +} + +/* + * Parse the options in 'arg'; put numeric mount flags into 'flags' and + * the rest into 'extra'. Return 0 on success, -1 on error. + */ +static int +parse_mount_options(char *arg, unsigned long *flags, struct extra_opts *extra) +{ + char *s; + + while ((s = strsep(&arg, ",")) != NULL) { + char *opt = s; + unsigned int i; + int res; + int no = (s[0] == 'n' && s[1] == 'o'); + int found = 0; + + if (no) + s += 2; + + for (i = 0; i < ARRAY_SIZE(options); i++) { + + res = strcmp(s, options[i].str); + if (res == 0) { + found = 1; + *flags &= ~options[i].rwmask; + if (no) + *flags |= options[i].rwnoset; + else + *flags |= options[i].rwset; + break; + + /* If we're beyond 's' alphabetically, we're done */ + } else if (res < 0) + break; + } + if (! found) + if (add_extra_option(extra, opt) != 0) + return -1; + } + + return 0; +} + +/* Create the device node "name" */ +int create_dev(const char *name, dev_t dev) +{ + unlink(name); + return mknod(name, S_IFBLK | 0600, dev); +} + + +/* + * If there is not a block device for the input 'name', try to create one; if + * we can't that's okay. + */ +static void create_dev_if_not_present(const char *name) +{ + struct stat st; + dev_t dev; + + if (stat(name, &st) == 0) /* file present; we're done */ + return; + dev = name_to_dev_t(name); + if (dev) + (void) create_dev(name, dev); +} + + +/* mount a filesystem, possibly trying a set of different types */ +const char *mount_block(const char *source, const char *target, + const char *type, unsigned long flags, + const void *data) +{ + char *fslist, *p, *ep; + const char *rp; + ssize_t fsbytes; + int fd; + + if (type) { + dprintf("kinit: trying to mount %s on %s " + "with type %s, flags 0x%lx, data '%s'\n", + source, target, type, flags, (char *)data); + int rv = mount(source, target, type, flags, data); + + if (rv != 0) + dprintf("kinit: mount %s on %s failed " + "with errno = %d\n", + source, target, errno); + /* Mount readonly if necessary */ + if (rv == -1 && errno == EACCES && !(flags & MS_RDONLY)) + rv = mount(source, target, type, flags | MS_RDONLY, + data); + return rv ? NULL : type; + } + + /* If no type given, try to identify the type first; this + also takes care of specific ordering requirements, like + ext3 before ext2... */ + fd = open(source, O_RDONLY); + if (fd >= 0) { + int err = identify_fs(fd, &type, NULL, 0); + close(fd); + + if (!err && type) { + dprintf("kinit: %s appears to be a %s filesystem\n", + source, type); + type = mount_block(source, target, type, flags, data); + if (type) + return type; + } + } + + dprintf("kinit: failed to identify filesystem %s, trying all\n", + source); + + fsbytes = readfile("/proc/filesystems", &fslist); + + errno = EINVAL; + if (fsbytes < 0) + return NULL; + + p = fslist; + ep = fslist + fsbytes; + + rp = NULL; + + while (p < ep) { + type = p; + p = strchr(p, '\n'); + if (!p) + break; + *p++ = '\0'; + /* We can't mount a block device as a "nodev" fs */ + if (*type != '\t') + continue; + + type++; + rp = mount_block(source, target, type, flags, data); + if (rp) + break; + if (errno != EINVAL) + break; + } + + free(fslist); + return rp; +} + +/* mount the root filesystem from a block device */ +static int +mount_block_root(int argc, char *argv[], dev_t root_dev, + const char *type, unsigned long flags) +{ + const char *data, *rp; + + data = get_arg(argc, argv, "rootflags="); + create_dev("/dev/root", root_dev); + + errno = 0; + + if (type) { + if ((rp = mount_block("/dev/root", "/root", type, flags, data))) + goto ok; + if (errno != EINVAL) + goto bad; + } + + if (!errno + && (rp = mount_block("/dev/root", "/root", NULL, flags, data))) + goto ok; + +bad: + if (errno != EINVAL) { + /* + * Allow the user to distinguish between failed open + * and bad superblock on root device. + */ + fprintf(stderr, "%s: Cannot open root device %s\n", + progname, bdevname(root_dev)); + return -errno; + } else { + fprintf(stderr, "%s: Unable to mount root fs on device %s\n", + progname, bdevname(root_dev)); + return -ESRCH; + } + +ok: + printf("%s: Mounted root (%s filesystem)%s.\n", + progname, rp, (flags & MS_RDONLY) ? " readonly" : ""); + return 0; +} + +static int +mount_roots(int argc, char *argv[], const char *root_dev_name) +{ + char *roots = strdup(root_dev_name); + char *root; + const char *sep = ","; + char *saveptr; + int ret = -ESRCH; + + root = strtok_r(roots, sep, &saveptr); + while (root) { + dev_t root_dev; + + dprintf("kinit: trying to mount %s\n", root); + root_dev = name_to_dev_t(root); + ret = mount_root(argc, argv, root_dev, root); + if (!ret) + break; + root = strtok_r(NULL, sep, &saveptr); + } + free(roots); + return ret; +} + +int +mount_root(int argc, char *argv[], dev_t root_dev, const char *root_dev_name) +{ + unsigned long flags = MS_RDONLY | MS_VERBOSE; + int ret; + const char *type = get_arg(argc, argv, "rootfstype="); + + if (get_flag(argc, argv, "rw") > get_flag(argc, argv, "ro")) { + dprintf("kinit: mounting root rw\n"); + flags &= ~MS_RDONLY; + } + + if (type) { + if (!strcmp(type, "nfs")) + root_dev = Root_NFS; + else if (!strcmp(type, "jffs2") && !major(root_dev)) + root_dev = Root_MTD; + } + + switch (root_dev) { + case Root_NFS: + ret = mount_nfs_root(argc, argv, flags); + break; + case Root_MTD: + ret = mount_mtd_root(argc, argv, root_dev_name, type, flags); + break; + default: + ret = mount_block_root(argc, argv, root_dev, type, flags); + break; + } + + if (!ret) + chdir("/root"); + + return ret; +} + +/* Allocate a buffer and prepend '/root' onto 'src'. */ +static char *prepend_root_dir(const char *src) +{ + size_t len = strlen(src) + 6; /* "/root" */ + char *p = malloc(len); + + if (!p) + return NULL; + + strcpy(p, "/root"); + strcat(p, src); + return p; +} + +int do_cmdline_mounts(int argc, char *argv[]) +{ + int arg_i; + int ret = 0; + + for (arg_i = 0; arg_i < argc; arg_i++) { + const char *fs_dev, *fs_dir, *fs_type; + char *fs_opts; + unsigned long flags = 0; + char *saveptr = NULL; + char *new_dir; + struct extra_opts extra = { 0, 0, 0, 0 }; + + if (strncmp(argv[arg_i], "kinit_mount=", 12)) + continue; + /* + * Format: + * <fs_dev>;<dir>;<fs_type>;[opt1],[optn...] + */ + fs_dev = strtok_r(&argv[arg_i][12], ";", &saveptr); + if (!fs_dev) { + fprintf(stderr, "Failed to parse fs_dev\n"); + continue; + } + fs_dir = strtok_r(NULL, ";", &saveptr); + if (!fs_dir) { + fprintf(stderr, "Failed to parse fs_dir\n"); + continue; + } + fs_type = strtok_r(NULL, ";", &saveptr); + if (!fs_type) { + fprintf(stderr, "Failed to parse fs_type\n"); + continue; + } + fs_opts = strtok_r(NULL, ";", &saveptr); + /* Don't error if there is no option string sent */ + + new_dir = prepend_root_dir(fs_dir); + if (! new_dir) + return -ENOMEM; + create_dev_if_not_present(fs_dev); + ret = parse_mount_options(fs_opts, &flags, &extra); + if (ret != 0) + break; + + if (!mount_block(fs_dev, new_dir, fs_type, + flags, extra.str)) + fprintf(stderr, "Skipping failed mount '%s'\n", fs_dev); + free(new_dir); + if (extra.str) + free(extra.str); + } + return ret; +} + +int do_fstab_mounts(FILE *fp) +{ + struct mntent *ent = NULL; + char *new_dir; + int ret = 0; + + while ((ent = getmntent(fp))) { + unsigned long flags = 0; + struct extra_opts extra = { 0, 0, 0, 0 }; + + new_dir = prepend_root_dir(ent->mnt_dir); + if (! new_dir) + return -ENOMEM; + create_dev_if_not_present(ent->mnt_fsname); + ret = parse_mount_options(ent->mnt_opts, &flags, &extra); + if (ret != 0) + break; + + if (!mount_block(ent->mnt_fsname, + new_dir, + ent->mnt_type, + flags, + extra.str)) { + fprintf(stderr, "Skipping failed mount '%s'\n", + ent->mnt_fsname); + } + free(new_dir); + if (extra.str) + free(extra.str); + } + return 0; +} + +int do_mounts(int argc, char *argv[]) +{ + const char *root_dev_name = get_arg(argc, argv, "root="); + const char *root_delay = get_arg(argc, argv, "rootdelay="); + const char *load_ramdisk = get_arg(argc, argv, "load_ramdisk="); + dev_t root_dev = 0; + int err; + FILE *fp; + + dprintf("kinit: do_mounts\n"); + + if (root_delay) { + int delay = atoi(root_delay); + fprintf(stderr, "Waiting %d s before mounting root device...\n", + delay); + sleep(delay); + } + + md_run(argc, argv); + + if (root_dev_name) { + root_dev = name_to_dev_t(root_dev_name); + } else if (get_arg(argc, argv, "nfsroot=") || + get_arg(argc, argv, "nfsaddrs=")) { + root_dev = Root_NFS; + } else { + long rootdev; + getintfile("/proc/sys/kernel/real-root-dev", &rootdev); + root_dev = (dev_t) rootdev; + } + + dprintf("kinit: root_dev = %s\n", bdevname(root_dev)); + + if (initrd_load(argc, argv, root_dev)) { + dprintf("initrd loaded\n"); + return 0; + } + + if (load_ramdisk && atoi(load_ramdisk)) { + if (ramdisk_load(argc, argv)) + root_dev = Root_RAM0; + } + + if (root_dev == Root_MULTI) + err = mount_roots(argc, argv, root_dev_name); + else + err = mount_root(argc, argv, root_dev, root_dev_name); + + if (err) + return err; + + if ((fp = setmntent("/etc/fstab", "r"))) { + err = do_fstab_mounts(fp); + fclose(fp); + } + + if (err) + return err; + + if (get_arg(argc, argv, "kinit_mount=")) + err = do_cmdline_mounts(argc, argv); + return err; +} |