blob: 150f0cdfba2898f502ddab3d66bad3a95378f970 [file] [log] [blame]
#include <ctype.h>
#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 <alloca.h>
#include "do_mounts.h"
#include "kinit.h"
#define BUF_SZ 4096
static const int do_devfs; // FIXME
/* Find dev_t for e.g. "hda,NULL" or "hdb,2" */
static dev_t
try_name(char *name, int part)
{
char path[BUF_SZ];
char buf[BUF_SZ];
int range;
dev_t res;
char *s;
int len;
int fd;
/* read device number from /sys/block/.../dev */
snprintf(path, sizeof(path), "/sys/block/%s/dev", name);
fd = open(path, 0, 0);
if (fd < 0)
goto fail;
len = read(fd, buf, BUF_SZ);
close(fd);
if (len <= 0 || len == BUF_SZ || buf[len - 1] != '\n')
goto fail;
buf[len - 1] = '\0';
res = (dev_t) strtoul(buf, &s, 16);
if (*s)
goto fail;
/* if it's there and we are not looking for a partition - that's it */
if (!part)
return res;
/* otherwise read range from .../range */
snprintf(path, sizeof(path), "/sys/block/%s/range", name);
fd = open(path, 0, 0);
if (fd < 0)
goto fail;
len = read(fd, buf, 32);
close(fd);
if (len <= 0 || len == 32 || buf[len - 1] != '\n')
goto fail;
buf[len - 1] = '\0';
range = strtoul(buf, &s, 10);
if (*s)
goto fail;
/* if partition is within range - we got it */
if (part < range)
return res + part;
fail:
return (dev_t) 0;
}
/*
* Convert a name into device number. We accept the following variants:
*
* 1) device number in hexadecimal represents itself
* 2) /dev/nfs represents Root_NFS
* 3) /dev/<disk_name> represents the device number of disk
* 4) /dev/<disk_name><decimal> represents the device number
* of partition - device number of disk plus the partition number
* 5) /dev/<disk_name>p<decimal> - same as the above, that form is
* used when disk name of partitioned disk ends on a digit.
* 6) an actual block device node in the initramfs filesystem
*
* If name doesn't have fall into the categories above, we return 0.
* Driverfs is used to check if something is a disk name - it has
* all known disks under bus/block/devices. If the disk name
* contains slashes, name of driverfs node has them replaced with
* dots. try_name() does the actual checks, assuming that driverfs
* is mounted on rootfs /sys.
*/
static dev_t
name_to_dev_t(const char *name)
{
char *p;
dev_t res = 0;
char *s;
int part;
struct stat st;
int len;
if ( name[0] == '/' && !stat(name, &st) && S_ISBLK(st.st_mode) )
return st.st_rdev;
if ( strncmp(name, "/dev/", 5) ) {
res = (dev_t) strtoul(name, &p, 16);
if (*p)
return 0;
return res;
}
name += 5;
if (strcmp(name, "nfs") == 0)
return Root_NFS;
len = strlen(name);
s = alloca(len+1);
memcpy(s, name, len+1);
for (p = s; *p; p++)
if (*p == '/')
*p = '.';
res = try_name(s, 0);
if (res)
return res;
while (p > s && isdigit(p[-1]))
p--;
if (p == s || !*p || *p == '0')
goto fail;
part = strtoul(p, NULL, 10);
*p = '\0';
res = try_name(s, part);
if (res)
return res;
if (p < s + 2 || !isdigit(p[-2]) || p[-1] != 'p')
goto fail;
p[-1] = '\0';
res = try_name(s, part);
return res;
fail:
return (dev_t)0;
}
static int
find_in_devfs(char *path, dev_t dev)
{
(void) path;
(void) dev;
#ifdef CONFIG_DEVFS_FS
// Leftover crap from the kernel. Ugh.
struct stat buf;
char *end = path + strlen(path);
int rest = path + 64 - end;
int size;
char *p = read_dir(path, &size);
char *s;
if (!p)
return -1;
for (s = p; s < p + size; s += ((struct linux_dirent64 *)s)->d_reclen) {
struct linux_dirent64 *d = (struct linux_dirent64 *)s;
if (strlen(d->d_name) + 2 > rest)
continue;
switch (d->d_type) {
case DT_BLK:
sprintf(end, "/%s", d->d_name);
if (sys_newstat(path, &buf) < 0)
break;
if (!S_ISBLK(buf.st_mode))
break;
if (buf.st_rdev != dev)
break;
kfree(p);
return 0;
case DT_DIR:
if (strcmp(d->d_name, ".") == 0)
break;
if (strcmp(d->d_name, "..") == 0)
break;
sprintf(end, "/%s", d->d_name);
if (find_in_devfs(path, dev) < 0)
break;
kfree(p);
return 0;
}
}
kfree(p);
#endif
return -1;
}
/* Create the device node "name" */
int
create_dev(const char *name, dev_t dev, const char *devfs_name)
{
char path[BUF_SZ];
unlink(name);
if (!do_devfs)
return mknod(name, S_IFBLK|0600, dev);
if (devfs_name && devfs_name[0]) {
if (strncmp(devfs_name, "/dev/", 5) == 0)
devfs_name += 5;
snprintf(path, BUF_SZ, "/dev/%s", devfs_name);
if (access(path, 0) == 0)
return symlink(devfs_name, name);
}
if (!dev)
return -1;
strcpy(path, "/dev");
if (find_in_devfs(path, dev) < 0)
return -1;
return symlink(path + 5, name);
}
/* 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;
if ( type ) {
int rv = mount(source, target, type, flags, data);
/* 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;
}
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';
if (*type != '\t')/* We can't mount a block device as a "nodev" fs */
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 *root_dev_name, unsigned long flags)
{
const char *data, *type;
const char *rp;
data = get_arg(argc, argv, "rootflags=");
create_dev("/dev/root", root_dev, root_dev_name);
errno = 0;
type = get_arg(argc, argv, "rootfstype=");
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 \"%04x\"\n",
progname, root_dev);
return -errno;
} else {
fprintf(stderr, "%s: Unable to mount root fs on \"%04x\"\n",
progname, root_dev);
return -ESRCH;
}
ok:
printf("%s: Mounted root (%s filesystem)%s.\n",
progname, rp, (flags & MS_RDONLY) ? " readonly" : "");
return 0;
}
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;
if (get_arg(argc, argv, "ro")) {
flags |= MS_RDONLY;
}
if (get_arg(argc, argv, "rw")) {
flags &= ~MS_RDONLY;
}
if (root_dev == Root_NFS) {
ret = mount_nfs_root(argc, argv, flags);
} else {
ret = mount_block_root(argc, argv, root_dev, root_dev_name, flags);
}
if (ret == 0) {
chdir("/root");
}
return ret;
}
void
md_run_setup(void)
{
/* Muck around with md device(s) if necessary */
}
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=");
dev_t root_dev = 0;
if (root_delay) {
int delay = atoi(root_delay);
fprintf(stderr, "Waiting %d s before mounting root device...\n", delay);
sleep(delay);
}
md_run_setup();
if (root_dev_name) {
root_dev = name_to_dev_t(root_dev_name);
if (strncmp(root_dev_name, "/dev/", 5) == 0) {
root_dev_name += 5;
}
}
if ( initrd_load(argc, argv, root_dev) )
return 0;
/* If it's a floppy(only?) and load_ramdisk is set, then load
a non-initrd ramdisk */
return mount_root(argc, argv, root_dev, root_dev_name);
}