pentaho/pentaho-kettle · error · IOException
unable to use a symbolic link:
Error message
unable to use a symbolic link:
What it means
CustomDataStoreFactory's constructor rejects a data directory that is a symbolic link. It canonicalizes the path first, then throws IOException 'unable to use a symbolic link: <dir>' if the canonical path is still a symlink, as a security measure against symlink attacks on the local Google Drive token/data cache.
Solutions
- Point the data directory at a real directory, not a symlink.
- Bind-mount or copy the target directory to a physical path and use that path.
- If a symlink is unavoidable, replace it (rm the link, mkdir a real dir) and move data over.
Example fix
// before
File dir = new File("/var/lib/app/drive-link"); // symlink
new CustomDataStoreFactory(dir);
// after
File dir = new File("/var/lib/app/drive").getCanonicalFile(); // real directory
new CustomDataStoreFactory(dir); Defensive patterns
Strategy: validation
Validate before calling
File canonical = dir.getCanonicalFile();
if (java.nio.file.Files.isSymbolicLink(canonical.toPath())) {
throw new IllegalStateException("data directory must not be a symlink: " + canonical);
} Try / catch
try {
new CustomDataStoreFactory(dataDirectory);
} catch (IOException e) {
if (e.getMessage().startsWith("unable to use a symbolic link")) {
// reconfigure to a real directory
} else { throw e; }
} Prevention
- Configure real (non-symlink) directories for token/data caches.
- Avoid symlink-based /tmp layouts in containers; use bind mounts of real directories.
- Call getCanonicalFile() yourself early to fail fast with a clearer message.
When it happens
Trigger: Constructing new CustomDataStoreFactory(dataDirectory) where dataDirectory (after getCanonicalFile()) is a symlink — e.g. pointing the cache dir at a symlinked folder, or /tmp-style dirs replaced by symlinks.
Common situations: Dev setups where the data directory is a symlink to another disk; container images where /tmp or the app dir is a symlink; hardening-sensitive environments where this check is intentional.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- unable to create directory:
- Error opening new file
- FixedInput.Log.OnlyLocalFilesAreSupported
- IOError while create
- KettleException( e )
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/0dc61b7fa69b6482.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/pentaho-googledrive-vfs/core/src/main/java/org/pentaho/googledrive/vfs/util/CustomDataStoreFactory.java:48
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.HashMap;
import java.util.List;
import java.util.Iterator;
public class CustomDataStoreFactory extends AbstractDataStoreFactory {
private final File dataDirectory;
public CustomDataStoreFactory( File dataDirectory ) throws IOException {
dataDirectory = dataDirectory.getCanonicalFile();
this.dataDirectory = dataDirectory;
if ( IOUtils.isSymbolicLink( dataDirectory ) ) {
throw new IOException( "unable to use a symbolic link: " + dataDirectory );
} else if ( !dataDirectory.exists() && !dataDirectory.mkdirs() ) {
throw new IOException( "unable to create directory: " + dataDirectory );
}
}
protected <V extends Serializable> DataStore<V> createDataStore( String id ) throws IOException {
return new CustomDataStore( this, this.dataDirectory, id );
}
static class CustomDataStore<V extends Serializable> extends AbstractDataStore<V> {
private File dataFile = null;
private File dataDirectory = null;
private HashMap<String, byte[]> keyValueMap = Maps.newHashMap();
private final Lock lock = new ReentrantLock();
CustomDataStore( CustomDataStoreFactory dataStore, File dataDirectory, String id ) throws IOException {
super( dataStore, id );
this.dataDirectory = dataDirectory;View on GitHub (pinned to f3058517a1)