Vít Kotačka
Vít Kotačka

Reputation: 1612

How to run a process in Java inside a symlink directory?

I need to run a native process from Java inside a symlink directory. Let's have following directory structure:

guido@Firefly:~/work$ tree
.
└── path
    └── to
        └── symlink -> /home/guido/work

3 directories, 0 files

I'm starting the Java application from the ~/work directory and want to run the native process in the ~/work/path/to/symlink directory.

However, if I use following Java code, the symlink working directory resolves to the real path. Instead, I would like to run the command in the absolute path.

(Please mind, that pwd command is just for illustration and should be replaced with the "real" one (e.g. go build in my case)).

File baseDir = new File("/home/guido/work");
File link = new File(baseDir, "path/to/symlink");
Files.createSymbolicLink(link.toPath(), baseDir.toPath());

Process process = new ProcessBuilder()
            .command("pwd")
            .directory(link)
            .start();

String output = getOutput(process);
System.out.println(output); // Prints: /home/guido/work

I was able to meet my needs with following workaround, but it looks stupid to start a shell just to be able to trigger a simple process in a specific directory. Plus, I lose platform independence.

String[] cmd = {"/bin/sh", "-c", "cd " + link + " && pwd"};
Process process = Runtime.getRuntime().exec(cmd);

String output = getOutput(process);
System.out.println(output); // Prints: /home/guido/work/path/to/symlink

Here, you can find a full example as a unit test gist with both solutions.

Upvotes: 4

Views: 1667

Answers (3)

binit92
binit92

Reputation: 95

This was able to meet my needs with following workaround, but it looks stupid to start a shell just to be able to trigger a simple process in a specific directory. Plus, I lose platform independence.

String[] cmd = {"/bin/sh", "-c", "cd " + link + " && pwd"};
Process process = Runtime.getRuntime().exec(cmd);

String output = getOutput(process);
System.out.println(output); // Prints: /home/guido/work/path/to/symlink

https://gist.github.com/sw-samuraj/2c09157b8175b5a2365ae4c843690de0

Upvotes: 0

ewramner
ewramner

Reputation: 6233

As the other answer indicates there is probably no way to get this to work with symlinks, but perhaps there is another way. You could use a bind mount instead. See https://unix.stackexchange.com/questions/198590/what-is-a-bind-mount or https://backdrift.org/how-to-use-bind-mounts-in-linux for more details. In short:

mkdir -p /path/to
mount -o bind /home/guido/work /path/to/symlink

This mounts a new file system in the path as a new view to the same directories and files. The Java application should resolve the path correctly. On the other hand a symlink is much more convenient and can be created without being root, so this is an exotic solution. Nevertheless, if it is important enough, perhaps it is the way to go?

Upvotes: 1

0xabadea
0xabadea

Reputation: 86

If I understand correctly, you want the process to see the symlink as its working directory, and not the symlink target. I doubt that this is possible. A similar question has been asked for Python:

Prevent `os.chdir` from resolving symbolic link

First, the JVM doesn't resolve symlinks while running a process. After forking, it calls the chdir() system call in the child process, and that resolves the symlink:

https://github.com/dmlloyd/openjdk/blob/342a565a2da8abd69c4ab85e285bb5f03b48b2c9/src/java.base/unix/native/libjava/childproc.c#L362

Next, the working directory is returned by the getcwd() system call, which is guaranteed to return a path whose components are not symlinks:

http://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html

Your workaround works for Bash, but it will not work for a process that calls getcwd(). Bash has its own version of pwd, which (I guess) also looks at the PWD environment variable (which is set by cd). If you modify pwd to /usr/bin/pwd in your workaround, it will display the symlink target.

Upvotes: 5

Related Questions