0
votes

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?

1
You most probably don't want the value of root= as that can have many different formats, for example root=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 what dev_t device is the root device. Is this what you are after? - Marco Bonelli
Ping! Care to address my comment? I have a simple solution for you if that's the case. - Marco Bonelli
Oh yes, that would be great,thanks! - willpnw
Done, there you go. - Marco Bonelli
Thanks so much! You really went all out - willpnw

1 Answers

2
votes

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:

  1. Call kern_path() to get a struct path for the path "/".
  2. Call vfs_getattr() passing that path to get a struct kstat.
  3. Check the ->dev member of the struct kstat, which is the root device (dev_t).
  4. If you want, you can also use 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:

  1. I did not do any kind of error checking in the above example, but you most definitely should! See the complete example below.

  2. 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.

  3. 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.

Working example

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