| |
| 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; |
| } |
| } |
| } |