65 lines
2.3 KiB
Java
65 lines
2.3 KiB
Java
package com.chaegeon.niro;
|
|
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
|
|
/** Root-only, per-run E2E evidence. No request, location or secret is stored. */
|
|
final class WeatherRefreshCounters {
|
|
private final File directory;
|
|
private long signal;
|
|
private long callback;
|
|
|
|
WeatherRefreshCounters(File directory) throws IOException {
|
|
if (!directory.isDirectory()
|
|
|| !directory.getCanonicalPath().equals(directory.getAbsolutePath())) {
|
|
throw new IOException("invalid counter directory");
|
|
}
|
|
this.directory = directory;
|
|
writeAtomic("signal.counter", 0L);
|
|
writeAtomic("callback.counter", 0L);
|
|
}
|
|
|
|
synchronized void signalAccepted() throws IOException {
|
|
if (signal == Long.MAX_VALUE) {
|
|
throw new IOException("signal counter overflow");
|
|
}
|
|
writeAtomic("signal.counter", signal + 1L);
|
|
++signal;
|
|
}
|
|
|
|
synchronized void freshCallbackCompleted() throws IOException {
|
|
if (callback == Long.MAX_VALUE) {
|
|
throw new IOException("callback counter overflow");
|
|
}
|
|
writeAtomic("callback.counter", callback + 1L);
|
|
++callback;
|
|
}
|
|
|
|
private void writeAtomic(String name, long value) throws IOException {
|
|
File target = new File(directory, name);
|
|
File pending = new File(directory, name + ".new");
|
|
if (pending.exists() || !pending.getCanonicalPath().equals(pending.getAbsolutePath())
|
|
|| !target.getCanonicalPath().equals(target.getAbsolutePath())) {
|
|
throw new IOException("counter path not safe");
|
|
}
|
|
FileOutputStream output = new FileOutputStream(pending);
|
|
try {
|
|
if (!pending.setReadable(false, false)
|
|
|| !pending.setWritable(false, false)
|
|
|| !pending.setExecutable(false, false)
|
|
|| !pending.setReadable(true, true)
|
|
|| !pending.setWritable(true, true)) {
|
|
throw new IOException("counter permission failure");
|
|
}
|
|
output.write((Long.toString(value) + "\n").getBytes("US-ASCII"));
|
|
output.getFD().sync();
|
|
} finally {
|
|
output.close();
|
|
}
|
|
if (!pending.renameTo(target)) {
|
|
throw new IOException("counter replacement failed");
|
|
}
|
|
}
|
|
}
|