@@ -4,9 +4,10 @@ use common::*;
44use anyhow:: { Result , Context , bail} ;
55use serde:: Deserialize ;
66use std:: collections:: HashMap ;
7+ use std:: os:: unix:: fs:: PermissionsExt ;
78use std:: process:: Command ;
89use std:: os:: unix:: process:: CommandExt ;
9- use std:: io:: { BufReader , Read } ;
10+ use std:: io:: { BufReader , Read , Write } ;
1011use std:: fs:: File ;
1112use std:: time:: { Instant , SystemTime } ;
1213use 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+
10391136fn 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