alibaba/canal · error · RuntimeException

Failed to read file

Error message

Failed to read file

What it means

Thrown by FileUtils.validateFileName() when getCanonicalPath() throws an IOException during path resolution. This wraps the underlying I/O error (which may be caused by the base directory not existing, a broken symlink, permission denied, or filesystem-level errors) rather than the path-traversal check itself.

Source

Thrown at common/src/main/java/com/alibaba/otter/canal/common/utils/FileUtils.java:112

     *
     * @param baseDir
     * @param destination
     * @return
     */
    public static String validateFileName(String baseDir, String destination) {
        try {
            // 验证 destination 是否在允许的基目录范围内
            String basePath = new File(baseDir).getCanonicalPath();
            String fullPath = new File(basePath, destination).getCanonicalPath();

            // 检查 fullPath 是否以 basePath 开头
            if (!fullPath.startsWith(basePath + File.separator)) {
                throw new IllegalArgumentException("Invalid destination path");
            }

            return fullPath;
        } catch (IOException e) {
            throw new RuntimeException("Failed to read file", e);
        }
    }

    public static void main(String[] args) throws IOException {
        String fullPath = validateFileName("/tmp/", "1.txt");
        System.out.println(fullPath);
        System.out.println(org.apache.commons.io.FileUtils.readLines(new File(fullPath)));

        fullPath = validateFileName("/tmp/", "test");
        fullPath = validateFileName(fullPath,"1.txt");
        System.out.println(fullPath);
        System.out.println(org.apache.commons.io.FileUtils.readLines(new File(fullPath)));


        fullPath = validateFileName("/tmp/", "../etc/hosts");
        System.out.println(fullPath);
        System.out.println(org.apache.commons.io.FileUtils.readLines(new File(fullPath)));
    }

View on GitHub (pinned to 87be50e876)

Solutions

  1. Verify that baseDir exists and is accessible: check with `ls -la baseDir` and confirm the JVM process has read/execute permissions.
  2. Ensure all symlinks in the path chain resolve to existing targets.
  3. In containerized deployments, confirm volume mounts are correctly configured and the path matches the canal configuration.
  4. Check the canal.instance/conf directory path in canal.properties — correct it if the deployment layout differs from the default.

Example fix

// before — baseDir does not exist
canal.conf.dir = /opt/canal/conf  // missing or not mounted

// after — correct path
canal.conf.dir = /home/admin/canal-server/conf
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify baseDir exists and is accessible before calling validateFileName
File base = new File(baseDir);
if (!base.exists() || !base.isDirectory()) {
    throw new FileNotFoundException("Base directory does not exist: " + baseDir);
}
if (!base.canRead()) {
    throw new SecurityException("Cannot read base directory: " + baseDir);
}

Type guard

null

Try / catch

try {
    String path = FileUtils.validateFileName(baseDir, destination);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("Failed to read file")) {
        logger.error("Cannot resolve canonical path for baseDir={}, dest={}", baseDir, destination, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling validateFileName() where either baseDir or basePath+destination cannot be resolved to a canonical path — e.g. baseDir does not exist, a path component is a dangling symlink, or the JVM lacks read/execute permission on a directory in the path.

Common situations: The configured canal data/conf directory does not exist at startup; permissions were changed after deployment; a symlink target was deleted; running in a container where the volume mount path is incorrect or not mounted.

Related errors


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/c13fbdc3dc9792f1. Report an issue: GitHub.