Nimbin2git.christianimmanuel.de / Linux From Scratch / dependency_collecter_for_lfs_packageuser_system / commits / d3a1519

dependency_collecter_for_lfs_packageuser_system git · main

git clone https://git.christianimmanuel.de/linux-from-scratch/dependency_collecter_for_lfs_packageuser_system.gitwget https://git.christianimmanuel.de/linux-from-scratch/dependency_collecter_for_lfs_packageuser_system/archive/dependency_collecter_for_lfs_packageuser_system.tar.gz

Add single transction with ChatGPT5.2

Christian Immanuel · 2026-07-05 11:48 · d3a1519c2be6b19ebe8cf3439943d010212e0467

 depscollector | 235 ++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 146 insertions(+), 89 deletions(-)

diff --git a/depscollector b/depscollector
index 8e0522f..5a968ad 100755
--- a/depscollector
+++ b/depscollector
@@ -2,6 +2,7 @@
 set -euo pipefail
 
 DB_PATH_DEFAULT="/var/lib/depscollector/depscollector.db"
+
 db_path="$DB_PATH_DEFAULT"
 update=0
 verbose=0
@@ -30,24 +31,78 @@ Options:
 Behavior:
   If no -b DIR is given, it reuses directories from the 'sources' table;
   if still empty, it falls back to /usr/bin.
-
-Examples:
-  depscollector -b /usr/bin
-  depscollector -u -b /usr/bin -b /usr/sbin
-  depscollector -u -d /var/lib/depscollector/depscollector.db
 EOF
 }
 
-init_db() {
+# --------------------- sqlite single-connection ---------------------
+# We run a persistent sqlite3 process and talk to it via stdin/stdout.
+# For SELECTs, we append a unique marker row and read until we see it.
+declare -a SQLCOPROC=()
+
+sql_start() {
   mkdir -p "$(dirname "$db_path")"
-  sqlite3 "$db_path" <<'EOF'
-PRAGMA foreign_keys = ON;
 
+  coproc SQLCOPROC { sqlite3 "$db_path" -batch; }
+
+  # Configure session
+  sql_send ".bail on"
+  sql_send ".echo off"
+  sql_send ".headers off"
+  sql_send ".mode list"
+  sql_send ".separator $'\t'"
+  sql_send "PRAGMA foreign_keys = ON;"
+}
+
+sql_stop() {
+  # Politely ask sqlite to quit; ignore errors during shutdown.
+  { sql_send ".quit"; } 2>/dev/null || true
+  { wait "${SQLCOPROC_PID:-0}"; } 2>/dev/null || true
+}
+
+sql_send() {
+  # Send exactly one line/statement (sqlite3 accepts multi-stmt lines too).
+  printf '%s\n' "$1" >&"${SQLCOPROC[1]}"
+}
+
+sql_exec() {
+  # Execute SQL that produces no output (or output we don't care about).
+  # We still add a marker SELECT to ensure sqlite processes it before we continue.
+  local sql="$1"
+  local mark="__EXEC_DONE__${RANDOM}${RANDOM}__"
+
+  sql_send "$sql"
+  sql_send "SELECT '$mark';"
+
+  local line
+  while IFS= read -r line <&"${SQLCOPROC[0]}"; do
+    [[ "$line" == "$mark" ]] && break
+  done
+}
+
+sql_query_lines() {
+  # Print result lines of a SELECT to stdout.
+  local sql="$1"
+  local mark="__END__${RANDOM}${RANDOM}__"
+
+  sql_send "$sql"
+  sql_send "SELECT '$mark';"
+
+  local line
+  while IFS= read -r line <&"${SQLCOPROC[0]}"; do
+    [[ "$line" == "$mark" ]] && break
+    printf '%s\n' "$line"
+  done
+}
+
+# --------------------- DB init ---------------------
+init_db() {
+  sql_exec "
 CREATE TABLE IF NOT EXISTS sources (
     id INTEGER PRIMARY KEY AUTOINCREMENT,
     dir TEXT UNIQUE NOT NULL
-);
+);"
 
+  sql_exec "
 CREATE TABLE IF NOT EXISTS binaries (
     id INTEGER PRIMARY KEY AUTOINCREMENT,
     path TEXT UNIQUE NOT NULL,
@@ -56,62 +111,86 @@ CREATE TABLE IF NOT EXISTS binaries (
     owner_group TEXT,
     source_id INTEGER NOT NULL,
     FOREIGN KEY (source_id) REFERENCES sources(id)
-);
+);"
 
+  sql_exec "
 CREATE TABLE IF NOT EXISTS libraries (
     id INTEGER PRIMARY KEY AUTOINCREMENT,
     path TEXT UNIQUE NOT NULL,
     name TEXT NOT NULL,
     owner_user TEXT,
     owner_group TEXT
-);
+);"
 
+  sql_exec "
 CREATE TABLE IF NOT EXISTS binary_dependencies (
     binary_id INTEGER NOT NULL,
     library_id INTEGER NOT NULL,
     PRIMARY KEY (binary_id, library_id),
     FOREIGN KEY (binary_id) REFERENCES binaries(id) ON DELETE CASCADE,
     FOREIGN KEY (library_id) REFERENCES libraries(id) ON DELETE CASCADE
-);
-EOF
+);"
 }
 
+# --------------------- scan helpers ---------------------
 get_owner() { stat -c '%U:%G' "$1"; }
 
-find_library_path() {
-  local lib="$1"
-  ldconfig -p | awk -v lib="$lib" '$1 == lib { print $NF; exit }'
+is_elf() { file -b "$1" 2>/dev/null | grep -q 'ELF'; }
+
+collect_bins_from_dirs() {
+  local -a all=()
+  local dir
+
+  for dir in "${bin_dirs[@]}"; do
+    [[ -d "$dir" ]] || { log "Skipping non-dir: $dir"; continue; }
+    while IFS= read -r -d '' f; do
+      all+=("$f")
+    done < <(find "$dir" -type f -perm -111 -print0 2>/dev/null)
+  done
+
+  printf "%s\n" "${all[@]}" | sort -u
 }
 
-insert_source_get_id() {
-  local dir="$1"
-  local dir_esc; dir_esc="$(sql_escape "$dir")"
+# One-time ldconfig cache: libname -> full path
+declare -A LDCACHE=()
+build_ldcache() {
+  local name path
+  while IFS= read -r line; do
+    # Example line:
+    #   libz.so.1 (libc6,x86-64) => /lib/libz.so.1
+    name="$(awk '{print $1}' <<<"$line")"
+    path="$(awk '{print $NF}' <<<"$line")"
+    [[ -n "$name" && -n "$path" ]] || continue
+    # Keep first hit
+    [[ -n "${LDCACHE[$name]+x}" ]] || LDCACHE["$name"]="$path"
+  done < <(ldconfig -p 2>/dev/null || true)
+}
 
-  sqlite3 "$db_path" <<EOF
-INSERT OR IGNORE INTO sources (dir) VALUES ('$dir_esc');
-EOF
-  sqlite3 "$db_path" "SELECT id FROM sources WHERE dir = '$dir_esc';"
+find_library_path() {
+  local lib="$1"
+  printf '%s' "${LDCACHE[$lib]:-}"
 }
 
+# --------------------- DB operations ---------------------
 insert_binary() {
   local path="$1"
-  local name owner_user owner_group source_dir source_id
+  local name owner_user owner_group source_dir
   name="$(basename "$path")"
   IFS=: read -r owner_user owner_group < <(get_owner "$path")
   source_dir="$(dirname "$path")"
-  source_id="$(insert_source_get_id "$source_dir")"
-  [[ -n "$source_id" ]] || die "Could not resolve source_id for $source_dir"
 
-  local path_esc name_esc ou_esc og_esc
-  path_esc="$(sql_escape "$path")"
-  name_esc="$(sql_escape "$name")"
-  ou_esc="$(sql_escape "$owner_user")"
-  og_esc="$(sql_escape "$owner_group")"
+  local p n ou og sd
+  p="$(sql_escape "$path")"
+  n="$(sql_escape "$name")"
+  ou="$(sql_escape "$owner_user")"
+  og="$(sql_escape "$owner_group")"
+  sd="$(sql_escape "$source_dir")"
 
-  sqlite3 "$db_path" <<EOF
+  sql_exec "INSERT OR IGNORE INTO sources (dir) VALUES ('$sd');"
+  sql_exec "
 INSERT OR IGNORE INTO binaries (path, name, owner_user, owner_group, source_id)
-VALUES ('$path_esc', '$name_esc', '$ou_esc', '$og_esc', $source_id);
-EOF
+VALUES ('$p', '$n', '$ou', '$og', (SELECT id FROM sources WHERE dir = '$sd'));
+"
 }
 
 insert_library() {
@@ -122,48 +201,40 @@ insert_library() {
   name="$(basename "$lib_path")"
   IFS=: read -r owner_user owner_group < <(get_owner "$lib_path")
 
-  local p_esc n_esc ou_esc og_esc
-  p_esc="$(sql_escape "$lib_path")"
-  n_esc="$(sql_escape "$name")"
-  ou_esc="$(sql_escape "$owner_user")"
-  og_esc="$(sql_escape "$owner_group")"
+  local p n ou og
+  p="$(sql_escape "$lib_path")"
+  n="$(sql_escape "$name")"
+  ou="$(sql_escape "$owner_user")"
+  og="$(sql_escape "$owner_group")"
 
-  sqlite3 "$db_path" <<EOF
+  sql_exec "
 INSERT OR IGNORE INTO libraries (path, name, owner_user, owner_group)
-VALUES ('$p_esc', '$n_esc', '$ou_esc', '$og_esc');
-EOF
+VALUES ('$p', '$n', '$ou', '$og');
+"
 }
 
 link_binary_to_library() {
   local binary_path="$1"
   local lib_path="$2"
 
-  local b_esc l_esc
-  b_esc="$(sql_escape "$binary_path")"
-  l_esc="$(sql_escape "$lib_path")"
+  local bp lp
+  bp="$(sql_escape "$binary_path")"
+  lp="$(sql_escape "$lib_path")"
 
-  sqlite3 "$db_path" <<EOF
+  sql_exec "
 INSERT OR IGNORE INTO binary_dependencies (binary_id, library_id)
 SELECT b.id, l.id
-FROM binaries b, libraries l
-WHERE b.path = '$b_esc'
-  AND l.path = '$l_esc';
-EOF
-}
-
-is_elf() {
-  # Fast-ish ELF check (works for most cases)
-  file -b "$1" 2>/dev/null | grep -q 'ELF'
+FROM binaries b
+JOIN libraries l
+WHERE b.path = '$bp' AND l.path = '$lp';
+"
 }
 
 process_binary() {
   local binary="$1"
-
   insert_binary "$binary"
 
-  if ! is_elf "$binary"; then
-    return 0
-  fi
+  is_elf "$binary" || return 0
 
   local libs lib lib_path
   libs="$(readelf -d "$binary" 2>/dev/null | awk '/NEEDED/ { gsub(/
@@ -182,50 +253,32 @@ $$
   done <<<"$libs"
 }
 
-collect_bins_from_dirs() {
-  local -a all=()
-  local dir
-  for dir in "${bin_dirs[@]}"; do
-    [[ -d "$dir" ]] || { log "Skipping non-dir: $dir"; continue; }
-    # NUL-safe
-    while IFS= read -r -d '' f; do
-      all+=("$f")
-    done < <(find "$dir" -type f -perm -111 -print0 2>/dev/null)
-  done
-
-  # unique
-  printf "%s\n" "${all[@]}" | sort -u
-}
-
 remove_stale_entries() {
-  # Build current list
   mapfile -t current_bins < <(collect_bins_from_dirs)
-
-  # DB list
-  mapfile -t db_bins < <(sqlite3 "$db_path" "SELECT path FROM binaries ORDER BY path;")
+  mapfile -t db_bins < <(sql_query_lines "SELECT path FROM binaries ORDER BY path;")
 
   local tmp_current tmp_db
   tmp_current="$(mktemp)"
   tmp_db="$(mktemp)"
+
   printf "%s\n" "${current_bins[@]}" | sort >"$tmp_current"
   printf "%s\n" "${db_bins[@]}" | sort >"$tmp_db"
 
-  # Remove stale
   while IFS= read -r stale_bin; do
     [[ -n "$stale_bin" ]] || continue
-    local s_esc; s_esc="$(sql_escape "$stale_bin")"
-    sqlite3 "$db_path" "DELETE FROM binaries WHERE path = '$s_esc';"
+    local s; s="$(sql_escape "$stale_bin")"
+    sql_exec "DELETE FROM binaries WHERE path = '$s';"
   done < <(comm -23 "$tmp_db" "$tmp_current")
 
   rm -f "$tmp_current" "$tmp_db"
 
-  # Cleanup orphan libs
-  sqlite3 "$db_path" <<'EOF'
+  sql_exec "
 DELETE FROM libraries
 WHERE id NOT IN (SELECT library_id FROM binary_dependencies);
-EOF
+"
 }
 
+# --------------------- main ---------------------
 main() {
   need_cmd sqlite3
   need_cmd find
@@ -248,10 +301,14 @@ main() {
     esac
   done
 
+  sql_start
+  trap sql_stop EXIT
+
   init_db
+  build_ldcache
 
   if [[ ${#bin_dirs[@]} -eq 0 ]]; then
-    mapfile -t bin_dirs < <(sqlite3 "$db_path" "SELECT DISTINCT dir FROM sources ORDER BY dir;")
+    mapfile -t bin_dirs < <(sql_query_lines "SELECT DISTINCT dir FROM sources ORDER BY dir;")
   fi
   if [[ ${#bin_dirs[@]} -eq 0 ]]; then
     bin_dirs=("/usr/bin")
@@ -260,15 +317,15 @@ main() {
   log "DB: $db_path"
   log "Dirs: ${bin_dirs[*]}"
 
-  # Transaction makes it much faster.
-  sqlite3 "$db_path" "BEGIN;"
+  sql_exec "BEGIN;"
 
   if (( update )); then
     remove_stale_entries
 
     mapfile -t all_bins < <(collect_bins_from_dirs)
-    mapfile -t tracked < <(sqlite3 "$db_path" "SELECT path FROM binaries ORDER BY path;")
+    mapfile -t tracked < <(sql_query_lines "SELECT path FROM binaries ORDER BY path;")
 
+    local tmp_all tmp_tracked
     tmp_all="$(mktemp)"
     tmp_tracked="$(mktemp)"
     printf "%s\n" "${all_bins[@]}" | sort >"$tmp_all"
@@ -287,7 +344,7 @@ main() {
     done < <(collect_bins_from_dirs)
   fi
 
-  sqlite3 "$db_path" "COMMIT;"
+  sql_exec "COMMIT;"
 }
 
 main "$@"