Saturday, December 14, 2013

Finding the device where a file is stored.

Most references that I found while searching for a simple way to find the device where a particular file is stored pointed to the df command. However, this does not work if the file is managed by a 'virtual' file system type such as ecryptfs that automatically encrypts its stored contents.

  me@pc :~$ df file.txt
  Filesystem        1K-blocks     Used Available Use% Mounted on
  /home/me/.Private 237996696 173473232 52433916 77% /home/me 

One solution could be to take the 'virtual' filesystem and use df to find its underlying file system etc. until a 'real' file system representing a storage device is found.

  me@pc :~$ df /home/me/.Private
  Filesystem     1K-blocks      Used Available Use% Mounted on
  /dev/sda1      237996696 173473824  52433324  77% /

Another way is based on the full pathname of the file: simply find the longest prefix of the file's pathname that corresponds to a mounted device from /dev.
The bash script shown below shows how to do this:
  1. Use the readlink command to obtain the full 'canonical' pathname of the input file name.
  2. Select all /dev devices that are currently mounted from /etc/mtab. The second column of /etc/mtab, referenced as $2 in the awk program, contains the mount point. Since the first character of a string is at index 1 in awk (instead of 0, as in C), the test
    (index(fnm, $2) == 1)
    succeeds iff fnm, which was imported into the awk program using -v fnm=$path, has the mount point $2 as a prefix. In such a case, the mount point path is printed.
  3. After sort, the mount point that is the longest prefix is the last line of the output, selected using the tail command into the $mpoint variable.
  4. It only remains to retrieve the device mounted on $mpoint by selecting appropriately from /etc/mtab.

#!/bin/bash
readonly USAGE="devof file"

function fatal() {
  echo "$1" 2>&1; exit 1
}

[[ $# == 1 ]] || fatal "usage: $USAGE"
[[ -e "$1" ]] || fatal "'$1' not a file";

# readlink -f: follow symbolic links
path=$(readlink -f "$1")
# get paths that are mount points of /dev/* devices
# select those mount points that are a prefix of $path
# grab the longest prefix path $mpoint
mpoint=$(egrep '^/dev' /etc/mtab |
awk -v "fnm=$path" \
    '{ if (index(fnm, $2) == 1) print $2 }' |
  sort | tail --lines=1
)
# now print the device that is mounted on $mpoint
egrep '^/dev' /etc/mtab |
  awk -v "m=$mpoint" '{ if ($2 == m ) print $1 }'

If you are interested in learning more about bash, I can highly recommend this Bash Guide, which assumes that you already know the basics, as explained e.g. in this tutorial.

No comments:

Post a Comment