This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat-jakartaee-migration.git
The following commit(s) were added to refs/heads/main by this push:
new c06f68f Improve same file comparison robustness
c06f68f is described below
commit c06f68f13f28448b1bea8a79bcc6ba4966490dcf
Author: remm <[email protected]>
AuthorDate: Wed Sep 9 11:33:13 2026 +0200
Improve same file comparison robustness
From code review.
---
.../org/apache/tomcat/jakartaee/Migration.java | 43 ++++++++++++++++++++--
.../org/apache/tomcat/jakartaee/MigrationTest.java | 32 ++++++++++++++++
2 files changed, 71 insertions(+), 4 deletions(-)
diff --git a/src/main/java/org/apache/tomcat/jakartaee/Migration.java
b/src/main/java/org/apache/tomcat/jakartaee/Migration.java
index 5f00ab4..4b760ef 100644
--- a/src/main/java/org/apache/tomcat/jakartaee/Migration.java
+++ b/src/main/java/org/apache/tomcat/jakartaee/Migration.java
@@ -24,7 +24,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.attribute.FileTime;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -333,7 +334,12 @@ public class Migration {
}
private void migrateFile(File src, File dest) throws IOException {
- if (src.equals(dest)) {
+ // If the source and destination are the same file, the migrated
+ // content is buffered first and only written to the destination
+ // (which truncates the file) after a successful migration. The
+ // same physical file may be identified by different path strings
+ // so File.equals() is not sufficient to detect this case.
+ if (isSameFile(src, dest)) {
if (src.length() > TEMP_FILE_THRESHOLD) {
// For very large files, use a temp file instead of memory
File tempFile = createTempFile();
@@ -392,6 +398,33 @@ public class Migration {
}
+ private boolean isSameFile(File src, File dest) throws IOException {
+ // File.equals() only compares path strings. The same physical file
+ // may be addressed by different path strings (e.g. differing use of
+ // "." or ".." components, a symlink or, on case-insensitive file
+ // systems, a different case) and File.equals() would not detect that.
+ // That must be detected, otherwise opening the destination for
+ // writing would truncate the source before it is read.
+ if (src.equals(dest)) {
+ return true;
+ }
+ // Files.isSameFile() requires both files to exist. If the
+ // destination does not exist, it cannot refer to the same file as
+ // the source.
+ if (dest.exists()) {
+ try {
+ return Files.isSameFile(src.toPath(), dest.toPath());
+ } catch (NoSuchFileException e) {
+ // The file disappeared between the check above and this call.
+ // Treat as different files and report the missing file when it
+ // is opened.
+ return false;
+ }
+ }
+ return false;
+ }
+
+
private boolean migrateArchiveStreaming(InputStream src, OutputStream
dest) throws IOException {
boolean convertedArchive = false;
try (ZipArchiveInputStream srcZipStream = new
ZipArchiveInputStream(CloseShieldInputStream.wrap(src));
@@ -833,8 +866,10 @@ public class Migration {
SourceSpool(EESpecProfile profile) throws IOException {
try {
digest = MessageDigest.getInstance("SHA-256");
- // Include profile name in hash to differentiate between
profiles
-
digest.update(profile.toString().getBytes(StandardCharsets.UTF_8));
+ // The keying data (tool version and profile definition) must
+ // be identical to the one used by MigrationCache so hashes
+ // computed here match those used for cache storage/lookup
+ digest.update(MigrationCache.getHashKeyData(profile));
} catch (NoSuchAlgorithmException e) {
throw new IOException(sm.getString("cache.hashError"), e);
}
diff --git a/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
b/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
index 6eb57ab..912a7ad 100644
--- a/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
+++ b/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
@@ -107,6 +107,38 @@ public class MigrationTest {
assertTrue("Migrated imports not found",
migratedSource.contains("import jakarta.servlet"));
}
+ /**
+ * The source and destination may identify the same physical file via
+ * different path strings (relative vs absolute components, symlinks,
+ * differing case on case-insensitive file systems). The migration must
+ * treat it as an in-place migration (migrate before overwriting),
+ * otherwise the destination is truncated before the source is read and
+ * all data is lost. This is a regression test for that behaviour.
+ */
+ @Test
+ public void testMigrateSingleSourceFileInPlaceWithAlternativePath() throws
Exception {
+ File sourceFile = tempFolder.newFile("HelloServlet.alt.inplace.java");
+ FileUtils.copyFile(new File("target/test-classes/HelloServlet.java"),
sourceFile);
+ String original = FileUtils.readFileToString(sourceFile,
StandardCharsets.UTF_8);
+ assertTrue(original.contains("import javax.servlet"));
+
+ // Build a destination path string that differs from the source path
+ // (the extra "." component) but resolves to the same physical file
+ File destinationFile = new File(new File(sourceFile.getParentFile(),
"."), sourceFile.getName());
+ assertFalse("Test set-up error - paths must differ",
destinationFile.equals(sourceFile));
+
+ Migration migration = new Migration();
+ migration.setSource(sourceFile);
+ migration.setDestination(destinationFile);
+ migration.execute();
+
+ // The source (same physical file as the destination) must have been
+ // migrated in place, not truncated by the destination stream
+ String migratedSource = FileUtils.readFileToString(sourceFile,
StandardCharsets.UTF_8);
+ assertFalse("Imports not migrated", migratedSource.contains("import
javax.servlet"));
+ assertTrue("Migrated imports not found",
migratedSource.contains("import jakarta.servlet"));
+ }
+
@Test
public void testInvalidOption() throws Exception {
File sourceFile = new File("target/test-classes/HelloServlet.java");
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]