Implement uuidgen

Implement uuidgen as a wrapper over the `uuid` crate.
This commit is contained in:
Reagan Bohan
2025-04-19 23:59:35 +00:00
parent 65959a5205
commit 1dcb5524e2
9 changed files with 430 additions and 51 deletions

128
Cargo.lock generated
View File

@@ -76,6 +76,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "atomic"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
dependencies = [
"bytemuck",
]
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -123,6 +132,12 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "cc"
version = "1.2.19"
@@ -391,7 +406,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.57.0",
"windows-core",
]
[[package]]
@@ -505,6 +520,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -521,6 +545,7 @@ dependencies = [
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]]
@@ -934,6 +959,12 @@ dependencies = [
"serde",
]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]]
name = "shlex"
version = "1.3.0"
@@ -1166,7 +1197,9 @@ dependencies = [
"uu_renice",
"uu_rev",
"uu_setsid",
"uu_uuidgen",
"uucore",
"uuid",
"xattr",
]
@@ -1331,6 +1364,19 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_uuidgen"
version = "0.0.1"
dependencies = [
"clap",
"nix",
"rand 0.9.1",
"thiserror",
"uucore",
"uuid",
"windows",
]
[[package]]
name = "uucore"
version = "0.0.30"
@@ -1369,6 +1415,29 @@ version = "0.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb6d972f580f8223cb7052d8580aea2b7061e368cf476de32ea9457b19459ed"
[[package]]
name = "uuid"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
"atomic",
"getrandom",
"md-5",
"rand 0.9.1",
"sha1_smol",
"uuid-rng-internal",
]
[[package]]
name = "uuid-rng-internal"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9376f53b15ed85851c10175b5e45f0af556b4853ff3fe335080b337e3828981e"
dependencies = [
"rand 0.9.1",
]
[[package]]
name = "version_check"
version = "0.9.5"
@@ -1499,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
"windows-collections",
"windows-core 0.61.0",
"windows-core",
"windows-future",
"windows-link",
"windows-numerics",
@@ -1511,19 +1580,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.0",
]
[[package]]
name = "windows-core"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
dependencies = [
"windows-implement 0.57.0",
"windows-interface 0.57.0",
"windows-result 0.1.2",
"windows-targets 0.52.6",
"windows-core",
]
[[package]]
@@ -1532,10 +1589,10 @@ version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement 0.60.0",
"windows-interface 0.59.1",
"windows-implement",
"windows-interface",
"windows-link",
"windows-result 0.3.2",
"windows-result",
"windows-strings",
]
@@ -1545,21 +1602,10 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
dependencies = [
"windows-core 0.61.0",
"windows-core",
"windows-link",
]
[[package]]
name = "windows-implement"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
@@ -1571,17 +1617,6 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-interface"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
@@ -1605,19 +1640,10 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.0",
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.3.2"

View File

@@ -42,6 +42,7 @@ feat_common_core = [
"renice",
"rev",
"setsid",
"uuidgen",
]
[workspace.dependencies]
@@ -69,6 +70,8 @@ tempfile = "3.9.0"
textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "2.0"
uucore = "0.0.30"
uuid = { version = "1.16.0", features = ["rng-rand"] }
windows = { version = "0.61.1" }
xattr = "1.3.1"
[dependencies]
@@ -99,6 +102,7 @@ mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", pa
renice = { optional = true, version = "0.0.1", package = "uu_renice", path = "src/uu/renice" }
rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" }
setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" }
uuidgen = { optional = true, version = "0.0.1", package = "uu_uuidgen", path ="src/uu/uuidgen" }
[dev-dependencies]
# dmesg test require fixed-boot-time feature turned on.
@@ -109,6 +113,7 @@ rand = { workspace = true }
regex = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals"] }
uuid = { workspace = true }
[target.'cfg(unix)'.dev-dependencies]
nix = { workspace = true, features = ["term"] }

View File

@@ -99,6 +99,7 @@ First, reimplement the most important tools from util-linux:
- `ldattach`: Attaches line discipline to a serial line.
- `readprofile`: Reads kernel profiling info.
- `i386, linux32, linux64, x86_64`: Set personality flags for execution environment.
- `uuidgen`: Generate different types of UUID.
Note:
* /bin/more is already implemented in https://github.com/uutils/coreutils

24
src/uu/uuidgen/Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "uu_uuidgen"
version = "0.0.1"
edition = "2021"
[lib]
path = "src/uuidgen.rs"
[[bin]]
name = "uuidgen"
path = "src/main.rs"
[dependencies]
clap = { workspace = true }
rand = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true }
uuid = { workspace = true, features = ["v1", "v3", "v4", "v5"] }
[target.'cfg(target_os = "windows")'.dependencies]
windows = { workspace = true, features = ["Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock"] }
[target.'cfg(all(target_family = "unix", not(target_os = "redox")))'.dependencies]
nix = { workspace = true, features = ["net"] }

View File

@@ -0,0 +1 @@
uucore::bin!(uu_uuidgen);

View File

@@ -0,0 +1,209 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[cfg(target_os = "windows")]
use windows::Win32::{
Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS},
NetworkManagement::IpHelper::{
GetAdaptersAddresses, GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER,
GAA_FLAG_SKIP_FRIENDLY_NAME, GAA_FLAG_SKIP_MULTICAST, GAA_FLAG_SKIP_UNICAST,
IP_ADAPTER_ADDRESSES_LH,
},
Networking::WinSock::AF_UNSPEC,
};
use clap::{crate_version, Arg, ArgAction, ArgGroup, Command};
#[cfg(all(target_family = "unix", not(target_os = "redox")))]
use nix::ifaddrs::getifaddrs;
use uucore::{
error::{UResult, USimpleError},
format_usage, help_about, help_usage,
};
use uuid::Uuid;
const ABOUT: &str = help_about!("uuidgen.md");
const USAGE: &str = help_usage!("uuidgen.md");
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = uu_app().try_get_matches_from_mut(args)?;
let md5 = args.get_flag(options::MD5);
let sha1 = args.get_flag(options::SHA1);
let namespace = args.get_one(options::NAMESPACE);
let name: Option<&String> = args.get_one(options::NAME);
// https://github.com/clap-rs/clap/issues/1537
if !(md5 || sha1) && (namespace.is_some() || name.is_some()) {
return Err(USimpleError::new(
1,
"--namespace and --name arguments require either --md5 or --sha1",
));
}
if args.get_flag(options::TIME) {
let node_id = get_node_id().unwrap_or_else(|| {
let mut default: [u8; 6] = rand::random();
default[0] |= 0x01;
default
});
println!("{:?}", Uuid::now_v1(&node_id));
} else if md5 || sha1 {
let f = if md5 { Uuid::new_v3 } else { Uuid::new_v5 };
println!(
"{:?}",
f(
namespace.expect("namespace arg to be set"),
name.expect("name to be set").as_bytes()
)
);
} else {
// Random is the default
println!("{}", Uuid::new_v4());
}
Ok(())
}
pub fn uu_app() -> Command {
let all_uuid_types = [options::RANDOM, options::TIME, options::MD5, options::SHA1];
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new(options::RANDOM)
.short('r')
.long(options::RANDOM)
.action(ArgAction::SetTrue)
.help("generate random UUID (v4)"),
)
.arg(
Arg::new(options::TIME)
.short('t')
.long(options::TIME)
.action(ArgAction::SetTrue)
.help("generate time UUID (v1)"),
)
.arg(
Arg::new(options::NAMESPACE)
.short('n')
.long(options::NAMESPACE)
.action(ArgAction::Set)
.value_parser(namespace_from_str)
.help("namespace for md5/sha1 - one of: @dns @url @oid @x500"),
)
.arg(
Arg::new(options::NAME)
.short('N')
.long(options::NAME)
.action(ArgAction::Set)
.help("name for md5/sha1"),
)
.arg(
Arg::new(options::MD5)
.short('m')
.long(options::MD5)
.action(ArgAction::SetTrue)
.requires_all([options::NAMESPACE, options::NAME])
.help("generate md5 UUID (v3)"),
)
.arg(
Arg::new(options::SHA1)
.short('s')
.long(options::SHA1)
.action(ArgAction::SetTrue)
.requires_all([options::NAMESPACE, options::NAME])
.help("generate sha1 UUID (v5)"),
)
.group(ArgGroup::new("mode").args(all_uuid_types).multiple(false))
}
fn namespace_from_str(s: &str) -> Result<Uuid, USimpleError> {
match s {
"@dns" => Ok(Uuid::NAMESPACE_DNS),
"@url" => Ok(Uuid::NAMESPACE_URL),
"@oid" => Ok(Uuid::NAMESPACE_OID),
"@x500" => Ok(Uuid::NAMESPACE_X500),
_ => Err(USimpleError {
code: 1,
message: format!("Invalid namespace {}.", s),
}),
}
}
mod options {
pub const RANDOM: &str = "random";
pub const TIME: &str = "time";
pub const MD5: &str = "md5";
pub const SHA1: &str = "sha1";
pub const NAMESPACE: &str = "namespace";
pub const NAME: &str = "name";
}
#[cfg(target_os = "windows")]
fn get_node_id() -> Option<[u8; 6]> {
unsafe {
// Skip everything we can - we are only interested in PhysicalAddress
let flags = GAA_FLAG_SKIP_UNICAST
| GAA_FLAG_SKIP_ANYCAST
| GAA_FLAG_SKIP_MULTICAST
| GAA_FLAG_SKIP_DNS_SERVER
| GAA_FLAG_SKIP_FRIENDLY_NAME;
let mut size = 0;
let ret = GetAdaptersAddresses(AF_UNSPEC.0 as u32, flags, None, None, &mut size);
if ret != ERROR_BUFFER_OVERFLOW.0 {
return None;
}
let mut buf = vec![0u8; size as usize];
let ret = GetAdaptersAddresses(
AF_UNSPEC.0 as u32,
flags,
None,
Some(buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH),
&mut size,
);
if ret != ERROR_SUCCESS.0 {
return None;
}
// SAFETY: GetAdaptersAddresses returns ERROR_NO_DATA error if it's zero len
let mut adapter_ptr = buf.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH;
while !adapter_ptr.is_null() {
let adapter = adapter_ptr.read();
if adapter.PhysicalAddressLength == 6 {
return Some(adapter.PhysicalAddress[0..6].try_into().unwrap());
}
adapter_ptr = adapter.Next;
}
}
None
}
#[cfg(all(target_family = "unix", not(target_os = "redox")))]
fn get_node_id() -> Option<[u8; 6]> {
getifaddrs().ok().and_then(|iflist| {
iflist
.filter_map(|intf| intf.address?.as_link_addr()?.addr())
.find(|mac| mac.iter().any(|x| *x != 0))
})
}
#[cfg(not(any(
target_os = "windows",
all(target_family = "unix", not(target_os = "redox"))
)))]
fn get_node_id() -> Option<[u8; 6]> {
None
}

11
src/uu/uuidgen/uuidgen.md Normal file
View File

@@ -0,0 +1,11 @@
# uuidgen
```
uuidgen [options]
```
Create UUIDs:
- v1: time + MAC address (-t/--time)
- v3: namespace + name, MD5 based (-m/--md5)
- v4: random (-r/--random)
- v5: namespace + name, SHA1 based (-s/--sha1)

View File

@@ -0,0 +1,98 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use uuid::Uuid;
use crate::common::util::{TestScenario, UCommand};
fn assert_ver_eq(cmd: &mut UCommand, ver: uuid::Version) {
let uuid = Uuid::parse_str(
cmd.succeeds()
.stdout_str()
.strip_suffix('\n')
.expect("newline"),
)
.expect("valid UUID");
assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
assert_eq!(uuid.get_version(), Some(ver));
}
#[test]
fn test_random() {
assert_ver_eq(&mut new_ucmd!(), uuid::Version::Random);
assert_ver_eq(new_ucmd!().arg("-r"), uuid::Version::Random);
assert_ver_eq(new_ucmd!().arg("--random"), uuid::Version::Random);
}
#[test]
fn test_time() {
assert_ver_eq(new_ucmd!().arg("-t"), uuid::Version::Mac);
assert_ver_eq(new_ucmd!().arg("--time"), uuid::Version::Mac);
}
#[test]
fn test_arg_conflict() {
new_ucmd!().args(&["-r", "-t"]).fails().code_is(1);
new_ucmd!().args(&["--time", "--random"]).fails().code_is(1);
}
#[test]
fn test_md5_sha1() {
new_ucmd!()
.args(&["--namespace", "@dns", "--name", "example.com", "-m"])
.succeeds()
.stdout_only("9073926b-929f-31c2-abc9-fad77ae3e8eb\n");
new_ucmd!()
.args(&["-s", "--namespace", "@dns", "--name", "foobar"])
.succeeds()
.stdout_only("a050b517-6677-5119-9a77-2d26bbf30507\n");
new_ucmd!()
.args(&["-s", "--namespace", "@url", "--name", "foobar"])
.succeeds()
.stdout_only("8304efdd-bd6e-5b7c-a27f-83f3f05c64e0\n");
new_ucmd!()
.args(&["--sha1", "--namespace", "@oid", "--name", "foobar"])
.succeeds()
.stdout_only("364c03e1-bcdc-58bb-94ed-43e9a92f5f08\n");
new_ucmd!()
.args(&["--sha1", "--namespace", "@x500", "--name", "foobar"])
.succeeds()
.stdout_only("34da942e-f4a3-5169-9c65-267d2b22cf11\n");
}
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("-Z").fails().code_is(1);
new_ucmd!().args(&["-r", "-Z"]).fails().code_is(1);
}
#[test]
fn test_name_namespace_on_non_hash() {
new_ucmd!()
.args(&["--namespace", "@dns", "-r"])
.fails()
.code_is(1);
new_ucmd!()
.args(&["--name", "example.com", "-r"])
.fails()
.code_is(1);
new_ucmd!()
.args(&["--namespace", "@dns", "--name", "example.com", "-r"])
.fails()
.code_is(1);
}
#[test]
fn test_missing_name_namespace() {
new_ucmd!().arg("--sha1").fails().code_is(1);
new_ucmd!()
.args(&["--sha1", "--namespace", "@dns"])
.fails()
.code_is(1);
new_ucmd!()
.args(&["--sha1", "--name", "example.com"])
.fails()
.code_is(1);
}

View File

@@ -60,3 +60,7 @@ mod test_fsfreeze;
#[cfg(feature = "mcookie")]
#[path = "by-util/test_mcookie.rs"]
mod test_mcookie;
#[cfg(feature = "uuidgen")]
#[path = "by-util/test_uuidgen.rs"]
mod test_uuidgen;