Dependency Hell na Unixie

Pewnego dnia widzę, że aplikacja webowa, którą uruchomiłem kilka dni temu, przestała działać. Zanim doszedłem do tego, co się dzieje, musiałem chwilę pomyśleć, by odtworzyć ciąg przyczynowo-skutkowy.

  • Aplikacja korzysta z bibliotek natywnych.
  • Biblioteki sam kompilowałem na hostingu.
  • Biblioteki są dynamicznie linkowane do bibliotek systemowych.
  • Administrator hostingu zrobił update.

Skutek był prosty, linker nie mógł już rozwiązać zależności, bo wersje bibliotek się nie zgadzały. Musiałem ponownie zbudować wszystkie biblioteki. By zabezpieczyć się na przyszłość, postanowiłem wykonać kopię wszystkich potrzebnych zależności dla mojej aplikacji.

Pomysł na rozwiązanie problemu był prosty, przeszukać zależności i wykonać kopię. Niektórzy na Windowsie mogli mieć do czynienia z podobnym problemem, tam wykorzystuje się narzędzie Dependency Walker. Na Unixach wystarczy odpalić ldd, by poznać, jakich bibliotek potrzebuje biblioteka.

ldd /usr/lib/libpng.so
	linux-vdso.so.1 (0x00007ffca6be7000)
	libz.so.1 => /usr/lib/libz.so.1 (0x00007fac5a048000)
	libm.so.6 => /usr/lib/libm.so.6 (0x00007fac59d43000)
	libc.so.6 => /usr/lib/libc.so.6 (0x00007fac599a2000)
	/usr/lib64/ld-linux-x86-64.so.2 (0x0000557eefee7000)

Wzorując się na skrypcie stąd, rozszerzyłem jego możliwości. Zamiast podawać każdą bibliotekę z osobna, należy podać katalog z własnymi bibliotekami. Dla nich zostanie stworzona kopia.
#!/bin/bash
function useage()
{
    cat << EOU
Useage: bash $0 <path to the libraries> <path to copy the dependencies>
EOU
exit 1
}

#Validate the inputs
[[ $# < 2 ]] && useage

#Check if the paths are valid
[[ ! -e $1 ]] && echo "Not a valid input $1" && exit 1
[[ -d $2 ]] || echo "No such directory $2 creating..."&& mkdir -p "$2"

exec=`find $1 -type f -exec file {} + | grep ELF | awk -F":" '{print $1}' | xargs readlink -f`

#Get the library dependencies
echo "Collecting the shared library dependencies for $1..."
deps=$exec
for el in $exec
do
deps=$(ldd $el | awk 'BEGIN{ORS=" "}$1\
~/^\//{print $1}$3~/^\//{print $3}'\
 | sed 's/,$/\n/')$deps
done

#remove duplicates
deps=$(readlink -f $deps | sort | uniq)

#remove dependencies to itselfs
deps=$(echo -e "$deps\n$exec" | sort | uniq -u)

echo "Copying the dependencies to $2"

#Copy the deps
for dep in $deps
do
    echo "Copying $dep to $2"
    cp "$dep" "$2"
done

echo "Done!"

W podanym katalogu zostaną znalezione wszystkie biblioteki i binarki (kod wykonywalny w formacie ELF), a następnie rozwiązane zostaną dla nich symlinki do ścieżek bezwzględnych. Pierwotnym założeniem jest to, że katalog z bibliotekami znajduje się w ścieżce linkera (biblioteki muszą działać zanim zostaną skopiowane zależności).

Następnym krokiem jest przeszukanie zależności, wywalenie duplikatów poleceniami sort i uniq, a następnie wywalenie zależności do bibliotek, które już znajdują się w podanym do skryptu katalogu.

Przykładowo

LD_LIBRARY_PATH=/home/globalbus/lib:/usr/local/lib:/usr/local/lib/gcc48/
bash cpld.sh /home/globalbus/lib/ shadow
No such directory shadow creating...
Collecting the shared library dependencies for lib/...
Copying the dependencies to shadow
Copying /lib/libc.so.7 to shadow
Copying /lib/libcrypt.so.5 to shadow
Copying /lib/libcrypto.so.7 to shadow
Copying /lib/libcxxrt.so.1 to shadow
Copying /lib/libm.so.5 to shadow
Copying /lib/libthr.so.3 to shadow
Copying /lib/libz.so.6 to shadow
Copying /usr/lib/libasn1.so.11 to shadow
Copying /usr/lib/libc++.so.1 to shadow
Copying /usr/lib/libcom_err.so.5 to shadow
Copying /usr/lib/libgssapi.so.10 to shadow
Copying /usr/lib/libgssapi_krb5.so.10 to shadow
Copying /usr/lib/libheimbase.so.11 to shadow
Copying /usr/lib/libheimntlm.so.11 to shadow
Copying /usr/lib/libhx509.so.11 to shadow
Copying /usr/lib/libkrb5.so.11 to shadow
Copying /usr/lib/liblzma.so.5 to shadow
Copying /usr/lib/libroken.so.11 to shadow
Copying /usr/lib/librt.so.1 to shadow
Copying /usr/lib/libssl.so.7 to shadow
Copying /usr/lib/libwind.so.11 to shadow
Copying /usr/lib/private/libheimipcc.so.11 to shadow
Copying /usr/local/lib/gcc48/libgcc_s.so.1 to shadow
Copying /usr/local/lib/gcc48/libstdc++.so.6.0.19 to shadow
Copying /usr/local/lib/libcurl.so.4.4.0 to shadow
Copying /usr/local/lib/libexpat.so.1.6.0 to shadow
Copying /usr/local/lib/libgeos-3.5.0.so to shadow
Copying /usr/local/lib/libgeos_c.so.1.9.0 to shadow
Copying /usr/local/lib/libgeotiff.so.2.1.1 to shadow
Copying /usr/local/lib/libgif.so.7.0.0 to shadow
Copying /usr/local/lib/libiconv.so.2.5.1 to shadow
Copying /usr/local/lib/libintl.so.8.1.4 to shadow
Copying /usr/local/lib/libjasper.so.4.0.0 to shadow
Copying /usr/local/lib/libjbig.so.2 to shadow
Copying /usr/local/lib/libjpeg.so.8.0.2 to shadow
Copying /usr/local/lib/libjson-c.so.2.0.1 to shadow
Copying /usr/local/lib/libopenjp2.so.2.1.0 to shadow
Copying /usr/local/lib/libpcre.so.1.2.5 to shadow
Copying /usr/local/lib/libpng16.so.16.21.0 to shadow
Copying /usr/local/lib/libpq.so.5 to shadow
Copying /usr/local/lib/libproj.so.9.0.0 to shadow
Copying /usr/local/lib/libsqlite3.so.0.8.6 to shadow
Copying /usr/local/lib/libssh2.so.1.0.1 to shadow
Copying /usr/local/lib/libtiff.so.5.2.4 to shadow
Copying /usr/local/lib/libxml2.so.2.9.3 to shadow
Done!

Teraz można użyć katalogu z kopią bibliotek, dodając go do ścieżki linkera
LD_LIBRARY_PATH=/home/globalbus/lib:/home/globalbus/shadow:/usr/local/lib:/usr/local/lib/gcc48/

Dzięki wykonaniu kopii, zmiana wersji bibliotek systemowych nie skutkuje przerwaniem pracy aplikacji.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *