ssf

init

a3fa3b5aea23127bf8d23b434d4ac690363fc3a9

IIIlllIIIllI <seb.michalk@gmail.com>

2026-01-18 12:49:20 +0000

 LICENSE    |   2 +-
 Makefile   |  39 +++++
 ascii      | 248 ++++++++++++++++++++++++++
 config.mk  |  12 ++
 readme.txt |  38 ++++
 ssf.1      |  63 +++++++
 ssf.c      | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 983 insertions(+), 1 deletion(-)

diff --git a/LICENSE b/LICENSE
index a4bf177..c24c4a2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2026 Sebastian
+Copyright (c) 2026 Sebastian Michalk
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cca41b1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+# ssf - simple system fetcher
+
+include config.mk
+
+SRC = ssf.c
+OBJ = ${SRC:.c=.o}
+BIN = ssf
+MAN1 = ssf.1
+MANDIR = ${MANPREFIX}
+
+all: options ${BIN}
+
+options:
+	@echo "ssf 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}
+	mkdir -p ${DESTDIR}${MANDIR}/man1
+	install -m 644 ${MAN1} ${DESTDIR}${MANDIR}/man1/${MAN1}
+
+uninstall:
+	rm -f ${DESTDIR}${BINDIR}/${BIN}
+	rm -f ${DESTDIR}${MANDIR}/man1/${MAN1}
+
+.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/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.1 b/ssf.1
new file mode 100644
index 0000000..5360def
--- /dev/null
+++ b/ssf.1
@@ -0,0 +1,63 @@
+.Dd 2025-12-31
+.Dt SSF 1
+.Os
+.Sh NAME
+.Nm ssf
+.Nd simple system fetcher
+.Sh SYNOPSIS
+.Nm
+.Op Fl m
+.Op Fl s
+.Op Fl k Ar key
+.Op Fl h
+.Op Fl v
+.Sh DESCRIPTION
+.Nm
+prints a small set of system facts next to a colored ASCII logo.
+It uses plain POSIX interfaces and supports Linux and OpenBSD.
+Output includes ANSI color escape sequences.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl m
+Use a pipe separator ("|") between field names and values for easier parsing.
+.It Fl s
+Show a short subset of fields: hostname, kernel, uptime, memory.
+.It Fl k Ar key
+Print only the value for the named field and exit.
+Known keys are:
+user, hostname, distro, kernel, uptime, shell, terminal, cpu, memory, load.
+If the key is unknown, nothing is printed.
+.It Fl h
+Show usage help and exit.
+.It Fl v
+Print the version string and exit.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width Ds
+.It Ev SHELL
+Used to derive the shell name.
+.It Ev TERM
+Used to display the terminal type.
+.El
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa /etc/os-release
+Distribution name (PRETTY_NAME).
+.It Pa /etc/issue
+Fallback distribution name.
+.It Pa /proc/cpuinfo
+CPU model and core count (Linux).
+.It Pa /proc/meminfo
+Memory totals (Linux).
+.El
+.Sh NOTES
+On OpenBSD, CPU and memory are read via
+.Xr sysctl 3 .
+If a value cannot be determined, it is printed as "unknown".
+The ASCII logo is selected by substring match on the distro name;
+if no match is found, a generic Linux logo is used.
+.Sh SEE ALSO
+.Xr uname 1 ,
+.Xr sysctl 3
+.Sh AUTHORS
+Sebastian Michalk <sebastian.michalk@pm.me>
diff --git a/ssf.c b/ssf.c
new file mode 100644
index 0000000..da617fe
--- /dev/null
+++ b/ssf.c
@@ -0,0 +1,582 @@
+#ifdef __OpenBSD__
+#define _BSD_SOURCE
+#else
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef __OpenBSD__
+#include <getopt.h>
+#endif
+
+#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 MAXIMUM(a,b) ((a) > (b) ? (a) : (b))
+#define UNKNOWN(buf) sset((buf), sizeof(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)
+
+#ifdef __OpenBSD__
+static int sysctl_hw (const char *, void *, size_t *);
+#endif
+
+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 sset (char *, size_t, const char *);
+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
+sset (char *dst, size_t dstsz, const char *src)
+{
+  if (!dstsz)
+    return;
+#ifdef __OpenBSD__
+  strlcpy (dst, src, dstsz);
+#else
+  snprintf (dst, dstsz, "%s", src);
+#endif
+}
+
+#ifdef __OpenBSD__
+static int
+sysctl_hw (const char *name, void *out, size_t *lenp)
+{
+  int mib[2];
+  int miblen = 2;
+
+  if (!strcmp (name, "hw.model")) {
+    mib[0] = CTL_HW;
+    mib[1] = HW_MODEL;
+  }
+  else if (!strcmp (name, "hw.ncpu")) {
+    mib[0] = CTL_HW;
+    mib[1] = HW_NCPU;
+  }
+  else if (!strcmp (name, "hw.physmem64")) {
+    mib[0] = CTL_HW;
+    mib[1] = HW_PHYSMEM64;
+  }
+  else {
+    return -1;
+  }
+  return sysctl (mib, (u_int) miblen, out, lenp, NULL, 0);
+}
+
+#endif
+
+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, ':')))
+	sset (model, sizeof (model), strchr (buf, ':') + 2);
+    }
+    else if (!strncmp (buf, "processor", 9))
+      cores++;
+
+  fclose (f);
+  sset (i->cpu, sizeof (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 (sysctl_hw ("hw.model", model, &len) == -1 || !*model) {
+    UNKNOWN (i->cpu);
+    return;
+  }
+  len = sizeof (cores);
+  if (sysctl_hw ("hw.ncpu", &cores, &len) == -1)
+    cores = 0;
+  sset (i->cpu, sizeof (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__)
+  int mib[2];
+  struct uvmexp uvmexp;
+  unsigned long long total, used;
+  size_t len;
+  long pagesize;
+
+  mib[0] = CTL_HW;
+  mib[1] = HW_PHYSMEM64;
+  len = sizeof (total);
+  if (sysctl (mib, 2, &total, &len, NULL, 0) == -1) {
+    UNKNOWN (i->memory);
+    return;
+  }
+  mib[0] = CTL_VM;
+  mib[1] = VM_UVMEXP;
+  len = sizeof (uvmexp);
+  if (sysctl (mib, 2, &uvmexp, &len, NULL, 0) == -1) {
+    UNKNOWN (i->memory);
+    return;
+  }
+  if ((pagesize = sysconf (_SC_PAGESIZE)) <= 0) {
+    UNKNOWN (i->memory);
+    return;
+  }
+  used =
+    (unsigned long long) (uvmexp.active +
+			  uvmexp.wired) * (unsigned long long) pagesize;
+  snprintf (i->memory, sizeof (i->memory), "%llu/%llu MiB",
+	    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;
+	  sset (i->distro, sizeof (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;
+      sset (i->distro, sizeof (i->distro), buf);
+    }
+    fclose (f);
+  }
+  if (!*i->distro) {
+    struct utsname u;
+    if (uname (&u) == 0 && *u.sysname)
+      sset (i->distro, sizeof (i->distro), u.sysname);
+    else
+      UNKNOWN (i->distro);
+  }
+}
+
+static void
+shell (struct Info *i)
+{
+  char *s, *b;
+  if (!(s = getenv ("SHELL"))) {
+    UNKNOWN (i->shell);
+    return;
+  }
+  sset (i->shell, sizeof (i->shell), (b = strrchr (s, '/')) ? b + 1 : s);
+}
+
+static void
+terminal (struct Info *i)
+{
+  char *t = getenv ("TERM");
+  if (!t) {
+    UNKNOWN (i->terminal);
+    return;
+  }
+  sset (i->terminal, sizeof (i->terminal), t);
+}
+
+static void
+user (struct Info *i)
+{
+  struct passwd *p = getpwuid (getuid ());
+  if (!p || !p->pw_name) {
+    UNKNOWN (i->user);
+    return;
+  }
+  sset (i->user, sizeof (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 = MAXIMUM (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;
+}