package jdk.jfr.internal;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Objects;
import jdk.jfr.internal.SecuritySupport.SafePath;
final class RepositoryChunk {
private static final int MAX_CHUNK_NAMES = 100;
static final Comparator<RepositoryChunk> END_TIME_COMPARATOR = new Comparator<RepositoryChunk>() {
@Override
public int compare(RepositoryChunk c1, RepositoryChunk c2) {
return c1.endTime.compareTo(c2.endTime);
}
};
private final SafePath repositoryPath;
private final SafePath unFinishedFile;
private final SafePath file;
private final Instant startTime;
private final RandomAccessFile unFinishedRAF;
private Instant endTime = null;
private int refCount = 0;
private long size;
RepositoryChunk(SafePath path, Instant startTime) throws Exception {
ZonedDateTime z = ZonedDateTime.now();
String fileName = Repository.REPO_DATE_FORMAT.format(
LocalDateTime.ofInstant(startTime, z.getZone()));
this.startTime = startTime;
this.repositoryPath = path;
this.unFinishedFile = findFileName(repositoryPath, fileName, ".part");
this.file = findFileName(repositoryPath, fileName, ".jfr");
this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile);
SecuritySupport.touch(file);
}
private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception {
Path p = directory.toPath().resolve(name + extension);
for (int i = 1; i < MAX_CHUNK_NAMES; i++) {
SafePath s = new SafePath(p);
if (!SecuritySupport.exists(s)) {
return s;
}
String extendedName = String.format("%s_%02d%s", name, i, extension);
p = directory.toPath().resolve(extendedName);
}
p = directory.toPath().resolve(name + "_" + System.currentTimeMillis() + extension);
return SecuritySupport.toRealPath(new SafePath(p));
}
public SafePath getUnfishedFile() {
return unFinishedFile;
}
void finish(Instant endTime) {
try {
finishWithException(endTime);
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not finish chunk. " + e.getMessage());
}
}
private void finishWithException(Instant endTime) throws IOException {
unFinishedRAF.close();
this.size = finish(unFinishedFile, file);
this.endTime = endTime;
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Chunk finished: " + file);
}
private static long finish(SafePath unFinishedFile, SafePath file) throws IOException {
Objects.requireNonNull(unFinishedFile);
Objects.requireNonNull(file);
SecuritySupport.delete(file);
SecuritySupport.moveReplace(unFinishedFile, file);
return SecuritySupport.getFileSize(file);
}
public Instant getStartTime() {
return startTime;
}
public Instant getEndTime() {
return endTime;
}
private void delete(SafePath f) {
try {
SecuritySupport.delete(f);
Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted");
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Repository chunk " + f + " could not be deleted: " + e.getMessage());
if (f != null) {
SecuritySupport.deleteOnExit(f);
}
}
}
private void destroy() {
if (!isFinished()) {
finish(Instant.MIN);
}
if (file != null) {
delete(file);
}
try {
unFinishedRAF.close();
} catch (IOException e) {
Logger.log(LogTag.JFR, LogLevel.ERROR, () -> "Could not close random access file: " + unFinishedFile.toString() + ". File will not be deleted due to: " + e.getMessage());
}
}
public synchronized void use() {
++refCount;
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Use chunk " + toString() + " ref count now " + refCount);
}
public synchronized void release() {
--refCount;
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, () -> "Release chunk " + toString() + " ref count now " + refCount);
if (refCount == 0) {
destroy();
}
}
@Override
@SuppressWarnings("deprecation")
protected void finalize() {
boolean destroy = false;
synchronized (this) {
if (refCount > 0) {
destroy = true;
}
}
if (destroy) {
destroy();
}
}
public long getSize() {
return size;
}
public boolean isFinished() {
return endTime != null;
}
@Override
public String toString() {
if (isFinished()) {
return file.toString();
}
return unFinishedFile.toString();
}
ReadableByteChannel newChannel() throws IOException {
if (!isFinished()) {
throw new IOException("Chunk not finished");
}
return ((SecuritySupport.newFileChannelToRead(file)));
}
public boolean inInterval(Instant startTime, Instant endTime) {
if (startTime != null && getEndTime().isBefore(startTime)) {
return false;
}
if (endTime != null && getStartTime().isAfter(endTime)) {
return false;
}
return true;
}
public SafePath getFile() {
return file;
}
}