portage-utils icon indicating copy to clipboard operation
portage-utils copied to clipboard

qfile: Detect matches with canonicalised target directory

Open BalkanMadman opened this issue 9 months ago • 5 comments

On certain systems, e.g. merged-usr (/{,s}bin and /usr/sbin are symlinks to /usr/bin), files that are installed to symlinked directories (/sbin etc.) actually reside in /usr/bin. Previously, qfile would only resolve (canonicalise -- i.e. call realpath(3) on argument's directory) the argument's directory, but not the target's.

This resulted in qfile's inability to detect that /sbin/init in sysvinit's vdb CONTENTS file refers to the same file /usr/bin/init, since /sbin is never canonicalised to /usr/bin. As such, the following line would not work on merged-usr systems, although /sbin/init and /usr/bin/init point to the exact same files:

$ qfile /usr/bin/init
## No output ##

whereas

$ qfile /sbin/init
sys-apps/sysvinit: /sbin/init

(To be precise, qfile both resolved and did not resolve the argument's dirname, producing a match if either the resolved or non-resolved dirname matched).

BalkanMadman avatar Mar 19 '25 18:03 BalkanMadman

Tests do pass. Additionally, this fixes a bug when ROOT is set. Previously, as far as I understand, it worked purely by accident:

https://github.com/gentoo/portage-utils/blob/1833294774ebfb4bcc1464e6ae28e71da0f6babf/qfile.c#L257-L278

This line

https://github.com/gentoo/portage-utils/blob/1833294774ebfb4bcc1464e6ae28e71da0f6babf/qfile.c#L272

is meant to compare ROOT-stripped target and source dirname. What it actually does, though, is comparing the full path (including the basename), which, obviously, yields false. The reason why qfile worked successfully with ROOT set is because this branch wasn't even taken. Due to the fact that dir_names contain unprefixed paths, a match is yielded a bit up, in these branches:

https://github.com/gentoo/portage-utils/blob/1833294774ebfb4bcc1464e6ae28e71da0f6babf/qfile.c#L236-L248

e->name holds the path that is being checked and dirname_len is the length of its directory component.

BalkanMadman avatar Mar 19 '25 18:03 BalkanMadman

so what does your qfile do if you call it on /usr/sbin/init?

grobian avatar Mar 20 '25 18:03 grobian

Outputs nothing. I'm not on my laptopt right now, I will add you some example queries in a few minutes.

BalkanMadman avatar Mar 20 '25 19:03 BalkanMadman

Here is a sample.

$ ls -la /bin /sbin /usr/sbin
lrwxrwxrwx 1 root root 7 Sep  1  2024 /bin -> usr/bin
lrwxrwxrwx 1 root root 7 Sep  1  2024 /sbin -> usr/bin
lrwxrwxrwx 1 root root 3 Sep  1  2024 /usr/sbin -> bin
$ eselect profile show
Current /etc/portage/make.profile symlink:
  default/linux/amd64/23.0/desktop
$ which init
/usr/bin/init
$ qfile /sbin/init
sys-apps/sysvinit: /sbin/init
$ qfile /usr/bin/init
$ qfile /usr/sbin/init
$ qfile /bin/init
$ which bash
/usr/bin/bash
$ qfile /bin/bash
app-shells/bash: /bin/bash
$ qfile /usr/bin/bash
$ qfile /sbin/bash
$ qfile /usr/sbin/bash

BalkanMadman avatar Mar 20 '25 23:03 BalkanMadman

I have merged-usr layout.

$ equery --quiet uses --forced-mask baselayout
-build
(-split-usr)

BalkanMadman avatar Mar 20 '25 23:03 BalkanMadman

qfile not working well with symlinks breaks the needrestart tool I use on my server. The tool restarts services, when the respective running binaries are updated. needrestart uses qfile to map binary to service in /etc/init.d. Since qfile fails on symlinks, sometimes needrestart is unable to find the respective service and restart the daemon.

Please, merge this PR, since the described use case is a very important part of keeping my server (and not only my, I suspect) up to date and secure.

BalkanMadman avatar Apr 15 '25 16:04 BalkanMadman

what this requires is an option -L to qfile that tells it to follow symlinks, so needrestart can use that

grobian avatar Apr 15 '25 18:04 grobian

The problem is not that symlinks are not resolved, but that the source directory (in case of sysvinit it is /sbin) is never canonicalised.

When qfile /usr/bin/init is executed (/usr/bin is not a symlink, but /sbin is) strcmp("/sbin", "/usr/bin") obviously fails. This commit canonicalises the source directory before calling strcmp, turning the source directory /sbin into /usr/bin.

BalkanMadman avatar Apr 15 '25 20:04 BalkanMadman

/bin is a symlink to usr/bin (on my systems).

The package manager records for bash:

% grep bin /var/db/pkg/app-shells/bash-5.2_p37/CONTENTS
dir /bin
sym /bin/rbash -> bash 1728503901
obj /bin/bash 5ae743380531ae9ad35c990a262a1349 1728503904
dir /usr/bin
obj /usr/bin/bashbug 0f5183015431921d4bca5c0cde23848c 1728503900

That is, /bin/bash.

In order to match /usr/bin/bash onto app-shells/bash-5.2_p37, we'd have to expand all entries in CONTENTS files, as opposed to the source.

This is kind of awkward, and thus we should only do this on demand.

Do I have understood the problem correctly now?

grobian avatar Apr 18 '25 17:04 grobian