apache/hadoop · error · IOException

can not copy a directory to a subdirectory of self

Error message

can not copy a directory to a subdirectory of self

What it means

A last-line defense inside copyDirectory: before copying, if the destination key starts with the source key (the destination directory lies inside the source directory), it throws IOException. The public rename() normally rejects this case earlier via its parent-into-subdirectory check, so seeing this message usually means an internal path or a key-layout edge (directory marker keys with trailing '/') slipped past the earlier checks.

Source

Thrown at hadoop-cloud-storage-project/hadoop-cos/src/main/java/org/apache/hadoop/fs/cosn/CosNFileSystem.java:720

  private boolean copyFile(Path srcPath, Path dstPath) throws IOException {
    String srcKey = pathToKey(srcPath);
    String dstKey = pathToKey(dstPath);
    this.store.copy(srcKey, dstKey);
    return true;
  }

  private boolean copyDirectory(Path srcPath, Path dstPath) throws IOException {
    String srcKey = pathToKey(srcPath);
    if (!srcKey.endsWith(PATH_DELIMITER)) {
      srcKey += PATH_DELIMITER;
    }
    String dstKey = pathToKey(dstPath);
    if (!dstKey.endsWith(PATH_DELIMITER)) {
      dstKey += PATH_DELIMITER;
    }

    if (dstKey.startsWith(srcKey)) {
      throw new IOException(
          "can not copy a directory to a subdirectory of self");
    }

    this.store.storeEmptyFile(dstKey);
    CosNCopyFileContext copyFileContext = new CosNCopyFileContext();

    int copiesToFinishes = 0;
    String priorLastKey = null;
    do {
      PartialListing objectList = this.store.list(
          srcKey, Constants.COS_MAX_LISTING_LENGTH, priorLastKey, true);
      for (FileMetadata file : objectList.getFiles()) {
        this.boundedCopyThreadPool.execute(new CosNCopyFileTask(
            this.store,
            file.getKey(),
            dstKey.concat(file.getKey().substring(srcKey.length())),
            copyFileContext));
        copiesToFinishes++;

View on GitHub (pinned to 2add963021)

Solutions

  1. Move the destination outside the source subtree.
  2. If nesting is required, copy then delete the source instead of rename.
  3. If src and dst are clearly not nested, report it as a CosN key-normalization bug with both keys.

Example fix

// before
fs.rename(new Path('/proj'), new Path('/proj/backup')); // reaches copyDirectory, throws

// after
fs.rename(new Path('/proj'), new Path('/backup/proj'));
Defensive patterns

Strategy: validation

Validate before calling

// key-level check mirroring the guard
String srcKey = src.toUri().getPath().startsWith('/') ? src.toUri().getPath().substring(1) : src.toUri().getPath();
String dstKey = dst.toUri().getPath().startsWith('/') ? dst.toUri().getPath().substring(1) : dst.toUri().getPath();
if ((dstKey + '/').startsWith(srcKey.endsWith('/') ? srcKey : srcKey + '/')) {
  throw new IllegalArgumentException('Destination inside source subtree');
}

Try / catch

try {
  fs.rename(src, dst);
} catch (IOException e) {
  if (e.getMessage() != null && e.getMessage().contains('subdirectory of self')) {
    // move outside the subtree, or copy+delete
    FileUtil.copy(fs, src, fs, outsideDst, true, conf);
    fs.delete(src, true);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Renaming a directory into its own subtree when earlier equality/ancestor checks were bypassed; key normalization differences where dstKey only differs from srcKey by a trailing-slash marker, making startsWith true.

Common situations: Edge-case renames near the bucket root; nested destinations computed directly from the source path string.

Related errors


AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22). Data as JSON: /api/errors/0b64369f2276bad4. Report an issue: GitHub.