summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbryan <bryan@veltar.systems>2026-08-26 15:16:23 +0200
committerbryan <bryan@veltar.systems>2026-08-26 15:16:23 +0200
commit22ade138c4e83b7c0b0c702c38c0ca00a9948d4b (patch)
treef78768a69dded978be14aa4290b198903b27f4c0
parentdfce501af03595a8fb5083fbc43b48d729ae9551 (diff)
downloadrg-22ade138c4e83b7c0b0c702c38c0ca00a9948d4b.tar.gz
rg-22ade138c4e83b7c0b0c702c38c0ca00a9948d4b.zip
Replace the placeholder with a suckless C rg.
rg prints n cryptographically random strings of length l, matching rg.veltar.systems. Bytes come from getrandom(2) on Linux and rand_s on Windows. make selects clang and .exe when OS=Windows_NT.
-rw-r--r--.gitattributes5
-rw-r--r--.gitignore46
-rw-r--r--LICENSE21
-rw-r--r--Makefile51
-rw-r--r--README73
-rw-r--r--README.md1
-rw-r--r--arg.h50
-rw-r--r--config.def.h16
-rw-r--r--config.mk29
-rw-r--r--rg.155
-rw-r--r--rg.c185
11 files changed, 531 insertions, 1 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..b5e1bde
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+* text=auto eol=lf
+*.c text eol=lf
+*.h text eol=lf
+*.1 text eol=lf
+Makefile text eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..263bcd6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,46 @@
+# local suckless config (copied from config.def.h by make)
+config.h
+
+# dist
+*.tar
+*.tar.gz
+rg-[0-9]*
+
+# compiled / binaries
+rg
+rg.exe
+*.o
+*.obj
+*.a
+*.so
+*.dll
+*.out
+a.out
+core
+core.*
+
+# editors
+*~
+*.bak
+*.tmp
+*.swp
+*.swo
+*.orig
+*.rej
+
+# OS
+.DS_Store
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+
+# IDEs
+.vscode/
+.idea/
+*.code-workspace
+
+# agent / session local files
+.grok/
+.claude/
+.cursor/
+*.log
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..533b62a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2026 bryan <bryan@veltar.systems>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..19ecfb6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,51 @@
+# See LICENSE file for copyright and license details.
+# rg - random string generator
+
+include config.mk
+
+SRC = rg.c
+OBJ = ${SRC:.c=.o}
+
+all: options rg${EXE}
+
+options:
+ @echo rg build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+config.h:
+ cp config.def.h $@
+
+.c.o:
+ ${CC} -c ${CFLAGS} $<
+
+${OBJ}: arg.h config.h config.mk
+
+rg${EXE}: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ rm -f rg rg.exe ${OBJ} rg-${VERSION}.tar.gz
+
+dist: clean
+ mkdir -p rg-${VERSION}
+ cp -R LICENSE Makefile README arg.h config.def.h config.mk \
+ rg.c rg.1 rg-${VERSION}
+ tar -cf - rg-${VERSION} | gzip -c > rg-${VERSION}.tar.gz
+ rm -rf rg-${VERSION}
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f rg${EXE} ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/rg${EXE}
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ sed "s/VERSION/${VERSION}/g" < rg.1 > ${DESTDIR}${MANPREFIX}/man1/rg.1
+ chmod 644 ${DESTDIR}${MANPREFIX}/man1/rg.1
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/rg \
+ ${DESTDIR}${PREFIX}/bin/rg.exe \
+ ${DESTDIR}${MANPREFIX}/man1/rg.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/README b/README
new file mode 100644
index 0000000..d470444
--- /dev/null
+++ b/README
@@ -0,0 +1,73 @@
+rg - random string generator
+============================
+rg prints cryptographically random alphanumeric strings. It is a
+suckless rewrite of rg.veltar.systems as a small C program that
+builds on Linux and Windows.
+
+ rg [-v] [-n n] [-l l]
+
+n number of strings (default 6, min 1, max 1000)
+l length of each string (default 10, min 1, max 256)
+
+
+Requirements
+------------
+A C99 compiler.
+
+Linux: libc with getrandom(2) (glibc 2.25+).
+Windows: a compiler with rand_s (MSVC, clang targeting MSVC, or
+MinGW-w64).
+
+
+Installation
+------------
+Edit config.mk to match your local setup. Edit config.def.h for the
+alphabet and limits.
+
+Linux:
+
+ make clean install
+
+Windows (GNU make + clang, from this tree):
+
+ make
+
+The Windows bits of config.mk are selected when OS=Windows_NT.
+Otherwise set CC, EXE=.exe, and CPPFLAGS by hand as commented there.
+
+
+Running without make
+--------------------
+ cp config.def.h config.h
+
+Linux:
+
+ cc -std=c99 -D_DEFAULT_SOURCE -o rg rg.c
+
+Windows (clang / MSVC CRT):
+
+ clang -std=c99 -D_CRT_RAND_S -o rg.exe rg.c
+
+Windows (MinGW-w64):
+
+ gcc -std=c99 -D_CRT_RAND_S -o rg.exe rg.c
+
+
+Usage
+-----
+ rg
+ rg -n 6 -l 10
+ rg -n 1 -l 32
+
+
+Configuration
+-------------
+Copy config.def.h to config.h (make does this) and edit. Do not
+edit rg.c for configuration.
+
+
+Remarks
+-------
+The original page at rg.veltar.systems used n % alphabet.length on
+raw 32-bit draws, which is slightly biased. This program rejects
+values in the incomplete last bucket so each symbol is equiprobable.
diff --git a/README.md b/README.md
deleted file mode 100644
index 149d672..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# rg
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..a22e019
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,50 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ int i_;\
+ for (i_ = 1, brk_ = 0, argv_ = argv;\
+ argv[0][i_] && !brk_;\
+ i_++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][i_];\
+ switch (argc_)
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][i_+1] != '\0')?\
+ (&argv[0][i_+1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][i_+1] != '\0')?\
+ (&argv[0][i_+1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/config.def.h b/config.def.h
new file mode 100644
index 0000000..3747be5
--- /dev/null
+++ b/config.def.h
@@ -0,0 +1,16 @@
+/* See LICENSE file for copyright and license details. */
+
+#ifndef VERSION
+#define VERSION "1.0"
+#endif
+
+/* alphabet drawn from; change and re-copy to config.h */
+static const char alphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+static const int defn = 6; /* default number of strings */
+static const int defl = 10; /* default length of each string */
+static const int minn = 1;
+static const int maxn = 1000;
+static const int minl = 1;
+static const int maxl = 256;
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..845b457
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,29 @@
+# rg version
+VERSION = 1.0
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+# compiler
+CC = cc
+EXE =
+LIBS =
+
+# Windows (MinGW-w64): uncomment
+# CC = gcc
+# EXE = .exe
+
+# Windows (clang targeting MSVC CRT): uncomment
+# CC = clang
+# EXE = .exe
+
+ifeq ($(OS),Windows_NT)
+CC = clang
+EXE = .exe
+endif
+
+CFLAGS = -std=c99 -pedantic -Wall -Wextra -Os
+LDFLAGS = ${LIBS}
diff --git a/rg.1 b/rg.1
new file mode 100644
index 0000000..dc54684
--- /dev/null
+++ b/rg.1
@@ -0,0 +1,55 @@
+.TH RG 1 rg-VERSION
+.SH NAME
+rg \- generate random strings
+.SH SYNOPSIS
+.B rg
+.RB [ \-v ]
+.RB [ \-n
+.IR n ]
+.RB [ \-l
+.IR l ]
+.SH DESCRIPTION
+.B rg
+prints
+.I n
+cryptographically random strings of length
+.I l
+drawn from a configurable alphabet. Defaults match
+rg.veltar.systems:
+.I n
+= 6,
+.I l
+= 10, alphabet A–Z a–z 0–9.
+.P
+Bytes come from the OS CSPRNG
+.RI ( getrandom
+on Linux,
+.I rand_s
+on Windows). Incomplete last buckets of 2^32 are rejected so each
+symbol is equiprobable.
+.SH OPTIONS
+.TP
+.BI \-n " n"
+Number of strings. Default 6. Range 1..1000.
+.TP
+.BI \-l " l"
+Length of each string. Default 10. Range 1..256.
+.TP
+.B \-v
+Print version and exit.
+.SH CONFIGURATION
+The alphabet, defaults, and limits live in
+.IR config.h ,
+copied from
+.I config.def.h
+by
+.BR make .
+Edit
+.I config.def.h
+and rebuild. Do not edit
+.I rg.c
+for configuration.
+.SH SEE ALSO
+.IR getrandom (2)
+.SH AUTHORS
+See LICENSE for copyright details.
diff --git a/rg.c b/rg.c
new file mode 100644
index 0000000..9191956
--- /dev/null
+++ b/rg.c
@@ -0,0 +1,185 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef __linux__
+#ifndef _DEFAULT_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+#endif
+#ifdef _WIN32
+#ifndef _CRT_RAND_S
+#define _CRT_RAND_S
+#endif
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#include <sys/random.h>
+#endif
+
+#include "arg.h"
+#include "config.h"
+
+char *argv0;
+
+static unsigned char rbuf[256];
+static size_t rpos = sizeof rbuf;
+
+static void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ exit(1);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-v] [-n n] [-l l]\n", argv0 ? argv0 : "rg");
+ exit(1);
+}
+
+static int
+fillrand(unsigned char *buf, size_t n)
+{
+#ifdef _WIN32
+ size_t i;
+ unsigned int r;
+ int k;
+
+ for (i = 0; i < n; ) {
+ if (rand_s(&r) != 0)
+ return -1;
+ for (k = 0; k < 4 && i < n; k++)
+ buf[i++] = (unsigned char)(r >> (8 * k));
+ }
+ return 0;
+#else
+ size_t off;
+ ssize_t r;
+
+ off = 0;
+ while (off < n) {
+ r = getrandom(buf + off, n - off, 0);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ if (r == 0)
+ return -1;
+ off += (size_t)r;
+ }
+ return 0;
+#endif
+}
+
+static unsigned char
+randbyte(void)
+{
+ if (rpos >= sizeof rbuf) {
+ if (fillrand(rbuf, sizeof rbuf) < 0)
+ die("rg: rng:");
+ rpos = 0;
+ }
+ return rbuf[rpos++];
+}
+
+static uint32_t
+randu32(void)
+{
+ return ((uint32_t)randbyte() << 24) |
+ ((uint32_t)randbyte() << 16) |
+ ((uint32_t)randbyte() << 8) |
+ (uint32_t)randbyte();
+}
+
+static uint32_t
+randbelow(uint32_t n)
+{
+ uint32_t x, min;
+
+ if (n <= 1)
+ return 0;
+ /* reject [0, 2^32 % n) so x % n is unbiased */
+ min = (uint32_t)(-n) % n;
+ do {
+ x = randu32();
+ } while (x < min);
+ return x % n;
+}
+
+static int
+eint(const char *s)
+{
+ char *end;
+ long v;
+
+ if (s == NULL || s[0] == '\0')
+ usage();
+ errno = 0;
+ v = strtol(s, &end, 10);
+ if (errno || end == s || *end != '\0' || v < INT_MIN || v > INT_MAX)
+ die("rg: invalid number '%s'", s);
+ return (int)v;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, j, n, l, alen;
+
+ n = defn;
+ l = defl;
+
+ ARGBEGIN {
+ case 'n':
+ n = eint(EARGF(usage()));
+ break;
+ case 'l':
+ l = eint(EARGF(usage()));
+ break;
+ case 'v':
+ puts("rg-" VERSION);
+ return 0;
+ default:
+ usage();
+ } ARGEND
+
+ if (argc)
+ usage();
+ if (n < minn || n > maxn)
+ die("rg: n must be %d..%d", minn, maxn);
+ if (l < minl || l > maxl)
+ die("rg: l must be %d..%d", minl, maxl);
+
+ alen = (int)strlen(alphabet);
+ if (alen < 1)
+ die("rg: empty alphabet");
+
+ for (i = 0; i < n; i++) {
+ for (j = 0; j < l; j++)
+ putchar(alphabet[randbelow((uint32_t)alen)]);
+ putchar('\n');
+ }
+
+ return 0;
+}