diff options
| author | David Faulkner <[email protected]> | 2026-08-05 23:25:25 -0500 |
|---|---|---|
| committer | David Faulkner <[email protected]> | 2026-08-05 23:25:25 -0500 |
| commit | 3d51ff671e0bf079063c6270bbb985bd3d218b34 (patch) | |
| tree | 2d99d81b2e0c1cad6ce119dfc6da5844ed49b3e1 | |
feat: initial commit for ud maintenance utility
- Add C source code for Debian apt update, autoremove, and cleanup tasks
- Add Makefile with build, install, uninstall, and clean targets
- Add README.md with build and usage instructions
- Add MIT license and .gitignore for build artifacts
| -rw-r--r-- | .gitignore | 7 | ||||
| -rw-r--r-- | LICENSE | 20 | ||||
| -rw-r--r-- | Makefile | 34 | ||||
| -rw-r--r-- | README.md | 36 | ||||
| -rw-r--r-- | main.c | 154 |
5 files changed, 251 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b38572 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Build targets and object files +ud +*.o + +# Editor backup files +*~ +.*.swp @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2026 David Faulkner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom it is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..939f257 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +# Compiler and flags +CC := gcc +CFLAGS := -Wall -Wextra -Wpedantic -O2 -std=c11 -D_POSIX_C_SOURCE=200809L +TARGET := ud +SRC := main.c +OBJ := $(SRC:.c=.o) + +# Default rule +all: $(TARGET) + +# Link binary +$(TARGET): $(OBJ) + $(CC) $(CFLAGS) -o $@ $^ + +# Compile source files to object files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# Install binary to user's local bin path (~/.local/bin) +install: $(TARGET) + @mkdir -p $(HOME)/.local/bin + install -m 755 $(TARGET) $(HOME)/.local/bin/$(TARGET) + @echo "Installed $(TARGET) to $(HOME)/.local/bin/" + +# Uninstall binary from local bin path +uninstall: + rm -f $(HOME)/.local/bin/$(TARGET) + @echo "Removed $(TARGET) from $(HOME)/.local/bin/" + +# Clean build directory +clean: + rm -f $(OBJ) $(TARGET) + +.PHONY: all clean install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae16766 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# ud + +A clean, minimalist, robust C utility designed to automate Debian-based Linux system maintenance tasks. It manages `apt` package list updates, performs conditional upgrades, purges orphaned dependencies, clears package caches, and cleans local user thumbnail and screenshot caches. + +## Features + +- **Automated APT Maintenance:** Updates repository indices, checks for upgradeable packages, and runs `apt full-upgrade` only when updates exist. +- **Dependency & Cache Cleanup:** Automatically purges unused dependencies and lingering configuration files (`apt autoremove --purge`) and empties cached installer packages (`apt clean`). +- **User Cache Housekeeping:** Safely cleans desktop thumbnail caches and user screenshot directories without failing if the directories are empty. +- **Robust POSIX C Implementation:** Built using strict standard C (`-std=c11`, POSIX 2008) with explicit bounds checking on formatted buffers and process status checking (`WIFEXITED`, `WEXITSTATUS`). +- **Clean Terminal Output:** Indents subprocess output using `sed` for a clear, readable terminal log. + +--- + +## Prerequisites & Dependencies + +This utility requires a **Debian-based distribution** (Debian, Ubuntu, Linux Mint, Pop!_OS, etc.) with the following installed: + +- `gcc` or `clang` +- `make` +- `apt` +- `sed` +- `sudo` privileges for package management operations + +--- + +## Build Instructions + +To compile the executable using `make`: + +```bash +# Build the utility (uses gcc with -Wall -Wextra -Wpedantic -O2 -std=c11) +make + +# Clean build artifacts +make clean @@ -0,0 +1,154 @@ +/** + * @file main.c + * @brief Automated Debian System Maintenance Utility + * + * Automates package list updates, conditional upgrades, orphaned dependency + * purging, package archive cleaning, and temporary user cache removal. + * + * Requirements: Debian-based distribution (Debian, Ubuntu, Linux Mint) with apt, sed, and sudo. + */ + +#define _POSIX_C_SOURCE 200809L + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <sys/wait.h> + +#define MAX_CMD_LEN 512 +#define MAX_MSG_LEN 128 +#define MAX_LINE_LEN 1024 + +/** + * @brief Prints an underlined section header. + * + * @param message Header text to display. + */ +static void print_message(const char *message) { + if (!message) return; + + printf("\n%s\n", message); + size_t len = strlen(message); + for (size_t i = 0; i < len; i++) { + putchar('-'); + } + putchar('\n'); +} + +/** + * @brief Formats and executes a shell command with indented stdout. + * + * Wraps the shell command to route standard error to /dev/null and + * pipes standard output to sed for clean line indentation. + * + * @param message Display title for the task. + * @param command Command string to execute via subshell. + * @return true if the command executed and returned status 0, false otherwise. + */ +static bool run_task(const char *message, const char *command) { + print_message(message); + + char full_cmd[MAX_CMD_LEN]; + int bytes_written = snprintf(full_cmd, sizeof(full_cmd), + "%s 2>/dev/null | sed 's/^/ /'", command); + + /* Verify buffer bounds to prevent shell command truncation vulnerabilities */ + if (bytes_written < 0 || (size_t)bytes_written >= sizeof(full_cmd)) { + fprintf(stderr, "Error: Command string truncated or invalid.\n"); + return false; + } + + int status = system(full_cmd); + + if (status == -1) { + perror("Error invoking shell process"); + return false; + } + + /* Inspect POSIX exit status from subshell execution */ + if (WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + if (exit_code != 0) { + fprintf(stderr, " [!] Command returned non-zero exit code: %d\n", exit_code); + return false; + } + } else { + fprintf(stderr, " [!] Command terminated abnormally.\n"); + return false; + } + + return true; +} + +/** + * @brief Queries APT for pending upgradable packages. + * + * Spawns a read pipeline to apt list --upgradable and tallies target lines. + * + * @return Count of upgradable packages, or -1 on execution error. + */ +static int count_upgradable_packages(void) { + FILE *fp = popen("apt list --upgradable 2>/dev/null", "r"); + if (!fp) { + perror("Error opening pipe to apt"); + return -1; + } + + int count = 0; + char buffer[MAX_LINE_LEN]; + + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + if (strstr(buffer, "upgradable from:") != NULL) { + count++; + } + } + + int status = pclose(fp); + if (status == -1 || (WIFEXITED(status) && WEXITSTATUS(status) != 0)) { + /* If apt list fails, report 0 to safely skip upgrade block without crashing */ + return 0; + } + + return count; +} + +int main(void) { + /* Step 1: Update local package index */ + run_task("UPDATING LOCAL CACHE `apt update`", "sudo apt update"); + + /* Step 2: Query for upgrades */ + int upgrade_count = count_upgradable_packages(); + + if (upgrade_count < 0) { + fprintf(stderr, "Failed to query pending updates.\n"); + } else if (upgrade_count == 0) { + print_message("NO UPDATES AVAILABLE | UPGRADE SKIPPED"); + } else { + char msg[MAX_MSG_LEN]; + int bytes = snprintf(msg, sizeof(msg), + "%d UPDATE%s AVAILABLE | UPGRADING `apt full-upgrade`", + upgrade_count, (upgrade_count > 1) ? "S" : ""); + + if (bytes > 0 && (size_t)bytes < sizeof(msg)) { + run_task(msg, "sudo apt full-upgrade -y"); + } + } + + /* Step 3: Remove lingering packages and clear package archive caches */ + run_task("PURGING ORPHANED DEPENDENCIES & CONFIGS `apt autoremove --purge`", + "sudo apt autoremove --purge -y"); + + run_task("WIPING ALL DOWNLOADED PACKAGE FILES `apt clean`", + "sudo apt clean"); + + /* Step 4: Clean local user caches safely (suppressing error if glob matches 0 files) */ + run_task("CLEARING LOCAL THUMBNAIL CACHE `rm -rf ~/.cache/thumbnails/*`", + "rm -rf ~/.cache/thumbnails/* 2>/dev/null || true"); + + run_task("CLEARING SCREENSHOTS FOLDER `rm -rf ~/Pictures/Screenshots/*`", + "rm -rf ~/Pictures/Screenshots/* 2>/dev/null || true"); + + putchar('\n'); + return EXIT_SUCCESS; +} |
