author | tnakamura |
Thu, 13 Jun 2019 08:42:47 +0200 | |
changeset 55357 | 6c8d5d0e1be4 |
parent 52938 | 5ff7480c9e28 |
child 58903 | eeb1c0da2126 |
permissions | -rw-r--r-- |
52938 | 1 |
/* |
55357
6c8d5d0e1be4
8224184: jshell got IOException at exiting with AIX
tnakamura
parents:
52938
diff
changeset
|
2 |
* Copyright (c) 2002-2019, the original author or authors. |
52938 | 3 |
* |
4 |
* This software is distributable under the BSD license. See the terms of the |
|
5 |
* BSD license in the documentation provided with this software. |
|
6 |
* |
|
7 |
* http://www.opensource.org/licenses/bsd-license.php |
|
8 |
*/ |
|
9 |
package jdk.internal.org.jline.terminal.impl; |
|
10 |
||
11 |
import java.io.FileInputStream; |
|
12 |
import java.io.FileOutputStream; |
|
13 |
import java.io.IOException; |
|
14 |
import java.io.InputStream; |
|
15 |
import java.io.FileDescriptor; |
|
16 |
import java.io.OutputStream; |
|
17 |
import java.util.ArrayList; |
|
18 |
import java.util.List; |
|
19 |
import java.util.regex.Matcher; |
|
20 |
import java.util.regex.Pattern; |
|
21 |
||
22 |
import jdk.internal.org.jline.terminal.Attributes; |
|
23 |
import jdk.internal.org.jline.terminal.Attributes.ControlChar; |
|
24 |
import jdk.internal.org.jline.terminal.Attributes.ControlFlag; |
|
25 |
import jdk.internal.org.jline.terminal.Attributes.InputFlag; |
|
26 |
import jdk.internal.org.jline.terminal.Attributes.LocalFlag; |
|
27 |
import jdk.internal.org.jline.terminal.Attributes.OutputFlag; |
|
28 |
import jdk.internal.org.jline.terminal.Size; |
|
29 |
import jdk.internal.org.jline.terminal.spi.Pty; |
|
30 |
import jdk.internal.org.jline.utils.OSUtils; |
|
31 |
||
32 |
import static jdk.internal.org.jline.utils.ExecHelper.exec; |
|
33 |
||
34 |
public class ExecPty extends AbstractPty implements Pty { |
|
35 |
||
36 |
private final String name; |
|
37 |
private final boolean system; |
|
38 |
||
39 |
public static Pty current() throws IOException { |
|
40 |
try { |
|
41 |
String result = exec(true, OSUtils.TTY_COMMAND); |
|
42 |
return new ExecPty(result.trim(), true); |
|
43 |
} catch (IOException e) { |
|
44 |
throw new IOException("Not a tty", e); |
|
45 |
} |
|
46 |
} |
|
47 |
||
48 |
protected ExecPty(String name, boolean system) { |
|
49 |
this.name = name; |
|
50 |
this.system = system; |
|
51 |
} |
|
52 |
||
53 |
@Override |
|
54 |
public void close() throws IOException { |
|
55 |
} |
|
56 |
||
57 |
public String getName() { |
|
58 |
return name; |
|
59 |
} |
|
60 |
||
61 |
@Override |
|
62 |
public InputStream getMasterInput() { |
|
63 |
throw new UnsupportedOperationException(); |
|
64 |
} |
|
65 |
||
66 |
@Override |
|
67 |
public OutputStream getMasterOutput() { |
|
68 |
throw new UnsupportedOperationException(); |
|
69 |
} |
|
70 |
||
71 |
@Override |
|
72 |
protected InputStream doGetSlaveInput() throws IOException { |
|
73 |
return system |
|
74 |
? new FileInputStream(FileDescriptor.in) |
|
75 |
: new FileInputStream(getName()); |
|
76 |
} |
|
77 |
||
78 |
@Override |
|
79 |
public OutputStream getSlaveOutput() throws IOException { |
|
80 |
return system |
|
81 |
? new FileOutputStream(FileDescriptor.out) |
|
82 |
: new FileOutputStream(getName()); |
|
83 |
} |
|
84 |
||
85 |
@Override |
|
86 |
public Attributes getAttr() throws IOException { |
|
87 |
String cfg = doGetConfig(); |
|
88 |
return doGetAttr(cfg); |
|
89 |
} |
|
90 |
||
91 |
@Override |
|
92 |
protected void doSetAttr(Attributes attr) throws IOException { |
|
93 |
List<String> commands = getFlagsToSet(attr, getAttr()); |
|
94 |
if (!commands.isEmpty()) { |
|
95 |
commands.add(0, OSUtils.STTY_COMMAND); |
|
96 |
if (!system) { |
|
97 |
commands.add(1, OSUtils.STTY_F_OPTION); |
|
98 |
commands.add(2, getName()); |
|
99 |
} |
|
100 |
try { |
|
101 |
exec(system, commands.toArray(new String[commands.size()])); |
|
102 |
} catch (IOException e) { |
|
103 |
// Handle partial failures with GNU stty, see #97 |
|
104 |
if (e.toString().contains("unable to perform all requested operations")) { |
|
105 |
commands = getFlagsToSet(attr, getAttr()); |
|
106 |
if (!commands.isEmpty()) { |
|
107 |
throw new IOException("Could not set the following flags: " + String.join(", ", commands), e); |
|
108 |
} |
|
109 |
} else { |
|
110 |
throw e; |
|
111 |
} |
|
112 |
} |
|
113 |
} |
|
114 |
} |
|
115 |
||
116 |
protected List<String> getFlagsToSet(Attributes attr, Attributes current) { |
|
117 |
List<String> commands = new ArrayList<>(); |
|
118 |
for (InputFlag flag : InputFlag.values()) { |
|
119 |
if (attr.getInputFlag(flag) != current.getInputFlag(flag)) { |
|
120 |
commands.add((attr.getInputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase()); |
|
121 |
} |
|
122 |
} |
|
123 |
for (OutputFlag flag : OutputFlag.values()) { |
|
124 |
if (attr.getOutputFlag(flag) != current.getOutputFlag(flag)) { |
|
125 |
commands.add((attr.getOutputFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase()); |
|
126 |
} |
|
127 |
} |
|
128 |
for (ControlFlag flag : ControlFlag.values()) { |
|
129 |
if (attr.getControlFlag(flag) != current.getControlFlag(flag)) { |
|
130 |
commands.add((attr.getControlFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase()); |
|
131 |
} |
|
132 |
} |
|
133 |
for (LocalFlag flag : LocalFlag.values()) { |
|
134 |
if (attr.getLocalFlag(flag) != current.getLocalFlag(flag)) { |
|
135 |
commands.add((attr.getLocalFlag(flag) ? flag.name() : "-" + flag.name()).toLowerCase()); |
|
136 |
} |
|
137 |
} |
|
138 |
String undef = System.getProperty("os.name").toLowerCase().startsWith("hp") ? "^-" : "undef"; |
|
139 |
for (ControlChar cchar : ControlChar.values()) { |
|
140 |
if (attr.getControlChar(cchar) != current.getControlChar(cchar)) { |
|
141 |
String str = ""; |
|
142 |
int v = attr.getControlChar(cchar); |
|
55357
6c8d5d0e1be4
8224184: jshell got IOException at exiting with AIX
tnakamura
parents:
52938
diff
changeset
|
143 |
if (v == -1) { // Skip if ControlChar is <UNDEF> |
6c8d5d0e1be4
8224184: jshell got IOException at exiting with AIX
tnakamura
parents:
52938
diff
changeset
|
144 |
continue; |
6c8d5d0e1be4
8224184: jshell got IOException at exiting with AIX
tnakamura
parents:
52938
diff
changeset
|
145 |
} |
52938 | 146 |
commands.add(cchar.name().toLowerCase().substring(1)); |
147 |
if (cchar == ControlChar.VMIN || cchar == ControlChar.VTIME) { |
|
148 |
commands.add(Integer.toBinaryString(v)); |
|
149 |
} |
|
150 |
else if (v == 0) { |
|
151 |
commands.add(undef); |
|
152 |
} |
|
153 |
else { |
|
154 |
if (v >= 128) { |
|
155 |
v -= 128; |
|
156 |
str += "M-"; |
|
157 |
} |
|
158 |
if (v < 32 || v == 127) { |
|
159 |
v ^= 0x40; |
|
160 |
str += "^"; |
|
161 |
} |
|
162 |
str += (char) v; |
|
163 |
commands.add(str); |
|
164 |
} |
|
165 |
} |
|
166 |
} |
|
167 |
return commands; |
|
168 |
} |
|
169 |
||
170 |
@Override |
|
171 |
public Size getSize() throws IOException { |
|
172 |
String cfg = doGetConfig(); |
|
173 |
return doGetSize(cfg); |
|
174 |
} |
|
175 |
||
176 |
protected String doGetConfig() throws IOException { |
|
177 |
return system |
|
178 |
? exec(true, OSUtils.STTY_COMMAND, "-a") |
|
179 |
: exec(false, OSUtils.STTY_COMMAND, OSUtils.STTY_F_OPTION, getName(), "-a"); |
|
180 |
} |
|
181 |
||
182 |
static Attributes doGetAttr(String cfg) throws IOException { |
|
183 |
Attributes attributes = new Attributes(); |
|
184 |
for (InputFlag flag : InputFlag.values()) { |
|
185 |
Boolean value = doGetFlag(cfg, flag); |
|
186 |
if (value != null) { |
|
187 |
attributes.setInputFlag(flag, value); |
|
188 |
} |
|
189 |
} |
|
190 |
for (OutputFlag flag : OutputFlag.values()) { |
|
191 |
Boolean value = doGetFlag(cfg, flag); |
|
192 |
if (value != null) { |
|
193 |
attributes.setOutputFlag(flag, value); |
|
194 |
} |
|
195 |
} |
|
196 |
for (ControlFlag flag : ControlFlag.values()) { |
|
197 |
Boolean value = doGetFlag(cfg, flag); |
|
198 |
if (value != null) { |
|
199 |
attributes.setControlFlag(flag, value); |
|
200 |
} |
|
201 |
} |
|
202 |
for (LocalFlag flag : LocalFlag.values()) { |
|
203 |
Boolean value = doGetFlag(cfg, flag); |
|
204 |
if (value != null) { |
|
205 |
attributes.setLocalFlag(flag, value); |
|
206 |
} |
|
207 |
} |
|
208 |
for (ControlChar cchar : ControlChar.values()) { |
|
209 |
String name = cchar.name().toLowerCase().substring(1); |
|
210 |
if ("reprint".endsWith(name)) { |
|
211 |
name = "(?:reprint|rprnt)"; |
|
212 |
} |
|
213 |
Matcher matcher = Pattern.compile("[\\s;]" + name + "\\s*=\\s*(.+?)[\\s;]").matcher(cfg); |
|
214 |
if (matcher.find()) { |
|
215 |
attributes.setControlChar(cchar, parseControlChar(matcher.group(1).toUpperCase())); |
|
216 |
} |
|
217 |
} |
|
218 |
return attributes; |
|
219 |
} |
|
220 |
||
221 |
private static Boolean doGetFlag(String cfg, Enum<?> flag) { |
|
222 |
Matcher matcher = Pattern.compile("(?:^|[\\s;])(\\-?" + flag.name().toLowerCase() + ")(?:[\\s;]|$)").matcher(cfg); |
|
223 |
return matcher.find() ? !matcher.group(1).startsWith("-") : null; |
|
224 |
} |
|
225 |
||
226 |
static int parseControlChar(String str) { |
|
227 |
// undef |
|
228 |
if ("<UNDEF>".equals(str)) { |
|
229 |
return -1; |
|
230 |
} |
|
231 |
// del |
|
232 |
if ("DEL".equalsIgnoreCase(str)) { |
|
233 |
return 127; |
|
234 |
} |
|
235 |
// octal |
|
236 |
if (str.charAt(0) == '0') { |
|
237 |
return Integer.parseInt(str, 8); |
|
238 |
} |
|
239 |
// decimal |
|
240 |
if (str.charAt(0) >= '1' && str.charAt(0) <= '9') { |
|
241 |
return Integer.parseInt(str, 10); |
|
242 |
} |
|
243 |
// control char |
|
244 |
if (str.charAt(0) == '^') { |
|
245 |
if (str.charAt(1) == '?') { |
|
246 |
return 127; |
|
247 |
} else { |
|
248 |
return str.charAt(1) - 64; |
|
249 |
} |
|
250 |
} else if (str.charAt(0) == 'M' && str.charAt(1) == '-') { |
|
251 |
if (str.charAt(2) == '^') { |
|
252 |
if (str.charAt(3) == '?') { |
|
253 |
return 127 + 128; |
|
254 |
} else { |
|
255 |
return str.charAt(3) - 64 + 128; |
|
256 |
} |
|
257 |
} else { |
|
258 |
return str.charAt(2) + 128; |
|
259 |
} |
|
260 |
} else { |
|
261 |
return str.charAt(0); |
|
262 |
} |
|
263 |
} |
|
264 |
||
265 |
static Size doGetSize(String cfg) throws IOException { |
|
266 |
return new Size(doGetInt("columns", cfg), doGetInt("rows", cfg)); |
|
267 |
} |
|
268 |
||
269 |
static int doGetInt(String name, String cfg) throws IOException { |
|
270 |
String[] patterns = new String[] { |
|
271 |
"\\b([0-9]+)\\s+" + name + "\\b", |
|
272 |
"\\b" + name + "\\s+([0-9]+)\\b", |
|
273 |
"\\b" + name + "\\s*=\\s*([0-9]+)\\b" |
|
274 |
}; |
|
275 |
for (String pattern : patterns) { |
|
276 |
Matcher matcher = Pattern.compile(pattern).matcher(cfg); |
|
277 |
if (matcher.find()) { |
|
278 |
return Integer.parseInt(matcher.group(1)); |
|
279 |
} |
|
280 |
} |
|
281 |
throw new IOException("Unable to parse " + name); |
|
282 |
} |
|
283 |
||
284 |
@Override |
|
285 |
public void setSize(Size size) throws IOException { |
|
286 |
if (system) { |
|
287 |
exec(true, |
|
288 |
OSUtils.STTY_COMMAND, |
|
289 |
"columns", Integer.toString(size.getColumns()), |
|
290 |
"rows", Integer.toString(size.getRows())); |
|
291 |
} else { |
|
292 |
exec(false, |
|
293 |
OSUtils.STTY_COMMAND, |
|
294 |
OSUtils.STTY_F_OPTION, getName(), |
|
295 |
"columns", Integer.toString(size.getColumns()), |
|
296 |
"rows", Integer.toString(size.getRows())); |
|
297 |
} |
|
298 |
} |
|
299 |
||
300 |
@Override |
|
301 |
public String toString() { |
|
302 |
return "ExecPty[" + getName() + (system ? ", system]" : "]"); |
|
303 |
} |
|
304 |
||
305 |
} |