fs: Fall back from atomic write to regular fs write when file handle is in use on Windows (#30222)

Smit Barmase created

Closes #30054

For reference, another way to work around this is to drop the file
handle which we can't do in this case, as it would require reopening the
settings.json worktree, which is a rather unpleasant fix.

Another approach might be to open the file handle with some special
flags, but I couldn't get that to work at the time of writing.

Release Notes:

- Fixed "Backup and Update" in settings migration not working on
Windows.

Change summary

crates/fs/src/fs.rs | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)

Detailed changes

crates/fs/src/fs.rs 🔗

@@ -546,7 +546,24 @@ impl Fs for RealFs {
                 NamedTempFile::new()
             }?;
             tmp_file.write_all(data.as_bytes())?;
-            tmp_file.persist(path)?;
+
+            let result = tmp_file.persist(&path);
+            if cfg!(target_os = "windows") {
+                // If file handle is already in used we receive error:
+                //
+                // failed to persist temporary file:
+                // Access is denied. (os error 5)
+                //
+                // So we use direct fs write instead to avoid it.
+                // https://github.com/zed-industries/zed/issues/30054
+                if let Err(persist_err) = &result {
+                    if persist_err.error.raw_os_error() == Some(5) {
+                        return std::fs::write(&path, data.as_bytes()).map_err(Into::into);
+                    }
+                }
+            }
+            result?;
+
             Ok::<(), anyhow::Error>(())
         })
         .await?;