paleofetch

mrgrouse's paleofetch fork, a system fetch tool written in C, inspired by suckless' programming style.
Log | Files | Refs | README | LICENSE

paleofetch.c (22513B)


      1 #pragma GCC diagnostic ignored "-Wunused-function"
      2 #include <unistd.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <stdbool.h>
      6 #include <string.h>
      7 #include <dirent.h>
      8 #include <errno.h>
      9 
     10 #include <sys/utsname.h>
     11 #include <sys/sysinfo.h>
     12 #include <sys/statvfs.h>
     13 
     14 #include <pci/pci.h>
     15 
     16 #include <X11/Xlib.h>
     17 #include <X11/Xatom.h>
     18 
     19 #include "paleofetch.h"
     20 #include "config.h"
     21 
     22 #define BUF_SIZE 150
     23 #define COUNT(x) (int)(sizeof x / sizeof *x)
     24 
     25 #define halt_and_catch_fire(fmt, ...) \
     26     do { \
     27         if(status != 0) { \
     28             fprintf(stderr, "paleofetch: " fmt "\n", ##__VA_ARGS__); \
     29             exit(status); \
     30         } \
     31     } while(0)
     32 
     33 struct conf {
     34     char *label, *(*function)();
     35     bool cached;
     36 } config[] = CONFIG;
     37 
     38 struct {
     39     char *substring;
     40     char *repl_str;
     41     size_t length;
     42     size_t repl_len;
     43 } cpu_config[] = CPU_CONFIG, gpu_config[] = GPU_CONFIG;
     44 
     45 Display *display;
     46 struct statvfs file_stats;
     47 struct utsname uname_info;
     48 struct sysinfo my_sysinfo;
     49 int title_length, status;
     50 
     51 /*
     52  * Replaces the first newline character with null terminator
     53  */
     54 void remove_newline(char *s) {
     55     while (*s != '\0' && *s != '\n')
     56         s++;
     57     *s = '\0';
     58 }
     59 
     60 /*
     61  * Replaces the first newline character with null terminator
     62  * and returns the length of the string
     63  */
     64 int remove_newline_get_length(char *s) {
     65     int i;
     66     for (i = 0; *s != '\0' && *s != '\n'; s++, i++);
     67     *s = '\0';
     68     return i;
     69 }
     70 
     71 /*
     72  * Cleans up repeated spaces in a string
     73  * Trim spaces at the front of a string
     74  */
     75 void truncate_spaces(char *str) {
     76     int src = 0, dst = 0;
     77     while(*(str + dst) == ' ') dst++;
     78 
     79     while(*(str + dst) != '\0') {
     80         *(str + src) = *(str + dst);
     81         if(*(str + (dst++)) == ' ')
     82             while(*(str + dst) == ' ') dst++;
     83 
     84         src++;
     85     }
     86 
     87     *(str + src) = '\0';
     88 }
     89 
     90 /*
     91  * Removes the first len characters of substring from str
     92  * Assumes that strlen(substring) >= len
     93  * Returns index where substring was found, or -1 if substring isn't found
     94  */
     95 void remove_substring(char *str, const char* substring, size_t len) {
     96     /* shift over the rest of the string to remove substring */
     97     char *sub = strstr(str, substring);
     98     if(sub == NULL) return;
     99 
    100     int i = 0;
    101     do *(sub+i) = *(sub+i+len);
    102     while(*(sub+(++i)) != '\0');
    103 }
    104 
    105 /*
    106  * Replaces the first sub_len characters of sub_str from str
    107  * with the first repl_len characters of repl_str
    108  */
    109 void replace_substring(char *str, const char *sub_str, const char *repl_str, size_t sub_len, size_t repl_len) {
    110     char buffer[BUF_SIZE / 2];
    111     char *start = strstr(str, sub_str);
    112     if (start == NULL) return; // substring not found
    113 
    114     /* check if we have enough space for new substring */
    115     if (strlen(str) - sub_len + repl_len >= BUF_SIZE / 2) {
    116         status = -1;
    117         halt_and_catch_fire("new substring too long to replace");
    118     }
    119 
    120     strcpy(buffer, start + sub_len);
    121     strncpy(start, repl_str, repl_len);
    122     strcpy(start + repl_len, buffer);
    123 }
    124 
    125 static char *get_title() {
    126     // reduce the maximum size for these, so that we don't over-fill the title string
    127     char hostname[BUF_SIZE / 3];
    128     status = gethostname(hostname, BUF_SIZE / 3);
    129     halt_and_catch_fire("unable to retrieve host name");
    130 
    131     char username[BUF_SIZE / 3];
    132     status = getlogin_r(username, BUF_SIZE / 3);
    133     halt_and_catch_fire("unable to retrieve login name");
    134 
    135     title_length = strlen(hostname) + strlen(username) + 1;
    136 
    137     char *title = malloc(BUF_SIZE);
    138     snprintf(title, BUF_SIZE, COLOR"%s\e[0m@"COLOR"%s", username, hostname);
    139 
    140     return title;
    141 }
    142 
    143 static char *get_bar() {
    144     char *bar = malloc(BUF_SIZE);
    145     char *s = bar;
    146 /*    for(int i = 0; i < title_length; i++) *(s) = "├──────────────────────────────";
    147     for(int i = 0; i < title_length; i++) *(s++) = '='; */
    148     for(int i = 0; i < title_length; i++) *(s++) = '\0';
    149     *s = '\0';
    150     return bar;
    151 }
    152 
    153 static char *get_os() {
    154     char *os = malloc(BUF_SIZE),
    155          *name = malloc(BUF_SIZE),
    156          *line = NULL;
    157     size_t len;
    158     FILE *os_release = fopen("/etc/os-release", "r");
    159     if(os_release == NULL) {
    160         status = -1;
    161         halt_and_catch_fire("unable to open /etc/os-release");
    162     }
    163 
    164     while (getline(&line, &len, os_release) != -1) {
    165         if (sscanf(line, "NAME=\"%[^\"]+", name) > 0) break;
    166     }
    167 
    168     free(line);
    169     fclose(os_release);
    170     snprintf(os, BUF_SIZE, "%s %s", name, uname_info.machine);
    171     free(name);
    172 
    173     return os;
    174 }
    175 
    176 static char *get_kernel() {
    177     char *kernel = malloc(BUF_SIZE);
    178     strncpy(kernel, uname_info.release, BUF_SIZE);
    179     return kernel;
    180 }
    181 
    182 static char *get_host() {
    183     char *host = malloc(BUF_SIZE), buffer[BUF_SIZE/2];
    184     FILE *product_name, *product_version, *model;
    185 
    186     if((product_name = fopen("/sys/devices/virtual/dmi/id/product_name", "r")) != NULL) {
    187         if((product_version = fopen("/sys/devices/virtual/dmi/id/product_version", "r")) != NULL) {
    188             fread(host, 1, BUF_SIZE/2, product_name);
    189             remove_newline(host);
    190             strcat(host, " ");
    191             fread(buffer, 1, BUF_SIZE/2, product_version);
    192             remove_newline(buffer);
    193             strcat(host, buffer);
    194             fclose(product_version);
    195         } else {
    196             fclose(product_name);
    197             goto model_fallback;
    198         }
    199         fclose(product_name);
    200         return host;
    201     }
    202 
    203 model_fallback:
    204     if((model = fopen("/sys/firmware/devicetree/base/model", "r")) != NULL) {
    205         fread(host, 1, BUF_SIZE, model);
    206         remove_newline(host);
    207         return host;
    208     }
    209 
    210     status = -1;
    211     halt_and_catch_fire("unable to get host");
    212     return NULL;
    213 }
    214 
    215 static char *get_uptime() {
    216     long seconds = my_sysinfo.uptime;
    217     struct { char *name; int secs; } units[] = {
    218         { "day",  60 * 60 * 24 },
    219         { "hour", 60 * 60 },
    220         { "min",  60 },
    221     };
    222 
    223     int n, len = 0;
    224     char *uptime = malloc(BUF_SIZE);
    225     for (int i = 0; i < 3; ++i ) {
    226         if ((n = seconds / units[i].secs) || i == 2) /* always print minutes */
    227             len += snprintf(uptime + len, BUF_SIZE - len, 
    228                             "%d %s%s, ", n, units[i].name, n != 1 ? "s": "");
    229         seconds %= units[i].secs;
    230     }
    231 
    232     // null-terminate at the trailing comma
    233     uptime[len - 2] = '\0';
    234     return uptime;
    235 }
    236 
    237 // returns "<Battery Percentage>% [<Charging | Discharging | Unknown>]"
    238 // Credit: allisio - https://gist.github.com/allisio/1e850b93c81150124c2634716fbc4815
    239 static char *get_battery_percentage() {
    240   int battery_capacity;
    241   FILE *capacity_file, *status_file;
    242   char battery_status[12] = "Unknown";
    243 
    244   if ((capacity_file = fopen(BATTERY_DIRECTORY "/capacity", "r")) == NULL) {
    245     status = ENOENT;
    246     halt_and_catch_fire("Unable to get battery information");
    247   }
    248 
    249   fscanf(capacity_file, "%d", &battery_capacity);
    250   fclose(capacity_file);
    251 
    252   if ((status_file = fopen(BATTERY_DIRECTORY "/status", "r")) != NULL) {
    253     fscanf(status_file, "%s", battery_status);
    254     fclose(status_file);
    255   }
    256 
    257   // max length of resulting string is 19
    258   // one byte for padding incase there is a newline
    259   // 100% [Discharging]
    260   // 1234567890123456789
    261   char *battery = malloc(20);
    262 
    263   snprintf(battery, 20, "%d%% [%s]", battery_capacity, battery_status);
    264 
    265   return battery;
    266 }
    267 
    268 static char *get_packages(const char* dirname, const char* pacname, int num_extraneous) {
    269     int num_packages = 0;
    270     DIR * dirp;
    271     struct dirent *entry;
    272 
    273     dirp = opendir(dirname);
    274 
    275     if(dirp == NULL) {
    276         status = -1;
    277         halt_and_catch_fire("You may not have %s installed", dirname);
    278     }
    279 
    280     while((entry = readdir(dirp)) != NULL) {
    281         if(entry->d_type == DT_DIR) num_packages++;
    282     }
    283     num_packages -= (2 + num_extraneous); // accounting for . and ..
    284 
    285     status = closedir(dirp);
    286 
    287     char *packages = malloc(BUF_SIZE);
    288     snprintf(packages, BUF_SIZE, "%d (%s)", num_packages, pacname);
    289 
    290     return packages;
    291 }
    292 
    293 static char *get_packages_pacman() {
    294     return get_packages("/var/lib/pacman/local", "pacman", 0);
    295 }
    296 
    297 static char *get_shell() {
    298     char *shell = malloc(BUF_SIZE);
    299     char *shell_path = getenv("SHELL");
    300     char *shell_name = strrchr(getenv("SHELL"), '/');
    301 
    302     if(shell_name == NULL) /* if $SHELL doesn't have a '/' */
    303         strncpy(shell, shell_path, BUF_SIZE); /* copy the whole thing over */
    304     else
    305         strncpy(shell, shell_name + 1, BUF_SIZE); /* o/w copy past the last '/' */
    306 
    307     return shell;
    308 }
    309 
    310 static char *get_resolution() {
    311     int screen, width, height;
    312     char *resolution = malloc(BUF_SIZE);
    313     
    314     if (display != NULL) {
    315         screen = DefaultScreen(display);
    316     
    317         width = DisplayWidth(display, screen);
    318         height = DisplayHeight(display, screen);
    319 
    320         snprintf(resolution, BUF_SIZE, "%dx%d", width, height);
    321     } else {
    322         DIR *dir;
    323         struct dirent *entry;
    324         char dir_name[] = "/sys/class/drm";
    325         char modes_file_name[BUF_SIZE * 2];
    326         FILE *modes;
    327         char *line = NULL;
    328         size_t len;
    329         
    330         /* preload resolution with empty string, in case we cant find a resolution through parsing */
    331         strncpy(resolution, "", BUF_SIZE);
    332 
    333         dir = opendir(dir_name);
    334         if (dir == NULL) {
    335             status = -1;
    336             halt_and_catch_fire("Could not open /sys/class/drm to determine resolution in tty mode.");
    337         }
    338         /* parse through all directories and look for a non empty modes file */
    339         while ((entry = readdir(dir)) != NULL) {
    340             if (entry->d_type == DT_LNK) {
    341                 snprintf(modes_file_name, BUF_SIZE * 2, "%s/%s/modes", dir_name, entry->d_name);
    342 
    343                 modes = fopen(modes_file_name, "r");
    344                 if (modes != NULL) {
    345                     if (getline(&line, &len, modes) != -1) {
    346                         strncpy(resolution, line, BUF_SIZE);
    347                         remove_newline(resolution);
    348 
    349                         free(line);
    350                         fclose(modes);
    351 
    352                         break;
    353                     }
    354 
    355                     fclose(modes);
    356                 }
    357             }
    358         }
    359         
    360         closedir(dir);
    361     }
    362 
    363     return resolution;
    364 }
    365 
    366 static char *get_terminal() {
    367     unsigned char *prop;
    368     char *terminal = malloc(BUF_SIZE);
    369 
    370     /* check if xserver is running or if we are running in a straight tty */
    371     if (display != NULL) {   
    372 
    373     unsigned long _, // not unused, but we don't need the results
    374                   window = RootWindow(display, XDefaultScreen(display));    
    375         Atom a,
    376              active = XInternAtom(display, "_NET_ACTIVE_WINDOW", True),
    377              class = XInternAtom(display, "WM_CLASS", True);
    378 
    379 #define GetProp(property) \
    380         XGetWindowProperty(display, window, property, 0, 64, 0, 0, &a, (int *)&_, &_, &_, &prop);
    381 
    382         GetProp(active);
    383         window = (prop[3] << 24) + (prop[2] << 16) + (prop[1] << 8) + prop[0];
    384         free(prop);
    385         if(!window) goto terminal_fallback;
    386         GetProp(class);
    387 
    388 #undef GetProp
    389 
    390         snprintf(terminal, BUF_SIZE, "%s", prop);
    391         free(prop);
    392     } else {
    393 terminal_fallback:
    394         strncpy(terminal, getenv("TERM"), BUF_SIZE); /* fallback to old method */
    395         /* in tty, $TERM is simply returned as "linux"; in this case get actual tty name */
    396         if (strcmp(terminal, "linux") == 0) {
    397             strncpy(terminal, ttyname(STDIN_FILENO), BUF_SIZE);
    398         }
    399     }
    400 
    401     return terminal;
    402 }
    403 
    404 static char *get_cpu() {
    405     FILE *cpuinfo = fopen("/proc/cpuinfo", "r"); /* read from cpu info */
    406     if(cpuinfo == NULL) {
    407         status = -1;
    408         halt_and_catch_fire("Unable to open cpuinfo");
    409     }
    410 
    411     char *cpu_model = malloc(BUF_SIZE / 2);
    412     char *line = NULL;
    413     size_t len; /* unused */
    414     int num_cores = 0, cpu_freq, prec = 3;
    415     double freq;
    416     char freq_unit[] = "GHz";
    417 
    418     /* read the model name into cpu_model, and increment num_cores every time model name is found */
    419     while(getline(&line, &len, cpuinfo) != -1) {
    420         num_cores += sscanf(line, "model name	: %[^\n@]", cpu_model);
    421     }
    422     free(line);
    423     fclose(cpuinfo);
    424 
    425     FILE *cpufreq = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r");
    426     line = NULL;
    427 
    428     if (cpufreq != NULL) {
    429         if (getline(&line, &len, cpufreq) != -1) {
    430             sscanf(line, "%d", &cpu_freq);
    431             cpu_freq /= 1000; // convert kHz to MHz
    432         } else {
    433             fclose(cpufreq);
    434             free(line);
    435             goto cpufreq_fallback;
    436         }
    437     } else {
    438 cpufreq_fallback:
    439         cpufreq = fopen("/proc/cpuinfo", "r"); /* read from cpu info */
    440         if (cpufreq == NULL) {
    441             status = -1;
    442             halt_and_catch_fire("Unable to open cpuinfo");
    443         }
    444 
    445         while (getline(&line, &len, cpufreq) != -1) {
    446             if (sscanf(line, "cpu MHz : %lf", &freq) > 0) break;
    447         }
    448 
    449         cpu_freq = (int) freq;
    450     }
    451 
    452     free(line);
    453     fclose(cpufreq);
    454 
    455     if (cpu_freq < 1000) {
    456         freq = (double) cpu_freq;
    457         freq_unit[0] = 'M'; // make MHz from GHz
    458         prec = 0; // show frequency as integer value
    459     } else {
    460         freq = cpu_freq / 1000.0; // convert MHz to GHz and cast to double
    461 
    462         while (cpu_freq % 10 == 0) {
    463             --prec;
    464             cpu_freq /= 10;
    465         }
    466 
    467         if (prec == 0) prec = 1; // we don't want zero decimal places
    468     }
    469 
    470     /* remove unneeded information */
    471     for (int i = 0; i < COUNT(cpu_config); ++i) {
    472         if (cpu_config[i].repl_str == NULL) {
    473             remove_substring(cpu_model, cpu_config[i].substring, cpu_config[i].length);
    474         } else {
    475             replace_substring(cpu_model, cpu_config[i].substring, cpu_config[i].repl_str, cpu_config[i].length, cpu_config[i].repl_len);
    476         }
    477     }
    478 
    479     char *cpu = malloc(BUF_SIZE);
    480     snprintf(cpu, BUF_SIZE, "%s (%d) @ %.*f%s", cpu_model, num_cores, prec, freq, freq_unit);
    481     free(cpu_model);
    482 
    483     truncate_spaces(cpu);
    484 
    485     if(num_cores == 0)
    486         *cpu = '\0';
    487     return cpu;
    488 }
    489 
    490 static char *find_gpu(int index) {
    491     // inspired by https://github.com/pciutils/pciutils/edit/master/example.c
    492     /* it seems that pci_lookup_name needs to be given a buffer, but I can't for the life of my figure out what its for */
    493     char buffer[BUF_SIZE], *device_class, *gpu = malloc(BUF_SIZE);
    494     struct pci_access *pacc;
    495     struct pci_dev *dev;
    496     int gpu_index = 0;
    497     bool found = false;
    498 
    499     pacc = pci_alloc();
    500     pci_init(pacc);
    501     pci_scan_bus(pacc);
    502     dev = pacc->devices;
    503 
    504     while(dev != NULL) {
    505         pci_fill_info(dev, PCI_FILL_IDENT);
    506         device_class = pci_lookup_name(pacc, buffer, sizeof(buffer), PCI_LOOKUP_CLASS, dev->device_class);
    507         if(strcmp("VGA compatible controller", device_class) == 0 || strcmp("3D controller", device_class) == 0) {
    508             strncpy(gpu, pci_lookup_name(pacc, buffer, sizeof(buffer), PCI_LOOKUP_DEVICE | PCI_LOOKUP_VENDOR, dev->vendor_id, dev->device_id), BUF_SIZE);
    509             if(gpu_index == index) {
    510                 found = true;
    511                 break;
    512             } else {
    513                 gpu_index++;
    514             }
    515         }
    516 
    517         dev = dev->next;
    518     }
    519 
    520     if (found == false) *gpu = '\0'; // empty string, so it will not be printed
    521 
    522     pci_cleanup(pacc);
    523 
    524     /* remove unneeded information */
    525     for (int i = 0; i < COUNT(gpu_config); ++i) {
    526         if (gpu_config[i].repl_str == NULL) {
    527             remove_substring(gpu, gpu_config[i].substring, gpu_config[i].length);
    528         } else {
    529             replace_substring(gpu, gpu_config[i].substring, gpu_config[i].repl_str, gpu_config[i].length, gpu_config[i].repl_len);
    530         }
    531     }
    532 
    533     truncate_spaces(gpu);
    534 
    535     return gpu;
    536 }
    537 
    538 static char *get_gpu1() {
    539     return find_gpu(0);
    540 }
    541 
    542 static char *get_gpu2() {
    543     return find_gpu(1);
    544 }
    545 
    546 static char *get_memory() {
    547     int total_memory, used_memory;
    548     int total, shared, memfree, buffers, cached, reclaimable;
    549 
    550     FILE *meminfo = fopen("/proc/meminfo", "r"); /* get infomation from meminfo */
    551     if(meminfo == NULL) {
    552         status = -1;
    553         halt_and_catch_fire("Unable to open meminfo");
    554     }
    555 
    556     /* We parse through all lines of meminfo and scan for the information we need */
    557     char *line = NULL; // allocation handled automatically by getline()
    558     size_t len; /* unused */
    559 
    560     /* parse until EOF */
    561     while (getline(&line, &len, meminfo) != -1) {
    562         /* if sscanf doesn't find a match, pointer is untouched */
    563         sscanf(line, "MemTotal: %d", &total);
    564         sscanf(line, "Shmem: %d", &shared);
    565         sscanf(line, "MemFree: %d", &memfree);
    566         sscanf(line, "Buffers: %d", &buffers);
    567         sscanf(line, "Cached: %d", &cached);
    568         sscanf(line, "SReclaimable: %d", &reclaimable);
    569     }
    570 
    571     free(line);
    572 
    573     fclose(meminfo);
    574 
    575     /* use same calculation as neofetch */
    576     used_memory = (total + shared - memfree - buffers - cached - reclaimable) / 1024;
    577     total_memory = total / 1024;
    578     int percentage = (int) (100 * (used_memory / (double) total_memory));
    579 
    580     char *memory = malloc(BUF_SIZE);
    581     snprintf(memory, BUF_SIZE, "%dMiB / %dMiB (%d%%)", used_memory, total_memory, percentage);
    582 
    583     return memory;
    584 }
    585 
    586 static char *get_disk_usage(const char *folder) {
    587     char *disk_usage = malloc(BUF_SIZE);
    588     long total, used, free;
    589     int percentage;
    590     status = statvfs(folder, &file_stats);
    591     halt_and_catch_fire("Error getting disk usage for %s", folder);
    592     total = file_stats.f_blocks * file_stats.f_frsize;
    593     free = file_stats.f_bfree * file_stats.f_frsize;
    594     used = total - free;
    595     percentage = (used / (double) total) * 100;
    596 #define TO_GB(A) ((A) / (1024.0 * 1024 * 1024))
    597     snprintf(disk_usage, BUF_SIZE, "%.1fGiB / %.1fGiB (%d%%)", TO_GB(used), TO_GB(total), percentage);
    598 #undef TO_GB
    599     return disk_usage;
    600 }
    601 
    602 static char *get_disk_usage_root() {
    603     return get_disk_usage("/");
    604 }
    605 
    606 static char *get_disk_usage_home() {
    607     return get_disk_usage("/home");
    608 }
    609 
    610 static char *get_colors1() {
    611     char *colors1 = malloc(BUF_SIZE);
    612     char *s = colors1;
    613 
    614     for(int i = 0; i < 8; i++) {
    615         sprintf(s, "\e[4%dm   ", i);
    616         s += 8;
    617     }
    618     snprintf(s, 5, "\e[0m");
    619 
    620     return colors1;
    621 }
    622 
    623 static char *get_colors2() {
    624     char *colors2 = malloc(BUF_SIZE);
    625     char *s = colors2;
    626 
    627     for(int i = 8; i < 16; i++) {
    628         sprintf(s, "\e[48;5;%dm   ", i);
    629         s += 12 + (i >= 10 ? 1 : 0);
    630     }
    631     snprintf(s, 5, "\e[0m");
    632 
    633     return colors2;
    634 }
    635 
    636 static char *spacer() {
    637     return calloc(1, 1); // freeable, null-terminated string of length 1
    638 }
    639 
    640 char *get_cache_file() {
    641     char *cache_file = malloc(BUF_SIZE);
    642     char *env = getenv("XDG_CACHE_HOME");
    643     if(env == NULL)
    644         snprintf(cache_file, BUF_SIZE, "%s/.cache/paleofetch", getenv("HOME"));
    645     else
    646         snprintf(cache_file, BUF_SIZE, "%s/paleofetch", env);
    647 
    648     return cache_file;
    649 }
    650 
    651 /* This isn't especially robust, but as long as we're the only one writing
    652  * to our cache file, the format is simple, effective, and fast. One way
    653  * we might get in trouble would be if the user decided not to have any
    654  * sort of sigil (like ':') after their labels. */
    655 char *search_cache(char *cache_data, char *label) {
    656     char *start = strstr(cache_data, label);
    657     if(start == NULL) {
    658         status = ENODATA;
    659         halt_and_catch_fire("cache miss on key '%s'; need to --recache?", label);
    660     }
    661     start += strlen(label);
    662     char *end = strchr(start, ';');
    663     char *buf = calloc(1, BUF_SIZE);
    664     // skip past the '=' and stop just before the ';'
    665     strncpy(buf, start + 1, end - start - 1);
    666 
    667     return buf;
    668 }
    669 
    670 char *get_value(struct conf c, int read_cache, char *cache_data) {
    671     char *value;
    672 
    673     // If the user's config specifies that this value should be cached
    674     if(c.cached && read_cache) // and we have a cache to read from
    675         value = search_cache(cache_data, c.label); // grab it from the cache
    676     else {
    677         // Otherwise, call the associated function to get the value
    678         value = c.function();
    679         if(c.cached) { // and append it to our cache data if appropriate
    680             char *buf = malloc(BUF_SIZE);
    681             sprintf(buf, "%s=%s;", c.label, value);
    682             strcat(cache_data, buf);
    683             free(buf);
    684         }
    685     }
    686 
    687     return value;
    688 }
    689 
    690 int main(int argc, char *argv[]) {
    691     char *cache, *cache_data = NULL;
    692     FILE *cache_file;
    693     int read_cache;
    694 
    695     status = uname(&uname_info);
    696     halt_and_catch_fire("uname failed");
    697     status = sysinfo(&my_sysinfo);
    698     halt_and_catch_fire("sysinfo failed");
    699     display = XOpenDisplay(NULL);
    700 
    701     cache = get_cache_file();
    702     if(argc == 2 && strcmp(argv[1], "--recache") == 0)
    703         read_cache = 0;
    704     else {
    705         cache_file = fopen(cache, "r");
    706         read_cache = cache_file != NULL;
    707     }
    708 
    709     if(!read_cache)
    710         cache_data = calloc(4, BUF_SIZE); // should be enough
    711     else {
    712         size_t len; /* unused */
    713         getline(&cache_data, &len, cache_file);
    714         fclose(cache_file); // We just need the first (and only) line.
    715     }
    716 
    717     int offset = 0;
    718 
    719     for (int i = 0; i < COUNT(LOGO); i++) {
    720         // If we've run out of information to show...
    721         if(i >= COUNT(config) - offset) // just print the next line of the logo
    722             printf(COLOR"%s\n", LOGO[i]);
    723         else {
    724             // Otherwise, we've got a bit of work to do.
    725             char *label = config[i+offset].label,
    726                  *value = get_value(config[i+offset], read_cache, cache_data);
    727             if (strcmp(value, "") != 0) { // check if value is an empty string
    728                 printf(COLOR"%s%s\e[0m%s\n", LOGO[i], label, value); // just print if not empty
    729             } else {
    730                 if (strcmp(label, "") != 0) { // check if label is empty, otherwise it's a spacer
    731                     ++offset; // print next line of information
    732                     free(value); // free memory allocated for empty value
    733                     label = config[i+offset].label; // read new label and value
    734                     value = get_value(config[i+offset], read_cache, cache_data);
    735                 }
    736                 printf(COLOR"%s%s\e[0m%s\n", LOGO[i], label, value);
    737             }
    738             free(value);
    739 
    740         }
    741     }
    742     puts("\e[0m");
    743 
    744     /* Write out our cache data (if we have any). */
    745     if(!read_cache && *cache_data) {
    746         cache_file = fopen(cache, "w");
    747         fprintf(cache_file, "%s", cache_data);
    748         fclose(cache_file);
    749     }
    750 
    751     free(cache);
    752     free(cache_data);
    753     if(display != NULL) { 
    754         XCloseDisplay(display);
    755     }
    756 
    757     return 0;
    758 }