From 2a9c9911797358dd84d1504cffbb9f158b2a344a Mon Sep 17 00:00:00 2001 From: Niloyyy Date: Wed, 10 Jun 2026 03:34:12 +0600 Subject: [PATCH] feat: add Windows-native newgrp command implementation --- Cargo.toml | 1 + deps/newgrp/Cargo.toml | 19 +++++++ deps/newgrp/src/lib.rs | 118 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 deps/newgrp/Cargo.toml create mode 100644 deps/newgrp/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 5e97da5..3e03d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ uptime = { package = "uu_uptime", path = "deps/coreutils/src/uu/uptime" } findutils = { package = "findutils", path = "deps/findutils" } grep = { package = "uu_grep", path = "deps/grep" } ntfind = { package = "find", path = "deps/ntfind" } +newgrp = { package = "uu_newgrp", path = "deps/newgrp" } # For registry access in main.rs [dependencies.windows-sys] diff --git a/deps/newgrp/Cargo.toml b/deps/newgrp/Cargo.toml new file mode 100644 index 0000000..8cec290 --- /dev/null +++ b/deps/newgrp/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "uu_newgrp" +version = "0.0.0" +edition = "2024" +license = "MIT" +publish = false + +[dependencies] +clap = { version = "4.5", features = ["wrap_help"] } +uucore = { path = "../coreutils/src/uucore" } + +[dependencies.windows-sys] +version = "*" +features = [ + "Win32_Security", + "Win32_System_Threading", + "Win32_Foundation", + "Win32_System_Environment", +] diff --git a/deps/newgrp/src/lib.rs b/deps/newgrp/src/lib.rs new file mode 100644 index 0000000..fb1e05a --- /dev/null +++ b/deps/newgrp/src/lib.rs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use clap::{Arg, Command}; +use std::process; +use std::ptr; + +use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, ERROR_INSUFFICIENT_BUFFER, HANDLE}; +use windows_sys::Win32::Security::{ + LookupAccountNameW, SetTokenInformation, TokenPrimaryGroup, + TOKEN_ADJUST_DEFAULT, TOKEN_PRIMARY_GROUP, TOKEN_QUERY, +}; +use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; + +pub fn uu_app() -> Command { + Command::new("newgrp") + .about("Log in to a new group") + .arg( + Arg::new("group") + .help("The group to log into") + .index(1) + .required(false), + ) +} + +pub fn uumain(args: impl uucore::Args) -> i32 { + let matches = uu_app().get_matches_from(args); + let group = matches.get_one::("group"); + + if let Some(g) = group { + if let Err(e) = change_primary_group(g) { + eprintln!("newgrp: failed to change primary group: {}", e); + return 1; + } + } + + // Spawn the shell + let shell = std::env::var("SHELL").unwrap_or_else(|_| "cmd.exe".to_string()); + + let mut child = match process::Command::new(shell).spawn() { + Ok(c) => c, + Err(e) => { + eprintln!("newgrp: failed to execute shell: {}", e); + return 1; + } + }; + + match child.wait() { + Ok(status) => status.code().unwrap_or(1), + Err(e) => { + eprintln!("newgrp: wait failed: {}", e); + 1 + } + } +} + +fn change_primary_group(group: &str) -> Result<(), String> { + unsafe { + let group_w: Vec = group.encode_utf16().chain(std::iter::once(0)).collect(); + let mut sid_size = 0; + let mut domain_size = 0; + let mut pe_use = 0; + + // First call to get required sizes + LookupAccountNameW( + ptr::null(), + group_w.as_ptr(), + ptr::null_mut(), + &mut sid_size, + ptr::null_mut(), + &mut domain_size, + &mut pe_use, + ); + + if GetLastError() != ERROR_INSUFFICIENT_BUFFER { + return Err(format!("LookupAccountNameW failed getting size: {}", GetLastError())); + } + + let mut sid = vec![0u8; sid_size as usize]; + let mut domain = vec![0u16; domain_size as usize]; + + if LookupAccountNameW( + ptr::null(), + group_w.as_ptr(), + sid.as_mut_ptr() as _, + &mut sid_size, + domain.as_mut_ptr(), + &mut domain_size, + &mut pe_use, + ) == 0 { + return Err(format!("LookupAccountNameW failed: {}", GetLastError())); + } + + let mut token: HANDLE = std::ptr::null_mut(); + if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_DEFAULT, &mut token) == 0 { + return Err(format!("OpenProcessToken failed: {}", GetLastError())); + } + + let mut token_group = TOKEN_PRIMARY_GROUP { + PrimaryGroup: sid.as_mut_ptr() as _, + }; + + let res = SetTokenInformation( + token, + TokenPrimaryGroup, + &mut token_group as *mut _ as *const _, + std::mem::size_of::() as u32, + ); + + CloseHandle(token); + + if res == 0 { + return Err(format!("SetTokenInformation failed: {}", GetLastError())); + } + + Ok(()) + } +}