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

com.sshtools.server.vsession.PosixChannelPtyTerminal Maven / Gradle / Ivy

The newest version!
package com.sshtools.server.vsession;

/*-
 * #%L
 * Virtual Sessions
 * %%
 * Copyright (C) 2002 - 2024 JADAPTIVE Limited
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import org.jline.terminal.Size;
import org.jline.terminal.impl.AbstractPosixTerminal;
import org.jline.terminal.spi.Pty;
import org.jline.utils.ClosedException;
import org.jline.utils.Log;
import org.jline.utils.NonBlocking;
import org.jline.utils.NonBlockingInputStream;
import org.jline.utils.NonBlockingReader;

import com.sshtools.common.ssh.Channel;

public class PosixChannelPtyTerminal extends AbstractPosixTerminal {

    private final Channel out;
    private final InputStream masterInput;
    private final OutputStream masterOutput;
    private final NonBlockingInputStream input;
    private final OutputStream output;
    private final NonBlockingReader reader;
    private final PrintWriter writer;

    private final Object lock = new Object();
    private Thread outputPumpThread;
    private boolean paused = true;
    private ByteArrayOutputStream pauseBuffer = new ByteArrayOutputStream();
    Size size;
    
    public PosixChannelPtyTerminal(String name, String type, Pty pty, int cols, int rows, Channel out, Charset encoding) throws IOException {
        this(name, type, pty, cols, rows, out, encoding, SignalHandler.SIG_DFL);
    }

    public PosixChannelPtyTerminal(String name, String type, Pty pty, int cols, int rows, Channel out, Charset encoding, SignalHandler signalHandler) throws IOException {
        this(name, type, pty, cols, rows, out, encoding, signalHandler, false);
    }

    public PosixChannelPtyTerminal(String name, String type, Pty pty, int cols, int rows, Channel  out, Charset encoding, SignalHandler signalHandler, boolean paused) throws IOException {
        super(name, type, pty, encoding, signalHandler);
        this.out = Objects.requireNonNull(out);
        this.masterInput = pty.getMasterInput();
        this.masterOutput = pty.getMasterOutput();
        this.input = new InputStreamWrapper(NonBlocking.nonBlocking(name, pty.getSlaveInput()));
        this.output = pty.getSlaveOutput();
        this.reader = NonBlocking.nonBlocking(name, input, encoding());
        this.writer = new PrintWriter(new OutputStreamWriter(output, encoding()));
        this.size = new Size(cols, rows);
        parseInfoCmp();
        if (!paused) {
            resume();
        }
    }
    
    public Size getSize() {
       return size;
    }

	public void in(byte[] buf, int off, int len) throws IOException {
        synchronized (lock) {
        	if(paused) {
        		pauseBuffer.write(buf, off, len);
        		return;
        	}
        }
        masterOutput.write(buf,off,len);
        masterOutput.flush();
	}

    public InputStream input() {
        return input;
    }

    public NonBlockingReader reader() {
        return reader;
    }

    public OutputStream output() {
        return output;
    }

    public PrintWriter writer() {
        return writer;
    }

    @Override
    protected void doClose() throws IOException {
        reader.close();
    }

    @Override
    public boolean canPauseResume() {
        return true;
    }

    @Override
    public void pause() {
        synchronized (lock) {
            paused = true;
        }
    }

    @Override
    public void pause(boolean wait) throws InterruptedException {
        Thread p2;
        synchronized (lock) {
            paused = true;
            p2 = outputPumpThread;
        }
        if (p2 != null) {
            p2.interrupt();
        }
        if (p2 !=null) {
            p2.join();
        }
    }

    @Override
    public void resume() {
        synchronized (lock) {
            paused = false;
            if (outputPumpThread == null) {
                outputPumpThread = new Thread(this::pumpOut, toString() + " output pump thread");
                outputPumpThread.setDaemon(true);
                outputPumpThread.start();
            }
            if(pauseBuffer.size() > 0) {
            	try {
	                masterOutput.write(pauseBuffer.toByteArray());
	                pauseBuffer.reset();
	                masterOutput.flush();
            	}
            	catch(IOException ioe) {
            		Log.error("Failed to flush pause buffer.", ioe);
            	}
            }
        }
    }

    @Override
    public boolean paused() {
        synchronized (lock) {
            return paused;
        }
    }

    private class InputStreamWrapper extends NonBlockingInputStream {

        private final NonBlockingInputStream in;
        private final AtomicBoolean closed = new AtomicBoolean();

        protected InputStreamWrapper(NonBlockingInputStream in) {
            this.in = in;
        }

        @Override
        public int read(long timeout, boolean isPeek) throws IOException {
            if (closed.get()) {
                throw new ClosedException();
            }
            return in.read(timeout, isPeek);
        }

        @Override
        public void close() throws IOException {
            closed.set(true);
        }
    }

    private void pumpOut() {
        try {

            byte[] buf = new byte[65536];
            for (;;) {
                synchronized (lock) {
                    if (paused) {
                        outputPumpThread = null;
                        return;
                    }
                }
                int b = masterInput.read(buf);
                if (b < 0) {
                    input.close();
                    break;
                }
                out.sendData(buf, 0, b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            synchronized (lock) {
                outputPumpThread = null;
            }
        }
        try {
            close();
        } catch (Throwable t) {
            // Ignore
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy