diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/dracut-cpio/Cargo.lock | 12 | ||||
-rw-r--r-- | src/dracut-cpio/Cargo.toml | 11 | ||||
-rw-r--r-- | src/dracut-cpio/src/main.rs | 1813 | ||||
-rw-r--r-- | src/dracut-cpio/third_party/crosvm/Cargo.toml | 8 | ||||
-rw-r--r-- | src/dracut-cpio/third_party/crosvm/LICENSE | 27 | ||||
-rw-r--r-- | src/dracut-cpio/third_party/crosvm/argument.rs | 549 | ||||
-rw-r--r-- | src/dracut-cpio/third_party/crosvm/crosvm.rs | 2 | ||||
-rw-r--r-- | src/install/Makefile | 7 | ||||
-rw-r--r-- | src/install/dracut-install.c | 2281 | ||||
-rw-r--r-- | src/install/hashmap.c | 658 | ||||
-rw-r--r-- | src/install/hashmap.h | 88 | ||||
-rw-r--r-- | src/install/log.c | 303 | ||||
-rw-r--r-- | src/install/log.h | 98 | ||||
-rw-r--r-- | src/install/macro.h | 299 | ||||
-rw-r--r-- | src/install/strv.c | 616 | ||||
-rw-r--r-- | src/install/strv.h | 118 | ||||
-rw-r--r-- | src/install/util.c | 574 | ||||
-rw-r--r-- | src/install/util.h | 609 | ||||
-rw-r--r-- | src/logtee/logtee.c | 71 | ||||
-rw-r--r-- | src/skipcpio/skipcpio.c | 207 | ||||
-rw-r--r-- | src/util/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/util/util.c | 306 |
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; +} |