From 852a8a00273c9128efeed0217a8e5d3fcd8bf780 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 1 Aug 2026 00:00:00 +0000 Subject: app: harden mirroring lifecycle and state --- .../main/java/invalid/lena/scrcpy/AtomicFiles.java | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'app/src/main/java/invalid/lena/scrcpy/AtomicFiles.java') diff --git a/app/src/main/java/invalid/lena/scrcpy/AtomicFiles.java b/app/src/main/java/invalid/lena/scrcpy/AtomicFiles.java index 6f755b6..77235f6 100644 --- a/app/src/main/java/invalid/lena/scrcpy/AtomicFiles.java +++ b/app/src/main/java/invalid/lena/scrcpy/AtomicFiles.java @@ -10,7 +10,8 @@ import java.nio.file.StandardCopyOption; // fsync it, then rename it over the destination. The rename is the only // mutation a concurrent reader can observe, so a reader sees either the old // file or the new file in full, never a truncated mix. A crash mid-write -// leaves at most a stale ".tmp", never a damaged destination. +// may leave a stale staging file, never a damaged destination. The next +// write removes stale staging files before creating its own. // // Deliberately no fsync of the parent directory: the rename itself may be // lost on power failure (the old content survives intact). Callers store @@ -21,20 +22,29 @@ final class AtomicFiles { private AtomicFiles() {} - static void write(File dest, byte[] data) throws IOException { + static synchronized void write(File dest, byte[] data) throws IOException { File parent = dest.getAbsoluteFile().getParentFile(); - File tmp = new File(parent, dest.getName() + ".tmp"); - try (FileOutputStream os = new FileOutputStream(tmp)) { - os.write(data); - os.flush(); - os.getFD().sync(); + if (parent == null || !parent.isDirectory()) { + throw new IOException("destination parent is not a directory: " + parent); } + String prefix = dest.getName() + ".tmp"; + File[] stale = parent.listFiles((dir, name) -> + name.equals(prefix) || name.startsWith(prefix + "-")); + if (stale == null) throw new IOException("cannot list destination parent: " + parent); + for (File file : stale) Files.deleteIfExists(file.toPath()); + File tmp = Files.createTempFile(parent.toPath(), dest.getName() + ".tmp-", null).toFile(); + boolean moved = false; try { + try (FileOutputStream os = new FileOutputStream(tmp)) { + os.write(data); + os.flush(); + os.getFD().sync(); + } Files.move(tmp.toPath(), dest.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - tmp.delete(); - throw e; + moved = true; + } finally { + if (!moved) Files.deleteIfExists(tmp.toPath()); } } } -- cgit v1.2.3