Device Number in Stat Command Output

Device number in stat command output

# stat tool
File: `tool'
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 801h/2049d Inode: 671689 Links: 3

# ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 2010-08-16 14:43 /dev/sda
brw-rw---- 1 root disk 8, 1 2010-08-16 14:43 /dev/sda1
brw-rw---- 1 root disk 8, 2 2010-08-16 14:43 /dev/sda2
brw-rw---- 1 root disk 8, 5 2010-08-16 14:43 /dev/sda5

In the example, 'tool' (801h) is in /dev/sda1 (major device number is 8, minor device number is 1). That's the first partition in /dev/sda.

Linux stat(2) call gives non-existing device ID

stat(2) in fs/stat.c uses inode->i_sb->s_dev to fill stat.st_dev

/proc/self/mountinfo in fs/proc_namespace.c uses mnt->mnt_sb->s_dev

Apparently struct inode.i_sb superblock may be different to struct vfsmount.mnt_sb superblock in case of mount of btrfs subvolume.

This is an issue inherent to btrfs implementation, which "requires non-trivial changes in the VFS layer" to fix: https://mail-archive.com/linux-btrfs@vger.kernel.org/msg57667.html

Reproducing Linux stat command in C

Since (hexadecimal) 0x801 == 2049 (decimal), you can get the output you're after from:

printf("Device: %xh/%dd\n", st.st_dev, st.st_dev);

The h in the format is the h that appears at the end of 801h; the %x means 'print number in hex'. Similarly, the %d means print in decimal, and the trailing d is the d in 2049d.

Incidentally, on Linux and other POSIX platforms, you can also avoid repeating the st.st_dev argument. For example:

#include <stdio.h>

int main(void)
{
printf("Device: %1$xh/%1$dd\n", 0x801);
return 0;
}

This also produces:

Device: 801h/2049d

To see why, read the printf()
specification very carefully. Note that if you use one of the 1$ modifiers, you must (should) use it with every conversion specification.

major() and minor() give different numbers from ls

st_dev is the ID of device containing file, according to the man page. In other words, the device where the file's name resides. So it's the same as for your /dev directory, as you'll see if you use the stat command from your shell.

You're interested in st_rdev, which is Device ID (if special file) (again, from the man page).

The stat command shows both:

stat /dev/tty /dev/tty1


File: /dev/tty
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 6h/6d Inode: 1035 Links: 1 Device type: 5,0
Access: (0620/crw--w----) Uid: ( 0/ root) Gid: ( 5/ tty)

....


File: /dev/tty1
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 6h/6d Inode: 1044 Links: 1 Device type: 4,1
Access: (0620/crw--w----) Uid: ( 0/ root) Gid: ( 5/ tty)


Fixed code

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>

int get_char_device(const char *name,
unsigned *dev_major, unsigned *dev_minor)
{
struct stat buf;
if (stat(name, &buf)) {
perror(name);
return 1;
}
if (!S_ISCHR(buf.st_mode)) {
fprintf(stderr, "%s: not a char device\n", name);
return 1;
}
*dev_major = major(buf.st_rdev);
*dev_minor = minor(buf.st_rdev);
return 0;
}

int main(void)
{
unsigned int major1, minor1, major2, minor2;
if (get_char_device("/dev/tty1", &major1, &minor1) ||
get_char_device("/dev/tty2", &major2, &minor2)) {
return 1;
}

printf("%d %d\n%d %d\n", major1, major2, minor1, minor2);
if (major1 == major2 && minor1 == minor2) {
puts("the two device files are equal");
return 1;
}
}

How do I convert s.st_dev to /sys/block/name

Okay, I really found it!

So my first solution, reading the partitions, wouldn't work. It would give me sbc1 instead of sbc. I also found the /proc/mounts which includes some info about what's mounted where, but it would still not help me convert the value to sbc.

Instead, I found another solution, which is to look at the block devices and more specifically this softlink:

/sys/dev/block/<major>:<minor>

The <major> and <minor> numbers can be extracted using the functions of the same name in C (I use C++, but the basic functions are all in C):

#include <sys/types.h>

...

std::string dev_path("/sys/dev/block/");
dev_path += std::to_string(major(s.st_dev));
dev_path += ":";
dev_path += std::to_string(minor(s.st_dev));

That path is a soft link and I want to get the real path of the destination:

char device_path[PATH_MAX + 1];
if(realpath(dev_path.c_str(), device_path) == nullptr)
{
return true;
}

From that real path, I then break up the path in segments and search for a directory with a sub-directory named queue and a file named rotational.

advgetopt::string_list_t segments;
advgetopt::split_string(device_path, segments, { "/" });
while(segments.size() > 3)
{
std::string path("/"
+ boost::algorithm::join(segments, "/")
+ "/queue/rotational");
std::ifstream in;
in.open(path);
if(in.is_open())
{
char line[32];
in.getline(line, sizeof(line));
return std::atoi(line) != 0;
}
segments.pop_back();
}

The in.getline() is what reads the .../queue/rotational file. If the value is not 0 then I consider that this is an HDD. If something fails, I also consider that the drive is an HDD drive. The only way my function returns false is if the rotational file exists and is set to 0.

My function can be found here. The line number may change over time, search for tool::is_hdd.


Old "Solution"

The file /proc/partition includes the major & minor device numbers, a size, and a name. So I just have to parse that one and return the name I need. Voilà.

$ cat /proc/partitions 
major minor #blocks name

8 16 1953514584 sdb
8 17 248832 sdb1
8 18 1 sdb2
8 21 1953263616 sdb5
8 0 1953514584 sda
8 1 248832 sda1
8 2 1 sda2
8 5 1953263616 sda5
11 0 1048575 sr0
8 32 976764928 sdc
8 33 976763904 sdc1
252 0 4096 dm-0
252 1 1936375808 dm-1
252 2 1936375808 dm-2
252 3 1936375808 dm-3
252 4 16744448 dm-4

As you can see in this example, the first two lines represent the column names and an empty.The Name column is what I was looking for.

How to use the stat command for files that are in subfolders?

Instead of shell globing use find:

find . -type f -exec stat {} \; | tee output.txt


Related Topics



Leave a reply



Submit