Nimbin[12]?Administration / depscollector

depscollector Administration

List dependencies of a User or a binary
This is specially useful for packageuser LFS systems

dependencies: depscollector
Synopsis
depscollector [-u] [-d db_path] [-b bin_dir]
DESCRIPTION
-u Update mode (remove stale entries, add new only)
-d db_path Path to SQLite database (default: /var/lib/depscollector/depscollector.db)
-b bin_dir Directory to scan for binaries (can be used multiple times)
-h Show this help message

wget https://git.christianimmanuel.de/scripts/raw/Administration/depscollector && chmod 740 depscollector
depscollector 7.9 KB · 270 lines raw
#!/bin/bash


#################################################
### List dependencies of a User or a binary
### This is specially useful for packageuser LFS systems
###
### dependencies: depscollector
### Synopsis
###		 depscollector [-u] [-d db_path] [-b bin_dir]
### DESCRIPTION
###      -u           Update mode (remove stale entries, add new only)
###      -d db_path   Path to SQLite database (default: /var/lib/depscollector/depscollector.db)
###      -b bin_dir   Directory to scan for binaries (can be used multiple times)
###      -h           Show this help message



init_db() {
    echo "INIT DB"
    mkdir -p "$(dirname "$db_path")"

    echo "Creating DB"
    sqlite3 "$db_path" <<'EOF'
CREATE TABLE IF NOT EXISTS sources (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    dir TEXT UNIQUE NOT NULL
);

CREATE TABLE IF NOT EXISTS binaries (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    path TEXT UNIQUE NOT NULL,
    name TEXT NOT NULL,
    owner_user TEXT,
    owner_group TEXT,
    source_id INTEGER NOT NULL,
    FOREIGN KEY (source_id) REFERENCES sources(id)
);

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
);

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


    local expected_tables=("sources" "binaries" "libraries" "binary_dependencies")
    for table in "${expected_tables[@]}"; do
        if ! sqlite3 "$db_path" ".schema $table" | grep -q "^CREATE TABLE"; then
            echo "Error: Table '$table' was not created successfully in $db_path" >&2
            rm -f "$db_path"
            exit 1
        else
            echo "Table $table exists."
        fi
    done
}

get_owner() {
    stat -c '%U:%G' "$1"
}

insert_binary() {
    local path="$1"
    local name owner_user owner_group source_dir source_id

    name="$(basename "$path")"
    IFS=: read -r owner_user owner_group < <(get_owner "$path")
    source_dir="$(dirname "$path")"

    # Insert or find source_id
    sqlite3 "$db_path" <<EOF
INSERT OR IGNORE INTO sources (dir) VALUES ('$source_dir');
EOF

    source_id=$(sqlite3 "$db_path" "SELECT id FROM sources WHERE dir = '$source_dir';")

    if [[ -z "$source_id" ]]; then
        echo "Error: Could not resolve source_id for $source_dir" >&2
        exit 1
    fi

    echo "## $name u:$owner_user g:$owner_group"
    echo "   $path ($source_dir, id=$source_id)"

    sqlite3 "$db_path" <<EOF
INSERT OR IGNORE INTO binaries (path, name, owner_user, owner_group, source_id)
VALUES ('$path', '$name', '$owner_user', '$owner_group', $source_id);
EOF
}

insert_library() {
    local lib_path="$1"
    [[ ! -e "$lib_path" ]] && return

    local name owner_user owner_group
    name="$(basename "$lib_path")"
    IFS=: read -r owner_user owner_group < <(get_owner "$lib_path")

    echo "      $name $owner_user $owner_group"

    sqlite3 "$db_path" <<EOF
INSERT OR IGNORE INTO libraries (path, name, owner_user, owner_group)
VALUES ('$lib_path', '$name', '$owner_user', '$owner_group');
EOF
}

link_binary_to_library() {
    local binary_path="$1"
    local lib_path="$2"

    local binary_id lib_id
    binary_id=$(sqlite3 "$db_path" "SELECT id FROM binaries WHERE path = '$binary_path';")
    lib_id=$(sqlite3 "$db_path" "SELECT id FROM libraries WHERE path = '$lib_path';")

    [[ -n "$binary_id" && -n "$lib_id" ]] || return

    sqlite3 "$db_path" <<EOF
INSERT OR IGNORE INTO binary_dependencies (binary_id, library_id)
VALUES ($binary_id, $lib_id);
EOF
}

find_library_path() {
    local lib="$1"
    ldconfig -p | awk -v lib="$lib" '$1 == lib { print $NF; exit }'
}

process_binary() {
    local binary="$1"
    insert_binary "$binary"

    if file "$binary" | grep -q ELF; then
        local libs
        libs=$(readelf -d "$binary" 2>/dev/null | awk '/NEEDED/ { gsub(/\[|\]/, "", $NF); print $NF }')

        echo
        for lib in $libs; do
            echo "   $lib"
            local lib_path
            lib_path="$(find_library_path "$lib")"
            if [[ -n "$lib_path" ]]; then
                insert_library "$lib_path"
                link_binary_to_library "$binary" "$lib_path"
            fi
        done
    fi
}

remove_stale_entries() {
    echo "Collecting current binaries..."
    current_bins=()
    for dir in "${bin_dirs[@]}"; do
        [[ -d "$dir" ]] && mapfile -t found < <(find "$dir" -type f -executable)
        current_bins+=("${found[@]}")
    done

    echo "Collecting tracked binaries from DB..."
    mapfile -t db_bins < <(sqlite3 "$db_path" "SELECT path FROM binaries;" | sort)

    # Use temp files to diff
    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 binaries that are no longer found
    while IFS= read -r stale_bin; do
        echo "Removing stale binary: $stale_bin"
        sqlite3 "$db_path" "DELETE FROM binaries WHERE path = '$stale_bin';"
    done < <(comm -23 "$tmp_db" "$tmp_current")

    rm -f "$tmp_current" "$tmp_db"

    echo "Cleaning unused libraries..."
    sqlite3 "$db_path" <<EOF
DELETE FROM libraries
WHERE id NOT IN (SELECT library_id FROM binary_dependencies);
EOF
}

main() {
    local update=0
    db_path="/var/lib/depscollector/depscollector.db"
    bin_dirs=()
    bin_dir="/usr/bin"

    while [[ $# -gt 0 ]]; do
        case "$1" in
            -u) update=1; shift ;;
            -d) db_path="$2"; shift 2 ;;
            -b) bin_dirs+=("$2"); shift 2 ;;
            -h)
                echo "Usage: $0 [-u] [-d db_path] [-b bin_dir]"
                echo "  -u           Update mode (remove stale entries, add new only)"
                echo "  -d db_path   Path to SQLite database (default: /var/lib/depscollector/depscollector.db)"
                echo "  -b bin_dir   Directory to scan for binaries (can be used multiple times)"
                echo "  -h           Show this help message"
                exit 0
                ;;
            *) echo "Invalid option: $1" >&2; echo "Try '$0 -h' for help." >&2; exit 1 ;;
        esac
    done


    if [[ ${#bin_dirs[@]} -eq 0 ]]; then
        mapfile -t bin_dirs < <(sqlite3 "$db_path" "SELECT DISTINCT dir FROM sources;")
    fi

    if [[ ${#bin_dirs[@]} -eq 0 ]]; then
        bin_dirs=("/usr/bin")
    fi

    echo "Using database: $db_path"
    echo "Scanning binaries in: ${bin_dirs[*]}"

    init_db

    echo "Collecting current binaries from ${bin_dirs[@]}..."
    all_bins=()
    for dir in "${bin_dirs[@]}"; do
        [[ -d "$dir" ]] && mapfile -t found < <(find "$dir" -type f -executable)
        all_bins+=("${found[@]}")
    done
    IFS=$'\n' all_bins=($(printf "%s\n" "${all_bins[@]}" | sort -u))

    if (( update )); then
        echo "Updating DB: removing stale entries..."
        current_bins=("${all_bins[@]}")
        remove_stale_entries

        echo "Collecting tracked binaries from DB after cleanup..."
        mapfile -t db_bins < <(sqlite3 "$db_path" "SELECT path FROM binaries ORDER BY path;")

        # Save to tmp files to diff
        tmp_all=$(mktemp)
        tmp_db=$(mktemp)
        printf "%s\n" "${all_bins[@]}" | sort > "$tmp_all"
        printf "%s\n" "${db_bins[@]}" | sort > "$tmp_db"

        echo "Processing new binaries only..."
        while IFS= read -r new_bin; do
            echo -e "\n###############################################"
            process_binary "$new_bin"
        done < <(comm -13 "$tmp_db" "$tmp_all")

        rm -f "$tmp_all" "$tmp_db"
    else
        # Full fresh scan: insert all
        for bin in "${all_bins[@]}"; do
            echo -e "\n###############################################"
            process_binary "$bin"
        done
    fi
}

main "$@"