Useful CLI tools (bash) for Arch Linux administration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

463 lines
22 KiB

5 years ago
  1. #!/bin/bash
  2. # Shared library & executable dependency finder for Linux
  3. # Copyright (C) 2018 Pekka Helenius
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. ######################################################################################################################
  18. # TODO print stored dependency packages as red if errors encountered
  19. # TODO check packages themselves, too (not only their dependencies...)
  20. # TODO
  21. # BUG :
  22. #
  23. # Optional dependencies for linux :
  24. #
  25. # crda: to set the correct wireless channels of your country
  26. #
  27. # Required dependencies for linux :
  28. #
  29. # coreutils Found libraries/executables: 102
  30. # grep: Unmatched [ or [^
  31. # grep: Invalid regular expression
  32. # TODO
  33. # Depends on: libtinfo.so.6 Provided by:
  34. # ei anna oikeaa tulosta...
  35. # TODO
  36. #Error: octopi not found. Skipping.
  37. # Lisää tähän "do you mean...<package>?...logiikka
  38. # -------------------------------------------
  39. # BUG
  40. # Ignore rules.d folder since it gives permission denied error
  41. # -------------------------------------------
  42. # BUG
  43. #1/1 (100%) - ncurses
  44. #Required dependencies for ncurses :
  45. # glibc Found libraries/executables: 302
  46. #ldd: warning: you do not have execution permission for `/usr/lib/libcursesw.so'
  47. #error: failed to find 'dynamic' in PATH: No such file or directory
  48. #ldd: warning: you do not have execution permission for `/usr/lib/libform.so'
  49. #error: failed to find 'dynamic' in PATH: No such file or directory
  50. #ldd: warning: you do not have execution permission for `/usr/lib/libmenu.so'
  51. #error: failed to find 'dynamic' in PATH: No such file or directory
  52. #ldd: warning: you do not have execution permission for `/usr/lib/libncurses++.so'
  53. #error: failed to find 'dynamic' in PATH: No such file or directory
  54. # gcc-libs-multilib Found libraries/executables: 17
  55. # -------------------------------------------
  56. # BUG
  57. # When using command 'pkgdeps wine-git', the following error occurs:
  58. #...
  59. # lib32-libsm Found libraries/executables: 1
  60. #error: failed to find 'dynamic' in PATH: No such file or directory
  61. #error: failed to find 'dynamic' in PATH: No such file or directory
  62. ######################################################################################################################
  63. # Look for missing library files for installed packages
  64. red=$'\33[91m'
  65. orange=$'\033[38;5;208m'
  66. yellow=$'\033[93m'
  67. green=$'\033[92m'
  68. blue=$'\033[94m'
  69. bold=$'\033[1m'
  70. reset=$'\033[0m'
  71. COMMANDS=(sudo pacman date ping package-query)
  72. for com in $COMMANDS; do
  73. if [[ ! $(which $com |grep "which: no $com in" | wc -w) -eq 0 ]]; then
  74. echo -e "\nCommand $com not found. Can't run the script.\n"
  75. return
  76. fi
  77. done
  78. echo -e "\n${bold}Dependency tracker - Find broken executables & libraries${reset}"
  79. if [[ $# -eq 1 ]]; then
  80. pacman -Q $1 2> /dev/null #|| echo 'something' # TODO silent normal output!
  81. if [[ $? -eq 1 ]]; then
  82. seterror=1
  83. else
  84. seterror=0
  85. fi
  86. else
  87. seterror=0
  88. fi
  89. function checkconnection() {
  90. PROVIDER="luna.archlinux.org"
  91. INTERNET_TEST=$(ping -c 1 $PROVIDER 2>&1 | grep -c "Name or service not known")
  92. if [[ ! $INTERNET_TEST -eq 0 ]]; then
  93. echo -e "\n${red}Error:${reset} Can't connect to $PROVIDER. Please check your internet connection.\n"
  94. else
  95. echo -e "\nUpdating file databases with pacman (root required)\n"
  96. sudo pacman -Fy
  97. fi
  98. }
  99. LOGFILE=/var/log/pacman.log
  100. if [[ -f $LOGFILE ]] && [[ $seterror -ne 1 ]]; then
  101. LASTRUN=$(date --date="$(awk '/pacman -Fy/ {a=$0} END{print a}' $LOGFILE | awk -F'[][]' '{print $2}')" +"%s")
  102. if [[ $? -ne 0 ]]; then
  103. echo -e " ${red}Error:${reset} Can't parse the latest 'pacman -Fy' date value from pacman log file $LOGFILE! Skipping database update.\n"
  104. else
  105. if [[ $(echo $LASTRUN | wc -w) -eq 1 ]]; then
  106. CURTIME=$(date +"%s")
  107. LASTUPDATE=$(( ($CURTIME - $LASTRUN)/60 ))
  108. TIME_THRESHOLD=180 # 3 hours
  109. if [[ $LASTUPDATE -ge $TIME_THRESHOLD ]]; then
  110. echo -e "\nPackage databases updated $LASTUPDATE minutes ago.\n" # TODO minutes....hours...days...ago...
  111. read -r -p "Do you want to update databases now? [y/N] " dbupdate
  112. if [[ $(echo $dbupdate | sed 's/ //g') =~ ^([yY][eE][sS]|[yY])$ ]]; then
  113. checkconnection
  114. else
  115. echo -e "\nSkipping database update.\n"
  116. fi
  117. else
  118. echo -e "\nPackage databases are updated.\n"
  119. fi
  120. else
  121. echo -e "\nPrevious database update timestamp not found. Updating...\n"
  122. checkconnection
  123. fi
  124. fi
  125. elif [[ ! -f $LOGFILE ]]; then
  126. echo -e "\n ${red}Error:${reset} Pacman log file not found.\n"
  127. fi
  128. pkgcount=$#
  129. curpkgcount=1
  130. typeset -A CHECKEDLIST
  131. # Loop through all input arguments (package names)
  132. for p in $(echo "${@}"); do # TODO show how many dependencies this package has if more than 1
  133. LOCALDIRS=("/usr/lib" "/usr/bin" "/usr/lib/" "/opt/")
  134. w=0
  135. for dir in $(pacman -Ql $p 2> /dev/null | awk '{print $2}'); do
  136. if [[ -d $dir ]]; then
  137. MAINPKGDIRS[$w]="${dir}"
  138. let w++
  139. fi
  140. done
  141. echo -e "----------------------------------------"
  142. echo -e "\n$curpkgcount/$pkgcount ($((100*$curpkgcount/$pkgcount))%) - ${orange}$p${reset}"
  143. # Check that the input package can actually be found
  144. if [[ $(pacman -Qi $p &>/dev/null || echo "was not found" | wc -l) -eq 1 ]]; then
  145. echo -e "\n ${red}Error:${reset} ${orange}$p${reset} not found. Skipping.\n"
  146. else
  147. # Parse dependencies of the selected package to a string list
  148. PKGDEPS=$(pacman -Qi $p | grep "Depends On" | sed 's/.*: //g; s/ / /g')
  149. #Parse optional dependencies
  150. PKGDEPS_OPT=$(pacman -Qi $p | grep "Optional Deps" | sed 's/.*\s: //; s/ / /')
  151. # Loop for each package ([@] means array value X)
  152. # We circumvent a pacman bug/issue here
  153. # pacman does a hard fixed listing for packages, meaning it doesn't recognize "provides" option
  154. # This is mainly a problem of git package versions
  155. #
  156. if [[ $PKGDEPS_OPT != "None" ]]; then
  157. echo -e "\nOptional dependencies for ${orange}$p${reset} :\n\n $PKGDEPS_OPT"
  158. fi
  159. if [[ $PKGDEPS != "None" ]]; then
  160. echo -e "\nRequired dependencies for ${orange}$p${reset} :\n" # TODO go even one step deeper with these!
  161. for i in $(echo "${PKGDEPS[@]}"); do
  162. function parseversion() {
  163. pattern="[<=>]"
  164. if [[ $i =~ $pattern ]]; then
  165. i=$(echo $i | awk -F'[<=>]' '{print $1}')
  166. # TODO do version check here as done in 'risks' function?
  167. #i_ver=$(echo $i | awk -F'[<=>]' '{print $3}')
  168. #return $pkgname $pkgver
  169. #else
  170. #return $i
  171. fi
  172. }
  173. parseversion
  174. # Error (package was not found)
  175. if [[ $(pacman -Qi $i &>/dev/null || echo "was not found" | wc -l) -eq 1 ]]; then
  176. # Get name of the package which provides the dependency
  177. ALTDEP=$(pacman -Qi | grep -iE "Name|Provides" | grep $i | grep "Name" | sed 's/.*: //')
  178. o=0
  179. for altdep in $ALTDEP; do
  180. ALTDEPS[$o]=$altdep
  181. let o++
  182. done
  183. function deepscan() {
  184. MAINPKGFILES=$(pacman -Ql $p | awk '{print $2}')
  185. for mainpkgfile in $MAINPKGFILES; do
  186. if [[ $(mimetype "${mainpkgfile}" | grep -iE "x\-sharedlib|x\-executable" | wc -l) -eq 1 ]]; then
  187. for deepscan in $(ldd "${mainpkgfile}" | awk '{print $3}' | sed '/^\s*$/d'); do
  188. OWNER_DEEPSCAN=$(pacman -Qo $deepscan | awk '{print $5}')
  189. for item in "${ALTDEPS[@]}"; do
  190. if [[ $OWNER_DEEPSCAN == "$item" ]]; then
  191. i=$OWNER_DEEPSCAN
  192. return 0
  193. fi
  194. done
  195. done
  196. fi
  197. done
  198. return 1
  199. }
  200. deepscan
  201. if [[ $? -eq 1 ]]; then
  202. #Get 'provides' from arch package database
  203. FETCHEDPKGS=$(package-query --qprovides "$i" -Q -f "%n")
  204. k=0
  205. for fetch in $FETCHEDPKGS; do
  206. if [[ ! $(pacman -Q $fetch &>/dev/null || echo "was not found" | wc -l) -eq 1 ]]; then
  207. i="$fetch"
  208. break
  209. # TODO show here the true 'depends on' stuff, not just 'provided by' package? bash <-> sh conversion for example
  210. else
  211. echo -e " ${yellow}Warning:${reset} Dependency '$i' not found and not recognized as a provided package"
  212. i="notfound"
  213. NOTFOUNDS[$k]=$i
  214. let k++
  215. fi
  216. done
  217. fi
  218. fi
  219. if [[ $i != "notfound" ]] && [[ -z ${CHECKEDLIST[$i]} ]]; then
  220. FILES=$(pacman -Ql $i | awk '{print $2}')
  221. x=0
  222. printf " %-30s%s\r" "$i" "Searching libraries/executables"
  223. for mimefile in $FILES; do
  224. if [[ $(mimetype "${mimefile}" | grep -iE "x\-sharedlib|x\-executable" | wc -l) -eq 1 ]]; then
  225. MIMEFILES[$x]="${mimefile}"
  226. let x++
  227. printf " %-30s%s\r" "$i" "Found libraries/executables: $x " #Yes, the trailing space is meant to be here
  228. fi
  229. done
  230. if [[ $x -gt 0 ]]; then
  231. CHECKEDLIST[$i]=$(printf " %-30s%s\n" "$i" "Found libraries/executables: $x")
  232. else
  233. CHECKEDLIST[$i]=$(printf " %-30s%s\n" "$i" "No libraries/executables")
  234. fi
  235. # Replace the current line (100 characters) with empty space and fill it with checkedlist value
  236. echo -e "\r$(for ((i = 0; i < 100; i++)); do echo -n ' '; done)\r${CHECKEDLIST[$i]}"
  237. # Go through all listed library & executable files
  238. for n in $(echo "${MIMEFILES[@]}"); do
  239. # Get count of all matching lines for a file. If no errors, this should return 1
  240. # Enable bash debugging with set -x and run it in subshell (otherwise we need to use set +x to disable debugging)
  241. # As debugging output is considered as STDERR (not default STDOUT), we pipe it with |&
  242. # Suppress ldd warning messages ("you have no permission") as they are not relevant here and delete empty lines
  243. LDDCMDCNT=$( (set -x; ldd $n |grep -i "not found") |& grep -E "$n|=> not found" | sed 's/.*warning.*//g; /^\s*$/d' | wc -l)
  244. # Get all lines for a file. If no errors, this should return only "++ ldd <filename>"
  245. LDDCMD=$( (set -x; ldd $n |grep -i "not found") |& grep -E "$n" | sed 's/.*warning.*//g; /^\s*$/d')
  246. LDDCMD_FILE=$(echo "$LDDCMD " | sed 's/++ ldd //g')
  247. # Basename doesn't accept empty/null parameters
  248. if [[ ! -z "${LDDCMD_FILE// }" ]]; then
  249. LDDCMD_FILE_BASENAME=$(basename $LDDCMD_FILE) # | sed "s/[-0-9.]*$//"
  250. fi
  251. # If we have match for "not found" messages, print the output
  252. if [[ $LDDCMDCNT -gt 1 ]]; then
  253. LIBNOTFOUND=true
  254. function checklocaldirs() {
  255. unset PKG
  256. for filepkgdir in $1; do
  257. #echo "Search dir is $filepkgdir Search term is $2"
  258. findfile=$(find "$filepkgdir" -maxdepth 1 -type f -iname "*$2*")
  259. if [[ -f $findfile ]]; then
  260. PKG="$(pacman -Qo $findfile | awk '{print $5 " " $6}')"
  261. return
  262. fi
  263. done
  264. }
  265. function checkrepopkgs() {
  266. unset PKG
  267. unset NIPKG
  268. REPOPKGS=$(pacman -Fs $1 | sed '2~2d' | awk -F '[/ ]' '{print $2}')
  269. if [[ ! -z $REPOPKGS ]]; then
  270. c=0
  271. for repopkg in $REPOPKGS; do
  272. t=0
  273. for repopkgfile in $(pacman -Fl "$repopkg" | awk '{print "/"$2}'); do
  274. if [[ -f $repopkgfile ]]; then
  275. REPOPKGFILES[$t]="$repopkgfile"
  276. let t++
  277. fi
  278. done
  279. if [ ! "${#REPOPKGFILES[@]}" -eq 0 ]; then
  280. for matchrepofile in "${REPOPKGFILES[@]}"; do
  281. if [[ "${matchrepofile}" == "${2}" ]]; then
  282. PKG=$repopkg
  283. return
  284. # else
  285. # if [[ $(pacman -Qo $matchrepofile | grep -o "owned by" | wc -w) -eq 2 ]]
  286. # PKG=$(echo "${REPOPKGS[*]}?" | tr '\n' ',' | sed 's/\,*$//g')
  287. # #return
  288. fi
  289. done
  290. unset REPOPKGFILES
  291. else
  292. #echo "do we get here $repopkg"
  293. if [[ $(pacman -Q $repopkg &>/dev/null || echo "was not found" | wc -l) -eq 1 ]]; then
  294. NIPKG[$c]="$repopkg"
  295. let c++
  296. fi
  297. #PKG="${red}N/A${reset}"
  298. fi
  299. done
  300. #PKG="${red}N/A${reset}"
  301. else
  302. PKG="${red}N/A${reset}"
  303. fi
  304. }
  305. checkrepopkgs $LDDCMD_FILE_BASENAME $LDDCMD_FILE
  306. # MAINPKG=$PKG
  307. #REPOPKG=$(pacman -Fs $(echo $n | awk '{print $NF}' FS=/) | sed '1!d' | awk -F '[/ ]' '{print $2}')
  308. #if [[ -z ${PKG} ]]; then
  309. # PKG="${red}N/A${reset}"
  310. # fi
  311. NOTFOUNDFILES=$( (set -x; ldd $n |grep -i "not found") |& grep -E "=> not found" | sed 's/ => not found//g; s/\s//g' | tr '\n' ' ' | sed 's/.*warning.*//g; /^\s*$/d')
  312. printf "\n %s %s\n" "${red}=>${reset} Missing or broken dependencies for" "${yellow}$LDDCMD_FILE${reset}"
  313. printf " %-38s%s\n" " Owned by system package:" "$(pacman -Qo $n | sed 's/.* by //g')"
  314. # if [[ ! $MAINPKG == *"N/A"* ]] || [[ ! -z "${NIPKG}" ]]; then
  315. # printf " %-38s%s\n" " Provided by..."
  316. # if [[ ! $MAINPKG == *"N/A"* ]]; then
  317. # printf " %-38s%s\n" " ...installed repo package(s):" "$MAINPKG"
  318. # fi
  319. if [[ ! -z "${NIPKG}" ]]; then
  320. printf " %-38s%s\n" " Provided by package(s):" "${NIPKG[*]}"
  321. # printf " %-38s%s\n" " ...non-installed repo package(s):" "${NIPKG[*]}"
  322. fi
  323. # fi
  324. printf "\n"
  325. # Set up a new array for r variables (libary files) and store printf output there.
  326. # This is just so that we don't need to check these library files again but instead
  327. # we can use a stored value. This is just to speed up processing.
  328. # TODO does this really work?
  329. r_count=0
  330. typeset -A RLIST
  331. for r in $(echo ${NOTFOUNDFILES[@]}); do
  332. if [[ ! ${RLIST[$r_count]} ]]; then
  333. checklocaldirs "${MAINPKGDIRS[*]}" $r #$r_basename
  334. if [[ -z ${PKG} ]]; then
  335. checklocaldirs "${LOCALDIRS[*]}" $r #$r_basename
  336. fi
  337. LIBPKG=$PKG
  338. RLIST[$r_count]=$(printf " %-47s%-30s%s\n" " ${red}Depends on${reset}:" "$r" "${blue}Provided by${reset}: $LIBPKG")
  339. fi
  340. echo "${RLIST[$r_count]}"
  341. #unset MAINPKG
  342. #unset PKG
  343. #r_basename=$(echo $r | sed "s/[-0-9.]*$//") # libicui18n.so.58.4 -> libicui18n.so
  344. #unset PKG
  345. #if [[ -z ${PKG} ]]; then
  346. # checkrepopkgs $r_basename
  347. #if [[ -z ${PKG} ]]; then
  348. # PKG="${red}N/A${reset}"
  349. #fi
  350. # fi
  351. #check_main $r_basename
  352. #checkrepopkgs $r_basename $r
  353. # if [[ -z ${PKG} ]]; then
  354. # PKG="N/A"
  355. # fi
  356. # for r_providers in $(pacman -Fs $r_basename | sed '2~2d' | awk -F '[/ ]' '{print $2}'); do
  357. # TODO
  358. #LIBPKG[0]=$(pacman -Fs $(echo $r | awk '{print $NF}' FS=/) | sed '1!d' | awk -F '[/ ]' '{print $2}')
  359. #LIBPKG=$(pacman -Fs $(echo $r | awk '{print $NF}' FS=/) | sed '1!d' | awk -F '[/ ]' '{print $2}')
  360. #LIBPKG=$(pacman -Fs $r | sed '1!d' | awk -F '[/ ]' '{print $2}')
  361. #if [[ "${#LIBPKG[@]}" -eq 0 ]]; then
  362. #if [[ $(echo $LIBPKG | wc -w) -eq 0 ]]; then
  363. # LIBPKG="N/A"
  364. #fi
  365. #printf " %-47s%-30s%s\n" " ${red}Depends on${reset}:" "$r" "${blue}Provided by${reset}: $LIBPKG"
  366. #unset LIBPKG
  367. #unset PKG
  368. #unset MAINPKG
  369. let r_count++
  370. done
  371. unset RLIST
  372. fi
  373. done
  374. unset MIMEFILES #Unset MIMEFILES array for the next dependency check
  375. else
  376. printf '%s\n' "${CHECKEDLIST[$i]}"
  377. fi
  378. done
  379. else
  380. echo -e "\n No dependencies for this package."
  381. fi
  382. if [[ ! -z "${NOTFOUNDS}" ]] || [[ $LIBNOTFOUND != true ]]; then
  383. echo -e "\n ${green}=>${reset} Dependencies checked. All ${green}OK${reset}.\n"
  384. else
  385. echo -e "\n ${red}=>${reset} Dependency problems discovered. See above messages for details.\n"
  386. fi
  387. unset NOTFOUNDS
  388. unset LIBNOTFOUND
  389. fi
  390. let curpkgcount++
  391. done