pentaho/pentaho-kettle · error · KettleException

GPG.IOException

Error message

GPG.IOException

What it means

Thrown by GPG.execGnuPG when spawning the gpg command-line process fails with a java.io.IOException. The gpg binary is invoked via Runtime.exec or ProcessBuilder (/bin/sh -c on non-Windows); if process creation fails, this wrapped KettleException is raised.

Solutions

  1. Confirm the gpg executable still exists and is executable at runtime (chmod +x, correct path).
  2. Check the wrapped IOException (getCause()) for errno details (ENOENT, EACCES, EMFILE).
  3. Raise the file-descriptor limit (ulimit -n) if EMFILE/too many open files is reported.
  4. On Windows ensure the path is quoted correctly; on non-Windows ensure /bin/sh exists in the runtime environment/container.

Example fix

// before
String command = gpgexe + " --decrypt " + filename; // unquoted spaces break exec
// after
String command = "\"" + gpgexe + "\" --decrypt \"" + filename + "\""; // quoted paths
p = Runtime.getRuntime().exec( command );
Defensive patterns

Strategy: try-catch

Validate before calling

java.io.File gpg = new java.io.File( gpgexe );
if ( !gpg.canExecute() ) throw new IllegalStateException( "gpg not executable: " + gpgexe );
// check fd headroom
if ( new java.io.File( "/proc/self/fd" ).list().length > 20000 ) log.warn( "High fd usage; exec may fail" );

Type guard

boolean canExecGpg( String p ) { return p != null && new java.io.File( p ).canExecute(); }

Try / catch

try {
  gpg.execGnuPG( args );
} catch ( KettleException e ) {
  if ( e.getCause() instanceof java.io.IOException ) {
    java.io.IOException io = (java.io.IOException) e.getCause();
    logError( "Failed to launch gpg: " + io.getMessage() + " — check path, permissions, and ulimit" );
  }
}

Prevention

When it happens

Trigger: Runtime.getRuntime().exec(command) or processBuilder.start() throws IOException — the executable path is invalid at exec time, working directory inaccessible, too many open files, or (on some JVMs) Command file creation fails.

Common situations: GPG binary deleted/moved between validation and execution; insufficient OS file descriptors (ulimit) under load; execute permission removed from the binary; container images without /bin/sh when ProcessBuilder path is used; ENOMEM/process limits hit.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/7715ada8a7a3a55e. Report an issue: GitHub.

Appendix: source

Thrown at engine/src/main/java/org/pentaho/di/job/entries/pgpencryptfiles/GPG.java:184

   */
  private String execGnuPG( String commandArgs, String inputStr, boolean fileMode ) throws KettleException {
    Process p;
    String command = getGpgExeFile() + " " + ( fileMode ? "" : gnuPGCommand + " " ) + commandArgs;

    if ( log.isDebug() ) {
      log.logDebug( BaseMessages.getString( PKG, "GPG.RunningCommand", command ) );
    }
    String retval;

    try {
      if ( Const.isWindows() ) {
        p = Runtime.getRuntime().exec( command );
      } else {
        ProcessBuilder processBuilder = new ProcessBuilder( "/bin/sh", "-c", command );
        p = processBuilder.start();
      }
    } catch ( IOException io ) {
      throw new KettleException( BaseMessages.getString( PKG, "GPG.IOException" ), io );
    }

    ProcessStreamReader psr_stdout = new ProcessStreamReader( p.getInputStream() );
    ProcessStreamReader psr_stderr = new ProcessStreamReader( p.getErrorStream() );
    psr_stdout.start();
    psr_stderr.start();
    if ( inputStr != null ) {
      BufferedWriter out = new BufferedWriter( new OutputStreamWriter( p.getOutputStream() ) );
      try {
        out.write( inputStr );
      } catch ( IOException io ) {
        throw new KettleException( BaseMessages.getString( PKG, "GPG.ExceptionWrite" ), io );
      } finally {
        if ( out != null ) {
          try {
            out.close();
          } catch ( Exception e ) {
            // Ignore

View on GitHub (pinned to f3058517a1)