From 22ade138c4e83b7c0b0c702c38c0ca00a9948d4b Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 26 Aug 2026 15:16:23 +0200 Subject: 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. --- .gitattributes | 5 ++ .gitignore | 46 ++++++++++++++ LICENSE | 21 +++++++ Makefile | 51 ++++++++++++++++ README | 73 +++++++++++++++++++++++ README.md | 1 - arg.h | 50 ++++++++++++++++ config.def.h | 16 +++++ config.mk | 29 +++++++++ rg.1 | 55 +++++++++++++++++ rg.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README delete mode 100644 README.md create mode 100644 arg.h create mode 100644 config.def.h create mode 100644 config.mk create mode 100644 rg.1 create mode 100644 rg.c 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 + +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 +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#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; +} -- cgit v1.2.3