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