ssf
Owner: IIIlllIIIllI URL: git@git.0x00nyx.xyz:seb/ssf.git
init
Commit a52d90f720cfd0cb030481a69c767c81973966be by SM <seb.michalk@gmail.com> on 2025-09-23 15:19:28 +0200
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..263fd99
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,34 @@
+# ssf - simple system fetcher
+
+include config.mk
+
+SRC = ssf.c
+OBJ = ${SRC:.c=.o}
+BIN = ssf
+
+all: options ${BIN}
+
+options:
+ @echo "sysinfo build options:"
+ @echo "CC = ${CC}"
+ @echo "CPPFLAGS = ${CPPFLAGS}"
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+
+${BIN}: ${OBJ}
+ ${CC} ${OBJ} ${LDFLAGS} -o $@
+
+%.o: %.c ascii
+ ${CC} ${CPPFLAGS} ${CFLAGS} -c $< -o $@
+
+clean:
+ rm -f ${BIN} ${OBJ}
+
+install: all
+ mkdir -p ${DESTDIR}${BINDIR}
+ install -m 755 ${BIN} ${DESTDIR}${BINDIR}/${BIN}
+
+uninstall:
+ rm -f ${DESTDIR}${BINDIR}/${BIN}
+
+.PHONY: all options clean install uninstall
diff --git a/ascii b/ascii
new file mode 100644
index 0000000..4c4c09d
--- /dev/null
+++ b/ascii
@@ -0,0 +1,248 @@
+static const char *ascii_alpine[] = {
+ " /\\ /\\",
+ " /./ \\ \\",
+ " /./ \\ \\",
+ "/./ \\ \\",
+ "// \\ \\",
+ " \\",
+ NULL
+};
+
+static const char *ascii_android[] = {
+ " ;, ,;",
+ " ';,.-----.,;'",
+ " ,' ',",
+ " / O O \\",
+ "| |",
+ "'-----------------'",
+ NULL
+};
+
+static const char *ascii_arch[] = {
+ " /\\",
+ " / \\",
+ " /\\ \\",
+ " / \\",
+ " / ,, \\",
+ " / | | -\\",
+ " /_-'' ''-_\\",
+ NULL
+};
+
+static const char *ascii_arco[] = {
+ " /\\",
+ " / \\",
+ " / /\\ \\",
+ " / / \\ \\",
+ " / / \\ \\",
+ " / / _____\\ \\",
+ "/_/ `----.\\_\\",
+ NULL
+};
+
+static const char *ascii_artix[] = {
+ " /\\",
+ " / \\",
+ " /`'.,\\",
+ " / ',",
+ " / ,`\\",
+ " / ,.'`. \\",
+ "/.,`' `'.\\",
+ NULL
+};
+
+static const char *ascii_centos[] = {
+ " ____^____",
+ " |\\ | /|",
+ " | \\ | / |",
+ "<---- ---->",
+ " | / | \\ |",
+ " |/__| __\\|",
+ " v",
+ NULL
+};
+
+static const char *ascii_debian[] = {
+ " _____",
+ " / __ \\",
+ "| / |",
+ "| \\___-",
+ "-_",
+ " --_",
+ NULL
+};
+
+static const char *ascii_endeavour[] = {
+ " /\\",
+ " // \\\\",
+ " // \\ \\",
+ " / // _) )",
+ "/_/___-- __-",
+ " /____--",
+ NULL
+};
+
+static const char *ascii_fedora[] = {
+ " ,'''''. ",
+ " | ,. | ",
+ " | | '_' ",
+ " ,....| |.. ",
+ ".' ,_;| ..' ",
+ "| | | | ",
+ "| ',_,' | ",
+ " '. ,' ",
+ " ''''' ",
+ NULL
+};
+
+static const char *ascii_freebsd[] = {
+ "/\\,-'''''-,/\\",
+ "\\_) (_/",
+ "| |",
+ "| |",
+ " ; ;",
+ " '-_____-'",
+ NULL
+};
+
+static const char *ascii_gentoo[] = {
+ " _-----_",
+ "( \\",
+ "\\ 0 \\",
+ " \\ )",
+ " / _/",
+ "( _-",
+ "\\____-",
+ NULL
+};
+
+static const char *ascii_linux[] = {
+ " ___",
+ " (.. |",
+ " (<> |",
+ " / __ \\",
+ " ( / \\ /|",
+ "_/\\ __)/_)",
+ "\\/----\\/",
+ NULL
+};
+
+static const char *ascii_linux_mint[] = {
+ " ___________",
+ "|_ \\",
+ " | | _____ |",
+ " | | | | | |",
+ " | | | | | |",
+ " | \\__ ___/ |",
+ " \\_________/",
+ NULL
+};
+
+static const char *ascii_macos[] = {
+ " .:'",
+ " _ :'_",
+ " .'`_`-'_`'.",
+ ":________.-'",
+ ":_______:",
+ " :_______`-;",
+ " `._.-._.'",
+ NULL
+};
+
+static const char *ascii_manjaro[] = {
+ "||||||||| ||||",
+ "||||||||| ||||",
+ "|||| ||||",
+ "|||| |||| ||||",
+ "|||| |||| ||||",
+ "|||| |||| ||||",
+ "|||| |||| ||||",
+ NULL
+};
+
+static const char *ascii_nixos[] = {
+ " \\\\ \\\\ //",
+ " ==\\\\__\\\\/ //",
+ " // \\\\//",
+ "==// //==",
+ " //\\\\___//",
+ "// /\\\\ \\\\==",
+ " // \\\\ \\\\",
+ NULL
+};
+
+static const char *ascii_openbsd[] = {
+ " _____",
+ " \\- -/",
+ " \\_/ \\_/",
+ " | O O |",
+ " |_ < ) 3 )",
+ " / \\ /",
+ " /-_____-\\",
+ NULL
+};
+
+static const char *ascii_opensuse[] = {
+ " _______",
+ "__| __ \\",
+ " / .\\ \\",
+ " \\__/ |",
+ " _______|",
+ " \\_______",
+ "__________/",
+ NULL
+};
+
+static const char *ascii_pop_os[] = {
+ "______",
+ "\\ _ \\ __",
+ " \\ \\ \\ \\ / /",
+ " \\ \\_\\ \\ / /",
+ " \\ ___\\ /_/",
+ " \\ \\ _",
+ " __\\_\\__(_)_",
+ " (___________)",
+ NULL
+};
+
+static const char *ascii_slackware[] = {
+ " ________",
+ " / ______|",
+ " | |______",
+ " \\______ \\",
+ " ______| |",
+ "| |________/",
+ "|____________",
+ NULL
+};
+
+static const char *ascii_solus[] = {
+ " /|",
+ " / |\\",
+ " / | \\ _",
+ " /___|__\\_\\",
+ " \\ /",
+ " `-------ยด",
+ NULL
+};
+
+static const char *ascii_ubuntu[] = {
+ " _",
+ " ---(_)",
+ " _/ --- \\",
+ "(_) | |",
+ " \\ --- _/",
+ " ---(_)",
+ NULL
+};
+
+static const char *ascii_void[] = {
+ " _______",
+ " _ \\______ -",
+ "| \\ ___ \\ |",
+ "| | / \\ | |",
+ "| | \\___/ | |",
+ "| \\______ \\_|",
+ " -_______\\",
+ NULL
+};
diff --git a/codingstyle b/codingstyle
new file mode 100644
index 0000000..1439c92
--- /dev/null
+++ b/codingstyle
@@ -0,0 +1,168 @@
+
+Style
+
+Note that the following are guidelines and the most important aspect of style is consistency. Strive to keep your style consistent with the project on which you are working. It is up to the project maintainer to take some liberty in the style guidelines.
+Recommended Reading
+
+The following contain good information, some of which is repeated below, some of which is contradicted below.
+
+ https://man.openbsd.org/style
+ http://doc.cat-v.org/bell_labs/pikestyle
+ https://www.kernel.org/doc/Documentation/process/coding-style.rst
+
+File Layout
+
+ Comment with LICENSE and possibly short explanation of file/tool.
+ Headers
+ Macros
+ Types
+ Function declarations:
+ Include variable names.
+ For short files these can be left out.
+ Group/order in logical manner.
+ Global variables.
+ Function definitions in same order as declarations.
+ main
+
+C Features
+
+ Use C99 without extensions (ISO/IEC 9899:1999).
+ Use POSIX.1-2008:
+ When using gcc define _POSIX_C_SOURCE 200809L.
+ Alternatively define _XOPEN_SOURCE 700.
+ Do not mix declarations and code.
+ Do not use for loop initial declarations.
+ Use /* */ for comments, not //.
+ Variadic macros are acceptable, but remember:
+ __VA_ARGS__ not a named parameter.
+ Arg list cannot be empty.
+
+Blocks
+
+ All variable declarations at top of block.
+ { on same line preceded by single space (except functions).
+ } on own line unless continuing statement (if else, do while, ...).
+
+Use block for single statement if inner statement needs a block.
+
+for (;;) {
+ if (foo) {
+ bar;
+ baz;
+ }
+}
+
+Use block if another branch of the same statement needs a block:
+
+if (foo) {
+ bar;
+} else {
+ baz;
+ qux;
+}
+
+Leading Whitespace
+
+Use tabs for indentation and spaces for alignment. This ensures everything will line up independent of tab size. This means:
+
+ No tabs except beginning of line.
+ Use spaces - not tabs - for multiline macros as the indentation level is 0, where the #define began.
+
+Functions
+
+ Return type and modifiers on own line.
+ Function name and argument list on next line. This allows to grep for function names simply using grep ^functionname(.
+ Opening { on own line (function definitions are a special case of blocks as they cannot be nested).
+ Functions not used outside translation unit should be declared and defined static.
+
+Example:
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [file ...]\n", argv0);
+}
+
+Variables
+
+ Global variables not used outside translation unit should be declared static.
+ In declaration of pointers the * is adjacent to variable name, not type.
+
+Keywords
+
+ Use a space after if, for, while, switch (they are not function calls).
+ Do not use a space after the opening ( and before the closing ).
+ Preferably use () with sizeof.
+ Do not use a space with sizeof().
+
+Switch
+
+ Do not indent cases another level.
+ Comment cases that FALLTHROUGH.
+
+Example:
+
+switch (value) {
+case 0: /* FALLTHROUGH */
+case 1:
+case 2:
+ break;
+default:
+ break;
+}
+
+Headers
+
+ Place system/libc headers first in alphabetical order.
+ If headers must be included in a specific order add a comment to explain.
+ Place local headers after an empty line.
+ When writing and using local headers.
+ Try to avoid cyclic header inclusion dependencies.
+ Instead ensure they are included where and when they are needed.
+ Read https://talks.golang.org/2012/splash.article#TOC_5.
+ Read http://plan9.io/sys/doc/comp.html
+
+User Defined Types
+
+ Do not use type_t naming (it is reserved for POSIX and less readable).
+ Typedef opaque structs.
+ Do not typedef builtin types.
+ Use CamelCase for typedef'd types.
+
+Line Length
+
+ Keep lines to reasonable length (max 79 characters).
+
+Tests and Boolean Values
+
+ Do not use C99 bool types (stick to integer types).
+ Otherwise use compound assignment and tests unless the line grows too long:
+
+if (!(p = malloc(sizeof(*p))))
+ hcf();
+
+Handling Errors
+
+ When functions return -1 for error test against 0 not -1:
+
+if (func() < 0)
+ hcf();
+
+ Use goto to unwind and cleanup when necessary instead of multiple nested levels.
+ return or exit early on failures instead of multiple nested levels.
+ Unreachable code should have a NOTREACHED comment.
+ Think long and hard on whether or not you should cleanup on fatal errors. For simple "one-shot" programs (not daemons) it can be OK to not free memory. It is advised to cleanup temporary files however.
+
+Enums and #define
+
+Use enums for values that are grouped semantically and #define otherwise:
+
+#define MAXSZ 4096
+#define MAGIC1 0xdeadbeef
+
+enum {
+ DIRECTION_X,
+ DIRECTION_Y,
+ DIRECTION_Z
+};
+
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..adea8f1
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,12 @@
+# ssf - simple system fetcher
+
+VERSION = 1.0
+
+PREFIX = /usr/local
+BINDIR = ${PREFIX}/bin
+MANPREFIX = ${PREFIX}/share/man
+
+CC = cc
+CPPFLAGS = -DVERSION=\"${VERSION}\"
+CFLAGS = -std=c99 -pedantic -Wall -Wextra -O2
+LDFLAGS =
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..595344c
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,38 @@
+ssf - simple system fetcher
+===============================
+
+ssf prints a small set of system facts next to a coloured ASCII logo to stdout.
+The code keeps to plain POSIX interfaces so it runs on Linux and OpenBSD.
+
+Requirements
+------------
+
+A POSIX environment with a C99 compiler and make(1).
+
+Installation
+------------
+
+Edit config.mk to match your local setup (ssf installs into /usr/local
+by default). Afterwards run:
+
+ make
+ make install
+
+Usage
+-----
+
+Simply run ssf with no arguments for the standard display. Optional
+flags mimic the original interface:
+
+ -m print values without labels
+ -s print a short subset of fields
+ -k print only the value for a specific key
+ -h show usage help
+ -v show the version string
+
+Customisation
+-------------
+
+ASCII definitions live in the ascii file and can be edited to taste.
+Feature toggles, compiler flags, and install paths are configured through
+config.mk.
diff --git a/ssf.c b/ssf.c
new file mode 100644
index 0000000..805f46f
--- /dev/null
+++ b/ssf.c
@@ -0,0 +1,478 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/utsname.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef __linux__
+#include <sys/sysinfo.h>
+#endif
+
+#ifdef __OpenBSD__
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <uvm/uvm_extern.h>
+#endif
+
+#include "ascii"
+
+/* sizes */
+#define MAXLINE 256
+#define BUFSIZE 512
+
+#define CLR_RESET "\033[0m"
+#define CLR_BLUE "\033[1;34m"
+#define CLR_CYAN "\033[1;36m"
+#define CLR_GREEN "\033[1;32m"
+#define CLR_MAGENTA "\033[1;35m"
+#define CLR_RED "\033[1;31m"
+#define CLR_WHITE "\033[1;37m"
+#define CLR_YELLOW "\033[1;33m"
+
+#define LEN(x) (sizeof(x) / sizeof(*(x)))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define UNKNOWN(buf) strcpy((buf), "unknown")
+#define APPEND(buf, fmt, ...) \
+ do { \
+ size_t __off = strlen(buf); \
+ if (__off < sizeof(buf)) \
+ snprintf((buf) + __off, sizeof(buf) - __off, fmt, __VA_ARGS__); \
+ } while (0)
+
+struct Info {
+ char hostname[MAXLINE], kernel[MAXLINE], uptime[MAXLINE];
+ char cpu[MAXLINE], memory[MAXLINE], load[MAXLINE];
+ char distro[MAXLINE], shell[MAXLINE], terminal[MAXLINE], user[MAXLINE];
+};
+
+struct AsciiEntry {
+ const char *needle;
+ const char **art;
+ const char *color;
+};
+
+static void usage(void);
+static void hostname(struct Info *);
+static void kernel(struct Info *);
+static void uptime(struct Info *);
+static void cpu(struct Info *);
+static void memory(struct Info *);
+static void load(struct Info *);
+static void distro(struct Info *);
+static void shell(struct Info *);
+static void terminal(struct Info *);
+static void user(struct Info *);
+static void collect(struct Info *);
+static void display(struct Info *, int, int, char *);
+static const char **asciisel(const char *, const char **);
+static int asciimatch(const char *, const char *);
+
+static char *argv0;
+
+static const struct AsciiEntry ascii_entries[] = {
+ {"alpine", ascii_alpine, CLR_CYAN},
+ {"android", ascii_android, CLR_GREEN},
+ {"arch", ascii_arch, CLR_CYAN},
+ {"arco", ascii_arco, CLR_BLUE},
+ {"artix", ascii_artix, CLR_CYAN},
+ {"centos", ascii_centos, CLR_MAGENTA},
+ {"debian", ascii_debian, CLR_RED},
+ {"endeavour", ascii_endeavour, CLR_MAGENTA},
+ {"fedora", ascii_fedora, CLR_BLUE},
+ {"freebsd", ascii_freebsd, CLR_RED},
+ {"gentoo", ascii_gentoo, CLR_MAGENTA},
+ {"linux mint", ascii_linux_mint, CLR_GREEN},
+ {"macos", ascii_macos, CLR_WHITE},
+ {"mac os", ascii_macos, CLR_WHITE},
+ {"manjaro", ascii_manjaro, CLR_GREEN},
+ {"nixos", ascii_nixos, CLR_BLUE},
+ {"openbsd", ascii_openbsd, CLR_YELLOW},
+ {"opensuse", ascii_opensuse, CLR_GREEN},
+ {"pop!_os", ascii_pop_os, CLR_CYAN},
+ {"pop os", ascii_pop_os, CLR_CYAN},
+ {"slackware", ascii_slackware, CLR_BLUE},
+ {"solus", ascii_solus, CLR_BLUE},
+ {"ubuntu", ascii_ubuntu, CLR_MAGENTA},
+ {"void", ascii_void, CLR_GREEN},
+ {"linux", ascii_linux, CLR_WHITE}
+};
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-m] [-s] [-k key] [-h] [-v]\n", argv0);
+ exit(1);
+}
+
+static void
+hostname(struct Info *i)
+{
+ if (gethostname(i->hostname, sizeof(i->hostname)) < 0)
+ UNKNOWN(i->hostname);
+}
+
+static void
+kernel(struct Info *i)
+{
+ struct utsname u;
+ if (uname(&u) < 0 || !*u.sysname)
+ UNKNOWN(i->kernel);
+ else
+ snprintf(i->kernel, sizeof(i->kernel), "%s %s", u.sysname, u.release);
+}
+
+static void
+uptime(struct Info *i)
+{
+#if defined(__linux__)
+ struct sysinfo s;
+ int d, h, m;
+ if (sysinfo(&s) < 0) {
+ UNKNOWN(i->uptime);
+ return;
+ }
+ d = s.uptime / 86400;
+ h = (s.uptime % 86400) / 3600;
+ m = (s.uptime % 3600) / 60;
+#elif defined(__OpenBSD__)
+ struct timeval boottime;
+ time_t now, up;
+ int mib[2] = { CTL_KERN, KERN_BOOTTIME };
+ size_t size = sizeof(boottime);
+ int d, h, m;
+
+ if (sysctl(mib, 2, &boottime, &size, NULL, 0) < 0 || !boottime.tv_sec) {
+ UNKNOWN(i->uptime);
+ return;
+ }
+ if ((now = time(NULL)) == (time_t)-1) {
+ UNKNOWN(i->uptime);
+ return;
+ }
+ up = now - boottime.tv_sec;
+ if (up < 0)
+ up = 0;
+ d = up / 86400;
+ h = (up % 86400) / 3600;
+ m = (up % 3600) / 60;
+#else
+ UNKNOWN(i->uptime);
+ return;
+#endif
+ if (d)
+ snprintf(i->uptime, sizeof(i->uptime), "%dd %dh %dm", d, h, m);
+ else if (h)
+ snprintf(i->uptime, sizeof(i->uptime), "%dh %dm", h, m);
+ else
+ snprintf(i->uptime, sizeof(i->uptime), "%dm", m);
+}
+
+static void
+cpu(struct Info *i)
+{
+#if defined(__linux__)
+ FILE *f;
+ char buf[BUFSIZE], model[MAXLINE] = "unknown";
+ int cores = 0;
+
+ if (!(f = fopen("/proc/cpuinfo", "r"))) {
+ UNKNOWN(i->cpu);
+ return;
+ }
+
+ while (fgets(buf, sizeof(buf), f))
+ if (!strncmp(buf, "model name", 10)) {
+ if ((buf[strcspn(buf, "\n")] = 0, strchr(buf, ':')))
+ strcpy(model, strchr(buf, ':') + 2);
+ } else if (!strncmp(buf, "processor", 9))
+ cores++;
+
+ fclose(f);
+ strcpy(i->cpu, model);
+ if (cores)
+ APPEND(i->cpu, " (%d cores)", cores);
+#elif defined(__OpenBSD__)
+ char model[MAXLINE] = "unknown";
+ int cores = 0;
+ size_t len;
+
+ len = sizeof(model);
+ if (sysctlbyname("hw.model", model, &len, NULL, 0) == -1 || !*model) {
+ UNKNOWN(i->cpu);
+ return;
+ }
+ len = sizeof(cores);
+ if (sysctlbyname("hw.ncpu", &cores, &len, NULL, 0) == -1)
+ cores = 0;
+ strcpy(i->cpu, model);
+ if (cores > 0)
+ APPEND(i->cpu, " (%d cores)", cores);
+#else
+ UNKNOWN(i->cpu);
+#endif
+}
+
+static void
+memory(struct Info *i)
+{
+#if defined(__linux__)
+ FILE *f;
+ char buf[BUFSIZE];
+ long total = 0, avail = 0;
+
+ if (!(f = fopen("/proc/meminfo", "r"))) {
+ UNKNOWN(i->memory);
+ return;
+ }
+
+ while (fgets(buf, sizeof(buf), f) && (!total || !avail))
+ if (!strncmp(buf, "MemTotal:", 9))
+ sscanf(buf, "MemTotal: %ld", &total);
+ else if (!strncmp(buf, "MemAvailable:", 13))
+ sscanf(buf, "MemAvailable: %ld", &avail);
+
+ fclose(f);
+ if (total && avail) {
+ long used = (total - avail) / 1024;
+ long tmb = total / 1024;
+ snprintf(i->memory, sizeof(i->memory), "%ld/%ld MB", used, tmb);
+ } else {
+ UNKNOWN(i->memory);
+ }
+#elif defined(__OpenBSD__)
+ struct uvmexp u;
+ size_t len = sizeof(u);
+ int mib[2] = { CTL_VM, VM_UVMEXP };
+ unsigned long long total, freep, used;
+
+ if (sysctl(mib, 2, &u, &len, NULL, 0) < 0 || !u.pagesize || !u.npages) {
+ UNKNOWN(i->memory);
+ return;
+ }
+ total = (unsigned long long)u.npages * (unsigned long long)u.pagesize;
+ freep = (unsigned long long)u.free * (unsigned long long)u.pagesize;
+ used = total > freep ? total - freep : 0;
+ snprintf(i->memory, sizeof(i->memory), "%llu/%llu MB",
+ used / (1024ULL * 1024ULL), total / (1024ULL * 1024ULL));
+#else
+ UNKNOWN(i->memory);
+#endif
+}
+
+static void
+load(struct Info *i)
+{
+#if defined(__linux__)
+ struct sysinfo s;
+ if (sysinfo(&s) < 0) {
+ UNKNOWN(i->load);
+ return;
+ }
+ snprintf(i->load, sizeof(i->load), "%.2f %.2f %.2f",
+ s.loads[0] / 65536.0, s.loads[1] / 65536.0, s.loads[2] / 65536.0);
+#elif defined(__OpenBSD__)
+ double loadavg[3];
+
+ if (getloadavg(loadavg, 3) == -1) {
+ UNKNOWN(i->load);
+ return;
+ }
+ snprintf(i->load, sizeof(i->load), "%.2f %.2f %.2f",
+ loadavg[0], loadavg[1], loadavg[2]);
+#else
+ UNKNOWN(i->load);
+#endif
+}
+
+static void
+distro(struct Info *i)
+{
+ FILE *f;
+ char buf[BUFSIZE], *p, *q;
+
+ *i->distro = 0;
+ if ((f = fopen("/etc/os-release", "r"))) {
+ while (fgets(buf, sizeof(buf), f))
+ if (!strncmp(buf, "PRETTY_NAME=", 12) && (p = strchr(buf, '"'))) {
+ if ((q = strchr(++p, '"'))) {
+ *q = 0;
+ strcpy(i->distro, p);
+ break;
+ }
+ }
+ fclose(f);
+ }
+ if (!*i->distro && (f = fopen("/etc/issue", "r"))) {
+ if (fgets(buf, sizeof(buf), f)) {
+ buf[strcspn(buf, "\n\\\\")] = 0;
+ strcpy(i->distro, buf);
+ }
+ fclose(f);
+ }
+ if (!*i->distro) UNKNOWN(i->distro);
+}
+
+static void
+shell(struct Info *i)
+{
+ char *s, *b;
+ if (!(s = getenv("SHELL"))) {
+ UNKNOWN(i->shell);
+ return;
+ }
+ strcpy(i->shell, (b = strrchr(s, '/')) ? b + 1 : s);
+}
+
+static void
+terminal(struct Info *i)
+{
+ char *t = getenv("TERM");
+ if (!t) {
+ UNKNOWN(i->terminal);
+ return;
+ }
+ strcpy(i->terminal, t);
+}
+
+static void
+user(struct Info *i)
+{
+ struct passwd *p = getpwuid(getuid());
+ if (!p || !p->pw_name) {
+ UNKNOWN(i->user);
+ return;
+ }
+ strcpy(i->user, p->pw_name);
+}
+
+static void
+collect(struct Info *i)
+{
+ hostname(i); kernel(i); uptime(i); cpu(i); memory(i);
+ load(i); distro(i); shell(i); terminal(i); user(i);
+}
+
+static void
+display(struct Info *i, int m, int s, char *k)
+{
+ char *sep;
+ struct { char *name, *val; } fields[] = {
+ {"user", i->user}, {"hostname", i->hostname}, {"distro", i->distro},
+ {"kernel", i->kernel}, {"uptime", i->uptime}, {"shell", i->shell},
+ {"terminal", i->terminal}, {"cpu", i->cpu}, {"memory", i->memory}, {"load", i->load}
+ }, short_fields[] = {
+ {"hostname", i->hostname}, {"kernel", i->kernel}, {"uptime", i->uptime}, {"memory", i->memory}
+ };
+ const char **ascii;
+ const char *color, *line;
+ int n, j, ascii_lines, ascii_width, max_lines, len, pad;
+ int total, short_total;
+
+ sep = m ? "|" : ": ";
+ total = LEN(fields);
+ short_total = LEN(short_fields);
+ if (k) {
+ for (j = 0; j < total; j++)
+ if (!strcmp(k, fields[j].name)) {
+ printf("%s\n", fields[j].val);
+ return;
+ }
+ return;
+ }
+
+ ascii = asciisel(i->distro, &color);
+ ascii_lines = 0;
+ ascii_width = 0;
+ if (ascii) {
+ for (j = 0; ascii[j]; j++) {
+ len = (int)strlen(ascii[j]);
+ if (len > ascii_width)
+ ascii_width = len;
+ }
+ ascii_lines = j;
+ }
+
+ n = s ? short_total : total;
+ max_lines = MAX(ascii_lines, n);
+ for (j = 0; j < max_lines; j++) {
+ len = 0;
+ pad = ascii_width;
+ if (j < ascii_lines) {
+ line = ascii[j];
+ len = (int)strlen(line);
+ pad = ascii_width - len;
+ if (pad < 0)
+ pad = 0;
+ printf("%s%s%s", color, line, CLR_RESET);
+ if (pad)
+ printf("%*s", pad, "");
+ } else if (ascii_width) {
+ printf("%*s", ascii_width, "");
+ }
+ printf(" ");
+ if (j < n)
+ printf("%s%s%s", (s ? short_fields : fields)[j].name, sep,
+ (s ? short_fields : fields)[j].val);
+ putchar('\n');
+ }
+}
+
+static const char **
+asciisel(const char *distro, const char **color)
+{
+ int idx, count;
+
+ count = (int)LEN(ascii_entries);
+ for (idx = 0; idx < count; idx++)
+ if (asciimatch(distro, ascii_entries[idx].needle)) {
+ *color = ascii_entries[idx].color;
+ return ascii_entries[idx].art;
+ }
+ *color = CLR_WHITE;
+ return ascii_linux;
+}
+
+static int
+asciimatch(const char *distro, const char *needle)
+{
+ size_t len;
+ const char *p;
+
+ if (!distro || !needle)
+ return 0;
+ len = strlen(needle);
+ if (!len)
+ return 0;
+ for (p = distro; *p; p++)
+ if (!strncasecmp(p, needle, len))
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ struct Info info = {0};
+ int m = 0, s = 0, c;
+ char *k = NULL;
+
+ for (argv0 = *argv; (c = getopt(argc, argv, "msk:hv")) != -1;)
+ switch (c) {
+ case 'm': m = 1; break;
+ case 's': s = 1; break;
+ case 'k': k = optarg; break;
+ case 'h': usage(); break;
+ case 'v': puts("ssf 1.0"); exit(0);
+ default: usage();
+ }
+
+ collect(&info);
+ display(&info, m, s, k);
+ return 0;
+}