feat: add chroot support

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-06-13 10:48:57 +08:00
parent e6fa0df9a5
commit f86ddb80bb
6 changed files with 98 additions and 20 deletions
+30 -7
View File
@@ -6,11 +6,21 @@ mod remove;
use clap::Parser;
use rust_i18n::t;
use std::path::{Path, PathBuf};
rust_i18n::i18n!("../locales/packie-cli");
#[derive(Debug, Parser)]
enum Cli {
struct Cli {
#[arg(long)]
chroot: Option<PathBuf>,
#[command(subcommand)]
subcommand: Subcommand,
}
#[derive(Debug, Parser)]
enum Subcommand {
/// Get package information
Info(info::Cli),
@@ -34,15 +44,28 @@ fn main() {
});
let cli = Cli::parse();
let result = match cli {
Cli::Info(cli) => info::main(cli),
Cli::InitDb(cli) => initdb::main(cli),
Cli::Install(cli) => install::main(cli),
Cli::Print(cli) => print::main(cli),
Cli::Remove(cli) => remove::main(cli),
if let Some(new_root) = cli.chroot
&& let Err(err) = chroot(&new_root)
{
eprintln!("{}", t!("main.error", error = err));
std::process::exit(1);
}
let result = match cli.subcommand {
Subcommand::Info(cli) => info::main(cli),
Subcommand::InitDb(cli) => initdb::main(cli),
Subcommand::Install(cli) => install::main(cli),
Subcommand::Print(cli) => print::main(cli),
Subcommand::Remove(cli) => remove::main(cli),
};
if let Err(err) = result {
eprintln!("{}", t!("main.error", error = err));
std::process::exit(1);
}
}
#[cfg(target_family = "unix")]
fn chroot(new_root: &Path) -> std::io::Result<()> {
std::os::unix::fs::chroot(new_root)?;
std::env::set_current_dir("/")?;
Ok(())
}
+1 -1
View File
@@ -162,7 +162,7 @@ fn copy_fs_node(src: &Path, dst: &Path) -> std::io::Result<()> {
}
#[cfg(target_family = "unix")]
fn symlink(src: &Path, dst: &Path) -> std::io::Result<()> {
pub fn symlink(src: &Path, dst: &Path) -> std::io::Result<()> {
std::os::unix::fs::symlink(src, dst)
}
+9 -1
View File
@@ -57,7 +57,7 @@ pub fn install_package(
// Record installation in the database
let installation = Installation {
pkg_manifest,
pkg_manifest: pkg_manifest.clone(),
install_pkgspec,
install_date: crate::common::timestamp_secs(),
update_date: crate::common::timestamp_secs(),
@@ -68,6 +68,14 @@ pub fn install_package(
.insert_installation(&installation)
.map_err(InstallError::Database)?;
// Enable default links
for link in pkg_manifest.links.iter() {
if !link.default {
continue;
}
_ = crate::link::activate_by_description(packie, &pkg_manifest.pkg_ident(), link);
}
Ok(())
}
+44 -9
View File
@@ -1,4 +1,6 @@
use crate::{Packie, package::PkgIdent};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
/// Description of a link.
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -14,15 +16,48 @@ pub struct LinkDescription {
pub default: bool,
}
/// Status of a link in an installed package.
#[derive(Debug, Clone)]
pub struct LinkStatus {
/// Absolute path of the link source.
pub from: String,
pub fn activate_by_description(
packie: &mut Packie,
pkg_ident: &PkgIdent,
description: &LinkDescription,
) -> std::io::Result<()> {
let bundle_path = packie.profile.pkg_dir.join(pkg_ident.to_string());
let from = bundle_path.join(&description.from);
create(from, &description.to)
}
/// Absolute path of the link destination.
pub to: String,
pub fn deactivate_by_description(
packie: &mut Packie,
pkg_ident: &PkgIdent,
description: &LinkDescription,
) -> std::io::Result<()> {
let bundle_path = packie.profile.pkg_dir.join(pkg_ident.to_string());
let from = bundle_path.join(&description.from);
let link_from = std::fs::read_link(&description.to)?;
if link_from != from {
return Err(std::io::ErrorKind::InvalidData.into());
}
remove(&description.to)
}
/// True if the link is activated.
pub activated: bool,
pub fn create(from: impl AsRef<Path>, to: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(
to.as_ref()
.parent()
.ok_or_else(|| std::io::ErrorKind::InvalidInput)?,
)?;
crate::common::symlink(from.as_ref(), to.as_ref())?;
Ok(())
}
pub fn remove(path: impl Into<PathBuf>) -> std::io::Result<()> {
let mut path = path.into();
if !std::fs::metadata(&path)?.is_symlink() {
return Err(std::io::ErrorKind::InvalidData.into());
}
std::fs::remove_file(&path)?;
while path.pop() {
_ = std::fs::remove_dir(&path)?;
}
Ok(())
}
+9 -2
View File
@@ -3,8 +3,6 @@ use rusqlite::{OpenFlags, Row, params};
use std::path::Path;
/// Schema of the database.
///
/// The "installation" table records installed packages.
const SCHEMA: &str = r#"
CREATE TABLE IF NOT EXISTS "installation"(
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -76,6 +74,15 @@ impl LocalDb {
Ok(ret)
}
pub fn select_installation_unique(
&mut self,
pkg_ident: &PkgIdent,
) -> rusqlite::Result<Installation> {
self.0.prepare_cached(r#"
SELECT * FROM "installation" WHERE "pkg_name" = ?1 AND "pkg_version" = ?2 AND "pkg_arch" = ?3
"#)?.query_one(params![pkg_ident.name, pkg_ident.version.to_string(), pkg_ident.arch], map_installation)
}
pub fn remove_installation(&mut self, pkg_ident: &PkgIdent) -> rusqlite::Result<bool> {
self
.0
+5
View File
@@ -14,6 +14,11 @@ pub fn remove(packie: &mut Packie, pkgspec: &PkgSpec) -> Result<(), RemoveError>
assert_eq!(found.len(), 1);
let found = found.remove(0);
// Remove all links
for link in found.pkg_manifest.links.iter() {
_ = crate::link::deactivate_by_description(packie, &found.pkg_manifest.pkg_ident(), link);
}
// Remove package files
let bundle_dir = packie
.profile