Skip to content
Closed
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
3 changes: 1 addition & 2 deletions common/src/main/java/org/tron/core/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static void resolveConfigFile(String fileName, File confFile) {
if (confFile.exists()) {
config = ConfigFactory.parseFile(confFile)
.withFallback(ConfigFactory.defaultReference());
} else if (Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)
} else if (Thread.currentThread().getContextClassLoader().getResource(fileName)
!= null) {
config = ConfigFactory.load(fileName);
} else {
Expand All @@ -59,4 +59,3 @@ private static void resolveConfigFile(String fileName, File confFile) {
}
}
}

10 changes: 4 additions & 6 deletions framework/src/main/java/org/tron/core/config/args/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -1188,9 +1188,8 @@ public String getOutputDirectory() {
private static void printVersion() {
Properties properties = new Properties();
boolean noGitProperties = true;
try {
InputStream in = Thread.currentThread()
.getContextClassLoader().getResourceAsStream("git.properties");
try (InputStream in = Thread.currentThread()
.getContextClassLoader().getResourceAsStream("git.properties")) {
if (in != null) {
noGitProperties = false;
properties.load(in);
Expand Down Expand Up @@ -1278,9 +1277,8 @@ public static String upperFirst(String name) {

private static String getCommitIdAbbrev() {
Properties properties = new Properties();
try {
InputStream in = Thread.currentThread()
.getContextClassLoader().getResourceAsStream("git.properties");
try (InputStream in = Thread.currentThread()
.getContextClassLoader().getResourceAsStream("git.properties")) {
if (in == null) {
logger.warn("git.properties not found on classpath");
return "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ public static void librustzcashInitZksnarkParams() {
}

private static String getParamsFile(String fileName) {
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("params" + File.separator + fileName);
String resourcePath = "params" + File.separator + fileName;
File fileOut = new File(System.getProperty("java.io.tmpdir")
+ File.separator + fileName + "." + System.currentTimeMillis());
try {
try (InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(resourcePath)) {
if (in == null) {
throw new IllegalStateException("Resource not found: " + resourcePath);
Comment thread
Federico2014 marked this conversation as resolved.
}
FileUtils.copyToFile(in, fileOut);
} catch (IOException e) {
logger.error(e.getMessage(), e);
Expand Down
28 changes: 12 additions & 16 deletions framework/src/test/java/org/tron/common/utils/FileUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

Expand All @@ -39,15 +34,16 @@ public void setUp() throws IOException {

@After
public void tearDown() throws IOException {
Files.walk(tempDir)
.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
});
try (Stream<Path> paths = Files.walk(tempDir)) {
paths.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

@Test
Expand Down Expand Up @@ -126,4 +122,4 @@ public void testCreateDirIfNotExists() {
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,17 @@ public void doPostTest() throws IOException {
}
Assert.assertNotNull(result);
in.close();
writer.flush();
FileInputStream fileInputStream = new FileInputStream("temp.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
writer.close();

StringBuilder sb = new StringBuilder();
String text;
while ((text = bufferedReader.readLine()) != null) {
sb.append(text);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
new FileInputStream("temp.txt"), StandardCharsets.UTF_8))) {
String text;
while ((text = bufferedReader.readLine()) != null) {
sb.append(text);
}
}
Assert.assertTrue(sb.toString().contains("{\"Error\":\"internal server error\"}"));
httpUrlConnection.disconnect();
}
}
}
54 changes: 30 additions & 24 deletions plugins/src/main/java/common/org/tron/plugins/utils/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;

@Slf4j
Expand Down Expand Up @@ -137,39 +138,44 @@ public static boolean isSymbolicLink(File file) throws IOException {
*/
public static void copyDatabases(Path src, Path dest, List<String> subDirs)
throws IOException {
// create subdirs, as using parallel() to run, so should create dirs first.
subDirs.forEach(dir -> {
if (isExists(Paths.get(src.toString(), dir).toString())) {
try {
Files.walk(Paths.get(src.toString(), dir), FileVisitOption.FOLLOW_LINKS)
.forEach(source -> copy(source, dest.resolve(src.relativize(source))));
} catch (IOException e) {
logger.error("copy database failed, src: {}, dest: {}, error: {}",
Paths.get(src.toString(), dir), Paths.get(dest.toString(), dir), e.getMessage());
throw new RuntimeException(e);
}
for (String dir : subDirs) {
if (!isExists(Paths.get(src.toString(), dir).toString())) {
continue;
}
});
}

public static void copyDir(Path src, Path dest, String dir) {
if (isExists(Paths.get(src.toString(), dir).toString())) {
try {
if (createDirIfNotExists(Paths.get(dest.toString(), dir).toString())) {
Files.walk(Paths.get(src.toString(), dir), FileVisitOption.FOLLOW_LINKS)
.forEach(source -> copy(source, dest.resolve(src.relativize(source))));
} else {
throw new IOException(String.format("dest %s create fail ",
Paths.get(dest.toString(), dir)));
}
copyTree(src, dest, dir);
} catch (IOException e) {
logger.error("copy dir failed, src: {}, dest: {}, error: {}",
logger.error("copy database failed, src: {}, dest: {}, error: {}",
Paths.get(src.toString(), dir), Paths.get(dest.toString(), dir), e.getMessage());
throw new RuntimeException(e);
}
}
}

public static void copyDir(Path src, Path dest, String dir) {
if (!isExists(Paths.get(src.toString(), dir).toString())) {
return;
}
try {
if (!createDirIfNotExists(Paths.get(dest.toString(), dir).toString())) {
throw new IOException(String.format("dest %s create fail ",
Paths.get(dest.toString(), dir)));
}
copyTree(src, dest, dir);
} catch (IOException e) {
logger.error("copy dir failed, src: {}, dest: {}, error: {}",
Paths.get(src.toString(), dir), Paths.get(dest.toString(), dir), e.getMessage());
throw new RuntimeException(e);
}
}

private static void copyTree(Path src, Path dest, String dir) throws IOException {
try (Stream<Path> paths = Files.walk(
Paths.get(src.toString(), dir), FileVisitOption.FOLLOW_LINKS)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: copyTree walks with FileVisitOption.FOLLOW_LINKS, and copy then uses Files.copy, which also follows links by default. A symlink inside a database directory that points outside the source tree copies/traverses content from outside the intended subtree, and a symlink cycle makes Files.walk throw FileSystemLoopException. Because copyDatabases/copyDir wrap any IOException in a RuntimeException, a single cycle aborts the entire multi-directory copy and leaves a partial destination in the backup/archive path. This behavior carried over from the pre-PR code, but the new shared copyTree is the right place to harden it: walk without following links (Files.walk(Paths.get(src.toString(), dir))), or switch to Files.walkFileTree with explicit visitFile/cycle detection if symlinked directories inside the tree are intentional.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/src/main/java/common/org/tron/plugins/utils/FileUtils.java, line 174:

<comment>`copyTree` walks with `FileVisitOption.FOLLOW_LINKS`, and `copy` then uses `Files.copy`, which also follows links by default. A symlink inside a database directory that points outside the source tree copies/traverses content from outside the intended subtree, and a symlink cycle makes `Files.walk` throw `FileSystemLoopException`. Because `copyDatabases`/`copyDir` wrap any `IOException` in a `RuntimeException`, a single cycle aborts the entire multi-directory copy and leaves a partial destination in the backup/archive path. This behavior carried over from the pre-PR code, but the new shared `copyTree` is the right place to harden it: walk without following links (`Files.walk(Paths.get(src.toString(), dir))`), or switch to `Files.walkFileTree` with explicit `visitFile`/cycle detection if symlinked directories inside the tree are intentional.</comment>

<file context>
@@ -138,43 +138,44 @@ public static boolean isSymbolicLink(File file) throws IOException {
+
+  private static void copyTree(Path src, Path dest, String dir) throws IOException {
+    try (Stream<Path> paths = Files.walk(
+        Paths.get(src.toString(), dir), FileVisitOption.FOLLOW_LINKS)) {
+      paths.forEach(source -> copy(source, dest.resolve(src.relativize(source))));
+    }
</file context>
Suggested change
Paths.get(src.toString(), dir), FileVisitOption.FOLLOW_LINKS)) {
try (Stream<Path> paths = Files.walk(Paths.get(src.toString(), dir))) {

paths.forEach(source -> copy(source, dest.resolve(src.relativize(source))));
}
}

public static void copy(Path source, Path dest) {
try {
// create hard link when file is .sst
Expand Down
99 changes: 99 additions & 0 deletions plugins/src/test/java/org/tron/plugins/utils/FileUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.tron.plugins.utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class FileUtilsTest {

@Rule
public final TemporaryFolder folder = new TemporaryFolder();

@Test
public void testCopyDatabasesCopiesTreeAfterMissingDirectory() throws IOException {
Path src = createSourceTree();
Path dest = folder.newFolder().toPath();

FileUtils.copyDatabases(src, dest, Arrays.asList("missing", "database"));

Assert.assertFalse(Files.exists(dest.resolve("missing")));
Assert.assertArrayEquals(new byte[]{1, 2, 3},
Files.readAllBytes(dest.resolve("database/nested/data")));
}

@Test
public void testCopyMethodsSkipMissingSource() throws IOException {
Path src = folder.newFolder().toPath();
Path dest = folder.getRoot().toPath().resolve("missing-dest");

FileUtils.copyDatabases(src, dest, Collections.singletonList("missing"));
FileUtils.copyDir(src, dest, "missing");

Assert.assertFalse(Files.exists(dest));
}

@Test
public void testCopyDatabasesDoesNotCreateDestinationParents() throws IOException {
Path src = createSourceTree();
Path dest = folder.getRoot().toPath().resolve("missing-parent/dest");

RuntimeException error = Assert.assertThrows(RuntimeException.class,
() -> FileUtils.copyDatabases(src, dest, Collections.singletonList("database")));

Assert.assertTrue(error.getCause() instanceof IOException);
Assert.assertFalse(Files.exists(dest.getParent()));
}

@Test
public void testCopyDirCreatesDestinationParents() throws IOException {
Path src = createSourceTree();
Path dest = folder.getRoot().toPath().resolve("missing-parent/dest");

FileUtils.copyDir(src, dest, "database");

Assert.assertArrayEquals(new byte[]{1, 2, 3},
Files.readAllBytes(dest.resolve("database/nested/data")));
}

@Test
public void testCopyMethodsPreserveLeadingSeparatorInDirectory() throws IOException {
Path src = createSourceTree();
Path databasesDest = folder.newFolder().toPath();
Path directoryDest = folder.newFolder().toPath();
String dir = File.separator + "database";

FileUtils.copyDatabases(src, databasesDest, Collections.singletonList(dir));
FileUtils.copyDir(src, directoryDest, dir);

Assert.assertArrayEquals(new byte[]{1, 2, 3},
Files.readAllBytes(databasesDest.resolve("database/nested/data")));
Assert.assertArrayEquals(new byte[]{1, 2, 3},
Files.readAllBytes(directoryDest.resolve("database/nested/data")));
}

@Test
public void testCopyDirWrapsDestinationCreationFailure() throws IOException {
Path src = createSourceTree();
Path dest = folder.newFile().toPath();

RuntimeException error = Assert.assertThrows(RuntimeException.class,
() -> FileUtils.copyDir(src, dest, "database"));

Assert.assertTrue(error.getCause() instanceof IOException);
Assert.assertTrue(error.getCause().getMessage().contains("create fail"));
}

private Path createSourceTree() throws IOException {
Path src = folder.newFolder().toPath();
Path nested = Files.createDirectories(src.resolve("database/nested"));
Files.write(nested.resolve("data"), new byte[]{1, 2, 3});
return src;
}
}
Loading