djthoms
djthoms

Reputation: 3106

Creating a symlink on Windows during JVM runtime

Slightly bizarre set of questions but I'm running into issues creating a symlink using mklink on Windows 7. I'm doing something a little weird due to the 260 character limit that exists when using cmd.exe by creating symlinks inside of my Java source code by using Process. Since I can't quite explain it, here's the code:

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Arrays;

public class WindowsSymlinkUtility {

    private List<String> command, currentSymlinks;

    public WindowsSymlinkUtility() {
        this.command = this.currentSymlinks = new ArrayList<String>();
        this.command.add("cmd.exe");
        this.command.add("/C");
    }

    /**
     * Automatically creates a directory junction
     * @param String link - the path and name of the symlink
     * @param String target - the directory to point the symlink to
     * @return boolean
     * @see http://ss64.com/nt/mklink.html
     */
    public boolean createSymlink(String link, String target) {
        return createSymlink("\\J", link, target);
    }

    /**
     *
     * @param String flag - the flag for mklink
     * @param String link - the path and name of the symlink
     * @param String target - the directory to point the symlink to
     * @return boolean
     * @see http://ss64.com/nt/mklink.html
     */
    public boolean createSymlink(String flag, String link, String target) {
        this.command.clear();
        this.command.add("mklink");
        this.command.add(flag);
        this.command.add(link);
        this.command.add(target);
        this.currentSymlinks.add(link);

        return this.runner() == 0;
    }

    public boolean removeSymlink(String link) {
        this.command.clear();
        this.command.add("RD");
        this.command.add(link);

        if(this.runner() != 0) {
            this.command.clear();
            this.command.add("DEL");
            this.command.add(link);
        } else {
            return true;
        }

        return this.runner() == 0;
    }

    public boolean removeAllSymlinks() {
        for(String link : this.currentSymlinks) {
            if(!this.removeSymlink(link)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Leave for debugging purposes
     * @return String
     */
    public String getCurrentCommand() {
        String cmd = "";
        for(String part : this.command) {
            cmd += part + " ";
        }

        return cmd;
    }

    private int runner() {
        Process process = null;
        String message = null;
        BufferedInputStream bis = null;
        int exitVal = -1;
        StringBuilder strBuff = new StringBuilder();

        try {
            if(this.command.size() < 1) throw new Exception("Length of Windows command cannot be zero");

            ProcessBuilder pb = new ProcessBuilder(this.command);
            Map<String, String> envVars = pb.environment();

            pb.directory();
            pb.redirectErrorStream(true);
            process = pb.start();
            bis = new BufferedInputStream(process.getInputStream());
            byte[] bArr = new byte[2048];
            while (bis.read(bArr) != -1) {
                strBuff.append(new String(bArr).trim());
                bArr = new byte[2048];
            }

            exitVal = process.waitFor();
            message = strBuff.toString();
        } catch(Exception e) {
            e.printStackTrace();
            System.err.println(e.getMessage());
            System.err.println(message);
        }

        return exitVal;
    }

    public static void main(String[] args) {
        WindowsSymlinkUtility foo = new WindowsSymlinkUtility();
        foo.createSymlink("%TEMP%\\foo", "C:\\Users\\djthomps\\Downloads");
    }

}

The error I'm getting:

java.io.IOException: Cannot run program "mklink": CreateProcess error=2, The system cannot find the file specified
        at java.lang.ProcessBuilder.start(Unknown Source)
        at WindowsSymlinkUtility.runner(WindowsSymlinkUtility.java:113)
        at WindowsSymlinkUtility.createSymlink(WindowsSymlinkUtility.java:56)
        at WindowsSymlinkUtility.createSymlink(WindowsSymlinkUtility.java:37)
        at WindowsSymlinkUtility.main(WindowsSymlinkUtility.java:134)
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
        at java.lang.ProcessImpl.create(Native Method)
        at java.lang.ProcessImpl.<init>(Unknown Source)
        at java.lang.ProcessImpl.start(Unknown Source)
        ... 5 more
Cannot run program "mklink": CreateProcess error=2, The system cannot find the file specified
null

Some questions you might have:

  1. Why are you doing this?
    • Because the length of the full command runs well over 260 characters due to relevant files and folders being deeply nested in the file system.
  2. How will symlinks help?
    • I have done tests to ensure that symlinks allow me to "bypass" the 260 character limit.

Here are my questions:

  1. Is there another way to create a symlink in Java such that Windows will behave when a command exceeds the 260 character limit?
  2. Can SET be used in lieu of mklink?
  3. Is it possible to use java.nio.file for this even if the command runs over 260 characters?

Again, I understand this is an odd question. Ask for clarification if something is amiss.

Upvotes: 1

Views: 1539

Answers (2)

djthoms
djthoms

Reputation: 3106

Eddie B's solution was on the right track but I kept getting errors when Java as attempting to run the command. Here's my rendition that works:

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Arrays;

public class WindowsSymlinkUtility {

    public static final String D_LINK = "/D";
    public static final String H_LINK = "/H";
    public static final String J_LINK = "/J";
    public static final String REM_LINK = "rmdir";

    private String command, flag, link, target;
    private List<String> commands = Arrays.asList(D_LINK, H_LINK, J_LINK, REM_LINK), symlinks;

    public WindowsSymlinkUtility() {
        this.command = this.flag = this.link = this.target = "";
        this.symlinks = new ArrayList<>();
    }

    /**
     * Automatically creates a directory junction
     * @param String link - the path and name of the symlink
     * @param String target - the directory to point the symlink to
     * @return boolean
     * @see http://ss64.com/nt/mklink.html
     */
    public boolean createSymlink(String link, String target) {
        return createSymlink(J_LINK, link, target);
    }

    /**
     *
     * @param String flag - the flag for mklink
     * @param String link - the path and name of the symlink
     * @param String target - the directory to point the symlink to
     * @return boolean
     * @see http://ss64.com/nt/mklink.html
     */
    public boolean createSymlink(String flag, String link, String target) {
        if(!this.commands.contains(flag)) {
            System.err.printf("%s is not a valid command\n", flag);
            return false;
        }

        this.command = "mklink";
        this.flag = flag;
        this.link = link;
        this.target = target;

        if(this.runner() == 0) {
            this.symlinks.add(this.link);
            return true;
        }

        return false;
    }

    private int runner() {
        Process process = null;
        String message = null;
        BufferedInputStream bis = null;
        StringBuilder strBuff = new StringBuilder();
        int exitVal = -1;

        try {
            ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/C", this.command, this.flag, this.link, this.target);
            Map<String, String> envVars = pb.environment();

            pb.directory();
            pb.redirectErrorStream(true);
            process = pb.start();
            bis = new BufferedInputStream(process.getInputStream());
            byte[] bArr = new byte[2048];
            while (bis.read(bArr) != -1) {
                strBuff.append(new String(bArr).trim());
                bArr = new byte[2048];
            }

            exitVal = process.waitFor();
            message = strBuff.toString();
            System.out.println(message);
        } catch(Exception e) {
            e.printStackTrace();
            System.err.println(e.getMessage());
            System.err.println(message);
        }

        return exitVal;
    }

    public static void main(String[] args) {
        (new WindowsSymlinkUtility()).createSymlink(J_LINK, "%TEMP%\\node", "C:\\users\\djthomps\\Downloads");
    }

}

Upvotes: 0

Edward J Beckett
Edward J Beckett

Reputation: 5140

I've modified your program a bit just to provide a working sample... essentially the problem is that you're not concatenating the variables and passing them as one argument to cmd.

One implementation note :: Do Not use del to remove a symlink otherwise all the files in the target directory will be erased. Use rmdir, which I've added for posterity.

/**
 * @author Edward Beckett :: <[email protected]>
 * @since :: 7/21/2015
 */
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;

public class WindowsSymlinkUtility {

    public static final String D_LINK = "/D";
    public static final String H_LINK = "/H";
    public static final String J_LINK = "/J";
    public static final String REM_LINK = "rmdir";
    private String command = "";
    private String link = "";
    private String target = "";

    private List<String> commands = Arrays.asList( D_LINK, H_LINK, J_LINK, REM_LINK );

    public void createSymlink( String command, String link, String target ) {
        this.command = command;
        this.link = link;
        this.target = target;

        if( !commands.contains( command ) ) {
            System.out.println( command + " Is not a valid command \n " );
            return;
        }
        runner();
    }


    private void runner() {

        try {

            String[] values = { "CMD", "/C", "mklink", this.command, this.link, this.target };
            ProcessBuilder builder = new ProcessBuilder( values );
            builder.directory( new File( this.link ) );
            Process process = builder.start();
            InputStream is = process.getInputStream();
            InputStreamReader isr = new InputStreamReader( is );
            BufferedReader br = new BufferedReader( isr );
            String line;
            System.out.printf( "Output of running %s is:\n",
                Arrays.toString( values ) );
            while( ( line = br.readLine() ) != null ) {
                System.out.println( line );
                int exitValue = process.waitFor();
                System.out.println( "\n\nExit Value is " + exitValue );
            }
        } catch( InterruptedException | IOException e ) {
            e.printStackTrace();
        }
    }
        public static void main( String[] args ) {
        ( new WindowsSymlinkUtility() ).createSymlink( J_LINK, "C:\\Foo", "C:\\Temp" );
    }

}

output

Output of running [CMD, /C, mklink, /J, C:\Foo, C:\Temp] is:
Junction created for C:\Foo <<===>> C:\Temp
Exit Value is 0

Upvotes: 2

Related Questions