Subversion Repositories Programming Utils

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
86 rm5248 1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.sshd.server.shell;
20
 
21
import java.io.FilterInputStream;
22
import java.io.FilterOutputStream;
23
import java.io.IOException;
24
import java.io.InputStream;
25
import java.io.OutputStream;
26
import java.util.EnumSet;
27
import java.util.Map;
28
 
29
import org.apache.sshd.common.Factory;
30
import org.apache.sshd.common.util.Buffer;
31
import org.apache.sshd.server.Command;
32
import org.slf4j.Logger;
33
import org.slf4j.LoggerFactory;
34
 
35
/**
36
 * A {@link Factory} of {@link Command} that will create a new process and bridge
37
 * the streams.
38
 *
39
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
40
 */
41
public class ProcessShellFactory implements Factory<Command> {
42
 
43
    public enum TtyOptions {
44
        Echo,
45
        INlCr,
46
        ICrNl,
47
        ONlCr,
48
        OCrNl
49
    }
50
 
51
    private static final Logger LOG = LoggerFactory.getLogger(ProcessShellFactory.class);
52
 
53
    private String[] command;
54
    private EnumSet<TtyOptions> ttyOptions;
55
 
56
    public ProcessShellFactory() {
57
    }
58
 
59
    public ProcessShellFactory(String[] command) {
60
        this(command, EnumSet.noneOf(TtyOptions.class));
61
    }
62
 
63
    public ProcessShellFactory(String[] command, EnumSet<TtyOptions> ttyOptions) {
64
        this.command = command;
65
        this.ttyOptions = ttyOptions;
66
    }
67
 
68
    public String[] getCommand() {
69
        return command;
70
    }
71
 
72
    public void setCommand(String[] command) {
73
        this.command = command;
74
    }
75
 
76
    public Command create() {
77
        return new InvertedShellWrapper(new ProcessShell());
78
    }
79
 
80
    public class ProcessShell implements InvertedShell {
81
 
82
        private Process process;
83
        private TtyFilterOutputStream in;
84
        private TtyFilterInputStream out;
85
        private TtyFilterInputStream err;
86
 
87
        public void start(Map<String,String> env) throws IOException {
88
            String[] cmds = new String[command.length];
89
            for (int i = 0; i < cmds.length; i++) {
90
                if ("$USER".equals(command[i])) {
91
                    cmds[i] = env.get("USER");
92
                } else {
93
                    cmds[i] = command[i];
94
                }
95
            }
96
            ProcessBuilder builder = new ProcessBuilder(cmds);
97
            if (env != null) {
98
                try {
99
                    builder.environment().putAll(env);
100
                } catch (Exception e) {
101
                    LOG.info("Could not set environment for command", e);
102
                }
103
            }
104
            LOG.info("Starting shell with command: '{}' and env: {}", builder.command(), builder.environment());
105
            process = builder.start();
106
            out = new TtyFilterInputStream(process.getInputStream());
107
            err = new TtyFilterInputStream(process.getErrorStream());
108
            in = new TtyFilterOutputStream(process.getOutputStream(), err);
109
        }
110
 
111
        public OutputStream getInputStream() {
112
            return in;
113
        }
114
 
115
        public InputStream getOutputStream() {
116
            return out;
117
        }
118
 
119
        public InputStream getErrorStream() {
120
            return err;
121
        }
122
 
123
        public boolean isAlive() {
124
            try {
125
                process.exitValue();
126
                return false;
127
            } catch (IllegalThreadStateException e) {
128
                return true;
129
            }
130
        }
131
 
132
        public int exitValue() {
133
            try {
134
                return process.waitFor();
135
            } catch (InterruptedException e) {
136
                throw new RuntimeException(e);
137
            }
138
        }
139
 
140
        public void destroy() {
141
            if (process != null) {
142
                process.destroy();
143
            }
144
        }
145
 
146
        protected class TtyFilterInputStream extends FilterInputStream {
147
            private Buffer buffer;
148
            private int lastChar;
149
            public TtyFilterInputStream(InputStream in) {
150
                super(in);
151
                buffer = new Buffer(32);
152
            }
153
            synchronized void write(int c) {
154
                buffer.putByte((byte) c);
155
            }
156
            synchronized void write(byte[] buf, int off, int len) {
157
                buffer.putBytes(buf, off, len);
158
            }
159
            @Override
160
            public int available() throws IOException {
161
                return super.available() + buffer.available();
162
            }
163
            @Override
164
            public synchronized int read() throws IOException {
165
                int c;
166
                if (buffer.available() > 0) {
167
                    c = buffer.getByte();
168
                    buffer.compact();
169
                } else {
170
                    c = super.read();
171
                }
172
                if (c == '\n' && ttyOptions.contains(TtyOptions.ONlCr) && lastChar != '\r') {
173
                    c = '\r';
174
                    Buffer buf = new Buffer();
175
                    buf.putByte((byte) '\n');
176
                    buf.putBuffer(buffer);
177
                    buffer = buf;
178
                } else if (c == '\r' && ttyOptions.contains(TtyOptions.OCrNl)) {
179
                    c = '\n';
180
                }
181
                lastChar = c;
182
                return c;
183
            }
184
            @Override
185
            public synchronized int read(byte[] b, int off, int len) throws IOException {
186
                if (buffer.available() == 0) {
187
                    int nb = super.read(b, off, len);
188
                    buffer.putRawBytes(b, off, nb);
189
                }
190
                int nb = 0;
191
                while (nb < len && buffer.available() > 0) {
192
                    b[off + nb++] = (byte) read();
193
                }
194
                return nb;
195
            }
196
        }
197
 
198
        protected class TtyFilterOutputStream extends FilterOutputStream {
199
            private TtyFilterInputStream echo;
200
            public TtyFilterOutputStream(OutputStream out, TtyFilterInputStream echo) {
201
                super(out);
202
                this.echo = echo;
203
            }
204
            @Override
205
            public void write(int c) throws IOException {
206
                if (c == '\n' && ttyOptions.contains(TtyOptions.INlCr)) {
207
                    c = '\r';
208
                } else if (c == '\r' && ttyOptions.contains(TtyOptions.ICrNl)) {
209
                    c = '\n';
210
                }
211
                super.write(c);
212
                if (ttyOptions.contains(TtyOptions.Echo)) {
213
                    echo.write(c);
214
                }
215
            }
216
            @Override
217
            public void write(byte[] b, int off, int len) throws IOException {
218
                for (int i = off; i < len; i++) {
219
                    write(b[i]);
220
                }
221
            }
222
        }
223
    }
224
 
225
}