{"record":{"id":"5882c7dd06e88056","repo":"apache/hadoop","slug":"concat-operation-doesn-t-support-fsdirectory","errorCode":null,"errorMessage":"\"Concat operation doesn't support \" + FSDirectory.DOT_RESERVED_STRING + \" relative path : \" + target","messagePattern":"\"Concat operation doesn't support \" \\+ FSDirectory\\.DOT_RESERVED_STRING \\+ \" relative path : \" \\+ target","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirConcatOp.java","lineNumber":87,"sourceCode":"    long timestamp = now();\n    fsd.writeLock();\n    try {\n      unprotectedConcat(fsd, targetIIP, srcFiles, timestamp);\n    } finally {\n      fsd.writeUnlock();\n    }\n    fsd.getEditLog().logConcat(target, srcs, timestamp, logRetryCache);\n    return fsd.getAuditFileInfo(targetIIP);\n  }\n\n  private static void validatePath(String target, String[] srcs)\n      throws IOException {\n    Preconditions.checkArgument(!target.isEmpty(), \"Target file name is empty\");\n    Preconditions.checkArgument(srcs != null && srcs.length > 0,\n        \"No sources given\");\n    if (FSDirectory.isReservedRawName(target)\n        || FSDirectory.isReservedInodesName(target)) {\n      throw new IOException(\"Concat operation doesn't support \"\n          + FSDirectory.DOT_RESERVED_STRING + \" relative path : \" + target);\n    }\n    for (String srcPath : srcs) {\n      if (FSDirectory.isReservedRawName(srcPath)\n          || FSDirectory.isReservedInodesName(srcPath)) {\n        throw new IOException(\"Concat operation doesn't support \"\n            + FSDirectory.DOT_RESERVED_STRING + \" relative path : \" + srcPath);\n      }\n    }\n  }\n\n  private static void verifyTargetFile(FSDirectory fsd, final String target,\n      final INodesInPath targetIIP) throws IOException {\n    // check the target\n    if (FSDirEncryptionZoneOp.getEZForPath(fsd, targetIIP) != null) {\n      throw new HadoopIllegalArgumentException(\n          \"concat can not be called for files in an encryption zone.\");\n    }","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirConcatOp.java#L69-L105","documentation":"The NameNode rejects concat operations whose TARGET path is a reserved path — one starting with /.reserved/raw or /.reserved/inodes. These virtual prefixes exist so clients can read raw encrypted bytes and address reserved inodes; they are read-only views, not real namespace locations, so a mutating operation like concat can never target them. The check is the first thing validatePath does before any file verification.","triggerScenarios":"Calling FileSystem.concat(target, srcs) where target still carries the /.reserved/raw prefix, typically a path that was originally obtained from an encryption-zone raw read (e.g. getFileChecksum on an encrypted file) and passed unchanged into concat.","commonSituations":"Backup/replication tools built for encryption zones that list files via /.reserved/raw and then feed those strings to every subsequent API; copy-pasting a raw path from a checksum or debug command into application config; downstream code that indiscriminately prefixes /.reserved/raw when an EZ is detected.","solutions":["Strip the /.reserved/raw or /.reserved/inodes prefix from the target so it points at the real namespace path, then call concat.","Normalize paths at the edge of your application: reject or rewrite reserved paths once, centrally, instead of per API call.","If you genuinely need raw encrypted bytes, read via /.reserved/raw, but perform all mutations (concat, rename, delete) on the un-prefixed path."],"exampleFix":"// before\nPath target = new Path(\"/.reserved/raw/zone/part-0000\");\nfs.concat(target, srcs);\n\n// after\nPath target = new Path(\"/zone/part-0000\"); // real namespace path\nfs.concat(target, srcs);","handlingStrategy":"validation","validationCode":"static boolean isReservedPath(Path p) {\n  String s = p.toUri().getPath();\n  return s != null && (s.equals(\"/.reserved\") || s.startsWith(\"/.reserved/\"));\n}\nif (isReservedPath(target)) {\n  throw new IllegalArgumentException(\"concat target must not use /.reserved: \" + target);\n}","typeGuard":null,"tryCatchPattern":"catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"doesn't support .reserved\")) {\n    target = stripReservedPrefix(target); // drop /.reserved/raw|inodes and retry once\n    fs.concat(target, srcs);\n  } else { throw e; }\n}","preventionTips":["Normalize every path once at system entry: reject or rewrite /.reserved prefixes for mutating APIs.","Keep raw paths (for reading encrypted bytes) and real paths (for concat/rename/delete) in separate, clearly typed variables or lists.","Add a lint rule or unit test asserting no /.reserved string reaches FileSystem mutation calls."],"tags":["hdfs","concat","reserved-path","encryption-zone"],"backgroundTag":"hdfs-reserved-path","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}