summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dracut-cpio/Cargo.lock12
-rw-r--r--src/dracut-cpio/Cargo.toml11
-rw-r--r--src/dracut-cpio/src/main.rs1813
-rw-r--r--src/dracut-cpio/third_party/crosvm/Cargo.toml8
-rw-r--r--src/dracut-cpio/third_party/crosvm/LICENSE27
-rw-r--r--src/dracut-cpio/third_party/crosvm/argument.rs549
-rw-r--r--src/dracut-cpio/third_party/crosvm/crosvm.rs2
-rw-r--r--src/install/Makefile7
-rw-r--r--src/install/dracut-install.c2281
-rw-r--r--src/install/hashmap.c658
-rw-r--r--src/install/hashmap.h88
-rw-r--r--src/install/log.c303
-rw-r--r--src/install/log.h98
-rw-r--r--src/install/macro.h299
-rw-r--r--src/install/strv.c616
-rw-r--r--src/install/strv.h118
-rw-r--r--src/install/util.c574
-rw-r--r--src/install/util.h609
-rw-r--r--src/logtee/logtee.c71
-rw-r--r--src/skipcpio/skipcpio.c207
-rw-r--r--src/util/CMakeLists.txt6
-rw-r--r--src/util/util.c306
22 files changed, 8663 insertions, 0 deletions
diff --git a/src/dracut-cpio/Cargo.lock b/src/dracut-cpio/Cargo.lock
new file mode 100644
index 0000000..75dca6f
--- /dev/null
+++ b/src/dracut-cpio/Cargo.lock
@@ -0,0 +1,12 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "crosvm"
+version = "0.1.0"
+
+[[package]]
+name = "dracut-cpio"
+version = "0.1.0"
+dependencies = [
+ "crosvm",
+]
diff --git a/src/dracut-cpio/Cargo.toml b/src/dracut-cpio/Cargo.toml
new file mode 100644
index 0000000..28cfb15
--- /dev/null
+++ b/src/dracut-cpio/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "dracut-cpio"
+description = "cpio archive generator for Dracut"
+authors = ["David Disseldorp"]
+license = "GPL-2.0"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+crosvm = { path = "third_party/crosvm" }
+# please avoid adding any more dependencies
diff --git a/src/dracut-cpio/src/main.rs b/src/dracut-cpio/src/main.rs
new file mode 100644
index 0000000..c3bfe90
--- /dev/null
+++ b/src/dracut-cpio/src/main.rs
@@ -0,0 +1,1813 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2021 SUSE LLC
+
+use std::convert::TryInto;
+use std::env;
+use std::ffi::OsStr;
+use std::fs;
+use std::io;
+use std::io::prelude::*;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::FileTypeExt;
+use std::os::unix::fs::MetadataExt as UnixMetadataExt;
+use std::path::{Path, PathBuf};
+
+use crosvm::argument::{self, Argument};
+
+macro_rules! NEWC_HDR_FMT {
+ () => {
+ concat!(
+ "{magic}{ino:08X}{mode:08X}{uid:08X}{gid:08X}{nlink:08X}",
+ "{mtime:08X}{filesize:08X}{major:08X}{minor:08X}{rmajor:08X}",
+ "{rminor:08X}{namesize:08X}{chksum:08X}"
+ )
+ };
+}
+
+// Don't print debug messages on release builds...
+#[cfg(debug_assertions)]
+macro_rules! dout {
+ ($($l:tt)*) => { println!($($l)*); }
+}
+#[cfg(not(debug_assertions))]
+macro_rules! dout {
+ ($($l:tt)*) => {};
+}
+
+const NEWC_HDR_LEN: u64 = 110;
+const PATH_MAX: u64 = 4096;
+
+struct HardlinkPath {
+ infile: PathBuf,
+ outfile: PathBuf,
+}
+
+struct HardlinkState {
+ names: Vec<HardlinkPath>,
+ source_ino: u64,
+ mapped_ino: u32,
+ nlink: u32,
+ seen: u32,
+}
+
+struct DevState {
+ dev: u64,
+ hls: Vec<HardlinkState>,
+}
+
+struct ArchiveProperties {
+ // first inode number to use. @ArchiveState.ino increments from this.
+ initial_ino: u32,
+ // if non-zero, then align file data segments to this offset by injecting
+ // extra zeros after the filename string terminator.
+ data_align: u32,
+ // When injecting extra zeros into the filename field for data alignment,
+ // ensure that it doesn't exceed this size. The linux kernel will ignore
+ // files where namesize is larger than PATH_MAX, hence the need for this.
+ namesize_max: u32,
+ // if the archive is being appended to the end of an existing file, then
+ // @initial_data_off is used when calculating @data_align alignment.
+ initial_data_off: u64,
+ // delimiter character for the stdin file list
+ list_separator: u8,
+ // mtime, uid and gid to use for archived inodes, instead of the value
+ // reported by stat.
+ fixed_mtime: Option<u32>,
+ fixed_uid: Option<u32>,
+ fixed_gid: Option<u32>,
+ // When archiving a subset of hardlinks, nlink values in the archive can
+ // represent the subset (renumber_nlink=true) or the original source file
+ // nlink values (renumber_nlink=false), where the latter matches GNU cpio.
+ renumber_nlink: bool,
+ // If OUTPUT file exists, then zero-truncate it instead of appending. The
+ // default append behaviour chains archives back-to-back, i.e. multiple
+ // archives will be separated by a TRAILER and 512-byte padding.
+ // See Linux's Documentation/driver-api/early-userspace/buffer-format.rst
+ // for details on how chained initramfs archives are handled.
+ truncate_existing: bool,
+}
+
+impl ArchiveProperties {
+ pub fn default() -> ArchiveProperties {
+ ArchiveProperties {
+ initial_ino: 0, // match GNU cpio numbering
+ data_align: 0,
+ namesize_max: PATH_MAX as u32,
+ initial_data_off: 0,
+ list_separator: b'\n',
+ fixed_mtime: None,
+ fixed_uid: None,
+ fixed_gid: None,
+ renumber_nlink: false,
+ truncate_existing: false,
+ }
+ }
+}
+
+struct ArchiveState {
+ // 2d dev + inode vector serves two purposes:
+ // - dev index provides reproducible major,minor values
+ // - inode@dev provides hardlink state tracking
+ ids: Vec<DevState>,
+ // offset from the start of this archive
+ off: u64,
+ // next mapped inode number, used instead of source file inode numbers to
+ // ensure reproducibility. XXX: should track inode per mapped dev?
+ ino: u32,
+}
+
+impl ArchiveState {
+ pub fn new(ino_start: u32) -> ArchiveState {
+ ArchiveState {
+ ids: Vec::new(),
+ off: 0,
+ ino: ino_start,
+ }
+ }
+
+ // lookup or create DevState for @dev. Return @major/@minor based on index
+ pub fn dev_seen(&mut self, dev: u64) -> Option<(u32, u32)> {
+ let index: u64 = match self.ids.iter().position(|i| i.dev == dev) {
+ Some(idx) => idx.try_into().ok()?,
+ None => {
+ self.ids.push(DevState {
+ dev: dev,
+ hls: Vec::new(),
+ });
+ (self.ids.len() - 1).try_into().ok()?
+ }
+ };
+
+ let major: u32 = (index >> 32).try_into().unwrap();
+ let minor: u32 = (index & u64::from(u32::MAX)).try_into().unwrap();
+ Some((major, minor))
+ }
+
+ // Check whether we've already seen this hardlink's dev/inode combination.
+ // If already seen, fill the existing mapped_ino.
+ // Return true if this entry has been deferred (seen != nlinks)
+ pub fn hardlink_seen<W: Write + Seek>(
+ &mut self,
+ props: &ArchiveProperties,
+ mut writer: W,
+ major: u32,
+ minor: u32,
+ md: fs::Metadata,
+ inpath: &Path,
+ outpath: &Path,
+ mapped_ino: &mut Option<u32>,
+ mapped_nlink: &mut Option<u32>,
+ ) -> std::io::Result<bool> {
+ assert!(md.nlink() > 1);
+ let index = u64::from(major) << 32 | u64::from(minor);
+ // reverse index->major/minor conversion that was just done
+ let devstate: &mut DevState = &mut self.ids[index as usize];
+ let (_index, hl) = match devstate
+ .hls
+ .iter_mut()
+ .enumerate()
+ .find(|(_, hl)| hl.source_ino == md.ino())
+ {
+ Some(hl) => hl,
+ None => {
+ devstate.hls.push(HardlinkState {
+ names: vec![HardlinkPath {
+ infile: inpath.to_path_buf(),
+ outfile: outpath.to_path_buf(),
+ }],
+ source_ino: md.ino(),
+ mapped_ino: self.ino,
+ nlink: md.nlink().try_into().unwrap(), // pre-checked
+ seen: 1,
+ });
+ self.ino += 1; // ino is reserved for all subsequent links
+ return Ok(true);
+ }
+ };
+
+ if (*hl).names.iter().any(|n| n.infile == inpath) {
+ println!(
+ "duplicate hardlink path {} for {}",
+ inpath.display(),
+ md.ino()
+ );
+ // GNU cpio doesn't swallow duplicates
+ }
+
+ // hl.nlink may not match md.nlink if we've come here via
+ // archive_flush_unseen_hardlinks() .
+
+ (*hl).seen += 1;
+ if (*hl).seen > (*hl).nlink {
+ // GNU cpio powers through if a hardlink is listed multiple times,
+ // exceeding nlink.
+ println!("hardlink seen {} exceeds nlink {}", (*hl).seen, (*hl).nlink);
+ }
+
+ if (*hl).seen < (*hl).nlink {
+ (*hl).names.push(HardlinkPath {
+ infile: inpath.to_path_buf(),
+ outfile: outpath.to_path_buf(),
+ });
+ return Ok(true);
+ }
+
+ // a new HardlinkPath entry isn't added, as return path handles cpio
+ // outpath header *and* data segment.
+
+ for path in (*hl).names.iter().rev() {
+ dout!("writing hardlink {}", path.outfile.display());
+ // length already PATH_MAX validated
+ let fname = path.outfile.as_os_str().as_bytes();
+
+ write!(
+ writer,
+ NEWC_HDR_FMT!(),
+ magic = "070701",
+ ino = (*hl).mapped_ino,
+ mode = md.mode(),
+ uid = match props.fixed_uid {
+ Some(u) => u,
+ None => md.uid(),
+ },
+ gid = match props.fixed_gid {
+ Some(g) => g,
+ None => md.gid(),
+ },
+ nlink = match props.renumber_nlink {
+ true => (*hl).nlink,
+ false => md.nlink().try_into().unwrap(),
+ },
+ mtime = match props.fixed_mtime {
+ Some(t) => t,
+ None => md.mtime().try_into().unwrap(),
+ },
+ filesize = 0,
+ major = major,
+ minor = major,
+ rmajor = 0,
+ rminor = 0,
+ namesize = fname.len() + 1,
+ chksum = 0
+ )?;
+ self.off += NEWC_HDR_LEN;
+ writer.write_all(fname)?;
+ self.off += fname.len() as u64;
+ // +1 as padding starts after fname nulterm
+ let seeklen = 1 + archive_padlen(self.off + 1, 4);
+ {
+ let z = vec![0u8; seeklen.try_into().unwrap()];
+ writer.write_all(&z)?;
+ }
+ self.off += seeklen;
+ }
+ *mapped_ino = Some((*hl).mapped_ino);
+ // cpio nlink may be different to stat nlink if only a subset of links
+ // are archived.
+ if props.renumber_nlink {
+ *mapped_nlink = Some((*hl).nlink);
+ }
+
+ // GNU cpio: if a name is given multiple times, exceeding nlink, then
+ // subsequent names continue to be packed (with a repeat data segment),
+ // using the same mapped inode.
+ dout!("resetting hl at index {}", index);
+ hl.seen = 0;
+ hl.names.clear();
+
+ return Ok(false);
+ }
+}
+
+fn archive_path<W: Seek + Write>(
+ state: &mut ArchiveState,
+ props: &ArchiveProperties,
+ path: &Path,
+ mut writer: W,
+) -> std::io::Result<()> {
+ let inpath = path;
+ let mut outpath = path.clone();
+ let mut datalen: u32 = 0;
+ let mut rmajor: u32 = 0;
+ let mut rminor: u32 = 0;
+ let mut hardlink_ino: Option<u32> = None;
+ let mut hardlink_nlink: Option<u32> = None;
+ let mut symlink_tgt = PathBuf::new();
+ let mut data_align_seek: u32 = 0;
+
+ outpath = match outpath.strip_prefix("./") {
+ Ok(p) => {
+ if p.as_os_str().as_bytes().len() == 0 {
+ outpath // retain './' and '.' paths
+ } else {
+ p
+ }
+ }
+ Err(_) => outpath,
+ };
+ let fname = outpath.as_os_str().as_bytes();
+ if fname.len() + 1 >= PATH_MAX.try_into().unwrap() {
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, "path too long"));
+ }
+
+ let md = match fs::symlink_metadata(inpath) {
+ Ok(m) => m,
+ Err(e) => {
+ println!("failed to get metadata for {}: {}", inpath.display(), e);
+ return Err(e);
+ }
+ };
+ dout!("archiving {} with mode {:o}", outpath.display(), md.mode());
+
+ let (major, minor) = match state.dev_seen(md.dev()) {
+ Some((maj, min)) => (maj, min),
+ None => return Err(io::Error::new(io::ErrorKind::Other, "failed to map dev")),
+ };
+
+ if md.nlink() > u32::MAX as u64 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "nlink too large",
+ ));
+ }
+
+ let mtime: u32 = match props.fixed_mtime {
+ Some(t) => t,
+ None => {
+ // check for 2106 epoch overflow
+ if md.mtime() > i64::from(u32::MAX) {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "mtime too large for cpio",
+ ));
+ }
+ md.mtime().try_into().unwrap()
+ }
+ };
+
+ let ftype = md.file_type();
+ if ftype.is_symlink() {
+ symlink_tgt = fs::read_link(inpath)?;
+ datalen = {
+ let d: usize = symlink_tgt.as_os_str().as_bytes().len();
+ if d >= PATH_MAX.try_into().unwrap() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "symlink path too long",
+ ));
+ }
+ d.try_into().unwrap()
+ };
+ // no zero terminator for symlink target path
+ }
+
+ // Linux kernel uses 32-bit dev_t, encoded as mmmM MMmm. glibc uses 64-bit
+ // MMMM Mmmm mmmM MMmm, which is compatible with the former.
+ if ftype.is_block_device() || ftype.is_char_device() {
+ let rd = md.rdev();
+ rmajor = (((rd >> 32) & 0xfffff000) | ((rd >> 8) & 0x00000fff)) as u32;
+ rminor = (((rd >> 12) & 0xffffff00) | (rd & 0x000000ff)) as u32;
+ }
+
+ if ftype.is_file() {
+ datalen = {
+ if md.len() > u64::from(u32::MAX) {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "file too large for newc",
+ ));
+ }
+ md.len().try_into().unwrap()
+ };
+
+ if md.nlink() > 1 {
+ // follow GNU cpio's behaviour of attaching hardlink data only to
+ // the last entry in the archive.
+ let deferred = state.hardlink_seen(
+ &props,
+ &mut writer,
+ major,
+ minor,
+ md.clone(),
+ &inpath,
+ outpath,
+ &mut hardlink_ino,
+ &mut hardlink_nlink,
+ )?;
+ if deferred {
+ dout!("deferring hardlink {} data portion", outpath.display());
+ return Ok(());
+ }
+ }
+
+ if props.data_align > 0 && datalen > props.data_align {
+ // XXX we're "bending" the newc spec a bit here to inject zeros
+ // after fname to provide data segment alignment. These zeros are
+ // accounted for in the namesize, but some applications may only
+ // expect a single zero-terminator (and 4 byte alignment). GNU cpio
+ // and Linux initramfs handle this fine as long as PATH_MAX isn't
+ // exceeded.
+ data_align_seek = {
+ let len: u64 = archive_padlen(
+ props.initial_data_off + state.off + NEWC_HDR_LEN + fname.len() as u64 + 1,
+ u64::from(props.data_align),
+ );
+ let padded_namesize = len + fname.len() as u64 + 1;
+ if padded_namesize > u64::from(props.namesize_max) {
+ dout!(
+ "{} misaligned. Required padding {} exceeds namesize maximum {}.",
+ outpath.display(),
+ len,
+ props.namesize_max
+ );
+ 0
+ } else {
+ len.try_into().unwrap()
+ }
+ };
+ }
+ }
+
+ write!(
+ writer,
+ NEWC_HDR_FMT!(),
+ magic = "070701",
+ ino = match hardlink_ino {
+ Some(i) => i,
+ None => {
+ let i = state.ino;
+ state.ino += 1;
+ i
+ }
+ },
+ mode = md.mode(),
+ uid = match props.fixed_uid {
+ Some(u) => u,
+ None => md.uid(),
+ },
+ gid = match props.fixed_gid {
+ Some(g) => g,
+ None => md.gid(),
+ },
+ nlink = match hardlink_nlink {
+ Some(n) => n,
+ None => md.nlink().try_into().unwrap(),
+ },
+ mtime = mtime,
+ filesize = datalen,
+ major = major,
+ minor = major,
+ rmajor = rmajor,
+ rminor = rminor,
+ namesize = fname.len() + 1 + data_align_seek as usize,
+ chksum = 0
+ )?;
+ state.off += NEWC_HDR_LEN;
+
+ writer.write_all(fname)?;
+ state.off += fname.len() as u64;
+
+ let mut seek_len: i64 = 1; // fname nulterm
+ if data_align_seek > 0 {
+ seek_len += data_align_seek as i64;
+ assert_eq!(archive_padlen(state.off + seek_len as u64, 4), 0);
+ } else {
+ let padding_len = archive_padlen(state.off + seek_len as u64, 4);
+ seek_len += padding_len as i64;
+ }
+ {
+ let z = vec![0u8; seek_len.try_into().unwrap()];
+ writer.write_all(&z)?;
+ }
+ state.off += seek_len as u64;
+
+ // io::copy() can reflink: https://github.com/rust-lang/rust/pull/75272 \o/
+ if datalen > 0 {
+ if ftype.is_file() {
+ let mut reader = io::BufReader::new(fs::File::open(inpath)?);
+ let copied = io::copy(&mut reader, &mut writer)?;
+ if copied != u64::from(datalen) {
+ return Err(io::Error::new(
+ io::ErrorKind::UnexpectedEof,
+ "copy returned unexpected length",
+ ));
+ }
+ } else if ftype.is_symlink() {
+ writer.write_all(symlink_tgt.as_os_str().as_bytes())?;
+ }
+ state.off += u64::from(datalen);
+ let dpad_len: usize = archive_padlen(state.off, 4).try_into().unwrap();
+ write!(writer, "{pad:.padlen$}", padlen = dpad_len, pad = "\0\0\0")?;
+ state.off += dpad_len as u64;
+ }
+
+ Ok(())
+}
+
+fn archive_padlen(off: u64, alignment: u64) -> u64 {
+ (alignment - (off & (alignment - 1))) % alignment
+}
+
+// this fn is inefficient, but optimizing for hardlinks isn't high priority
+fn archive_flush_unseen_hardlinks<W: Write + Seek>(
+ state: &mut ArchiveState,
+ props: &ArchiveProperties,
+ mut writer: W,
+) -> std::io::Result<()> {
+ let mut deferred_inpaths: Vec<PathBuf> = Vec::new();
+ for id in state.ids.iter_mut() {
+ for hl in id.hls.iter_mut() {
+ if hl.seen == 0 || hl.seen == hl.nlink {
+ dout!("HardlinkState complete with seen {}", hl.seen);
+ continue;
+ }
+ dout!(
+ "pending HardlinkState with seen {} != nlinks {}",
+ hl.seen,
+ hl.nlink
+ );
+
+ while hl.names.len() > 0 {
+ let path = hl.names.pop().unwrap();
+ deferred_inpaths.push(path.infile);
+ }
+ // ensure that data segment gets added on archive_path recall
+ hl.nlink = hl.seen;
+ hl.seen = 0;
+ // existing allocated inode should be used
+ }
+ }
+
+ if deferred_inpaths.len() > 0 {
+ // rotate-right to match gnu ordering
+ deferred_inpaths.rotate_right(1);
+
+ // .reverse() to match gnu ordering
+ for p in deferred_inpaths.iter().rev() {
+ archive_path(state, props, p.as_path(), &mut writer)?;
+ }
+ }
+
+ Ok(())
+}
+
+fn archive_trailer<W: Write>(mut writer: W, cur_off: u64) -> std::io::Result<u64> {
+ let fname = "TRAILER!!!";
+ let fname_len = fname.len() + 1;
+
+ write!(
+ writer,
+ NEWC_HDR_FMT!(),
+ magic = "070701",
+ ino = 0,
+ mode = 0,
+ uid = 0,
+ gid = 0,
+ nlink = 1,
+ mtime = 0,
+ filesize = 0,
+ major = 0,
+ minor = 0,
+ rmajor = 0,
+ rminor = 0,
+ namesize = fname_len,
+ chksum = 0
+ )?;
+ let mut off: u64 = cur_off + NEWC_HDR_LEN;
+
+ let padding_len = archive_padlen(off + fname_len as u64, 4);
+ write!(
+ writer,
+ "{}\0{pad:.padlen$}",
+ fname,
+ padlen = padding_len as usize,
+ pad = "\0\0\0"
+ )?;
+ off += fname_len as u64 + padding_len as u64;
+
+ Ok(off)
+}
+
+fn archive_loop<R: BufRead, W: Seek + Write>(
+ mut reader: R,
+ mut writer: W,
+ props: &ArchiveProperties,
+) -> std::io::Result<u64> {
+ if props.data_align > 0 && (props.initial_data_off + u64::from(props.data_align)) % 4 != 0 {
+ // must satisfy both data_align and cpio 4-byte padding alignment
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "data alignment must be a multiple of 4",
+ ));
+ }
+
+ let mut state = ArchiveState::new(props.initial_ino);
+ loop {
+ let mut linebuf: Vec<u8> = Vec::new();
+ let mut r = reader.by_ref().take(PATH_MAX);
+ match r.read_until(props.list_separator, &mut linebuf) {
+ Ok(l) => {
+ if l == 0 {
+ break; // EOF
+ }
+ if l >= PATH_MAX.try_into().unwrap() {
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, "path too long"));
+ }
+ }
+ Err(e) => {
+ println!("read_until() failed: {}", e);
+ return Err(e);
+ }
+ };
+
+ // trim separator. len > 0 already checked.
+ let last_byte = linebuf.last().unwrap();
+ if *last_byte == props.list_separator {
+ linebuf.pop().unwrap();
+ if linebuf.len() == 0 {
+ continue;
+ }
+ } else {
+ println!(
+ "\'{:0x}\' ending not separator \'{:0x}\' terminated",
+ last_byte, props.list_separator
+ );
+ }
+
+ let linestr = OsStr::from_bytes(linebuf.as_slice());
+ let path = Path::new(linestr);
+ archive_path(&mut state, props, path, &mut writer)?;
+ }
+ archive_flush_unseen_hardlinks(&mut state, props, &mut writer)?;
+ state.off = archive_trailer(&mut writer, state.off)?;
+
+ // GNU cpio pads the end of an archive out to blocklen with zeros
+ let block_padlen = archive_padlen(state.off, 512);
+ if block_padlen > 0 {
+ let z = vec![0u8; block_padlen.try_into().unwrap()];
+ writer.write_all(&z)?;
+ state.off += block_padlen;
+ }
+ writer.flush()?;
+
+ Ok(state.off)
+}
+
+fn params_usage(params: &[Argument]) {
+ argument::print_help("dracut-cpio", "OUTPUT", params);
+ println!("\nExample: find fs-tree/ | dracut-cpio archive.cpio\n");
+}
+
+fn params_process(props: &mut ArchiveProperties) -> argument::Result<PathBuf> {
+ let params = &[
+ Argument::positional("OUTPUT", "Write cpio archive to this file path."),
+ Argument::value(
+ "data-align",
+ "ALIGNMENT",
+ "Attempt to pad archive to achieve ALIGNMENT for file data.",
+ ),
+ Argument::short_flag(
+ '0',
+ "null",
+ "Expect null delimeters in stdin filename list instead of newline.",
+ ),
+ Argument::value(
+ "mtime",
+ "EPOCH",
+ "Use EPOCH for archived mtime instead of filesystem reported values.",
+ ),
+ Argument::value(
+ "owner",
+ "UID:GID",
+ "Use UID and GID instead of filesystem reported owner values.",
+ ),
+ Argument::flag(
+ "truncate-existing",
+ "Truncate and overwrite any existing OUTPUT file, instead of appending.",
+ ),
+ Argument::short_flag('h', "help", "Print help message."),
+ ];
+
+ let mut positional_args = 0;
+ let args = env::args().skip(1); // skip binary name
+ let match_res = argument::set_arguments(args, params, |name, value| {
+ match name {
+ "" => positional_args += 1,
+ "data-align" => {
+ let v: u32 = value
+ .unwrap()
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("data-align must be an integer"),
+ })?;
+ if v > props.namesize_max {
+ println!(
+ concat!(
+ "Requested data-align {} larger than namesize maximum {}.",
+ " This will likely result in misalignment."
+ ),
+ v, props.namesize_max
+ );
+ }
+ props.data_align = v;
+ }
+ "null" => props.list_separator = b'\0',
+ "mtime" => {
+ let v: u32 = value
+ .unwrap()
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("mtime must be an integer"),
+ })?;
+ props.fixed_mtime = Some(v);
+ }
+ "owner" => {
+ let ugv_parsed: argument::Result<Vec<u32>> = value
+ .unwrap()
+ .split(':')
+ .map(|id| {
+ id.parse().map_err(|_| argument::Error::InvalidValue {
+ value: id.to_owned(),
+ expected: String::from("uid/gid must be an integer"),
+ })
+ })
+ .collect();
+
+ let ugv_parsed = ugv_parsed?;
+ if ugv_parsed.len() != 2 {
+ return Err(argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("owner must be UID:GID"),
+ });
+ }
+ props.fixed_uid = Some(ugv_parsed[0]);
+ props.fixed_gid = Some(ugv_parsed[1]);
+ }
+ "truncate-existing" => props.truncate_existing = true,
+ "help" => return Err(argument::Error::PrintHelp),
+ _ => unreachable!(),
+ };
+ Ok(())
+ });
+
+ match match_res {
+ Ok(_) => {
+ if positional_args != 1 {
+ params_usage(params);
+ return Err(argument::Error::ExpectedArgument(
+ "one OUTPUT parameter required".to_string(),
+ ));
+ }
+ }
+ Err(e) => {
+ params_usage(params);
+ return Err(e);
+ }
+ }
+
+ let last_arg = env::args_os().last().unwrap();
+ Ok(PathBuf::from(&last_arg))
+}
+
+fn main() -> std::io::Result<()> {
+ let mut props = ArchiveProperties::default();
+ let output_path = match params_process(&mut props) {
+ Ok(p) => p,
+ Err(argument::Error::PrintHelp) => return Ok(()),
+ Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidInput, e.to_string())),
+ };
+
+ let mut f = fs::OpenOptions::new()
+ .read(false)
+ .write(true)
+ .create(true)
+ .truncate(props.truncate_existing)
+ .open(&output_path)?;
+ if !props.truncate_existing {
+ props.initial_data_off = f.seek(io::SeekFrom::End(0))?;
+ }
+ let mut writer = io::BufWriter::new(f);
+
+ let stdin = std::io::stdin();
+ let mut reader = io::BufReader::new(stdin);
+
+ let _wrote = archive_loop(&mut reader, &mut writer, &props)?;
+
+ if props.initial_data_off > 0 {
+ dout!(
+ "appended {} bytes to archive {} at offset {}",
+ _wrote,
+ output_path.display(),
+ props.initial_data_off
+ );
+ } else {
+ dout!(
+ "wrote {} bytes to archive {}",
+ _wrote,
+ output_path.display()
+ );
+ }
+
+ Ok(())
+}
+
+// tests change working directory, so need to be run with:
+// cargo test -- --test-threads=1 --nocapture
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::cmp;
+ use std::os::unix::fs as unixfs;
+ use std::path::PathBuf;
+ use std::process::{Command, Stdio};
+
+ struct TempWorkDir {
+ prev_dir: PathBuf,
+ parent_tmp_dir: PathBuf,
+ cleanup_files: Vec<PathBuf>,
+ cleanup_dirs: Vec<PathBuf>,
+ ignore_cleanup: bool, // useful for debugging
+ }
+
+ impl TempWorkDir {
+ // create a temporary directory under CWD and cd into it.
+ // The directory will be cleaned up when twd goes out of scope.
+ pub fn new() -> TempWorkDir {
+ let mut buf = [0u8; 16];
+ let mut s = String::from("cpio-selftest-");
+ fs::File::open("/dev/urandom")
+ .unwrap()
+ .read_exact(&mut buf)
+ .unwrap();
+ for i in &buf {
+ s.push_str(&format!("{:02x}", i).to_string());
+ }
+ let mut twd = TempWorkDir {
+ prev_dir: env::current_dir().unwrap(),
+ parent_tmp_dir: {
+ let mut t = env::current_dir().unwrap().clone();
+ t.push(s);
+ println!("parent_tmp_dir: {}", t.display());
+ t
+ },
+ cleanup_files: Vec::new(),
+ cleanup_dirs: Vec::new(),
+ ignore_cleanup: false,
+ };
+ fs::create_dir(&twd.parent_tmp_dir).unwrap();
+ twd.cleanup_dirs.push(twd.parent_tmp_dir.clone());
+ env::set_current_dir(&twd.parent_tmp_dir).unwrap();
+
+ twd
+ }
+
+ pub fn create_tmp_file(&mut self, name: &str, len_bytes: u64) {
+ let mut bytes = len_bytes;
+ let f = fs::File::create(name).unwrap();
+ self.cleanup_files.push(PathBuf::from(name));
+ let mut writer = io::BufWriter::new(f);
+ let mut buf = [0u8; 512];
+
+ for (i, elem) in buf.iter_mut().enumerate() {
+ *elem = !(i & 0xFF) as u8;
+ }
+
+ while bytes > 0 {
+ let this_len = cmp::min(buf.len(), bytes.try_into().unwrap());
+ writer.write_all(&buf[0..this_len]).unwrap();
+ bytes -= this_len as u64;
+ }
+
+ writer.flush().unwrap();
+ }
+
+ pub fn create_tmp_dir(&mut self, name: &str) {
+ fs::create_dir(name).unwrap();
+ self.cleanup_dirs.push(PathBuf::from(name));
+ }
+
+ // execute coreutils mknod NAME TYPE [MAJOR MINOR]
+ pub fn create_tmp_mknod(&mut self, name: &str, typ: char,
+ maj_min: Option<(u32, u32)>) {
+ let t = typ.to_string();
+ let proc = match maj_min {
+ Some(maj_min) => {
+ let (maj, min) = maj_min;
+ Command::new("mknod")
+ .args(&[name, &t, &maj.to_string(), &min.to_string()])
+ .spawn()
+ },
+ None => Command::new("mknod").args(&[name, &t]).spawn()
+ };
+ let status = proc.expect("mknod failed to start").wait().unwrap();
+ assert!(status.success());
+
+ self.cleanup_files.push(PathBuf::from(name));
+ }
+ }
+
+ impl Drop for TempWorkDir {
+ fn drop(&mut self) {
+ for f in self.cleanup_files.iter().rev() {
+ if self.ignore_cleanup {
+ println!("ignoring cleanup of file {}", f.display());
+ continue;
+ }
+ println!("cleaning up test file at {}", f.display());
+ match fs::remove_file(f) {
+ Err(e) => println!("file removal failed {}", e),
+ Ok(_) => {}
+ };
+ }
+ for f in self.cleanup_dirs.iter().rev() {
+ if self.ignore_cleanup {
+ println!("ignoring cleanup of dir {}", f.display());
+ continue;
+ }
+ println!("cleaning up test dir at {}", f.display());
+ match fs::remove_dir(f) {
+ Err(e) => println!("dir removal failed {}", e),
+ Ok(_) => {}
+ };
+ }
+ println!("returning cwd to {}", self.prev_dir.display());
+ env::set_current_dir(self.prev_dir.as_path()).unwrap();
+ }
+ }
+
+ fn gnu_cpio_create(stdinput: &[u8], out: &str) {
+ let mut proc = Command::new("cpio")
+ .args(&["--quiet", "-o", "-H", "newc", "--reproducible", "-F", out])
+ .stdin(Stdio::piped())
+ .spawn()
+ .expect("GNU cpio failed to start");
+ {
+ let mut stdin = proc.stdin.take().unwrap();
+ stdin.write_all(stdinput).expect("Failed to write to stdin");
+ }
+
+ let status = proc.wait().unwrap();
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_empty_file() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 0);
+
+ gnu_cpio_create("file.txt\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ // use dracut-cpio to archive file.txt
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("file.txt\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_small_file() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 33);
+
+ gnu_cpio_create("file.txt\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("file.txt\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 2 + 33);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_prefixed_path() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 0);
+
+ gnu_cpio_create("./file.txt\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("./file.txt\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_absolute_path() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 0);
+
+ let canon_path = fs::canonicalize("file.txt").unwrap();
+ let mut canon_file_list = canon_path.into_os_string();
+ canon_file_list.push("\n");
+
+ gnu_cpio_create(canon_file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(canon_file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_dir() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_dir("dir");
+
+ gnu_cpio_create("dir\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("dir\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_dir_file() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_dir("dir");
+ twd.create_tmp_file("dir/file.txt", 512 * 32);
+ let file_list: &str = "dir\n\ndir/file.txt\n"; // double separator
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 512 * 32);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_dot_path() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_dir("dir");
+ twd.create_tmp_file("dir/file.txt", 512 * 32);
+ let file_list: &str = ".\ndir\ndir/file.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 4 + 512 * 32);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_dot_slash_path() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_dir("dir");
+ twd.create_tmp_file("dir/file.txt", 512 * 32);
+ let file_list: &str = "./\ndir\ndir/file.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 4 + 512 * 32);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_symlink() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 0);
+ unixfs::symlink("file.txt", "symlink.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("symlink.txt"));
+
+ gnu_cpio_create("file.txt\nsymlink.txt\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("file.txt\nsymlink.txt\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_fifo() {
+ let mut twd = TempWorkDir::new();
+
+ twd.create_tmp_mknod("fifo", 'p', None);
+
+ gnu_cpio_create("fifo\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("fifo\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_char() {
+ let mut twd = TempWorkDir::new();
+
+ gnu_cpio_create("/dev/zero\n".as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("/dev/zero\n".as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert_eq!(wrote, 512);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_data_align() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_dir("dir");
+ twd.create_tmp_file("dir/file.txt", 1024 * 1024); // 1M
+
+ twd.create_tmp_dir("extractor");
+ let f = fs::File::create("extractor/dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new("dir\ndir/file.txt\n".as_bytes());
+ // 4k cpio data segment alignment injects zeros after filename nullterm
+ let wrote = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties {
+ data_align: 4096,
+ ..ArchiveProperties::default()
+ },
+ )
+ .unwrap();
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 1024 * 1024);
+
+ // check 4k data segment alignment
+ let mut proc = Command::new("diff")
+ .args(&["dir/file.txt", "-"])
+ .stdin(Stdio::piped())
+ .spawn()
+ .expect("diff failed to start");
+ {
+ let f = fs::File::open("extractor/dracut.cpio").unwrap();
+ let mut reader = io::BufReader::new(f);
+ reader.seek(io::SeekFrom::Start(4096)).unwrap();
+ let mut take = reader.take(1024 * 1024 as u64);
+ let mut stdin = proc.stdin.take().unwrap();
+ let copied = io::copy(&mut take, &mut stdin).unwrap();
+ assert_eq!(copied, 1024 * 1024);
+ }
+ let status = proc.wait().unwrap();
+ assert!(status.success());
+
+ // confirm that GNU cpio can extract fname-zeroed paths
+ let status = Command::new("cpio")
+ .current_dir("extractor")
+ .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dir/file.txt"));
+ twd.cleanup_dirs.push(PathBuf::from("extractor/dir"));
+
+ let status = Command::new("diff")
+ .args(&["dir/file.txt", "extractor/dir/file.txt"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_data_align_off() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_dir("dir1");
+ twd.create_tmp_dir("dir2");
+ twd.create_tmp_dir("dir3");
+ twd.create_tmp_file("dir1/file.txt", 514 * 1024);
+
+ twd.create_tmp_dir("extractor");
+ let data_before_cpio = [5u8; 16384 + 4];
+ let f = fs::File::create("extractor/dracut.cpio").unwrap();
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dracut.cpio"));
+ let mut writer = io::BufWriter::new(f);
+ writer.write_all(&data_before_cpio).unwrap();
+ let mut reader = io::BufReader::new("dir1\ndir2\ndir3\ndir1/file.txt\n".as_bytes());
+ let wrote = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties {
+ data_align: 4096,
+ initial_data_off: data_before_cpio.len() as u64,
+ ..ArchiveProperties::default()
+ },
+ )
+ .unwrap();
+ assert!(wrote > NEWC_HDR_LEN * 5 + 514 * 1024);
+
+ let mut proc = Command::new("diff")
+ .args(&["dir1/file.txt", "-"])
+ .stdin(Stdio::piped())
+ .spawn()
+ .expect("diff failed to start");
+ {
+ let f = fs::File::open("extractor/dracut.cpio").unwrap();
+ let mut reader = io::BufReader::new(f);
+ reader.seek(io::SeekFrom::Start(16384 + 4096)).unwrap();
+ let mut take = reader.take(514 * 1024 as u64);
+ let mut stdin = proc.stdin.take().unwrap();
+ let copied = io::copy(&mut take, &mut stdin).unwrap();
+ assert_eq!(copied, 514 * 1024);
+ }
+ let status = proc.wait().unwrap();
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_data_align_off_bad() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 514 * 1024);
+
+ let data_before_cpio = [5u8; 16384 + 3];
+ let f = fs::File::create("dracut.cpio").unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ let mut writer = io::BufWriter::new(f);
+ writer.write_all(&data_before_cpio).unwrap();
+ let mut reader = io::BufReader::new("file.txt\n".as_bytes());
+ let res = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties {
+ data_align: 4096,
+ initial_data_off: data_before_cpio.len() as u64,
+ ..ArchiveProperties::default()
+ },
+ );
+ assert!(res.is_err());
+ assert_eq!(io::ErrorKind::InvalidInput, res.unwrap_err().kind());
+ }
+
+ #[test]
+ fn test_archive_hardlinks_order() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 512 * 4);
+ fs::hard_link("file.txt", "link1.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link1.txt"));
+ fs::hard_link("file.txt", "link2.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link2.txt"));
+ twd.create_tmp_file("another.txt", 512 * 4);
+ let file_list: &str = "file.txt\nanother.txt\nlink1.txt\nlink2.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 5 + 512 * 8);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_hardlinks_empty() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 0);
+ fs::hard_link("file.txt", "link1.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link1.txt"));
+ fs::hard_link("file.txt", "link2.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link2.txt"));
+ twd.create_tmp_file("another.txt", 512 * 4);
+ let file_list: &str = "file.txt\nanother.txt\nlink1.txt\nlink2.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 5 + 512 * 4);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_hardlinks_missing() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 512 * 4);
+ fs::hard_link("file.txt", "link1.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link1.txt"));
+ fs::hard_link("file.txt", "link2.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link2.txt"));
+ fs::hard_link("file.txt", "link3.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link3.txt"));
+ twd.create_tmp_file("another.txt", 512 * 4);
+ // link2 missing from the archive, throwing off deferrals
+ let file_list: &str = "file.txt\nanother.txt\nlink1.txt\nlink3.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 5 + 512 * 8);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_hardlinks_multi() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 512 * 4);
+ fs::hard_link("file.txt", "link1.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link1.txt"));
+ fs::hard_link("file.txt", "link2.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("link2.txt"));
+ twd.create_tmp_file("another.txt", 512 * 4);
+ fs::hard_link("another.txt", "anotherlink.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("anotherlink.txt"));
+ // link2 missing from the archive, throwing off deferrals
+ let file_list: &str = "file.txt\nanother.txt\nlink1.txt\nanotherlink.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 5 + 512 * 8);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_duplicates() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 512 * 4);
+ twd.create_tmp_file("another.txt", 512 * 4);
+ // file.txt is listed twice
+ let file_list: &str = "file.txt\nanother.txt\nfile.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 4 + 512 * 12);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_hardlink_duplicates() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file.txt", 512 * 4);
+ fs::hard_link("file.txt", "ln1.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("ln1.txt"));
+ fs::hard_link("file.txt", "ln2.txt").unwrap();
+ twd.cleanup_files.push(PathBuf::from("ln2.txt"));
+ twd.create_tmp_file("f2.txt", 512 * 4);
+ // ln1 listed twice
+ let file_list: &str = "file.txt\nf2.txt\nln1.txt\nln1.txt\nln1.txt\n";
+
+ gnu_cpio_create(file_list.as_bytes(), "gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 4 + 512 * 8);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_list_separator() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file1", 33);
+ twd.create_tmp_file("file2", 55);
+ let file_list_nulldelim: &str = "file1\0file2\0";
+
+ let mut proc = Command::new("cpio")
+ .args(&[
+ "--quiet",
+ "-o",
+ "-H",
+ "newc",
+ "--reproducible",
+ "-F",
+ "gnu.cpio",
+ "--null",
+ ])
+ .stdin(Stdio::piped())
+ .spawn()
+ .expect("GNU cpio failed to start");
+ {
+ let mut stdin = proc.stdin.take().unwrap();
+ stdin
+ .write_all(file_list_nulldelim.as_bytes())
+ .expect("Failed to write to stdin");
+ }
+
+ let status = proc.wait().unwrap();
+ assert!(status.success());
+ twd.cleanup_files.push(PathBuf::from("gnu.cpio"));
+
+ let f = fs::File::create("dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list_nulldelim.as_bytes());
+ let wrote = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties {
+ list_separator: b'\0',
+ ..ArchiveProperties::default()
+ },
+ )
+ .unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 33 + 55);
+
+ let status = Command::new("diff")
+ .args(&["gnu.cpio", "dracut.cpio"])
+ .status()
+ .expect("diff failed to start");
+ assert!(status.success());
+ }
+
+ #[test]
+ fn test_archive_fixed_mtime() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file1", 33);
+ twd.create_tmp_file("file2", 55);
+ let file_list: &str = "file1\nfile2\n";
+
+ twd.create_tmp_dir("extractor");
+ let f = fs::File::create("extractor/dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties {
+ fixed_mtime: Some(0),
+ ..ArchiveProperties::default()
+ },
+ )
+ .unwrap();
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 33 + 55);
+
+ let status = Command::new("cpio")
+ .current_dir("extractor")
+ .args(&[
+ "--quiet",
+ "-i",
+ "--preserve-modification-time",
+ "-H",
+ "newc",
+ "-F",
+ "dracut.cpio",
+ ])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ twd.cleanup_files.push(PathBuf::from("extractor/file1"));
+ twd.cleanup_files.push(PathBuf::from("extractor/file2"));
+
+ let md = fs::symlink_metadata("extractor/file1").unwrap();
+ assert_eq!(md.mtime(), 0);
+ let md = fs::symlink_metadata("extractor/file2").unwrap();
+ assert_eq!(md.mtime(), 0);
+ }
+
+ #[test]
+ fn test_archive_stat_mtime() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file1", 33);
+ twd.create_tmp_file("file2", 55);
+ let file_list: &str = "file1\nfile2\n";
+
+ twd.create_tmp_dir("extractor");
+ let f = fs::File::create("extractor/dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ assert_eq!(ArchiveProperties::default().fixed_mtime, None);
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 33 + 55);
+
+ let status = Command::new("cpio")
+ .current_dir("extractor")
+ .args(&[
+ "--quiet",
+ "-i",
+ "--preserve-modification-time",
+ "-H",
+ "newc",
+ "-F",
+ "dracut.cpio",
+ ])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ twd.cleanup_files.push(PathBuf::from("extractor/file1"));
+ twd.cleanup_files.push(PathBuf::from("extractor/file2"));
+
+ let src_md = fs::symlink_metadata("file1").unwrap();
+ let ex_md = fs::symlink_metadata("extractor/file1").unwrap();
+ assert_eq!(src_md.mtime(), ex_md.mtime());
+ let src_md = fs::symlink_metadata("file2").unwrap();
+ let ex_md = fs::symlink_metadata("extractor/file2").unwrap();
+ assert_eq!(src_md.mtime(), ex_md.mtime());
+ }
+
+ #[test]
+ fn test_archive_fixed_owner() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file1", 33);
+ twd.create_tmp_file("file2", 55);
+ let file_list: &str = "file1\nfile2\n";
+
+ let md = fs::symlink_metadata("file1").unwrap();
+ // ideally we should check the process euid, but this will do...
+ if md.uid() != 0 {
+ println!("SKIPPED: this test requires root");
+ return;
+ }
+
+ twd.create_tmp_dir("extractor");
+ let f = fs::File::create("extractor/dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties {
+ fixed_uid: Some(65534),
+ fixed_gid: Some(65534),
+ ..ArchiveProperties::default()
+ },
+ )
+ .unwrap();
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 33 + 55);
+
+ let status = Command::new("cpio")
+ .current_dir("extractor")
+ .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ twd.cleanup_files.push(PathBuf::from("extractor/file1"));
+ twd.cleanup_files.push(PathBuf::from("extractor/file2"));
+
+ let md = fs::symlink_metadata("extractor/file1").unwrap();
+ assert_eq!(md.uid(), 65534);
+ assert_eq!(md.gid(), 65534);
+ let md = fs::symlink_metadata("extractor/file2").unwrap();
+ assert_eq!(md.uid(), 65534);
+ assert_eq!(md.gid(), 65534);
+ }
+
+ #[test]
+ fn test_archive_stat_owner() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file1", 33);
+ twd.create_tmp_file("file2", 55);
+ let file_list: &str = "file1\nfile2\n";
+
+ twd.create_tmp_dir("extractor");
+ let f = fs::File::create("extractor/dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ assert_eq!(ArchiveProperties::default().fixed_uid, None);
+ assert_eq!(ArchiveProperties::default().fixed_gid, None);
+ let wrote = archive_loop(&mut reader, &mut writer, &ArchiveProperties::default()).unwrap();
+ twd.cleanup_files
+ .push(PathBuf::from("extractor/dracut.cpio"));
+ assert!(wrote > NEWC_HDR_LEN * 3 + 33 + 55);
+
+ let status = Command::new("cpio")
+ .current_dir("extractor")
+ .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ twd.cleanup_files.push(PathBuf::from("extractor/file1"));
+ twd.cleanup_files.push(PathBuf::from("extractor/file2"));
+
+ let src_md = fs::symlink_metadata("file1").unwrap();
+ let ex_md = fs::symlink_metadata("extractor/file1").unwrap();
+ assert_eq!(src_md.uid(), ex_md.uid());
+ assert_eq!(src_md.gid(), ex_md.gid());
+ let src_md = fs::symlink_metadata("file2").unwrap();
+ let ex_md = fs::symlink_metadata("extractor/file2").unwrap();
+ assert_eq!(src_md.uid(), ex_md.uid());
+ assert_eq!(src_md.gid(), ex_md.gid());
+ }
+
+ #[test]
+ fn test_archive_dev_maj_min() {
+ let mut twd = TempWorkDir::new();
+ twd.create_tmp_file("file1", 0);
+
+ let md = fs::symlink_metadata("file1").unwrap();
+ if md.uid() != 0 {
+ println!("SKIPPED: this test requires root");
+ return;
+ }
+
+ twd.create_tmp_mknod("bdev1", 'b', Some((0x01, 0x01)));
+ twd.create_tmp_mknod("bdev2", 'b', Some((0x02, 0x100)));
+ twd.create_tmp_mknod("bdev3", 'b', Some((0x03, 0x1000)));
+ twd.create_tmp_mknod("bdev4", 'b', Some((0x04, 0x10000)));
+ twd.create_tmp_mknod("bdev5", 'b', Some((0x100, 0x05)));
+ twd.create_tmp_mknod("bdev6", 'b', Some((0x100, 0x06)));
+ let file_list: &str = "file1\nbdev1\nbdev2\nbdev3\nbdev4\nbdev5\nbdev6\n";
+
+ // create GNU cpio archive
+ twd.create_tmp_dir("gnucpio_xtr");
+ gnu_cpio_create(file_list.as_bytes(), "gnucpio_xtr/gnu.cpio");
+ twd.cleanup_files.push(PathBuf::from("gnucpio_xtr/gnu.cpio"));
+
+ // create Dracut cpio archive
+ twd.create_tmp_dir("dracut_xtr");
+ let f = fs::File::create("dracut_xtr/dracut.cpio").unwrap();
+ let mut writer = io::BufWriter::new(f);
+ let mut reader = io::BufReader::new(file_list.as_bytes());
+ let wrote = archive_loop(
+ &mut reader,
+ &mut writer,
+ &ArchiveProperties::default()
+ )
+ .unwrap();
+ twd.cleanup_files.push(PathBuf::from("dracut_xtr/dracut.cpio"));
+
+ let file_list_count = file_list.split_terminator('\n').count() as u64;
+ assert!(wrote >= NEWC_HDR_LEN * file_list_count
+ + (file_list.len() as u64));
+
+ let status = Command::new("cpio")
+ .current_dir("gnucpio_xtr")
+ .args(&["--quiet", "-i", "-H", "newc", "-F", "gnu.cpio"])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ for s in file_list.split_terminator('\n') {
+ let p = PathBuf::from("gnucpio_xtr/".to_owned() + s);
+ twd.cleanup_files.push(p);
+ }
+
+ let status = Command::new("cpio")
+ .current_dir("dracut_xtr")
+ .args(&["--quiet", "-i", "-H", "newc", "-F", "dracut.cpio"])
+ .status()
+ .expect("GNU cpio failed to start");
+ assert!(status.success());
+ for s in file_list.split_terminator('\n') {
+ let dp = PathBuf::from("dracut_xtr/".to_owned() + s);
+ twd.cleanup_files.push(dp);
+ }
+
+ // diff extracted major/minor between dracut and GNU cpio created archives
+ for s in file_list.split_terminator('\n') {
+ let gmd = fs::symlink_metadata("gnucpio_xtr/".to_owned() + s).unwrap();
+ let dmd = fs::symlink_metadata("dracut_xtr/".to_owned() + s).unwrap();
+ print!("{}: cpio extracted dev_t gnu: {:#x}, dracut: {:#x}\n",
+ s, gmd.rdev(), dmd.rdev());
+ assert!(gmd.rdev() == dmd.rdev());
+ }
+ }
+}
diff --git a/src/dracut-cpio/third_party/crosvm/Cargo.toml b/src/dracut-cpio/third_party/crosvm/Cargo.toml
new file mode 100644
index 0000000..a56ff2d
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "crosvm"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+path = "crosvm.rs"
diff --git a/src/dracut-cpio/third_party/crosvm/LICENSE b/src/dracut-cpio/third_party/crosvm/LICENSE
new file mode 100644
index 0000000..8bafca3
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/dracut-cpio/third_party/crosvm/argument.rs b/src/dracut-cpio/third_party/crosvm/argument.rs
new file mode 100644
index 0000000..a2dbbbd
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/argument.rs
@@ -0,0 +1,549 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Handles argument parsing.
+//!
+//! # Example
+//!
+//! ```
+//! # use crosvm::argument::{Argument, Error, print_help, set_arguments};
+//! # let args: std::slice::Iter<String> = [].iter();
+//! let arguments = &[
+//! Argument::positional("FILES", "files to operate on"),
+//! Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
+//! Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
+//! Argument::flag("unmount", "Unmount the root"),
+//! Argument::short_flag('h', "help", "Print help message."),
+//! ];
+//!
+//! let match_res = set_arguments(args, arguments, |name, value| {
+//! match name {
+//! "" => println!("positional arg! {}", value.unwrap()),
+//! "program" => println!("gonna use program {}", value.unwrap()),
+//! "cpus" => {
+//! let v: u32 = value.unwrap().parse().map_err(|_| {
+//! Error::InvalidValue {
+//! value: value.unwrap().to_owned(),
+//! expected: String::from("this value for `cpus` needs to be integer"),
+//! }
+//! })?;
+//! }
+//! "unmount" => println!("gonna unmount"),
+//! "help" => return Err(Error::PrintHelp),
+//! _ => unreachable!(),
+//! }
+//! unreachable!();
+//! });
+//!
+//! match match_res {
+//! Ok(_) => println!("running with settings"),
+//! Err(Error::PrintHelp) => print_help("best_program", "FILES", arguments),
+//! Err(e) => println!("{}", e),
+//! }
+//! ```
+
+use std::fmt::{self, Display};
+use std::result;
+
+/// An error with argument parsing.
+#[derive(Debug)]
+pub enum Error {
+ /// There was a syntax error with the argument.
+ Syntax(String),
+ /// The argument's name is unused.
+ UnknownArgument(String),
+ /// The argument was required.
+ ExpectedArgument(String),
+ /// The argument's given value is invalid.
+ InvalidValue { value: String, expected: String },
+ /// The argument was already given and none more are expected.
+ TooManyArguments(String),
+ /// The argument expects a value.
+ ExpectedValue(String),
+ /// The argument does not expect a value.
+ UnexpectedValue(String),
+ /// The help information was requested
+ PrintHelp,
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ match self {
+ Syntax(s) => write!(f, "syntax error: {}", s),
+ UnknownArgument(s) => write!(f, "unknown argument: {}", s),
+ ExpectedArgument(s) => write!(f, "expected argument: {}", s),
+ InvalidValue { value, expected } => {
+ write!(f, "invalid value {:?}: {}", value, expected)
+ }
+ TooManyArguments(s) => write!(f, "too many arguments: {}", s),
+ ExpectedValue(s) => write!(f, "expected parameter value: {}", s),
+ UnexpectedValue(s) => write!(f, "unexpected parameter value: {}", s),
+ PrintHelp => write!(f, "help was requested"),
+ }
+ }
+}
+
+/// Result of a argument parsing.
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(Debug, PartialEq)]
+pub enum ArgumentValueMode {
+ /// Specifies that an argument requires a value and that an error should be generated if
+ /// no value is provided during parsing.
+ Required,
+
+ /// Specifies that an argument does not allow a value and that an error should be returned
+ /// if a value is provided during parsing.
+ Disallowed,
+
+ /// Specifies that an argument may have a value during parsing but is not required to.
+ Optional,
+}
+
+/// Information about an argument expected from the command line.
+///
+/// # Examples
+///
+/// To indicate a flag style argument:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// Argument::short_flag('f', "flag", "enable awesome mode");
+/// ```
+///
+/// To indicate a parameter style argument that expects a value:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
+/// // arguments.
+/// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information");
+/// Argument::value("netmask", "NETMASK", "hides your netface");
+/// ```
+///
+/// To indicate an argument with no short version:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// Argument::flag("verbose", "this option is hard to type quickly");
+/// ```
+///
+/// To indicate a positional argument:
+///
+/// ```
+/// # use crosvm::argument::Argument;
+/// Argument::positional("VALUES", "these are positional arguments");
+/// ```
+pub struct Argument {
+ /// The name of the value to display in the usage information.
+ pub value: Option<&'static str>,
+ /// Specifies how values should be handled for this this argument.
+ pub value_mode: ArgumentValueMode,
+ /// Optional single character shortened argument name.
+ pub short: Option<char>,
+ /// The long name of this argument.
+ pub long: &'static str,
+ /// Helpful usage information for this argument to display to the user.
+ pub help: &'static str,
+}
+
+impl Argument {
+ pub fn positional(value: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Required,
+ short: None,
+ long: "",
+ help,
+ }
+ }
+
+ pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Required,
+ short: None,
+ long,
+ help,
+ }
+ }
+
+ pub fn short_value(
+ short: char,
+ long: &'static str,
+ value: &'static str,
+ help: &'static str,
+ ) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Required,
+ short: Some(short),
+ long,
+ help,
+ }
+ }
+
+ pub fn flag(long: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: None,
+ value_mode: ArgumentValueMode::Disallowed,
+ short: None,
+ long,
+ help,
+ }
+ }
+
+ pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: None,
+ value_mode: ArgumentValueMode::Disallowed,
+ short: Some(short),
+ long,
+ help,
+ }
+ }
+
+ pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
+ Argument {
+ value: Some(value),
+ value_mode: ArgumentValueMode::Optional,
+ short: None,
+ long,
+ help,
+ }
+ }
+}
+
+fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
+where
+ I: Iterator<Item = R>,
+ R: AsRef<str>,
+ F: FnMut(&str, Option<&str>) -> Result<()>,
+{
+ enum State {
+ // Initial state at the start and after finishing a single argument/value.
+ Top,
+ // The remaining arguments are all positional.
+ Positional,
+ // The next string is the value for the argument `name`.
+ Value { name: String },
+ }
+ let mut s = State::Top;
+ for arg in args {
+ let arg = arg.as_ref();
+ loop {
+ let mut arg_consumed = true;
+ s = match s {
+ State::Top => {
+ if arg == "--" {
+ State::Positional
+ } else if arg.starts_with("--") {
+ let param = arg.trim_start_matches('-');
+ if param.contains('=') {
+ let mut iter = param.splitn(2, '=');
+ let name = iter.next().unwrap();
+ let value = iter.next().unwrap();
+ if name.is_empty() {
+ return Err(Error::Syntax(
+ "expected parameter name before `=`".to_owned(),
+ ));
+ }
+ if value.is_empty() {
+ return Err(Error::Syntax(
+ "expected parameter value after `=`".to_owned(),
+ ));
+ }
+ f(name, Some(value))?;
+ State::Top
+ } else {
+ State::Value {
+ name: param.to_owned(),
+ }
+ }
+ } else if arg.starts_with('-') {
+ if arg.len() == 1 {
+ return Err(Error::Syntax(
+ "expected argument short name after `-`".to_owned(),
+ ));
+ }
+ let name = &arg[1..2];
+ let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
+ if let Err(e) = f(name, value) {
+ if let Error::ExpectedValue(_) = e {
+ State::Value {
+ name: name.to_owned(),
+ }
+ } else {
+ return Err(e);
+ }
+ } else {
+ State::Top
+ }
+ } else {
+ f("", Some(&arg))?;
+ State::Positional
+ }
+ }
+ State::Positional => {
+ f("", Some(&arg))?;
+ State::Positional
+ }
+ State::Value { name } => {
+ if arg.starts_with('-') {
+ arg_consumed = false;
+ f(&name, None)?;
+ } else if let Err(e) = f(&name, Some(&arg)) {
+ arg_consumed = false;
+ f(&name, None).map_err(|_| e)?;
+ }
+ State::Top
+ }
+ };
+
+ if arg_consumed {
+ break;
+ }
+ }
+ }
+
+ // If we ran out of arguments while parsing the last parameter, which may be either a
+ // value parameter or a flag, try to parse it as a flag. This will produce "missing value"
+ // error if the parameter is in fact a value parameter, which is the desired outcome.
+ match s {
+ State::Value { name } => f(&name, None),
+ _ => Ok(()),
+ }
+}
+
+/// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
+/// present argument and value if required.
+///
+/// This function guarantees that only valid long argument names from `arg_list` are sent to the
+/// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
+/// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
+/// returns `Err`, this function will end parsing and return that `Err`.
+///
+/// See the [module level](index.html) example for a usage example.
+pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
+where
+ I: Iterator<Item = R>,
+ R: AsRef<str>,
+ F: FnMut(&str, Option<&str>) -> Result<()>,
+{
+ parse_arguments(args, |name, value| {
+ let mut matches = None;
+ for arg in arg_list {
+ if let Some(short) = arg.short {
+ if name.len() == 1 && name.starts_with(short) {
+ if value.is_some() != arg.value.is_some() {
+ return Err(Error::ExpectedValue(short.to_string()));
+ }
+ matches = Some(arg.long);
+ }
+ }
+ if matches.is_none() && arg.long == name {
+ if value.is_none() && arg.value_mode == ArgumentValueMode::Required {
+ return Err(Error::ExpectedValue(arg.long.to_owned()));
+ }
+ if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed {
+ return Err(Error::UnexpectedValue(arg.long.to_owned()));
+ }
+ matches = Some(arg.long);
+ }
+ }
+ match matches {
+ Some(long) => f(long, value),
+ None => Err(Error::UnknownArgument(name.to_owned())),
+ }
+ })
+}
+
+/// Prints command line usage information to stdout.
+///
+/// Usage information is printed according to the help fields in `args` with a leading usage line.
+/// The usage line is of the format "`program_name` \[ARGUMENTS\] `required_arg`".
+pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
+ println!(
+ "Usage: {} {}{}\n",
+ program_name,
+ if args.is_empty() { "" } else { "[ARGUMENTS] " },
+ required_arg
+ );
+ if args.is_empty() {
+ return;
+ }
+ println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
+ for arg in args {
+ match arg.short {
+ Some(s) => print!(" -{}, ", s),
+ None => print!(" "),
+ }
+ if arg.long.is_empty() {
+ print!(" ");
+ } else {
+ print!("--");
+ }
+ print!("{:<12}", arg.long);
+ if let Some(v) = arg.value {
+ if arg.long.is_empty() {
+ print!(" ");
+ } else {
+ print!("=");
+ }
+ print!("{:<10}", v);
+ } else {
+ print!("{:<11}", "");
+ }
+ println!("{}", arg.help);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn request_help() {
+ let arguments = [Argument::short_flag('h', "help", "Print help message.")];
+
+ let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| {
+ match name {
+ "help" => return Err(Error::PrintHelp),
+ _ => unreachable!(),
+ };
+ });
+ match match_res {
+ Err(Error::PrintHelp) => {}
+ _ => unreachable!(),
+ }
+ }
+
+ #[test]
+ fn mixed_args() {
+ let arguments = [
+ Argument::positional("FILES", "files to operate on"),
+ Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
+ Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
+ Argument::flag("unmount", "Unmount the root"),
+ Argument::short_flag('h', "help", "Print help message."),
+ ];
+
+ let mut unmount = false;
+ let match_res = set_arguments(
+ ["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(),
+ &arguments[..],
+ |name, value| {
+ match name {
+ "" => assert_eq!(value.unwrap(), "file"),
+ "program" => assert_eq!(value.unwrap(), "hello"),
+ "cpus" => {
+ let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("this value for `cpus` needs to be integer"),
+ })?;
+ assert_eq!(c, 3);
+ }
+ "unmount" => unmount = true,
+ "help" => return Err(Error::PrintHelp),
+ _ => unreachable!(),
+ };
+ Ok(())
+ },
+ );
+ assert!(match_res.is_ok());
+ assert!(unmount);
+ }
+
+ #[test]
+ fn name_value_pair() {
+ let arguments = [Argument::short_value(
+ 'c',
+ "cpus",
+ "N",
+ "Number of CPUs to use. (default: 1)",
+ )];
+ let match_res = set_arguments(
+ ["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
+ &arguments[..],
+ |name, value| {
+ assert_eq!(name, "cpus");
+ assert_eq!(value, Some("5"));
+ Ok(())
+ },
+ );
+ assert!(match_res.is_ok());
+ let not_match_res = set_arguments(
+ ["-c", "5", "--cpus"].iter(),
+ &arguments[..],
+ |name, value| {
+ assert_eq!(name, "cpus");
+ assert_eq!(value, Some("5"));
+ Ok(())
+ },
+ );
+ assert!(not_match_res.is_err());
+ }
+
+ #[test]
+ fn flag_or_value() {
+ let run_case = |args| -> Option<String> {
+ let arguments = [
+ Argument::positional("FILES", "files to operate on"),
+ Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"),
+ Argument::flag("foo", "Enable foo."),
+ Argument::value("bar", "stuff", "Configure bar."),
+ ];
+
+ let mut gpu_value: Option<String> = None;
+ let match_res =
+ set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| {
+ match name {
+ "" => assert_eq!(value.unwrap(), "file1"),
+ "foo" => assert!(value.is_none()),
+ "bar" => assert_eq!(value.unwrap(), "stuff"),
+ "gpu" => match value {
+ Some(v) => match v {
+ "2D" | "3D" => {
+ gpu_value = Some(v.to_string());
+ }
+ _ => {
+ return Err(Error::InvalidValue {
+ value: v.to_string(),
+ expected: String::from("2D or 3D"),
+ })
+ }
+ },
+ None => {
+ gpu_value = None;
+ }
+ },
+ _ => unreachable!(),
+ };
+ Ok(())
+ });
+
+ assert!(match_res.is_ok());
+ gpu_value
+ };
+
+ // Used as flag and followed by positional
+ assert_eq!(run_case(["--gpu", "file1"].iter()), None);
+ // Used as flag and followed by flag
+ assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None);
+ // Used as flag and followed by value
+ assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None);
+
+ // Used as value and followed by positional
+ assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D");
+ // Used as value and followed by flag
+ assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D");
+ // Used as value and followed by value
+ assert_eq!(
+ run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(),
+ "2D"
+ );
+ }
+}
diff --git a/src/dracut-cpio/third_party/crosvm/crosvm.rs b/src/dracut-cpio/third_party/crosvm/crosvm.rs
new file mode 100644
index 0000000..ddccbfe
--- /dev/null
+++ b/src/dracut-cpio/third_party/crosvm/crosvm.rs
@@ -0,0 +1,2 @@
+// ensure that existing crosvm::argument imports work without modification...
+pub mod argument;
diff --git a/src/install/Makefile b/src/install/Makefile
new file mode 100644
index 0000000..5332f25
--- /dev/null
+++ b/src/install/Makefile
@@ -0,0 +1,7 @@
+all:
+ $(MAKE) -C ..
+
+clean:
+ $(MAKE) -C .. clean
+
+.PHONY: all clean
diff --git a/src/install/dracut-install.c b/src/install/dracut-install.c
new file mode 100644
index 0000000..485143a
--- /dev/null
+++ b/src/install/dracut-install.c
@@ -0,0 +1,2281 @@
+/* dracut-install.c -- install files and executables
+
+ Copyright (C) 2012 Harald Hoyer
+ Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+
+ This program is free software: you can redistribute it and/or modify
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define PROGRAM_VERSION_STRING "2"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <glob.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <libkmod.h>
+#include <fts.h>
+#include <regex.h>
+#include <sys/utsname.h>
+
+#include "log.h"
+#include "hashmap.h"
+#include "util.h"
+#include "strv.h"
+
+#define _asprintf(strp, fmt, ...) \
+ do { \
+ if (dracut_asprintf(strp, fmt, __VA_ARGS__) < 0) { \
+ log_error("Out of memory\n"); \
+ exit(EXIT_FAILURE); \
+ } \
+ } while (0)
+
+static bool arg_hmac = false;
+static bool arg_createdir = false;
+static int arg_loglevel = -1;
+static bool arg_optional = false;
+static bool arg_silent = false;
+static bool arg_all = false;
+static bool arg_module = false;
+static bool arg_modalias = false;
+static bool arg_resolvelazy = false;
+static bool arg_resolvedeps = false;
+static bool arg_hostonly = false;
+static bool no_xattr = false;
+static char *destrootdir = NULL;
+static char *sysrootdir = NULL;
+static size_t sysrootdirlen = 0;
+static char *kerneldir = NULL;
+static size_t kerneldirlen = 0;
+static char **firmwaredirs = NULL;
+static char **pathdirs;
+static char *ldd = NULL;
+static char *logdir = NULL;
+static char *logfile = NULL;
+FILE *logfile_f = NULL;
+static Hashmap *items = NULL;
+static Hashmap *items_failed = NULL;
+static Hashmap *modules_loaded = NULL;
+static Hashmap *modules_suppliers = NULL;
+static Hashmap *processed_suppliers = NULL;
+static regex_t mod_filter_path;
+static regex_t mod_filter_nopath;
+static regex_t mod_filter_symbol;
+static regex_t mod_filter_nosymbol;
+static regex_t mod_filter_noname;
+static bool arg_mod_filter_path = false;
+static bool arg_mod_filter_nopath = false;
+static bool arg_mod_filter_symbol = false;
+static bool arg_mod_filter_nosymbol = false;
+static bool arg_mod_filter_noname = false;
+
+static int dracut_install(const char *src, const char *dst, bool isdir, bool resolvedeps, bool hashdst);
+static int install_dependent_modules(struct kmod_ctx *ctx, struct kmod_list *modlist, Hashmap *suppliers_paths);
+
+static void item_free(char *i)
+{
+ assert(i);
+ free(i);
+}
+
+static inline void kmod_module_unrefp(struct kmod_module **p)
+{
+ if (*p)
+ kmod_module_unref(*p);
+}
+
+#define _cleanup_kmod_module_unref_ _cleanup_(kmod_module_unrefp)
+
+static inline void kmod_module_unref_listp(struct kmod_list **p)
+{
+ if (*p)
+ kmod_module_unref_list(*p);
+}
+
+#define _cleanup_kmod_module_unref_list_ _cleanup_(kmod_module_unref_listp)
+
+static inline void kmod_module_info_free_listp(struct kmod_list **p)
+{
+ if (*p)
+ kmod_module_info_free_list(*p);
+}
+
+#define _cleanup_kmod_module_info_free_list_ _cleanup_(kmod_module_info_free_listp)
+
+static inline void kmod_unrefp(struct kmod_ctx **p)
+{
+ kmod_unref(*p);
+}
+
+#define _cleanup_kmod_unref_ _cleanup_(kmod_unrefp)
+
+static inline void kmod_module_dependency_symbols_free_listp(struct kmod_list **p)
+{
+ if (*p)
+ kmod_module_dependency_symbols_free_list(*p);
+}
+
+#define _cleanup_kmod_module_dependency_symbols_free_list_ _cleanup_(kmod_module_dependency_symbols_free_listp)
+
+static inline void fts_closep(FTS **p)
+{
+ if (*p)
+ fts_close(*p);
+}
+
+#define _cleanup_fts_close_ _cleanup_(fts_closep)
+
+#define _cleanup_globfree_ _cleanup_(globfree)
+
+static inline void destroy_hashmap(Hashmap **hashmap)
+{
+ void *i = NULL;
+
+ while ((i = hashmap_steal_first(*hashmap)))
+ item_free(i);
+
+ hashmap_free(*hashmap);
+}
+
+#define _cleanup_destroy_hashmap_ _cleanup_(destroy_hashmap)
+
+static size_t dir_len(char const *file)
+{
+ size_t length;
+
+ if (!file)
+ return 0;
+
+ /* Strip the basename and any redundant slashes before it. */
+ for (length = strlen(file) - 1; 0 < length; length--)
+ if (file[length] == '/' && file[length - 1] != '/')
+ break;
+ return length;
+}
+
+static char *convert_abs_rel(const char *from, const char *target)
+{
+ /* we use the 4*MAXPATHLEN, which should not overrun */
+ char buf[MAXPATHLEN * 4];
+ _cleanup_free_ char *realtarget = NULL, *realfrom = NULL, *from_dir_p = NULL;
+ _cleanup_free_ char *target_dir_p = NULL;
+ size_t level = 0, fromlevel = 0, targetlevel = 0;
+ int l;
+ size_t i, rl, dirlen;
+
+ dirlen = dir_len(from);
+ from_dir_p = strndup(from, dirlen);
+ if (!from_dir_p)
+ return strdup(from + strlen(destrootdir));
+ if (realpath(from_dir_p, buf) == NULL) {
+ log_warning("convert_abs_rel(): from '%s' directory has no realpath: %m", from);
+ return strdup(from + strlen(destrootdir));
+ }
+ /* dir_len() skips double /'s e.g. //lib64, so we can't skip just one
+ * character - need to skip all leading /'s */
+ for (i = dirlen + 1; from[i] == '/'; ++i)
+ ;
+ _asprintf(&realfrom, "%s/%s", buf, from + i);
+
+ dirlen = dir_len(target);
+ target_dir_p = strndup(target, dirlen);
+ if (!target_dir_p)
+ return strdup(from + strlen(destrootdir));
+ if (realpath(target_dir_p, buf) == NULL) {
+ log_warning("convert_abs_rel(): target '%s' directory has no realpath: %m", target);
+ return strdup(from + strlen(destrootdir));
+ }
+
+ for (i = dirlen + 1; target[i] == '/'; ++i)
+ ;
+ _asprintf(&realtarget, "%s/%s", buf, target + i);
+
+ /* now calculate the relative path from <from> to <target> and
+ store it in <buf>
+ */
+ rl = 0;
+
+ /* count the pathname elements of realtarget */
+ for (targetlevel = 0, i = 0; realtarget[i]; i++)
+ if (realtarget[i] == '/')
+ targetlevel++;
+
+ /* count the pathname elements of realfrom */
+ for (fromlevel = 0, i = 0; realfrom[i]; i++)
+ if (realfrom[i] == '/')
+ fromlevel++;
+
+ /* count the pathname elements, which are common for both paths */
+ for (level = 0, i = 0; realtarget[i] && (realtarget[i] == realfrom[i]); i++)
+ if (realtarget[i] == '/')
+ level++;
+
+ /* add "../" to the buf path, until the common pathname is
+ reached */
+ for (i = level; i < targetlevel; i++) {
+ if (i != level)
+ buf[rl++] = '/';
+ buf[rl++] = '.';
+ buf[rl++] = '.';
+ }
+
+ /* set l to the next uncommon pathname element in realfrom */
+ for (l = 1, i = 1; i < level; i++)
+ for (l++; realfrom[l] && realfrom[l] != '/'; l++) ;
+ /* skip next '/' */
+ l++;
+
+ /* append the uncommon rest of realfrom to the buf path */
+ for (i = level; i <= fromlevel; i++) {
+ if (rl)
+ buf[rl++] = '/';
+ while (realfrom[l] && realfrom[l] != '/')
+ buf[rl++] = realfrom[l++];
+ l++;
+ }
+
+ buf[rl] = 0;
+ return strdup(buf);
+}
+
+static int ln_r(const char *src, const char *dst)
+{
+ int ret;
+ _cleanup_free_ const char *points_to = convert_abs_rel(src, dst);
+
+ log_info("ln -s '%s' '%s'", points_to, dst);
+ ret = symlink(points_to, dst);
+
+ if (ret != 0) {
+ log_error("ERROR: ln -s '%s' '%s': %m", points_to, dst);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Perform the O(1) btrfs clone operation, if possible.
+ Upon success, return 0. Otherwise, return -1 and set errno. */
+static inline int clone_file(int dest_fd, int src_fd)
+{
+#undef BTRFS_IOCTL_MAGIC
+#define BTRFS_IOCTL_MAGIC 0x94
+#undef BTRFS_IOC_CLONE
+#define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int)
+ return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd);
+}
+
+static bool use_clone = true;
+
+static int cp(const char *src, const char *dst)
+{
+ int pid;
+ int ret = 0;
+
+ if (use_clone) {
+ struct stat sb;
+ _cleanup_close_ int dest_desc = -1, source_desc = -1;
+
+ if (lstat(src, &sb) != 0)
+ goto normal_copy;
+
+ if (S_ISLNK(sb.st_mode))
+ goto normal_copy;
+
+ source_desc = open(src, O_RDONLY | O_CLOEXEC);
+ if (source_desc < 0)
+ goto normal_copy;
+
+ dest_desc =
+ open(dst, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
+ (sb.st_mode) & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO));
+
+ if (dest_desc < 0) {
+ goto normal_copy;
+ }
+
+ ret = clone_file(dest_desc, source_desc);
+
+ if (ret == 0) {
+ struct timeval tv[2];
+ if (fchown(dest_desc, sb.st_uid, sb.st_gid) != 0)
+ if (fchown(dest_desc, (uid_t) - 1, sb.st_gid) != 0) {
+ if (geteuid() == 0)
+ log_error("Failed to chown %s: %m", dst);
+ else
+ log_info("Failed to chown %s: %m", dst);
+ }
+
+ tv[0].tv_sec = sb.st_atime;
+ tv[0].tv_usec = 0;
+ tv[1].tv_sec = sb.st_mtime;
+ tv[1].tv_usec = 0;
+ futimes(dest_desc, tv);
+ return ret;
+ }
+ close(dest_desc);
+ dest_desc = -1;
+ /* clone did not work, remove the file */
+ unlink(dst);
+ /* do not try clone again */
+ use_clone = false;
+ }
+
+normal_copy:
+ pid = fork();
+ const char *preservation = (geteuid() == 0
+ && no_xattr == false) ? "--preserve=mode,xattr,timestamps,ownership" : "--preserve=mode,timestamps,ownership";
+ if (pid == 0) {
+ execlp("cp", "cp", "--reflink=auto", "--sparse=auto", preservation, "-fL", src, dst, NULL);
+ _exit(errno == ENOENT ? 127 : 126);
+ }
+
+ while (waitpid(pid, &ret, 0) == -1) {
+ if (errno != EINTR) {
+ log_error("ERROR: waitpid() failed: %m");
+ return 1;
+ }
+ }
+ ret = WIFSIGNALED(ret) ? 128 + WTERMSIG(ret) : WEXITSTATUS(ret);
+ if (ret != 0)
+ log_error("ERROR: 'cp --reflink=auto --sparse=auto %s -fL %s %s' failed with %d", preservation, src, dst, ret);
+ log_debug("cp ret = %d", ret);
+ return ret;
+}
+
+static int library_install(const char *src, const char *lib)
+{
+ _cleanup_free_ char *p = NULL;
+ _cleanup_free_ char *pdir = NULL, *ppdir = NULL, *pppdir = NULL, *clib = NULL;
+ char *q, *clibdir;
+ int r, ret = 0;
+
+ r = dracut_install(lib, lib, false, false, true);
+ if (r != 0)
+ log_error("ERROR: failed to install '%s' for '%s'", lib, src);
+ else
+ log_debug("Lib install: '%s'", lib);
+ ret += r;
+
+ /* also install lib.so for lib.so.* files */
+ q = strstr(lib, ".so.");
+ if (q) {
+ p = strndup(lib, q - lib + 3);
+
+ /* ignore errors for base lib symlink */
+ if (dracut_install(p, p, false, false, true) == 0)
+ log_debug("Lib install: '%s'", p);
+
+ free(p);
+ }
+
+ /* Also try to install the same library from one directory above
+ * or from one directory above glibc-hwcaps.
+ This fixes the case, where only the HWCAP lib would be installed
+ # ldconfig -p|grep -F libc.so
+ libc.so.6 (libc6,64bit, hwcap: 0x0000001000000000, OS ABI: Linux 2.6.32) => /lib64/power6/libc.so.6
+ libc.so.6 (libc6,64bit, hwcap: 0x0000000000000200, OS ABI: Linux 2.6.32) => /lib64/power6x/libc.so.6
+ libc.so.6 (libc6,64bit, OS ABI: Linux 2.6.32) => /lib64/libc.so.6
+ */
+
+ p = strdup(lib);
+
+ pdir = dirname_malloc(p);
+ if (!pdir)
+ return ret;
+
+ ppdir = dirname_malloc(pdir);
+ /* only one parent directory, not HWCAP library */
+ if (!ppdir || streq(ppdir, "/"))
+ return ret;
+
+ pppdir = dirname_malloc(ppdir);
+ if (!pppdir)
+ return ret;
+
+ clibdir = streq(basename(ppdir), "glibc-hwcaps") ? pppdir : ppdir;
+ clib = strjoin(clibdir, "/", basename(p), NULL);
+ if (dracut_install(clib, clib, false, false, true) == 0)
+ log_debug("Lib install: '%s'", clib);
+ /* also install lib.so for lib.so.* files */
+ q = strstr(clib, ".so.");
+ if (q) {
+ q[3] = '\0';
+
+ /* ignore errors for base lib symlink */
+ if (dracut_install(clib, clib, false, false, true) == 0)
+ log_debug("Lib install: '%s'", p);
+ }
+
+ return ret;
+}
+
+static char *get_real_file(const char *src, bool fullyresolve)
+{
+ struct stat sb;
+ ssize_t linksz;
+ char linktarget[PATH_MAX + 1];
+ _cleanup_free_ char *fullsrcpath_a = NULL;
+ const char *fullsrcpath;
+ _cleanup_free_ char *abspath = NULL;
+
+ if (sysrootdirlen) {
+ if (strncmp(src, sysrootdir, sysrootdirlen) == 0) {
+ fullsrcpath = src;
+ } else {
+ _asprintf(&fullsrcpath_a, "%s/%s",
+ (sysrootdirlen ? sysrootdir : ""),
+ (src[0] == '/' ? src + 1 : src));
+ fullsrcpath = fullsrcpath_a;
+ }
+ } else {
+ fullsrcpath = src;
+ }
+
+ log_debug("get_real_file('%s')", fullsrcpath);
+
+ if (lstat(fullsrcpath, &sb) < 0)
+ return NULL;
+
+ switch (sb.st_mode & S_IFMT) {
+ case S_IFDIR:
+ case S_IFREG:
+ return strdup(fullsrcpath);
+ case S_IFLNK:
+ break;
+ default:
+ return NULL;
+ }
+
+ linksz = readlink(fullsrcpath, linktarget, sizeof(linktarget));
+ if (linksz < 0)
+ return NULL;
+ linktarget[linksz] = '\0';
+
+ log_debug("get_real_file: readlink('%s') returns '%s'", fullsrcpath, linktarget);
+
+ if (streq(fullsrcpath, linktarget)) {
+ log_error("ERROR: '%s' is pointing to itself", fullsrcpath);
+ return NULL;
+ }
+
+ if (linktarget[0] == '/') {
+ _asprintf(&abspath, "%s%s", (sysrootdirlen ? sysrootdir : ""), linktarget);
+ } else {
+ _asprintf(&abspath, "%.*s/%s", (int)dir_len(fullsrcpath), fullsrcpath, linktarget);
+ }
+
+ if (fullyresolve) {
+ struct stat st;
+ if (lstat(abspath, &st) < 0) {
+ if (errno != ENOENT) {
+ return NULL;
+ }
+ }
+ if (S_ISLNK(st.st_mode)) {
+ return get_real_file(abspath, fullyresolve);
+ }
+ }
+
+ log_debug("get_real_file('%s') => '%s'", src, abspath);
+ return TAKE_PTR(abspath);
+}
+
+static int resolve_deps(const char *src)
+{
+ int ret = 0, err;
+
+ _cleanup_free_ char *buf = NULL;
+ size_t linesize = LINE_MAX + 1;
+ _cleanup_free_ char *fullsrcpath = NULL;
+
+ fullsrcpath = get_real_file(src, true);
+ log_debug("resolve_deps('%s') -> get_real_file('%s', true) = '%s'", src, src, fullsrcpath);
+ if (!fullsrcpath)
+ return 0;
+
+ buf = malloc(linesize);
+ if (buf == NULL)
+ return -errno;
+
+ if (strstr(src, ".so") == NULL) {
+ _cleanup_close_ int fd = -1;
+ fd = open(fullsrcpath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ ret = read(fd, buf, linesize - 1);
+ if (ret == -1)
+ return -errno;
+
+ buf[ret] = '\0';
+ if (buf[0] == '#' && buf[1] == '!') {
+ /* we have a shebang */
+ char *p, *q;
+ for (p = &buf[2]; *p && isspace(*p); p++) ;
+ for (q = p; *q && (!isspace(*q)); q++) ;
+ *q = '\0';
+ log_debug("Script install: '%s'", p);
+ ret = dracut_install(p, p, false, true, false);
+ if (ret != 0)
+ log_error("ERROR: failed to install '%s'", p);
+ return ret;
+ }
+ }
+
+ int fds[2];
+ FILE *fptr;
+ if (pipe2(fds, O_CLOEXEC) == -1 || (fptr = fdopen(fds[0], "r")) == NULL) {
+ log_error("ERROR: pipe stream initialization for '%s' failed: %m", ldd);
+ exit(EXIT_FAILURE);
+ }
+
+ log_debug("%s %s", ldd, fullsrcpath);
+ pid_t ldd_pid;
+ if ((ldd_pid = fork()) == 0) {
+ dup2(fds[1], 1);
+ dup2(fds[1], 2);
+ putenv("LC_ALL=C");
+ execlp(ldd, ldd, fullsrcpath, (char *)NULL);
+ _exit(errno == ENOENT ? 127 : 126);
+ }
+ close(fds[1]);
+
+ ret = 0;
+
+ while (getline(&buf, &linesize, fptr) >= 0) {
+ char *p;
+
+ log_debug("ldd: '%s'", buf);
+
+ if (strstr(buf, "you do not have execution permission")) {
+ log_error("%s", buf);
+ ret += 1;
+ break;
+ }
+
+ /* errors from cross-compiler-ldd */
+ if (strstr(buf, "unable to find sysroot")) {
+ log_error("%s", buf);
+ ret += 1;
+ break;
+ }
+
+ /* musl ldd */
+ if (strstr(buf, "Not a valid dynamic program"))
+ break;
+
+ /* glibc */
+ if (strstr(buf, "cannot execute binary file"))
+ continue;
+
+ if (strstr(buf, "not a dynamic executable"))
+ break;
+
+ if (strstr(buf, "loader cannot load itself"))
+ break;
+
+ if (strstr(buf, "not regular file"))
+ break;
+
+ if (strstr(buf, "cannot read header"))
+ break;
+
+ if (strstr(buf, "cannot be preloaded"))
+ break;
+
+ if (strstr(buf, destrootdir))
+ break;
+
+ p = buf;
+ if (strchr(p, '$')) {
+ /* take ldd variable expansion into account */
+ p = strstr(p, "=>");
+ if (!p)
+ p = buf;
+ }
+ p = strchr(p, '/');
+
+ if (p) {
+ char *q;
+
+ for (q = p; *q && *q != ' ' && *q != '\n'; q++) ;
+ *q = '\0';
+
+ ret += library_install(src, p);
+
+ }
+ }
+
+ fclose(fptr);
+ while (waitpid(ldd_pid, &err, 0) == -1) {
+ if (errno != EINTR) {
+ log_error("ERROR: waitpid() failed: %m");
+ return 1;
+ }
+ }
+ err = WIFSIGNALED(err) ? 128 + WTERMSIG(err) : WEXITSTATUS(err);
+ /* ldd has error conditions we largely don't care about ("not a dynamic executable", &c.):
+ only error out on hard errors (ENOENT, ENOEXEC, signals) */
+ if (err >= 126) {
+ log_error("ERROR: '%s %s' failed with %d", ldd, fullsrcpath, err);
+ return err;
+ } else
+ return ret;
+}
+
+/* Install ".<filename>.hmac" file for FIPS self-checks */
+static int hmac_install(const char *src, const char *dst, const char *hmacpath)
+{
+ _cleanup_free_ char *srchmacname = NULL;
+ _cleanup_free_ char *dsthmacname = NULL;
+
+ size_t dlen = dir_len(src);
+
+ if (endswith(src, ".hmac"))
+ return 0;
+
+ if (!hmacpath) {
+ hmac_install(src, dst, "/lib/fipscheck");
+ hmac_install(src, dst, "/lib64/fipscheck");
+ hmac_install(src, dst, "/lib/hmaccalc");
+ hmac_install(src, dst, "/lib64/hmaccalc");
+ }
+
+ if (hmacpath) {
+ _asprintf(&srchmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]);
+ _asprintf(&dsthmacname, "%s/%s.hmac", hmacpath, &src[dlen + 1]);
+ } else {
+ _asprintf(&srchmacname, "%.*s/.%s.hmac", (int)dlen, src, &src[dlen + 1]);
+ _asprintf(&dsthmacname, "%.*s/.%s.hmac", (int)dir_len(dst), dst, &src[dlen + 1]);
+ }
+ log_debug("hmac cp '%s' '%s'", srchmacname, dsthmacname);
+ dracut_install(srchmacname, dsthmacname, false, false, true);
+ return 0;
+}
+
+void mark_hostonly(const char *path)
+{
+ _cleanup_free_ char *fulldstpath = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+
+ _asprintf(&fulldstpath, "%s/lib/dracut/hostonly-files", destrootdir);
+
+ f = fopen(fulldstpath, "a");
+
+ if (f == NULL) {
+ log_error("Could not open '%s' for writing.", fulldstpath);
+ return;
+ }
+
+ fprintf(f, "%s\n", path);
+}
+
+void dracut_log_cp(const char *path)
+{
+ int ret;
+ ret = fprintf(logfile_f, "%s\n", path);
+ if (ret < 0)
+ log_error("Could not append '%s' to logfile '%s': %m", path, logfile);
+}
+
+static bool check_hashmap(Hashmap *hm, const char *item)
+{
+ char *existing;
+ existing = hashmap_get(hm, item);
+ if (existing) {
+ if (strcmp(existing, item) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static int dracut_mkdir(const char *src)
+{
+ _cleanup_free_ char *parent = NULL;
+ char *path;
+ struct stat sb;
+
+ parent = strdup(src);
+ if (!parent)
+ return 1;
+
+ path = parent[0] == '/' ? parent + 1 : parent;
+ while (path) {
+ path = strstr(path, "/");
+ if (path)
+ *path = '\0';
+
+ if (stat(parent, &sb) == 0) {
+ if (!S_ISDIR(sb.st_mode)) {
+ log_error("%s exists but is not a directory!", parent);
+ return 1;
+ }
+ } else if (errno != ENOENT) {
+ log_error("ERROR: stat '%s': %m", parent);
+ return 1;
+ } else {
+ if (mkdir(parent, 0755) < 0) {
+ log_error("ERROR: mkdir '%s': %m", parent);
+ return 1;
+ }
+ }
+
+ if (path) {
+ *path = '/';
+ path++;
+ }
+ }
+
+ return 0;
+}
+
+static int dracut_install(const char *orig_src, const char *orig_dst, bool isdir, bool resolvedeps, bool hashdst)
+{
+ struct stat sb;
+ _cleanup_free_ char *fullsrcpath = NULL;
+ _cleanup_free_ char *fulldstpath = NULL;
+ _cleanup_free_ char *fulldstdir = NULL;
+ int ret;
+ bool src_islink = false;
+ bool src_isdir = false;
+ mode_t src_mode = 0;
+ bool dst_exists = true;
+ char *i = NULL;
+ const char *src, *dst;
+
+ if (sysrootdirlen) {
+ if (strncmp(orig_src, sysrootdir, sysrootdirlen) == 0) {
+ src = orig_src + sysrootdirlen;
+ fullsrcpath = strdup(orig_src);
+ } else {
+ src = orig_src;
+ _asprintf(&fullsrcpath, "%s%s", sysrootdir, src);
+ }
+ if (strncmp(orig_dst, sysrootdir, sysrootdirlen) == 0)
+ dst = orig_dst + sysrootdirlen;
+ else
+ dst = orig_dst;
+ } else {
+ src = orig_src;
+ fullsrcpath = strdup(src);
+ dst = orig_dst;
+ }
+
+ log_debug("dracut_install('%s', '%s', %d, %d, %d)", src, dst, isdir, resolvedeps, hashdst);
+
+ if (check_hashmap(items_failed, src)) {
+ log_debug("hash hit items_failed for '%s'", src);
+ return 1;
+ }
+
+ if (hashdst && check_hashmap(items, dst)) {
+ log_debug("hash hit items for '%s'", dst);
+ return 0;
+ }
+
+ if (lstat(fullsrcpath, &sb) < 0) {
+ if (!isdir) {
+ i = strdup(src);
+ hashmap_put(items_failed, i, i);
+ /* src does not exist */
+ return 1;
+ }
+ } else {
+ src_islink = S_ISLNK(sb.st_mode);
+ src_isdir = S_ISDIR(sb.st_mode);
+ src_mode = sb.st_mode;
+ }
+
+ _asprintf(&fulldstpath, "%s/%s", destrootdir, (dst[0] == '/' ? (dst + 1) : dst));
+
+ ret = stat(fulldstpath, &sb);
+ if (ret != 0) {
+ dst_exists = false;
+ if (errno != ENOENT) {
+ log_error("ERROR: stat '%s': %m", fulldstpath);
+ return 1;
+ }
+ }
+
+ if (ret == 0) {
+ if (resolvedeps && S_ISREG(sb.st_mode) && (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
+ log_debug("'%s' already exists, but checking for any deps", fulldstpath);
+ ret = resolve_deps(fulldstpath + sysrootdirlen);
+ } else
+ log_debug("'%s' already exists", fulldstpath);
+
+ /* dst does already exist */
+ } else {
+
+ /* check destination directory */
+ fulldstdir = strndup(fulldstpath, dir_len(fulldstpath));
+ if (!fulldstdir) {
+ log_error("Out of memory!");
+ return 1;
+ }
+
+ ret = access(fulldstdir, F_OK);
+
+ if (ret < 0) {
+ _cleanup_free_ char *dname = NULL;
+
+ if (errno != ENOENT) {
+ log_error("ERROR: stat '%s': %m", fulldstdir);
+ return 1;
+ }
+ /* create destination directory */
+ log_debug("dest dir '%s' does not exist", fulldstdir);
+
+ dname = strndup(dst, dir_len(dst));
+ if (!dname)
+ return 1;
+ ret = dracut_install(dname, dname, true, false, true);
+
+ if (ret != 0) {
+ log_error("ERROR: failed to create directory '%s'", fulldstdir);
+ return 1;
+ }
+ }
+
+ if (src_isdir) {
+ if (dst_exists) {
+ if (S_ISDIR(sb.st_mode)) {
+ log_debug("dest dir '%s' already exists", fulldstpath);
+ return 0;
+ }
+ log_error("dest dir '%s' already exists but is not a directory", fulldstpath);
+ return 1;
+ }
+
+ log_info("mkdir '%s'", fulldstpath);
+ ret = dracut_mkdir(fulldstpath);
+ if (ret == 0) {
+ i = strdup(dst);
+ if (!i)
+ return -ENOMEM;
+
+ hashmap_put(items, i, i);
+ }
+ return ret;
+ }
+
+ /* ready to install src */
+
+ if (src_islink) {
+ _cleanup_free_ char *abspath = NULL;
+
+ abspath = get_real_file(src, false);
+
+ if (abspath == NULL)
+ return 1;
+
+ if (dracut_install(abspath, abspath, false, resolvedeps, hashdst)) {
+ log_debug("'%s' install error", abspath);
+ return 1;
+ }
+
+ if (faccessat(AT_FDCWD, abspath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
+ log_debug("lstat '%s': %m", abspath);
+ return 1;
+ }
+
+ if (faccessat(AT_FDCWD, fulldstpath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
+ _cleanup_free_ char *absdestpath = NULL;
+
+ _asprintf(&absdestpath, "%s/%s", destrootdir,
+ (abspath[0] == '/' ? (abspath + 1) : abspath) + sysrootdirlen);
+
+ ln_r(absdestpath, fulldstpath);
+ }
+
+ if (arg_hmac) {
+ /* copy .hmac files also */
+ hmac_install(src, dst, NULL);
+ }
+
+ return 0;
+ }
+
+ if (src_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
+ if (resolvedeps)
+ ret += resolve_deps(fullsrcpath + sysrootdirlen);
+ if (arg_hmac) {
+ /* copy .hmac files also */
+ hmac_install(src, dst, NULL);
+ }
+ }
+
+ log_debug("dracut_install ret = %d", ret);
+
+ if (arg_hostonly && !arg_module)
+ mark_hostonly(dst);
+
+ if (isdir) {
+ log_info("mkdir '%s'", fulldstpath);
+ ret += dracut_mkdir(fulldstpath);
+ } else {
+ log_info("cp '%s' '%s'", fullsrcpath, fulldstpath);
+ ret += cp(fullsrcpath, fulldstpath);
+ }
+ }
+
+ if (ret == 0) {
+ i = strdup(dst);
+ if (!i)
+ return -ENOMEM;
+
+ hashmap_put(items, i, i);
+
+ if (logfile_f)
+ dracut_log_cp(src);
+ }
+
+ log_debug("dracut_install ret = %d", ret);
+
+ return ret;
+}
+
+static void usage(int status)
+{
+ /* */
+ printf("Usage: %s -D DESTROOTDIR [-r SYSROOTDIR] [OPTION]... -a SOURCE...\n"
+ "or: %s -D DESTROOTDIR [-r SYSROOTDIR] [OPTION]... SOURCE DEST\n"
+ "or: %s -D DESTROOTDIR [-r SYSROOTDIR] [OPTION]... -m KERNELMODULE [KERNELMODULE …]\n"
+ "\n"
+ "Install SOURCE (from rootfs or SYSROOTDIR) to DEST in DESTROOTDIR with all needed dependencies.\n"
+ "\n"
+ " KERNELMODULE can have the format:\n"
+ " <absolute path> with a leading /\n"
+ " =<kernel subdir>[/<kernel subdir>…] like '=drivers/hid'\n"
+ " <module name>\n"
+ "\n"
+ " -D --destrootdir Install all files to DESTROOTDIR as the root\n"
+ " -r --sysrootdir Install all files from SYSROOTDIR\n"
+ " -a --all Install all SOURCE arguments to DESTROOTDIR\n"
+ " -o --optional If SOURCE does not exist, do not fail\n"
+ " -d --dir SOURCE is a directory\n"
+ " -l --ldd Also install shebang executables and libraries\n"
+ " -L --logdir <DIR> Log files, which were installed from the host to <DIR>\n"
+ " -R --resolvelazy Only install shebang executables and libraries\n"
+ " for all SOURCE files\n"
+ " -H --hostonly Mark all SOURCE files as hostonly\n\n"
+ " -f --fips Also install all '.SOURCE.hmac' files\n"
+ "\n"
+ " --module,-m Install kernel modules, instead of files\n"
+ " --kerneldir Specify the kernel module directory\n"
+ " (default: /lib/modules/$(uname -r))\n"
+ " --firmwaredirs Specify the firmware directory search path with : separation\n"
+ " (default: $DRACUT_FIRMWARE_PATH, otherwise kernel-compatible\n"
+ " $(</sys/module/firmware_class/parameters/path),\n"
+ " /lib/firmware/updates/$(uname -r), /lib/firmware/updates\n"
+ " /lib/firmware/$(uname -r), /lib/firmware)\n"
+ " --silent Don't display error messages for kernel module install\n"
+ " --modalias Only generate module list from /sys/devices modalias list\n"
+ " -o --optional If kernel module does not exist, do not fail\n"
+ " -p --mod-filter-path Filter kernel modules by path regexp\n"
+ " -P --mod-filter-nopath Exclude kernel modules by path regexp\n"
+ " -s --mod-filter-symbol Filter kernel modules by symbol regexp\n"
+ " -S --mod-filter-nosymbol Exclude kernel modules by symbol regexp\n"
+ " -N --mod-filter-noname Exclude kernel modules by name regexp\n"
+ "\n"
+ " -v --verbose Show more output\n"
+ " --debug Show debug output\n"
+ " --version Show package version\n"
+ " -h --help Show this help\n"
+ "\n", program_invocation_short_name, program_invocation_short_name, program_invocation_short_name);
+ exit(status);
+}
+
+static int parse_argv(int argc, char *argv[])
+{
+ int c;
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_SILENT,
+ ARG_MODALIAS,
+ ARG_KERNELDIR,
+ ARG_FIRMWAREDIRS,
+ ARG_DEBUG
+ };
+
+ static struct option const options[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"version", no_argument, NULL, ARG_VERSION},
+ {"dir", no_argument, NULL, 'd'},
+ {"debug", no_argument, NULL, ARG_DEBUG},
+ {"verbose", no_argument, NULL, 'v'},
+ {"ldd", no_argument, NULL, 'l'},
+ {"resolvelazy", no_argument, NULL, 'R'},
+ {"optional", no_argument, NULL, 'o'},
+ {"hostonly", no_argument, NULL, 'H'},
+ {"all", no_argument, NULL, 'a'},
+ {"module", no_argument, NULL, 'm'},
+ {"fips", no_argument, NULL, 'f'},
+ {"destrootdir", required_argument, NULL, 'D'},
+ {"sysrootdir", required_argument, NULL, 'r'},
+ {"logdir", required_argument, NULL, 'L'},
+ {"mod-filter-path", required_argument, NULL, 'p'},
+ {"mod-filter-nopath", required_argument, NULL, 'P'},
+ {"mod-filter-symbol", required_argument, NULL, 's'},
+ {"mod-filter-nosymbol", required_argument, NULL, 'S'},
+ {"mod-filter-noname", required_argument, NULL, 'N'},
+ {"modalias", no_argument, NULL, ARG_MODALIAS},
+ {"silent", no_argument, NULL, ARG_SILENT},
+ {"kerneldir", required_argument, NULL, ARG_KERNELDIR},
+ {"firmwaredirs", required_argument, NULL, ARG_FIRMWAREDIRS},
+ {NULL, 0, NULL, 0}
+ };
+
+ while ((c = getopt_long(argc, argv, "madfhlL:oD:Hr:Rp:P:s:S:N:v", options, NULL)) != -1) {
+ switch (c) {
+ case ARG_VERSION:
+ puts(PROGRAM_VERSION_STRING);
+ return 0;
+ case 'd':
+ arg_createdir = true;
+ break;
+ case ARG_DEBUG:
+ arg_loglevel = LOG_DEBUG;
+ break;
+ case ARG_SILENT:
+ arg_silent = true;
+ break;
+ case ARG_MODALIAS:
+ arg_modalias = true;
+ return 1;
+ break;
+ case 'v':
+ arg_loglevel = LOG_INFO;
+ break;
+ case 'o':
+ arg_optional = true;
+ break;
+ case 'l':
+ arg_resolvedeps = true;
+ break;
+ case 'R':
+ arg_resolvelazy = true;
+ break;
+ case 'a':
+ arg_all = true;
+ break;
+ case 'm':
+ arg_module = true;
+ break;
+ case 'D':
+ destrootdir = optarg;
+ break;
+ case 'r':
+ sysrootdir = optarg;
+ sysrootdirlen = strlen(sysrootdir);
+ break;
+ case 'p':
+ if (regcomp(&mod_filter_path, optarg, REG_NOSUB | REG_EXTENDED) != 0) {
+ log_error("Module path filter %s is not a regular expression", optarg);
+ exit(EXIT_FAILURE);
+ }
+ arg_mod_filter_path = true;
+ break;
+ case 'P':
+ if (regcomp(&mod_filter_nopath, optarg, REG_NOSUB | REG_EXTENDED) != 0) {
+ log_error("Module path filter %s is not a regular expression", optarg);
+ exit(EXIT_FAILURE);
+ }
+ arg_mod_filter_nopath = true;
+ break;
+ case 's':
+ if (regcomp(&mod_filter_symbol, optarg, REG_NOSUB | REG_EXTENDED) != 0) {
+ log_error("Module symbol filter %s is not a regular expression", optarg);
+ exit(EXIT_FAILURE);
+ }
+ arg_mod_filter_symbol = true;
+ break;
+ case 'S':
+ if (regcomp(&mod_filter_nosymbol, optarg, REG_NOSUB | REG_EXTENDED) != 0) {
+ log_error("Module symbol filter %s is not a regular expression", optarg);
+ exit(EXIT_FAILURE);
+ }
+ arg_mod_filter_nosymbol = true;
+ break;
+ case 'N':
+ if (regcomp(&mod_filter_noname, optarg, REG_NOSUB | REG_EXTENDED) != 0) {
+ log_error("Module symbol filter %s is not a regular expression", optarg);
+ exit(EXIT_FAILURE);
+ }
+ arg_mod_filter_noname = true;
+ break;
+ case 'L':
+ logdir = optarg;
+ break;
+ case ARG_KERNELDIR:
+ kerneldir = optarg;
+ break;
+ case ARG_FIRMWAREDIRS:
+ firmwaredirs = strv_split(optarg, ":");
+ break;
+ case 'f':
+ arg_hmac = true;
+ break;
+ case 'H':
+ arg_hostonly = true;
+ break;
+ case 'h':
+ usage(EXIT_SUCCESS);
+ break;
+ default:
+ usage(EXIT_FAILURE);
+ }
+ }
+
+ if (arg_loglevel >= 0) {
+ log_set_max_level(arg_loglevel);
+ }
+
+ struct utsname buf = {0};
+ if (!kerneldir) {
+ uname(&buf);
+ _asprintf(&kerneldir, "/lib/modules/%s", buf.release);
+ }
+
+ if (arg_modalias) {
+ return 1;
+ }
+
+ if (arg_module) {
+ if (!firmwaredirs) {
+ char *path = getenv("DRACUT_FIRMWARE_PATH");
+
+ if (path) {
+ log_debug("DRACUT_FIRMWARE_PATH=%s", path);
+ firmwaredirs = strv_split(path, ":");
+ } else {
+ if (!*buf.release)
+ uname(&buf);
+
+ char fw_path_para[PATH_MAX + 1] = "";
+ int path = open("/sys/module/firmware_class/parameters/path", O_RDONLY | O_CLOEXEC);
+ if (path != -1) {
+ ssize_t rd = read(path, fw_path_para, PATH_MAX);
+ if (rd != -1)
+ fw_path_para[rd - 1] = '\0';
+ close(path);
+ }
+ char uk[22 + sizeof(buf.release)], fk[14 + sizeof(buf.release)];
+ sprintf(uk, "/lib/firmware/updates/%s", buf.release);
+ sprintf(fk, "/lib/firmware/%s", buf.release);
+ firmwaredirs = strv_new(STRV_IFNOTNULL(*fw_path_para ? fw_path_para : NULL),
+ uk,
+ "/lib/firmware/updates",
+ fk,
+ "/lib/firmware",
+ NULL);
+ }
+ }
+ }
+
+ if (!optind || optind == argc) {
+ if (!arg_optional) {
+ log_error("No SOURCE argument given");
+ usage(EXIT_FAILURE);
+ } else {
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ return 1;
+}
+
+static int resolve_lazy(int argc, char **argv)
+{
+ int i;
+ size_t destrootdirlen = strlen(destrootdir);
+ int ret = 0;
+ char *item;
+ for (i = 0; i < argc; i++) {
+ const char *src = argv[i];
+ char *p = argv[i];
+
+ log_debug("resolve_deps('%s')", src);
+
+ if (strstr(src, destrootdir)) {
+ p = &argv[i][destrootdirlen];
+ }
+
+ if (check_hashmap(items, p)) {
+ continue;
+ }
+
+ item = strdup(p);
+ hashmap_put(items, item, item);
+
+ ret += resolve_deps(src);
+ }
+ return ret;
+}
+
+static char **find_binary(const char *src)
+{
+ char **ret = NULL;
+ char **q;
+ char *newsrc = NULL;
+
+ STRV_FOREACH(q, pathdirs) {
+ char *fullsrcpath;
+
+ _asprintf(&newsrc, "%s/%s", *q, src);
+
+ fullsrcpath = get_real_file(newsrc, false);
+ if (!fullsrcpath) {
+ log_debug("get_real_file(%s) not found", newsrc);
+ free(newsrc);
+ newsrc = NULL;
+ continue;
+ }
+
+ if (faccessat(AT_FDCWD, fullsrcpath, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
+ log_debug("lstat(%s) != 0", fullsrcpath);
+ free(newsrc);
+ newsrc = NULL;
+ free(fullsrcpath);
+ fullsrcpath = NULL;
+ continue;
+ }
+
+ strv_push(&ret, newsrc);
+
+ free(fullsrcpath);
+ fullsrcpath = NULL;
+ };
+
+ if (ret) {
+ STRV_FOREACH(q, ret) {
+ log_debug("find_binary(%s) == %s", src, *q);
+ }
+ }
+
+ return ret;
+}
+
+static int install_one(const char *src, const char *dst)
+{
+ int r = EXIT_SUCCESS;
+ int ret = 0;
+
+ if (strchr(src, '/') == NULL) {
+ char **p = find_binary(src);
+ if (p) {
+ char **q = NULL;
+ STRV_FOREACH(q, p) {
+ char *newsrc = *q;
+ log_debug("dracut_install '%s' '%s'", newsrc, dst);
+ ret = dracut_install(newsrc, dst, arg_createdir, arg_resolvedeps, true);
+ if (ret == 0) {
+ log_debug("dracut_install '%s' '%s' OK", newsrc, dst);
+ }
+ }
+ strv_free(p);
+ } else {
+ ret = -1;
+ }
+ } else {
+ ret = dracut_install(src, dst, arg_createdir, arg_resolvedeps, true);
+ }
+
+ if ((ret != 0) && (!arg_optional)) {
+ log_error("ERROR: installing '%s' to '%s'", src, dst);
+ r = EXIT_FAILURE;
+ }
+
+ return r;
+}
+
+static int install_all(int argc, char **argv)
+{
+ int r = EXIT_SUCCESS;
+ int i;
+ for (i = 0; i < argc; i++) {
+ int ret = 0;
+ log_debug("Handle '%s'", argv[i]);
+
+ if (strchr(argv[i], '/') == NULL) {
+ char **p = find_binary(argv[i]);
+ if (p) {
+ char **q = NULL;
+ STRV_FOREACH(q, p) {
+ char *newsrc = *q;
+ log_debug("dracut_install '%s'", newsrc);
+ ret = dracut_install(newsrc, newsrc, arg_createdir, arg_resolvedeps, true);
+ if (ret == 0) {
+ log_debug("dracut_install '%s' OK", newsrc);
+ }
+ }
+ strv_free(p);
+ } else {
+ ret = -1;
+ }
+
+ } else {
+ if (strchr(argv[i], '*') == NULL) {
+ ret = dracut_install(argv[i], argv[i], arg_createdir, arg_resolvedeps, true);
+ } else {
+ _cleanup_free_ char *realsrc = NULL;
+ _cleanup_globfree_ glob_t globbuf;
+
+ _asprintf(&realsrc, "%s%s", sysrootdir ? sysrootdir : "", argv[i]);
+
+ ret = glob(realsrc, 0, NULL, &globbuf);
+ if (ret == 0) {
+ size_t j;
+
+ for (j = 0; j < globbuf.gl_pathc; j++) {
+ ret |= dracut_install(globbuf.gl_pathv[j] + sysrootdirlen,
+ globbuf.gl_pathv[j] + sysrootdirlen,
+ arg_createdir, arg_resolvedeps, true);
+ }
+ }
+ }
+ }
+
+ if ((ret != 0) && (!arg_optional)) {
+ log_error("ERROR: installing '%s'", argv[i]);
+ r = EXIT_FAILURE;
+ }
+ }
+ return r;
+}
+
+static int install_firmware_fullpath(const char *fwpath)
+{
+ const char *fw = fwpath;
+ _cleanup_free_ char *fwpath_compressed = NULL;
+ int ret;
+ if (access(fwpath, F_OK) != 0) {
+ _asprintf(&fwpath_compressed, "%s.zst", fwpath);
+ if (access(fwpath_compressed, F_OK) != 0) {
+ strcpy(fwpath_compressed + strlen(fwpath) + 1, "xz");
+ if (access(fwpath_compressed, F_OK) != 0) {
+ log_debug("stat(%s) != 0", fwpath);
+ return 1;
+ }
+ }
+ fw = fwpath_compressed;
+ }
+ ret = dracut_install(fw, fw, false, false, true);
+ if (ret == 0) {
+ log_debug("dracut_install '%s' OK", fwpath);
+ }
+ return ret;
+}
+
+static int install_firmware(struct kmod_module *mod)
+{
+ struct kmod_list *l = NULL;
+ _cleanup_kmod_module_info_free_list_ struct kmod_list *list = NULL;
+ int ret;
+ char **q;
+
+ ret = kmod_module_get_info(mod, &list);
+ if (ret < 0) {
+ log_error("could not get modinfo from '%s': %s\n", kmod_module_get_name(mod), strerror(-ret));
+ return ret;
+ }
+ kmod_list_foreach(l, list) {
+ const char *key = kmod_module_info_get_key(l);
+ const char *value = NULL;
+ bool found_this = false;
+
+ if (!streq("firmware", key))
+ continue;
+
+ value = kmod_module_info_get_value(l);
+ log_debug("Firmware %s", value);
+ ret = -1;
+ STRV_FOREACH(q, firmwaredirs) {
+ _cleanup_free_ char *fwpath = NULL;
+
+ _asprintf(&fwpath, "%s/%s", *q, value);
+
+ if (strpbrk(value, "*?[") != NULL
+ && access(fwpath, F_OK) != 0) {
+ size_t i;
+ _cleanup_globfree_ glob_t globbuf;
+
+ glob(fwpath, 0, NULL, &globbuf);
+ for (i = 0; i < globbuf.gl_pathc; i++) {
+ ret = install_firmware_fullpath(globbuf.gl_pathv[i]);
+ if (ret == 0)
+ found_this = true;
+ }
+ } else {
+ ret = install_firmware_fullpath(fwpath);
+ if (ret == 0)
+ found_this = true;
+ }
+ }
+ if (!found_this) {
+ /* firmware path was not found in any firmwaredirs */
+ log_info("Missing firmware %s for kernel module %s",
+ value, kmod_module_get_name(mod));
+ }
+ }
+ return 0;
+}
+
+static bool check_module_symbols(struct kmod_module *mod)
+{
+ struct kmod_list *itr = NULL;
+ _cleanup_kmod_module_dependency_symbols_free_list_ struct kmod_list *deplist = NULL;
+
+ if (!arg_mod_filter_symbol && !arg_mod_filter_nosymbol)
+ return true;
+
+ if (kmod_module_get_dependency_symbols(mod, &deplist) < 0) {
+ log_debug("kmod_module_get_dependency_symbols failed");
+ if (arg_mod_filter_symbol)
+ return false;
+ return true;
+ }
+
+ if (arg_mod_filter_nosymbol) {
+ kmod_list_foreach(itr, deplist) {
+ const char *symbol = kmod_module_symbol_get_symbol(itr);
+ // log_debug("Checking symbol %s", symbol);
+ if (regexec(&mod_filter_nosymbol, symbol, 0, NULL, 0) == 0) {
+ log_debug("Module %s: symbol %s matched exclusion filter", kmod_module_get_name(mod),
+ symbol);
+ return false;
+ }
+ }
+ }
+
+ if (arg_mod_filter_symbol) {
+ kmod_list_foreach(itr, deplist) {
+ const char *symbol = kmod_module_dependency_symbol_get_symbol(itr);
+ // log_debug("Checking symbol %s", symbol);
+ if (regexec(&mod_filter_symbol, symbol, 0, NULL, 0) == 0) {
+ log_debug("Module %s: symbol %s matched inclusion filter", kmod_module_get_name(mod),
+ symbol);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_module_path(const char *path)
+{
+ if (arg_mod_filter_nopath && (regexec(&mod_filter_nopath, path, 0, NULL, 0) == 0)) {
+ log_debug("Path %s matched exclusion filter", path);
+ return false;
+ }
+
+ if (arg_mod_filter_path && (regexec(&mod_filter_path, path, 0, NULL, 0) != 0)) {
+ log_debug("Path %s matched inclusion filter", path);
+ return false;
+ }
+ return true;
+}
+
+static int find_kmod_module_from_sysfs_node(struct kmod_ctx *ctx, const char *sysfs_node, int sysfs_node_len,
+ struct kmod_list **modules)
+{
+ char modalias_path[PATH_MAX];
+ if (snprintf(modalias_path, sizeof(modalias_path), "%.*s/modalias", sysfs_node_len,
+ sysfs_node) >= sizeof(modalias_path))
+ return -1;
+
+ _cleanup_close_ int modalias_file = -1;
+ if ((modalias_file = open(modalias_path, O_RDONLY | O_CLOEXEC)) == -1)
+ return 0;
+
+ char alias[page_size()];
+ ssize_t len = read(modalias_file, alias, sizeof(alias));
+ alias[len - 1] = '\0';
+
+ return kmod_module_new_from_lookup(ctx, alias, modules);
+}
+
+static int find_modules_from_sysfs_node(struct kmod_ctx *ctx, const char *sysfs_node, Hashmap *modules)
+{
+ _cleanup_kmod_module_unref_list_ struct kmod_list *list = NULL;
+ struct kmod_list *l = NULL;
+
+ if (find_kmod_module_from_sysfs_node(ctx, sysfs_node, strlen(sysfs_node), &list) >= 0) {
+ kmod_list_foreach(l, list) {
+ struct kmod_module *mod = kmod_module_get_module(l);
+ char *module = strdup(kmod_module_get_name(mod));
+ kmod_module_unref(mod);
+
+ if (hashmap_put(modules, module, module) < 0)
+ free(module);
+ }
+ }
+
+ return 0;
+}
+
+static void find_suppliers_for_sys_node(struct kmod_ctx *ctx, Hashmap *suppliers, const char *node_path_raw,
+ size_t node_path_len)
+{
+ char node_path[PATH_MAX];
+ char real_path[PATH_MAX];
+
+ memcpy(node_path, node_path_raw, node_path_len);
+ node_path[node_path_len] = '\0';
+
+ DIR *d;
+ struct dirent *dir;
+ while (realpath(node_path, real_path) != NULL && strcmp(real_path, "/sys/devices")) {
+ d = opendir(node_path);
+ if (d) {
+ size_t real_path_len = strlen(real_path);
+ while ((dir = readdir(d)) != NULL) {
+ if (strstr(dir->d_name, "supplier:platform") != NULL) {
+ if (snprintf(real_path + real_path_len, sizeof(real_path) - real_path_len, "/%s/supplier",
+ dir->d_name) < sizeof(real_path) - real_path_len) {
+ char *real_supplier_path = realpath(real_path, NULL);
+ if (real_supplier_path != NULL)
+ if (hashmap_put(suppliers, real_supplier_path, real_supplier_path) < 0)
+ free(real_supplier_path);
+ }
+ }
+ }
+ closedir(d);
+ }
+ strncat(node_path, "/..", 3); // Also find suppliers of parents
+ }
+}
+
+static void find_suppliers(struct kmod_ctx *ctx)
+{
+ _cleanup_fts_close_ FTS *fts;
+ char *paths[] = { "/sys/devices/platform", NULL };
+ fts = fts_open(paths, FTS_NOSTAT | FTS_PHYSICAL, NULL);
+
+ for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) {
+ if (strcmp(ftsent->fts_name, "modalias") == 0) {
+ _cleanup_kmod_module_unref_list_ struct kmod_list *list = NULL;
+ struct kmod_list *l;
+
+ if (find_kmod_module_from_sysfs_node(ctx, ftsent->fts_parent->fts_path, ftsent->fts_parent->fts_pathlen, &list) < 0)
+ continue;
+
+ kmod_list_foreach(l, list) {
+ _cleanup_kmod_module_unref_ struct kmod_module *mod = kmod_module_get_module(l);
+ const char *name = kmod_module_get_name(mod);
+ Hashmap *suppliers = hashmap_get(modules_suppliers, name);
+ if (suppliers == NULL) {
+ suppliers = hashmap_new(string_hash_func, string_compare_func);
+ hashmap_put(modules_suppliers, strdup(name), suppliers);
+ }
+
+ find_suppliers_for_sys_node(ctx, suppliers, ftsent->fts_parent->fts_path, ftsent->fts_parent->fts_pathlen);
+ }
+ }
+ }
+}
+
+static Hashmap *find_suppliers_paths_for_module(const char *module)
+{
+ return hashmap_get(modules_suppliers, module);
+}
+
+static int install_dependent_module(struct kmod_ctx *ctx, struct kmod_module *mod, Hashmap *suppliers_paths, int *err)
+{
+ const char *path = NULL;
+ const char *name = NULL;
+
+ path = kmod_module_get_path(mod);
+
+ if (path == NULL)
+ return 0;
+
+ if (check_hashmap(items_failed, path))
+ return -1;
+
+ if (check_hashmap(items, &path[kerneldirlen])) {
+ return 0;
+ }
+
+ name = kmod_module_get_name(mod);
+
+ if (arg_mod_filter_noname && (regexec(&mod_filter_noname, name, 0, NULL, 0) == 0)) {
+ return 0;
+ }
+
+ *err = dracut_install(path, &path[kerneldirlen], false, false, true);
+ if (*err == 0) {
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL;
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modpre = NULL;
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modpost = NULL;
+ log_debug("dracut_install '%s' '%s' OK", path, &path[kerneldirlen]);
+ install_firmware(mod);
+ modlist = kmod_module_get_dependencies(mod);
+ *err = install_dependent_modules(ctx, modlist, suppliers_paths);
+ if (*err == 0) {
+ *err = kmod_module_get_softdeps(mod, &modpre, &modpost);
+ if (*err == 0) {
+ int r;
+ *err = install_dependent_modules(ctx, modpre, NULL);
+ r = install_dependent_modules(ctx, modpost, NULL);
+ *err = *err ? : r;
+ }
+ }
+ } else {
+ log_error("dracut_install '%s' '%s' ERROR", path, &path[kerneldirlen]);
+ }
+
+ return 0;
+}
+
+static int install_dependent_modules(struct kmod_ctx *ctx, struct kmod_list *modlist, Hashmap *suppliers_paths)
+{
+ struct kmod_list *itr = NULL;
+ int ret = 0;
+
+ kmod_list_foreach(itr, modlist) {
+ _cleanup_kmod_module_unref_ struct kmod_module *mod = NULL;
+ mod = kmod_module_get_module(itr);
+ if (install_dependent_module(ctx, mod, find_suppliers_paths_for_module(kmod_module_get_name(mod)), &ret))
+ return -1;
+ }
+
+ const char *supplier_path;
+ Iterator i;
+ HASHMAP_FOREACH(supplier_path, suppliers_paths, i) {
+ if (check_hashmap(processed_suppliers, supplier_path))
+ continue;
+
+ char *path = strdup(supplier_path);
+ hashmap_put(processed_suppliers, path, path);
+
+ _cleanup_destroy_hashmap_ Hashmap *modules = hashmap_new(string_hash_func, string_compare_func);
+ find_modules_from_sysfs_node(ctx, supplier_path, modules);
+
+ _cleanup_destroy_hashmap_ Hashmap *suppliers = hashmap_new(string_hash_func, string_compare_func);
+ find_suppliers_for_sys_node(ctx, suppliers, supplier_path, strlen(supplier_path));
+
+ if (!hashmap_isempty(modules)) { // Supplier is a module
+ const char *module;
+ Iterator j;
+ HASHMAP_FOREACH(module, modules, j) {
+ _cleanup_kmod_module_unref_ struct kmod_module *mod = NULL;
+ if (!kmod_module_new_from_name(ctx, module, &mod)) {
+ if (install_dependent_module(ctx, mod, suppliers, &ret))
+ return -1;
+ }
+ }
+ } else { // Supplier is builtin
+ install_dependent_modules(ctx, NULL, suppliers);
+ }
+ }
+
+ return ret;
+}
+
+static int install_module(struct kmod_ctx *ctx, struct kmod_module *mod)
+{
+ int ret = 0;
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL;
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modpre = NULL;
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modpost = NULL;
+ const char *path = NULL;
+ const char *name = NULL;
+
+ name = kmod_module_get_name(mod);
+
+ path = kmod_module_get_path(mod);
+ if (!path) {
+ log_debug("dracut_install '%s' is a builtin kernel module", name);
+ return 0;
+ }
+
+ if (arg_mod_filter_noname && (regexec(&mod_filter_noname, name, 0, NULL, 0) == 0)) {
+ log_debug("dracut_install '%s' is excluded", name);
+ return 0;
+ }
+
+ if (arg_hostonly && !check_hashmap(modules_loaded, name)) {
+ log_debug("dracut_install '%s' not hostonly", name);
+ return 0;
+ }
+
+ if (check_hashmap(items_failed, path))
+ return -1;
+
+ if (check_hashmap(items, path))
+ return 0;
+
+ if (!check_module_path(path) || !check_module_symbols(mod)) {
+ log_debug("No symbol or path match for '%s'", path);
+ return 1;
+ }
+
+ log_debug("dracut_install '%s' '%s'", path, &path[kerneldirlen]);
+
+ ret = dracut_install(path, &path[kerneldirlen], false, false, true);
+ if (ret == 0) {
+ log_debug("dracut_install '%s' OK", kmod_module_get_name(mod));
+ } else if (!arg_optional) {
+ if (!arg_silent)
+ log_error("dracut_install '%s' ERROR", kmod_module_get_name(mod));
+ return ret;
+ }
+ install_firmware(mod);
+
+ Hashmap *suppliers = find_suppliers_paths_for_module(name);
+ modlist = kmod_module_get_dependencies(mod);
+ ret = install_dependent_modules(ctx, modlist, suppliers);
+
+ if (ret == 0) {
+ ret = kmod_module_get_softdeps(mod, &modpre, &modpost);
+ if (ret == 0) {
+ int r;
+ ret = install_dependent_modules(ctx, modpre, NULL);
+ r = install_dependent_modules(ctx, modpost, NULL);
+ ret = ret ? : r;
+ }
+ }
+
+ return ret;
+}
+
+static int modalias_list(struct kmod_ctx *ctx)
+{
+ int err;
+ struct kmod_list *loaded_list = NULL;
+ struct kmod_list *l = NULL;
+ struct kmod_list *itr = NULL;
+ _cleanup_fts_close_ FTS *fts = NULL;
+
+ {
+ char *paths[] = { "/sys/devices", NULL };
+ fts = fts_open(paths, FTS_NOCHDIR | FTS_NOSTAT, NULL);
+ }
+ for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_kmod_module_unref_list_ struct kmod_list *list = NULL;
+
+ int err;
+
+ char alias[2048] = {0};
+ size_t len;
+
+ if (strncmp("modalias", ftsent->fts_name, 8) != 0)
+ continue;
+ if (!(f = fopen(ftsent->fts_accpath, "r")))
+ continue;
+
+ if (!fgets(alias, sizeof(alias), f))
+ continue;
+
+ len = strlen(alias);
+
+ if (len == 0)
+ continue;
+
+ if (alias[len - 1] == '\n')
+ alias[len - 1] = 0;
+
+ err = kmod_module_new_from_lookup(ctx, alias, &list);
+ if (err < 0)
+ continue;
+
+ kmod_list_foreach(l, list) {
+ struct kmod_module *mod = kmod_module_get_module(l);
+ char *name = strdup(kmod_module_get_name(mod));
+ kmod_module_unref(mod);
+ hashmap_put(modules_loaded, name, name);
+ }
+ }
+
+ err = kmod_module_new_from_loaded(ctx, &loaded_list);
+ if (err < 0) {
+ errno = err;
+ log_error("Could not get list of loaded modules: %m. Switching to non-hostonly mode.");
+ arg_hostonly = false;
+ } else {
+ kmod_list_foreach(itr, loaded_list) {
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL;
+
+ struct kmod_module *mod = kmod_module_get_module(itr);
+ char *name = strdup(kmod_module_get_name(mod));
+ hashmap_put(modules_loaded, name, name);
+ kmod_module_unref(mod);
+
+ /* also put the modules from the new kernel in the hashmap,
+ * which resolve the name as an alias, in case a kernel module is
+ * renamed.
+ */
+ err = kmod_module_new_from_lookup(ctx, name, &modlist);
+ if (err < 0)
+ continue;
+ if (!modlist)
+ continue;
+ kmod_list_foreach(l, modlist) {
+ mod = kmod_module_get_module(l);
+ char *name = strdup(kmod_module_get_name(mod));
+ hashmap_put(modules_loaded, name, name);
+ kmod_module_unref(mod);
+ }
+ }
+ kmod_module_unref_list(loaded_list);
+ }
+ return 0;
+}
+
+static int install_modules(int argc, char **argv)
+{
+ _cleanup_kmod_unref_ struct kmod_ctx *ctx = NULL;
+ struct kmod_list *itr = NULL;
+
+ struct kmod_module *mod = NULL, *mod_o = NULL;
+
+ const char *abskpath = NULL;
+ char *p;
+ int i;
+ int modinst = 0;
+
+ ctx = kmod_new(kerneldir, NULL);
+ abskpath = kmod_get_dirname(ctx);
+
+ p = strstr(abskpath, "/lib/modules/");
+ if (p != NULL)
+ kerneldirlen = p - abskpath;
+
+ modules_suppliers = hashmap_new(string_hash_func, string_compare_func);
+ find_suppliers(ctx);
+
+ if (arg_hostonly) {
+ char *modalias_file;
+ modalias_file = getenv("DRACUT_KERNEL_MODALIASES");
+
+ if (modalias_file == NULL) {
+ modalias_list(ctx);
+ } else {
+ _cleanup_fclose_ FILE *f = NULL;
+ if ((f = fopen(modalias_file, "r"))) {
+ char name[2048];
+
+ while (!feof(f)) {
+ size_t len;
+ char *dupname = NULL;
+
+ if (!(fgets(name, sizeof(name), f)))
+ continue;
+ len = strlen(name);
+
+ if (len == 0)
+ continue;
+
+ if (name[len - 1] == '\n')
+ name[len - 1] = 0;
+
+ log_debug("Adding module '%s' to hostonly module list", name);
+ dupname = strdup(name);
+ hashmap_put(modules_loaded, dupname, dupname);
+ }
+ }
+ }
+
+ }
+
+ for (i = 0; i < argc; i++) {
+ int r = 0;
+ int ret = -1;
+ log_debug("Handle module '%s'", argv[i]);
+
+ if (argv[i][0] == '/') {
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL;
+ _cleanup_free_ const char *modname = NULL;
+
+ r = kmod_module_new_from_path(ctx, argv[i], &mod_o);
+ if (r < 0) {
+ log_debug("Failed to lookup modules path '%s': %m", argv[i]);
+ if (!arg_optional)
+ return -ENOENT;
+ continue;
+ }
+ /* Check, if we have to load another module with that name instead */
+ modname = strdup(kmod_module_get_name(mod_o));
+
+ if (!modname) {
+ if (!arg_optional) {
+ if (!arg_silent)
+ log_error("Failed to get name for module '%s'", argv[i]);
+ return -ENOENT;
+ }
+ log_info("Failed to get name for module '%s'", argv[i]);
+ continue;
+ }
+
+ r = kmod_module_new_from_lookup(ctx, modname, &modlist);
+ kmod_module_unref(mod_o);
+ mod_o = NULL;
+
+ if (r < 0) {
+ if (!arg_optional) {
+ if (!arg_silent)
+ log_error("3 Failed to lookup alias '%s': %d", modname, r);
+ return -ENOENT;
+ }
+ log_info("3 Failed to lookup alias '%s': %d", modname, r);
+ continue;
+ }
+ if (!modlist) {
+ if (!arg_optional) {
+ if (!arg_silent)
+ log_error("Failed to find module '%s' %s", modname, argv[i]);
+ return -ENOENT;
+ }
+ log_info("Failed to find module '%s' %s", modname, argv[i]);
+ continue;
+ }
+ kmod_list_foreach(itr, modlist) {
+ mod = kmod_module_get_module(itr);
+ r = install_module(ctx, mod);
+ kmod_module_unref(mod);
+ if ((r < 0) && !arg_optional) {
+ if (!arg_silent)
+ log_error("ERROR: installing module '%s'", modname);
+ return -ENOENT;
+ };
+ ret = (ret == 0 ? 0 : r);
+ modinst = 1;
+ }
+ } else if (argv[i][0] == '=') {
+ _cleanup_free_ char *path1 = NULL, *path2 = NULL, *path3 = NULL;
+ _cleanup_fts_close_ FTS *fts = NULL;
+
+ log_debug("Handling =%s", &argv[i][1]);
+ /* FIXME and add more paths */
+ _asprintf(&path2, "%s/kernel/%s", kerneldir, &argv[i][1]);
+ _asprintf(&path1, "%s/extra/%s", kerneldir, &argv[i][1]);
+ _asprintf(&path3, "%s/updates/%s", kerneldir, &argv[i][1]);
+
+ {
+ char *paths[] = { path1, path2, path3, NULL };
+ fts = fts_open(paths, FTS_COMFOLLOW | FTS_NOCHDIR | FTS_NOSTAT | FTS_LOGICAL, NULL);
+ }
+
+ for (FTSENT *ftsent = fts_read(fts); ftsent != NULL; ftsent = fts_read(fts)) {
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL;
+ _cleanup_free_ const char *modname = NULL;
+
+ if ((ftsent->fts_info == FTS_D) && !check_module_path(ftsent->fts_accpath)) {
+ fts_set(fts, ftsent, FTS_SKIP);
+ log_debug("Skipping %s", ftsent->fts_accpath);
+ continue;
+ }
+ if ((ftsent->fts_info != FTS_F) && (ftsent->fts_info != FTS_SL)) {
+ log_debug("Ignoring %s", ftsent->fts_accpath);
+ continue;
+ }
+ log_debug("Handling %s", ftsent->fts_accpath);
+ r = kmod_module_new_from_path(ctx, ftsent->fts_accpath, &mod_o);
+ if (r < 0) {
+ log_debug("Failed to lookup modules path '%s': %m", ftsent->fts_accpath);
+ if (!arg_optional) {
+ return -ENOENT;
+ }
+ continue;
+ }
+
+ /* Check, if we have to load another module with that name instead */
+ modname = strdup(kmod_module_get_name(mod_o));
+
+ if (!modname) {
+ log_error("Failed to get name for module '%s'", ftsent->fts_accpath);
+ if (!arg_optional) {
+ return -ENOENT;
+ }
+ continue;
+ }
+ r = kmod_module_new_from_lookup(ctx, modname, &modlist);
+ kmod_module_unref(mod_o);
+ mod_o = NULL;
+
+ if (r < 0) {
+ log_error("Failed to lookup alias '%s': %m", modname);
+ if (!arg_optional) {
+ return -ENOENT;
+ }
+ continue;
+ }
+
+ if (!modlist) {
+ log_error("Failed to find module '%s' %s", modname, ftsent->fts_accpath);
+ if (!arg_optional) {
+ return -ENOENT;
+ }
+ continue;
+ }
+ kmod_list_foreach(itr, modlist) {
+ mod = kmod_module_get_module(itr);
+ r = install_module(ctx, mod);
+ kmod_module_unref(mod);
+ if ((r < 0) && !arg_optional) {
+ if (!arg_silent)
+ log_error("ERROR: installing module '%s'", modname);
+ return -ENOENT;
+ };
+ ret = (ret == 0 ? 0 : r);
+ modinst = 1;
+ }
+ }
+ if (errno) {
+ log_error("FTS ERROR: %m");
+ }
+ } else {
+ _cleanup_kmod_module_unref_list_ struct kmod_list *modlist = NULL;
+ char *modname = argv[i];
+
+ if (endswith(modname, ".ko")) {
+ int len = strlen(modname);
+ modname[len - 3] = 0;
+ }
+ if (endswith(modname, ".ko.xz") || endswith(modname, ".ko.gz")) {
+ int len = strlen(modname);
+ modname[len - 6] = 0;
+ }
+ if (endswith(modname, ".ko.zst")) {
+ int len = strlen(modname);
+ modname[len - 7] = 0;
+ }
+ r = kmod_module_new_from_lookup(ctx, modname, &modlist);
+ if (r < 0) {
+ if (!arg_optional) {
+ if (!arg_silent)
+ log_error("Failed to lookup alias '%s': %m", modname);
+ return -ENOENT;
+ }
+ log_info("Failed to lookup alias '%s': %m", modname);
+ continue;
+ }
+ if (!modlist) {
+ if (!arg_optional) {
+ if (!arg_silent)
+ log_error("Failed to find module '%s'", modname);
+ return -ENOENT;
+ }
+ log_info("Failed to find module '%s'", modname);
+ continue;
+ }
+ kmod_list_foreach(itr, modlist) {
+ mod = kmod_module_get_module(itr);
+ r = install_module(ctx, mod);
+ kmod_module_unref(mod);
+ if ((r < 0) && !arg_optional) {
+ if (!arg_silent)
+ log_error("ERROR: installing '%s'", argv[i]);
+ return -ENOENT;
+ };
+ ret = (ret == 0 ? 0 : r);
+ modinst = 1;
+ }
+ }
+
+ if ((modinst != 0) && (ret != 0) && (!arg_optional)) {
+ if (!arg_silent)
+ log_error("ERROR: installing '%s'", argv[i]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ int r;
+ char *i;
+ char *path = NULL;
+ char *env_no_xattr = NULL;
+
+ log_set_target(LOG_TARGET_CONSOLE);
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ modules_loaded = hashmap_new(string_hash_func, string_compare_func);
+ if (arg_modalias) {
+ Iterator i;
+ char *name;
+ _cleanup_kmod_unref_ struct kmod_ctx *ctx = NULL;
+ ctx = kmod_new(kerneldir, NULL);
+
+ modalias_list(ctx);
+ HASHMAP_FOREACH(name, modules_loaded, i) {
+ printf("%s\n", name);
+ }
+ exit(0);
+ }
+
+ log_debug("Program arguments:");
+ for (r = 0; r < argc; r++)
+ log_debug("%s", argv[r]);
+
+ path = getenv("DRACUT_INSTALL_PATH");
+ if (path == NULL)
+ path = getenv("PATH");
+
+ if (path == NULL) {
+ log_error("PATH is not set");
+ exit(EXIT_FAILURE);
+ }
+
+ log_debug("PATH=%s", path);
+
+ ldd = getenv("DRACUT_LDD");
+ if (ldd == NULL)
+ ldd = "ldd";
+ log_debug("LDD=%s", ldd);
+
+ env_no_xattr = getenv("DRACUT_NO_XATTR");
+ if (env_no_xattr != NULL)
+ no_xattr = true;
+
+ pathdirs = strv_split(path, ":");
+
+ umask(0022);
+
+ if (destrootdir == NULL || strlen(destrootdir) == 0) {
+ destrootdir = getenv("DESTROOTDIR");
+ if (destrootdir == NULL || strlen(destrootdir) == 0) {
+ log_error("Environment DESTROOTDIR or argument -D is not set!");
+ usage(EXIT_FAILURE);
+ }
+ }
+
+ if (strcmp(destrootdir, "/") == 0) {
+ log_error("Environment DESTROOTDIR or argument -D is set to '/'!");
+ usage(EXIT_FAILURE);
+ }
+
+ i = destrootdir;
+ if (!(destrootdir = realpath(i, NULL))) {
+ log_error("Environment DESTROOTDIR or argument -D is set to '%s': %m", i);
+ r = EXIT_FAILURE;
+ goto finish2;
+ }
+
+ items = hashmap_new(string_hash_func, string_compare_func);
+ items_failed = hashmap_new(string_hash_func, string_compare_func);
+ processed_suppliers = hashmap_new(string_hash_func, string_compare_func);
+
+ if (!items || !items_failed || !processed_suppliers || !modules_loaded) {
+ log_error("Out of memory");
+ r = EXIT_FAILURE;
+ goto finish1;
+ }
+
+ if (logdir) {
+ _asprintf(&logfile, "%s/%d.log", logdir, getpid());
+
+ logfile_f = fopen(logfile, "a");
+ if (logfile_f == NULL) {
+ log_error("Could not open %s for logging: %m", logfile);
+ r = EXIT_FAILURE;
+ goto finish1;
+ }
+ }
+
+ r = EXIT_SUCCESS;
+
+ if (((optind + 1) < argc) && (strcmp(argv[optind + 1], destrootdir) == 0)) {
+ /* ugly hack for compat mode "inst src $destrootdir" */
+ if ((optind + 2) == argc) {
+ argc--;
+ } else {
+ /* ugly hack for compat mode "inst src $destrootdir dst" */
+ if ((optind + 3) == argc) {
+ argc--;
+ argv[optind + 1] = argv[optind + 2];
+ }
+ }
+ }
+
+ if (arg_module) {
+ r = install_modules(argc - optind, &argv[optind]);
+ } else if (arg_resolvelazy) {
+ r = resolve_lazy(argc - optind, &argv[optind]);
+ } else if (arg_all || (argc - optind > 2) || ((argc - optind) == 1)) {
+ r = install_all(argc - optind, &argv[optind]);
+ } else {
+ /* simple "inst src dst" */
+ r = install_one(argv[optind], argv[optind + 1]);
+ }
+
+ if (arg_optional)
+ r = EXIT_SUCCESS;
+
+finish1:
+ free(destrootdir);
+finish2:
+ if (logfile_f)
+ fclose(logfile_f);
+
+ while ((i = hashmap_steal_first(modules_loaded)))
+ item_free(i);
+
+ while ((i = hashmap_steal_first(items)))
+ item_free(i);
+
+ while ((i = hashmap_steal_first(items_failed)))
+ item_free(i);
+
+ Hashmap *h;
+ while ((h = hashmap_steal_first(modules_suppliers))) {
+ while ((i = hashmap_steal_first(h))) {
+ item_free(i);
+ }
+ hashmap_free(h);
+ }
+
+ while ((i = hashmap_steal_first(processed_suppliers)))
+ item_free(i);
+
+ hashmap_free(items);
+ hashmap_free(items_failed);
+ hashmap_free(modules_loaded);
+ hashmap_free(modules_suppliers);
+ hashmap_free(processed_suppliers);
+
+ strv_free(firmwaredirs);
+ strv_free(pathdirs);
+ return r;
+}
diff --git a/src/install/hashmap.c b/src/install/hashmap.c
new file mode 100644
index 0000000..ff0f681
--- /dev/null
+++ b/src/install/hashmap.c
@@ -0,0 +1,658 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "hashmap.h"
+#include "macro.h"
+
+#define NBUCKETS 127
+
+struct hashmap_entry {
+ const void *key;
+ void *value;
+ struct hashmap_entry *bucket_next, *bucket_previous;
+ struct hashmap_entry *iterate_next, *iterate_previous;
+};
+
+struct Hashmap {
+ hash_func_t hash_func;
+ compare_func_t compare_func;
+
+ struct hashmap_entry *iterate_list_head, *iterate_list_tail;
+ unsigned int n_entries;
+};
+
+#define BY_HASH(h) ((struct hashmap_entry**) ((uint8_t*) (h) + ALIGN(sizeof(Hashmap))))
+
+unsigned int string_hash_func(const void *p)
+{
+ unsigned int hash = 5381;
+ const signed char *c;
+
+ /* DJB's hash function */
+
+ for (c = p; *c; c++)
+ hash = (hash << 5) + hash + (unsigned int)*c;
+
+ return hash;
+}
+
+int string_compare_func(const void *a, const void *b)
+{
+ return strcmp(a, b);
+}
+
+unsigned int trivial_hash_func(const void *p)
+{
+ return PTR_TO_UINT(p);
+}
+
+int trivial_compare_func(const void *a, const void *b)
+{
+ return a < b ? -1 : (a > b ? 1 : 0);
+}
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func)
+{
+ Hashmap *h;
+ size_t size;
+
+ size = ALIGN(sizeof(Hashmap)) + NBUCKETS * sizeof(struct hashmap_entry *);
+
+ h = malloc0(size);
+
+ if (!h)
+ return NULL;
+
+ h->hash_func = hash_func ? hash_func : trivial_hash_func;
+ h->compare_func = compare_func ? compare_func : trivial_compare_func;
+
+ h->n_entries = 0;
+ h->iterate_list_head = h->iterate_list_tail = NULL;
+
+ return h;
+}
+
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func)
+{
+ assert(h);
+
+ if (*h)
+ return 0;
+
+ if (!(*h = hashmap_new(hash_func, compare_func)))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void link_entry(Hashmap *h, struct hashmap_entry *e, unsigned int hash)
+{
+ assert(h);
+ assert(e);
+
+ /* Insert into hash table */
+ e->bucket_next = BY_HASH(h)[hash];
+ e->bucket_previous = NULL;
+ if (BY_HASH(h)[hash])
+ BY_HASH(h)[hash]->bucket_previous = e;
+ BY_HASH(h)[hash] = e;
+
+ /* Insert into iteration list */
+ e->iterate_previous = h->iterate_list_tail;
+ e->iterate_next = NULL;
+ if (h->iterate_list_tail) {
+ assert(h->iterate_list_head);
+ h->iterate_list_tail->iterate_next = e;
+ } else {
+ assert(!h->iterate_list_head);
+ h->iterate_list_head = e;
+ }
+ h->iterate_list_tail = e;
+
+ h->n_entries++;
+ assert(h->n_entries >= 1);
+}
+
+static void unlink_entry(Hashmap *h, struct hashmap_entry *e, unsigned int hash)
+{
+ assert(h);
+ assert(e);
+
+ /* Remove from iteration list */
+ if (e->iterate_next)
+ e->iterate_next->iterate_previous = e->iterate_previous;
+ else
+ h->iterate_list_tail = e->iterate_previous;
+
+ if (e->iterate_previous)
+ e->iterate_previous->iterate_next = e->iterate_next;
+ else
+ h->iterate_list_head = e->iterate_next;
+
+ /* Remove from hash table bucket list */
+ if (e->bucket_next)
+ e->bucket_next->bucket_previous = e->bucket_previous;
+
+ if (e->bucket_previous)
+ e->bucket_previous->bucket_next = e->bucket_next;
+ else
+ BY_HASH(h)[hash] = e->bucket_next;
+
+ assert(h->n_entries >= 1);
+ h->n_entries--;
+}
+
+static void remove_entry(Hashmap *h, struct hashmap_entry **ep)
+{
+ struct hashmap_entry *e = *ep;
+ unsigned int hash;
+
+ assert(h);
+ assert(e);
+
+ hash = h->hash_func(e->key) % NBUCKETS;
+
+ unlink_entry(h, e, hash);
+
+ free(e);
+ *ep = NULL;
+}
+
+void hashmap_free(Hashmap *h)
+{
+
+ if (!h)
+ return;
+
+ hashmap_clear(h);
+
+ free(h);
+}
+
+void hashmap_free_free(Hashmap *h)
+{
+ void *p;
+
+ while ((p = hashmap_steal_first(h)))
+ free(p);
+
+ hashmap_free(h);
+}
+
+void hashmap_clear(Hashmap *h)
+{
+ if (!h)
+ return;
+
+ while (h->iterate_list_head) {
+ struct hashmap_entry *e = h->iterate_list_head;
+ remove_entry(h, &e);
+ }
+}
+
+static struct hashmap_entry *hash_scan(Hashmap *h, unsigned int hash, const void *key)
+{
+ struct hashmap_entry *e;
+ assert(h);
+ assert(hash < NBUCKETS);
+
+ for (e = BY_HASH(h)[hash]; e; e = e->bucket_next)
+ if (h->compare_func(e->key, key) == 0)
+ return e;
+
+ return NULL;
+}
+
+int hashmap_put(Hashmap *h, const void *key, void *value)
+{
+ struct hashmap_entry *e;
+ unsigned int hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if ((e = hash_scan(h, hash, key))) {
+
+ if (e->value == value)
+ return 0;
+
+ return -EEXIST;
+ }
+
+ e = new(struct hashmap_entry, 1);
+
+ if (!e)
+ return -ENOMEM;
+
+ e->key = key;
+ e->value = value;
+
+ link_entry(h, e, hash);
+
+ return 1;
+}
+
+int hashmap_replace(Hashmap *h, const void *key, void *value)
+{
+ struct hashmap_entry *e;
+ unsigned int hash;
+
+ assert(h);
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if ((e = hash_scan(h, hash, key))) {
+ e->key = key;
+ e->value = value;
+ return 0;
+ }
+
+ return hashmap_put(h, key, value);
+}
+
+void *hashmap_get(Hashmap *h, const void *key)
+{
+ unsigned int hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ return e->value;
+}
+
+void *hashmap_remove(Hashmap *h, const void *key)
+{
+ struct hashmap_entry *e;
+ unsigned int hash;
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ data = e->value;
+ remove_entry(h, &e);
+
+ return data;
+}
+
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value)
+{
+ struct hashmap_entry *e;
+ unsigned int old_hash, new_hash;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = h->hash_func(old_key) % NBUCKETS;
+ if (!(e = hash_scan(h, old_hash, old_key)))
+ return -ENOENT;
+
+ new_hash = h->hash_func(new_key) % NBUCKETS;
+ if (hash_scan(h, new_hash, new_key))
+ return -EEXIST;
+
+ unlink_entry(h, e, old_hash);
+
+ e->key = new_key;
+ e->value = value;
+
+ link_entry(h, e, new_hash);
+
+ return 0;
+}
+
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value)
+{
+ struct hashmap_entry *e, *k;
+ unsigned int old_hash, new_hash;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = h->hash_func(old_key) % NBUCKETS;
+ if (!(e = hash_scan(h, old_hash, old_key)))
+ return -ENOENT;
+
+ new_hash = h->hash_func(new_key) % NBUCKETS;
+
+ if ((k = hash_scan(h, new_hash, new_key)))
+ if (e != k)
+ remove_entry(h, &k);
+
+ unlink_entry(h, e, old_hash);
+
+ e->key = new_key;
+ e->value = value;
+
+ link_entry(h, e, new_hash);
+
+ return 0;
+}
+
+void *hashmap_remove_value(Hashmap *h, const void *key, void *value)
+{
+ struct hashmap_entry *e;
+ unsigned int hash;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ if (e->value != value)
+ return NULL;
+
+ remove_entry(h, &e);
+
+ return value;
+}
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key)
+{
+ struct hashmap_entry *e;
+
+ assert(i);
+
+ if (!h)
+ goto at_end;
+
+ if (*i == ITERATOR_LAST)
+ goto at_end;
+
+ if (*i == ITERATOR_FIRST && !h->iterate_list_head)
+ goto at_end;
+
+ e = *i == ITERATOR_FIRST ? h->iterate_list_head : (struct hashmap_entry *)*i;
+
+ if (e->iterate_next)
+ *i = (Iterator) e->iterate_next;
+ else
+ *i = ITERATOR_LAST;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_end:
+ *i = ITERATOR_LAST;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key)
+{
+ struct hashmap_entry *e;
+
+ assert(i);
+
+ if (!h)
+ goto at_beginning;
+
+ if (*i == ITERATOR_FIRST)
+ goto at_beginning;
+
+ if (*i == ITERATOR_LAST && !h->iterate_list_tail)
+ goto at_beginning;
+
+ e = *i == ITERATOR_LAST ? h->iterate_list_tail : (struct hashmap_entry *)*i;
+
+ if (e->iterate_previous)
+ *i = (Iterator) e->iterate_previous;
+ else
+ *i = ITERATOR_FIRST;
+
+ if (key)
+ *key = e->key;
+
+ return e->value;
+
+at_beginning:
+ *i = ITERATOR_FIRST;
+
+ if (key)
+ *key = NULL;
+
+ return NULL;
+}
+
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i)
+{
+ unsigned int hash;
+ struct hashmap_entry *e;
+
+ if (!h)
+ return NULL;
+
+ hash = h->hash_func(key) % NBUCKETS;
+
+ if (!(e = hash_scan(h, hash, key)))
+ return NULL;
+
+ *i = (Iterator) e;
+
+ return e->value;
+}
+
+void *hashmap_first(Hashmap *h)
+{
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ return h->iterate_list_head->value;
+}
+
+void *hashmap_first_key(Hashmap *h)
+{
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ return (void *)h->iterate_list_head->key;
+}
+
+void *hashmap_last(Hashmap *h)
+{
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_tail)
+ return NULL;
+
+ return h->iterate_list_tail->value;
+}
+
+void *hashmap_steal_first(Hashmap *h)
+{
+ struct hashmap_entry *e;
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ e = h->iterate_list_head;
+ data = e->value;
+ remove_entry(h, &e);
+
+ return data;
+}
+
+void *hashmap_steal_first_key(Hashmap *h)
+{
+ struct hashmap_entry *e;
+ void *key;
+
+ if (!h)
+ return NULL;
+
+ if (!h->iterate_list_head)
+ return NULL;
+
+ e = h->iterate_list_head;
+ key = (void *)e->key;
+ remove_entry(h, &e);
+
+ return key;
+}
+
+unsigned int hashmap_size(Hashmap *h)
+{
+
+ if (!h)
+ return 0;
+
+ return h->n_entries;
+}
+
+bool hashmap_isempty(Hashmap *h)
+{
+
+ if (!h)
+ return true;
+
+ return h->n_entries == 0;
+}
+
+int hashmap_merge(Hashmap *h, Hashmap *other)
+{
+ struct hashmap_entry *e;
+
+ assert(h);
+
+ if (!other)
+ return 0;
+
+ for (e = other->iterate_list_head; e; e = e->iterate_next) {
+ int r;
+
+ if ((r = hashmap_put(h, e->key, e->value)) < 0)
+ if (r != -EEXIST)
+ return r;
+ }
+
+ return 0;
+}
+
+void hashmap_move(Hashmap *h, Hashmap *other)
+{
+ struct hashmap_entry *e, *n;
+
+ assert(h);
+
+ /* The same as hashmap_merge(), but every new item from other
+ * is moved to h. This function is guaranteed to succeed. */
+
+ if (!other)
+ return;
+
+ for (e = other->iterate_list_head; e; e = n) {
+ unsigned int h_hash, other_hash;
+
+ n = e->iterate_next;
+
+ h_hash = h->hash_func(e->key) % NBUCKETS;
+
+ if (hash_scan(h, h_hash, e->key))
+ continue;
+
+ other_hash = other->hash_func(e->key) % NBUCKETS;
+
+ unlink_entry(other, e, other_hash);
+ link_entry(h, e, h_hash);
+ }
+}
+
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key)
+{
+ unsigned int h_hash, other_hash;
+ struct hashmap_entry *e;
+
+ if (!other)
+ return 0;
+
+ assert(h);
+
+ h_hash = h->hash_func(key) % NBUCKETS;
+ if (hash_scan(h, h_hash, key))
+ return -EEXIST;
+
+ other_hash = other->hash_func(key) % NBUCKETS;
+ if (!(e = hash_scan(other, other_hash, key)))
+ return -ENOENT;
+
+ unlink_entry(other, e, other_hash);
+ link_entry(h, e, h_hash);
+
+ return 0;
+}
+
+char **hashmap_get_strv(Hashmap *h)
+{
+ char **sv;
+ Iterator it;
+ char *item;
+ int n;
+
+ sv = new(char *, h->n_entries + 1);
+ if (!sv)
+ return NULL;
+
+ n = 0;
+ HASHMAP_FOREACH(item, h, it) {
+ sv[n++] = item;
+ }
+ sv[n] = NULL;
+
+ return sv;
+}
diff --git a/src/install/hashmap.h b/src/install/hashmap.h
new file mode 100644
index 0000000..2537c49
--- /dev/null
+++ b/src/install/hashmap.h
@@ -0,0 +1,88 @@
+#ifndef foohashmaphfoo
+#define foohashmaphfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+/* Pretty straightforward hash table implementation. As a minor
+ * optimization a NULL hashmap object will be treated as empty hashmap
+ * for all read operations. That way it is not necessary to
+ * instantiate an object for each Hashmap use. */
+
+typedef struct Hashmap Hashmap;
+typedef struct _IteratorStruct _IteratorStruct;
+typedef _IteratorStruct *Iterator;
+
+#define ITERATOR_FIRST ((Iterator) 0)
+#define ITERATOR_LAST ((Iterator) -1)
+
+typedef unsigned int (*hash_func_t)(const void *p);
+typedef int (*compare_func_t)(const void *a, const void *b);
+
+unsigned int string_hash_func(const void *p);
+int string_compare_func(const void *a, const void *b);
+
+unsigned int trivial_hash_func(const void *p);
+int trivial_compare_func(const void *a, const void *b);
+
+Hashmap *hashmap_new(hash_func_t hash_func, compare_func_t compare_func);
+void hashmap_free(Hashmap *h);
+void hashmap_free_free(Hashmap *h);
+int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t compare_func);
+
+int hashmap_put(Hashmap *h, const void *key, void *value);
+int hashmap_replace(Hashmap *h, const void *key, void *value);
+void *hashmap_get(Hashmap *h, const void *key);
+void *hashmap_remove(Hashmap *h, const void *key);
+void *hashmap_remove_value(Hashmap *h, const void *key, void *value);
+int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
+
+int hashmap_merge(Hashmap *h, Hashmap *other);
+void hashmap_move(Hashmap *h, Hashmap *other);
+int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key);
+
+unsigned int hashmap_size(Hashmap *h);
+bool hashmap_isempty(Hashmap *h);
+
+void *hashmap_iterate(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_backwards(Hashmap *h, Iterator *i, const void **key);
+void *hashmap_iterate_skip(Hashmap *h, const void *key, Iterator *i);
+
+void hashmap_clear(Hashmap *h);
+void *hashmap_steal_first(Hashmap *h);
+void *hashmap_steal_first_key(Hashmap *h);
+void *hashmap_first(Hashmap *h);
+void *hashmap_first_key(Hashmap *h);
+void *hashmap_last(Hashmap *h);
+
+char **hashmap_get_strv(Hashmap *h);
+
+#define HASHMAP_FOREACH(e, h, i) \
+ for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), NULL); (e); (e) = hashmap_iterate((h), &(i), NULL))
+
+#define HASHMAP_FOREACH_KEY(e, k, h, i) \
+ for ((i) = ITERATOR_FIRST, (e) = hashmap_iterate((h), &(i), (const void**) &(k)); (e); (e) = hashmap_iterate((h), &(i), (const void**) &(k)))
+
+#define HASHMAP_FOREACH_BACKWARDS(e, h, i) \
+ for ((i) = ITERATOR_LAST, (e) = hashmap_iterate_backwards((h), &(i), NULL); (e); (e) = hashmap_iterate_backwards((h), &(i), NULL))
+
+#endif
diff --git a/src/install/log.c b/src/install/log.c
new file mode 100644
index 0000000..f5ba54e
--- /dev/null
+++ b/src/install/log.c
@@ -0,0 +1,303 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stddef.h>
+
+#include "log.h"
+#include "util.h"
+#include "macro.h"
+
+#define SNDBUF_SIZE (8*1024*1024)
+
+static LogTarget log_target = LOG_TARGET_CONSOLE;
+static int log_max_level = LOG_WARNING;
+static int log_facility = LOG_DAEMON;
+
+static int console_fd = STDERR_FILENO;
+
+static bool show_location = false;
+
+/* Akin to glibc's __abort_msg; which is private and we hence cannot
+ * use here. */
+static char *log_abort_msg = NULL;
+
+void log_close_console(void)
+{
+
+ if (console_fd < 0)
+ return;
+
+ if (getpid() == 1) {
+ if (console_fd >= 3)
+ close_nointr_nofail(console_fd);
+
+ console_fd = -1;
+ }
+}
+
+static int log_open_console(void)
+{
+
+ if (console_fd >= 0)
+ return 0;
+
+ if (getpid() == 1) {
+
+ console_fd = open_terminal("/dev/console", O_WRONLY | O_NOCTTY | O_CLOEXEC);
+ if (console_fd < 0) {
+ log_error("Failed to open /dev/console for logging: %s", strerror(-console_fd));
+ return console_fd;
+ }
+
+ log_debug("Successfully opened /dev/console for logging.");
+ } else
+ console_fd = STDERR_FILENO;
+
+ return 0;
+}
+
+int log_open(void)
+{
+ return log_open_console();
+}
+
+void log_close(void)
+{
+ log_close_console();
+}
+
+void log_set_max_level(int level)
+{
+ assert((level & LOG_PRIMASK) == level);
+
+ log_max_level = level;
+}
+
+void log_set_facility(int facility)
+{
+ log_facility = facility;
+}
+
+static int write_to_console(int level, const char *file, unsigned int line, const char *func, const char *buffer)
+{
+ struct iovec iovec[5];
+ unsigned int n = 0;
+
+ // might be useful going ahead
+ UNUSED(level);
+
+ if (console_fd < 0)
+ return 0;
+
+ zero(iovec);
+
+ IOVEC_SET_STRING(iovec[n++], "dracut-install: ");
+
+ if (show_location) {
+ char location[LINE_MAX] = {0};
+ if (snprintf(location, sizeof(location), "(%s:%s:%u) ", file, func, line) <= 0)
+ return -errno;
+ IOVEC_SET_STRING(iovec[n++], location);
+ }
+
+ IOVEC_SET_STRING(iovec[n++], buffer);
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ if (writev(console_fd, iovec, n) < 0)
+ return -errno;
+
+ return 1;
+}
+
+static int log_dispatch(int level, const char *file, unsigned int line, const char *func, char *buffer)
+{
+
+ int r = 0;
+
+ if (log_target == LOG_TARGET_NULL)
+ return 0;
+
+ /* Patch in LOG_DAEMON facility if necessary */
+ if ((level & LOG_FACMASK) == 0)
+ level = log_facility | LOG_PRI(level);
+
+ do {
+ char *e;
+ int k = 0;
+
+ buffer += strspn(buffer, NEWLINE);
+
+ if (buffer[0] == 0)
+ break;
+
+ if ((e = strpbrk(buffer, NEWLINE)))
+ *(e++) = 0;
+
+ k = write_to_console(level, file, line, func, buffer);
+ if (k < 0)
+ return k;
+ buffer = e;
+ } while (buffer);
+
+ return r;
+}
+
+int log_metav(int level, const char *file, unsigned int line, const char *func, const char *format, va_list ap)
+{
+ char buffer[LINE_MAX] = {0};
+ int saved_errno, r;
+
+ if (_likely_(LOG_PRI(level) > log_max_level))
+ return 0;
+
+ saved_errno = errno;
+
+ r = vsnprintf(buffer, sizeof(buffer), format, ap);
+ if (r <= 0) {
+ goto end;
+ }
+
+ char_array_0(buffer);
+
+ r = log_dispatch(level, file, line, func, buffer);
+
+end:
+ errno = saved_errno;
+ return r;
+}
+
+int log_meta(int level, const char *file, unsigned int line, const char *func, const char *format, ...)
+{
+
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = log_metav(level, file, line, func, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+_noreturn_ static void log_assert(const char *text, const char *file, unsigned int line, const char *func,
+ const char *format)
+{
+ static char buffer[LINE_MAX];
+
+ if (snprintf(buffer, sizeof(buffer), format, text, file, line, func) > 0) {
+ char_array_0(buffer);
+ log_abort_msg = buffer;
+ log_dispatch(LOG_CRIT, file, line, func, buffer);
+ }
+
+ abort();
+}
+
+#pragma GCC diagnostic pop
+
+_noreturn_ void log_assert_failed(const char *text, const char *file, unsigned int line, const char *func)
+{
+ log_assert(text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.");
+}
+
+_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, unsigned int line, const char *func)
+{
+ log_assert(text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.");
+}
+
+void log_set_target(LogTarget target)
+{
+ assert(target >= 0);
+ assert(target < _LOG_TARGET_MAX);
+
+ log_target = target;
+}
+
+int log_set_target_from_string(const char *e)
+{
+ LogTarget t;
+
+ t = log_target_from_string(e);
+ if (t < 0)
+ return -EINVAL;
+
+ log_set_target(t);
+ return 0;
+}
+
+int log_set_max_level_from_string(const char *e)
+{
+ int t;
+
+ t = log_level_from_string(e);
+ if (t < 0)
+ return t;
+
+ log_set_max_level(t);
+ return 0;
+}
+
+void log_parse_environment(void)
+{
+ const char *e;
+
+ if ((e = getenv("DRACUT_INSTALL_LOG_TARGET"))) {
+ if (log_set_target_from_string(e) < 0)
+ log_warning("Failed to parse log target %s. Ignoring.", e);
+ } else if ((e = getenv("DRACUT_LOG_TARGET"))) {
+ if (log_set_target_from_string(e) < 0)
+ log_warning("Failed to parse log target %s. Ignoring.", e);
+ }
+
+ if ((e = getenv("DRACUT_INSTALL_LOG_LEVEL"))) {
+ if (log_set_max_level_from_string(e) < 0)
+ log_warning("Failed to parse log level %s. Ignoring.", e);
+ } else if ((e = getenv("DRACUT_LOG_LEVEL"))) {
+ if (log_set_max_level_from_string(e) < 0)
+ log_warning("Failed to parse log level %s. Ignoring.", e);
+ }
+}
+
+LogTarget log_get_target(void)
+{
+ return log_target;
+}
+
+int log_get_max_level(void)
+{
+ return log_max_level;
+}
+
+static const char *const log_target_table[] = {
+ [LOG_TARGET_CONSOLE] = "console",
+ [LOG_TARGET_AUTO] = "auto",
+ [LOG_TARGET_SAFE] = "safe",
+ [LOG_TARGET_NULL] = "null"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget);
diff --git a/src/install/log.h b/src/install/log.h
new file mode 100644
index 0000000..08536f3
--- /dev/null
+++ b/src/install/log.h
@@ -0,0 +1,98 @@
+#ifndef foologhfoo
+#define foologhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <syslog.h>
+#include <stdbool.h>
+#include <stdarg.h>
+
+#include "macro.h"
+
+typedef enum LogTarget {
+ LOG_TARGET_CONSOLE,
+ LOG_TARGET_KMSG,
+ LOG_TARGET_JOURNAL,
+ LOG_TARGET_JOURNAL_OR_KMSG,
+ LOG_TARGET_SYSLOG,
+ LOG_TARGET_SYSLOG_OR_KMSG,
+ LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */
+ LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */
+ LOG_TARGET_NULL,
+ _LOG_TARGET_MAX,
+ _LOG_TARGET_INVALID = -1
+} LogTarget;
+
+void log_set_target(LogTarget target);
+void log_set_max_level(int level);
+void log_set_facility(int facility);
+
+int log_set_target_from_string(const char *e);
+int log_set_max_level_from_string(const char *e);
+
+void log_show_color(bool b);
+void log_show_location(bool b);
+
+int log_show_color_from_string(const char *e);
+int log_show_location_from_string(const char *e);
+
+LogTarget log_get_target(void);
+int log_get_max_level(void);
+
+int log_open(void);
+void log_close(void);
+void log_forget_fds(void);
+
+void log_close_syslog(void);
+void log_close_journal(void);
+void log_close_kmsg(void);
+void log_close_console(void);
+
+void log_parse_environment(void);
+
+int log_meta(int level, const char *file, unsigned int line, const char *func,
+ const char *format, ...) _printf_attr_(5, 6);
+
+int log_metav(int level, const char *file, unsigned int line, const char *func, const char *format, va_list ap);
+
+_noreturn_ void log_assert_failed(const char *text, const char *file, unsigned int line, const char *func);
+_noreturn_ void log_assert_failed_unreachable(const char *text, const char *file, unsigned int line, const char *func);
+
+/* This modifies the buffer passed! */
+int log_dump_internal(int level, const char *file, int line, const char *func, char *buffer);
+
+#define log_full(level, ...) log_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define log_debug(...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_info(...) log_meta(LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_notice(...) log_meta(LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_warning(...) log_meta(LOG_WARNING, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_error(...) log_meta(LOG_ERR, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
+/* This modifies the buffer passed! */
+#define log_dump(level, buffer) log_dump_internal(level, __FILE__, __LINE__, __func__, buffer)
+
+const char *log_target_to_string(LogTarget target);
+LogTarget log_target_from_string(const char *s);
+
+const char *log_level_to_string(int i);
+int log_level_from_string(const char *s);
+
+#endif
diff --git a/src/install/macro.h b/src/install/macro.h
new file mode 100644
index 0000000..cc426c9
--- /dev/null
+++ b/src/install/macro.h
@@ -0,0 +1,299 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <inttypes.h>
+
+#define _printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#define _sentinel_ __attribute__ ((sentinel))
+#define _noreturn_ __attribute__((noreturn))
+#define _unused_ __attribute__ ((unused))
+#define _destructor_ __attribute__ ((destructor))
+#define _pure_ __attribute__ ((pure))
+#define _const_ __attribute__ ((const))
+#define _deprecated_ __attribute__ ((deprecated))
+#define _packed_ __attribute__ ((packed))
+#define _malloc_ __attribute__ ((malloc))
+#define _weak_ __attribute__ ((weak))
+#define _likely_(x) (__builtin_expect(!!(x),1))
+#define _unlikely_(x) (__builtin_expect(!!(x),0))
+#define _public_ __attribute__ ((visibility("default")))
+#define _hidden_ __attribute__ ((visibility("hidden")))
+#define _weakref_(x) __attribute__((weakref(#x)))
+#define _introspect_(x) __attribute__((section("introspect." x)))
+#define _alignas_(x) __attribute__((aligned(__alignof(x))))
+#define _cleanup_(x) __attribute__((cleanup(x)))
+
+/* automake test harness */
+#define EXIT_TEST_SKIP 77
+
+#define XSTRINGIFY(x) #x
+#define STRINGIFY(x) XSTRINGIFY(x)
+
+/* Rounds up */
+
+#define ALIGN4(l) (((l) + 3) & ~3)
+#define ALIGN8(l) (((l) + 7) & ~7)
+
+#if __SIZEOF_POINTER__ == 8
+#define ALIGN(l) ALIGN8(l)
+#elif __SIZEOF_POINTER__ == 4
+#define ALIGN(l) ALIGN4(l)
+#else
+#error "Wut? Pointers are neither 4 nor 8 bytes long?"
+#endif
+
+#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) p))
+#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) p))
+#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) p))
+
+static inline size_t ALIGN_TO(size_t l, size_t ali)
+{
+ return ((l + ali - 1) & ~(ali - 1));
+}
+
+#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) p))
+
+#define ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
+
+/*
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) \
+ __extension__ ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) ); \
+ })
+
+#undef MAX
+#define MAX(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+
+#define MAX3(x,y,z) \
+ __extension__ ({ \
+ typeof(x) _c = MAX(x,y); \
+ MAX(_c, z); \
+ })
+
+#undef MIN
+#define MIN(a,b) \
+ __extension__ ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+ })
+
+#ifndef CLAMP
+#define CLAMP(x, low, high) \
+ __extension__ ({ \
+ typeof(x) _x = (x); \
+ typeof(low) _low = (low); \
+ typeof(high) _high = (high); \
+ ((_x > _high) ? _high : ((_x < _low) ? _low : _x)); \
+ })
+#endif
+
+#define assert_se(expr) \
+ do { \
+ if (_unlikely_(!(expr))) \
+ log_assert_failed(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false) \
+
+/* We override the glibc assert() here. */
+#undef assert
+#ifdef NDEBUG
+#define assert(expr) do {} while(false)
+#else
+#define assert(expr) assert_se(expr)
+#endif
+
+#define assert_not_reached(t) \
+ do { \
+ log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false)
+
+#if defined(static_assert)
+#define assert_cc(expr) \
+ do { \
+ static_assert(expr, #expr); \
+ } while (false)
+#else
+#define assert_cc(expr) \
+ do { \
+ switch (0) { \
+ case 0: \
+ case !!(expr): \
+ ; \
+ } \
+ } while (false)
+#endif
+
+#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p)))
+#define UINT_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p)))
+#define ULONG_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define INT_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define TO_INT32(p) ((int32_t) ((intptr_t) (p)))
+#define INT32_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define PTR_TO_LONG(p) ((long) ((intptr_t) (p)))
+#define LONG_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define memzero(x,l) (memset((x), 0, (l)))
+#define zero(x) (memzero(&(x), sizeof(x)))
+
+#define CHAR_TO_STR(x) ((char[2]) { x, 0 })
+
+#define char_array_0(x) x[sizeof(x)-1] = 0;
+
+#define IOVEC_SET_STRING(i, s) \
+ do { \
+ struct iovec *_i = &(i); \
+ char *_s = (char *)(s); \
+ _i->iov_base = _s; \
+ _i->iov_len = strlen(_s); \
+ } while(false)
+
+static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned int n)
+{
+ unsigned int j;
+ size_t r = 0;
+
+ for (j = 0; j < n; j++)
+ r += i[j].iov_len;
+
+ return r;
+}
+
+static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned int n, size_t k)
+{
+ unsigned int j;
+
+ for (j = 0; j < n; j++) {
+ size_t sub;
+
+ if (_unlikely_(k == 0))
+ break;
+
+ sub = MIN(i[j].iov_len, k);
+ i[j].iov_len -= sub;
+ i[j].iov_base = (uint8_t *) i[j].iov_base + sub;
+ k -= sub;
+ }
+
+ return k;
+}
+
+#define VA_FORMAT_ADVANCE(format, ap) \
+do { \
+ int _argtypes[128]; \
+ size_t _i, _k; \
+ _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
+ assert(_k < ELEMENTSOF(_argtypes)); \
+ for (_i = 0; _i < _k; _i++) { \
+ if (_argtypes[_i] & PA_FLAG_PTR) { \
+ (void) va_arg(ap, void*); \
+ continue; \
+ } \
+ \
+ switch (_argtypes[_i]) { \
+ case PA_INT: \
+ case PA_INT|PA_FLAG_SHORT: \
+ case PA_CHAR: \
+ (void) va_arg(ap, int); \
+ break; \
+ case PA_INT|PA_FLAG_LONG: \
+ (void) va_arg(ap, long int); \
+ break; \
+ case PA_INT|PA_FLAG_LONG_LONG: \
+ (void) va_arg(ap, long long int); \
+ break; \
+ case PA_WCHAR: \
+ (void) va_arg(ap, wchar_t); \
+ break; \
+ case PA_WSTRING: \
+ case PA_STRING: \
+ case PA_POINTER: \
+ (void) va_arg(ap, void*); \
+ break; \
+ case PA_FLOAT: \
+ case PA_DOUBLE: \
+ (void) va_arg(ap, double); \
+ break; \
+ case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \
+ (void) va_arg(ap, long double); \
+ break; \
+ default: \
+ assert_not_reached("Unknown format string argument."); \
+ } \
+ } \
+} while(false)
+
+/* Because statfs.t_type can be int on some architectures, we have to cast
+ * the const magic to the type, otherwise the compiler warns about
+ * signed/unsigned comparison, because the magic can be 32 bit unsigned.
+ */
+#define F_TYPE_CMP(a, b) (a == (typeof(a)) b)
+
+/* Returns the number of chars needed to format variables of the
+ * specified type as a decimal string. Adds in extra space for a
+ * negative '-' prefix. */
+
+#define DECIMAL_STR_MAX(type) \
+ (1+(sizeof(type) <= 1 ? 3 : \
+ sizeof(type) <= 2 ? 5 : \
+ sizeof(type) <= 4 ? 10 : \
+ sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)])))
+
+/*
+ * Takes inspiration from Rust's Option::take() method: reads and returns a pointer.
+ * But at the same time resets it to NULL.
+ * See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take
+ */
+#define TAKE_PTR(ptr) \
+ ({ \
+ typeof(ptr) _ptr_ = (ptr); \
+ (ptr) = NULL; \
+ _ptr_; \
+ })
+
+/* Use to suppress unused variable/function arg warning */
+#define UNUSED(var) ((void)var)
+
+#include "log.h"
diff --git a/src/install/strv.c b/src/install/strv.c
new file mode 100644
index 0000000..02b0507
--- /dev/null
+++ b/src/install/strv.c
@@ -0,0 +1,616 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "util.h"
+#include "strv.h"
+
+char *strv_find(char **l, const char *name)
+{
+ char **i;
+
+ assert(name);
+
+ STRV_FOREACH(i, l) {
+ if (streq(*i, name))
+ return *i;
+ }
+
+ return NULL;
+}
+
+char *strv_find_prefix(char **l, const char *name)
+{
+ char **i;
+
+ assert(name);
+
+ STRV_FOREACH(i, l) {
+ if (startswith(*i, name))
+ return *i;
+ }
+
+ return NULL;
+}
+
+void strv_free(char **l)
+{
+ char **k;
+
+ if (!l)
+ return;
+
+ for (k = l; *k; k++)
+ free(*k);
+
+ free(l);
+}
+
+char **strv_copy(char *const *l)
+{
+ char **r, **k;
+
+ k = r = new(char *, strv_length(l) + 1);
+ if (!r)
+ return NULL;
+
+ if (l)
+ for (; *l; k++, l++) {
+ *k = strdup(*l);
+ if (!*k) {
+ strv_free(r);
+ return NULL;
+ }
+ }
+
+ *k = NULL;
+ return r;
+}
+
+unsigned int strv_length(char *const *l)
+{
+ unsigned int n = 0;
+
+ if (!l)
+ return 0;
+
+ for (; *l; l++)
+ n++;
+
+ return n;
+}
+
+char **strv_new_ap(const char *x, va_list ap)
+{
+ const char *s;
+ char **a;
+ unsigned int n = 0, i = 0;
+ va_list aq;
+
+ /* As a special trick we ignore all listed strings that equal
+ * (const char*) -1. This is supposed to be used with the
+ * STRV_IFNOTNULL() macro to include possibly NULL strings in
+ * the string list. */
+
+ if (x) {
+ n = x == (const char *)-1 ? 0 : 1;
+
+ va_copy(aq, ap);
+ while ((s = va_arg(aq, const char *))) {
+ if (s == (const char *)-1)
+ continue;
+
+ n++;
+ }
+
+ va_end(aq);
+ }
+
+ a = new(char *, n + 1);
+ if (!a)
+ return NULL;
+
+ if (x) {
+ if (x != (const char *)-1) {
+ a[i] = strdup(x);
+ if (!a[i])
+ goto fail;
+ i++;
+ }
+
+ while ((s = va_arg(ap, const char *))) {
+
+ if (s == (const char *)-1)
+ continue;
+
+ a[i] = strdup(s);
+ if (!a[i])
+ goto fail;
+
+ i++;
+ }
+ }
+
+ a[i] = NULL;
+
+ return a;
+
+fail:
+ strv_free(a);
+ return NULL;
+}
+
+char **strv_new(const char *x, ...)
+{
+ char **r;
+ va_list ap;
+
+ va_start(ap, x);
+ r = strv_new_ap(x, ap);
+ va_end(ap);
+
+ return r;
+}
+
+char **strv_merge(char **a, char **b)
+{
+ char **r, **k;
+
+ if (!a)
+ return strv_copy(b);
+
+ if (!b)
+ return strv_copy(a);
+
+ r = new(char *, strv_length(a) + strv_length(b) + 1);
+ if (!r)
+ return NULL;
+
+ for (k = r; *a; k++, a++) {
+ *k = strdup(*a);
+ if (!*k)
+ goto fail;
+ }
+
+ for (; *b; k++, b++) {
+ *k = strdup(*b);
+ if (!*k)
+ goto fail;
+ }
+
+ *k = NULL;
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+}
+
+char **strv_merge_concat(char **a, char **b, const char *suffix)
+{
+ char **r, **k;
+
+ /* Like strv_merge(), but appends suffix to all strings in b, before adding */
+
+ if (!b)
+ return strv_copy(a);
+
+ r = new(char *, strv_length(a) + strv_length(b) + 1);
+ if (!r)
+ return NULL;
+
+ k = r;
+ if (a)
+ for (; *a; k++, a++) {
+ *k = strdup(*a);
+ if (!*k)
+ goto fail;
+ }
+
+ for (; *b; k++, b++) {
+ *k = strappend(*b, suffix);
+ if (!*k)
+ goto fail;
+ }
+
+ *k = NULL;
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+
+}
+
+char **strv_split(const char *s, const char *separator)
+{
+ char *state;
+ char *w;
+ size_t l;
+ unsigned int n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_SEPARATOR(w, l, s, separator, state) {
+ n++;
+ }
+
+ r = new(char *, n + 1);
+ if (!r)
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_SEPARATOR(w, l, s, separator, state) {
+ r[i] = strndup(w, l);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+
+ i++;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char **strv_split_quoted(const char *s)
+{
+ char *state;
+ char *w;
+ size_t l;
+ unsigned int n, i;
+ char **r;
+
+ assert(s);
+
+ n = 0;
+ FOREACH_WORD_QUOTED(w, l, s, state) {
+ n++;
+ }
+
+ r = new(char *, n + 1);
+ if (!r)
+ return NULL;
+
+ i = 0;
+ FOREACH_WORD_QUOTED(w, l, s, state) {
+ r[i] = cunescape_length(w, l);
+ if (!r[i]) {
+ strv_free(r);
+ return NULL;
+ }
+ i++;
+ }
+
+ r[i] = NULL;
+ return r;
+}
+
+char **strv_split_newlines(const char *s)
+{
+ char **l;
+ unsigned int n;
+
+ assert(s);
+
+ /* Special version of strv_split() that splits on newlines and
+ * suppresses an empty string at the end */
+
+ l = strv_split(s, NEWLINE);
+ if (!l)
+ return NULL;
+
+ n = strv_length(l);
+ if (n == 0)
+ return l;
+
+ if (isempty(l[n - 1])) {
+ free(l[n - 1]);
+ l[n - 1] = NULL;
+ }
+
+ return l;
+}
+
+char *strv_join(char **l, const char *separator)
+{
+ char *r, *e;
+ char **s;
+ size_t n, k;
+
+ if (!separator)
+ separator = " ";
+
+ k = strlen(separator);
+
+ n = 0;
+ STRV_FOREACH(s, l) {
+ if (n != 0)
+ n += k;
+ n += strlen(*s);
+ }
+
+ r = new(char, n + 1);
+ if (!r)
+ return NULL;
+
+ e = r;
+ STRV_FOREACH(s, l) {
+ if (e != r)
+ e = stpcpy(e, separator);
+
+ e = stpcpy(e, *s);
+ }
+
+ *e = 0;
+
+ return r;
+}
+
+char **strv_append(char **l, const char *s)
+{
+ char **r, **k;
+
+ if (!l)
+ return strv_new(s, NULL);
+
+ if (!s)
+ return strv_copy(l);
+
+ r = new(char *, strv_length(l) + 2);
+ if (!r)
+ return NULL;
+
+ for (k = r; *l; k++, l++) {
+ *k = strdup(*l);
+ if (!*k)
+ goto fail;
+ }
+
+ k[0] = strdup(s);
+ if (!k[0])
+ goto fail;
+
+ k[1] = NULL;
+ return r;
+
+fail:
+ strv_free(r);
+ return NULL;
+}
+
+int strv_push(char ***l, char *value)
+{
+ char **c;
+ unsigned int n;
+
+ if (!value)
+ return 0;
+
+ n = strv_length(*l);
+ c = realloc(*l, sizeof(char *) * (n + 2));
+ if (!c)
+ return -ENOMEM;
+
+ c[n] = value;
+ c[n + 1] = NULL;
+
+ *l = c;
+ return 0;
+}
+
+int strv_extend(char ***l, const char *value)
+{
+ char *v;
+ int r;
+
+ if (!value)
+ return 0;
+
+ v = strdup(value);
+ if (!v)
+ return -ENOMEM;
+
+ r = strv_push(l, v);
+ if (r < 0)
+ free(v);
+
+ return r;
+}
+
+char **strv_uniq(char **l)
+{
+ char **i;
+
+ /* Drops duplicate entries. The first identical string will be
+ * kept, the others dropped */
+
+ STRV_FOREACH(i, l) {
+ strv_remove(i + 1, *i);
+ }
+ return l;
+}
+
+char **strv_remove(char **l, const char *s)
+{
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(s);
+
+ /* Drops every occurrence of s in the string list, edits
+ * in-place. */
+
+ for (f = t = l; *f; f++) {
+
+ if (streq(*f, s)) {
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return l;
+}
+
+char **strv_remove_prefix(char **l, const char *s)
+{
+ char **f, **t;
+
+ if (!l)
+ return NULL;
+
+ assert(s);
+
+ /* Drops every occurrence of a string prefixed with s in the
+ * string list, edits in-place. */
+
+ for (f = t = l; *f; f++) {
+
+ if (startswith(*f, s)) {
+ free(*f);
+ continue;
+ }
+
+ *(t++) = *f;
+ }
+
+ *t = NULL;
+ return l;
+}
+
+char **strv_parse_nulstr(const char *s, size_t l)
+{
+ const char *p;
+ unsigned int c = 0, i = 0;
+ char **v;
+
+ assert(s || l == 0);
+
+ if (l == 0)
+ return new0(char *, 1);
+
+ for (p = s; p < s + l; p++)
+ if (*p == 0)
+ c++;
+
+ if (s[l - 1] != 0)
+ c++;
+
+ v = new0(char *, c + 1);
+ if (!v)
+ return NULL;
+
+ p = s;
+ while (p < s + l) {
+ const char *e;
+
+ e = memchr(p, 0, s + l - p);
+
+ v[i] = strndup(p, e ? e - p : s + l - p);
+ if (!v[i]) {
+ strv_free(v);
+ return NULL;
+ }
+
+ i++;
+
+ if (!e)
+ break;
+
+ p = e + 1;
+ }
+
+ assert(i == c);
+
+ return v;
+}
+
+char **strv_split_nulstr(const char *s)
+{
+ const char *i;
+ char **r = NULL;
+
+ NULSTR_FOREACH(i, s) {
+ if (strv_extend(&r, i) < 0) {
+ strv_free(r);
+ return NULL;
+ }
+ }
+
+ if (!r)
+ return strv_new(NULL, NULL);
+
+ return r;
+}
+
+bool strv_overlap(char **a, char **b)
+{
+ char **i, **j;
+
+ STRV_FOREACH(i, a) {
+ STRV_FOREACH(j, b) {
+ if (streq(*i, *j))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int str_compare(const void *_a, const void *_b)
+{
+ const char **a = (const char **)_a, **b = (const char **)_b;
+
+ return strcmp(*a, *b);
+}
+
+char **strv_sort(char **l)
+{
+
+ if (strv_isempty(l))
+ return l;
+
+ qsort(l, strv_length(l), sizeof(char *), str_compare);
+ return l;
+}
+
+void strv_print(char **l)
+{
+ char **s;
+
+ if (!l)
+ return;
+
+ STRV_FOREACH(s, l) {
+ puts(*s);
+ }
+}
diff --git a/src/install/strv.h b/src/install/strv.h
new file mode 100644
index 0000000..cf99f5d
--- /dev/null
+++ b/src/install/strv.h
@@ -0,0 +1,118 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+#include "util.h"
+
+char *strv_find(char **l, const char *name) _pure_;
+char *strv_find_prefix(char **l, const char *name) _pure_;
+
+void strv_free(char **l);
+DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free);
+#define _cleanup_strv_free_ _cleanup_(strv_freep)
+
+char **strv_copy(char *const *l);
+unsigned int strv_length(char *const *l) _pure_;
+
+char **strv_merge(char **a, char **b);
+char **strv_merge_concat(char **a, char **b, const char *suffix);
+char **strv_append(char **l, const char *s);
+int strv_extend(char ***l, const char *value);
+int strv_push(char ***l, char *value);
+
+char **strv_remove(char **l, const char *s);
+char **strv_remove_prefix(char **l, const char *s);
+char **strv_uniq(char **l);
+
+#define strv_contains(l, s) (!!strv_find((l), (s)))
+
+char **strv_new(const char *x, ...) _sentinel_;
+char **strv_new_ap(const char *x, va_list ap);
+
+static inline const char *STRV_IFNOTNULL(const char *x)
+{
+ return x ? x : (const char *)-1;
+}
+
+static inline bool strv_isempty(char *const *l)
+{
+ return !l || !*l;
+}
+
+char **strv_split(const char *s, const char *separator);
+char **strv_split_quoted(const char *s);
+char **strv_split_newlines(const char *s);
+
+char *strv_join(char **l, const char *separator);
+
+char **strv_parse_nulstr(const char *s, size_t l);
+char **strv_split_nulstr(const char *s);
+
+bool strv_overlap(char **a, char **b) _pure_;
+
+#define STRV_FOREACH(s, l) \
+ for ((s) = (l); (s) && *(s); (s)++)
+
+#define STRV_FOREACH_BACKWARDS(s, l) \
+ STRV_FOREACH(s, l) \
+ ; \
+ for ((s)--; (l) && ((s) >= (l)); (s)--)
+
+#define STRV_FOREACH_PAIR(x, y, l) \
+ for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
+
+char **strv_sort(char **l);
+void strv_print(char **l);
+
+#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL }))
+
+#define STRV_MAKE_EMPTY ((char*[1]) { NULL })
+
+#define strv_from_stdarg_alloca(first) \
+ ({ \
+ char **_l; \
+ \
+ if (!first) \
+ _l = (char**) &first; \
+ else { \
+ unsigned int _n; \
+ va_list _ap; \
+ \
+ _n = 1; \
+ va_start(_ap, first); \
+ while (va_arg(_ap, char*)) \
+ _n++; \
+ va_end(_ap); \
+ \
+ _l = newa(char*, _n+1); \
+ _l[_n = 0] = (char*) first; \
+ va_start(_ap, first); \
+ for (;;) { \
+ _l[++_n] = va_arg(_ap, char*); \
+ if (!_l[_n]) \
+ break; \
+ } \
+ va_end(_ap); \
+ } \
+ _l; \
+ })
diff --git a/src/install/util.c b/src/install/util.c
new file mode 100644
index 0000000..5f32f64
--- /dev/null
+++ b/src/install/util.c
@@ -0,0 +1,574 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <libgen.h>
+
+#include "util.h"
+
+#define gettid() ((pid_t) syscall(SYS_gettid))
+
+size_t page_size(void)
+{
+ static __thread size_t pgsz = 0;
+ long r;
+
+ if (_likely_(pgsz > 0))
+ return pgsz;
+
+ assert_se((r = sysconf(_SC_PAGESIZE)) > 0);
+
+ pgsz = (size_t)r;
+
+ return pgsz;
+}
+
+bool endswith(const char *s, const char *postfix)
+{
+ size_t sl, pl;
+
+ assert(s);
+ assert(postfix);
+
+ sl = strlen(s);
+ pl = strlen(postfix);
+
+ if (pl == 0)
+ return true;
+
+ if (sl < pl)
+ return false;
+
+ return memcmp(s + sl - pl, postfix, pl) == 0;
+}
+
+int close_nointr(int fd)
+{
+ assert(fd >= 0);
+
+ for (;;) {
+ int r;
+
+ r = close(fd);
+ if (r >= 0)
+ return r;
+
+ if (errno != EINTR)
+ return -errno;
+ }
+}
+
+void close_nointr_nofail(int fd)
+{
+ int saved_errno = errno;
+
+ /* like close_nointr() but cannot fail, and guarantees errno
+ * is unchanged */
+
+ assert_se(close_nointr(fd) == 0);
+
+ errno = saved_errno;
+}
+
+int open_terminal(const char *name, int mode)
+{
+ int fd, r;
+ unsigned int c = 0;
+
+ /*
+ * If a TTY is in the process of being closed opening it might
+ * cause EIO. This is horribly awful, but unlikely to be
+ * changed in the kernel. Hence we work around this problem by
+ * retrying a couple of times.
+ *
+ * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245
+ */
+
+ for (;;) {
+ if ((fd = open(name, mode)) >= 0)
+ break;
+
+ if (errno != EIO)
+ return -errno;
+
+ if (c >= 20)
+ return -errno;
+
+ usleep(50 * USEC_PER_MSEC);
+ c++;
+ }
+
+ if (fd < 0)
+ return -errno;
+
+ if ((r = isatty(fd)) < 0) {
+ close_nointr_nofail(fd);
+ return -errno;
+ }
+
+ if (!r) {
+ close_nointr_nofail(fd);
+ return -ENOTTY;
+ }
+
+ return fd;
+}
+
+bool streq_ptr(const char *a, const char *b)
+{
+
+ /* Like streq(), but tries to make sense of NULL pointers */
+
+ if (a && b)
+ return streq(a, b);
+
+ if (!a && !b)
+ return true;
+
+ return false;
+}
+
+bool is_main_thread(void)
+{
+ static __thread int cached = 0;
+
+ if (_unlikely_(cached == 0))
+ cached = getpid() == gettid()? 1 : -1;
+
+ return cached > 0;
+}
+
+int safe_atou(const char *s, unsigned int *ret_u)
+{
+ char *x = NULL;
+ unsigned long l;
+
+ assert(s);
+ assert(ret_u);
+
+ errno = 0;
+ l = strtoul(s, &x, 0);
+
+ if (!x || *x || errno)
+ return errno ? -errno : -EINVAL;
+
+ if ((unsigned long)(unsigned int)l != l)
+ return -ERANGE;
+
+ *ret_u = (unsigned int)l;
+ return 0;
+}
+
+static const char *const log_level_table[] = {
+ [LOG_EMERG] = "emerg",
+ [LOG_ALERT] = "alert",
+ [LOG_CRIT] = "crit",
+ [LOG_ERR] = "err",
+ [LOG_WARNING] = "warning",
+ [LOG_NOTICE] = "notice",
+ [LOG_INFO] = "info",
+ [LOG_DEBUG] = "debug"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(log_level, int);
+
+char *strnappend(const char *s, const char *suffix, size_t b)
+{
+ size_t a;
+ char *r;
+
+ if (!s && !suffix)
+ return strdup("");
+
+ if (!s)
+ return strndup(suffix, b);
+
+ if (!suffix)
+ return strdup(s);
+
+ assert(s);
+ assert(suffix);
+
+ a = strlen(s);
+ if (b > ((size_t)-1) - a)
+ return NULL;
+
+ r = new(char, a + b + 1);
+ if (!r)
+ return NULL;
+
+ memcpy(r, s, a);
+ memcpy(r + a, suffix, b);
+ r[a + b] = 0;
+
+ return r;
+}
+
+char *strappend(const char *s, const char *suffix)
+{
+ return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
+}
+
+char *strjoin(const char *x, ...)
+{
+ va_list ap;
+ size_t l;
+ char *r;
+
+ va_start(ap, x);
+
+ if (x) {
+ l = strlen(x);
+
+ for (;;) {
+ const char *t;
+ size_t n;
+
+ t = va_arg(ap, const char *);
+ if (!t)
+ break;
+
+ n = strlen(t);
+ if (n > ((size_t)-1) - l) {
+ va_end(ap);
+ return NULL;
+ }
+
+ l += n;
+ }
+ } else
+ l = 0;
+
+ va_end(ap);
+
+ r = new(char, l + 1);
+ if (!r)
+ return NULL;
+
+ if (x) {
+ char *p;
+
+ p = stpcpy(r, x);
+
+ va_start(ap, x);
+
+ for (;;) {
+ const char *t;
+
+ t = va_arg(ap, const char *);
+ if (!t)
+ break;
+
+ p = stpcpy(p, t);
+ }
+
+ va_end(ap);
+ } else
+ r[0] = 0;
+
+ return r;
+}
+
+char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix)
+{
+ char *r, *t;
+ const char *f;
+ size_t pl;
+
+ assert(s);
+
+ /* Undoes C style string escaping, and optionally prefixes it. */
+
+ pl = prefix ? strlen(prefix) : 0;
+
+ r = new(char, pl + length + 1);
+ if (!r)
+ return r;
+
+ if (prefix)
+ memcpy(r, prefix, pl);
+
+ for (f = s, t = r + pl; f < s + length; f++) {
+
+ if (*f != '\\') {
+ *(t++) = *f;
+ continue;
+ }
+
+ f++;
+
+ switch (*f) {
+
+ case 'a':
+ *(t++) = '\a';
+ break;
+ case 'b':
+ *(t++) = '\b';
+ break;
+ case 'f':
+ *(t++) = '\f';
+ break;
+ case 'n':
+ *(t++) = '\n';
+ break;
+ case 'r':
+ *(t++) = '\r';
+ break;
+ case 't':
+ *(t++) = '\t';
+ break;
+ case 'v':
+ *(t++) = '\v';
+ break;
+ case '\\':
+ *(t++) = '\\';
+ break;
+ case '"':
+ *(t++) = '"';
+ break;
+ case '\'':
+ *(t++) = '\'';
+ break;
+
+ case 's':
+ /* This is an extension of the XDG syntax files */
+ *(t++) = ' ';
+ break;
+
+ case 'x': {
+ /* hexadecimal encoding */
+ int a, b;
+
+ a = unhexchar(f[1]);
+ b = unhexchar(f[2]);
+
+ if (a < 0 || b < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = 'x';
+ } else {
+ *(t++) = (char)((a << 4) | b);
+ f += 2;
+ }
+
+ break;
+ }
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ /* octal encoding */
+ int a, b, c;
+
+ a = unoctchar(f[0]);
+ b = unoctchar(f[1]);
+ c = unoctchar(f[2]);
+
+ if (a < 0 || b < 0 || c < 0) {
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = f[0];
+ } else {
+ *(t++) = (char)((a << 6) | (b << 3) | c);
+ f += 2;
+ }
+
+ break;
+ }
+
+ case 0:
+ /* premature end of string. */
+ *(t++) = '\\';
+ goto finish;
+
+ default:
+ /* Invalid escape code, let's take it literal then */
+ *(t++) = '\\';
+ *(t++) = *f;
+ break;
+ }
+ }
+
+finish:
+ *t = 0;
+ return r;
+}
+
+char *cunescape_length(const char *s, size_t length)
+{
+ return cunescape_length_with_prefix(s, length, NULL);
+}
+
+/* Split a string into words, but consider strings enclosed in '' and
+ * "" as words even if they include spaces. */
+char *split_quoted(const char *c, size_t *l, char **state)
+{
+ const char *current, *e;
+ bool escaped = false;
+
+ assert(c);
+ assert(l);
+ assert(state);
+
+ current = *state ? *state : c;
+
+ current += strspn(current, WHITESPACE);
+
+ if (*current == 0)
+ return NULL;
+
+ else if (*current == '\'') {
+ current++;
+
+ for (e = current; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ else if (*e == '\'')
+ break;
+ }
+
+ *l = e - current;
+ *state = (char *)(*e == 0 ? e : e + 1);
+
+ } else if (*current == '\"') {
+ current++;
+
+ for (e = current; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ else if (*e == '\"')
+ break;
+ }
+
+ *l = e - current;
+ *state = (char *)(*e == 0 ? e : e + 1);
+
+ } else {
+ for (e = current; *e; e++) {
+ if (escaped)
+ escaped = false;
+ else if (*e == '\\')
+ escaped = true;
+ else if (strchr(WHITESPACE, *e))
+ break;
+ }
+ *l = e - current;
+ *state = (char *)e;
+ }
+
+ return (char *)current;
+}
+
+/* Split a string into words. */
+char *split(const char *c, size_t *l, const char *separator, char **state)
+{
+ char *current;
+
+ current = *state ? *state : (char *)c;
+
+ if (!*current || *c == 0)
+ return NULL;
+
+ current += strspn(current, separator);
+ *l = strcspn(current, separator);
+ *state = current + *l;
+
+ return (char *)current;
+}
+
+int unhexchar(char c)
+{
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ return -1;
+}
+
+int unoctchar(char c)
+{
+
+ if (c >= '0' && c <= '7')
+ return c - '0';
+
+ return -1;
+}
+
+int dracut_asprintf(char **restrict strp, const char *restrict fmt, ...)
+{
+ int ret = -1;
+ va_list args;
+
+ if (!strp || !fmt) {
+ return ret;
+ }
+
+ va_start(args, fmt);
+ ret = vasprintf(strp, fmt, args);
+ if (ret < 0) {
+ *strp = NULL;
+ }
+ va_end(args);
+
+ return ret;
+}
+
+char *dirname_malloc(const char *path)
+{
+ char *d, *dir, *dir2;
+
+ assert(path);
+
+ d = strdup(path);
+ if (!d)
+ return NULL;
+
+ dir = dirname(d);
+ assert(dir);
+
+ if (dir == d)
+ return d;
+
+ dir2 = strdup(dir);
+ free(d);
+
+ return dir2;
+}
diff --git a/src/install/util.h b/src/install/util.h
new file mode 100644
index 0000000..f022f15
--- /dev/null
+++ b/src/install/util.h
@@ -0,0 +1,609 @@
+#ifndef fooutilhfoo
+#define fooutilhfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#define _GNU_SOURCE
+
+#include <inttypes.h>
+#include <time.h>
+#include <sys/time.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <sched.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/resource.h>
+
+#include "macro.h"
+
+typedef uint64_t usec_t;
+typedef uint64_t nsec_t;
+
+typedef struct dual_timestamp {
+ usec_t realtime;
+ usec_t monotonic;
+} dual_timestamp;
+
+#define MSEC_PER_SEC 1000ULL
+#define USEC_PER_SEC 1000000ULL
+#define USEC_PER_MSEC 1000ULL
+#define NSEC_PER_SEC 1000000000ULL
+#define NSEC_PER_MSEC 1000000ULL
+#define NSEC_PER_USEC 1000ULL
+
+#define USEC_PER_MINUTE (60ULL*USEC_PER_SEC)
+#define NSEC_PER_MINUTE (60ULL*NSEC_PER_SEC)
+#define USEC_PER_HOUR (60ULL*USEC_PER_MINUTE)
+#define NSEC_PER_HOUR (60ULL*NSEC_PER_MINUTE)
+#define USEC_PER_DAY (24ULL*USEC_PER_HOUR)
+#define NSEC_PER_DAY (24ULL*NSEC_PER_HOUR)
+#define USEC_PER_WEEK (7ULL*USEC_PER_DAY)
+#define NSEC_PER_WEEK (7ULL*NSEC_PER_DAY)
+#define USEC_PER_MONTH (2629800ULL*USEC_PER_SEC)
+#define NSEC_PER_MONTH (2629800ULL*NSEC_PER_SEC)
+#define USEC_PER_YEAR (31557600ULL*USEC_PER_SEC)
+#define NSEC_PER_YEAR (31557600ULL*NSEC_PER_SEC)
+
+/* What is interpreted as whitespace? */
+#define WHITESPACE " \t\n\r"
+#define NEWLINE "\n\r"
+#define QUOTES "\"\'"
+#define COMMENTS "#;\n"
+
+#define FORMAT_TIMESTAMP_MAX 64
+#define FORMAT_TIMESTAMP_PRETTY_MAX 256
+#define FORMAT_TIMESPAN_MAX 64
+#define FORMAT_BYTES_MAX 8
+
+#define ANSI_HIGHLIGHT_ON "\x1B[1;39m"
+#define ANSI_HIGHLIGHT_RED_ON "\x1B[1;31m"
+#define ANSI_HIGHLIGHT_GREEN_ON "\x1B[1;32m"
+#define ANSI_HIGHLIGHT_YELLOW_ON "\x1B[1;33m"
+#define ANSI_HIGHLIGHT_OFF "\x1B[0m"
+
+usec_t now(clockid_t clock);
+
+dual_timestamp *dual_timestamp_get(dual_timestamp *ts);
+dual_timestamp *dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
+
+#define dual_timestamp_is_set(ts) ((ts)->realtime > 0)
+
+usec_t timespec_load(const struct timespec *ts);
+struct timespec *timespec_store(struct timespec *ts, usec_t u);
+
+usec_t timeval_load(const struct timeval *tv);
+struct timeval *timeval_store(struct timeval *tv, usec_t u);
+
+size_t page_size(void);
+#define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
+
+#define streq(a,b) (strcmp((a),(b)) == 0)
+#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0)
+
+bool streq_ptr(const char *a, const char *b);
+
+#define new(t, n) ((t*) malloc(sizeof(t)*(n)))
+
+#define new0(t, n) ((t*) calloc((n), sizeof(t)))
+
+#define newa(t, n) ((t*) alloca(sizeof(t)*(n)))
+
+#define newdup(t, p, n) ((t*) memdup(p, sizeof(t)*(n)))
+
+#define malloc0(n) (calloc((n), 1))
+
+static inline const char *yes_no(bool b)
+{
+ return b ? "yes" : "no";
+}
+
+static inline const char *strempty(const char *s)
+{
+ return s ? s : "";
+}
+
+static inline const char *strnull(const char *s)
+{
+ return s ? s : "(null)";
+}
+
+static inline const char *strna(const char *s)
+{
+ return s ? s : "n/a";
+}
+
+static inline bool isempty(const char *p)
+{
+ return !p || !p[0];
+}
+
+static inline const char *startswith(const char *s, const char *prefix)
+{
+ if (strncmp(s, prefix, strlen(prefix)) == 0)
+ return s + strlen(prefix);
+ return NULL;
+}
+
+bool endswith(const char *s, const char *postfix);
+
+bool startswith_no_case(const char *s, const char *prefix);
+
+bool first_word(const char *s, const char *word);
+
+int close_nointr(int fd);
+void close_nointr_nofail(int fd);
+void close_many(const int fds[], unsigned int n_fd);
+
+int parse_boolean(const char *v);
+int parse_usec(const char *t, usec_t *usec);
+int parse_nsec(const char *t, nsec_t *nsec);
+int parse_bytes(const char *t, off_t *bytes);
+int parse_pid(const char *s, pid_t *ret_pid);
+int parse_uid(const char *s, uid_t *ret_uid);
+#define parse_gid(s, ret_uid) parse_uid(s, ret_uid)
+
+int safe_atou(const char *s, unsigned int *ret_u);
+int safe_atoi(const char *s, int *ret_i);
+
+int safe_atollu(const char *s, unsigned long long *ret_u);
+int safe_atolli(const char *s, long long int *ret_i);
+
+#if LONG_MAX == INT_MAX
+static inline int safe_atolu(const char *s, unsigned long *ret_u)
+{
+ assert_cc(sizeof(unsigned long) == sizeof(unsigned int));
+ return safe_atou(s, (unsigned int *)ret_u);
+}
+
+static inline int safe_atoli(const char *s, long int *ret_u)
+{
+ assert_cc(sizeof(long int) == sizeof(int));
+ return safe_atoi(s, (int *)ret_u);
+}
+#else
+static inline int safe_atolu(const char *s, unsigned long *ret_u)
+{
+ assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
+ return safe_atollu(s, (unsigned long long *)ret_u);
+}
+
+static inline int safe_atoli(const char *s, long int *ret_u)
+{
+ assert_cc(sizeof(long int) == sizeof(long long int));
+ return safe_atolli(s, (long long int *)ret_u);
+}
+#endif
+
+static inline int safe_atou32(const char *s, uint32_t *ret_u)
+{
+ assert_cc(sizeof(uint32_t) == sizeof(unsigned int));
+ return safe_atou(s, (unsigned int *)ret_u);
+}
+
+static inline int safe_atoi32(const char *s, int32_t *ret_i)
+{
+ assert_cc(sizeof(int32_t) == sizeof(int));
+ return safe_atoi(s, (int *)ret_i);
+}
+
+static inline int safe_atou64(const char *s, uint64_t *ret_u)
+{
+ assert_cc(sizeof(uint64_t) == sizeof(unsigned long long));
+ return safe_atollu(s, (unsigned long long *)ret_u);
+}
+
+static inline int safe_atoi64(const char *s, int64_t *ret_i)
+{
+ assert_cc(sizeof(int64_t) == sizeof(long long int));
+ return safe_atolli(s, (long long int *)ret_i);
+}
+
+char *split(const char *c, size_t *l, const char *separator, char **state);
+char *split_quoted(const char *c, size_t *l, char **state);
+
+#define FOREACH_WORD(word, length, s, state) \
+ for ((state) = NULL, (word) = split((s), &(length), WHITESPACE, &(state)); (word); (word) = split((s), &(length), WHITESPACE, &(state)))
+
+#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \
+ for ((state) = NULL, (word) = split((s), &(length), (separator), &(state)); (word); (word) = split((s), &(length), (separator), &(state)))
+
+#define FOREACH_WORD_QUOTED(word, length, s, state) \
+ for ((state) = NULL, (word) = split_quoted((s), &(length), &(state)); (word); (word) = split_quoted((s), &(length), &(state)))
+
+pid_t get_parent_of_pid(pid_t pid, pid_t *ppid);
+int get_starttime_of_pid(pid_t pid, unsigned long long *st);
+
+int write_one_line_file(const char *fn, const char *line);
+int write_one_line_file_atomic(const char *fn, const char *line);
+int read_one_line_file(const char *fn, char **line);
+int read_full_file(const char *fn, char **contents, size_t *size);
+
+int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
+int load_env_file(const char *fname, char ***l);
+int write_env_file(const char *fname, char **l);
+
+char *strappend(const char *s, const char *suffix);
+char *strnappend(const char *s, const char *suffix, size_t length);
+
+char *replace_env(const char *format, char **env);
+char **replace_env_argv(char **argv, char **env);
+
+int readlink_malloc(const char *p, char **r);
+int readlink_and_make_absolute(const char *p, char **r);
+int readlink_and_canonicalize(const char *p, char **r);
+
+int reset_all_signal_handlers(void);
+
+char *strstrip(char *s);
+char *delete_chars(char *s, const char *bad);
+char *truncate_nl(char *s);
+
+char *file_in_same_dir(const char *path, const char *filename);
+
+int rmdir_parents(const char *path, const char *stop);
+
+int get_process_comm(pid_t pid, char **name);
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
+int get_process_exe(pid_t pid, char **name);
+int get_process_uid(pid_t pid, uid_t *uid);
+
+char hexchar(int x);
+int unhexchar(char c);
+char octchar(int x);
+int unoctchar(char c);
+char decchar(int x);
+int undecchar(char c);
+
+char *cescape(const char *s);
+char *cunescape(const char *s);
+char *cunescape_length(const char *s, size_t length);
+
+char *xescape(const char *s, const char *bad);
+
+char *bus_path_escape(const char *s);
+char *bus_path_unescape(const char *s);
+
+char *ascii_strlower(char *path);
+
+bool dirent_is_file(const struct dirent *de);
+bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix);
+
+bool ignore_file(const char *filename);
+
+bool chars_intersect(const char *a, const char *b);
+
+char *format_timestamp(char *buf, size_t l, usec_t t);
+char *format_timestamp_pretty(char *buf, size_t l, usec_t t);
+char *format_timespan(char *buf, size_t l, usec_t t);
+
+int make_stdio(int fd);
+int make_null_stdio(void);
+
+unsigned long long random_ull(void);
+
+#define __DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \
+ scope const char *name##_to_string(type i) { \
+ if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \
+ return NULL; \
+ return name##_table[i]; \
+ } \
+ scope type name##_from_string(const char *s) { \
+ type i; \
+ unsigned int u = 0; \
+ assert(s); \
+ for (i = 0; i < (type)ELEMENTSOF(name##_table); i++) \
+ if (name##_table[i] && \
+ streq(name##_table[i], s)) \
+ return i; \
+ if (safe_atou(s, &u) >= 0 && \
+ u < ELEMENTSOF(name##_table)) \
+ return (type) u; \
+ return (type) -1; \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+#define DEFINE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) __DEFINE_STRING_TABLE_LOOKUP(name,type,static)
+
+int fd_nonblock(int fd, bool nonblock);
+int fd_cloexec(int fd, bool cloexec);
+
+int close_all_fds(const int except[], unsigned int n_except);
+
+bool fstype_is_network(const char *fstype);
+
+int chvt(int vt);
+
+int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl);
+int ask(char *ret, const char *replies, const char *text, ...);
+
+int reset_terminal_fd(int fd, bool switch_to_text);
+int reset_terminal(const char *name);
+
+int open_terminal(const char *name, int mode);
+int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm);
+int release_terminal(void);
+
+int flush_fd(int fd);
+
+int ignore_signals(int sig, ...);
+int default_signals(int sig, ...);
+int sigaction_many(const struct sigaction *sa, ...);
+
+int close_pipe(int p[]);
+int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll);
+ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll);
+
+bool is_device_path(const char *path);
+
+int dir_is_empty(const char *path);
+
+void rename_process(const char name[8]);
+
+void sigset_add_many(sigset_t *ss, ...);
+
+char *gethostname_malloc(void);
+bool hostname_is_set(void);
+char *getlogname_malloc(void);
+
+int getttyname_malloc(int fd, char **r);
+int getttyname_harder(int fd, char **r);
+
+int get_ctty_devnr(pid_t pid, dev_t *d);
+int get_ctty(pid_t, dev_t *_devnr, char **r);
+
+int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid);
+
+int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev);
+int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky);
+
+int pipe_eof(int fd);
+
+cpu_set_t *cpu_set_malloc(unsigned int *ncpus);
+
+void status_vprintf(const char *status, bool ellipse, const char *format, va_list ap);
+void status_printf(const char *status, bool ellipse, const char *format, ...);
+void status_welcome(void);
+
+int fd_columns(int fd);
+unsigned int columns(void);
+
+int fd_lines(int fd);
+unsigned int lines(void);
+
+int running_in_chroot(void);
+
+char *ellipsize(const char *s, size_t length, unsigned int percent);
+char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned int percent);
+
+int touch(const char *path);
+
+char *unquote(const char *s, const char *quotes);
+char *normalize_env_assignment(const char *s);
+
+int wait_for_terminate(pid_t pid, siginfo_t *status);
+int wait_for_terminate_and_warn(const char *name, pid_t pid);
+
+_noreturn_ void freeze(void);
+
+bool null_or_empty(struct stat *st);
+int null_or_empty_path(const char *fn);
+
+DIR *xopendirat(int dirfd, const char *name, int flags);
+
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t);
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t);
+
+char *fstab_node_to_udev_node(const char *p);
+
+bool tty_is_vc(const char *tty);
+bool tty_is_vc_resolve(const char *tty);
+bool tty_is_console(const char *tty);
+int vtnr_from_tty(const char *tty);
+const char *default_term_for_tty(const char *tty);
+
+void execute_directory(const char *directory, DIR *_d, char *argv[]);
+
+int kill_and_sigcont(pid_t pid, int sig);
+
+bool nulstr_contains(const char *nulstr, const char *needle);
+
+bool plymouth_running(void);
+
+void parse_syslog_priority(char **p, int *priority);
+void skip_syslog_pid(char **buf);
+void skip_syslog_date(char **buf);
+
+bool hostname_is_valid(const char *s);
+char *hostname_cleanup(char *s);
+
+char *strshorten(char *s, size_t l);
+
+int terminal_vhangup_fd(int fd);
+int terminal_vhangup(const char *name);
+
+int vt_disallocate(const char *name);
+
+int copy_file(const char *from, const char *to);
+int symlink_or_copy(const char *from, const char *to);
+int symlink_or_copy_atomic(const char *from, const char *to);
+
+int fchmod_umask(int fd, mode_t mode);
+
+bool display_is_local(const char *display);
+int socket_from_display(const char *display, char **path);
+
+int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home);
+int get_group_creds(const char **groupname, gid_t *gid);
+
+int in_group(const char *name);
+
+int glob_exists(const char *path);
+
+int dirent_ensure_type(DIR *d, struct dirent *de);
+
+int in_search_path(const char *path, char **search);
+int get_files_in_directory(const char *path, char ***list);
+
+char *join(const char *x, ...) _sentinel_;
+
+bool is_main_thread(void);
+
+bool in_charset(const char *s, const char *charset);
+
+int block_get_whole_disk(dev_t d, dev_t *ret);
+
+int file_is_priv_sticky(const char *p);
+
+int strdup_or_null(const char *a, char **b);
+
+#define NULSTR_FOREACH(i, l) \
+ for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
+
+#define NULSTR_FOREACH_PAIR(i, j, l) \
+ for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i))
+
+const char *ioprio_class_to_string(int i);
+int ioprio_class_from_string(const char *s);
+
+const char *sigchld_code_to_string(int i);
+int sigchld_code_from_string(const char *s);
+
+const char *log_facility_unshifted_to_string(int i);
+int log_facility_unshifted_from_string(const char *s);
+
+const char *log_level_to_string(int i);
+int log_level_from_string(const char *s);
+
+const char *sched_policy_to_string(int i);
+int sched_policy_from_string(const char *s);
+
+const char *rlimit_to_string(int i);
+int rlimit_from_string(const char *s);
+
+const char *ip_tos_to_string(int i);
+int ip_tos_from_string(const char *s);
+
+const char *signal_to_string(int i);
+int signal_from_string(const char *s);
+
+int signal_from_string_try_harder(const char *s);
+
+extern int saved_argc;
+extern char **saved_argv;
+
+bool kexec_loaded(void);
+
+int prot_from_flags(int flags);
+
+char *format_bytes(char *buf, size_t l, off_t t);
+
+int fd_wait_for_event(int fd, int event, usec_t timeout);
+
+void *memdup(const void *p, size_t l);
+
+int is_kernel_thread(pid_t pid);
+
+static inline void freep(void *p)
+{
+ free(*(void **)p);
+}
+
+static inline void fclosep(FILE **f)
+{
+ if (*f)
+ fclose(*f);
+}
+
+static inline void pclosep(FILE **f)
+{
+ if (*f)
+ pclose(*f);
+}
+
+static inline void closep(int *fd)
+{
+ if (*fd >= 0)
+ close_nointr_nofail(*fd);
+}
+
+static inline void closedirp(DIR **d)
+{
+ if (*d)
+ closedir(*d);
+}
+
+static inline void umaskp(mode_t *u)
+{
+ umask(*u);
+}
+
+#define _cleanup_free_ _cleanup_(freep)
+#define _cleanup_fclose_ _cleanup_(fclosep)
+#define _cleanup_pclose_ _cleanup_(pclosep)
+#define _cleanup_close_ _cleanup_(closep)
+#define _cleanup_closedir_ _cleanup_(closedirp)
+#define _cleanup_umask_ _cleanup_(umaskp)
+#define _cleanup_globfree_ _cleanup_(globfree)
+
+int fd_inc_sndbuf(int fd, size_t n);
+int fd_inc_rcvbuf(int fd, size_t n);
+
+int fork_agent(pid_t *pid, const int except[], unsigned int n_except, const char *path, ...);
+
+int setrlimit_closest(int resource, const struct rlimit *rlim);
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value);
+
+int can_sleep(const char *type);
+
+bool is_valid_documentation_url(const char *url);
+
+bool in_initrd(void);
+
+void warn_melody(void);
+
+char *strjoin(const char *x, ...) _sentinel_;
+
+#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
+ static inline void func##p(type *p) { \
+ if (*p) \
+ func(*p); \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+char *split_quoted(const char *c, size_t *l, char **state);
+char *cunescape_length(const char *s, size_t length);
+int unhexchar(char c) _const_;
+int unoctchar(char c) _const_;
+
+int dracut_asprintf(char **restrict strp, const char *restrict fmt, ...);
+char *dirname_malloc(const char *path);
+
+#endif
diff --git a/src/logtee/logtee.c b/src/logtee/logtee.c
new file mode 100644
index 0000000..55520db
--- /dev/null
+++ b/src/logtee/logtee.c
@@ -0,0 +1,71 @@
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+#define BUFLEN 4096
+
+int main(int argc, char *argv[])
+{
+ int fd;
+ int len, slen;
+ int ret;
+ int timeout;
+ char *timeout_env;
+ struct pollfd fds[] = { {
+ .fd = STDIN_FILENO,
+ .events = POLLIN | POLLERR,
+ }
+ };
+
+ timeout_env = getenv("LOGTEE_TIMEOUT_MS");
+ if (timeout_env)
+ timeout = atoi(timeout_env);
+ else
+ timeout = -1;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <file>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, 0644);
+ if (fd == -1) {
+ perror("open");
+ exit(EXIT_FAILURE);
+ }
+
+ fprintf(stderr, "Logging to %s: ", argv[1]);
+
+ slen = 0;
+
+ do {
+ ret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout);
+ if (ret == 0) {
+ fprintf(stderr, "Timed out after %d milliseconds of no output.\n", timeout);
+ exit(EXIT_FAILURE);
+ }
+ len = splice(STDIN_FILENO, NULL, fd, NULL, BUFLEN, SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
+
+ if (len < 0) {
+ if (errno == EAGAIN)
+ continue;
+ perror("tee");
+ exit(EXIT_FAILURE);
+ } else if (len == 0)
+ break;
+ slen += len;
+ if ((slen / BUFLEN) > 0) {
+ fprintf(stderr, ".");
+ }
+ slen = slen % BUFLEN;
+
+ } while (1);
+ close(fd);
+ fprintf(stderr, "\n");
+ exit(EXIT_SUCCESS);
+}
diff --git a/src/skipcpio/skipcpio.c b/src/skipcpio/skipcpio.c
new file mode 100644
index 0000000..f66c186
--- /dev/null
+++ b/src/skipcpio/skipcpio.c
@@ -0,0 +1,207 @@
+/* skipcpio.c
+
+ Copyright (C) 2012 Harald Hoyer
+ Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+
+ This program is free software: you can redistribute it and/or modify
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define PROGRAM_VERSION_STRING "1"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CPIO_MAGIC "070701"
+#define CPIO_MAGIC_LEN (sizeof(CPIO_MAGIC) - 1)
+
+#define CPIO_END "TRAILER!!!"
+#define CPIO_ENDLEN (sizeof(CPIO_END) - 1)
+
+#define CPIO_ALIGNMENT 4
+
+#define ALIGN_UP(n, a) (((n) + (a) - 1) & (~((a) - 1)))
+
+#define pr_err(fmt, ...) \
+ fprintf(stderr, "ERROR: %s:%d:%s(): " fmt, __FILE__, __LINE__, \
+ __func__, ##__VA_ARGS__)
+
+struct cpio_header {
+ char c_magic[CPIO_MAGIC_LEN];
+ char c_ino[8];
+ char c_mode[8];
+ char c_uid[8];
+ char c_gid[8];
+ char c_nlink[8];
+ char c_mtime[8];
+ char c_filesize[8];
+ char c_dev_maj[8];
+ char c_dev_min[8];
+ char c_rdev_maj[8];
+ char c_rdev_min[8];
+ char c_namesize[8];
+ char c_chksum[8];
+} __attribute__((packed));
+
+struct buf_struct {
+ struct cpio_header h;
+ char filename[CPIO_ENDLEN];
+} __attribute__((packed));
+
+union buf_union {
+ struct buf_struct cpio;
+ char copy_buffer[2048];
+};
+
+static union buf_union buf;
+
+int main(int argc, char **argv)
+{
+ size_t s;
+ long pos = 0;
+ FILE *f = NULL;
+ char *fname = NULL;
+ int ret = EXIT_FAILURE;
+ unsigned long filesize;
+ unsigned long filename_length;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <file>\n", argv[0]);
+ goto end;
+ }
+
+ fname = argv[1];
+ f = fopen(fname, "r");
+ if (f == NULL) {
+ pr_err("Cannot open file '%s'\n", fname);
+ goto end;
+ }
+
+ if ((fread(&buf.cpio, sizeof(buf.cpio), 1, f) != 1) ||
+ ferror(f)) {
+ pr_err("Read error from file '%s'\n", fname);
+ goto end;
+ }
+
+ if (fseek(f, 0, SEEK_SET)) {
+ pr_err("fseek error on file '%s'\n", fname);
+ goto end;
+ }
+
+ /* check, if this is a cpio archive */
+ if (memcmp(buf.cpio.h.c_magic, CPIO_MAGIC, CPIO_MAGIC_LEN)) {
+ goto cat_rest;
+ }
+
+ do {
+ // zero string, spilling into next unused field, to use strtol
+ buf.cpio.h.c_chksum[0] = 0;
+ filename_length = strtoul(buf.cpio.h.c_namesize, NULL, 16);
+ pos = ALIGN_UP(pos + sizeof(struct cpio_header) + filename_length, CPIO_ALIGNMENT);
+
+ // zero string, spilling into next unused field, to use strtol
+ buf.cpio.h.c_dev_maj[0] = 0;
+ filesize = strtoul(buf.cpio.h.c_filesize, NULL, 16);
+ pos = ALIGN_UP(pos + filesize, CPIO_ALIGNMENT);
+
+ if (filename_length == (CPIO_ENDLEN + 1)
+ && strncmp(buf.cpio.filename, CPIO_END, CPIO_ENDLEN) == 0) {
+ if (fseek(f, pos, SEEK_SET)) {
+ pr_err("fseek\n");
+ goto end;
+ }
+ break;
+ }
+
+ if (fseek(f, pos, SEEK_SET)) {
+ pr_err("fseek\n");
+ goto end;
+ }
+
+ if ((fread(&buf.cpio, sizeof(buf.cpio), 1, f) != 1) ||
+ ferror(f)) {
+ pr_err("fread\n");
+ goto end;
+ }
+
+ if (memcmp(buf.cpio.h.c_magic, CPIO_MAGIC, CPIO_MAGIC_LEN)) {
+ pr_err("Corrupt CPIO archive!\n");
+ goto end;
+ }
+ } while (!feof(f));
+
+ if (feof(f)) {
+ /* CPIO_END not found, just cat the whole file */
+ if (fseek(f, 0, SEEK_SET)) {
+ pr_err("fseek\n");
+ goto end;
+ }
+ } else {
+ /* skip zeros */
+ do {
+ size_t i;
+
+ s = fread(buf.copy_buffer, 1, sizeof(buf.copy_buffer) - 1, f);
+ if (ferror(f)) {
+ pr_err("fread\n");
+ goto end;
+ }
+
+ for (i = 0; (i < s) && (buf.copy_buffer[i] == 0); i++) ;
+
+ if (buf.copy_buffer[i]) {
+ pos += i;
+
+ if (fseek(f, pos, SEEK_SET)) {
+ pr_err("fseek\n");
+ goto end;
+ }
+ break;
+ }
+
+ pos += s;
+ } while (!feof(f));
+ }
+
+cat_rest:
+ /* cat out the rest */
+ while (!feof(f)) {
+ s = fread(buf.copy_buffer, 1, sizeof(buf.copy_buffer), f);
+ if (ferror(f)) {
+ pr_err("fread\n");
+ goto end;
+ }
+
+ errno = 0;
+ if (fwrite(buf.copy_buffer, 1, s, stdout) != s) {
+ if (errno != EPIPE)
+ pr_err("fwrite\n");
+ goto end;
+ }
+ }
+
+ ret = EXIT_SUCCESS;
+
+end:
+ if (f) {
+ fclose(f);
+ }
+
+ return ret;
+}
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
new file mode 100644
index 0000000..ab380d2
--- /dev/null
+++ b/src/util/CMakeLists.txt
@@ -0,0 +1,6 @@
+cmake_minimum_required(VERSION 3.17)
+project(dracut-util C)
+
+set(CMAKE_C_STANDARD 99)
+
+add_executable(dracut-util util.c)
diff --git a/src/util/util.c b/src/util/util.c
new file mode 100644
index 0000000..b3498df
--- /dev/null
+++ b/src/util/util.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Parts are copied from the linux kernel
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+// CODE FROM LINUX KERNEL START
+
+#define _U 0x01 /* upper */
+#define _L 0x02 /* lower */
+#define _D 0x04 /* digit */
+#define _C 0x08 /* cntrl */
+#define _P 0x10 /* punct */
+#define _S 0x20 /* white space (space/lf/tab) */
+#define _X 0x40 /* hex digit */
+#define _SP 0x80 /* hard space (0x20) */
+
+const unsigned char _ctype[] = {
+ _C, _C, _C, _C, _C, _C, _C, _C, /* 0-7 */
+ _C, _C | _S, _C | _S, _C | _S, _C | _S, _C | _S, _C, _C, /* 8-15 */
+ _C, _C, _C, _C, _C, _C, _C, _C, /* 16-23 */
+ _C, _C, _C, _C, _C, _C, _C, _C, /* 24-31 */
+ _S | _SP, _P, _P, _P, _P, _P, _P, _P, /* 32-39 */
+ _P, _P, _P, _P, _P, _P, _P, _P, /* 40-47 */
+ _D, _D, _D, _D, _D, _D, _D, _D, /* 48-55 */
+ _D, _D, _P, _P, _P, _P, _P, _P, /* 56-63 */
+ _P, _U | _X, _U | _X, _U | _X, _U | _X, _U | _X, _U | _X, _U, /* 64-71 */
+ _U, _U, _U, _U, _U, _U, _U, _U, /* 72-79 */
+ _U, _U, _U, _U, _U, _U, _U, _U, /* 80-87 */
+ _U, _U, _U, _P, _P, _P, _P, _P, /* 88-95 */
+ _P, _L | _X, _L | _X, _L | _X, _L | _X, _L | _X, _L | _X, _L, /* 96-103 */
+ _L, _L, _L, _L, _L, _L, _L, _L, /* 104-111 */
+ _L, _L, _L, _L, _L, _L, _L, _L, /* 112-119 */
+ _L, _L, _L, _P, _P, _P, _P, _C, /* 120-127 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128-143 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 144-159 */
+ _S | _SP, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, /* 160-175 */
+ _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, /* 176-191 */
+ _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, /* 192-207 */
+ _U, _U, _U, _U, _U, _U, _U, _P, _U, _U, _U, _U, _U, _U, _U, _L, /* 208-223 */
+ _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, /* 224-239 */
+ _L, _L, _L, _L, _L, _L, _L, _P, _L, _L, _L, _L, _L, _L, _L, _L /* 240-255 */
+};
+
+#define __ismask(x) (_ctype[(int)(unsigned char)(x)])
+
+#define kernel_isspace(c) ((__ismask(c)&(_S)) != 0)
+
+static char *skip_spaces(const char *str)
+{
+ while (kernel_isspace(*str))
+ ++str;
+ return (char *)str;
+}
+
+/*
+ * Parse a string to get a param value pair.
+ * You can use " around spaces, but can't escape ".
+ * Hyphens and underscores equivalent in parameter names.
+ */
+static char *next_arg(char *args, char **param, char **val)
+{
+ unsigned int i, equals = 0;
+ int in_quote = 0, quoted = 0;
+ char *next;
+
+ if (*args == '"') {
+ args++;
+ in_quote = 1;
+ quoted = 1;
+ }
+
+ for (i = 0; args[i]; i++) {
+ if (kernel_isspace(args[i]) && !in_quote)
+ break;
+ if (equals == 0) {
+ if (args[i] == '=')
+ equals = i;
+ }
+ if (args[i] == '"')
+ in_quote = !in_quote;
+ }
+
+ *param = args;
+ if (!equals)
+ *val = NULL;
+ else {
+ args[equals] = '\0';
+ *val = args + equals + 1;
+
+ /* Don't include quotes in value. */
+ if (**val == '"') {
+ (*val)++;
+ if (args[i - 1] == '"')
+ args[i - 1] = '\0';
+ }
+ }
+ if (quoted && args[i - 1] == '"')
+ args[i - 1] = '\0';
+
+ if (args[i]) {
+ args[i] = '\0';
+ next = args + i + 1;
+ } else
+ next = args + i;
+
+ /* Chew up trailing spaces. */
+ return skip_spaces(next);
+}
+
+// CODE FROM LINUX KERNEL STOP
+
+enum EXEC_MODE {
+ UNDEFINED,
+ GETARG,
+ GETARGS,
+};
+
+static void usage(enum EXEC_MODE enumExecMode, int ret, char *msg)
+{
+ switch (enumExecMode) {
+ case UNDEFINED:
+ fprintf(stderr, "ERROR: 'dracut-util' has to be called via a symlink to the tool name.\n");
+ break;
+ case GETARG:
+ fprintf(stderr, "ERROR: %s\nUsage: dracut-getarg <KEY>[=[<VALUE>]]\n", msg);
+ break;
+ case GETARGS:
+ fprintf(stderr, "ERROR: %s\nUsage: dracut-getargs <KEY>[=]\n", msg);
+ break;
+ }
+ exit(ret);
+}
+
+#define ARGV0_GETARG "dracut-getarg"
+#define ARGV0_GETARGS "dracut-getargs"
+
+static enum EXEC_MODE get_mode(const char *argv_0)
+{
+ struct _mode_table {
+ enum EXEC_MODE mode;
+ const char *arg;
+ size_t arg_len;
+ const char *s_arg;
+ } modeTable[] = {
+ {GETARG, ARGV0_GETARG, sizeof(ARGV0_GETARG), "/" ARGV0_GETARG},
+ {GETARGS, ARGV0_GETARGS, sizeof(ARGV0_GETARGS), "/" ARGV0_GETARGS},
+ {UNDEFINED, NULL, 0, NULL}
+ };
+ int i;
+
+ size_t argv_0_len = strlen(argv_0);
+
+ if (!argv_0_len)
+ return UNDEFINED;
+
+ for (i = 0; modeTable[i].mode != UNDEFINED; i++) {
+ if (argv_0_len == (modeTable[i].arg_len - 1)) {
+ if (strncmp(argv_0, modeTable[i].arg, argv_0_len) == 0) {
+ return modeTable[i].mode;
+ }
+ }
+
+ if (modeTable[i].arg_len > argv_0_len)
+ continue;
+
+ if (strncmp(argv_0 + argv_0_len - modeTable[i].arg_len, modeTable[i].s_arg, modeTable[i].arg_len) == 0)
+ return modeTable[i].mode;
+ }
+ return UNDEFINED;
+}
+
+static int getarg(int argc, char **argv)
+{
+ char *search_key;
+ char *search_value;
+ char *end_value = NULL;
+ bool bool_value = false;
+ char *cmdline = NULL;
+
+ char *p = getenv("CMDLINE");
+ if (p == NULL) {
+ usage(GETARG, EXIT_FAILURE, "CMDLINE env not set");
+ }
+ cmdline = strdup(p);
+
+ if (argc != 2) {
+ usage(GETARG, EXIT_FAILURE, "Number of arguments invalid");
+ }
+
+ search_key = argv[1];
+
+ search_value = strchr(argv[1], '=');
+ if (search_value != NULL) {
+ *search_value = 0;
+ search_value++;
+ if (*search_value == 0)
+ search_value = NULL;
+ }
+
+ if (strlen(search_key) == 0)
+ usage(GETARG, EXIT_FAILURE, "search key undefined");
+
+ do {
+ char *key = NULL, *value = NULL;
+ cmdline = next_arg(cmdline, &key, &value);
+ if (strcmp(key, search_key) == 0) {
+ if (value) {
+ end_value = value;
+ bool_value = -1;
+ } else {
+ end_value = NULL;
+ bool_value = true;
+ }
+ }
+ } while (cmdline[0]);
+
+ if (search_value) {
+ if (end_value && strcmp(end_value, search_value) == 0) {
+ return EXIT_SUCCESS;
+ }
+ return EXIT_FAILURE;
+ }
+
+ if (end_value) {
+ // includes "=0"
+ puts(end_value);
+ return EXIT_SUCCESS;
+ }
+
+ if (bool_value) {
+ return EXIT_SUCCESS;
+ }
+
+ return EXIT_FAILURE;
+}
+
+static int getargs(int argc, char **argv)
+{
+ char *search_key;
+ char *search_value;
+ bool found_value = false;
+ char *cmdline = NULL;
+
+ char *p = getenv("CMDLINE");
+ if (p == NULL) {
+ usage(GETARGS, EXIT_FAILURE, "CMDLINE env not set");
+ }
+ cmdline = strdup(p);
+
+ if (argc != 2) {
+ usage(GETARGS, EXIT_FAILURE, "Number of arguments invalid");
+ }
+
+ search_key = argv[1];
+
+ search_value = strchr(argv[1], '=');
+ if (search_value != NULL) {
+ *search_value = 0;
+ search_value++;
+ if (*search_value == 0)
+ search_value = NULL;
+ }
+
+ if (strlen(search_key) == 0)
+ usage(GETARGS, EXIT_FAILURE, "search key undefined");
+
+ do {
+ char *key = NULL, *value = NULL;
+ cmdline = next_arg(cmdline, &key, &value);
+ if (strcmp(key, search_key) == 0) {
+ if (search_value) {
+ if (strcmp(value, search_value) == 0) {
+ printf("%s\n", value);
+ found_value = true;
+ }
+ } else {
+ if (value) {
+ printf("%s\n", value);
+ } else {
+ puts(key);
+ }
+ found_value = true;
+ }
+ }
+ } while (cmdline[0]);
+ return found_value ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+int main(int argc, char **argv)
+{
+ switch (get_mode(argv[0])) {
+ case UNDEFINED:
+ usage(UNDEFINED, EXIT_FAILURE, NULL);
+ break;
+ case GETARG:
+ return getarg(argc, argv);
+ case GETARGS:
+ return getargs(argc, argv);
+ }
+
+ return EXIT_FAILURE;
+}