Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions build-logic/src/main/kotlin/cryptad.runtime.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import java.io.ByteArrayOutputStream
import java.util.concurrent.TimeUnit

plugins { java }

Expand Down Expand Up @@ -166,28 +167,25 @@ val createJreImage by
val javaHomePath = launcher.map { it.metadata.installationPath.asFile.absolutePath }
val jreDirProvider = layout.buildDirectory.dir("jre")
val modulesFileProvider = layout.buildDirectory.dir("jlink").map { it.file("modules.list") }
val jlinkCompressionProvider = providers.gradleProperty("jlinkCompression")
doLast {
val osName = System.getProperty("os.name").lowercase()
val javaHome = File(javaHomePath.get())
val jlink =
javaHome.resolve(
"bin/jlink${if (System.getProperty("os.name").lowercase().contains("win")) ".exe" else ""}"
)
val jlink = javaHome.resolve("bin/jlink${if (osName.contains("win")) ".exe" else ""}")
val jmods = javaHome.resolve("jmods")

val jreDir = jreDirProvider.get().asFile
if (jreDir.exists()) jreDir.deleteRecursively()

val modulesFile = modulesFileProvider.get().asFile
val modulesArg = if (modulesFile.isFile) modulesFile.readText().trim() else "java.base"
val jlinkCompression =
jlinkCompressionProvider.orNull?.trim().takeUnless { it.isNullOrBlank() } ?: "zip-6"

val args =
mutableListOf(
jlink.absolutePath,
"-v",
"--strip-debug",
// Use non-deprecated compression syntax (replaces numeric level 2)
"--compress",
"zip-6",
mutableListOf(jlink.absolutePath, "-v", "--strip-debug", "--compress", jlinkCompression)
args.addAll(
listOf(
"--no-header-files",
"--no-man-pages",
"--module-path",
Expand All @@ -197,10 +195,41 @@ val createJreImage by
"--output",
jreDir.absolutePath,
)
)

println("Executing jlink: ${args.joinToString(" ")}")
val process = ProcessBuilder(args).inheritIO().start()
val exit = process.waitFor()
val process = ProcessBuilder(args).redirectErrorStream(true).start()
val outputPump =
Thread {
process.inputStream.bufferedReader().useLines { lines ->
lines.forEach { line -> logger.lifecycle(line) }
}
}
.apply {
name = "jlink-output-pump"
isDaemon = true
start()
}

val completed = process.waitFor(10, TimeUnit.MINUTES)
outputPump.join(2_000)
if (!completed) {
val jreReady =
(jreDir.resolve("release").isFile &&
jreDir.resolve("lib/modules").isFile &&
(jreDir.resolve("bin/java.exe").isFile || jreDir.resolve("bin/java").isFile))
if (jreReady) {
logger.warn(
"jlink did not exit, but the runtime image is complete. Terminating lingering jlink process."
)
process.destroyForcibly()
return@doLast
}
process.destroyForcibly()
throw GradleException("jlink timed out after 10 minutes")
}

val exit = process.exitValue()
if (exit != 0) {
throw GradleException("jlink failed with exit code $exit")
}
Expand Down
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ tasks.register("printVersion") {
doLast { println(project.version.toString()) }
}

// The default from build-logic (512m) is too small for the full test suite on Windows and can
// trigger OOM in long-running integration tests.
tasks.withType<org.gradle.api.tasks.testing.Test>().configureEach { maxHeapSize = "2g" }

// Application entrypoint (used by jpackage). This does not change how we build the wrapper
// distribution; it's only to inform launchers that invoke the launcher main class directly.
// Align with the actual launcher entrypoint in Launcher.java
Expand Down
75 changes: 42 additions & 33 deletions src/main/java/network/crypta/client/async/SplitFileFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import network.crypta.support.compress.Compressor.COMPRESSOR_TYPE;
import network.crypta.support.io.BucketTools;
import network.crypta.support.io.FileUtil;
import network.crypta.support.io.IOUtils;
import network.crypta.support.io.InsufficientDiskSpaceException;
import network.crypta.support.io.PooledFileRandomAccessBuffer;
import network.crypta.support.io.ResumeFailedException;
Expand Down Expand Up @@ -791,40 +792,48 @@ public boolean writeTrivialProgress(DataOutputStream dos) throws IOException {
public SplitFileFetcher(ClientGetter getter, DataInputStream dis, ClientContext context)
throws StorageFormatException, ResumeFailedException, IOException {
LOG.info("Resuming splitfile download for {}", this);
boolean completeViaTruncation = dis.readBoolean();
if (completeViaTruncation) {
fileCompleteViaTruncation = new File(dis.readUTF());
if (!fileCompleteViaTruncation.exists())
throw new ResumeFailedException(
"Storage file does not exist: " + fileCompleteViaTruncation);
callbackCompleteViaTruncation = getter;
long rafSize = dis.readLong();
if (fileCompleteViaTruncation.length() != rafSize)
throw new ResumeFailedException("Storage file is not of the correct length");
// Note: Could verify against finalLength to finish immediately if it matches.
this.raf =
new PooledFileRandomAccessBuffer(fileCompleteViaTruncation, false, rafSize, -1, true);
} else {
this.raf =
BucketTools.restoreRAFFrom(
dis,
context.persistentFG,
context.getPersistentFileTracker(),
context.getPersistentMasterSecret());
fileCompleteViaTruncation = null;
callbackCompleteViaTruncation = null;
LockableRandomAccessBuffer restored = null;
boolean success = false;
try {
boolean completeViaTruncation = dis.readBoolean();
if (completeViaTruncation) {
fileCompleteViaTruncation = new File(dis.readUTF());
if (!fileCompleteViaTruncation.exists())
throw new ResumeFailedException(
"Storage file does not exist: " + fileCompleteViaTruncation);
callbackCompleteViaTruncation = getter;
long rafSize = dis.readLong();
if (fileCompleteViaTruncation.length() != rafSize)
throw new ResumeFailedException("Storage file is not of the correct length");
// Note: Could verify against finalLength to finish immediately if it matches.
restored =
new PooledFileRandomAccessBuffer(fileCompleteViaTruncation, false, rafSize, -1, true);
} else {
restored =
BucketTools.restoreRAFFrom(
dis,
context.persistentFG,
context.getPersistentFileTracker(),
context.getPersistentMasterSecret());
fileCompleteViaTruncation = null;
callbackCompleteViaTruncation = null;
}
this.raf = restored;
this.parent = getter;
this.cb = getter;
this.persistent = true;
this.realTimeFlag = parent.realTimeFlag();
this.requestKey = null;
token = dis.readLong();
this.blockFetchContext = getter.ctx;
this.wantBinaryBlob = getter.collectingBinaryBlob();
// onResume() will do the rest.
LOG.info("Resumed splitfile download for {}", this);
lastNotifiedStoreFetch = System.currentTimeMillis();
success = true;
} finally {
if (!success) IOUtils.closeQuietly(restored);
}
this.parent = getter;
this.cb = getter;
this.persistent = true;
this.realTimeFlag = parent.realTimeFlag();
this.requestKey = null;
token = dis.readLong();
this.blockFetchContext = getter.ctx;
this.wantBinaryBlob = getter.collectingBinaryBlob();
// onResume() will do the rest.
LOG.info("Resumed splitfile download for {}", this);
lastNotifiedStoreFetch = System.currentTimeMillis();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import network.crypta.support.io.ArrayBucket;
import network.crypta.support.io.ArrayBucketFactory;
import network.crypta.support.io.BucketTools;
import network.crypta.support.io.IOUtils;
import network.crypta.support.io.NullBucket;
import network.crypta.support.io.RAFInputStream;
import network.crypta.support.io.ResumeFailedException;
Expand Down Expand Up @@ -1128,7 +1129,14 @@ private LockableRandomAccessBuffer chooseOriginalData(
LockableRandomAccessBuffer passed, LockableRandomAccessBuffer restored)
throws StorageFormatException {
if (passed == null) return restored;
if (!passed.equals(restored))
if (passed == restored) return passed;
boolean same;
try {
same = passed.equals(restored);
} finally {
IOUtils.closeQuietly(restored);
}
if (!same)
throw new StorageFormatException(
"Original data restored from different filename! Expected "
+ passed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import network.crypta.support.api.LockableRandomAccessBuffer;
import network.crypta.support.io.BucketTools;
import network.crypta.support.io.FilenameGenerator;
import network.crypta.support.io.IOUtils;
import network.crypta.support.io.PersistentFileTracker;
import network.crypta.support.io.ResumeFailedException;
import network.crypta.support.io.StorageFormatException;
Expand Down Expand Up @@ -578,11 +579,17 @@ public static LockableRandomAccessBuffer create(
if (type == null) throw new StorageFormatException("Unknown EncryptedRandomAccessBufferType");
LockableRandomAccessBuffer underlying =
BucketTools.restoreRAFFrom(dis, fg, persistentFileTracker, masterKey);
boolean success = false;
try {
return new EncryptedRandomAccessBuffer(type, underlying, masterKey, false);
EncryptedRandomAccessBuffer restored =
new EncryptedRandomAccessBuffer(type, underlying, masterKey, false);
success = true;
return restored;
} catch (GeneralSecurityException e) {
throw new ResumeFailedException(
new GeneralSecurityException("Crypto error resuming EncryptedRandomAccessBuffer", e));
} finally {
if (!success) IOUtils.closeQuietly(underlying);
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/main/java/network/crypta/crypt/KeyGenUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ private static int getJavaVersion() {
// 9-ea
// 9
// 9.0.1
// 21
int dotPos = version.indexOf('.');
int dashPos = version.indexOf('-');
int end = 1;
if (dotPos > -1) {
int end = version.length();
if (dotPos > -1 && dotPos < end) {
end = dotPos;
} else if (dashPos > -1) {
}
if (dashPos > -1 && dashPos < end) {
end = dashPos;
}
return Integer.parseInt(version.substring(0, end));
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/network/crypta/launcher/LauncherController.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ public synchronized void start() {

private void startProcessAndWatch() {
Path cryptadPath = LauncherUtils.resolveCryptadPath(cwd);
if (!Files.isRegularFile(cryptadPath) || !Files.isExecutable(cryptadPath)) {
AppEnv env = new AppEnv();
boolean launchable =
Files.isRegularFile(cryptadPath) && (env.isWindows() || Files.isExecutable(cryptadPath));
if (!launchable) {
emitLog(ts() + " ERROR: Cannot find executable 'cryptad' at " + cryptadPath);
return;
}
Expand Down Expand Up @@ -247,6 +250,7 @@ private void watchProcess(Process started) {
}
emitLog(ts() + " cryptad exited with code " + exitCode);
clearTrackedProcess(started);
interruptTailThread();
updateState(s -> s.withRunning(false));
}

Expand Down Expand Up @@ -293,6 +297,11 @@ private void stopManagedProcess(Process current) {
if (current.isAlive()) {
current.destroyForcibly();
}
if (!current.isAlive()) {
clearTrackedProcess(current);
interruptTailThread();
updateState(s -> s.withRunning(false));
}
} catch (Exception e) {
emitLog(ts() + " ERROR: Failed to stop process: " + e.getMessage());
logDebug("Failed to stop process gracefully", e);
Expand Down Expand Up @@ -799,6 +808,7 @@ public void shutdownAndWait() {
if (current != null && current.isAlive()) {
stopManagedProcess(current);
}
interruptTailThread();
}

private void tryEnableConsoleFlush(Path cryptadPath) {
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/network/crypta/launcher/LauncherUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ private static Path resolveCryptadPathFromJar(boolean isWindows) {
candidates.add(jarDir.resolve("../bin/" + CRYPTAD_SCRIPT).normalize());

for (Path candidate : candidates) {
if (Files.isRegularFile(candidate) && Files.isExecutable(candidate)) {
if (isLaunchScriptCandidate(candidate, isWindows)) {
return candidate;
}
}
Expand All @@ -439,13 +439,24 @@ private static Path resolveCryptadPathFromCwd(Path cwd, boolean isWindows) {
candidates.add(cwd.resolve(CRYPTAD_SCRIPT));

for (Path candidate : candidates) {
if (Files.isRegularFile(candidate) && Files.isExecutable(candidate)) {
if (isLaunchScriptCandidate(candidate, isWindows)) {
return candidate;
}
}
return null;
}

private static boolean isLaunchScriptCandidate(Path candidate, boolean isWindows) {
if (!Files.isRegularFile(candidate)) {
return false;
}
// Windows launch scripts are often .bat files where executable bit checks are unreliable.
if (isWindows) {
return true;
}
return Files.isExecutable(candidate);
}

private static boolean isCryptadJarFile(Path path) {
if (!Files.isRegularFile(path)) {
return false;
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/network/crypta/node/PeerPersistence.java
Original file line number Diff line number Diff line change
Expand Up @@ -514,21 +514,24 @@ private void writePeersInner(String filename, String sb, int maxBackups, boolean
w.write(sb);
w.flush();
fos.getFD().sync();

if (rotateBackups) {
rotateBackupFiles(filename, maxBackups, f);
} else {
FileUtil.moveTo(f, getBackupFilename(filename, 0));
}
} catch (FileNotFoundException e2) {
LOG.error("Cannot write peers to disk: cannot create {} (error={})", f, e2, e2);
safeDeleteIfExists(f);
return;
} catch (IOException e) {
LOG.error("I/O error writing peers file: {}", e, e);
safeDeleteIfExists(f);
// don't overwrite the old file!
return;
}

try {
if (rotateBackups) {
rotateBackupFiles(filename, maxBackups, f);
} else {
FileUtil.moveTo(f, getBackupFilename(filename, 0));
}
} finally {
// Try-with-resources handles the stream cleanup
safeDeleteIfExists(f);
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/network/crypta/support/BinaryBloomFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystems;
import java.nio.file.Files;
Expand Down Expand Up @@ -78,7 +77,7 @@ public final class BinaryBloomFilter extends BloomFilter {
try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel channel = raf.getChannel()) {
raf.setLength(length / 8);
filter = channel.map(MapMode.READ_WRITE, 0, length / 8).load();
filter = mapReadWriteBuffer(channel, length / 8L);
}
}

Expand Down
Loading
Loading