{"record":{"id":"dcdec8403759e692","repo":"pentaho/pentaho-kettle","slug":"vfs-provider-copy-file-error","errorCode":"vfs.provider/copy-file.error","errorMessage":"vfs.provider/copy-file.error","messagePattern":"vfs\\.provider/copy-file\\.error","errorType":"error_code","errorClass":"FileSystemException","httpStatus":null,"severity":"error","filePath":"plugins/s3-vfs/core/src/main/java/org/pentaho/s3common/S3CommonFileObject.java","lineNumber":438,"sourceCode":"\n    // Destination is a folder - calculate the relative path to preserve structure\n    try {\n      String relativePath = srcBase.getName().getRelativeName( srcFile.getName() );\n      if ( relativePath == null || relativePath.isEmpty() ) {\n        relativePath = srcFile.getName().getBaseName();\n      }\n\n      // Build the destination key: current key + relative path\n      String dstKey = this.key;\n      if ( !dstKey.isEmpty() && !dstKey.endsWith( DELIMITER ) ) {\n        dstKey += DELIMITER;\n      }\n      dstKey += relativePath;\n\n      // Create a new S3CommonFileObject with the calculated key\n      return (S3CommonFileObject) fileSystem.resolveFile( getName().getRoot() + DELIMITER + bucketName + DELIMITER + dstKey );\n    } catch ( Exception e ) {\n      throw new FileSystemException( \"vfs.provider/copy-file.error\", srcFile, this, e );\n    }\n  }\n\n  /**\n   * Copies a single file (folders are not supported as both src and dst) from the specified source\n   * to the specified destination S3CommonFileObject.\n   * Uses S3 server-side copy if both source and destination are S3CommonFileObject.\n   * Falls back to S3 upload if server-side copy fails or if the source is not an S3CommonFileObject.\n   *\n   * @param src The source FileObject to copy from\n   * @param dst The destination S3CommonFileObject to copy to\n   * @throws FileSystemException If an error occurs during the copy operation.\n   */\n  private void copySingleFileFrom( final FileObject src, final S3CommonFileObject dst ) throws FileSystemException {\n    S3CommonFileObject s3Src = extractDelegateS3FileObject( src );\n    FileSystemException copyException = null;\n    if ( s3Src != null ) {\n      // S3 to S3 copy","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/s3-vfs/core/src/main/java/org/pentaho/s3common/S3CommonFileObject.java#L420-L456","documentation":"calculateDestination() builds the destination S3 key for a copy (bucket + relative path) and resolves the resulting S3CommonFileObject. Any exception in that resolution (invalid bucket name, malformed key, filesystem resolve failure) is wrapped in FileSystemException('vfs.provider/copy-file.error', srcFile, this, cause).","triggerScenarios":"copyFrom() walking a file tree when resolving the computed destination path fails: destination bucket does not exist, resolved key contains illegal characters, or fileSystem.resolveFile() throws for the constructed VFS URI.","commonSituations":"Copy into a destination bucket that does not exist or the account cannot access, source/destination path construction producing double slashes or empty key segments, special characters in filenames not URL-encodable.","solutions":["Verify the destination bucket exists and is writable by the configured credentials.","Inspect the wrapped cause for the exact resolveFile() failure.","Normalize relative paths (strip leading separators, empty segments) before building dstKey.","Sanitize/encode special characters in file names before constructing the S3 key.","Test the destination URI resolution independently before the bulk copy."],"exampleFix":"// before\nreturn (S3CommonFileObject) fileSystem.resolveFile( getName().getRoot() + DELIMITER + bucketName + DELIMITER + dstKey );\n// after\nString cleanKey = dstKey.replaceAll( \"/+\", \"/\" );\nif ( !bucketExists( bucketName ) ) {\n  throw new FileSystemException( \"Destination bucket missing: \" + bucketName );\n}\nreturn (S3CommonFileObject) fileSystem.resolveFile( getName().getRoot() + DELIMITER + bucketName + DELIMITER + cleanKey );","handlingStrategy":"validation","validationCode":"// before copying, validate destination key construction\nif ( bucketName == null || bucketName.isEmpty() )\n  throw new IllegalArgumentException( \"destination bucket missing\" );\nString cleanKey = relativePath.replaceAll( \"/+\", \"/\" );\ntry {\n  fileSystem.resolveFile( root + \"/\" + bucketName + \"/\" + cleanKey );\n} catch ( FileSystemException e ) {\n  throw new IllegalStateException( \"destination unresolvable: \" + cleanKey, e );\n}","typeGuard":null,"tryCatchPattern":"try {\n  dst.copyFrom( src, Selectors.SELECT_ALL );\n} catch ( FileSystemException e ) {\n  // cause from calculateDestination() → resolveFile failure\n  logger.error( \"copy failed resolving destination\", e.getCause() );\n  throw e;\n}","preventionTips":["Sanitize file names (no control characters, encoded specials) before key construction","Ensure destination bucket exists and is writable in the target account","Normalize relative paths to prevent double slashes or empty segments","Validate bucket names against S3 naming rules (3-63 chars, lowercase)"],"tags":["s3","vfs","copy","path-resolution"],"backgroundTag":"invalid-argument-value","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}