← Back to davo.co
summaryrefslogtreecommitdiffstats
path: root/main.c
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 /main.c
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
Diffstat (limited to 'main.c')
-rw-r--r--main.c154
1 files changed, 154 insertions, 0 deletions
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;
+}