summaryrefslogtreecommitdiff
path: root/corebinutils
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:30:21 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:30:21 +0300
commit0788f5f3de23dc8e2515830dc10f43980fd3c3c2 (patch)
treed4490263cefa00fed34e0671bea9c87acfce5024 /corebinutils
parentb82d29b6d7d6e7f092f705bb3a1387367562aa24 (diff)
parent5cdcecb6c24e931462c92c66ead41a572302b454 (diff)
downloadProject-Tick-0788f5f3de23dc8e2515830dc10f43980fd3c3c2.tar.gz
Project-Tick-0788f5f3de23dc8e2515830dc10f43980fd3c3c2.zip
Add 'corebinutils/uuidgen/' from commit '5cdcecb6c24e931462c92c66ead41a572302b454'
git-subtree-dir: corebinutils/uuidgen git-subtree-mainline: b82d29b6d7d6e7f092f705bb3a1387367562aa24 git-subtree-split: 5cdcecb6c24e931462c92c66ead41a572302b454
Diffstat (limited to 'corebinutils')
-rw-r--r--corebinutils/uuidgen/.gitignore2
-rw-r--r--corebinutils/uuidgen/GNUmakefile35
-rw-r--r--corebinutils/uuidgen/LICENSE24
-rw-r--r--corebinutils/uuidgen/README.md46
-rwxr-xr-xcorebinutils/uuidgen/tests/test.sh376
-rw-r--r--corebinutils/uuidgen/uuidgen.1112
-rw-r--r--corebinutils/uuidgen/uuidgen.c468
7 files changed, 1063 insertions, 0 deletions
diff --git a/corebinutils/uuidgen/.gitignore b/corebinutils/uuidgen/.gitignore
new file mode 100644
index 0000000000..a07d5ed8b8
--- /dev/null
+++ b/corebinutils/uuidgen/.gitignore
@@ -0,0 +1,2 @@
+/build/
+/out/
diff --git a/corebinutils/uuidgen/GNUmakefile b/corebinutils/uuidgen/GNUmakefile
new file mode 100644
index 0000000000..01cfd2d1d1
--- /dev/null
+++ b/corebinutils/uuidgen/GNUmakefile
@@ -0,0 +1,35 @@
+.DEFAULT_GOAL := all
+
+CC ?= cc
+CPPFLAGS ?=
+CFLAGS ?= -O2
+CFLAGS += -std=c17 -g -Wall -Wextra -Werror
+LDFLAGS ?=
+LDLIBS ?=
+
+OBJDIR := $(CURDIR)/build
+OUTDIR := $(CURDIR)/out
+TARGET := $(OUTDIR)/uuidgen
+OBJS := $(OBJDIR)/uuidgen.o
+
+.PHONY: all clean dirs status test
+
+all: $(TARGET)
+
+dirs:
+ @mkdir -p "$(OBJDIR)" "$(OUTDIR)"
+
+$(TARGET): $(OBJS) | dirs
+ $(CC) $(LDFLAGS) -o "$@" $(OBJS) $(LDLIBS)
+
+$(OBJDIR)/uuidgen.o: $(CURDIR)/uuidgen.c | dirs
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c "$(CURDIR)/uuidgen.c" -o "$@"
+
+test: $(TARGET)
+ CC="$(CC)" UUIDGEN_BIN="$(TARGET)" sh "$(CURDIR)/tests/test.sh"
+
+status:
+ @printf '%s\n' "$(TARGET)"
+
+clean:
+ @rm -rf "$(OBJDIR)" "$(OUTDIR)"
diff --git a/corebinutils/uuidgen/LICENSE b/corebinutils/uuidgen/LICENSE
new file mode 100644
index 0000000000..80cc443f4b
--- /dev/null
+++ b/corebinutils/uuidgen/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2002 Marcel Moolenaar
+Copyright (c) 2022 Tobias C. Berner
+Copyright (c) 2026 Project Tick
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/corebinutils/uuidgen/README.md b/corebinutils/uuidgen/README.md
new file mode 100644
index 0000000000..baf1345d3e
--- /dev/null
+++ b/corebinutils/uuidgen/README.md
@@ -0,0 +1,46 @@
+# uuidgen
+
+Standalone Linux-native port of FreeBSD `uuidgen` for Project Tick BSD/Linux Distribution.
+
+## Build
+
+```sh
+gmake -f GNUmakefile
+gmake -f GNUmakefile CC=musl-gcc
+```
+
+## Test
+
+```sh
+gmake -f GNUmakefile test
+gmake -f GNUmakefile test CC=musl-gcc
+```
+
+## Port Strategy
+
+- Port structure follows sibling standalone ports (`bin/hostname`, `bin/domainname`, `bin/timeout`): local `GNUmakefile`, technical `README.md`, shell tests under `tests/`.
+- FreeBSD-only layers (`capsicum`, `err(3)`, `uuidgen(2)`, `uuid(3)` string helpers) were removed.
+- Linux-native RFC 4122 generation is implemented directly in-tree, with strict argument parsing and explicit diagnostics.
+- No GNU feature macros are enabled; the code is written for Linux + musl without glibc-specific wrappers.
+
+## Linux API Mapping
+
+- FreeBSD `uuidgen(2)` / `uuidgen(3)` generation maps to an in-process RFC 4122 implementation:
+ - Version 1 UUIDs: `clock_gettime(CLOCK_REALTIME)` timestamp source + in-process clock-sequence logic + random multicast node identifier.
+ - Version 4 UUIDs (`-r`): direct Linux `SYS_getrandom` syscall entropy with `/dev/urandom` fallback when unavailable (`ENOSYS`).
+- FreeBSD `uuid_to_string(3)` and compact formatting helper map to direct lowercase textual formatting in this utility.
+- FreeBSD Capsicum confinement is intentionally not emulated on Linux.
+
+## Supported Linux Semantics
+
+- `uuidgen [-1] [-r] [-c] [-n count] [-o filename]` from `uuidgen.1`.
+- Default output is one RFC 4122 version 1 UUID.
+- `-n count` supports strict range `1..2048` (matching `uuidgen.1` hard limit).
+- Batched version 1 generation (`-n` without `-1`) produces dense consecutive timestamp ticks.
+- `-1` generation mode emits UUIDs one-at-a-time (timestamp still strictly monotonic).
+- `-c` outputs compact UUIDs without hyphens.
+
+## Intentionally Unsupported / Different
+
+- Cross-process global ordering identical to FreeBSD kernel `uuidgen(2)` is not provided; ordering guarantees are process-local.
+- Version 1 node identity is randomized per process (multicast bit set) instead of exposing hardware MAC-derived identifiers.
diff --git a/corebinutils/uuidgen/tests/test.sh b/corebinutils/uuidgen/tests/test.sh
new file mode 100755
index 0000000000..ee1cf4267d
--- /dev/null
+++ b/corebinutils/uuidgen/tests/test.sh
@@ -0,0 +1,376 @@
+#!/bin/sh
+set -eu
+
+ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
+UUIDGEN_BIN=${UUIDGEN_BIN:-"$ROOT/out/uuidgen"}
+CC=${CC:-cc}
+TMPDIR=${TMPDIR:-/tmp}
+WORKDIR=$(mktemp -d "$TMPDIR/uuidgen-test.XXXXXX")
+PROBE_C="$WORKDIR/probe.c"
+PROBE_BIN="$WORKDIR/probe"
+STDOUT_FILE="$WORKDIR/stdout"
+STDERR_FILE="$WORKDIR/stderr"
+LAST_STATUS=0
+LAST_STDOUT=
+LAST_STDERR=
+USAGE_TEXT='usage: uuidgen [-1] [-r] [-c] [-n count] [-o filename]'
+
+trap 'rm -rf "$WORKDIR"' EXIT INT TERM
+
+export LC_ALL=C
+
+fail() {
+ printf '%s\n' "FAIL: $1" >&2
+ exit 1
+}
+
+assert_eq() {
+ name=$1
+ expected=$2
+ actual=$3
+ if [ "$expected" != "$actual" ]; then
+ printf '%s\n' "FAIL: $name" >&2
+ printf '%s\n' "--- expected ---" >&2
+ printf '%s\n' "$expected" >&2
+ printf '%s\n' "--- actual ---" >&2
+ printf '%s\n' "$actual" >&2
+ exit 1
+ fi
+}
+
+assert_contains() {
+ name=$1
+ text=$2
+ pattern=$3
+ case $text in
+ *"$pattern"*) ;;
+ *) fail "$name" ;;
+ esac
+}
+
+assert_empty() {
+ name=$1
+ text=$2
+ if [ -n "$text" ]; then
+ printf '%s\n' "FAIL: $name" >&2
+ printf '%s\n' "--- expected empty ---" >&2
+ printf '%s\n' "--- actual ---" >&2
+ printf '%s\n' "$text" >&2
+ exit 1
+ fi
+}
+
+assert_status() {
+ name=$1
+ expected=$2
+ actual=$3
+ if [ "$expected" -ne "$actual" ]; then
+ printf '%s\n' "FAIL: $name" >&2
+ printf '%s\n' "expected status: $expected" >&2
+ printf '%s\n' "actual status: $actual" >&2
+ exit 1
+ fi
+}
+
+assert_line_count() {
+ name=$1
+ expected=$2
+ actual=$(wc -l <"$STDOUT_FILE" | tr -d ' ')
+ if [ "$expected" -ne "$actual" ]; then
+ printf '%s\n' "FAIL: $name" >&2
+ printf '%s\n' "expected lines: $expected" >&2
+ printf '%s\n' "actual lines: $actual" >&2
+ exit 1
+ fi
+}
+
+run_capture() {
+ if "$@" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then
+ LAST_STATUS=0
+ else
+ LAST_STATUS=$?
+ fi
+ LAST_STDOUT=$(cat "$STDOUT_FILE")
+ LAST_STDERR=$(cat "$STDERR_FILE")
+}
+
+build_probe() {
+ cat >"$PROBE_C" <<'PROBE_EOF'
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int
+hex_value(char c)
+{
+ if (c >= '0' && c <= '9')
+ return (c - '0');
+ if (c >= 'a' && c <= 'f')
+ return (10 + (c - 'a'));
+ if (c >= 'A' && c <= 'F')
+ return (10 + (c - 'A'));
+ return (-1);
+}
+
+static int
+parse_uuid(const char *s, uint8_t out[16])
+{
+ char compact[32];
+ size_t i;
+ size_t len;
+ size_t pos;
+
+ len = strlen(s);
+ if (len == 36) {
+ if (s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-')
+ return (-1);
+ pos = 0;
+ for (i = 0; i < len; i++) {
+ if (s[i] == '-')
+ continue;
+ if (pos >= sizeof(compact))
+ return (-1);
+ compact[pos++] = s[i];
+ }
+ if (pos != sizeof(compact))
+ return (-1);
+ } else if (len == 32) {
+ memcpy(compact, s, sizeof(compact));
+ } else {
+ return (-1);
+ }
+
+ for (i = 0; i < 16; i++) {
+ int hi, lo;
+
+ hi = hex_value(compact[i * 2]);
+ lo = hex_value(compact[i * 2 + 1]);
+ if (hi < 0 || lo < 0)
+ return (-1);
+ out[i] = (uint8_t)((hi << 4) | lo);
+ }
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ uint8_t uuid[16];
+ uint64_t timestamp;
+ unsigned version;
+ unsigned variant_ok;
+
+ if (argc != 2)
+ return (2);
+ if (parse_uuid(argv[1], uuid) != 0)
+ return (1);
+
+ version = (unsigned)((uuid[6] >> 4) & 0x0f);
+ variant_ok = (((uuid[8] >> 6) & 0x03) == 0x02) ? 1U : 0U;
+ timestamp = ((uint64_t)(uuid[6] & 0x0f) << 56) |
+ ((uint64_t)uuid[7] << 48) |
+ ((uint64_t)uuid[4] << 40) |
+ ((uint64_t)uuid[5] << 32) |
+ ((uint64_t)uuid[0] << 24) |
+ ((uint64_t)uuid[1] << 16) |
+ ((uint64_t)uuid[2] << 8) |
+ (uint64_t)uuid[3];
+
+ printf("%u %u %llu\n", version, variant_ok,
+ (unsigned long long)timestamp);
+ return (0);
+}
+PROBE_EOF
+
+ "$CC" -O2 -std=c17 -Wall -Wextra -Werror "$PROBE_C" -o "$PROBE_BIN" \
+ || fail "failed to build probe with $CC"
+}
+
+validate_output() {
+ expected_version=$1
+ expected_lines=$2
+ expected_format=$3
+ timestamp_mode=$4
+
+ count=0
+ prev_ts=
+ while IFS= read -r line; do
+ count=$((count + 1))
+
+ case $expected_format in
+ hyphen)
+ [ ${#line} -eq 36 ] || fail "line length for hyphen UUID"
+ case $line in
+ *-*) ;;
+ *) fail "hyphen UUID missing separators" ;;
+ esac
+ ;;
+ compact)
+ [ ${#line} -eq 32 ] || fail "line length for compact UUID"
+ case $line in
+ *-*) fail "compact UUID contains hyphen" ;;
+ esac
+ ;;
+ *)
+ fail "invalid test format mode"
+ ;;
+ esac
+
+ probe_output=$("$PROBE_BIN" "$line") ||
+ fail "probe rejected UUID line: $line"
+ set -- $probe_output
+ version=$1
+ variant_ok=$2
+ timestamp=$3
+
+ [ "$version" -eq "$expected_version" ] ||
+ fail "unexpected UUID version: got $version expected $expected_version"
+ [ "$variant_ok" -eq 1 ] || fail "UUID variant is not RFC4122"
+
+ case $timestamp_mode in
+ none)
+ ;;
+ dense)
+ if [ -n "$prev_ts" ]; then
+ expected_next=$((prev_ts + 1))
+ [ "$timestamp" -eq "$expected_next" ] ||
+ fail "batch UUID timestamps are not dense"
+ fi
+ prev_ts=$timestamp
+ ;;
+ increasing)
+ if [ -n "$prev_ts" ]; then
+ [ "$timestamp" -gt "$prev_ts" ] ||
+ fail "iterative UUID timestamps are not strictly increasing"
+ fi
+ prev_ts=$timestamp
+ ;;
+ *)
+ fail "invalid test timestamp mode"
+ ;;
+ esac
+ done <"$STDOUT_FILE"
+
+ if [ "$count" -ne "$expected_lines" ]; then
+ printf '%s\n' "FAIL: output line count mismatch" >&2
+ printf '%s\n' "expected: $expected_lines" >&2
+ printf '%s\n' "actual: $count" >&2
+ exit 1
+ fi
+}
+
+assert_unique_lines() {
+ if [ -n "$(sort "$STDOUT_FILE" | uniq -d)" ]; then
+ fail "duplicate UUID lines detected"
+ fi
+}
+
+[ -x "$UUIDGEN_BIN" ] || fail "missing binary: $UUIDGEN_BIN"
+build_probe
+
+run_capture "$UUIDGEN_BIN" -x
+assert_status "invalid flag status" 1 "$LAST_STATUS"
+assert_empty "invalid flag stdout" "$LAST_STDOUT"
+assert_eq "invalid flag usage" "$USAGE_TEXT" "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN" extra-arg
+assert_status "extra arg status" 1 "$LAST_STATUS"
+assert_empty "extra arg stdout" "$LAST_STDOUT"
+assert_eq "extra arg usage" "$USAGE_TEXT" "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN" -n 0
+assert_status "count zero status" 1 "$LAST_STATUS"
+assert_empty "count zero stdout" "$LAST_STDOUT"
+assert_eq "count zero error" "uuidgen: invalid count '0' (expected 1..2048)" \
+ "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN" -n foo
+assert_status "count non-numeric status" 1 "$LAST_STATUS"
+assert_empty "count non-numeric stdout" "$LAST_STDOUT"
+assert_eq "count non-numeric error" \
+ "uuidgen: invalid count 'foo' (expected 1..2048)" "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN" -n 2049
+assert_status "count too-large status" 1 "$LAST_STATUS"
+assert_empty "count too-large stdout" "$LAST_STDOUT"
+assert_eq "count too-large error" \
+ "uuidgen: invalid count '2049' (expected 1..2048)" "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN" -n 1 -n 2
+assert_status "duplicate -n status" 1 "$LAST_STATUS"
+assert_empty "duplicate -n stdout" "$LAST_STDOUT"
+assert_eq "duplicate -n error" "uuidgen: option -n specified more than once" \
+ "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN" -o "$WORKDIR/a" -o "$WORKDIR/b"
+assert_status "duplicate -o status" 1 "$LAST_STATUS"
+assert_empty "duplicate -o stdout" "$LAST_STDOUT"
+assert_eq "duplicate -o error" "uuidgen: multiple output files not allowed" \
+ "$LAST_STDERR"
+
+run_capture "$UUIDGEN_BIN"
+assert_status "default status" 0 "$LAST_STATUS"
+assert_empty "default stderr" "$LAST_STDERR"
+assert_line_count "default line count" 1
+validate_output 1 1 hyphen increasing
+
+run_capture "$UUIDGEN_BIN" -r
+assert_status "-r status" 0 "$LAST_STATUS"
+assert_empty "-r stderr" "$LAST_STDERR"
+assert_line_count "-r line count" 1
+validate_output 4 1 hyphen none
+
+run_capture "$UUIDGEN_BIN" -c
+assert_status "-c status" 0 "$LAST_STATUS"
+assert_empty "-c stderr" "$LAST_STDERR"
+assert_line_count "-c line count" 1
+validate_output 1 1 compact increasing
+
+run_capture "$UUIDGEN_BIN" -r -c -n 8
+assert_status "-r -c -n status" 0 "$LAST_STATUS"
+assert_empty "-r -c -n stderr" "$LAST_STDERR"
+assert_line_count "-r -c -n line count" 8
+validate_output 4 8 compact none
+assert_unique_lines
+
+run_capture "$UUIDGEN_BIN" -n 6
+assert_status "batch v1 status" 0 "$LAST_STATUS"
+assert_empty "batch v1 stderr" "$LAST_STDERR"
+assert_line_count "batch v1 line count" 6
+validate_output 1 6 hyphen dense
+assert_unique_lines
+
+run_capture "$UUIDGEN_BIN" -1 -n 6
+assert_status "iterate v1 status" 0 "$LAST_STATUS"
+assert_empty "iterate v1 stderr" "$LAST_STDERR"
+assert_line_count "iterate v1 line count" 6
+validate_output 1 6 hyphen increasing
+assert_unique_lines
+
+OUT_FILE="$WORKDIR/out.txt"
+run_capture "$UUIDGEN_BIN" -r -n 3 -o "$OUT_FILE"
+assert_status "-o status" 0 "$LAST_STATUS"
+assert_empty "-o stdout" "$LAST_STDOUT"
+assert_empty "-o stderr" "$LAST_STDERR"
+cp "$OUT_FILE" "$STDOUT_FILE"
+validate_output 4 3 hyphen none
+assert_unique_lines
+
+MISSING_DIR="$WORKDIR/missing"
+run_capture "$UUIDGEN_BIN" -o "$MISSING_DIR/out.txt"
+assert_status "unopenable output status" 1 "$LAST_STATUS"
+assert_empty "unopenable output stdout" "$LAST_STDOUT"
+assert_contains "unopenable output prefix" "$LAST_STDERR" \
+ "uuidgen: cannot open '$MISSING_DIR/out.txt':"
+assert_contains "unopenable output errno" "$LAST_STDERR" \
+ "No such file or directory"
+
+run_capture "$UUIDGEN_BIN" -n 2048
+assert_status "max-count status" 0 "$LAST_STATUS"
+assert_empty "max-count stderr" "$LAST_STDERR"
+assert_line_count "max-count line count" 2048
+validate_output 1 2048 hyphen dense
+assert_unique_lines
+
+printf '%s\n' "PASS"
diff --git a/corebinutils/uuidgen/uuidgen.1 b/corebinutils/uuidgen/uuidgen.1
new file mode 100644
index 0000000000..f7911b408f
--- /dev/null
+++ b/corebinutils/uuidgen/uuidgen.1
@@ -0,0 +1,112 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2002 Marcel Moolenaar
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 27, 2025
+.Dt UUIDGEN 1
+.Os
+.Sh NAME
+.Nm uuidgen
+.Nd generate universally unique identifiers
+.Sh SYNOPSIS
+.Nm
+.Op Fl 1cr
+.Op Fl n Ar count
+.Op Fl o Ar filename
+.Sh DESCRIPTION
+The
+.Nm
+utility by default generates a single DCE version 1
+universally unique identifier (UUID),
+also known as a globally unique identifier (GUID).
+The UUID is written to stdout by default.
+The following options can be used to change the behaviour of
+.Nm :
+.Bl -tag -width indent
+.It Fl 1
+This option only has effect if multiple identifiers are to be generated and
+instructs
+.Nm
+to not generate them in batch, but one at a time.
+.It Fl c
+This option controls creation of compact UUID (without hyphen).
+.It Fl n
+This option controls the number of identifiers generated.
+By default, multiple identifiers are generated in batch.
+The upper hard limit is 2048
+.Po see
+.Xr uuidgen 2
+.Pc .
+.It Fl o
+Redirect output to
+.Ar filename
+instead of stdout.
+.It Fl r
+This option controls creation of random UUID (version 4).
+.El
+.Pp
+Batched generation yields a dense set of identifiers in such a way that there
+is no identifier that is larger than the smallest identifier in the set and
+smaller than the largest identifier in the set and that is not already in the
+set.
+.Pp
+When generating the identifiers one at a time, the identifiers will be close
+to each other, but operating system latency and processing time will be
+reflected in the distance between two successive identifiers.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Generate a batch of three UUIDs.
+Notice the similarity of the string before the first hyphen of the UUID
+.Po known as
+.Em time_low
+in
+.Em rfc4122
+.Pc :
+.Bd -literal -offset indent
+$ uuidgen -n3
+8bc44345-4d90-11ee-88c7-b42e991fc52e
+8bc44346-4d90-11ee-88c7-b42e991fc52e
+8bc44347-4d90-11ee-88c7-b42e991fc52e
+.Ed
+.Pp
+Generate a batch of random UUIDs without hyphens:
+.Bd -literal -offset indent
+$ uuidgen -r -c -n3
+5ad8b60a0f4e41f59c82d273202275f9
+6c41925486cd4bf59720a5bad85de2e4
+8144fdab63f648a1812d12453f975313
+.Ed
+.Pp
+Notice that the UUIDs are not similar to each other.
+.Sh SEE ALSO
+.Xr uuidgen 2 ,
+.Xr uuid 3
+.Sh HISTORY
+The
+.Nm
+command first appeared in
+.Fx 5.0 .
diff --git a/corebinutils/uuidgen/uuidgen.c b/corebinutils/uuidgen/uuidgen.c
new file mode 100644
index 0000000000..b5abd74da9
--- /dev/null
+++ b/corebinutils/uuidgen/uuidgen.c
@@ -0,0 +1,468 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2002 Marcel Moolenaar
+ * Copyright (c) 2022 Tobias C. Berner
+ * Copyright (c) 2026 Project Tick
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#define UUID_COUNT_MAX 2048U
+#define UUID_GREGORIAN_OFFSET 0x01B21DD213814000ULL
+#define UUID_TICKS_PER_SECOND 10000000ULL
+#define UUID_NANOSECONDS_PER_TICK 100ULL
+
+struct uuid {
+ uint8_t bytes[16];
+};
+
+struct uuid_v1_state {
+ uint8_t node[6];
+ uint16_t clock_seq;
+ uint64_t last_timestamp;
+ bool initialized;
+};
+
+struct options {
+ bool iterate;
+ bool compact;
+ bool random_v4;
+ size_t count;
+ const char *output_path;
+};
+
+static const char *progname = "uuidgen";
+
+extern long syscall(long number, ...);
+
+static void die_errno(const char *what) __attribute__((noreturn));
+static void diex(const char *fmt, ...) __attribute__((format(printf, 1, 2),
+ noreturn));
+static void fill_random_bytes(void *buf, size_t len);
+static void format_uuid(const struct uuid *id, bool compact,
+ char out[static 37]);
+static ssize_t linux_getrandom(void *buf, size_t len, unsigned int flags);
+static uint64_t now_uuid_timestamp(void);
+static void pack_uuid_v1(struct uuid *id, uint64_t timestamp,
+ uint16_t clock_seq, const uint8_t node[static 6]);
+static void parse_options(int argc, char *argv[], struct options *opts);
+static size_t parse_count(const char *arg);
+static void set_progname(const char *argv0);
+static void usage(void) __attribute__((noreturn));
+static void uuid_v1_init(struct uuid_v1_state *state);
+static void uuid_v1_generate(struct uuid_v1_state *state,
+ struct uuid *ids, size_t count, bool iterate);
+static void uuid_v4_generate(struct uuid *ids, size_t count);
+static void write_random_from_urandom(void *buf, size_t len);
+
+int
+main(int argc, char *argv[])
+{
+ FILE *out;
+ struct options opts;
+ struct uuid_v1_state state;
+ struct uuid *ids;
+ size_t i;
+
+ set_progname(argv[0]);
+ parse_options(argc, argv, &opts);
+
+ out = stdout;
+ if (opts.output_path != NULL) {
+ out = fopen(opts.output_path, "w");
+ if (out == NULL)
+ diex("cannot open '%s': %s", opts.output_path,
+ strerror(errno));
+ }
+
+ ids = calloc(opts.count, sizeof(*ids));
+ if (ids == NULL)
+ die_errno("calloc");
+
+ memset(&state, 0, sizeof(state));
+
+ if (opts.random_v4)
+ uuid_v4_generate(ids, opts.count);
+ else
+ uuid_v1_generate(&state, ids, opts.count, opts.iterate);
+
+ for (i = 0; i < opts.count; i++) {
+ char line[37];
+
+ format_uuid(&ids[i], opts.compact, line);
+ if (fputs(line, out) == EOF || fputc('\n', out) == EOF)
+ die_errno("write");
+ }
+
+ free(ids);
+
+ if (out != stdout && fclose(out) != 0)
+ die_errno("fclose");
+
+ return (0);
+}
+
+static void
+set_progname(const char *argv0)
+{
+ const char *slash;
+
+ if (argv0 == NULL || argv0[0] == '\0')
+ return;
+
+ slash = strrchr(argv0, '/');
+ if (slash != NULL && slash[1] != '\0')
+ progname = slash + 1;
+ else
+ progname = argv0;
+}
+
+static void
+parse_options(int argc, char *argv[], struct options *opts)
+{
+ bool count_set, output_set;
+ int ch;
+
+ memset(opts, 0, sizeof(*opts));
+ opts->count = 1;
+
+ count_set = false;
+ output_set = false;
+
+ opterr = 0;
+ while ((ch = getopt(argc, argv, ":1crn:o:")) != -1) {
+ switch (ch) {
+ case '1':
+ opts->iterate = true;
+ break;
+ case 'c':
+ opts->compact = true;
+ break;
+ case 'r':
+ opts->random_v4 = true;
+ break;
+ case 'n':
+ if (count_set)
+ diex("option -n specified more than once");
+ opts->count = parse_count(optarg);
+ count_set = true;
+ break;
+ case 'o':
+ if (output_set)
+ diex("multiple output files not allowed");
+ opts->output_path = optarg;
+ output_set = true;
+ break;
+ case ':':
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ if (argc != 0)
+ usage();
+}
+
+static size_t
+parse_count(const char *arg)
+{
+ char *end;
+ uintmax_t value;
+
+ errno = 0;
+ value = strtoumax(arg, &end, 10);
+ if (arg[0] == '\0' || *end != '\0' || errno == ERANGE || value == 0 ||
+ value > UUID_COUNT_MAX) {
+ diex("invalid count '%s' (expected 1..%u)", arg, UUID_COUNT_MAX);
+ }
+
+ return ((size_t)value);
+}
+
+static void
+uuid_v4_generate(struct uuid *ids, size_t count)
+{
+ size_t i;
+
+ if (count == 0)
+ return;
+
+ fill_random_bytes(ids, sizeof(*ids) * count);
+ for (i = 0; i < count; i++) {
+ ids[i].bytes[6] = (uint8_t)((ids[i].bytes[6] & 0x0fU) | 0x40U);
+ ids[i].bytes[8] = (uint8_t)((ids[i].bytes[8] & 0x3fU) | 0x80U);
+ }
+}
+
+static void
+uuid_v1_init(struct uuid_v1_state *state)
+{
+ uint8_t seed[8];
+
+ fill_random_bytes(seed, sizeof(seed));
+ memcpy(state->node, seed, sizeof(state->node));
+ state->node[0] |= 0x01U;
+ state->clock_seq = (uint16_t)(((uint16_t)seed[6] << 8) | seed[7]);
+ state->clock_seq &= 0x3fffU;
+ state->last_timestamp = 0;
+ state->initialized = true;
+}
+
+static void
+uuid_v1_generate(struct uuid_v1_state *state, struct uuid *ids, size_t count,
+ bool iterate)
+{
+ size_t i;
+
+ if (count == 0)
+ return;
+ if (!state->initialized)
+ uuid_v1_init(state);
+
+ if (!iterate) {
+ uint64_t base;
+
+ base = now_uuid_timestamp();
+ if (base < state->last_timestamp)
+ state->clock_seq = (uint16_t)((state->clock_seq + 1) &
+ 0x3fffU);
+ if (base <= state->last_timestamp)
+ base = state->last_timestamp + 1;
+ if (UINT64_MAX - base < (uint64_t)(count - 1))
+ diex("timestamp overflow while generating UUID batch");
+
+ for (i = 0; i < count; i++)
+ pack_uuid_v1(&ids[i], base + i, state->clock_seq,
+ state->node);
+ state->last_timestamp = base + (count - 1);
+ return;
+ }
+
+ for (i = 0; i < count; i++) {
+ uint64_t now;
+
+ now = now_uuid_timestamp();
+ if (now < state->last_timestamp)
+ state->clock_seq = (uint16_t)((state->clock_seq + 1) &
+ 0x3fffU);
+ if (now <= state->last_timestamp)
+ now = state->last_timestamp + 1;
+
+ pack_uuid_v1(&ids[i], now, state->clock_seq, state->node);
+ state->last_timestamp = now;
+ }
+}
+
+static uint64_t
+now_uuid_timestamp(void)
+{
+ struct timespec ts;
+ uint64_t seconds, ticks;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ die_errno("clock_gettime");
+ if (ts.tv_sec < 0)
+ diex("clock_gettime returned a pre-1970 timestamp");
+
+ seconds = (uint64_t)ts.tv_sec;
+ ticks = seconds * UUID_TICKS_PER_SECOND;
+ ticks += (uint64_t)ts.tv_nsec / UUID_NANOSECONDS_PER_TICK;
+ ticks += UUID_GREGORIAN_OFFSET;
+
+ return (ticks);
+}
+
+static void
+pack_uuid_v1(struct uuid *id, uint64_t timestamp, uint16_t clock_seq,
+ const uint8_t node[static 6])
+{
+ uint16_t time_mid, time_hi;
+ uint32_t time_low;
+
+ time_low = (uint32_t)(timestamp & 0xffffffffU);
+ time_mid = (uint16_t)((timestamp >> 32) & 0xffffU);
+ time_hi = (uint16_t)((timestamp >> 48) & 0x0fffU);
+
+ id->bytes[0] = (uint8_t)(time_low >> 24);
+ id->bytes[1] = (uint8_t)(time_low >> 16);
+ id->bytes[2] = (uint8_t)(time_low >> 8);
+ id->bytes[3] = (uint8_t)time_low;
+ id->bytes[4] = (uint8_t)(time_mid >> 8);
+ id->bytes[5] = (uint8_t)time_mid;
+ id->bytes[6] = (uint8_t)(((time_hi >> 8) & 0x0fU) | 0x10U);
+ id->bytes[7] = (uint8_t)time_hi;
+ id->bytes[8] = (uint8_t)(((clock_seq >> 8) & 0x3fU) | 0x80U);
+ id->bytes[9] = (uint8_t)clock_seq;
+ memcpy(&id->bytes[10], node, 6);
+}
+
+static void
+format_uuid(const struct uuid *id, bool compact, char out[static 37])
+{
+ int n;
+
+ if (compact) {
+ n = snprintf(out, 33,
+ "%02x%02x%02x%02x%02x%02x%02x%02x"
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
+ id->bytes[0], id->bytes[1], id->bytes[2], id->bytes[3],
+ id->bytes[4], id->bytes[5], id->bytes[6], id->bytes[7],
+ id->bytes[8], id->bytes[9], id->bytes[10], id->bytes[11],
+ id->bytes[12], id->bytes[13], id->bytes[14], id->bytes[15]);
+ if (n != 32)
+ diex("internal error formatting compact UUID");
+ return;
+ }
+
+ n = snprintf(out, 37,
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ id->bytes[0], id->bytes[1], id->bytes[2], id->bytes[3],
+ id->bytes[4], id->bytes[5], id->bytes[6], id->bytes[7],
+ id->bytes[8], id->bytes[9], id->bytes[10], id->bytes[11],
+ id->bytes[12], id->bytes[13], id->bytes[14], id->bytes[15]);
+ if (n != 36)
+ diex("internal error formatting UUID");
+}
+
+static void
+fill_random_bytes(void *buf, size_t len)
+{
+ uint8_t *p;
+
+ p = buf;
+ while (len > 0) {
+ ssize_t n;
+
+ n = linux_getrandom(p, len, 0);
+ if (n > 0) {
+ p += n;
+ len -= (size_t)n;
+ continue;
+ }
+ if (n == 0)
+ continue;
+ if (errno == EINTR)
+ continue;
+ if (errno == ENOSYS) {
+ write_random_from_urandom(p, len);
+ return;
+ }
+ die_errno("getrandom");
+ }
+}
+
+static ssize_t
+linux_getrandom(void *buf, size_t len, unsigned int flags)
+{
+#ifdef SYS_getrandom
+ return ((ssize_t)syscall(SYS_getrandom, buf, len, flags));
+#else
+ (void)buf;
+ (void)len;
+ (void)flags;
+ errno = ENOSYS;
+ return (-1);
+#endif
+}
+
+static void
+write_random_from_urandom(void *buf, size_t len)
+{
+ const char *path;
+ uint8_t *p;
+ int fd;
+
+ path = "/dev/urandom";
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ die_errno(path);
+
+ p = buf;
+ while (len > 0) {
+ ssize_t n;
+
+ n = read(fd, p, len);
+ if (n > 0) {
+ p += n;
+ len -= (size_t)n;
+ continue;
+ }
+ if (n == 0) {
+ (void)close(fd);
+ diex("%s: unexpected EOF", path);
+ }
+ if (errno == EINTR)
+ continue;
+ (void)close(fd);
+ die_errno(path);
+ }
+
+ if (close(fd) != 0)
+ die_errno("close");
+}
+
+static void
+die_errno(const char *what)
+{
+ fprintf(stderr, "%s: %s: %s\n", progname, what, strerror(errno));
+ exit(1);
+}
+
+static void
+diex(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", progname);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-1] [-r] [-c] [-n count] [-o filename]\n",
+ progname);
+ exit(1);
+}