diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:30:21 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:30:21 +0300 |
| commit | 0788f5f3de23dc8e2515830dc10f43980fd3c3c2 (patch) | |
| tree | d4490263cefa00fed34e0671bea9c87acfce5024 /corebinutils | |
| parent | b82d29b6d7d6e7f092f705bb3a1387367562aa24 (diff) | |
| parent | 5cdcecb6c24e931462c92c66ead41a572302b454 (diff) | |
| download | Project-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/.gitignore | 2 | ||||
| -rw-r--r-- | corebinutils/uuidgen/GNUmakefile | 35 | ||||
| -rw-r--r-- | corebinutils/uuidgen/LICENSE | 24 | ||||
| -rw-r--r-- | corebinutils/uuidgen/README.md | 46 | ||||
| -rwxr-xr-x | corebinutils/uuidgen/tests/test.sh | 376 | ||||
| -rw-r--r-- | corebinutils/uuidgen/uuidgen.1 | 112 | ||||
| -rw-r--r-- | corebinutils/uuidgen/uuidgen.c | 468 |
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); +} |
