ciechanowiec opened a new issue, #977:
URL: https://github.com/apache/maven-enforcer/issues/977

   ### Affected version
   
   3.6.2, 3.5.0, 3.4.1
   
   ### Bug description
   
   ## Summary
   
   `RequireFilesExist` reports an existing, readable, non-symlink file as 
missing when the configured path contains `..` segments that traverse a 
firmlinked directory on macOS (e.g. `/Users`). The rule's symlink heuristic 
calls `Path.toRealPath()`, which throws on such paths; the resulting exception 
is caught and the file is conservatively classified as a symlink, causing the 
rule to fail.
   
   ## Affected versions
   
   Reproduced with `maven-enforcer-plugin`:
   
   - 3.6.2
   - 3.5.0
   - 3.4.1
   
   ## Environment
   
   - OS: macOS (Darwin 25.3.0, APFS, `/Users` is a firmlinked directory)
   - Architecture: aarch64 (Apple Silicon)
   - JDK: project's `${maven.compiler.release}` (reproduced under standard 
Temurin JDK)
   - Maven: 3.9.x
   
   ## Steps to reproduce
   
   1. On macOS, place a file at `/Users/<user>/projects/repo/README.adoc`.
   2. From a Maven module at `/Users/<user>/projects/repo/src/a/b/pom.xml`, 
configure:
   
      ```xml
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>3.6.2</version>
        <executions>
          <execution>
            <id>require-readme</id>
            <phase>validate</phase>
            <goals><goal>enforce</goal></goals>
            <configuration>
              <rules>
                <requireFilesExist>
                  <files>
                    <file>${project.basedir}/../../../README.adoc</file>
                  </files>
                </requireFilesExist>
              </rules>
            </configuration>
          </execution>
        </executions>
      </plugin>
      ```
   
   3. Run `mvn enforcer:enforce@require-readme`.
   
   ### Expected
   
   Build succeeds. The path resolves to an existing regular file (verifiable 
via `ls`, `[ -f ... ]`, `Files.exists()`, and `File.exists()`).
   
   ### Actual
   
   ```
   [ERROR] Rule 0: org.apache.maven.enforcer.rules.files.RequireFilesExist 
failed with message:
   [ERROR] Some required files are missing:
   [ERROR] /Users/<user>/projects/repo/src/a/b/../../../README.adoc
   ```
   
   The same rule **succeeds** when the `<file>` value is the canonical path 
with `..` collapsed (`/Users/<user>/projects/repo/README.adoc`).
   
   ## Root cause
   
   In `enforcer-rules`, 
[`RequireFilesExist`](https://github.com/apache/maven-enforcer/blob/maven-enforcer-3.6.2/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/files/RequireFilesExist.java)
 treats files as missing if they are symlinks:
   
   ```java
   @Override
   boolean checkFile(File file) {
       if (file == null) {
           return true;
       }
       return file.exists() && !isSymlink(file);
   }
   
   private static boolean isSymlink(File file) {
       try {
           return Files.isSymbolicLink(file.toPath())
               || !Files.isSameFile(file.toPath(), file.toPath().toRealPath());
       } catch (Exception e) {
           return true; // ← any exception is treated as "this is a symlink"
       }
   }
   ```
   
   On macOS, `Path.toRealPath()` can throw `IOException` (or a JDK-internal 
exception) when the input path traverses firmlinked system directories such as 
`/Users` via `..` segments, even when the file itself exists, is a regular 
file, and is not a symlink. The catch-all then classifies the file as a symlink 
and the rule rejects it.
   
   This is reproducible with a tiny Java snippet:
   
   ```java
   Path p = 
Paths.get("/Users/<user>/projects/repo/src/a/b/../../../README.adoc");
   System.out.println(Files.exists(p));        // true
   System.out.println(p.toFile().exists());    // true
   System.out.println(Files.isSymbolicLink(p));// false
   System.out.println(p.toRealPath());         // throws on affected macOS 
setups
   ```
   
   ## Impact
   
   Any multi-module project on macOS that uses `RequireFilesExist` with a path 
expressed relative to `${project.basedir}` (e.g. checking a file at the 
repository root from a nested module) will get a spurious build failure. There 
is no diagnostic clue: the message says the file is missing, but it is not.
   
   ## Suggested fixes
   
   Any one of the following would resolve the issue:
   
   1. **Drop the `toRealPath()`-based symlink check from `RequireFilesExist`.** 
`RequireFilesExist`'s contract is "this file must exist" — 
symlink-vs-regular-file is arguably out of scope. If symlink rejection is 
required, expose it as an opt-in property.
   2. **Use `Files.isSymbolicLink(path)` only.** Treating the file as a symlink 
only when `Files.isSymbolicLink()` returns `true` is sufficient for the 
documented contract and avoids `toRealPath()` failures on `..`-containing paths 
entirely.
   3. **Normalize the path before the symlink check.** Calling 
`path.toAbsolutePath().normalize()` before `toRealPath()` collapses `..` 
segments and avoids the macOS-specific throw.
   4. **Stop swallowing exceptions as "symlink".** If `toRealPath()` is 
retained, an `IOException` should not silently flip the verdict to "missing"; 
either fail with the underlying error or fall back to `Files.isSymbolicLink()`.
   
   ## Workaround
   
   Replace the rule with `maven-antrun-plugin`, which uses Ant's `<available>` 
and resolves `..` correctly:
   
   ```xml
   <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-antrun-plugin</artifactId>
     <executions>
       <execution>
         <id>require-readme</id>
         <phase>validate</phase>
         <goals><goal>run</goal></goals>
         <configuration>
           <target>
             <fail message="Required file not found: 
${project.basedir}/../../../README.adoc">
               <condition>
                 <not>
                   <available file="${project.basedir}/../../../README.adoc"/>
                 </not>
               </condition>
             </fail>
           </target>
         </configuration>
       </execution>
     </executions>
   </plugin>
   ```
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to