Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: check if two Linux paths are on same physical disk

For now I use os.stat(path).st_dev to get the device id. But the id seems to be different for logical disks on same same physical drive. So it doesn't actually works for me. Is there a better or direct solution to it.

like image 760
Barun Sharma Avatar asked Mar 23 '23 11:03

Barun Sharma


1 Answers

Look at the hex output. The first number after the 0x prefix indicates the device driver:

>>> hex(os.stat("/usr").st_dev)
'0x801L'

This is a 'SCSI' disk, because all of them have ID 8. Reference: https://www.kernel.org/doc/Documentation/devices.txt. Drive ID and partition number are encoded in the remaining part of the st_dev.

The exact transformation for major ID and minor ID as implemented by glibc is as follows:

>>> minor = int(os.stat("/lib").st_dev & 0xff)
>>> major = int(os.stat("/lib").st_dev >> 8 & 0xff)
>>> major, minor
(8, 1)

Meaning major number 8 (SCSI host adapter), minor number 1. The minor number encodes drive number as well as partition. As can also be inferred from here, all partitions on the first disk have a minor ID between 1 and 15. All partitions on the second disk have a minor ID between 17 and 31, and so on.

Showcase, same device controller (SCSI):

>>> int(os.stat("/lib").st_dev >> 8 & 0xff)
8
>>> int(os.stat("/usr").st_dev >> 8 & 0xff)
8

Showcase, different device controller (NFS mount in this case):

>>> int(os.stat("/home/*****").st_dev >> 8 & 0xff)
0

Background:

What you get from e.g.

>>> os.stat("/usr").st_dev
2049L

corresponds to the decimal Device output of the stat program:

$ stat /usr
  File: `/usr'
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: 801h/2049d  Inode: 1308164     Links: 11

From man 2 stat (or e.g. http://linux.die.net/man/2/stat) you can then read

The st_dev field describes the device on which this file resides. (The major(3) and minor(3) macros may be useful to decompose the device ID in this field.)

These macros are not defined by POSIX, but implemented in glibc, as can be seen here:

https://github.com/jeremie-koenig/glibc/blob/master-beware-rebase/sysdeps/generic/sys/sysmacros.h

The actual C implementation is:

#define major(dev) ((int)(((unsigned int) (dev) >> 8) & 0xff))
#define minor(dev) ((int)((dev) & 0xff))

This can easily be translated into Python as I have done above. As of these macros it is also obvious that the hex notation is more intuitive than the decimal one:

>>> hex(os.stat("/usr").st_dev)
'0x801L'

From here you can already see the 8 and the 1 being major device ID and minor device ID, respectively. We can also check this like so:

$ pwd
/usr
$ df -h .
Filesystem                                              Size  Used Avail Use% Mounted on
/dev/disk/by-uuid/cba70a49-04a7-40a6-8a53-465f817e51cd   29G  8.6G   19G  32% /

This is the disk, which actually corresponds to /dev/sda1:

$ ls -al /dev/disk/by-uuid/cba70a49-04a7-40a6-8a53-465f817e51cd
0 lrwxrwxrwx 1 root root 10 May  6 16:33 /dev/disk/by-uuid/cba70a49-04a7-40a6-8a53-465f817e51cd -> ../../sda1

Major ID 8 (-> sd, SCSI device), minor 1 (-> a1, first disk, first partition).

like image 145
Dr. Jan-Philip Gehrcke Avatar answered Apr 06 '23 02:04

Dr. Jan-Philip Gehrcke