@@ -242,6 +242,11 @@ impl App {
242242 fs:: remove_file ( path) ?;
243243 }
244244 }
245+ // Append current serial.log to serial.history.log before QEMU truncates it.
246+ rotate_serial_log ( & work_dir, self . config . cvm . serial_history_max_bytes ) ;
247+ // Add boot separator to stdout/stderr (they are opened in append mode).
248+ append_boot_separator ( & work_dir. stdout_file ( ) ) ;
249+ append_boot_separator ( & work_dir. stderr_file ( ) ) ;
245250
246251 let devices = self . try_allocate_gpus ( & vm_config. manifest ) ?;
247252 let processes = vm_config. config_qemu ( & work_dir, & self . config . cvm , & devices) ?;
@@ -1051,6 +1056,64 @@ impl App {
10511056 }
10521057}
10531058
1059+ /// Append a boot separator line with timestamp to an append-mode log file.
1060+ fn append_boot_separator ( path : & std:: path:: Path ) {
1061+ use std:: io:: Write ;
1062+ if !path. exists ( ) {
1063+ return ;
1064+ }
1065+ let Ok ( mut file) = std:: fs:: OpenOptions :: new ( ) . append ( true ) . open ( path) else {
1066+ return ;
1067+ } ;
1068+ let timestamp = humantime:: format_rfc3339_seconds ( std:: time:: SystemTime :: now ( ) ) ;
1069+ let _ = writeln ! ( file, "\n ===== boot @ {timestamp} =====\n " ) ;
1070+ }
1071+
1072+ /// Append current serial.log into serial.history.log with a boot separator,
1073+ /// then truncate history if it exceeds `max_bytes`.
1074+ fn rotate_serial_log ( work_dir : & VmWorkDir , max_bytes : u64 ) {
1075+ use std:: io:: Write ;
1076+
1077+ let serial = work_dir. serial_file ( ) ;
1078+ if !serial. exists ( ) {
1079+ return ;
1080+ }
1081+ let Ok ( content) = fs:: read ( & serial) else {
1082+ return ;
1083+ } ;
1084+ if content. is_empty ( ) {
1085+ return ;
1086+ }
1087+ let history = work_dir. serial_history_file ( ) ;
1088+ let Ok ( mut file) = std:: fs:: OpenOptions :: new ( )
1089+ . create ( true )
1090+ . append ( true )
1091+ . open ( & history)
1092+ else {
1093+ return ;
1094+ } ;
1095+ let timestamp = humantime:: format_rfc3339_seconds ( std:: time:: SystemTime :: now ( ) ) ;
1096+ let _ = writeln ! ( file, "\n ===== boot @ {timestamp} =====\n " ) ;
1097+ let _ = file. write_all ( & content) ;
1098+ drop ( file) ;
1099+
1100+ // Truncate from the front if history exceeds max_bytes.
1101+ if let Ok ( meta) = fs:: metadata ( & history) {
1102+ if meta. len ( ) > max_bytes {
1103+ if let Ok ( data) = fs:: read ( & history) {
1104+ let skip = data. len ( ) - max_bytes as usize ;
1105+ // Find the next newline after skip point to avoid cutting mid-line.
1106+ let start = data[ skip..]
1107+ . iter ( )
1108+ . position ( |& b| b == b'\n' )
1109+ . map ( |p| skip + p + 1 )
1110+ . unwrap_or ( skip) ;
1111+ let _ = fs:: write ( & history, & data[ start..] ) ;
1112+ }
1113+ }
1114+ }
1115+ }
1116+
10541117pub ( crate ) fn make_sys_config ( cfg : & Config , manifest : & Manifest ) -> Result < String > {
10551118 let image_path = cfg. image_path . join ( & manifest. image ) ;
10561119 let image = Image :: load ( image_path) . context ( "Failed to load image info" ) ?;
0 commit comments