ssf

Owner: IIIlllIIIllI URL: git@git.0x00nyx.xyz:seb/ssf.git

ssf.c

/* (c) 2025 Sebastian Michalk <sebastian.michalk@pm.me> */

#ifndef __OpenBSD__
#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 __linux__
#include <sys/sysinfo.h>
#endif

#ifdef __OpenBSD__
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/time.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) 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__)
	long pagesize = 0;
	unsigned int pagecount = 0, freecount = 0;
	unsigned long long total, freep, used;
	size_t len;

	len = sizeof(pagesize);
	if (sysctlbyname("hw.pagesize", &pagesize, &len, NULL, 0) == -1 || pagesize <= 0) {
		UNKNOWN(i->memory);
		return;
	}
	len = sizeof(pagecount);
	if (sysctlbyname("vm.stats.vm.v_page_count", &pagecount, &len, NULL, 0) == -1 || !pagecount) {
		UNKNOWN(i->memory);
		return;
	}
	len = sizeof(freecount);
	if (sysctlbyname("vm.stats.vm.v_free_count", &freecount, &len, NULL, 0) == -1)
		freecount = 0;
	total = (unsigned long long)pagecount * (unsigned long long)pagesize;
	freep = (unsigned long long)freecount * (unsigned long long)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 = 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;
}