2026-03-05 16:19:06 +01:00
|
|
|
// SPDX-FileCopyrightText: 2026 Qyriad <qyriad@qyriad.me>
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: EUPL-1.1
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
use std::{
|
|
|
|
|
iter,
|
|
|
|
|
sync::{Arc, LazyLock},
|
|
|
|
|
};
|
2026-02-16 18:02:39 +01:00
|
|
|
|
|
|
|
|
pub(crate) mod prelude {
|
|
|
|
|
#![allow(unused_imports)]
|
|
|
|
|
|
|
|
|
|
pub use std::{
|
|
|
|
|
error::Error as StdError,
|
|
|
|
|
ffi::{OsStr, OsString},
|
2026-03-11 14:26:59 +01:00
|
|
|
fmt::{Debug, Display, Formatter, Result as FmtResult},
|
|
|
|
|
io::{Error as IoError, ErrorKind as IoErrorKind, Read, Seek, SeekFrom, Write},
|
2026-02-16 18:02:39 +01:00
|
|
|
path::{Path, PathBuf},
|
|
|
|
|
process::{Command, ExitCode},
|
|
|
|
|
str::FromStr,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
|
pub use std::os::{
|
|
|
|
|
fd::AsRawFd,
|
|
|
|
|
unix::ffi::{OsStrExt, OsStringExt},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub type BoxDynError = Box<dyn StdError + Send + Sync + 'static>;
|
|
|
|
|
|
|
|
|
|
pub use command_error::{CommandExt, OutputLike};
|
|
|
|
|
pub use fs_err::File;
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
|
pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt};
|
|
|
|
|
|
2026-03-11 14:26:59 +01:00
|
|
|
pub use bstr::ByteSlice;
|
|
|
|
|
|
2026-03-20 20:43:12 +01:00
|
|
|
pub use itertools::Itertools;
|
|
|
|
|
|
2026-03-19 21:39:11 +01:00
|
|
|
pub use rustix::io::Errno;
|
|
|
|
|
|
|
|
|
|
pub use tap::{Pipe, Tap, TapFallible, TapOptional};
|
2026-02-16 18:02:39 +01:00
|
|
|
|
|
|
|
|
pub use tracing::{Level, debug, error, info, trace, warn};
|
2026-03-19 12:36:46 +01:00
|
|
|
|
|
|
|
|
pub use crate::boxext::BoxedPathExt;
|
2026-02-16 18:02:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use prelude::*;
|
|
|
|
|
|
|
|
|
|
pub mod args;
|
2026-03-10 18:46:55 +01:00
|
|
|
pub use args::{AppendCmd, Args};
|
2026-03-19 12:36:46 +01:00
|
|
|
mod boxext;
|
2026-02-16 18:02:39 +01:00
|
|
|
mod color;
|
|
|
|
|
pub use color::{_CLI_ENABLE_COLOR, SHOULD_COLOR};
|
2026-03-11 14:26:59 +01:00
|
|
|
mod daemon;
|
|
|
|
|
pub use daemon::Daemon;
|
2026-03-22 17:15:04 +01:00
|
|
|
pub use daemon::api as daemon_api;
|
2026-03-11 14:26:59 +01:00
|
|
|
mod daemon_io;
|
|
|
|
|
pub use daemon_io::OwnedFdWithFlags;
|
2026-03-19 19:45:09 +01:00
|
|
|
mod daemon_tokfd;
|
|
|
|
|
pub(crate) use daemon_tokfd::TokenFd;
|
2026-02-16 18:02:39 +01:00
|
|
|
pub mod line;
|
|
|
|
|
pub use line::Line;
|
2026-03-11 14:26:59 +01:00
|
|
|
mod nixcmd;
|
2026-02-16 18:02:39 +01:00
|
|
|
pub mod source;
|
2026-03-19 12:36:46 +01:00
|
|
|
use rustix::fs::Mode;
|
2026-02-16 18:02:39 +01:00
|
|
|
pub use source::{SourceFile, SourceLine};
|
2026-02-16 18:02:39 +01:00
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
#[cfg(all(not(feature = "regex-full"), not(feature = "regex-lite")))]
|
|
|
|
|
compile_error!("At least one of features `regex-full` or `regex-lite` must be used");
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "regex-full")]
|
|
|
|
|
use regex as _regex;
|
2026-02-16 18:02:39 +01:00
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
// Having both `regex-full` and `regex-lite` isn't an error; it's just wasteful.
|
|
|
|
|
#[cfg(not(feature = "regex-full"))]
|
|
|
|
|
use regex_lite as _regex;
|
|
|
|
|
|
|
|
|
|
use _regex::Regex;
|
|
|
|
|
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2026-02-16 18:02:39 +01:00
|
|
|
|
2026-03-11 14:26:59 +01:00
|
|
|
use crate::args::DaemonCmd;
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' '];
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
/// Regex pattern to extract the priority in a `lib.mkOverride` call.
|
|
|
|
|
static MK_OVERRIDE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
|
|
|
|
// Named capture group: priority
|
|
|
|
|
// - Word boundary
|
|
|
|
|
// - Literal `mkOverride`
|
|
|
|
|
// - One or more whitespace characters
|
|
|
|
|
// - Literal open parenthesis
|
|
|
|
|
// - Named capture group "priority"
|
|
|
|
|
// - One or more of: digit characters, or literal `-`
|
|
|
|
|
// - Literal close parenthesis
|
|
|
|
|
Regex::new(r"(?-u)\bmkOverride\s+\((?<priority>[\d-]+)\)").unwrap()
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
#[tracing::instrument(level = "debug")]
|
|
|
|
|
pub fn do_append(args: Arc<Args>, append_args: AppendCmd) -> Result<(), BoxDynError> {
|
|
|
|
|
let filepath = Path::new(&args.file);
|
|
|
|
|
let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") {
|
|
|
|
|
iter::once(OsStr::new("./"))
|
|
|
|
|
.chain(filepath.iter())
|
|
|
|
|
.collect()
|
|
|
|
|
} else {
|
|
|
|
|
filepath.to_path_buf()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut opts = File::options();
|
|
|
|
|
opts.read(true)
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(false)
|
|
|
|
|
.custom_flags(libc::O_CLOEXEC);
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
let source_file = SourceFile::open_from(Arc::from(filepath), opts)?;
|
|
|
|
|
let pri = get_where(source_file.clone())?;
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
let new_pri = pri - 1;
|
|
|
|
|
|
|
|
|
|
let new_pri_line = get_next_prio_line(
|
|
|
|
|
source_file.clone(),
|
2026-03-10 18:46:55 +01:00
|
|
|
append_args.name,
|
2026-02-16 18:02:39 +01:00
|
|
|
new_pri,
|
2026-03-10 18:46:55 +01:00
|
|
|
append_args.value,
|
2026-02-16 18:02:39 +01:00
|
|
|
)?;
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
debug!("new_pri_line={new_pri_line}");
|
2026-02-16 18:02:39 +01:00
|
|
|
|
|
|
|
|
write_next_prio(source_file, new_pri_line)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 14:26:59 +01:00
|
|
|
//#[tracing::instrument(level = "debug")]
|
2026-03-20 20:43:12 +01:00
|
|
|
pub fn do_daemon(args: Arc<Args>, daemon_args: DaemonCmd) -> Result<(), BoxDynError> {
|
|
|
|
|
let config_file = Path::new(&args.file);
|
|
|
|
|
let config_file: PathBuf = if config_file.is_relative() && !config_file.starts_with("./") {
|
|
|
|
|
iter::once(OsStr::new("./"))
|
|
|
|
|
.chain(config_file.iter())
|
|
|
|
|
.collect()
|
|
|
|
|
} else {
|
|
|
|
|
config_file.to_path_buf()
|
|
|
|
|
};
|
|
|
|
|
let config_file: Arc<Path> = Arc::from(config_file);
|
|
|
|
|
|
2026-03-19 12:36:46 +01:00
|
|
|
// FIXME: make configurable?
|
|
|
|
|
let _ = rustix::process::umask(Mode::from_bits_retain(0o600).complement());
|
|
|
|
|
|
|
|
|
|
let mut daemon = match daemon_args {
|
2026-03-20 20:43:12 +01:00
|
|
|
DaemonCmd { stdin: true, .. } => Daemon::from_stdin(config_file),
|
|
|
|
|
DaemonCmd { socket: None, .. } => Daemon::open_default_socket(config_file)?,
|
2026-03-19 12:36:46 +01:00
|
|
|
DaemonCmd {
|
|
|
|
|
socket: Some(socket),
|
|
|
|
|
..
|
2026-03-20 20:43:12 +01:00
|
|
|
} => Daemon::from_unix_socket_path(config_file, &socket)?,
|
2026-03-19 12:36:46 +01:00
|
|
|
};
|
|
|
|
|
|
2026-03-11 14:26:59 +01:00
|
|
|
daemon.enter_loop().unwrap();
|
2026-03-19 12:36:46 +01:00
|
|
|
|
|
|
|
|
info!("daemon has exited");
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2026-03-11 14:26:59 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
|
|
|
|
|
pub struct DefinitionWithLocation {
|
|
|
|
|
pub file: Box<Path>,
|
|
|
|
|
pub value: Box<serde_json::Value>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn expr_for_configuration(source_file: &Path) -> OsString {
|
|
|
|
|
[
|
|
|
|
|
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
|
|
|
|
source_file.as_os_str(),
|
|
|
|
|
OsStr::new("; }"),
|
|
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
fn maybe_extract_prio_from_line(line: &SourceLine) -> Option<i64> {
|
|
|
|
|
MK_OVERRIDE_RE
|
|
|
|
|
.captures(line.text_ref())
|
|
|
|
|
.map(|caps| caps.name("priority").unwrap().as_str())
|
|
|
|
|
.map(|prio_str| {
|
|
|
|
|
i64::from_str(prio_str).unwrap_or_else(|e| {
|
|
|
|
|
panic!(
|
|
|
|
|
"lib.mkOverride called with non-integer {}: {}. Nix source code is wrong!\n{}",
|
|
|
|
|
prio_str, e, line,
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
})
|
2026-02-16 18:02:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
pub fn get_where(dynamic_nix: SourceFile) -> Result<i64, BoxDynError> {
|
|
|
|
|
let lines = dynamic_nix.lines()?;
|
|
|
|
|
let prio = lines
|
2026-03-10 18:46:55 +01:00
|
|
|
.iter()
|
2026-02-16 18:02:39 +01:00
|
|
|
.filter_map(maybe_extract_prio_from_line)
|
|
|
|
|
.sorted_unstable()
|
|
|
|
|
.next() // Priorities with lower integer values are "stronger" priorities.
|
|
|
|
|
.unwrap_or(0);
|
2026-02-16 18:02:39 +01:00
|
|
|
|
2026-02-16 18:02:39 +01:00
|
|
|
Ok(prio)
|
2026-02-16 18:02:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_next_prio_line(
|
|
|
|
|
source: SourceFile,
|
|
|
|
|
option_name: Arc<str>,
|
|
|
|
|
new_prio: i64,
|
|
|
|
|
new_value: Arc<str>,
|
|
|
|
|
) -> Result<SourceLine, BoxDynError> {
|
|
|
|
|
let source_lines = source.lines()?;
|
2026-02-16 18:02:39 +01:00
|
|
|
let penultimate = source_lines.get(source_lines.len() - 2);
|
|
|
|
|
// FIXME: don't rely on whitespace lol
|
|
|
|
|
debug_assert_eq!(penultimate.map(SourceLine::text).as_deref(), Some(" ];"));
|
|
|
|
|
let penultimate = penultimate.unwrap();
|
|
|
|
|
|
|
|
|
|
let new_generation = 0 - new_prio;
|
2026-02-16 18:02:39 +01:00
|
|
|
|
|
|
|
|
let new_line = SourceLine {
|
2026-02-16 18:02:39 +01:00
|
|
|
line: penultimate.line,
|
2026-02-16 18:02:39 +01:00
|
|
|
path: source.path(),
|
|
|
|
|
text: Arc::from(format!(
|
2026-02-16 18:02:39 +01:00
|
|
|
" {} = lib.mkOverride ({}) ({}); # DYNIX GENERATION {}",
|
|
|
|
|
option_name, new_prio, new_value, new_generation,
|
2026-02-16 18:02:39 +01:00
|
|
|
)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(new_line)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> {
|
|
|
|
|
let new_mod_start = SourceLine {
|
|
|
|
|
line: new_line.line.prev(),
|
|
|
|
|
path: source.path(),
|
2026-02-16 18:02:39 +01:00
|
|
|
text: Arc::from(" {"),
|
2026-02-16 18:02:39 +01:00
|
|
|
};
|
|
|
|
|
let new_mod_end = SourceLine {
|
|
|
|
|
line: new_line.line.next(),
|
|
|
|
|
path: source.path(),
|
2026-02-16 18:02:39 +01:00
|
|
|
text: Arc::from(" }"),
|
2026-02-16 18:02:39 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
source.insert_lines(&[new_mod_start, new_line, new_mod_end])?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|