rust/passwd: finish moving group and passwd parsers
This drops the remaining C compatibility hops, moving group and passwd parsing logic fully into Rust, under a `nameservice` module.
This commit is contained in:
parent
a0e6427bb6
commit
15a32c12d6
@ -11,9 +11,6 @@ prefix = "ROR"
|
||||
# Here we exclude entries belonging to the C side which we use on the Rust side and so
|
||||
# doesn't make sense to re-export.
|
||||
exclude = ["RpmOstreeOrigin",
|
||||
"PasswdDB",
|
||||
"rpmostree_add_group_to_hash",
|
||||
"rpmostree_add_passwd_to_hash",
|
||||
"rpmostree_get_repodata_chksum_repr",
|
||||
"rpmostree_origin_get_live_state" ]
|
||||
|
||||
|
@ -122,6 +122,7 @@ pub(crate) fn ref_from_raw_ptr<T>(p: *mut T) -> &'static mut T {
|
||||
// TODO: Try upstreaming this into the glib crate?
|
||||
|
||||
/// Convert C results (int + GError convention) to anyhow.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn int_gerror_to_result(res: i32, gerror: *mut glib_sys::GError) -> anyhow::Result<()> {
|
||||
if res != 0 {
|
||||
Ok(())
|
||||
|
@ -8,7 +8,6 @@ NOTICE: The C header definitions are canonical, please update those first
|
||||
then synchronize the entries here.
|
||||
!*/
|
||||
|
||||
use crate::passwd::PasswdDB;
|
||||
use libdnf_sys::DnfPackage;
|
||||
|
||||
// From `libpriv/rpmostree-rpm-util.h`.
|
||||
@ -19,22 +18,3 @@ extern "C" {
|
||||
gerror: *mut *mut glib_sys::GError,
|
||||
) -> libc::c_int;
|
||||
}
|
||||
|
||||
// From `libpriv/rpmostree-passwd-util.h`.
|
||||
extern "C" {
|
||||
#[allow(improper_ctypes)]
|
||||
pub(crate) fn rpmostree_add_passwd_to_hash(
|
||||
rootfs_dfd: libc::c_int,
|
||||
path: *const libc::c_char,
|
||||
db: *mut PasswdDB,
|
||||
gerror: *mut *mut glib_sys::GError,
|
||||
) -> libc::c_int;
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
pub(crate) fn rpmostree_add_group_to_hash(
|
||||
rootfs_dfd: libc::c_int,
|
||||
path: *const libc::c_char,
|
||||
db: *mut PasswdDB,
|
||||
gerror: *mut *mut glib_sys::GError,
|
||||
) -> libc::c_int;
|
||||
}
|
||||
|
@ -175,13 +175,8 @@ mod ffi {
|
||||
fn passwd_cleanup(rootfd: i32) -> Result<()>;
|
||||
|
||||
type PasswdDB;
|
||||
fn add_user(self: &mut PasswdDB, uid: u32, username: &str);
|
||||
fn lookup_user(self: &PasswdDB, uid: u32) -> Result<String>;
|
||||
fn add_group(self: &mut PasswdDB, gid: u32, groupname: &str);
|
||||
fn lookup_group(self: &PasswdDB, gid: u32) -> Result<String>;
|
||||
// TODO(lucab): get rid of the two methods below.
|
||||
fn add_group_content(self: &mut PasswdDB, rootfs: i32, group_path: &str) -> Result<()>;
|
||||
fn add_passwd_content(self: &mut PasswdDB, rootfs: i32, passwd_path: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
// countme.rs
|
||||
@ -228,6 +223,7 @@ mod lockfile;
|
||||
pub use self::lockfile::*;
|
||||
mod live;
|
||||
pub(crate) use self::live::*;
|
||||
mod nameservice;
|
||||
// An origin parser in Rust but only built when testing until
|
||||
// we're ready to try porting the C++ code.
|
||||
#[cfg(test)]
|
||||
|
97
rust/src/nameservice/group.rs
Normal file
97
rust/src/nameservice/group.rs
Normal file
@ -0,0 +1,97 @@
|
||||
//! Helpers for [user passwd file](https://man7.org/linux/man-pages/man5/passwd.5.html).
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::io::BufRead;
|
||||
|
||||
// Entry from group file.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct GroupEntry {
|
||||
pub(crate) name: String,
|
||||
pub(crate) passwd: String,
|
||||
pub(crate) gid: u32,
|
||||
pub(crate) users: Vec<String>,
|
||||
}
|
||||
|
||||
impl GroupEntry {
|
||||
pub fn parse_line(s: impl AsRef<str>) -> Option<Self> {
|
||||
let mut parts = s.as_ref().splitn(4, ':');
|
||||
let entry = Self {
|
||||
name: parts.next()?.to_string(),
|
||||
passwd: parts.next()?.to_string(),
|
||||
gid: parts.next().and_then(|s| s.parse().ok())?,
|
||||
users: {
|
||||
let users = parts.next()?;
|
||||
users.split(',').map(String::from).collect()
|
||||
},
|
||||
};
|
||||
Some(entry)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_group_content(content: impl BufRead) -> Result<Vec<GroupEntry>> {
|
||||
let mut groups = vec![];
|
||||
for (line_num, line) in content.lines().enumerate() {
|
||||
let input =
|
||||
line.with_context(|| format!("failed to read group entry at line {}", line_num))?;
|
||||
|
||||
// Skip empty and comment lines
|
||||
if input.is_empty() || input.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
// Skip NSS compat lines, see "Compatibility mode" in
|
||||
// https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html
|
||||
if input.starts_with('+') || input.starts_with('-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = GroupEntry::parse_line(&input).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"failed to parse group entry at line {}, content: {}",
|
||||
line_num,
|
||||
&input
|
||||
)
|
||||
})?;
|
||||
groups.push(entry);
|
||||
}
|
||||
Ok(groups)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{BufReader, Cursor};
|
||||
|
||||
#[test]
|
||||
fn test_parse_lines() {
|
||||
let content = r#"
|
||||
+groupA
|
||||
-groupB
|
||||
|
||||
root:x:0:
|
||||
daemon:x:1:
|
||||
bin:x:2:
|
||||
sys:x:3:
|
||||
adm:x:4:
|
||||
www-data:x:33:
|
||||
backup:x:34:
|
||||
operator:x:37:
|
||||
|
||||
# Dummy comment
|
||||
staff:x:50:operator
|
||||
|
||||
+
|
||||
"#;
|
||||
|
||||
let input = BufReader::new(Cursor::new(content));
|
||||
let groups = parse_group_content(input).unwrap();
|
||||
assert_eq!(groups.len(), 9);
|
||||
|
||||
let staff = GroupEntry {
|
||||
name: "staff".to_string(),
|
||||
passwd: "x".to_string(),
|
||||
gid: 50,
|
||||
users: vec!["operator".to_string()],
|
||||
};
|
||||
assert_eq!(groups[8], staff);
|
||||
}
|
||||
}
|
6
rust/src/nameservice/mod.rs
Normal file
6
rust/src/nameservice/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
//! Linux name-service information helpers.
|
||||
|
||||
// TODO(lucab): consider moving this to its own crate.
|
||||
|
||||
pub(crate) mod group;
|
||||
pub(crate) mod passwd;
|
102
rust/src/nameservice/passwd.rs
Normal file
102
rust/src/nameservice/passwd.rs
Normal file
@ -0,0 +1,102 @@
|
||||
//! Helpers for [user group file](https://man7.org/linux/man-pages/man5/group.5.html).
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::io::BufRead;
|
||||
|
||||
// Entry from passwd file.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct PasswdEntry {
|
||||
pub(crate) name: String,
|
||||
pub(crate) passwd: String,
|
||||
pub(crate) uid: u32,
|
||||
pub(crate) gid: u32,
|
||||
pub(crate) gecos: String,
|
||||
pub(crate) home_dir: String,
|
||||
pub(crate) shell: String,
|
||||
}
|
||||
|
||||
impl PasswdEntry {
|
||||
pub fn parse_line(s: impl AsRef<str>) -> Option<Self> {
|
||||
let mut parts = s.as_ref().splitn(7, ':');
|
||||
let entry = Self {
|
||||
name: parts.next()?.to_string(),
|
||||
passwd: parts.next()?.to_string(),
|
||||
uid: parts.next().and_then(|s| s.parse().ok())?,
|
||||
gid: parts.next().and_then(|s| s.parse().ok())?,
|
||||
gecos: parts.next()?.to_string(),
|
||||
home_dir: parts.next()?.to_string(),
|
||||
shell: parts.next()?.to_string(),
|
||||
};
|
||||
Some(entry)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_passwd_content(content: impl BufRead) -> Result<Vec<PasswdEntry>> {
|
||||
let mut passwds = vec![];
|
||||
for (line_num, line) in content.lines().enumerate() {
|
||||
let input =
|
||||
line.with_context(|| format!("failed to read passwd entry at line {}", line_num))?;
|
||||
|
||||
// Skip empty and comment lines
|
||||
if input.is_empty() || input.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
// Skip NSS compat lines, see "Compatibility mode" in
|
||||
// https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html
|
||||
if input.starts_with('+') || input.starts_with('-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = PasswdEntry::parse_line(&input).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"failed to parse group entry at line {}, content: {}",
|
||||
line_num,
|
||||
&input
|
||||
)
|
||||
})?;
|
||||
passwds.push(entry);
|
||||
}
|
||||
Ok(passwds)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{BufReader, Cursor};
|
||||
|
||||
#[test]
|
||||
fn test_parse_lines() {
|
||||
let content = r#"
|
||||
root:x:0:0:root:/root:/bin/bash
|
||||
|
||||
+userA
|
||||
-userB
|
||||
|
||||
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||
systemd-coredump:x:1:1:systemd Core Dumper:/:/usr/sbin/nologin
|
||||
|
||||
+@groupA
|
||||
-@groupB
|
||||
|
||||
# Dummy comment
|
||||
someuser:x:1000:1000:Foo BAR,,,:/home/foobar:/bin/bash
|
||||
|
||||
+
|
||||
"#;
|
||||
|
||||
let input = BufReader::new(Cursor::new(content));
|
||||
let groups = parse_passwd_content(input).unwrap();
|
||||
assert_eq!(groups.len(), 4);
|
||||
|
||||
let someuser = PasswdEntry {
|
||||
name: "someuser".to_string(),
|
||||
passwd: "x".to_string(),
|
||||
uid: 1000,
|
||||
gid: 1000,
|
||||
gecos: "Foo BAR,,,".to_string(),
|
||||
home_dir: "/home/foobar".to_string(),
|
||||
shell: "/bin/bash".to_string(),
|
||||
};
|
||||
assert_eq!(groups[3], someuser);
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
use crate::cxxrsutil;
|
||||
use crate::ffiutil;
|
||||
use crate::includes::*;
|
||||
use crate::nameservice;
|
||||
use anyhow::{anyhow, Result};
|
||||
use c_utf8::CUtf8Buf;
|
||||
use nix::unistd::{Gid, Uid};
|
||||
use openat_ext::OpenatDirExt;
|
||||
use std::collections::HashMap;
|
||||
use std::io::BufReader;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -167,37 +167,29 @@ impl PasswdDB {
|
||||
.ok_or_else(|| anyhow!("failed to find group ID '{}'", gid))
|
||||
}
|
||||
|
||||
/// Add a user ID with the associated name.
|
||||
pub fn add_user(&mut self, uid: u32, username: &str) {
|
||||
let id = Uid::from_raw(uid);
|
||||
self.users.insert(id, username.to_string());
|
||||
}
|
||||
|
||||
/// Add a group ID with the associated name.
|
||||
pub fn add_group(&mut self, gid: u32, groupname: &str) {
|
||||
let id = Gid::from_raw(gid);
|
||||
self.groups.insert(id, groupname.to_string());
|
||||
}
|
||||
|
||||
/// Add content from a `group` file.
|
||||
pub fn add_group_content(&mut self, rootfs: i32, group_path: &str) -> anyhow::Result<()> {
|
||||
let c_path: CUtf8Buf = group_path.to_string().into();
|
||||
let db_ptr = self as *mut Self;
|
||||
let mut gerror: *mut glib_sys::GError = std::ptr::null_mut();
|
||||
// TODO(lucab): find a replacement for `fgetgrent` and drop this.
|
||||
let res =
|
||||
unsafe { rpmostree_add_group_to_hash(rootfs, c_path.as_ptr(), db_ptr, &mut gerror) };
|
||||
ffiutil::int_gerror_to_result(res, gerror)
|
||||
fn add_group_content(&mut self, rootfs_dfd: i32, group_path: &str) -> anyhow::Result<()> {
|
||||
let rootfs = ffiutil::ffi_view_openat_dir(rootfs_dfd);
|
||||
let db = rootfs.open_file(group_path)?;
|
||||
let entries = nameservice::group::parse_group_content(BufReader::new(db))?;
|
||||
|
||||
for group in entries {
|
||||
let id = Gid::from_raw(group.gid);
|
||||
self.groups.insert(id, group.name);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add content from a `passwd` file.
|
||||
pub fn add_passwd_content(&mut self, rootfs: i32, passwd_path: &str) -> anyhow::Result<()> {
|
||||
let c_path: CUtf8Buf = passwd_path.to_string().into();
|
||||
let db_ptr = self as *mut Self;
|
||||
let mut gerror: *mut glib_sys::GError = std::ptr::null_mut();
|
||||
// TODO(lucab): find a replacement for `fgetpwent` and drop this.
|
||||
let res =
|
||||
unsafe { rpmostree_add_passwd_to_hash(rootfs, c_path.as_ptr(), db_ptr, &mut gerror) };
|
||||
ffiutil::int_gerror_to_result(res, gerror)
|
||||
fn add_passwd_content(&mut self, rootfs_dfd: i32, passwd_path: &str) -> anyhow::Result<()> {
|
||||
let rootfs = ffiutil::ffi_view_openat_dir(rootfs_dfd);
|
||||
let db = rootfs.open_file(passwd_path)?;
|
||||
let entries = nameservice::passwd::parse_passwd_content(BufReader::new(db))?;
|
||||
|
||||
for user in entries {
|
||||
let id = Uid::from_raw(user.uid);
|
||||
self.users.insert(id, user.name);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1133,61 +1133,3 @@ rpmostree_passwd_compose_prep (int rootfs_dfd,
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
rpmostree_add_passwd_to_hash (int rootfs_dfd, const char *path,
|
||||
rpmostreecxx::PasswdDB *db,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(FILE) src_stream = open_file_stream_read_at (rootfs_dfd, path, error);
|
||||
if (!src_stream)
|
||||
return FALSE;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
errno = 0;
|
||||
struct passwd *pw = fgetpwent (src_stream);
|
||||
if (!pw)
|
||||
{
|
||||
if (errno != 0 && errno != ENOENT)
|
||||
return glnx_throw_errno_prefix (error, "fgetpwent");
|
||||
break;
|
||||
}
|
||||
if (pw->pw_name != NULL)
|
||||
{
|
||||
std::string username(pw->pw_name);
|
||||
db->add_user (pw->pw_uid, username);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
rpmostree_add_group_to_hash (int rootfs_dfd, const char *path,
|
||||
rpmostreecxx::PasswdDB *db,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(FILE) src_stream = open_file_stream_read_at (rootfs_dfd, path, error);
|
||||
if (!src_stream)
|
||||
return FALSE;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
errno = 0;
|
||||
struct group *gr = fgetgrent (src_stream);
|
||||
if (!gr)
|
||||
{
|
||||
if (errno != 0 && errno != ENOENT)
|
||||
return glnx_throw_errno_prefix (error, "fgetgrent");
|
||||
break;
|
||||
}
|
||||
if (gr->gr_name != NULL)
|
||||
{
|
||||
std::string groupname(gr->gr_name);
|
||||
db->add_group (gr->gr_gid, groupname);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
@ -30,15 +30,6 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gboolean
|
||||
rpmostree_add_passwd_to_hash (int rootfs_dfd, const char *path,
|
||||
rpmostreecxx::PasswdDB *db,
|
||||
GError **error);
|
||||
gboolean
|
||||
rpmostree_add_group_to_hash (int rootfs_dfd, const char *path,
|
||||
rpmostreecxx::PasswdDB *db,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
rpmostree_check_passwd (OstreeRepo *repo,
|
||||
int rootfs_dfd,
|
||||
|
Loading…
Reference in New Issue
Block a user