All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kohsuke.ajaxterm.PtyProcessBuilder Maven / Gradle / Ivy

The newest version!
package org.kohsuke.ajaxterm;

import com.sun.jna.Memory;
import com.sun.jna.ptr.IntByReference;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.kohsuke.ajaxterm.CLibrary.*;
import static org.kohsuke.ajaxterm.UtilLibrary.LIBUTIL;

/**
 * {@link ProcessBuilder} for launching {@linkplain ProcessWithPty a child process with pseudo-terminal}.
 *
 * 

* A pseudo-terminal is a special kind of pipe, so when we launch a new child process * with a {@link Terminal}, a special kind of fork needs to be used, hence this class * and not the standard {@link ProcessBuilder}. * * @author Kohsuke Kawaguchi */ public class PtyProcessBuilder { private List commands = new ArrayList(); private File pwd; private Map environment = new HashMap(); public PtyProcessBuilder commands(List cmds) { this.commands.addAll(cmds); return this; } public PtyProcessBuilder commands(String... cmds) { return commands(Arrays.asList(cmds)); } public List commands() { return commands(); } public PtyProcessBuilder pwd(File pwd) { this.pwd = pwd; return this; } public PtyProcessBuilder env(String name, String value) { this.environment.put(name, value); return this; } public PtyProcessBuilder envs(Map envs) { this.environment.putAll(envs); return this; } public Map envs() { return this.environment; } /** * Uses Python helper code to handle tty. This is more portable but requires a separate python interpreter. */ public ProcessWithPty forkWithHelper() throws IOException { File py = File.createTempFile("ttycontrol", "py"); InputStream in = PtyProcessBuilder.class.getResourceAsStream("ttycontrol.py"); copyToFile(in,py); py.deleteOnExit(); ProcessBuilder pb = new ProcessBuilder(); if (pwd!=null) pb.directory(pwd); pb.redirectErrorStream(true); pb.environment().putAll(environment); List pyCmds = pb.command(); pyCmds.add("python"); pyCmds.add("-u"); pyCmds.add(py.getAbsolutePath()); pyCmds.addAll(commands); final Process base = pb.start(); return new ProcessWithPty() { final DataOutputStream out = new DataOutputStream(base.getOutputStream()); @Override public void setWindowSize(int width, int height) throws IOException { out.write(0x03); out.writeShort(4); out.writeShort(height); out.writeShort(width); out.flush(); } @Override public void kill(int signal) throws IOException { out.write(0x02); out.writeShort(2); out.writeShort(signal); out.flush(); } @Override public OutputStream getOutputStream() { return new OutputStream() { @Override public void write(int b) throws IOException { write(new byte[]{(byte) b}); } @Override public void write(byte[] b, int off, int len) throws IOException { while (len>0) { int chunk = (len&0x7FFF); out.write(0x01); out.writeShort(chunk); out.write(b,off,chunk); off+=chunk; len-=chunk; } flush(); } @Override public void flush() throws IOException { out.flush(); } // TODO: how do we handle close()? }; } @Override public InputStream getInputStream() { return base.getInputStream(); } @Override public InputStream getErrorStream() { return base.getErrorStream(); } @Override public int waitFor() throws InterruptedException { return base.waitFor(); } @Override public int exitValue() { return base.exitValue(); } @Override public void destroy() { base.destroy(); } }; } /** * Uses JNA to fork a process without external helper. Less portable but requires no Python. */ public ProcessWithPty fork() { if(commands.size()==0) throw new IllegalArgumentException("No command line arguments"); // make execv call to force classloading // once we fork, the child process cannot load more classes reliably. LIBC.execv("-",new String[]{"-","-"}); for( int i=LIBC.getdtablesize()-1; i>0; i-- ) { LIBC.fcntl(1,F_GETFD,0); } // capture everything before the fork since JVM will be unstable between fork & exec String program = commands.get(0); String[] args = commands.toArray(new String[commands.size()]); String pwd = this.pwd==null ? null : this.pwd.getAbsolutePath(); String[] envs = new String[environment.size()*2]; int idx=0; for (Map.Entry e :environment.entrySet()) { envs[idx++] = e.getKey(); envs[idx++] = e.getValue(); } final IntByReference pty = new IntByReference(); final int pid = LIBUTIL.forkpty(pty, null, null, null); if(pid==0) { // on child process LIBC.setsid(); for( int i=LIBC.getdtablesize()-1; i>=3; i-- ) { LIBC.fcntl(i, F_SETFD,LIBC.fcntl(i, F_GETFD,0)|FD_CLOEXEC); } if (pwd!=null) LIBC.chdir(pwd); // environment variable overrides for (int i=0; i 0; if (isDead) { int x = status.getValue(); if ((x&0x7F)!=0) exitCode=128+(x&0x7F); exitCode = (x>>8)&0xFF; } } @Override public void destroy() { if (exitCode==null) LIBC.kill(pid,15/*SIGTERM*/); } @Override public void setWindowSize(int width, int height) { Memory struct = new Memory(8); // 4 unsigned shorts struct.setShort(0,(short)height); struct.setShort(2,(short)width); struct.setInt(4,0); if (LIBC.ioctl(pty.getValue(),TIOCSWINSZ,struct)!=0) throw new IllegalStateException("Failed to ioctl(TIOCSWINSZ)"); } @Override public void kill(int signal) { LIBC.kill(pid,signal); } }; } private static FileDescriptor createFileDescriptor(int v) { try { FileDescriptor fd = new FileDescriptor(); Field f = FileDescriptor.class.getDeclaredField("fd"); f.setAccessible(true); f.set(fd,v); return fd; } catch (NoSuchFieldException e) { throw new AssertionError(e); } catch (IllegalAccessException e) { throw new AssertionError(e); } } private void copyToFile(InputStream in, File out) throws IOException { byte[] buf = new byte[1024]; OutputStream os = new FileOutputStream(out); int len; try { while ((len=in.read(buf))>=0) os.write(buf,0,len); } finally { os.close(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy