ssf

[root] / ssf.c

13.3KB

raw
#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>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.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];
  char ip[16];
};

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 ip (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
ip (struct Info *i)
{
  struct ifaddrs *ifap, *ifa;

  if (getifaddrs (&ifap) < 0) {
    strcpy (i->ip, "0.0.0.0");
    return;
  }
  for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
    if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET)
      continue;

    if (strcmp (ifa->ifa_name, "lo0") == 0
	|| strcmp (ifa->ifa_name, "lo") == 0)
      continue;
    struct sockaddr_in *sa = (struct sockaddr_in *) ifa->ifa_addr;
    inet_ntop (AF_INET, &sa->sin_addr, i->ip, sizeof (i->ip));
    break;
  }
  freeifaddrs (ifap);
  if (!i->ip[0])
    strcpy (i->ip, "0.0.0.0");

}

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);
  ip (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}, 
	{"ip", i->ip}, {"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;
}