@@ -60,6 +60,8 @@ pub fn find_or_create_base_disk(
6060 }
6161
6262 // Base disk doesn't exist or was stale, create it
63+ // Multiple concurrent processes may race to create this, but each uses
64+ // a unique temp file, so they won't conflict
6365 info ! ( "Creating base disk: {:?}" , base_disk_path) ;
6466 create_base_disk (
6567 & base_disk_path,
@@ -85,16 +87,23 @@ fn create_base_disk(
8587 use crate :: run_ephemeral:: CommonVmOpts ;
8688 use crate :: to_disk:: { Format , ToDiskAdditionalOpts , ToDiskOpts } ;
8789
88- // Use a temporary location during installation to avoid caching incomplete disks
89- let temp_disk_path = base_disk_path. with_extension ( "qcow2.tmp" ) ;
90+ // Use a unique temporary file to avoid conflicts when multiple processes
91+ // race to create the same base disk
92+ let temp_file = tempfile:: Builder :: new ( )
93+ . prefix ( & format ! ( "{}." , base_disk_path. file_stem( ) . unwrap( ) ) )
94+ . suffix ( ".tmp.qcow2" )
95+ . tempfile_in ( base_disk_path. parent ( ) . unwrap ( ) )
96+ . with_context ( || {
97+ format ! (
98+ "Failed to create temp file in {:?}" ,
99+ base_disk_path. parent( )
100+ )
101+ } ) ?;
90102
91- // Helper to cleanup temp disk on error
92- let cleanup_temp_disk = || {
93- if temp_disk_path. exists ( ) {
94- debug ! ( "Cleaning up temporary base disk: {:?}" , temp_disk_path) ;
95- let _ = fs:: remove_file ( & temp_disk_path) ;
96- }
97- } ;
103+ let temp_disk_path = Utf8PathBuf :: from ( temp_file. path ( ) . to_str ( ) . unwrap ( ) ) ;
104+
105+ // Keep the temp file open so it gets cleaned up automatically if we error out
106+ // We'll persist it manually on success
98107
99108 // Create the disk using to_disk at temporary location
100109 let to_disk_opts = ToDiskOpts {
@@ -120,12 +129,9 @@ fn create_base_disk(
120129 } ;
121130
122131 // Run bootc install - if it succeeds, the disk is valid
123- if let Err ( e) = crate :: to_disk:: run ( to_disk_opts) {
124- cleanup_temp_disk ( ) ;
125- return Err ( e) . with_context ( || {
126- format ! ( "Failed to install bootc to base disk: {:?}" , temp_disk_path)
127- } ) ;
128- }
132+ // On error, temp_file is automatically cleaned up when dropped
133+ crate :: to_disk:: run ( to_disk_opts)
134+ . with_context ( || format ! ( "Failed to install bootc to base disk: {:?}" , temp_disk_path) ) ?;
129135
130136 // If we got here, bootc install succeeded - verify metadata was written
131137 let metadata_valid = crate :: cache_metadata:: check_cached_disk (
@@ -138,15 +144,25 @@ fn create_base_disk(
138144
139145 match metadata_valid {
140146 Ok ( ( ) ) => {
141- // All validations passed - move to final location
142- if let Err ( e) = fs:: rename ( & temp_disk_path, base_disk_path) {
143- cleanup_temp_disk ( ) ;
144- return Err ( e) . with_context ( || {
145- format ! (
146- "Failed to move validated base disk from {:?} to {:?}" ,
147- temp_disk_path, base_disk_path
148- )
149- } ) ;
147+ // All validations passed - persist temp file to final location
148+ // If another concurrent process already created the file, that's fine
149+ match temp_file. persist ( base_disk_path) {
150+ Ok ( _) => {
151+ debug ! ( "Successfully created base disk: {:?}" , base_disk_path) ;
152+ }
153+ Err ( e) if e. error . kind ( ) == std:: io:: ErrorKind :: AlreadyExists => {
154+ // Another process won the race and created the base disk
155+ debug ! (
156+ "Base disk already created by another process: {:?}" ,
157+ base_disk_path
158+ ) ;
159+ // temp file is cleaned up when e is dropped
160+ }
161+ Err ( e) => {
162+ return Err ( e. error ) . with_context ( || {
163+ format ! ( "Failed to persist base disk to {:?}" , base_disk_path)
164+ } ) ;
165+ }
150166 }
151167
152168 // Refresh libvirt storage pool so the new disk is visible to virsh
@@ -171,7 +187,7 @@ fn create_base_disk(
171187 Ok ( ( ) )
172188 }
173189 Err ( e) => {
174- cleanup_temp_disk ( ) ;
190+ // temp_file will be automatically cleaned up when dropped
175191 Err ( eyre ! ( "Generated disk metadata validation failed: {e}" ) )
176192 }
177193 }
0 commit comments