Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8262908: JFR: Allow JFR to stream events from a known repository path #3685

Closed
wants to merge 3 commits into from
Closed
Changes from 2 commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
432 patch.txt

Large diffs are not rendered by default.

@@ -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
);
}
}