I am writing a driver and I need to know what disk/partition contains the root filesystem. This can be viewed from userspace using:
cat /proc/cmdline
root=/dev/mmcblk0p1
How can I get the value of root in kernel space?
In order to get the dev_t for the device where your filesystem root is mounted, you can use a strategy similar to what the stat() syscall does. The only exception is that you are not working with __user buffers, so you'll need to use slightly different APIs.
In short, you want to:
kern_path() to get a struct path for the path "/".vfs_getattr() passing that path to get a struct kstat.->dev member of the struct kstat, which is the root device (dev_t).bdget() to find the block device corresponding to the obtained dev_t, then use bdevname() to get its name (e.g. sda1).This translates to the following:
struct path root_path;
struct kstat root_stat;
struct block_device *root_device;
char root_device_name[BDEVNAME_SIZE];
kern_path("/", 0, &root_path);
// KERNEL > 4.10
// vfs_getattr(&root_path, &root_stat, STATX_BASIC_STATS, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
// KERNEL <= 4.10
vfs_getattr(&root_path, &root_stat);
pr_info("root device number is 0x%08x; major = %d, minor = %d\n", root_stat.dev, MAJOR(root_stat.dev), MINOR(root_stat.dev));
root_device = bdget(root_stat.dev);
bdevname(root_device, root_device_name);
pr_info("root device name: %s, path: /dev/%s\n", root_device_name, root_device_name);
bdput(root_device);
path_put(&root_path);
Some notes:
I did not do any kind of error checking in the above example, but you most definitely should! See the complete example below.
kern_path() seems to take LOOKUP_* flags defined in linux/namei.h as
second argument, but passing a default value of 0 is also ok.
Likewise, vfs_getattr() takes STATX_* flags defined in linux/stat.h as
third arg. The stat() syscall passes STATX_BASIC_STATS, but passing 0
should be fine too here since we do not need to know anything other
than the device (->dev field), which seems to get populated
regardless of flags. I could not test this though since my kernel is <= 4.10, and these flags are necessary only for kernel > 4.10.
Here's a complete example of a working kernel module which does the above also applying proper error checking.
// SPDX-License-Identifier: GPL-3.0
#include <linux/kernel.h> // printk(), pr_*()
#include <linux/module.h> // THIS_MODULE, MODULE_VERSION, ...
#include <linux/init.h> // module_{init,exit}
#include <linux/types.h> // dev_t
#include <linux/kdev_t.h> // MAJOR(), MINOR(), MKDEV()
#include <linux/path.h> // struct path
#include <linux/namei.h> // kern_path(), path_put()
#include <linux/stat.h> // struct kstat, STATX_*
#include <linux/fs.h> // vfs_getattr(), struct block_device, bdget(),...
#include <uapi/linux/fcntl.h> // AT_*
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
static int __init findrootdev_init(void)
{
struct path root_path;
struct kstat root_stat;
struct block_device *root_device;
char root_device_name[BDEVNAME_SIZE];
int err = 0;
pr_info("init\n");
err = kern_path("/", 0, &root_path);
if (err) {
pr_err("kern_path error %d\n", err);
goto kern_path_fail;
}
// KERNEL > 4.10
// err = vfs_getattr(&root_path, &root_stat, STATX_BASIC_STATS,
// AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
// KERNEL <= 4.10
err = vfs_getattr(&root_path, &root_stat);
if (err) {
pr_err("vfs_getattr error %d\n", err);
goto vfs_getattr_fail;
}
pr_info("root device number is 0x%08x; major = %d, minor = %d\n",
root_stat.dev, MAJOR(root_stat.dev), MINOR(root_stat.dev));
root_device = bdget(root_stat.dev);
if (!root_device) {
pr_err("bdget failed\n");
err = -1;
goto bdget_fail;
}
if (!bdevname(root_device, root_device_name)) {
pr_err("bdevname failed\n");
err = -1;
goto bdevname_fail;
}
pr_info("root device name: %s, path: /dev/%s\n",
root_device_name, root_device_name);
bdevname_fail:
bdput(root_device);
bdget_fail:
vfs_getattr_fail:
path_put(&root_path);
kern_path_fail:
return err;
}
static void __exit findrootdev_exit(void)
{
// This function is only needed to be able to unload the module.
pr_info("exit\n");
}
module_init(findrootdev_init);
module_exit(findrootdev_exit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Silly test module to find the root device and its name.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");
Compiling and loading the above module with insmod generates this output in dmesg:
[12664.685699] findrootdev: init
[12664.685703] findrootdev: root device number is 0x00800003; major = 8, minor = 3
[12664.685706] findrootdev: root device name: sda3, path: /dev/sda3
[12671.671038] findrootdev: exit
root=as that can have many different formats, for exampleroot=UUID=xxx,root=/dev/xxx,root=maj:min,root=PARTUUID=xxx/PARTNROFF=yyy, and so on... if you need this inside a module, you most probably would like to know whatdev_tdevice is the root device. Is this what you are after? - Marco Bonelli