Skip to content

Commit 56b21c3

Browse files
committed
env: detect the lowerdir overlay mount options at runtime
Replace the "legacy-mount-options" feature flag with runtime detection of this feature. We assume that systems with kernel version v6.8 and newer are capable of using the "lowerdir+" mount option. Related-to: #968 Related-to: #1164
1 parent bd9a13c commit 56b21c3

File tree

3 files changed

+50
-12
lines changed

3 files changed

+50
-12
lines changed

crates/spfs/Cargo.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ default = []
1818
# If enabled, will create the "local" repository in a subdirectory
1919
# of the standard storage root, named "ci/pipeline_${CI_PIPELINE_ID}".
2020
gitlab-ci-local-repo-isolation = []
21-
# Use "mount" commands that are compatible with older centos7-era kernels that
22-
# do not support the "lowerdir+=" overlayfs options. "legacy-mount-options"
23-
# can run into path length limits when mounting many layers.
24-
# https://github.com/spkenv/spk/issues/968
25-
legacy-mount-options = []
2621
sentry = ["dep:sentry"]
2722
server = ["hyper/server", "tokio-util/codec", "tokio-util/io-util"]
2823
"protobuf-src" = ["dep:protobuf-src"]

crates/spfs/src/env.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,13 +1052,18 @@ const OVERLAY_ARGS_INDEX: &str = "index";
10521052
const OVERLAY_ARGS_INDEX_ON: &str = "index=on";
10531053
const OVERLAY_ARGS_METACOPY: &str = "metacopy";
10541054
const OVERLAY_ARGS_METACOPY_ON: &str = "metacopy=on";
1055+
pub(crate) const OVERLAY_ARGS_LOWERDIR_APPEND: &str = "lowerdir+";
1056+
const OVERLAY_ARGS_LOWERDIR_APPEND_ASSIGN: &str = "lowerdir+=";
1057+
const OVERLAY_ARGS_LOWERDIR_ASSIGN: &str = "lowerdir=";
10551058

10561059
/// A struct for holding the options that will be included
10571060
/// in the overlayfs mount command when mounting an environment.
10581061
#[derive(Default)]
10591062
pub(crate) struct OverlayMountOptions {
10601063
/// Specifies that the overlay file system is mounted as read-only
10611064
pub read_only: bool,
1065+
/// The lowerdir+ mount option will be used to append layers when true.
1066+
pub lowerdir_append: bool,
10621067
/// When true, inodes are indexed in the mount so that
10631068
/// files which share the same inode (hardlinks) are broken
10641069
/// in the final mount and changes to one file don't affect
@@ -1089,11 +1094,22 @@ impl OverlayMountOptions {
10891094
fn new(rt: &runtime::Runtime) -> Self {
10901095
Self {
10911096
read_only: !rt.status.editable,
1097+
lowerdir_append: true,
10921098
break_hardlinks: true,
10931099
metadata_copy_up: true,
10941100
}
10951101
}
10961102

1103+
/// Update state variables to match the features supported by the current overlay version.
1104+
fn query(mut self) -> Self {
1105+
let params = runtime::overlayfs::overlayfs_available_options();
1106+
if self.lowerdir_append && !params.contains(OVERLAY_ARGS_LOWERDIR_APPEND) {
1107+
self.lowerdir_append = false;
1108+
}
1109+
1110+
self
1111+
}
1112+
10971113
/// Return the options that should be included in the mount request.
10981114
pub fn to_options(&self) -> Vec<&'static str> {
10991115
let params = runtime::overlayfs::overlayfs_available_options();
@@ -1130,7 +1146,7 @@ pub(crate) fn get_overlay_args<P: AsRef<Path>>(
11301146
// Allocate a large buffer up front to avoid resizing/copying.
11311147
let mut args = String::with_capacity(4096);
11321148

1133-
let mount_options = OverlayMountOptions::new(rt);
1149+
let mount_options = OverlayMountOptions::new(rt).query();
11341150
for option in mount_options.to_options() {
11351151
args.push_str(option);
11361152
args.push(',');
@@ -1142,19 +1158,19 @@ pub(crate) fn get_overlay_args<P: AsRef<Path>>(
11421158
// the rightmost on the command line is the bottom layer, and the
11431159
// leftmost is on the top). For more details see:
11441160
// https://docs.kernel.org/filesystems/overlayfs.html#multiple-lower-layers
1145-
if cfg!(feature = "legacy-mount-options") {
1146-
args.push_str("lowerdir=");
1161+
if mount_options.lowerdir_append {
11471162
for path in layer_dirs.iter().rev() {
1163+
args.push_str(OVERLAY_ARGS_LOWERDIR_APPEND_ASSIGN);
11481164
args.push_str(&path.as_ref().to_string_lossy());
1149-
args.push(':');
1165+
args.push(',');
11501166
}
1167+
args.push_str(OVERLAY_ARGS_LOWERDIR_APPEND_ASSIGN);
11511168
} else {
1169+
args.push_str(OVERLAY_ARGS_LOWERDIR_ASSIGN);
11521170
for path in layer_dirs.iter().rev() {
1153-
args.push_str("lowerdir+=");
11541171
args.push_str(&path.as_ref().to_string_lossy());
1155-
args.push(',');
1172+
args.push(':');
11561173
}
1157-
args.push_str("lowerdir+=");
11581174
}
11591175
args.push_str(&rt.config.lower_dir.to_string_lossy());
11601176

crates/spfs/src/runtime/overlayfs.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::collections::HashSet;
66
use std::io::{BufRead, BufReader};
77
use std::os::unix::fs::MetadataExt;
88

9+
use crate::env::OVERLAY_ARGS_LOWERDIR_APPEND;
910
use crate::{Error, Result};
1011

1112
#[cfg(test)]
@@ -57,10 +58,36 @@ fn query_overlayfs_available_options() -> Result<HashSet<String>> {
5758
#[cfg(target_os = "linux")]
5859
fn parse_modinfo_params<R: BufRead>(reader: &mut R) -> Result<HashSet<String>> {
5960
let mut params = HashSet::new();
61+
let mut vermagic_seen = false;
6062
for line in reader.lines() {
6163
let line = line.map_err(|err| {
6264
Error::String(format!("Failed to read kernel module information: {err}"))
6365
})?;
66+
67+
// The output from "modinfo overlay" looks like this:
68+
// ...
69+
// vermagic: 6.12.9-amd64 SMP preempt mod_unload modversions
70+
// ...
71+
// parm: metacopy:Default to on or off for the metadata only copy up feature (bool)
72+
//
73+
// The "vermagic:" line appears before the "parm:" lines.
74+
75+
if !vermagic_seen {
76+
let version_string = match line.strip_prefix("vermagic:") {
77+
Some(remainder) => remainder.trim(),
78+
None => continue,
79+
};
80+
vermagic_seen = true;
81+
let mut parts = version_string.splitn(3, '.'); // ("6", "12", "9-...")
82+
let major_version = parts.next().unwrap_or("0").parse().unwrap_or(0);
83+
let minor_version = parts.next().unwrap_or("0").parse().unwrap_or(0);
84+
// The "lowerdir+" option was added in Linux v6.8.
85+
// https://docs.kernel.org/6.8/filesystems/overlayfs.html#multiple-lower-layers
86+
if major_version >= 7 || (major_version == 6 && minor_version >= 8) {
87+
params.insert(OVERLAY_ARGS_LOWERDIR_APPEND.to_string());
88+
}
89+
continue;
90+
}
6491
let param = match line.strip_prefix("parm:") {
6592
Some(remainder) => remainder.trim(),
6693
None => continue,

0 commit comments

Comments
 (0)