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

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

There is a newer version: 1.7
Show newest version
package org.kohsuke.ajaxterm;

import com.sun.jna.ptr.IntByReference;
import static org.kohsuke.ajaxterm.UtilLibrary.LIBUTIL;
import static org.kohsuke.ajaxterm.CLibrary.*;
import static org.kohsuke.ajaxterm.CLibrary.FD_CLOEXEC;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents a session.
 *
 * 

* A {@link Thread} is used to shuttle data back and force between the HTTP client * and the process that was forked. You can check the liveness of this thread to see * if the child process is still alive or not. * * @author Kohsuke Kawaguchi */ public final class Session extends Thread { /** * PID of the child process. */ private final int pid; /** * Exit code of the process. */ private int exitCode; private final Terminal terminal; private final long time = System.currentTimeMillis(); /** * When was this session accessed the last time? */ private long lastAccess; private final Reader in; private final Writer out; /** * * @param width * Width of the terminal. For example, 80. * @param height * Height of the terminal. For example, 25. * @param commands * Command line arguments of the process to launch. * {"/bin/bash","--login"} for example. */ public Session(int width, int height, String... commands) throws Exception { if(commands.length==0) throw new IllegalArgumentException("No command line arguments"); this.terminal = new Terminal(width,height); // 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); } IntByReference pty = new IntByReference(); 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); } LIBC.setenv("TERM","linux",1); LIBC.execv(commands[0],commands); } /* in = new FileReader(createFileDescriptor(pty.getValue())); out = new FileWriter(createFileDescriptor(pty.getValue())); */ FileDescriptor fileDescriptor = createFileDescriptor(pty.getValue()); in = new FileReader(fileDescriptor); out = new FileWriter(fileDescriptor); setName("Terminal pump thread for "+ Arrays.asList(commands)); start(); // start pumping } private FileDescriptor createFileDescriptor(int v) throws NoSuchFieldException, IllegalAccessException { FileDescriptor fd = new FileDescriptor(); Field f = FileDescriptor.class.getDeclaredField("fd"); f.setAccessible(true); f.set(fd,v); return fd; } /** * When was this session accessed by the client the last time? */ public long getLastAccess() { return lastAccess; } /** * PID of the forked child process. */ public int getPID() { return pid; } /** * Exit code of the process, if the process has already terminated. * * If {@linkplain #isAlive() it's still running}, this method returns 0. */ public int getExitCode() { return exitCode; } /** * When was this session allocated? */ public long getTime() { return time; } /** * Kills the process. */ public void kill() throws IOException { in.close(); out.close(); LIBC.kill(pid,15/*SIGTERM*/); } @Override public void run() { char[] buf = new char[128]; int len; try { while((len=in.read(buf))>=0) { terminal.write(new String(buf,0,len)); String reply = terminal.read(); if(reply!=null) out.write(reply); } hasChildProcessFinished(); } catch (IOException e) { if (!hasChildProcessFinished()) LOGGER.log(Level.WARNING, "Session pump thread is dead", e); } finally { closeQuietly(in); closeQuietly(out); } } private boolean hasChildProcessFinished() { IntByReference status = new IntByReference(); boolean b = LIBC.waitpid(pid, status, LIBC.WNOHANG) > 0; if (b) { int x = status.getValue(); if ((x&0x7F)!=0) exitCode=128+(x&0x7F); exitCode = (x>>8)&0xFF; } return b; } private void closeQuietly(Closeable c) { try { if (c!=null) c.close(); } catch (IOException e) { // silently ignore } } /** * Receives the call from the client-side JavaScript. */ public void handleUpdate(HttpServletRequest req, HttpServletResponse rsp) throws IOException, InterruptedException { lastAccess = System.currentTimeMillis(); String k = req.getParameter("k"); if(k!=null && k.length()!=0) { out.write(k); out.flush(); } Thread.sleep(20); // give a bit of time to let the app respond rsp.setContentType("application/xml;charset=UTF-8"); if(terminal.showCursor) { rsp.addHeader("Cursor-X",String.valueOf(terminal.getCx())); rsp.addHeader("Cursor-Y",String.valueOf(terminal.getCy())); rsp.addHeader("Screen-X",String.valueOf(terminal.width)); rsp.addHeader("Screen-Y",String.valueOf(terminal.height)); } terminal.setCssClass(isAlive() ? "":"dead"); ScreenImage screen = terminal.dumpHtml( req.getParameter("c") != null, Integer.parseInt(req.getParameter("t"))); rsp.addHeader("Screen-Timestamp",String.valueOf(screen.timestamp)); rsp.getWriter().println(screen.screen); } private static final Logger LOGGER = Logger.getLogger(Session.class.getName()); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy