Feat: grp module (#3967)

This commit is contained in:
Sion Kang authored and GitHub committed 2022-08-04 15:44:40 +09:00
1 parent 33b420fd0b
commit bb52ed774e
6 files changed
+227 -8

No files matched your search

Generated
+1
View File
@@ -1750,6 +1750,7 @@ dependencies = [
"openssl-probe",
"openssl-sys",
"page_size",
"parking_lot",
"paste",
"puruspe",
"rand",
+109
View File
@@ -0,0 +1,109 @@
"""Test script for the grp module."""
import unittest
from test.support import import_helper
grp = import_helper.import_module('grp')
@unittest.skipUnless(hasattr(grp, 'getgrall'), 'Does not have getgrall()')
class GroupDatabaseTestCase(unittest.TestCase):
def check_value(self, value):
# check that a grp tuple has the entries and
# attributes promised by the docs
self.assertEqual(len(value), 4)
self.assertEqual(value[0], value.gr_name)
self.assertIsInstance(value.gr_name, str)
self.assertEqual(value[1], value.gr_passwd)
self.assertIsInstance(value.gr_passwd, str)
self.assertEqual(value[2], value.gr_gid)
self.assertIsInstance(value.gr_gid, int)
self.assertEqual(value[3], value.gr_mem)
self.assertIsInstance(value.gr_mem, list)
def test_values(self):
entries = grp.getgrall()
for e in entries:
self.check_value(e)
def test_values_extended(self):
entries = grp.getgrall()
if len(entries) > 1000: # Huge group file (NIS?) -- skip the rest
self.skipTest('huge group file, extended test skipped')
for e in entries:
e2 = grp.getgrgid(e.gr_gid)
self.check_value(e2)
self.assertEqual(e2.gr_gid, e.gr_gid)
name = e.gr_name
if name.startswith('+') or name.startswith('-'):
# NIS-related entry
continue
e2 = grp.getgrnam(name)
self.check_value(e2)
# There are instances where getgrall() returns group names in
# lowercase while getgrgid() returns proper casing.
# Discovered on Ubuntu 5.04 (custom).
self.assertEqual(e2.gr_name.lower(), name.lower())
def test_errors(self):
self.assertRaises(TypeError, grp.getgrgid)
self.assertRaises(TypeError, grp.getgrnam)
self.assertRaises(TypeError, grp.getgrall, 42)
# embedded null character
self.assertRaises(ValueError, grp.getgrnam, 'a\x00b')
# try to get some errors
bynames = {}
bygids = {}
for (n, p, g, mem) in grp.getgrall():
if not n or n == '+':
continue # skip NIS entries etc.
bynames[n] = g
bygids[g] = n
allnames = list(bynames.keys())
namei = 0
fakename = allnames[namei]
while fakename in bynames:
chars = list(fakename)
for i in range(len(chars)):
if chars[i] == 'z':
chars[i] = 'A'
break
elif chars[i] == 'Z':
continue
else:
chars[i] = chr(ord(chars[i]) + 1)
break
else:
namei = namei + 1
try:
fakename = allnames[namei]
except IndexError:
# should never happen... if so, just forget it
break
fakename = ''.join(chars)
self.assertRaises(KeyError, grp.getgrnam, fakename)
# Choose a non-existent gid.
fakegid = 4127
while fakegid in bygids:
fakegid = (fakegid * 3) % 0x10000
self.assertRaises(KeyError, grp.getgrgid, fakegid)
def test_noninteger_gid(self):
entries = grp.getgrall()
if not entries:
self.skipTest('no groups')
# Choose an existent gid.
gid = entries[0][2]
self.assertRaises(TypeError, grp.getgrgid, float(gid))
self.assertRaises(TypeError, grp.getgrgid, str(gid))
if __name__ == "__main__":
unittest.main()
+4
View File
@@ -1999,6 +1999,8 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, user=65535)
# TODO: RUSTPYTHON, observed gids do not match expected gids
@unittest.expectedFailure
@unittest.skipUnless(hasattr(os, 'setregid'), 'no setregid() on platform')
def test_group(self):
gid = os.getegid()
@@ -2046,6 +2048,8 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, group=65535)
# TODO: RUSTPYTHON, observed gids do not match expected gids
@unittest.expectedFailure
@unittest.skipUnless(hasattr(os, 'setgroups'), 'no setgroups() on platform')
def test_extra_groups(self):
gid = os.getegid()
+1
View File
@@ -63,6 +63,7 @@ ahash = "0.7.6"
libz-sys = { version = "1.1.5", optional = true }
num_enum = "0.5.7"
ascii = "1.0.0"
parking_lot = "0.12.0"
# mmap
[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies]
+97
View File
@@ -0,0 +1,97 @@
pub(crate) use grp::make_module;
#[pymodule]
mod grp {
use crate::vm::{
builtins::{PyIntRef, PyListRef, PyStrRef},
convert::{IntoPyException, ToPyObject},
exceptions,
types::PyStructSequence,
PyObjectRef, PyResult, VirtualMachine,
};
use nix::unistd;
use std::ptr::NonNull;
#[pyattr]
#[pyclass(module = "grp", name = "struct_group")]
#[derive(PyStructSequence)]
struct Group {
gr_name: String,
gr_passwd: String,
gr_gid: u32,
gr_mem: PyListRef,
}
#[pyimpl(with(PyStructSequence))]
impl Group {
fn from_unistd_group(group: unistd::Group, vm: &VirtualMachine) -> Self {
let cstr_lossy = |s: std::ffi::CString| {
s.into_string()
.unwrap_or_else(|e| e.into_cstring().to_string_lossy().into_owned())
};
Group {
gr_name: group.name,
gr_passwd: cstr_lossy(group.passwd),
gr_gid: group.gid.as_raw(),
gr_mem: vm
.ctx
.new_list(group.mem.iter().map(|s| s.to_pyobject(vm)).collect()),
}
}
}
#[pyfunction]
fn getgrgid(gid: PyIntRef, vm: &VirtualMachine) -> PyResult<Group> {
let gr_gid = gid.as_bigint();
let gid = libc::gid_t::try_from(gr_gid)
.map(unistd::Gid::from_raw)
.ok();
let group = gid
.map(unistd::Group::from_gid)
.transpose()
.map_err(|err| err.into_pyexception(vm))?
.flatten();
let group = group.ok_or_else(|| {
vm.new_key_error(
vm.ctx
.new_str(format!("getgrgid: group id {} not found", gr_gid))
.into(),
)
})?;
Ok(Group::from_unistd_group(group, vm))
}
#[pyfunction]
fn getgrnam(name: PyStrRef, vm: &VirtualMachine) -> PyResult<Group> {
let gr_name = name.as_str();
if gr_name.contains('\0') {
return Err(exceptions::cstring_error(vm));
}
let group = unistd::Group::from_name(gr_name).map_err(|err| err.into_pyexception(vm))?;
let group = group.ok_or_else(|| {
vm.new_key_error(
vm.ctx
.new_str(format!("getgrnam: group name {} not found", gr_name))
.into(),
)
})?;
Ok(Group::from_unistd_group(group, vm))
}
#[pyfunction]
fn getgrall(vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
// setgrent, getgrent, etc are not thread safe. Could use fgetgrent_r, but this is easier
static GETGRALL: parking_lot::Mutex<()> = parking_lot::const_mutex(());
let _guard = GETGRALL.lock();
let mut list = Vec::new();
unsafe { libc::setgrent() };
while let Some(ptr) = NonNull::new(unsafe { libc::getgrent() }) {
let group = unistd::Group::from(unsafe { ptr.as_ref() });
let group = Group::from_unistd_group(group, vm).to_pyobject(vm);
list.push(group);
}
unsafe { libc::endgrent() };
Ok(list)
}
}
+15 -8
View File
@@ -41,6 +41,8 @@ mod multiprocessing;
#[cfg(unix)]
mod posixsubprocess;
// libc is missing constants on redox
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
mod grp;
#[cfg(all(unix, not(target_os = "redox")))]
mod resource;
#[cfg(target_os = "macos")]
@@ -118,20 +120,25 @@ pub fn get_module_inits() -> impl Iterator<Item = (Cow<'static, str>, StdlibInit
{
"_ssl" => ssl::make_module,
}
#[cfg(all(unix, not(target_os = "redox"), not(target_os = "ios")))]
{
"termios" => termios::make_module,
}
#[cfg(all(unix, not(target_os = "redox")))]
{
"resource" => resource::make_module,
}
// Unix-only
#[cfg(unix)]
{
"_posixsubprocess" => posixsubprocess::make_module,
"syslog" => syslog::make_module,
"mmap" => mmap::make_module,
}
#[cfg(all(unix, not(target_os = "redox")))]
{
"resource" => resource::make_module,
}
#[cfg(all(unix, not(any(target_os = "ios", target_os = "redox"))))]
{
"termios" => termios::make_module,
}
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
{
"grp" => grp::make_module,
}
#[cfg(target_os = "macos")]
{
"_scproxy" => scproxy::make_module,