2

I got a little script that lists the number of inotify watches per process. That usually gets me what I want, but now I would like to know which files are being watched. I assume this is possible and that a inotify watch corresponds to a file being monitored by an inotify instance?

I also assume that I can build upon what I currently have in that script. For instance,

sudo find /proc/*/fd -lname anon_inode:inotify  | cut -d "/" -f 3

gets me a list of processes with inotify file descriptors. If I look at the info for one of the file descriptors, I get what I assume is a list of file handles/watches:

$ sudo cat /proc/50679/fdinfo/19
pos:    0
flags:  00
mnt_id: 15
inotify wd:8 ino:640001 sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:01006400feaad211
inotify wd:7 ino:a08da sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:da080a0094019e8f
inotify wd:6 ino:840003 sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:030084005ae9e3df
inotify wd:5 ino:840002 sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:020084000d506c1f
inotify wd:4 ino:840001 sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:01008400e47bab26
inotify wd:3 ino:32004e sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:4e003200488122df
inotify wd:2 ino:320001 sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:01003200545a9f32
inotify wd:1 ino:2 sdev:800001 mask:3cc ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:0200000000000000

I was hoping I could find out which file f_handle:01003200545a9f32 corresponds to, basically translate a f_handle in /proc/../fdinfo/ to a file name.

2
  • According to this discussion (external), it seems this is not possible: "Using the inotify extension only it is not possible to get the full path to the file (directory) from the event structures. Your application code will need special lookup tables storing links between watch descriptors and full path names."
    – oligofren
    Apr 20 at 12:49
  • Workaround using strace: hassansin.github.io/…
    – oligofren
    Apr 20 at 13:46
1

I don't know about standard tools that deal with the f_handle field. That would be convenient what with the open_by_handle_at(2) syscall, but anyways that field may not always be valid. The kernel's nfsd for instance does not provide it.

However, the full coordinates of any file in Linux are still the good-old device number and inode number, which are reported in the sdev and ino fields. It's only a matter of decoding them.

Those two values are (currently) expressed in hexadecimal notation. You can take the ino as is, just convert it into decimal notation.

The sdev value on the other hand needs some decoding because you need to split it into the traditional "major and minor" device numbers. Note that even files residing in filesystems not backed by actual block-devices still carry a unique pseudo-device number, which is reported in that sdev field.

Assuming that the sdev field is encoded according to Linux's so-called "huge encoding", which uses 20 bits (instead of 8) for minor numbers, in bitwise parlance the major number is sdev >> 20 while the minor is sdev & 0xfffff. Or using a layman text-manipulation approach, the minor number is the rightmost (up to) 5 hex-digits while the major number is everything before the final 5 hex-digits. If there are fewer than 5 hex-digits, the major number is simply 0.

Once major & minor are obtained, you go looking for them into the target process's mountinfo file. In your example that would be /proc/50679/mountinfo. Specifically you look for a line carrying such major:minor pair as third field. The found line's fifth field is the path you need for a final find that goes hunting for the wanted file/dir.

Note: major & minor obtained from an inotify line in /proc/*/fdinfo/* are expressed in hex-notation, but in mountinfo are expressed in decimal, so you need to convert them before searching them in mountinfo.

Note: the fifth field in mountinfo may contain \-escaped octal sequences in case the \ itself or <space>, <newline>, <tab> characters are part of the path. Meaning that a whitespace is encoded as \040, a \ as \134, and so on. You can unescape those by e.g. feeding that path to printf(1)'s own %b specifier.

Note: in order to account for namespaces (i.e. containers) you need to run the final find command within the mount-namespace that the target process lives into, hence for instance something like (for your example):

nsenter -mt 50679 find "$unescaped_path" -inum "$decimal_ino" -print -quit
4
  • That's a truckload of great information. Thank you! The steps needed for conversion was quite involved, so I'll need to put it on the backburner before I will have time to test it out. If it works, I'll gladly award you the answer, of course. Will be interesting if I can get it to work in my bash script without resorting to making utils in C. Bash does support bitshifting, after all :)
    – oligofren
    May 5 at 11:32
  • 1
    @oligofren You most certainly will be able to get it to work in Bash as I've already done it myself (a 5 lines proof-of-concept). I didn't include any Bash code mainly because I personally wouldn't do the whole thing in Bash at all (it'd be slow and would be a bit quirky to get it secure), and also because you would likely need to rewrite it anyway in order to integrate everything in your existing script plus you seem more than capable of doing it yourself the way you see fit.
    – LL3
    May 5 at 13:52
  • OK, thanks. With regards to "security", do you mean any of the above would require sudo to work? Not totally sure what I should be worried about when it comes to a local script :)
    – oligofren
    May 5 at 14:42
  • 1
    @oligofren Yes, reading from fdinfo requires privileges as you already know, then nsenter requires them too. Then in general reading&parsing text in shell is inherently a delicate operation as it requires quoting, or (when you need not to quote) accounting for IFS, unwanted pathname expansion, sanitization. Your script is local but some of the data it reads is foreign (e.g. path names). Even some input from the kernel (though presumably not malicious) needs correct parsing, and possibly graceful handling of unexpected data e.g. fields that get relocated or reformatted
    – LL3
    May 5 at 15:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.