File LBA Range – Find Using Inode

filesystems

While answering this U&L question titled: What command do I use to see the start and end block of a file in the file system?, I tried to figure out if it was possible to determine a file's LBA using it's inode.

My answer determined that I could use hdparm as one method for finding LBAs:

$ sudo hdparm --fibmap afile 

afile:
 filesystem blocksize 4096, begins at LBA 0; assuming 512 byte sectors.
 byte_offset  begin_LBA    end_LBA    sectors
           0  282439184  282439191          8

But I was curious if there was some method using a file's inode to also get the LBA's; without using hdparm.

I think there might be alternative methods hiding in the tools filefrag, stat, debugfs, and tune2fs but teasing it out is eluding me.

Can anyone think of alternatives?


Here's some of my research thus far that might be useful to those brave enough to attempt to answer this.

filefrag

I suspect you could use the tool filefrag to do it, specifically using the results from its -e switch, perhaps by performing several calculations to get there that I'm not that familiar with.

sample output

$ filefrag -e afile
Filesystem type is: ef53
File size of afile is 20 (1 block of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..       0:   35304898..  35304898:      1:             eof
afile: 1 extent found

inodes

Another potential method I suspect might have potential is to use a file's inode information, either directly or through some complex math that is poorly documented on the interwebs.

Example

First we find out the file's inode. We can do this using either the stat command or ls -i.

stat

$ stat afile 
  File: ‘afile’
  Size: 20          Blocks: 8          IO Block: 4096   regular file
Device: fd02h/64770d    Inode: 6560281     Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/    saml)   Gid: ( 1000/    saml)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2013-12-27 18:40:12.788333778 -0500
Modify: 2013-12-27 18:40:23.103333073 -0500
Change: 2013-12-27 18:44:03.697317989 -0500
 Birth: -

ls -i

$ ls -i 
6560281 afile

With the inode information in hand, we can now open up the filesystem this file resides on using the tool, debugfs.

NOTE: To determine the filesystem a file resides on you can use the command df <filename>.

Now if we run debugfs and run the command stat <inode #> we can get a list of extents that contain this file's data.

$ sudo debugfs -R "stat <6560281>" /dev/mapper/fedora_greeneggs-home
debugfs 1.42.7 (21-Jan-2013)
Inode: 6560281   Type: regular    Mode:  0664   Flags: 0x80000
Generation: 1999478298    Version: 0x00000000:00000001
User:  1000   Group:  1000   Size: 20
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x52be10c3:a640e994 -- Fri Dec 27 18:44:03 2013
 atime: 0x52be0fdc:bbf41348 -- Fri Dec 27 18:40:12 2013
 mtime: 0x52be0fe7:18a2f344 -- Fri Dec 27 18:40:23 2013
crtime: 0x52be0dd8:64394b00 -- Fri Dec 27 18:31:36 2013
Size of extra inode fields: 28
Extended attributes stored in inode body: 
  selinux = "unconfined_u:object_r:user_home_t:s0\000" (37)
EXTENTS:
(0):35304898

Now we have the extents information above, and this is where I get lost and do not know how to proceed.

References

Best Answer

filefrag and debugfs report offset expressed in number of filesystem blocks.

To get the offset in number of 512 byte units, you need to multiply by the size of the block in 512 byte units. On ext4 FS, block size is often 4k, so you need to multiply by 8.

With filefrag, you can also use a -b 512 option to get the offset in 512 byte units.

You can get the block size with the stats command in debugfs, or with GNU stat:

stat -fc%s /mount/point

(or any file in that filesystem).

Note that hdparm is a hard-disk utility, it will try to give the offset within the disk as opposed to the block device the file system is mounted on (assuming that block device does reside on disk somehow). It only works that way for partitions (by adding the content of /sys/class/block/the-block-device/start to the actual offset), and md RAID 1 devices, but not other possibly disk backed block device types like device mapper devices, other RAID levels, dmraid devices, loop, nbd... Also note that older versions of hdparm relied on the FIBMAP ioctl which is limited in what block device it can be use on, while newer versions use FIEMAP like filefrag.

So, for instance, if you have an ext2 filesystem on /dev/sda1.

# hdparm --fibmap /file/in/there
/file/in/there:
 filesystem blocksize 1024, begins at LBA 2048; assuming 512 byte sectors.
 byte_offset  begin_LBA    end_LBA    sectors
           0     109766     109767          2

You can get those two sectors (but note that the file likely uses only part of it):

dd skip=109766 count=2 if=/dev/sda # not /dev/sda1

While with filefrag or debugfs.

# filefrag -v /file/in/there
Filesystem type is: ef53
Filesystem cylinder groups is approximately 12
File size of /file/in/there is 87 (1 block, blocksize 1024)
 ext logical physical expected length flags
   0       0    53859               1 merged,eof

You get it from the actual block device:

dd bs=1024 skip=53859 count=1 if=/dev/sda1