aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/AtomicFiles.java
blob: 6f755b6e144d9715bdb12d9b4e19ab31648e38e5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package invalid.lena.scrcpy;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

// Crash-atomic whole-file write: stage the bytes into a sibling temp file,
// 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.
//
// Deliberately no fsync of the parent directory: the rename itself may be
// lost on power failure (the old content survives intact). Callers store
// re-creatable state, so atomicity matters here and durability does not.
//
// android-free (java.io/java.nio only) so it is unit-testable on the JVM.
final class AtomicFiles {

    private AtomicFiles() {}

    static 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();
        }
        try {
            Files.move(tmp.toPath(), dest.toPath(),
                    StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            tmp.delete();
            throw e;
        }
    }
}