← Back to davo.co
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Faulkner <[email protected]>2026-08-05 23:25:25 -0500
committerDavid Faulkner <[email protected]>2026-08-05 23:25:25 -0500
commit3d51ff671e0bf079063c6270bbb985bd3d218b34 (patch)
tree2d99d81b2e0c1cad6ce119dfc6da5844ed49b3e1
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--.gitignore7
-rw-r--r--LICENSE20
-rw-r--r--Makefile34
-rw-r--r--README.md36
-rw-r--r--main.c154
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6653785
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8377585
--- /dev/null
+++ b/main.c
@@ -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;
+}