diff --git a/asm/src/main/java/dev/openrs2/asm/classpath/Library.kt b/asm/src/main/java/dev/openrs2/asm/classpath/Library.kt index d77c61e0..696bc967 100644 --- a/asm/src/main/java/dev/openrs2/asm/classpath/Library.kt +++ b/asm/src/main/java/dev/openrs2/asm/classpath/Library.kt @@ -3,6 +3,7 @@ package dev.openrs2.asm.classpath import com.github.michaelbull.logging.InlineLogger import dev.openrs2.asm.io.LibraryReader import dev.openrs2.asm.io.LibraryWriter +import dev.openrs2.util.io.useAtomicOutputStream import org.objectweb.asm.tree.ClassNode import java.nio.file.Files import java.nio.file.Path @@ -47,7 +48,7 @@ class Library(val name: String) : Iterable { fun write(path: Path, writer: LibraryWriter, classPath: ClassPath) { logger.info { "Writing library $path" } - Files.newOutputStream(path).use { output -> + path.useAtomicOutputStream { output -> writer.write(output, classPath, classes.values) } } diff --git a/crypto/build.gradle.kts b/crypto/build.gradle.kts index f38da2f8..e2d42fd0 100644 --- a/crypto/build.gradle.kts +++ b/crypto/build.gradle.kts @@ -9,6 +9,8 @@ dependencies { api("org.bouncycastle:bcpkix-jdk15on:${Versions.bouncyCastlePkix}") api("org.bouncycastle:bcprov-jdk15on:${Versions.bouncyCastleProvider}") + implementation(project(":util")) + testImplementation("com.google.jimfs:jimfs:${Versions.jimfs}") } diff --git a/crypto/src/main/java/dev/openrs2/crypto/Pkcs12KeyStore.kt b/crypto/src/main/java/dev/openrs2/crypto/Pkcs12KeyStore.kt index 99cf3ddb..087a3096 100644 --- a/crypto/src/main/java/dev/openrs2/crypto/Pkcs12KeyStore.kt +++ b/crypto/src/main/java/dev/openrs2/crypto/Pkcs12KeyStore.kt @@ -1,5 +1,6 @@ package dev.openrs2.crypto +import dev.openrs2.util.io.useAtomicOutputStream import jdk.security.jarsigner.JarSigner import org.bouncycastle.asn1.nist.NISTObjectIdentifiers import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers @@ -66,7 +67,7 @@ class Pkcs12KeyStore private constructor(privateKeyEntry: KeyStore.PrivateKeyEnt val entry = createPrivateKeyEntry(signerName) keyStore.setEntry(ALIAS, entry, PASSWORD_PARAMETER) - Files.newOutputStream(path).use { output -> + path.useAtomicOutputStream { output -> keyStore.store(output, PASSWORD_CHARS) } diff --git a/crypto/src/main/java/dev/openrs2/crypto/Rsa.kt b/crypto/src/main/java/dev/openrs2/crypto/Rsa.kt index 5e07e73d..c42ced04 100644 --- a/crypto/src/main/java/dev/openrs2/crypto/Rsa.kt +++ b/crypto/src/main/java/dev/openrs2/crypto/Rsa.kt @@ -1,5 +1,6 @@ package dev.openrs2.crypto +import dev.openrs2.util.io.useAtomicBufferedWriter import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufUtil import io.netty.buffer.Unpooled @@ -201,8 +202,10 @@ object Rsa { } private fun writeSinglePemObject(path: Path, type: String, content: ByteArray) { - PemWriter(Files.newBufferedWriter(path)).use { - it.writeObject(PemObject(type, content)) + path.useAtomicBufferedWriter { writer -> + PemWriter(writer).use { + it.writeObject(PemObject(type, content)) + } } } } diff --git a/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.kt b/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.kt index 657c842f..26327e42 100644 --- a/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.kt +++ b/decompiler/src/main/java/dev/openrs2/decompiler/DecompilerIo.kt @@ -1,5 +1,6 @@ package dev.openrs2.decompiler +import dev.openrs2.util.io.useAtomicBufferedWriter import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider import org.jetbrains.java.decompiler.main.extern.IResultSaver import java.io.Closeable @@ -64,7 +65,7 @@ class DecompilerIo(private val destination: Path) : IBytecodeProvider, IResultSa ) { val p = destination.resolve(entryName) Files.createDirectories(p.parent) - Files.newBufferedWriter(p).use { + p.useAtomicBufferedWriter { it.write(content) } } diff --git a/deob-processor/src/main/java/dev/openrs2/deob/processor/NameMapProcessor.kt b/deob-processor/src/main/java/dev/openrs2/deob/processor/NameMapProcessor.kt index 67458869..2a4ccec9 100644 --- a/deob-processor/src/main/java/dev/openrs2/deob/processor/NameMapProcessor.kt +++ b/deob-processor/src/main/java/dev/openrs2/deob/processor/NameMapProcessor.kt @@ -11,6 +11,7 @@ import dev.openrs2.deob.annotation.OriginalMember import dev.openrs2.deob.map.Field import dev.openrs2.deob.map.Method import dev.openrs2.deob.map.NameMap +import dev.openrs2.util.io.useAtomicBufferedWriter import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -106,7 +107,7 @@ class NameMapProcessor : AbstractProcessor() { combinedMap = map } - Files.newBufferedWriter(mapPath).use { writer -> + mapPath.useAtomicBufferedWriter { writer -> mapper.writeValue(writer, combinedMap) } } diff --git a/util/src/main/java/dev/openrs2/util/io/PathExtensions.kt b/util/src/main/java/dev/openrs2/util/io/PathExtensions.kt new file mode 100644 index 00000000..dfa72a90 --- /dev/null +++ b/util/src/main/java/dev/openrs2/util/io/PathExtensions.kt @@ -0,0 +1,44 @@ +package dev.openrs2.util.io + +import java.io.BufferedWriter +import java.io.OutputStream +import java.nio.charset.Charset +import java.nio.file.Files +import java.nio.file.OpenOption +import java.nio.file.Path +import java.nio.file.StandardCopyOption + +inline fun Path.atomicWrite(f: (Path) -> T): T { + val tempFile = Files.createTempFile(parent, ".$fileName", ".tmp") + try { + val result = f(tempFile) + Files.move(tempFile, this, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING) + return result + } finally { + Files.deleteIfExists(tempFile) + } +} + +inline fun Path.useAtomicBufferedWriter(vararg options: OpenOption, f: (BufferedWriter) -> T): T { + return atomicWrite { path -> + Files.newBufferedWriter(path, *options).use { writer -> + f(writer) + } + } +} + +inline fun Path.useAtomicBufferedWriter(cs: Charset, vararg options: OpenOption, f: (BufferedWriter) -> T): T { + return atomicWrite { path -> + Files.newBufferedWriter(path, cs, *options).use { writer -> + f(writer) + } + } +} + +inline fun Path.useAtomicOutputStream(vararg options: OpenOption, f: (OutputStream) -> T): T { + return atomicWrite { path -> + Files.newOutputStream(path, *options).use { output -> + f(output) + } + } +}