rust/treefile: add support for check-passwd/groups data entries
This adds treefile support for both `check-passwd` and `check-groups` entries with "type: data".
This commit is contained in:
parent
a7e333925e
commit
6b13f2596c
@ -5,7 +5,7 @@
|
||||
use crate::cxxrsutil::*;
|
||||
use crate::ffiutil;
|
||||
use crate::nameservice;
|
||||
use crate::treefile::{CheckPasswdType, Treefile};
|
||||
use crate::treefile::{CheckGroups, CheckPasswd, Treefile};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use gio::prelude::InputStreamExtManual;
|
||||
use gio::FileExt;
|
||||
@ -331,17 +331,17 @@ fn data_from_json(
|
||||
|
||||
// Migrate the check data from the specified file to /etc.
|
||||
let mut src_file = if target == "passwd" {
|
||||
let check_passwd_cfg = treefile.get_check_passwd();
|
||||
if check_passwd_cfg.variant != CheckPasswdType::File {
|
||||
return Ok(false);
|
||||
let check_passwd_file = match treefile.parsed.get_check_passwd() {
|
||||
CheckPasswd::File(cfg) => cfg,
|
||||
_ => return Ok(false),
|
||||
};
|
||||
treefile.passwd_file_mut().context("missing passwd file")?
|
||||
treefile.externals.passwd_file_mut(&check_passwd_file)?
|
||||
} else if target == "group" {
|
||||
let check_groups_cfg = treefile.get_check_groups();
|
||||
if check_groups_cfg.variant != CheckPasswdType::File {
|
||||
return Ok(false);
|
||||
let check_groups_file = match treefile.parsed.get_check_groups() {
|
||||
CheckGroups::File(cfg) => cfg,
|
||||
_ => return Ok(false),
|
||||
};
|
||||
treefile.group_file_mut().context("missing group file")?
|
||||
treefile.externals.group_file_mut(&check_groups_file)?
|
||||
} else {
|
||||
unreachable!("impossible merge target '{}'", target);
|
||||
};
|
||||
|
@ -43,7 +43,7 @@ const DEFAULT_RPMDB_BACKEND: RpmdbBackend = RpmdbBackend::Sqlite;
|
||||
|
||||
/// This struct holds file descriptors for any external files/data referenced by
|
||||
/// a TreeComposeConfig.
|
||||
struct TreefileExternals {
|
||||
pub(crate) struct TreefileExternals {
|
||||
postprocess_script: Option<fs::File>,
|
||||
add_files: collections::BTreeMap<String, fs::File>,
|
||||
passwd: Option<fs::File>,
|
||||
@ -61,7 +61,7 @@ pub struct Treefile {
|
||||
rojig_name: Option<String>,
|
||||
rojig_spec: Option<String>,
|
||||
serialized: CUtf8Buf,
|
||||
externals: TreefileExternals,
|
||||
pub(crate) externals: TreefileExternals,
|
||||
}
|
||||
|
||||
// We only use this while parsing
|
||||
@ -178,18 +178,11 @@ fn take_archful_pkgs(
|
||||
Ok(archful_pkgs)
|
||||
}
|
||||
|
||||
// If a passwd/group file is provided explicitly, load it as a fd
|
||||
fn load_passwd_file<P: AsRef<Path>>(
|
||||
basedir: P,
|
||||
v: &Option<CheckPasswd>,
|
||||
) -> Result<Option<fs::File>> {
|
||||
if let Some(ref v) = *v {
|
||||
/// If a passwd/group file is provided explicitly, load it as a fd.
|
||||
fn load_passwd_file<P: AsRef<Path>>(basedir: P, cfg: &CheckFile) -> Result<Option<fs::File>> {
|
||||
let basedir = basedir.as_ref();
|
||||
if let Some(ref path) = v.filename {
|
||||
return Ok(Some(utils::open_file(basedir.join(path))?));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
let file = utils::open_file(basedir.join(&cfg.filename))?;
|
||||
Ok(Some(file))
|
||||
}
|
||||
|
||||
type IncludeMap = collections::BTreeMap<(u64, u64), String>;
|
||||
@ -237,8 +230,15 @@ fn treefile_parse<P: AsRef<Path>>(
|
||||
}
|
||||
}
|
||||
let parent = utils::parent_dir(filename).unwrap();
|
||||
let passwd = load_passwd_file(&parent, &tf.check_passwd)?;
|
||||
let group = load_passwd_file(&parent, &tf.check_groups)?;
|
||||
let passwd = match tf.get_check_passwd() {
|
||||
CheckPasswd::File(ref f) => load_passwd_file(&parent, f)?,
|
||||
_ => None,
|
||||
};
|
||||
let group = match tf.get_check_groups() {
|
||||
CheckGroups::File(ref f) => load_passwd_file(&parent, f)?,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(ConfigAndExternals {
|
||||
config: tf,
|
||||
externals: TreefileExternals {
|
||||
@ -481,24 +481,10 @@ impl Treefile {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn passwd_file_mut(&mut self) -> Option<&mut fs::File> {
|
||||
self.externals
|
||||
.passwd
|
||||
.as_mut()
|
||||
.and_then(|f| f.seek(io::SeekFrom::Start(0)).ok().map(|_| f))
|
||||
}
|
||||
|
||||
pub(crate) fn get_passwd_fd(&mut self) -> i32 {
|
||||
self.externals.passwd.as_mut().map_or(-1, raw_seeked_fd)
|
||||
}
|
||||
|
||||
pub(crate) fn group_file_mut(&mut self) -> Option<&mut fs::File> {
|
||||
self.externals
|
||||
.group
|
||||
.as_mut()
|
||||
.and_then(|f| f.seek(io::SeekFrom::Start(0)).ok().map(|_| f))
|
||||
}
|
||||
|
||||
pub(crate) fn get_group_fd(&mut self) -> i32 {
|
||||
self.externals.group.as_mut().map_or(-1, raw_seeked_fd)
|
||||
}
|
||||
@ -566,22 +552,6 @@ impl Treefile {
|
||||
files_to_remove
|
||||
}
|
||||
|
||||
pub(crate) fn get_check_passwd(&self) -> &CheckPasswd {
|
||||
static DEFAULT: CheckPasswd = CheckPasswd {
|
||||
variant: CheckPasswdType::Previous,
|
||||
filename: None,
|
||||
};
|
||||
self.parsed.check_passwd.as_ref().unwrap_or(&DEFAULT)
|
||||
}
|
||||
|
||||
pub(crate) fn get_check_groups(&self) -> &CheckPasswd {
|
||||
static DEFAULT: CheckPasswd = CheckPasswd {
|
||||
variant: CheckPasswdType::Previous,
|
||||
filename: None,
|
||||
};
|
||||
self.parsed.check_groups.as_ref().unwrap_or(&DEFAULT)
|
||||
}
|
||||
|
||||
/// Do some upfront semantic checks we can do beyond just the type safety serde provides.
|
||||
fn validate_config(config: &TreeComposeConfig) -> Result<()> {
|
||||
// check add-files
|
||||
@ -747,6 +717,24 @@ fn hash_file(hasher: &mut glib::Checksum, mut f: &fs::File) -> Result<()> {
|
||||
}
|
||||
|
||||
impl TreefileExternals {
|
||||
pub(crate) fn group_file_mut(&mut self, _sentinel: &CheckFile) -> Result<&mut fs::File> {
|
||||
let group_file = self
|
||||
.group
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow::anyhow!("missing passwd file"))?;
|
||||
group_file.seek(io::SeekFrom::Start(0))?;
|
||||
Ok(group_file)
|
||||
}
|
||||
|
||||
pub(crate) fn passwd_file_mut(&mut self, _sentinel: &CheckFile) -> Result<&mut fs::File> {
|
||||
let passwd_file = self
|
||||
.passwd
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow::anyhow!("missing passwd file"))?;
|
||||
passwd_file.seek(io::SeekFrom::Start(0))?;
|
||||
Ok(passwd_file)
|
||||
}
|
||||
|
||||
fn hasher_update(&self, hasher: &mut glib::Checksum) -> Result<()> {
|
||||
if let Some(ref f) = self.postprocess_script {
|
||||
hash_file(hasher, f)?;
|
||||
@ -819,25 +807,70 @@ impl Default for BootLocation {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub(crate) enum CheckPasswdType {
|
||||
#[serde(tag = "type")]
|
||||
pub(crate) enum CheckGroups {
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
#[serde(rename = "previous")]
|
||||
Previous,
|
||||
#[serde(rename = "file")]
|
||||
File,
|
||||
File(CheckFile),
|
||||
#[serde(rename = "data")]
|
||||
Data,
|
||||
Data(CheckGroupsData),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct CheckPasswd {
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) variant: CheckPasswdType,
|
||||
filename: Option<String>,
|
||||
// Skip this for now, a separate file is easier
|
||||
// and anyways we want to switch to sysusers
|
||||
// entries: Option<Map<>String>,
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub(crate) struct CheckFile {
|
||||
filename: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub(crate) struct CheckGroupsData {
|
||||
entries: HashMap<String, u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
pub(crate) enum CheckPasswd {
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
#[serde(rename = "previous")]
|
||||
Previous,
|
||||
#[serde(rename = "file")]
|
||||
File(CheckFile),
|
||||
#[serde(rename = "data")]
|
||||
Data(CheckPasswdData),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub(crate) struct CheckPasswdData {
|
||||
entries: HashMap<String, CheckPasswdDataEntries>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum CheckPasswdDataEntries {
|
||||
IdValue(u64),
|
||||
IdTuple([u64; 1]),
|
||||
UidGid((u64, u64)),
|
||||
}
|
||||
|
||||
impl From<u64> for CheckPasswdDataEntries {
|
||||
fn from(item: u64) -> Self {
|
||||
Self::IdValue(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u64; 1]> for CheckPasswdDataEntries {
|
||||
fn from(item: [u64; 1]) -> Self {
|
||||
Self::IdTuple(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u64, u64)> for CheckPasswdDataEntries {
|
||||
fn from(item: (u64, u64)) -> Self {
|
||||
Self::UidGid(item)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -974,7 +1007,7 @@ pub(crate) struct TreeComposeConfig {
|
||||
check_passwd: Option<CheckPasswd>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "check-groups")]
|
||||
check_groups: Option<CheckPasswd>,
|
||||
check_groups: Option<CheckGroups>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "ignore-removed-users")]
|
||||
ignore_removed_users: Option<Vec<String>>,
|
||||
@ -1091,6 +1124,16 @@ impl TreeComposeConfig {
|
||||
hasher.update(serde_json::to_vec(self)?.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_check_passwd(&self) -> &CheckPasswd {
|
||||
static DEFAULT: CheckPasswd = CheckPasswd::Previous;
|
||||
self.check_passwd.as_ref().unwrap_or(&DEFAULT)
|
||||
}
|
||||
|
||||
pub(crate) fn get_check_groups(&self) -> &CheckGroups {
|
||||
static DEFAULT: CheckGroups = CheckGroups::Previous;
|
||||
self.check_groups.as_ref().unwrap_or(&DEFAULT)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -1532,9 +1575,32 @@ etc-group-members:
|
||||
{
|
||||
let workdir = tempfile::tempdir().unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), VALID_PRELUDE, None).unwrap();
|
||||
let default_cfg = tf.get_check_passwd();
|
||||
assert_eq!(default_cfg.variant, CheckPasswdType::Previous);
|
||||
assert_eq!(default_cfg.filename, None);
|
||||
let default_cfg = tf.parsed.get_check_passwd();
|
||||
assert_eq!(default_cfg, &CheckPasswd::Previous);
|
||||
}
|
||||
{
|
||||
let input = VALID_PRELUDE.to_string() + r#"check-passwd: { "type": "none" }"#;
|
||||
let workdir = tempfile::tempdir().unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
|
||||
let custom_cfg = tf.parsed.get_check_passwd();
|
||||
assert_eq!(custom_cfg, &CheckPasswd::None);
|
||||
}
|
||||
{
|
||||
let input = VALID_PRELUDE.to_string()
|
||||
+ r#"check-passwd: { "type": "data", "entries": { "bin": 1, "adm": [3, 4], "foo" : [2] } }"#;
|
||||
let workdir = tempfile::tempdir().unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
|
||||
let custom_cfg = tf.parsed.get_check_passwd();
|
||||
assert_eq!(
|
||||
custom_cfg,
|
||||
&CheckPasswd::Data(CheckPasswdData {
|
||||
entries: maplit::hashmap!(
|
||||
"bin".into() => 1.into(),
|
||||
"adm".into() => (3, 4).into(),
|
||||
"foo".into() => [2].into(),
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
{
|
||||
let input = VALID_PRELUDE.to_string()
|
||||
@ -1545,9 +1611,13 @@ etc-group-members:
|
||||
.write_file_contents("local-file", 0o755, "")
|
||||
.unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
|
||||
let custom_cfg = tf.get_check_passwd();
|
||||
assert_eq!(custom_cfg.variant, CheckPasswdType::File);
|
||||
assert_eq!(custom_cfg.filename, Some("local-file".to_string()));
|
||||
let custom_cfg = tf.parsed.get_check_passwd();
|
||||
assert_eq!(
|
||||
custom_cfg,
|
||||
&CheckPasswd::File(CheckFile {
|
||||
filename: "local-file".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1556,9 +1626,30 @@ etc-group-members:
|
||||
{
|
||||
let workdir = tempfile::tempdir().unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), VALID_PRELUDE, None).unwrap();
|
||||
let default_cfg = tf.get_check_groups();
|
||||
assert_eq!(default_cfg.variant, CheckPasswdType::Previous);
|
||||
assert_eq!(default_cfg.filename, None);
|
||||
let default_cfg = tf.parsed.get_check_groups();
|
||||
assert_eq!(default_cfg, &CheckGroups::Previous);
|
||||
}
|
||||
{
|
||||
let input = VALID_PRELUDE.to_string() + r#"check-groups: { "type": "none" }"#;
|
||||
let workdir = tempfile::tempdir().unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
|
||||
let custom_cfg = tf.parsed.get_check_groups();
|
||||
assert_eq!(custom_cfg, &CheckGroups::None);
|
||||
}
|
||||
{
|
||||
let input = VALID_PRELUDE.to_string()
|
||||
+ r#"check-groups: { "type": "data", "entries": { "bin": 1 } }"#;
|
||||
let workdir = tempfile::tempdir().unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
|
||||
let custom_cfg = tf.parsed.get_check_groups();
|
||||
assert_eq!(
|
||||
custom_cfg,
|
||||
&CheckGroups::Data(CheckGroupsData {
|
||||
entries: maplit::hashmap!(
|
||||
"bin".into() => 1,
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
{
|
||||
let input = VALID_PRELUDE.to_string()
|
||||
@ -1569,9 +1660,13 @@ etc-group-members:
|
||||
.write_file_contents("local-file", 0o755, "")
|
||||
.unwrap();
|
||||
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
|
||||
let custom_cfg = tf.get_check_groups();
|
||||
assert_eq!(custom_cfg.variant, CheckPasswdType::File);
|
||||
assert_eq!(custom_cfg.filename, Some("local-file".to_string()));
|
||||
let custom_cfg = tf.parsed.get_check_groups();
|
||||
assert_eq!(
|
||||
custom_cfg,
|
||||
&CheckGroups::File(CheckFile {
|
||||
filename: "local-file".to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user