SystemOperation.kt
package de.pflugradts.passbird.application.util
import de.pflugradts.kotlinextensions.tryCatching
import de.pflugradts.passbird.application.Directory
import de.pflugradts.passbird.application.FileName
import de.pflugradts.passbird.application.toFileName
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.nio.file.attribute.PosixFileAttributeView
import java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE
import java.nio.file.attribute.PosixFilePermission.OWNER_READ
import java.nio.file.attribute.PosixFilePermission.OWNER_WRITE
import java.time.Clock
import kotlin.io.path.name
import kotlin.system.exitProcess
const val FAILURE_EXIT_STATUS = 1
const val SUCCESS_EXIT_STATUS = 0
private val PRIVATE_DIRECTORY_PERMISSIONS = setOf(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
private val PRIVATE_FILE_PERMISSIONS = setOf(OWNER_READ, OWNER_WRITE)
class SystemOperation {
val clock = Clock.systemUTC()
fun getFileNames(directory: Directory): List<FileName> = Files.list(getPath(directory)).map { it.name.toFileName() }.toList()
fun getPath(directory: Directory): Path = Paths.get(directory.value)
fun resolvePath(directory: Directory, fileName: FileName): Path = getPath(directory).resolve(fileName.value)
fun resolvePath(directory: Directory, other: Directory): Path = getPath(directory).resolve(other.value)
fun copyTo(source: Path, target: Path) {
writeToSensitiveFile(target) { outputStream ->
newInputStream(source).use { inputStream -> inputStream.copyTo(outputStream) }
}
}
fun createDirectory(directory: Directory) {
Files.createDirectories(getPath(directory))
applyPosixPermissionsIfSupported(getPath(directory), PRIVATE_DIRECTORY_PERMISSIONS)
}
fun delete(path: Path) = Files.delete(path)
fun exists(directory: Directory): Boolean = exists(getPath(directory))
fun exists(path: Path): Boolean = Files.exists(path)
fun isDirectory(directory: Directory): Boolean = Files.isDirectory(getPath(directory))
fun newInputStream(path: Path): InputStream = Files.newInputStream(path)
fun newOutputStream(path: Path): OutputStream = Files.newOutputStream(path)
fun writeToSensitiveFile(path: Path, write: (OutputStream) -> Unit): Path {
val targetPath = path.toAbsolutePath()
val tempPath = Files.createTempFile(targetPath.parent, ".${targetPath.fileName}.", ".tmp")
return tryCatching {
newOutputStream(tempPath).use(write)
Files.move(tempPath, targetPath, REPLACE_EXISTING)
applyPosixPermissionsIfSupported(targetPath, PRIVATE_FILE_PERMISSIONS)
targetPath
}.let { result ->
result.exceptionOrNull()?.let {
runCatching { Files.deleteIfExists(tempPath) }
throw it
}
result.getOrNull()!!
}
}
fun writeBytesToSensitiveFile(path: Path, byteArray: ByteArray): Path = writeToSensitiveFile(path) { outputStream ->
outputStream.write(byteArray)
}
fun writeStringToSensitiveFile(path: Path, content: String): Path = writeToSensitiveFile(path) { outputStream ->
outputStream.write(content.toByteArray())
}
fun readBytesFromFile(path: Path): ByteArray = Files.readAllBytes(path)
fun exit(status: Int = SUCCESS_EXIT_STATUS): Unit = exitProcess(status)
private fun applyPosixPermissionsIfSupported(path: Path, permissions: Set<java.nio.file.attribute.PosixFilePermission>) {
runCatching {
Files.getFileAttributeView(path, PosixFileAttributeView::class.java)?.setPermissions(permissions)
}
}
}