Skip to content

Commit 26d07c5

Browse files
committed
Add initial tmpfs support (#29)
1 parent 56bb466 commit 26d07c5

File tree

5 files changed

+151
-32
lines changed

5 files changed

+151
-32
lines changed

src/actions/container.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,15 @@ pub fn rollback_container(instance: &str) -> Result<()> {
355355

356356
/// Create a new instance
357357
#[inline]
358-
pub fn add_instance(instance: &str) -> Result<()> {
359-
overlayfs::create_new_instance_fs(CIEL_INST_DIR, instance)?;
358+
pub fn add_instance(instance: &str, tmpfs: bool) -> Result<()> {
359+
overlayfs::create_new_instance_fs(CIEL_INST_DIR, instance, tmpfs)?;
360360
info!("{}: instance created.", instance);
361+
if tmpfs {
362+
warn!(
363+
"{}: tmpfs is an experimental feature, use at your own risk!",
364+
instance
365+
);
366+
}
361367

362368
Ok(())
363369
}
@@ -376,10 +382,10 @@ pub fn remove_instance(instance: &str) -> Result<()> {
376382
}
377383

378384
/// Update AOSC OS in the container/instance
379-
pub fn update_os(force_use_apt: bool) -> Result<()> {
385+
pub fn update_os(force_use_apt: bool, tmpfs: bool) -> Result<()> {
380386
info!("Updating base OS...");
381387
let instance = format!("update-{:x}", random::<u32>());
382-
add_instance(&instance)?;
388+
add_instance(&instance, tmpfs)?;
383389

384390
if force_use_apt {
385391
return apt_update_os(&instance);

src/actions/onboarding.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub fn onboarding(custom_tarball: Option<&String>, arch: Option<&str>) -> Result
107107
}
108108

109109
if let Some(init_instance) = init_instance {
110-
create_new_instance_fs(CIEL_INST_DIR, &init_instance)?;
110+
create_new_instance_fs(CIEL_INST_DIR, &init_instance, false)?;
111111
info!("{}: instance initialized.", init_instance);
112112
if config.local_repo {
113113
mount_fs(&init_instance)?;

src/cli.rs

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub fn build_cli() -> Command {
5252
.subcommand(
5353
Command::new("update-os")
5454
.arg(Arg::new("force_use_apt").long("force-use-apt").help("Use apt to update-os").action(clap::ArgAction::SetTrue))
55+
.arg(Arg::new("tmpfs").long("tmpfs").help("Use tmpfs for containers").action(clap::ArgAction::SetTrue))
5556
.about("Update the OS in the container")
5657
)
5758
.subcommand(
@@ -79,6 +80,7 @@ pub fn build_cli() -> Command {
7980
.subcommand(
8081
Command::new("add")
8182
.arg(Arg::new("INSTANCE").required(true))
83+
.arg(Arg::new("tmpfs").long("tmpfs").help("Use tmpfs for containers").action(clap::ArgAction::SetTrue))
8284
.about("Add a new instance"),
8385
)
8486
.subcommand(

src/main.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ fn main() -> Result<()> {
229229
args.get_flag("force_use_apt") || read_config().is_ok_and(|x| x.force_use_apt)
230230
};
231231

232-
print_error!({ actions::update_os(force_use_apt,) });
232+
let tmpfs = args.get_flag("tmpfs");
233+
234+
print_error!({ actions::update_os(force_use_apt, tmpfs) });
233235
}
234236
("config", args) => {
235237
if args.get_flag("g") {
@@ -294,7 +296,8 @@ fn main() -> Result<()> {
294296
}
295297
("add", args) => {
296298
let instance = args.get_one::<String>("INSTANCE").unwrap();
297-
print_error!({ actions::add_instance(instance) });
299+
let tmpfs = args.get_flag("tmpfs");
300+
print_error!({ actions::add_instance(instance, tmpfs) });
298301
}
299302
("build", args) => {
300303
let instance = get_instance_option(args)?;

src/overlayfs.rs

+133-25
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use crate::common;
1+
use crate::{common, info};
22
use anyhow::{anyhow, bail, Context, Result};
3-
use libmount::{mountinfo::Parser, Overlay};
3+
use libmount::{mountinfo::Parser, Overlay, Tmpfs};
44
use nix::mount::{umount2, MntFlags};
5-
use std::fs;
65
use std::os::unix::ffi::OsStrExt;
76
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
87
use std::path::{Path, PathBuf};
@@ -11,6 +10,7 @@ use std::{
1110
ffi::OsStr,
1211
io::{BufRead, BufReader},
1312
};
13+
use std::{fs, path};
1414

1515
pub trait LayerManager {
1616
/// Return the name of the layer manager, e.g. "overlay".
@@ -31,12 +31,18 @@ pub trait LayerManager {
3131
fn mount(&mut self, to: &Path) -> Result<()>;
3232
/// Return if the filesystem is mounted
3333
fn is_mounted(&self, target: &Path) -> Result<bool>;
34+
/// Return if the filesystem uses tmpfs for upper layer.
35+
fn is_tmpfs(&self) -> bool;
36+
/// Return if tmpfs is mounted.
37+
fn is_tmpfs_mounted(&self) -> Result<bool>;
3438
/// Rollback the filesystem to the distribution state
3539
fn rollback(&mut self) -> Result<()>;
3640
/// Commit the current state of the instance filesystem to the distribution state
3741
fn commit(&mut self) -> Result<()>;
3842
/// Un-mount the filesystem
3943
fn unmount(&mut self, target: &Path) -> Result<()>;
44+
/// Un-mount tmpfs.
45+
fn unmount_tmpfs(&self) -> Result<()>;
4046
/// Return the directory where the configuration layer is located
4147
/// You may temporary mount this directory if your backend does not expose this directory directly
4248
fn get_config_layer(&mut self) -> Result<PathBuf>;
@@ -55,12 +61,20 @@ struct OverlayFS {
5561
upper: PathBuf,
5662
work: PathBuf,
5763
volatile: bool,
64+
tmpfs: Option<PathBuf>,
5865
}
5966

6067
/// Create a new overlay filesystem on the host system
61-
pub fn create_new_instance_fs<P: AsRef<Path>>(inst_path: P, inst_name: P) -> Result<()> {
68+
pub fn create_new_instance_fs<P: AsRef<Path>>(
69+
inst_path: P,
70+
inst_name: P,
71+
tmpfs: bool,
72+
) -> Result<()> {
6273
let inst = inst_path.as_ref().join(inst_name.as_ref());
63-
fs::create_dir_all(inst)?;
74+
fs::create_dir_all(&inst)?;
75+
if tmpfs {
76+
fs::create_dir_all(inst.join("layers/tmpfs"))?;
77+
}
6478
Ok(())
6579
}
6680

@@ -174,28 +188,56 @@ impl LayerManager for OverlayFS {
174188
{
175189
let dist = dist_path.as_ref();
176190
let inst = inst_path.as_ref().join(inst_name.as_ref());
177-
Ok(Box::new(OverlayFS {
178-
inst: inst.to_owned(),
179-
base: dist.to_owned(),
180-
lower: inst.join("layers/local"),
181-
upper: inst.join("layers/diff"),
182-
work: inst.join("layers/diff.tmp"),
183-
volatile: false,
184-
}))
191+
if inst.join("layers/tmpfs").exists() {
192+
Ok(Box::new(OverlayFS {
193+
inst: inst.to_owned(),
194+
base: dist.to_owned(),
195+
lower: inst.join("layers/local"),
196+
upper: inst.join("layers/tmpfs/upper"),
197+
work: inst.join("layers/tmpfs/work"),
198+
volatile: false,
199+
tmpfs: Some(inst.join("layers/tmpfs")),
200+
}))
201+
} else {
202+
Ok(Box::new(OverlayFS {
203+
inst: inst.to_owned(),
204+
base: dist.to_owned(),
205+
lower: inst.join("layers/local"),
206+
upper: inst.join("layers/diff"),
207+
work: inst.join("layers/diff.tmp"),
208+
volatile: false,
209+
tmpfs: None,
210+
}))
211+
}
185212
}
213+
186214
fn mount(&mut self, to: &Path) -> Result<()> {
187215
let base_dirs = [self.lower.clone(), self.base.clone()];
216+
217+
// mount tmpfs if needed
218+
if let Some(tmpfs) = &self.tmpfs {
219+
fs::create_dir_all(&tmpfs)?;
220+
if !self.is_tmpfs_mounted()? {
221+
info!("Mounting container upper tmpfs");
222+
let tmpfs = Tmpfs::new(tmpfs).size_bytes(4 * 1024 * 1024 * 1024);
223+
tmpfs
224+
.mount()
225+
.map_err(|e| anyhow!("failed to mount tmpfs: {}", e.to_string()))?;
226+
}
227+
}
228+
229+
// create the directories if they don't exist (work directory may be missing)
230+
fs::create_dir_all(&self.upper)?;
231+
fs::create_dir_all(&self.work)?;
232+
fs::create_dir_all(&self.lower)?;
233+
188234
let mut overlay = Overlay::writable(
189235
// base_dirs variable contains the base and lower directories
190236
base_dirs.iter().map(|x| x.as_ref()),
191237
self.upper.clone(),
192238
self.work.clone(),
193239
to,
194240
);
195-
// create the directories if they don't exist (work directory may be missing)
196-
fs::create_dir_all(&self.work)?;
197-
fs::create_dir_all(&self.upper)?;
198-
fs::create_dir_all(&self.lower)?;
199241
// check overlay usability
200242
load_overlayfs_support()?;
201243
if self.volatile {
@@ -218,11 +260,28 @@ impl LayerManager for OverlayFS {
218260
is_mounted(target, OsStr::new("overlay"))
219261
}
220262

263+
fn is_tmpfs(&self) -> bool {
264+
self.tmpfs.is_some()
265+
}
266+
267+
fn is_tmpfs_mounted(&self) -> Result<bool> {
268+
if let Some(tmpfs) = &self.tmpfs {
269+
is_mounted(&path::absolute(&tmpfs)?, OsStr::new("tmpfs"))
270+
} else {
271+
bail!("the container does not use tmpfs")
272+
}
273+
}
274+
221275
fn rollback(&mut self) -> Result<()> {
222-
fs::remove_dir_all(&self.upper)?;
223-
fs::remove_dir_all(&self.work)?;
224-
fs::create_dir(&self.upper)?;
225-
fs::create_dir(&self.work)?;
276+
if self.is_tmpfs() {
277+
// for mounted tmpfs containers, simply un-mount the tmpfs
278+
self.unmount_tmpfs()?;
279+
} else {
280+
fs::remove_dir_all(&self.upper)?;
281+
fs::remove_dir_all(&self.work)?;
282+
fs::create_dir(&self.upper)?;
283+
fs::create_dir(&self.work)?;
284+
}
226285

227286
Ok(())
228287
}
@@ -261,6 +320,16 @@ impl LayerManager for OverlayFS {
261320
Ok(())
262321
}
263322

323+
fn unmount_tmpfs(&self) -> Result<()> {
324+
if let Some(tmpfs) = &self.tmpfs {
325+
if self.is_tmpfs_mounted()? {
326+
info!("Un-mounting tmpfs ...");
327+
umount2(tmpfs, MntFlags::MNT_DETACH)?;
328+
}
329+
}
330+
Ok(())
331+
}
332+
264333
fn get_config_layer(&mut self) -> Result<PathBuf> {
265334
Ok(self.lower.clone())
266335
}
@@ -270,6 +339,9 @@ impl LayerManager for OverlayFS {
270339
}
271340

272341
fn destroy(&mut self) -> Result<()> {
342+
if self.is_tmpfs() {
343+
self.unmount_tmpfs()?;
344+
}
273345
fs::remove_dir_all(&self.inst)?;
274346

275347
Ok(())
@@ -351,14 +423,50 @@ fn sync_permission(from: &Path, to: &Path) -> Result<()> {
351423
Ok(())
352424
}
353425

426+
fn rename_file(from: &Path, to: &Path, overlay: &OverlayFS) -> Result<()> {
427+
if overlay.is_tmpfs() {
428+
if to.symlink_metadata().is_ok() {
429+
if to.is_dir() {
430+
fs::remove_dir_all(to)?;
431+
} else {
432+
fs::remove_file(to)?;
433+
}
434+
}
435+
if from.is_symlink() {
436+
std::os::unix::fs::symlink(fs::read_link(from)?, to)?;
437+
fs::remove_file(from)?;
438+
} else if from.is_file() {
439+
fs::copy(from, to)?;
440+
fs::remove_file(from)?;
441+
} else if from.is_dir() {
442+
fs::create_dir_all(to)?;
443+
fs::set_permissions(to, from.metadata()?.permissions())?;
444+
for entry in fs::read_dir(from)? {
445+
let entry = entry?;
446+
rename_file(
447+
&from.join(entry.file_name()),
448+
&to.join(entry.file_name()),
449+
overlay,
450+
)?;
451+
}
452+
fs::remove_dir_all(from)?;
453+
} else {
454+
bail!("unsupported file type");
455+
}
456+
} else {
457+
fs::rename(from, to)?;
458+
}
459+
Ok(())
460+
}
461+
354462
#[inline]
355463
fn overlay_exec_action(action: &Diff, overlay: &OverlayFS) -> Result<()> {
356464
match action {
357465
Diff::Symlink(path) => {
358466
let upper_path = overlay.upper.join(path);
359467
let lower_path = overlay.base.join(path);
360468
// Replace lower dir with upper
361-
fs::rename(upper_path, lower_path)?;
469+
rename_file(&upper_path, &lower_path, overlay)?;
362470
}
363471
Diff::OverrideDir(path) => {
364472
let upper_path = overlay.upper.join(path);
@@ -371,7 +479,7 @@ fn overlay_exec_action(action: &Diff, overlay: &OverlayFS) -> Result<()> {
371479
// If it's a file, then remove it as well
372480
fs::remove_file(&lower_path)?;
373481
}
374-
fs::rename(upper_path, &lower_path)?;
482+
rename_file(&upper_path, &lower_path, overlay)?;
375483
}
376484
Diff::RenamedDir(from, to) => {
377485
// TODO: Implement copy down
@@ -381,7 +489,7 @@ fn overlay_exec_action(action: &Diff, overlay: &OverlayFS) -> Result<()> {
381489
let to_path = overlay.base.join(to);
382490
// TODO: Merge files from upper to lower
383491
// Replace lower dir with upper
384-
fs::rename(from_path, to_path)?;
492+
rename_file(&from_path, &to_path, overlay)?;
385493
}
386494
Diff::NewDir(path) => {
387495
let lower_path = overlay.base.join(path);
@@ -408,7 +516,7 @@ fn overlay_exec_action(action: &Diff, overlay: &OverlayFS) -> Result<()> {
408516
let upper_path = overlay.upper.join(path);
409517
let lower_path = overlay.base.join(path);
410518
// Move upper file to overwrite the lower
411-
fs::rename(upper_path, lower_path)?;
519+
rename_file(&upper_path, &lower_path, overlay)?;
412520
}
413521
}
414522

0 commit comments

Comments
 (0)