Programming way to list shared library dependency on linux - linux

Is there any programming way (system call?) to list shared library dependency on Linux? Instead of using ldd ...

readelf -Wa lib.so|grep NEEDED

Gentoo Linux has an lddtree.sh
http://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-projects/pax-utils/lddtree.sh?revision=1.22&content-type=text%2Fplain
You may find it helpful.

Set LD_TRACE_LOADED_OBJECTS environment variable to non-empty string and run your binary. Look at this man page.
LD_TRACE_LOADED_OBJECTS
(ELF only) If set to non-empty string, causes the program to list its dynamic library dependencies, as if run by ldd(1), instead of running normally.

This is the simple bash script I use myself on Fedora, it relies on find-requires of rpm package, you can look inside find-requires to find what tools it internally uses.
#!/bin/bash
#
# Use rpm to recursively list dependencies of all files in a directory
#
# Syntax:
# lsdep path/to/directory
# Example:
# lsdep /usr/src/kernels/`uname -r`/
find $1 -type f -exec sh -c 'res=`echo '{}' | /usr/lib/rpm/find-requires`; [ -n "$res" ] && (echo;echo file '{}'; echo $res)' \;

Related

Removing paths from .so files so that RPM check-buildroot succeeds

I am packaging some Python libraries as RPMs. Some of the libraries are only available as source distributions (no wheels).
In my RPM spec I do:
pip install --root=%{buildroot} --prefix=/x/y tornado
When rpmbuild finishes up it runs check-buildroot, and the build fails with errors like:
Binary file /a/b/c/BUILDROOT/my-rpm-1.0.0-1.el7.x86_64/x/y/lib64/python2.7/site-packages/tornado/speedups.so matches
I see the %{buildroot} path listed if I run strings tornado.so | grep BUILDROOT.
How can I sanitize the .so files? Or more generally, how can I make check-buildroot pass?
I figured out how to remove the paths from the SO files.
I determined that the paths were embedded debug information using this command:
readelf --debug-dump=line speedups.so | less
The strip command can remove debug information from SO files, so I added this to my RPM spec:
BuildRequires: binutils
set +e
find "%{buildroot}{%_prefix}/lib64/python2.7/site-packages" -type f -name "*.so" | while read so_file
do
strip --strip-debug "$so_file"
done
set -e
Note: strip segfaults on some SO files, and it's not clear why. I disabled immediate exits with set +e so that the build ignores them.

Can I use /dev/null for the find command?

I'm updating a shell script that uses the find command that follows symlinks:
find -L somedir ...
However, on some older platforms, the -L isn't supported and the command must use the older -follow syntax:
find somedir -follow ...
The "-follow" flag is deprecated on newer systems, so my strategy is to test if the command works with the newer -L flag, and if not fall back on the -follow flag.
The script currently runs on RedHawk 5.4.11, but the find incompatibility was discovered on an older Linux version. I was directed to make this work on all Unix/Linux platforms.
So, while creating a dummy find command to test, I'm creating an empty temp directory in /tmp for the find command to return quickly. I then found out that mktemp -d is not supported on the older systems, so I was going to create one the old-fashioned way.
It then dawned on me, "why not just try /dev/null as a temp dir instead of creating one?" So I tried the command:
TEMPDIR=/dev/null
FIND_L_SUPPORTED=`find -L $TEMPDIR &> /dev/null; echo $?`
and it seems to work, but I'm not sure why (since /dev/null is not a directory), or if it's reliable on all platforms.
Two questions:
Is using find against /dev/null reliable on all platforms?
Any other solutions to my original find problem, where some platforms need -L but others need -follow?
Trying to support unknown platforms is not productive. Just because you get one non-standard aspect right (e.g., replacing -L with -follow) doesn't mean there aren't other non-standard behaviors you don't know about that could cause your script to break.
Instead, write your script to support the standard by default, but provide a flag that users can explicitly set to support known older platforms. For example,
if [ "$NON_STANDARD_FIND" = "no-L" ]; then
find somedir -follow ...
else
find -L somedir ...
fi
Then run the script as
my_script ...
or
NON_STANDRD_FIND=no-L my_script ...
as necessary.
In the end, document the platforms you know you can support and how to run your script correctly on those platforms. Users of other platforms should only run your script at their peril.

Retrieving current architecture for RPM

I'm automating RPM package building with rpmbuild. The files end up in the architecture subdirectory under RPMS.
Question - how do I retrieve, from a shell script, the architecture name of the host that RPM is using? It's not the same as arch command.
It looks like
rpm --eval '%{_arch}'
does the trick:
$ rpm --eval '%{_arch}'
x86_64
$ rpm --target 'SPARC64' --eval '%{_arch}'
sparc64
There's /usr/lib/rpm/rpmrc that translates known OS-level architecture names into canonical RPM architecture names. The following shell script does the job for me:
ARCH=`arch`
# OS-level architecture name, like 'i686'
ARCH=`cat /usr/lib/rpm/rpmrc | grep "buildarchtranslate: $ARCH" | cut -c21-`
# returns the translate line as "arch-from: arch-to"
ARCH=${ARCH/#*: /}
# strips the prefix up to colon and following space, returns arch-to.
# Assumes just one space after colon. If not, more regex magic is needed.
You're doing it wrong. Redefine %_build_name_fmt in ~/.rpmmacros.

List CPAN modules that have been made but not installed

Is there an easy/clean way to do this in Linux/ a Linux-like environment?
Purpose
My aim is to run CPAN with admin permissions only during the installation phase, not at the get/make/test phases.
The CPAN configuration items make_install_make_command and mbuild_install_build_command deal with this. Change them to enable sudo support.
Assuming you're using CPAN.pm for that, I have a somewhat unorthodox suggestion.
Make a subclass of CPAN.pm, which actually publishes the results/stages of each module it works with to a registry (via a suplied callback API to make the registry implementation flexible).
Then you need to simply check that registry.
(or you can try to put that as a patch into CPAN.pm itself)
For the sake of documenting an approach that seems promising, but doesn't work - the shell command:
find . -type d -mindepth 1 -maxdepth 1 -print | while read -r DIR; do pushd $DIR; make -q; mk=$?; make -q install; inst=$?; make -q test; tst=$?; echo Directory "$DIR $mk $inst $tst"; popd; done| fgrep -ve /build
when executed in the cpan build dir lists the exit statuses of make -q for "", "test" and "install", which says whether that make goal needs any work to achieve.
But all have nonzero exit statuses, which means they all will do something if you execute them, even if the make has successfully been completed. So you can't tell anything this way.

How can a bash script know the directory it is installed in when it is sourced with . operator?

What I'd like to do is to include settings from a file into my current interactive bash shell like this:
$ . /path/to/some/dir/.settings
The problem is that the .settings script also needs to use the "." operator to include other files like this:
. .extra_settings
How do I reference the relative path for .extra_settings in the .settings file? These two files are always stored in the same directory, but the path to this directory will be different depending on where these files were installed.
The operator always knows the /path/to/some/dir/ as shown above. How can the .settings file know the directory where it is installed? I would rather not have an install process that records the name of the installed directory.
I believe $(dirname "$BASH_SOURCE") will do what you want, as long as the file you are sourcing is not a symlink.
If the file you are sourcing may be a symlink, you can do something like the following to get the true directory:
PRG="$BASH_SOURCE"
progname=`basename "$BASH_SOURCE"`
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
dir=$(dirname "$PRG")
Here is what might be an elegant solution:
script_path="${BASH_SOURCE[0]}"
script_dir="$(cd "$(dirname "${script_path}")" && pwd)"
This will not, however, work when sourcing links. In that case, one might do
script_path="$(readlink -f "$(readlink "${BASH_SOURCE[0]}")")"
script_dir="$(cd "$(dirname "${script_path}")" && pwd)"
Things to note:
arrays like ${array[x]} are not POSIX compliant - but then, the BASH_SOURCE array is only available in Bash, anyway
on macOS, the native BSD readlink does not support -f, so you might have to install GNU readlink using e.g. brew by brew install coreutils and replace readlink by greadlink
depending on your use case, you might want to use the -e or -m switches instead of -f plus possibly -n; see readlink man page for details
A different take on the problem - if you're using "." in order to set environment variables, another standard way to do this is to have your script echo variable setting commands, e.g.:
# settings.sh
echo export CLASSPATH=${CLASSPATH}:/foo/bar
then eval the output:
eval $(/path/to/settings.sh)
That's how packages like modules work. This way also makes it easy to support shells derived from sh (X=...; export X) and csh (setenv X ...)
We found $(dirname "$(realpath "$0")") to be the most reliable with both sh and bash. As team mates used them interchangeably, we ran into problems with $BASH_SOURCE which is not supported by sh.
Instead, we now rely on dirname, which can also be stacked to get parent, or grandparent folders.
The following example returns the parent dir of the folder that contains the .sh file:
parent_path=$(dirname "$(dirname "$(realpath "$0")")")
echo $parent_path
I tried messing with variants of $(dirname $0) but it fails when the .settings file is included with ".". If I were executing the .settings file instead of including it, this solution would work. Instead, the $(dirname $0) always returns ".", meaning current directory. This fails when doing something like this:
$ cd /
$ . /some/path/.settings
This sort of works. It works in the sense that you can use the $(dirname $0) syntax within the .settings file to determine its home since you are executing this script in a new shell. However, it adds an extra layer of convolution where you need to change lines such as:
export MYDATE=$(date)
to
echo "export MYDATE=\$(date)"
Maybe this is the only way?

Resources