5

In Ubuntu, I have mounted a raw disk .img file as a loop device that contains a LUKS encrypted LVM with an Ubuntu install on it.

It is mounted like so: (output is from lsblk -o NAME,PKNAME,KNAME,FSTYPE,SIZE /dev/loop0

NAME                      PKNAME KNAME FSTYPE        SIZE
loop0                            loop0               240G
├─loop0p1                 loop0  dm-11 ext4          487M
├─loop0p2                 loop0  dm-12                 1K
└─loop0p5                 loop0  dm-13 crypto_LUKS 239.5G
  └─cloneluks             dm-13  dm-14 LVM2_member 239.5G
    ├─ubuntuclone-lv_swap dm-14  dm-15                 8G
    └─ubuntuclone-lv_root dm-14  dm-16 ext4        231.5G

Is there any command that I can use in a script to return the root "block device" (I'm not sure if that's the correct term), when I give the mounted LV name?

I was hoping that lsblk -no pkname /dev/ubuntuclone/lv_root would work, but it outputs nothing - using kname gives me dm-16.

I want to get to loop0.

I also saw this answer which implied I could use "$(basename "$(readlink -f /dev/VG/LV)")", but I couldn't work out how to use it:

 dev=/dev/ubuntuclone/lv_root ; echo "$(basename "$(readlink -f $dev)")"

outputs dm-16.

I can't work out how to get "past" the crypto_LUKS container.

This is what I'm looking for:

for input:

/dev/ubuntuclone/lv_root or ubuntuclone-lv_root

I would like to get output:

/dev/loop0

Thanks.

edit: I think using lsblk --json | jq might be exactly what I want, but I'm having a lot of trouble working out the correct incantations for jq...

1

2 Answers 2

6

You can use --inverse to get the dependencies in reverse order for a device:

$ lsblk --inverse /dev/test/lvol0
NAME        MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINTS
test-lvol0  252:2    0   12M  0 lvm   
└─a         252:1    0   83M  0 crypt 
  └─loop0p1 259:1    0   99M  0 part  
    └─loop0   7:0    0  100M  0 loop

And with this you can add --list to flatten the "tree" structure and then just use tail -1 to print the last row. So for your case the full command would look like this:

$ lsblk /dev/test/lvol0 --inverse --list -o PATH | tail -1
/dev/loop0
1
  • Thank you very much, very tricky! I'm currently trying to see if I can work out how to get it using lsblk --json | jq
    – localhost
    Commented Apr 11 at 9:55
3

My "professional" opinion is that jq is overkill for this task and the simple pipeline in Vojtech Trefny's answer is the best way to go, unless you have some extra factor pushing you toward using JSON. But since you asked, here is one way to do it:

$ lsblk --json | jq --arg path "/dev/ubuntuclone/lv_root" -r '
  ($path | ltrimstr("/dev/") | gsub("/"; "-")) as $name
  | .blockdevices[]
  | select(any(.. | objects | select(.name == $name)))
  | "/dev/\(.name | gsub("-"; "/"))"
'
/dev/loop0

(line breaks in the jq program are only for readability and can be omitted)

The strategy here is basically:

  1. ($path | ltrimstr("/dev/") | gsub("/"; "-")) as $name: Transform the input path, from --arg path <path>, into a block device name by stripping the /dev/ prefix and replacing all / with - (as a bonus, you can pass the block device name instead of a path and this part will be a no-op so it should still work)
  2. .blockdevices[]: Split the top-level blockdevices array into individual elements for further processing
  3. select(any(.. | objects | select(.name == $name))): Among those elements, select only the one (theoretically there should be only one, but if there are multiple, the program will emit them all) that, somewhere in its hierarchy, contains a JSON object with a name field whose value matches $name from step 1
  4. "/dev/\(.name | gsub("-"; "/"))": Extract the name field at the top level of each selected element and convert that device name back into a path. Note that this assumes any - appearing in the block device name should be converted back into a / to get the path, which is trivially true in your example (there are no -s in the name loop0), but if you find a situation where this is not true, you'd have to adapt the logic accordingly.

It's possible this could be made a bit more efficient or at least shorter; I didn't look into optimizing it once I found something that worked.

2
  • 1
    Thank you so much, I never would have worked all that out! I agree, seeing it all written out like that, it does seem a bit like overkill. I thought it might be a bit simpler but I'm an absolute amateur with jq.
    – localhost
    Commented Apr 11 at 23:56
  • 1
    @localhost Understandable. I think over time as you get more experience you'll develop an intuition that tells you jq is not the ideal tool for a task like this. It's a bit hard to explain, but off the top of my head the main factors that I've seen tend to make jq well suited are (1) your input and output both have to be JSON and there is no plain-text alternative, (2) there are many records that need to be processed similarly, and (3) your processing logic is primarily structural transformations, i.e. moving around objects and strings and numbers, rather than changing them.
    – David Z
    Commented 2 days ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.