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.

477 lines
16 KiB

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