Skip to content

Commit 47ae558

Browse files
committed
image: add basic support for overlay of a proto directory onto an image
1 parent 5292491 commit 47ae558

File tree

6 files changed

+179
-9
lines changed

6 files changed

+179
-9
lines changed

image/templates/gimlet/zfs-compliance.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@
119119
"extsrc": "faux-mgs",
120120
"owner": "root", "group": "bin", "mode": "0755" },
121121

122+
{ "t": "include", "with": "genproto",
123+
"name": "genproto", "file": "${genproto}" },
124+
122125
{ "t": "seed_smf", "apply_site": true }
123126
]
124127
}

image/templates/gimlet/zfs-recovery.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
"extsrc": "pilot/${pilot_profile}.toml",
7878
"owner": "root", "group": "bin", "mode": "0644" },
7979

80+
{ "t": "include", "with": "genproto",
81+
"name": "genproto", "file": "${genproto}" },
82+
8083
{ "t": "seed_smf", "apply_site": true }
8184
]
8285
}

image/templates/gimlet/zfs.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
"src": "mfg.xml",
7878
"owner": "root", "group": "sys", "mode": "0644" },
7979

80+
{ "t": "include", "with": "genproto",
81+
"name": "genproto", "file": "${genproto}" },
82+
8083
{ "t": "include", "name": "smf-reduce" },
8184
{ "t": "seed_smf", "apply_site": true, "skip_seed": true }
8285
]

tools/helios-build/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ edition = "2018"
99
#
1010
rust-version = "1.65.0"
1111

12-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13-
1412
[features]
1513
default = ["vendored-openssl"]
1614
vendored-openssl = ['openssl/vendored']
@@ -38,4 +36,4 @@ regex = "1.4"
3836
#
3937
openssl = { version = "0.10", optional = true }
4038
json5 = "0.4.1"
41-
time = { version = "0.3", feature = ["formatting"] }
39+
time = { version = "0.3" }

tools/helios-build/src/common.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::sync::Mutex;
1010
use serde::Deserialize;
1111
use std::fs::File;
1212
use std::io::{Read, BufReader};
13-
use std::path::Path;
13+
use std::path::{Path, PathBuf};
1414
use anyhow::{Result, bail};
1515

1616
pub use slog::{info, warn, error, debug, trace, o};
@@ -121,3 +121,27 @@ pub fn exists_dir<P: AsRef<Path>>(path: P) -> Result<bool> {
121121
Ok(false)
122122
}
123123
}
124+
125+
pub fn unprefix(prefix: &Path, path: &Path) -> Result<PathBuf> {
126+
if prefix.is_absolute() != path.is_absolute() {
127+
bail!("prefix and path must not be a mix of absolute and relative");
128+
}
129+
130+
let cprefix = prefix.components().collect::<Vec<_>>();
131+
let cpath = path.components().collect::<Vec<_>>();
132+
133+
if let Some(tail) = cpath.strip_prefix(cprefix.as_slice()) {
134+
Ok(tail.iter().collect())
135+
} else {
136+
bail!("{:?} does not start with prefix {:?}", path, prefix);
137+
}
138+
}
139+
140+
pub fn reprefix(prefix: &Path, path: &Path, target: &Path) -> Result<PathBuf> {
141+
if !target.is_absolute() {
142+
bail!("target must be absolute");
143+
}
144+
let mut newpath = target.to_path_buf();
145+
newpath.push(unprefix(prefix, path)?);
146+
Ok(newpath)
147+
}

tools/helios-build/src/main.rs

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use common::*;
44
use anyhow::{Result, Context, bail};
55
use serde::Deserialize;
66
use std::collections::HashMap;
7+
use std::os::unix::fs::PermissionsExt;
78
use std::process::Command;
89
use std::os::unix::process::CommandExt;
9-
use std::io::{BufReader, Read};
10+
use std::io::{BufReader, Read, Write};
1011
use std::fs::File;
1112
use std::time::{Instant,SystemTime};
1213
use slog::Logger;
@@ -1007,10 +1008,6 @@ fn cmd_build_omnios(ca: &CommandArg) -> Result<()> {
10071008
return Ok(());
10081009
}
10091010

1010-
// if res.free.is_empty() {
1011-
// bail!("which package should I build?");
1012-
// }
1013-
10141011
let dir = top_path(&["projects", "omnios-build", "build"])?;
10151012

10161013
let mut pkgs = extract_pkgs(log, &dir)?;
@@ -1036,6 +1033,106 @@ fn cargo_target_cmd(project: &str, command: &str, debug: bool)
10361033
Ok(bin.to_str().unwrap().to_string())
10371034
}
10381035

1036+
/*
1037+
* If we have been provided an extra proto directory, we want to include all of
1038+
* the files and directories and symbolic links that have been assembled in that
1039+
* proto area in the final image. The image-builder tool cannot do this
1040+
* natively because there is no way to know what metadata to use for the files
1041+
* without some kind of explicit manifest provided as input to ensure_*
1042+
* directives.
1043+
*
1044+
* For our purposes here, it seems sufficient to use the mode bits as-is and
1045+
* just request that root own the files in the resultant image. We generate a
1046+
* partial template by walking the proto area, for inclusion when the "genproto"
1047+
* feature is also enabled in our main template.
1048+
*/
1049+
fn genproto(proto: &Path, output_template: &Path) -> Result<()> {
1050+
let rootdir = PathBuf::from("/");
1051+
let mut steps: Vec<serde_json::Value> = Default::default();
1052+
1053+
for ent in WalkDir::new(proto).min_depth(1).into_iter() {
1054+
let ent = ent?;
1055+
1056+
let relpath = unprefix(proto, ent.path())?;
1057+
if relpath == PathBuf::from("bin") {
1058+
/*
1059+
* On illumos, /bin is always a symbolic link to /usr/bin.
1060+
*/
1061+
bail!("proto {:?} contains a /bin directory; should use /usr/bin",
1062+
proto);
1063+
}
1064+
1065+
/*
1066+
* Use the relative path within the proto area as the absolute path
1067+
* in the image; e.g., "proto/bin/id" would become "/bin/id" in the
1068+
* image.
1069+
*/
1070+
let path = reprefix(proto, ent.path(), &rootdir)?;
1071+
let path = path.to_str().unwrap();
1072+
1073+
let md = ent.metadata()?;
1074+
let mode = format!("{:o}", md.permissions().mode() & 0o777);
1075+
if md.file_type().is_symlink() {
1076+
let target = std::fs::read_link(ent.path())?;
1077+
1078+
steps.push(serde_json::json!({
1079+
"t": "ensure_symlink", "link": path, "target": target,
1080+
"owner": "root", "group": "root",
1081+
}));
1082+
} else if md.file_type().is_dir() {
1083+
/*
1084+
* Some system directories are owned by groups other than "root".
1085+
* The rules are not exact; this is an approximation to reduce
1086+
* churn:
1087+
*/
1088+
let group = if relpath.starts_with("var")
1089+
|| relpath.starts_with("etc")
1090+
|| relpath.starts_with("lib/svc/manifest")
1091+
|| relpath.starts_with("platform")
1092+
|| relpath.starts_with("kernel")
1093+
|| relpath == PathBuf::from("usr")
1094+
|| relpath == PathBuf::from("usr/share")
1095+
|| relpath.starts_with("usr/platform")
1096+
|| relpath.starts_with("usr/kernel")
1097+
|| relpath == PathBuf::from("opt")
1098+
{
1099+
"sys"
1100+
} else if relpath.starts_with("lib")
1101+
|| relpath.starts_with("usr")
1102+
{
1103+
"bin"
1104+
} else {
1105+
"root"
1106+
};
1107+
1108+
steps.push(serde_json::json!({
1109+
"t": "ensure_dir", "dir": path,
1110+
"owner": "root", "group": group, "mode": mode,
1111+
}));
1112+
} else if md.file_type().is_file() {
1113+
steps.push(serde_json::json!({
1114+
"t": "ensure_file", "file": path, "extsrc": relpath,
1115+
"owner": "root", "group": "root", "mode": mode,
1116+
}));
1117+
} else {
1118+
bail!("unhandled file type at {:?}", ent.path());
1119+
}
1120+
}
1121+
1122+
let out = serde_json::to_vec_pretty(&serde_json::json!({
1123+
"steps": steps,
1124+
}))?;
1125+
let mut f = std::fs::OpenOptions::new()
1126+
.write(true)
1127+
.create(true)
1128+
.truncate(true)
1129+
.open(output_template)?;
1130+
f.write_all(&out)?;
1131+
f.flush()?;
1132+
1133+
Ok(())
1134+
}
1135+
10391136
fn cmd_image(ca: &CommandArg) -> Result<()> {
10401137
let mut opts = baseopts();
10411138
opts.optflag("d", "", "use DEBUG packages");
@@ -1050,6 +1147,7 @@ fn cmd_image(ca: &CommandArg) -> Result<()> {
10501147
opts.optmulti("X", "", "skip this phase", "PHASE");
10511148
opts.optflag("", "ddr-testing", "build ROMs for other DDR frequencies");
10521149
opts.optopt("p", "", "use an external package repository", "PUBLISHER=URL");
1150+
opts.optopt("P", "", "include all files from extra proto area", "DIR");
10531151

10541152
let usage = || {
10551153
println!("{}",
@@ -1073,6 +1171,16 @@ fn cmd_image(ca: &CommandArg) -> Result<()> {
10731171
let skips = res.opt_strs("X");
10741172
let recovery = res.opt_present("R");
10751173

1174+
let extra_proto = if let Some(dir) = res.opt_str("P") {
1175+
let dir = PathBuf::from(dir);
1176+
if !dir.is_dir() {
1177+
bail!("-P must specify a proto area directory");
1178+
}
1179+
Some(dir)
1180+
} else {
1181+
None
1182+
};
1183+
10761184
let user = illumos::get_username()?.unwrap_or("unknown".to_string());
10771185

10781186
let image_name = res.opt_str("N").unwrap_or_else(|| {
@@ -1159,6 +1267,25 @@ fn cmd_image(ca: &CommandArg) -> Result<()> {
11591267

11601268
let tempdir = ensure_dir(&["tmp", &timage])?;
11611269

1270+
let genproto = {
1271+
let p = rel_path(Some(&tempdir), &["genproto.json"])?;
1272+
if p.exists() {
1273+
/*
1274+
* Remove the old template file to ensure we do not accidentally use
1275+
* a stale copy later.
1276+
*/
1277+
std::fs::remove_file(&p)?;
1278+
}
1279+
1280+
if let Some(dir) = extra_proto.as_deref() {
1281+
genproto(dir, &p)?;
1282+
info!(log, "generated template {:?} for extra proto {:?}", p, dir);
1283+
Some(p)
1284+
} else {
1285+
None
1286+
}
1287+
};
1288+
11621289
let repo = if let Some(extrepo) = &extrepo {
11631290
/*
11641291
* If we have been instructed to use a repository URL, we do not need to
@@ -1193,6 +1320,11 @@ fn cmd_image(ca: &CommandArg) -> Result<()> {
11931320
cmd.arg("-d").arg(&imgds);
11941321
cmd.arg("-g").arg("gimlet");
11951322
cmd.arg("-T").arg(&templates);
1323+
if let Some(genproto) = &genproto {
1324+
cmd.arg("-E").arg(extra_proto.as_deref().unwrap());
1325+
cmd.arg("-F").arg(&format!("genproto={}",
1326+
genproto.to_str().unwrap()));
1327+
}
11961328
if let Some(cdock) = &cdock {
11971329
cmd.arg("-F").arg("compliance");
11981330
cmd.arg("-F").arg("stress");
@@ -1821,6 +1953,13 @@ fn main() -> Result<()> {
18211953
hide: false,
18221954
blank: false,
18231955
});
1956+
handlers.push(CommandInfo {
1957+
name: "image".into(),
1958+
desc: "experimental image construction for Gimlets".into(),
1959+
func: cmd_image,
1960+
hide: true,
1961+
blank: false,
1962+
});
18241963

18251964
let usage = || {
18261965
let mut out = String::new();

0 commit comments

Comments
 (0)