← Back to davo.co
aboutsummaryrefslogtreecommitdiffstats
path: root/main.c
blob: 837758556823be7ee98c178a27c2651c496eee58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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;
}