diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..40b10df --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,14 @@ +name: build +on: [push,pull_request,workflow_dispatch] + +jobs: + clang-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Clang + run: sudo apt update && sudo apt install -y clang + + - name: Build Datum + run: clang zfetch.c -Wall -Wextra -Werror -pedantic-errors -fstack-protector-all -fsanitize=undefined -fsanitize=address -Wwrite-strings -o zfetch \ No newline at end of file diff --git a/README.md b/README.md index b0dfc58..f23fc9d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,100 @@ -# zFetch -Minimalistic system fetcher for Linux platform written in C99. \ No newline at end of file +
+

zFetch

+ +
Lightweight system information fetcher for Linux.
+ +![](https://git.marcocetica.com/marco/zfetch/actions/workflows/build.yml/badge.svg) +![Static Badge](https://img.shields.io/badge/license-MIT-blue) +
+ +**zFetch** is a lightweight system information fetcher for Linux written in C99. +Unlike other system fetchers, this tool focuses on providing a simple, easy-to-remember +interface and configuration syntax, extremely fast startup and a reasonable amount +of core features. + +## Installation +zFetch consists on a single source file which can be compiled with any modern C +compiler (Clang/GCC) on a Linux-based system. In order to build it, you can just run +`gcc zfetch.c -o zfetch` or use the following Makefile rule: + +```sh +$ make clean all +rm -f *.o *.a zfetch +clang -Wall -Wextra -Werror -pedantic-errors -Wwrite-strings -std=c99 -O3 zfetch.c -o zfetch +``` + +In both cases, you will get a binary file named `zfetch` that you can move wherever you +want. + +## Usage +As stated before, I've built zFetch to be extremely simple to use since I believe that system +fetchers play a paramount role on providing essential data to whoever has a lot of machines to manage. + +In its most basic form, you can just invoke the program without any additional parameter and it will +run with all option enabled, that is: + +![](imgs/ex1.png) + +If you instead would like to granularly enable or disable some features, +you can do so by creating a configuration file in one of the following paths: + +- `$HOME/.zfetch.conf`; +- `$HOME/.config/zfetch/conf` (**this takes precedence**). + +Inside it, you can specify which option to enable and which one to disable: + +```conf +HOST = 1 +OS = 0 +UPTIME = 1 +CPU = 1 +MEMORY = 1 +DISK = 1 +IP = 1 +LOGO = 1 +BARS = 0 +``` + +Any other line will be considered invalid and silently skipped by the builtin parser. +To retrieve which options are currently enabled, you can run the program with the `-s` flag, that is: + +```sh +$ ./zfetch --list-opts # or -s +HOST : ON +OS : OFF +UPTIME : ON +CPU : ON +MEMORY : ON +DISK : ON +IP : ON +LOGO : ON +BARS : OFF +``` + +You can also dynamically specify a different path by using the `-c` CLI argument: + +```sh +$ ./zfetch -c $PWD/config -s +Using custom config file: '/home/marco/Projects/zfetch/config' +HOST : OFF +OS : OFF +UPTIME : OFF +CPU : OFF +MEMORY : ON +DISK : ON +IP : ON +LOGO : OFF +BARS : ON +``` + +Finally, you can list all supported distribution, using the `--list-logos/-a` flag: + +```sh +$ /zfetch -a +Available logos: alpine, arch, debian, fedora, mint, gentoo, artix, linux, nixos, redhat, slackware, suse, ubuntu. +``` + + +## License + +[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file diff --git a/imgs/ex1.png b/imgs/ex1.png new file mode 100644 index 0000000..3af6714 Binary files /dev/null and b/imgs/ex1.png differ diff --git a/imgs/preview.gif b/imgs/preview.gif new file mode 100644 index 0000000..0717721 Binary files /dev/null and b/imgs/preview.gif differ diff --git a/zfetch.c b/zfetch.c index 335badb..e2f93c5 100644 --- a/zfetch.c +++ b/zfetch.c @@ -198,6 +198,17 @@ static const char *logo_ubuntu[] = { NULL }; +static const char *logo_mint[] = { + C2 " __________", + C2 "|_ \\", + C2 " |" C7" | _____" C2 " |", + C2 " |" C7" | | | |" C2 " |", + C2 " |" C7" | | | |" C2 " |", + C2 " |" C7" \\_____/" C2 " |", + C2 " \\_________/", + NULL +}; + static const char *logo_suse[] = { C2 " _______", C2 "__| __ \\", @@ -232,6 +243,7 @@ static const logo_entry_t available_logos[] = { { C6 "arch" RESET, logo_arch }, { C1 "debian" RESET, logo_debian }, { C4 "fedora" RESET, logo_fedora }, + { C2 "mint" RESET, logo_mint }, { C5 "gentoo" RESET, logo_gentoo }, { C6 "artix" RESET, logo_artix }, { C3 "linux" RESET, logo_linux }, @@ -440,6 +452,7 @@ static const char **get_logo(const char *id) { else if (STRCMP(id, "slackware")) { return logo_slackware; } else if (STRCMP(id, "alpine")) { return logo_alpine; } else if (STRCMP(id, "ubuntu")) { return logo_ubuntu; } + else if (STRCMP(id, "mint")) { return logo_mint; } else if (STRCMP(id, "opensuse") || STRCMP(id, "opensuse-tumbleweed") || STRCMP(id, "opensuse-leap") || STRCMP(id, "sles") || STRCMP(id, "suse")) { return logo_suse; } @@ -469,7 +482,7 @@ static const char *get_logo_accent(const char **logo) { return C5; // Magenta } else if (logo == logo_linux || logo == logo_ubuntu) { return C3; // Yellow - } else if (logo == logo_suse) { + } else if (logo == logo_suse || logo == logo_mint) { return C2; // Green } @@ -565,6 +578,35 @@ static inline void str_to_lower(char *str) { } } +static inline size_t fmt_uptime(char *buf, size_t buf_size, size_t offset, uint64_t value, const char *unit) { + if (value == 0) { + return offset; + } + + const char *sep = (offset == 0) ? "" : ", "; + + if (offset >= buf_size) { + return offset; + } + + int n = snprintf(buf + offset, buf_size - offset, + "%s%" PRIu64 " %s%s", + sep, value, unit, + value == 1 ? "" : "s"); + + if (n < 0) { + return offset; + } + + const size_t written = (size_t)n; + + if (written >= buf_size - offset) { + return buf_size; + } + + return offset + written; +} + /* * Features */ @@ -589,16 +631,15 @@ static void get_hostname(char *buf, size_t buf_size) { static void get_uptime(char *buf, size_t buf_size) { FILE *fp = fopen("/proc/uptime", "r"); uint64_t total = 0; - uint64_t days, hours, minutes; if (fp == NULL) { snprintf(buf, buf_size, "unknown"); return; } - // The /proc/uptime has two fields formatted as follows: + // The /proc/uptime file has two fields formatted as follows: // . . - // We only read the seconds and ignore the fractional part + // We only read and ignore if (fscanf(fp, "%" SCNu64, &total) != 1) { fclose(fp); snprintf(buf, buf_size, "unknown"); @@ -607,21 +648,19 @@ static void get_uptime(char *buf, size_t buf_size) { fclose(fp); - days = total / 86400ULL; - hours = (total % 86400ULL) / 3600ULL; - minutes = (total % 3600ULL) / 60ULL; + const uint64_t days = total / 86400ULL; + const uint64_t hours = (total % 86400ULL) / 3600ULL; + const uint64_t minutes = (total % 3600ULL) / 60ULL; + const uint64_t seconds = total % 60ULL; - if (days > 0) { - snprintf(buf, buf_size, "%" PRIu64 " day%s, %" PRIu64 " hour%s, %" PRIu64 " min", - days, days == 1 ? "" : "s", - hours, hours == 1 ? "" : "s", - minutes); - } else if (hours > 0) { - snprintf(buf, buf_size, "%" PRIu64 " hour%s, %" PRIu64 " min", - hours, hours == 1 ? "" : "s", - minutes); - } else { - snprintf(buf, buf_size, "%" PRIu64 " min", minutes); + size_t offset = 0; + + offset = fmt_uptime(buf, buf_size, offset, days, "day"); + offset = fmt_uptime(buf, buf_size, offset, hours, "hour"); + offset = fmt_uptime(buf, buf_size, offset, minutes, "minute"); + + if (offset == 0) { + offset = fmt_uptime(buf, buf_size, offset, seconds, "second"); } } @@ -709,7 +748,7 @@ static size_t get_ips(char entries[][IFACE_ENTRY_LEN], size_t max_entries) { char ipv4[INET_ADDRSTRLEN]; struct sockaddr_in *sin; - // Skip loopback, disabled interfaces and non-IPV4 interfaces + // Skip loopback, disabled interfaces and non-IPv4 interfaces if (!ifa->ifa_addr) { continue; } if (ifa->ifa_addr->sa_family != AF_INET) { continue; } if (!(ifa->ifa_flags & IFF_UP)) { continue; } @@ -767,9 +806,9 @@ static void get_options(uint16_t options, const char *config_path) { for (size_t idx = 0; idx < ARR_LEN(flags); idx++) { const bool enabled = (options & flags[idx].flag) != 0; - printf("%-11s %s%s%s\n", flags[idx].name, + printf("%-6s : %s%s%s\n", flags[idx].name, enabled ? C2 : C1, - enabled ? "enabled" : "disabled", + enabled ? "ON" : "OFF", RESET); } } @@ -865,7 +904,8 @@ int main(int argc, char **argv) { }; uint16_t options = 0; os_t os_release; - line_t lines[64]; + // number of available options + max number of network interfaces + line_t lines[9 + IFACE_ENTRY_LEN]; size_t line_count = 0; bool list_logos = false, list_opts = false; int opt;