shellcheck fixes, including real bug
[distro-functions] / src / package-manager-abstractions
index f66a7620b2292f6c402bcf7127ee28b17057db83..aa14875a093bfee508bd2dca1fa85012cfdc9c85 100644 (file)
@@ -1,5 +1,12 @@
 #!/bin/bash
-# Copyright (C) 2014 Ian Kelling
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to switch
+# its license to GPL.
+
+# Copyright 2024 Ian Kelling
 
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -26,7 +33,7 @@ if command -v yum &> /dev/null; then
     $s yum -y install "$@"
   }
   # package find
-  pf() {
+  pfd() {
     local s; [[ $EUID != 0 ]] && s=sudo
     $s yum search "$@"
   }
@@ -35,6 +42,7 @@ if command -v yum &> /dev/null; then
     local s; [[ $EUID != 0 ]] && s=sudo
     $s yum autoremove "$@"
   }
+  # shellcheck disable=SC2120
   pup() { # upgrade
     local s; [[ $EUID != 0 ]] && s=sudo
     $s yum -y distro-sync full "$@"
@@ -48,6 +56,31 @@ if command -v yum &> /dev/null; then
   }
 
 elif command -v apt-get &>/dev/null; then
+  plock-wait() {
+    local i
+    i=0
+    while fuser /var/lib/dpkg/lock &>/dev/null; do
+      sleep 1
+      i=$(( i+1 ))
+      if (( i > 300 )); then
+        echo "error: timed out waiting for /var/lib/dpkg/lock" >&2
+        return 1
+      fi
+    done
+  }
+  pcheck() {
+    for arg; do
+      if [[ $1 == -* ]]; then
+        shift
+      else
+        break
+      fi
+    done
+    if dpkg -s -- "$@" |& grep -Fx "Status: install ok installed" &>/dev/null; then
+      return 1
+    fi
+    return 0
+  }
   pp() { # package policy
     apt-cache policy $@
   }
@@ -63,44 +96,76 @@ elif command -v apt-get &>/dev/null; then
     esac
   }
   pupdate() {
-    local s f; [[ $EUID != 0 ]] && s=sudo
+    local now t s f cachetime limittime; [[ $EUID != 0 ]] && s=sudo
     # update package list if its more than an 2 hours old
     f=/var/cache/apt/pkgcache.bin
-    if [[ ! -r $f ]] \
-         || (( $(( $(date +%s) - $(stat -c %Y $f ) )) > 60*60*2 )); then
+    if [[ -r $f ]]; then
+      cachetime=$(stat -c %Y $f )
+    else
+      cachetime=0
+    fi
+    now=$(date +%s)
+    limittime=$(( now - 60*60*2 ))
+    for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
+      if [[ -r $f ]]; then
+        t=$(stat  -c %Y $f )
+        if (( t > limittime )); then
+          limittime=$t
+        fi
+      fi
+    done
+    if (( cachetime > limittime )); then
       $s apt-get update
     fi
   }
   pi() {
-    if dpkg -s -- "$@" &>/dev/null; then
-      return 0
-    fi
-    while fuser /var/lib/dpkg/lock &>/dev/null; do sleep 1; done
+    pcheck "$@" || return 0
     pupdate
-    local s; [[ $EUID != 0 ]] && s=sudo
-    $s $PI_PREFIX apt-get -y --allow-downgrades install --purge --auto-remove "$@"
+    if [[ $- != *i* ]]; then
+      echo pi "$*"
+    fi
+    if [[ $EUID == 0 ]]; then
+      DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@"
+    else
+      sudo DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@"
+    fi
+
   }
 
   pi-nostart() {
-    if dpkg -s -- "$@" &>/dev/null; then
-      return 0
-    fi
-    while fuser /var/lib/dpkg/lock &>/dev/null; do sleep 1; done
+    local ret=
+    pcheck "$@" || return 0
+    plock-wait
     pupdate
-    local s; [[ $EUID != 0 ]] && s=sudo
     local f=/usr/sbin/policy-rc.d
-    $s dd of=$f <<EOF
+    if [[ $- != *i* ]]; then
+      echo pi-nostart "$@"
+    fi
+    if [[ $EUID == 0 ]]; then
+      dd of=$f status=none <<EOF
 #!/bin/sh
 exit 101
 EOF
-    $s chmod +x $f
-    $s apt-get -y install --allow-downgrades --purge --auto-remove "$@"
-    $s rm $f
+      chmod +x $f
+      DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@" || ret=$?
+      rm $f
+    else
+      sudo dd of=$f status=none <<EOF
+#!/bin/sh
+exit 101
+EOF
+      sudo chmod +x $f
+      sudo DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@" || ret=$?
+      sudo rm $f
+    fi
+    return $ret
   }
-  pf() {
+  # package find description
+  pfd() {
     # package name and descriptions
     apt-cache search "$@"
   }
+  # package find file
   pff() {
     local s; [[ $EUID != 0 ]] && s=sudo
     # nice aptitude search from emacs shell. package description width as
@@ -113,17 +178,26 @@ EOF
   }
   pu() {
     local s; [[ $EUID != 0 ]] && s=sudo
-    while fuser /var/lib/dpkg/lock &>/dev/null; do sleep 1; done
-    $s apt-get -y remove --allow-downgrades --purge --auto-remove  "$@"
+    local needed=false
+    for arg; do
+      if dpkg -s -- "$arg" &>/dev/null; then
+        needed=true
+        break
+      fi
+    done
+    $needed || return 0
+    plock-wait
+    $s apt-get -y remove --purge --auto-remove  "$@"
     # seems slightly redundant, but it removes more stuff sometimes.
-    $s apt-get -y --allow-downgrades autoremove
+    $s apt-get -y autoremove
   }
+  # shellcheck disable=SC2120
   pup() { # upgrade
-    while fuser /var/lib/dpkg/lock &>/dev/null; do sleep 1; done
+    plock-wait
     pupdate
     local s; [[ $EUID != 0 ]] && s=sudo
-    $s apt-get -y dist-upgrade --allow-downgrades --purge --auto-remove "$@"
-    $s apt-get -y autoremove --allow-downgrades
+    $s apt-get -y dist-upgrade --purge --auto-remove "$@"
+    $s apt-get -y autoremove
   }
   # package info
   pl() {
@@ -134,6 +208,15 @@ EOF
     fi
   }
   pfile() {
+    # -a = search all repos
+    local -a arg all
+    all=false
+    case $1 in
+      -a)
+        all=true
+        shift
+        ;;
+    esac
     local file=$1
     # ucfq can tell us about config files which are not tracked
     # with apt-file. but, for at least a few files I tested
@@ -144,16 +227,23 @@ EOF
     # if [[ $file == /* ]] && ! ucfq -w $file | grep ::: &>/dev/null; then
     #    ucfq $file
 
-    if [[ $file == */* ]]; then
-      apt-file find -x "$file"\$
+    if [[ $file == /* ]]; then
+      dpkg -S "$file"
     else
-      apt-file find -x /"$file"\$
-      update-alternatives --list "$file" 2>/dev/null
+      if ! $all; then
+        arg=(--filter-origins "$(positive-origins)")
+      fi
+      if [[ $file == /* ]]; then
+        apt-file "${arg[@]}" find -x /"$file"\$
+        update-alternatives --list "$file" 2>/dev/null
+      else
+        apt-file "${arg[@]}" find -x "$file"\$
+      fi
     fi
   }
   pkgfiles() {
     if dpkg -s "$1" &>/dev/null; then
-      dpkg-query -L $1
+      dpkg-query -L $1 | while read -r l; do [[ -f $l ]] && printf "%s\n" "$l"; done
     else
       apt-file -x list "^$1$"
     fi
@@ -166,7 +256,7 @@ elif command -v pacman &>/dev/null; then
   pi() {
     pacaur -S --noconfirm --needed --noedit "$@"
   }
-  pf() {
+  pfd() {
     pacaur -Ss "$@"
   }
   pu() {
@@ -178,14 +268,15 @@ elif command -v pacman &>/dev/null; then
   aurex() {
     p="$1"
     aur='https://aur.archlinux.org'
-    curl -s $aur/$(curl -s "$aur/rpc.php?type=info&arg=$p" \
-                     | jq -r .results.URLPath) | tar xz
+    curl -s $aur/"$(curl -s "$aur/rpc.php?type=info&arg=$p" \
+                     | jq -r .results.URLPath)" | tar xz
     cd "$p"
 
   }
   pmirror() {
     local s; [[ $EUID != 0 ]] && s=sudo
-    local x=$(mktemp)
+    local x
+    x=$(mktemp)
     curl -s "https://www.archlinux.org/mirrorlist/\
 ?country=US&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on" \
       | sed -r 's/^[ #]*(Server *=)/\1/' > $x
@@ -194,6 +285,7 @@ elif command -v pacman &>/dev/null; then
       rm $x
     fi
   }
+  # shellcheck disable=SC2120
   pup() { # upgrade
     local s; [[ $EUID != 0 ]] && s=sudo
     # file_time + 24 hours > current_time