Skip to content
Permalink
Browse files
8262908: JFR: Allow JFR to stream events from a known repository path
Reviewed-by: mgronlun
  • Loading branch information
egahlin committed Apr 28, 2021
1 parent 30b1354 commit e144104bb3f658391b1ac3e6beff5a2902e8e0a6
@@ -138,7 +138,14 @@
*/
public static EventStream openRepository() throws IOException {
Utils.checkAccessFlightRecorder();
return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILEGED, null, Collections.emptyList());
return new EventDirectoryStream(
AccessController.getContext(),
null,
SecuritySupport.PRIVILEGED,
null,
Collections.emptyList(),
false
);
}

/**
@@ -161,7 +168,14 @@ public static EventStream openRepository() throws IOException {
public static EventStream openRepository(Path directory) throws IOException {
Objects.requireNonNull(directory);
AccessControlContext acc = AccessController.getContext();
return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILEGED, null, Collections.emptyList());
return new EventDirectoryStream(
acc,
directory,
FileAccess.UNPRIVILEGED,
null,
Collections.emptyList(),
true
);
}

/**
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -108,7 +108,14 @@ public RecordingStream() {
this.recording.setName("Recording Stream: " + creationTime);
try {
PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
this.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILEGED, pr, configurations());
this.directoryStream = new EventDirectoryStream(
acc,
null,
SecuritySupport.PRIVILEGED,
pr,
configurations(),
false
);
} catch (IOException ioe) {
this.recording.close();
throw new IllegalStateException(ioe.getMessage());
@@ -46,6 +46,7 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
@@ -506,6 +507,18 @@ public long fileSize(Path p) throws IOException {
public boolean exists(Path p) throws IOException {
return doPrivilegedIOWithReturn( () -> Files.exists(p));
}

@Override
public boolean isDirectory(Path p) {
return doPrivilegedWithReturn( () -> Files.isDirectory(p));
}

@Override
public FileTime getLastModified(Path p) throws IOException {
// Timestamp only needed when examining repository for other JVMs,
// in which case an unprivileged mode should be used.
throw new InternalError("Should not reach here");
}
}


@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -40,6 +40,7 @@
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.internal.JVM;
import jdk.jfr.internal.PlatformRecording;
import jdk.jfr.internal.SecuritySupport;
import jdk.jfr.internal.Utils;
import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;

@@ -64,10 +65,19 @@

private volatile Consumer<Long> onCompleteHandler;

public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, PlatformRecording recording, List<Configuration> configurations) throws IOException {
public EventDirectoryStream(
AccessControlContext acc,
Path p,
FileAccess fileAccess,
PlatformRecording recording,
List<Configuration> configurations,
boolean allowSubDirectories) throws IOException {
super(acc, recording, configurations);
if (p != null && SecuritySupport.PRIVILEGED == fileAccess) {
throw new SecurityException("Priviliged file access not allowed with potentially malicious Path implementation");
}
this.fileAccess = Objects.requireNonNull(fileAccess);
this.repositoryFiles = new RepositoryFiles(fileAccess, p);
this.repositoryFiles = new RepositoryFiles(fileAccess, p, allowSubDirectories);
}

@Override
@@ -31,6 +31,7 @@
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;

// Protected by modular boundaries.
public abstract class FileAccess {
@@ -48,6 +49,10 @@

public abstract boolean exists(Path s) throws IOException;

public abstract boolean isDirectory(Path p);

public abstract FileTime getLastModified(Path p) throws IOException;

private static class UnPrivileged extends FileAccess {
@Override
public RandomAccessFile openRAF(File f, String mode) throws IOException {
@@ -78,5 +83,15 @@ public long fileSize(Path p) throws IOException {
public boolean exists(Path p) {
return Files.exists(p);
}

@Override
public boolean isDirectory(Path p) {
return Files.isDirectory(p);
}

@Override
public FileTime getLastModified(Path p) throws IOException {
return Files.getLastModifiedTime(p);
}
}
}
@@ -61,7 +61,7 @@ public OngoingStream(Recording recording, int blockSize, long startTimeNanos, lo
this.blockSize = blockSize;
this.startTimeNanos = startTimeNanos;
this.endTimeNanos = endTimeNanos;
this.repositoryFiles = new RepositoryFiles(SecuritySupport.PRIVILEGED, null);
this.repositoryFiles = new RepositoryFiles(SecuritySupport.PRIVILEGED, null, false);
}

@Override
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -47,6 +48,7 @@

public final class RepositoryFiles {
private static final Object WAIT_OBJECT = new Object();
private static final String DIRECTORY_PATTERN = "DDDD_DD_DD_DD_DD_DD_";
public static void notifyNewFile() {
synchronized (WAIT_OBJECT) {
WAIT_OBJECT.notifyAll();
@@ -56,15 +58,16 @@ public static void notifyNewFile() {
private final FileAccess fileAccess;
private final NavigableMap<Long, Path> pathSet = new TreeMap<>();
private final Map<Path, Long> pathLookup = new HashMap<>();
private final Path repository;
private final Object waitObject;

private boolean allowSubDirectory;
private volatile boolean closed;
private Path repository;

public RepositoryFiles(FileAccess fileAccess, Path repository) {
public RepositoryFiles(FileAccess fileAccess, Path repository, boolean allowSubDirectory) {
this.repository = repository;
this.fileAccess = fileAccess;
this.waitObject = repository == null ? WAIT_OBJECT : new Object();
this.allowSubDirectory = allowSubDirectory;
}

long getTimestamp(Path p) {
@@ -167,6 +170,14 @@ private void nap() {
private boolean updatePaths() throws IOException {
boolean foundNew = false;
Path repoPath = repository;

if (allowSubDirectory) {
Path subDirectory = findSubDirectory(repoPath);
if (subDirectory != null) {
repoPath = subDirectory;
}
}

if (repoPath == null) {
// Always get the latest repository if 'jcmd JFR.configure
// repositorypath=...' has been executed
@@ -209,20 +220,78 @@ private boolean updatePaths() throws IOException {
long size = fileAccess.fileSize(p);
if (size >= ChunkHeader.headerSize()) {
long startNanos = readStartTime(p);
pathSet.put(startNanos, p);
pathLookup.put(p, startNanos);
foundNew = true;
if (startNanos != -1) {
pathSet.put(startNanos, p);
pathLookup.put(p, startNanos);
foundNew = true;
}
}
}
if (allowSubDirectory && foundNew) {
// Found a valid file, possibly in a subdirectory.
// Use the same (sub)directory from now on.
repository = repoPath;
allowSubDirectory = false;
}

return foundNew;
}
}

private long readStartTime(Path p) throws IOException {
private Path findSubDirectory(Path repoPath) {
FileTime latestTimestamp = null;
Path latestPath = null;
try (DirectoryStream<Path> dirStream = fileAccess.newDirectoryStream(repoPath)) {
for (Path p : dirStream) {
String filename = p.getFileName().toString();
if (isRepository(filename) && fileAccess.isDirectory(p)) {
FileTime timestamp = getLastModified(p);
if (timestamp != null) {
if (latestPath == null || latestTimestamp.compareTo(timestamp) <= 0) {
latestPath = p;
latestTimestamp = timestamp;
}
}
}
}
} catch (IOException e) {
// Ignore
}
return latestPath;
}

private FileTime getLastModified(Path p) {
try {
return fileAccess.getLastModified(p);
} catch (IOException e) {
return null;
}
}

private static boolean isRepository(String filename) {
if (filename.length() < DIRECTORY_PATTERN.length()) {
return false;
}
for (int i = 0; i < DIRECTORY_PATTERN.length(); i++) {
char expected = DIRECTORY_PATTERN.charAt(i);
char c = filename.charAt(i);
if (expected == 'D' && !Character.isDigit(c)) {
return false;
}
if (expected == '_' && c != '_') {
return false;
}
}
return true;
}

private long readStartTime(Path p) {
try (RecordingInput in = new RecordingInput(p.toFile(), fileAccess, 100)) {
Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Parsing header for chunk start time");
ChunkHeader c = new ChunkHeader(in);
return c.getStartNanos();
} catch (IOException ioe) {
return -1;
}
}

@@ -174,6 +174,13 @@ public static EventStream newEventDirectoryStream(
AccessControlContext acc,
Path directory,
List<Configuration> confs) throws IOException {
return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILEGED, null, confs);
return new EventDirectoryStream(
acc,
directory,
FileAccess.UNPRIVILEGED,
null,
confs,
false
);
}
}
Loading

1 comment on commit e144104

@openjdk-notifier

This comment has been minimized.

Copy link

@openjdk-notifier openjdk-notifier bot commented on e144104 Apr 28, 2021

Please sign in to comment.