blob: 8365f12838808eceac169b1b61b84d040f438d0b [file] [log] [blame]
package com.trilead.ssh2.channel;
/**
* Channel.
*
* @author Christian Plattner, plattner@trilead.com
* @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
*/
public class Channel
{
/*
* OK. Here is an important part of the JVM Specification:
* (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214)
*
* Any association between locks and variables is purely conventional.
* Locking any lock conceptually flushes all variables from a thread's
* working memory, and unlocking any lock forces the writing out to main
* memory of all variables that the thread has assigned. That a lock may be
* associated with a particular object or a class is purely a convention.
* (...)
*
* If a thread uses a particular shared variable only after locking a
* particular lock and before the corresponding unlocking of that same lock,
* then the thread will read the shared value of that variable from main
* memory after the lock operation, if necessary, and will copy back to main
* memory the value most recently assigned to that variable before the
* unlock operation.
*
* This, in conjunction with the mutual exclusion rules for locks, suffices
* to guarantee that values are correctly transmitted from one thread to
* another through shared variables.
*
* ====> Always keep that in mind when modifying the Channel/ChannelManger
* code.
*
*/
static final int STATE_OPENING = 1;
static final int STATE_OPEN = 2;
static final int STATE_CLOSED = 4;
static final int CHANNEL_BUFFER_SIZE = 30000;
/*
* To achieve correctness, the following rules have to be respected when
* accessing this object:
*/
// These fields can always be read
final ChannelManager cm;
final ChannelOutputStream stdinStream;
final ChannelInputStream stdoutStream;
final ChannelInputStream stderrStream;
// These two fields will only be written while the Channel is in state
// STATE_OPENING.
// The code makes sure that the two fields are written out when the state is
// changing to STATE_OPEN.
// Therefore, if you know that the Channel is in state STATE_OPEN, then you
// can read these two fields without synchronizing on the Channel. However, make
// sure that you get the latest values (e.g., flush caches by synchronizing on any
// object). However, to be on the safe side, you can lock the channel.
int localID = -1;
int remoteID = -1;
/*
* Make sure that we never send a data/EOF/WindowChange msg after a CLOSE
* msg.
*
* This is a little bit complicated, but we have to do it in that way, since
* we cannot keep a lock on the Channel during the send operation (this
* would block sometimes the receiver thread, and, in extreme cases, can
* lead to a deadlock on both sides of the connection (senders are blocked
* since the receive buffers on the other side are full, and receiver
* threads wait for the senders to finish). It all depends on the
* implementation on the other side. But we cannot make any assumptions, we
* have to assume the worst case. Confused? Just believe me.
*/
/*
* If you send a message on a channel, then you have to aquire the
* "channelSendLock" and check the "closeMessageSent" flag (this variable
* may only be accessed while holding the "channelSendLock" !!!
*
* BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation
* above.
*/
final Object channelSendLock = new Object();
boolean closeMessageSent = false;
/*
* Stop memory fragmentation by allocating this often used buffer.
* May only be used while holding the channelSendLock
*/
final byte[] msgWindowAdjust = new byte[9];
// If you access (read or write) any of the following fields, then you have
// to synchronize on the channel.
int state = STATE_OPENING;
boolean closeMessageRecv = false;
/* This is a stupid implementation. At the moment we can only wait
* for one pending request per channel.
*/
int successCounter = 0;
int failedCounter = 0;
int localWindow = 0; /* locally, we use a small window, < 2^31 */
long remoteWindow = 0; /* long for readable 2^32 - 1 window support */
int localMaxPacketSize = -1;
int remoteMaxPacketSize = -1;
final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE];
final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE];
int stdoutReadpos = 0;
int stdoutWritepos = 0;
int stderrReadpos = 0;
int stderrWritepos = 0;
boolean EOF = false;
Integer exit_status;
String exit_signal;
// we keep the x11 cookie so that this channel can be closed when this
// specific x11 forwarding gets stopped
String hexX11FakeCookie;
// reasonClosed is special, since we sometimes need to access it
// while holding the channelSendLock.
// We protect it with a private short term lock.
private final Object reasonClosedLock = new Object();
private String reasonClosed = null;
public Channel(ChannelManager cm)
{
this.cm = cm;
this.localWindow = CHANNEL_BUFFER_SIZE;
this.localMaxPacketSize = 35000 - 1024; // leave enough slack
this.stdinStream = new ChannelOutputStream(this);
this.stdoutStream = new ChannelInputStream(this, false);
this.stderrStream = new ChannelInputStream(this, true);
}
/* Methods to allow access from classes outside of this package */
public ChannelInputStream getStderrStream()
{
return stderrStream;
}
public ChannelOutputStream getStdinStream()
{
return stdinStream;
}
public ChannelInputStream getStdoutStream()
{
return stdoutStream;
}
public String getExitSignal()
{
synchronized (this)
{
return exit_signal;
}
}
public Integer getExitStatus()
{
synchronized (this)
{
return exit_status;
}
}
public String getReasonClosed()
{
synchronized (reasonClosedLock)
{
return reasonClosed;
}
}
public void setReasonClosed(String reasonClosed)
{
synchronized (reasonClosedLock)
{
if (this.reasonClosed == null)
this.reasonClosed = reasonClosed;
}
}
}