initial public commit
authorgit <redacted>
Sun, 24 May 2026 12:59:48 +0000 (08:59 -0400)
committergit <redacted>
Sun, 24 May 2026 12:59:48 +0000 (08:59 -0400)
Makefile [new file with mode: 0644]
config.mk [new file with mode: 0644]
free.1 [new file with mode: 0644]
free.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..78e53b5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+include config.mk
+
+TARGET = free
+SRC = free.c
+
+all: ${TARGET}
+
+clean:
+       rm -f *.[oa] ${TARGET}
+
+install: all
+       mkdir -p ${PREFIX}/bin
+       cp -f ${TARGET} ${PREFIX}/bin
+       chmod 755 ${PREFIX}/bin/${TARGET}
+       mkdir -p ${MANPREFIX}/man1
+       cp -f ${TARGET}.1 ${MANPREFIX}/man1
+       chmod 644 ${MANPREFIX}/man1/${TARGET}.1
+
+uninstall:
+       rm -f ${PREFIX}/bin/${TARGET} ${MANPREFIX}/man1/${TARGET}.1
+
+.PHONY: all clean install uninstall
diff --git a/config.mk b/config.mk
new file mode 100644 (file)
index 0000000..ed60c37
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,7 @@
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+CFLAGS = -Wall -Wextra -Wpedantic -O2 -lkvm
+LDFLAGS =
+
+CC = cc
diff --git a/free.1 b/free.1
new file mode 100644 (file)
index 0000000..b623262
--- /dev/null
+++ b/free.1
@@ -0,0 +1,46 @@
+.Dd May 2026
+.Dt FREE 1
+.Os
+.Sh NAME
+.Nm free
+.Nd display amount of free and used memory in the system
+.Sh SYNOPSIS
+.Nm
+.Op Fl bghkmpt
+.Sh DESCRIPTION
+.Nm
+displays the total amount of free and used physical and swap memory in the system, as
+well as the buffers, caches (including ZFS ARC) and wired memory used by the kernel.
+The displayed columns are:
+.Bl -tag -width -indent
+.It total
+Total usable memory.
+.It used
+Unavailable memory.
+.It free
+Truly unallocated memory.
+.It wired
+Memory that can't be swapped to disk.
+.It buff/cache
+Memory used by kernel buffers, traditional FS cache and ARC.
+.It available
+Available memory.
+.Sh OPTIONS
+.Bl -tag -width ".Fl b "
+.It Fl b
+Display in bytes.
+.It Fl k
+Display in kibibytes.
+.It Fl m
+Display in mebibytes.
+.It Fl g
+Display in gibibytes.
+.It Fl t
+Display in tebibytes.
+.It Fl p
+Display in pebytes.
+.It Fl h
+Display in human-readable units.
+.Sh SEE ALSO
+.Xr top 1 ,
+.Xr sysctl 1
diff --git a/free.c b/free.c
new file mode 100644 (file)
index 0000000..971711b
--- /dev/null
+++ b/free.c
@@ -0,0 +1,233 @@
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <err.h>
+#include <kvm.h>
+
+typedef enum {
+       NO_CONV,
+       KB_CONV,
+       MB_CONV,
+       GB_CONV,
+       TB_CONV,
+       PB_CONV,
+       HU_CONV
+} ConvFlags;
+
+static kvm_t *kd;
+static long pgsz;
+static char sbuff[32];
+
+void
+usage(void)
+{
+       fprintf(stderr, "usage: %s [-bghkmpt]\n", getprogname());
+       exit(2);
+}
+
+long long
+getphysmem(int unused)
+{
+       (void)unused;
+
+       long long physcnt;
+       size_t len = sizeof(physcnt);
+
+       if (sysctlbyname("hw.physmem", &physcnt, &len, NULL, 0) < 0)
+               err(1, "hw.physmem");
+
+       return physcnt;
+}
+
+long long
+getavailmem(int flag)
+{
+       size_t len;
+       unsigned int freecnt, inactcnt;
+
+       len = sizeof(freecnt);
+       if (sysctlbyname("vm.stats.vm.v_free_count", &freecnt, &len, NULL, 0) < 0)
+               err(1, "vm.stats.vm.v_free_count");
+       if (sysctlbyname("vm.stats.vm.v_inactive_count", &inactcnt, &len, NULL, 0) < 0)
+               err(1, "vm.stats.vm.v_inactive_count");
+
+       if (flag == 1)
+               return (long long)(getphysmem(0) - (freecnt + inactcnt) * pgsz);
+       else if (flag == 2)
+               return (long long)(freecnt * pgsz);
+       else
+               return (long long)((freecnt + inactcnt) * pgsz);
+}
+
+long long
+getwiredmem(int unused)
+{
+       (void)unused;
+
+       size_t len;
+       unsigned int wirecnt;
+
+       len = sizeof(wirecnt);
+       if (sysctlbyname("vm.stats.vm.v_wire_count", &wirecnt, &len, NULL, 0) < 0)
+               err(1, "vm.stats.vm.v_wire_count");
+
+       return (long long)(wirecnt * pgsz);
+
+}
+
+long long
+getbufcachestats(int unused)
+{
+       (void)unused;
+
+       size_t len;
+       long bufspace, arcspace;
+       unsigned int cachecnt;
+
+       len = sizeof(bufspace);
+       if (sysctlbyname("vfs.bufspace", &bufspace, &len, NULL, 0) < 0)
+               err(1, "vfs.bufspace");
+       /* Ignore failure in case ZFS is not used */
+       (void)sysctlbyname("kstat.zfs.misc.arcstats.size", &arcspace, &len, NULL, 0);
+
+       len = sizeof(cachecnt);
+       if (sysctlbyname("vm.stats.vm.v_cache_count", &cachecnt, &len, NULL, 0) < 0)
+               err(1, "vm.stats.vm.v_inactive_count");
+
+       return (long long)(cachecnt * pgsz + bufspace + arcspace);
+}
+
+long long
+getswapstats(int flag)
+{
+       int nswap;
+       struct kvm_swap swap;
+
+       if ((nswap = kvm_getswapinfo(kd, &swap, 1, 0)) < 0)
+               errx(1, "kvm_getswapinfo: %s", kvm_geterr(kd));
+
+       if (flag == 0)
+               return (long long)(swap.ksw_total * pgsz);
+       else if (flag == 1)
+               return (long long)(swap.ksw_used * pgsz);
+       else
+               return (long long)((swap.ksw_total - swap.ksw_used) * pgsz);
+}
+
+int
+tohumansize(long long bytes, char **buff, ConvFlags flag)
+{
+       int i = 0;
+       double size = (double)bytes;
+       char units[] = {'\0', 'K', 'M', 'G', 'T', 'P'};
+
+       if (flag != HU_CONV)
+               memset(units, '\0', sizeof(units));
+       if (flag == NO_CONV)
+               return asprintf(buff, "%.0f", size);
+
+       while (i < (int)flag) {
+               if (size <= 1024 && flag == HU_CONV) break;
+               size /= 1024;
+               i++;
+       }
+
+       if (i == 0 || size < 0.1)
+               return asprintf(buff, "%.0f", size);
+       else if (size < 10)
+               return asprintf(buff, "%.1f%c", size, units[i]);
+       else
+               return asprintf(buff, "%.0f%c", size, units[i]);
+}
+
+void
+sprintfaligned(char *buff, size_t len)
+{
+       size_t blen;
+       char *ind;
+
+       blen = strlen(buff);
+       ind = sbuff + len - blen;
+       if (ind < sbuff) {
+               size_t t = sbuff - ind;
+               ind = sbuff;
+               *ind = '%';
+               ind++; buff += t;
+               blen = 30;
+       }
+       strlcpy(ind, buff, blen + 1);
+
+       return;
+}
+
+int
+main(int argc, char *argv[])
+{
+       int c, i;
+       char *buff;
+       size_t offset;
+       ConvFlags flags;
+       long long (*getmemstats[])(int) =
+               {getphysmem, getavailmem, getavailmem, getwiredmem, getbufcachestats, getavailmem};
+
+       flags = KB_CONV;
+       while ((c = getopt(argc, argv, "bghkmpt")) >= 0) {
+               switch(c) {
+               case 'b':
+                       flags = NO_CONV; break;
+               case 'k':
+                       flags = KB_CONV; break;
+               case 'm':
+                       flags = MB_CONV; break;
+               case 'g':
+                       flags = GB_CONV; break;
+               case 't':
+                       flags = TB_CONV; break;
+               case 'p':
+                       flags = PB_CONV; break;
+               case 'h':
+                       flags = HU_CONV; break;
+               default:
+                       usage();
+               }
+       }
+
+       pgsz = sysconf(_SC_PAGESIZE);
+
+       /* initializing kvm device for swap stats */
+       if (!(kd = kvm_open(NULL, "/dev/null", NULL, 0, "kvm_open")))
+               err(1, "kvm_open");
+
+       printf("%16s%12s%12s%12s%12s%12s\nMem:",
+              "total", "used", "free", "wired", "buff/cache", "available");
+
+       offset = 12;
+       for (i = 0; i < 6; i++) {
+               memset(sbuff, ' ', sizeof(sbuff));
+               tohumansize(getmemstats[i](i), &buff, flags);
+               sprintfaligned(buff, offset);
+               printf("%s", sbuff);
+               free(buff);
+       }
+
+       offset = 11;
+       printf("\nSwap:");
+       for (i = 0; i < 3; i++) {
+               memset(sbuff, ' ', sizeof(sbuff));
+               tohumansize(getswapstats(i), &buff, flags);
+               sprintfaligned(buff, offset);
+               printf("%s", sbuff);
+               free(buff);
+               offset = 12;
+       }
+       putc('\n', stdout);
+
+       return 0;
+}