1 /*
2  * JPty - A small PTY interface for Java.
3  *
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *    http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21 package com.pty4j.unix;
22 
23 import com.pty4j.PtyProcess;
24 import com.pty4j.WinSize;
25 import com.pty4j.util.LazyValue;
26 import com.pty4j.util.PtyUtil;
27 import com.sun.jna.Native;
28 import com.sun.jna.Platform;
29 import jtermios.JTermios;
30 import jtermios.Termios;
31 import org.apache.log4j.Logger;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34 
35 import java.io.File;
36 
37 /**
38  * Provides access to the pseudoterminal functionality on POSIX(-like) systems,
39  * emulating such system calls on non POSIX systems.
40  */
41 public class PtyHelpers {
42   private static final Logger LOG = Logger.getLogger(PtyHelpers.class);
43 
44   /**
45    * Provides a OS-specific interface to the PtyHelpers methods.
46    */
47   public interface OSFacade {
48     /**
49      * Terminates or signals the process with the given PID.
50      *
51      * @param pid the process ID to terminate or signal;
52      * @param sig the signal number to send, for example, 9 to terminate the
53      *            process.
54      * @return a value of <tt>0</tt> upon success, or a non-zero value in case
55      *         of an error (see {@link PtyHelpers#errno()} for details).
56      */
kill(int pid, int sig)57     int kill(int pid, int sig);
58 
59     /**
60      * Waits until the process with the given PID is stopped.
61      *
62      * @param pid     the PID of the process to wait for;
63      * @param stat    the array in which the result code of the process will be
64      *                stored;
65      * @param options the options for waitpid (not used at the moment).
66      * @return 0 upon success, -1 upon failure (see {@link PtyHelpers#errno()} for
67      *         details).
68      */
waitpid(int pid, int[] stat, int options)69     int waitpid(int pid, int[] stat, int options);
70 
sigprocmask(int how, com.sun.jna.ptr.IntByReference set, com.sun.jna.ptr.IntByReference oldset)71     int sigprocmask(int how, com.sun.jna.ptr.IntByReference set, com.sun.jna.ptr.IntByReference oldset);
72 
strerror(int errno)73     String strerror(int errno);
74 
getpt()75     int getpt(); //getpt
76 
grantpt(int fdm)77     int grantpt(int fdm);
78 
unlockpt(int fdm)79     int unlockpt(int fdm);
80 
close(int fdm)81     int close(int fdm);
82 
ptsname(int fdm)83     String ptsname(int fdm);
84 
killpg(int pid, int sig)85     int killpg(int pid, int sig);
86 
fork()87     int fork();
88 
pipe(int[] pipe2)89     int pipe(int[] pipe2);
90 
setsid()91     int setsid();
92 
getpid()93     int getpid();
94 
setpgid(int pid, int pgid)95     int setpgid(int pid, int pgid);
96 
dup2(int fds, int fileno)97     void dup2(int fds, int fileno);
98 
getppid()99     int getppid();
100 
unsetenv(String s)101     void unsetenv(String s);
102 
login_tty(int fd)103     int login_tty(int fd);
104 
chdir(String dirpath)105     void chdir(String dirpath);
106 
tcdrain(int fd)107     default int tcdrain(int fd) {
108       return JTermios.tcdrain(fd);
109     }
110 
open(String path, int mode)111     default int open(String path, int mode) {
112       return JTermios.open(path, mode);
113     }
114 
read(int fd, byte[] buffer, int len)115     default int read(int fd, byte[] buffer, int len) {
116       return JTermios.read(fd, buffer, len);
117     }
118 
errno()119     default int errno() {
120       return JTermios.errno();
121     }
122 
tcgetattr(int fd, TerminalSettings settings)123     default int tcgetattr(int fd, TerminalSettings settings) {
124       Termios termios = new Termios();
125       int result = JTermios.tcgetattr(fd, termios);
126       fillTerminalSettings(settings, termios);
127       return result;
128     }
129 
tcsetattr(int fd, int opt, TerminalSettings settings)130     default int tcsetattr(int fd, int opt, TerminalSettings settings) {
131       Termios termios = convertToTermios(settings);
132       return JTermios.tcsetattr(fd, opt, termios);
133     }
134   }
135 
136   public static class TerminalSettings {
137     public int c_iflag;
138     public int c_oflag;
139     public int c_cflag;
140     public int c_lflag;
141     public byte[] c_cc = new byte[20];
142     public int c_ispeed;
143     public int c_ospeed;
144   }
145 
convertToTermios(TerminalSettings settings)146   private static Termios convertToTermios(TerminalSettings settings) {
147     Termios result = new Termios();
148     result.c_iflag = settings.c_iflag;
149     result.c_oflag = settings.c_oflag;
150     result.c_cflag = settings.c_cflag;
151     result.c_lflag = settings.c_lflag;
152     System.arraycopy(settings.c_cc, 0, result.c_cc, 0, settings.c_cc.length);
153     result.c_ispeed = settings.c_ispeed;
154     result.c_ospeed = settings.c_ospeed;
155     return result;
156   }
157 
fillTerminalSettings(TerminalSettings settings, Termios termios)158   private static void fillTerminalSettings(TerminalSettings settings, Termios termios) {
159     settings.c_iflag = termios.c_iflag;
160     settings.c_oflag = termios.c_oflag;
161     settings.c_cflag = termios.c_cflag;
162     settings.c_lflag = termios.c_lflag;
163     System.arraycopy(termios.c_cc, 0, settings.c_cc, 0, termios.c_cc.length);
164     settings.c_ispeed = termios.c_ispeed;
165     settings.c_ospeed = termios.c_ospeed;
166   }
167 
168   // CONSTANTS
169 
170   public static int ONLCR = 0x04;
171 
172   public static int VINTR = 0;
173   public static int VQUIT = 1;
174   public static int VERASE = 2;
175   public static int VKILL = 3;
176   public static int VSUSP = 10;
177   public static int VREPRINT = 12;
178   public static int VWERASE = 14;
179 
180   public static int ECHOCTL = 0x1000;
181   public static int ECHOKE = 0x4000;
182   public static int ECHOK = 0x00000004;
183 
184   public static int IMAXBEL = 0x00002000;
185   public static int HUPCL = 0x00004000;
186 
187   public static int IUTF8 = 0x00004000;
188 
189   public static int O_NOCTTY = JTermios.O_NOCTTY;
190   public static int O_RDWR = JTermios.O_RDWR;
191   public static int TCSANOW = JTermios.TCSANOW;
192 
193   private static final int STDIN_FILENO = 0;
194   private static final int STDOUT_FILENO = 1;
195   private static final int STDERR_FILENO = 2;
196 
197 
198   /*
199  * Flags for sigprocmask:
200  */
201   private static final int SIG_UNBLOCK = 2;
202 
203   public static int SIGHUP = 1;
204   public static int SIGINT = 2;
205   public static int SIGQUIT = 3;
206   public static int SIGILL = 4;
207   public static int SIGABORT = 6;
208   public static int SIGFPE = 8;
209   public static int SIGKILL = 9;
210   public static int SIGSEGV = 11;
211   public static int SIGPIPE = 13;
212   public static int SIGALRM = 14;
213   public static int SIGTERM = 15;
214   public static int SIGCHLD = 20;
215 
216   public static int WNOHANG = 1;
217   public static int WUNTRACED = 2;
218 
219   private static final LazyValue<OSFacade> OS_FACADE_VALUE = new LazyValue<>(() -> {
220     if (Platform.isMac()) {
221       return new com.pty4j.unix.macosx.OSFacadeImpl();
222     }
223     if (Platform.isFreeBSD()) {
224       return new com.pty4j.unix.freebsd.OSFacadeImpl();
225     }
226     if (Platform.isOpenBSD()) {
227       return new com.pty4j.unix.openbsd.OSFacadeImpl();
228     }
229     if (Platform.isLinux() || Platform.isAndroid()) {
230       return new com.pty4j.unix.linux.OSFacadeImpl();
231     }
232     if (Platform.isWindows()) {
233       throw new IllegalArgumentException("WinPtyProcess should be used on Windows");
234     }
235     throw new RuntimeException("Pty4J has no support for OS " + System.getProperty("os.name"));
236   });
237 
238   private static final LazyValue<PtyExecutor> PTY_EXECUTOR_VALUE = new LazyValue<>(() -> {
239     File lib = PtyUtil.resolveNativeLibrary();
240     return new NativePtyExecutor(lib.getAbsolutePath());
241   });
242 
243   static {
244     try {
getOsFacade()245       getOsFacade();
246     }
247     catch (Throwable t) {
248       LOG.error(t.getMessage(), t.getCause());
249     }
250     try {
getPtyExecutor()251       getPtyExecutor();
252     }
253     catch (Throwable t) {
254       LOG.error(t.getMessage(), t.getCause());
255     }
256   }
257 
258   @NotNull
getOsFacade()259   private static OSFacade getOsFacade() {
260     try {
261       return OS_FACADE_VALUE.getValue();
262     }
263     catch (Throwable t) {
264       throw new RuntimeException("Cannot load implementation of " + OSFacade.class, t);
265     }
266   }
267 
getPtyExecutor()268   @NotNull static PtyExecutor getPtyExecutor() {
269     try {
270       return PTY_EXECUTOR_VALUE.getValue();
271     }
272     catch (Throwable t) {
273       throw new RuntimeException("Cannot load native pty executor library", t);
274     }
275   }
276 
getInstance()277   public static OSFacade getInstance() {
278     return getOsFacade();
279   }
280 
createTermios()281   public static Termios createTermios() {
282     Termios term = new Termios();
283 
284     boolean isUTF8 = true;
285     term.c_iflag = JTermios.ICRNL | JTermios.IXON | JTermios.IXANY | IMAXBEL | JTermios.BRKINT | (isUTF8 ? IUTF8 : 0);
286     term.c_oflag = JTermios.OPOST | ONLCR;
287     term.c_cflag = JTermios.CREAD | JTermios.CS8 | HUPCL;
288     term.c_lflag = JTermios.ICANON | JTermios.ISIG | JTermios.IEXTEN | JTermios.ECHO | JTermios.ECHOE | ECHOK | ECHOKE | ECHOCTL;
289 
290     term.c_cc[JTermios.VEOF] = CTRLKEY('D');
291 //    term.c_cc[VEOL] = -1;
292 //    term.c_cc[VEOL2] = -1;
293     term.c_cc[VERASE] = 0x7f;           // DEL
294     term.c_cc[VWERASE] = CTRLKEY('W');
295     term.c_cc[VKILL] = CTRLKEY('U');
296     term.c_cc[VREPRINT] = CTRLKEY('R');
297     term.c_cc[VINTR] = CTRLKEY('C');
298     term.c_cc[VQUIT] = 0x1c;           // Control+backslash
299     term.c_cc[VSUSP] = CTRLKEY('Z');
300 //    term.c_cc[VDSUSP] = CTRLKEY('Y');
301     term.c_cc[JTermios.VSTART] = CTRLKEY('Q');
302     term.c_cc[JTermios.VSTOP] = CTRLKEY('S');
303 //    term.c_cc[VLNEXT] = CTRLKEY('V');
304 //    term.c_cc[VDISCARD] = CTRLKEY('O');
305 //    term.c_cc[VMIN] = 1;
306 //    term.c_cc[VTIME] = 0;
307 //    term.c_cc[VSTATUS] = CTRLKEY('T');
308 
309     term.c_ispeed = JTermios.B38400;
310     term.c_ospeed = JTermios.B38400;
311 
312     return term;
313   }
314 
CTRLKEY(char c)315   private static byte CTRLKEY(char c) {
316     return (byte)((byte)c - (byte)'A' + 1);
317   }
318 
__sigbits(int __signo)319   private static int __sigbits(int __signo) {
320     return __signo > 32 ? 0 : (1 << (__signo - 1));
321   }
322 
getWinSize(int fd, @Nullable PtyProcess process)323   public static @NotNull WinSize getWinSize(int fd, @Nullable PtyProcess process) throws UnixPtyException {
324     return getPtyExecutor().getWindowSize(fd, process);
325   }
326 
327   /**
328    * Tests whether the process with the given process ID is alive or terminated.
329    *
330    * @param pid the process-ID to test.
331    * @return <code>true</code> if the process with the given process ID is
332    *         alive, <code>false</code> if it is terminated.
333    */
isProcessAlive(int pid)334   public static boolean isProcessAlive(int pid) {
335     int[] stat = {-1};
336     int result = PtyHelpers.waitpid(pid, stat, WNOHANG);
337     return (result == 0) && (stat[0] < 0);
338   }
339 
340   /**
341    * Terminates or signals the process with the given PID.
342    *
343    * @param pid    the process ID to terminate or signal;
344    * @param signal the signal number to send, for example, 9 to terminate the
345    *               process.
346    * @return a value of <tt>0</tt> upon success, or a non-zero value in case of
347    *         an error.
348    */
signal(int pid, int signal)349   public static int signal(int pid, int signal) {
350     return getOsFacade().kill(pid, signal);
351   }
352 
353   /**
354    * Blocks and waits until the given PID either terminates, or receives a
355    * signal.
356    *
357    * @param pid     the process ID to wait for;
358    * @param stat    an array of 1 integer in which the status of the process is
359    *                stored;
360    * @param options the bit mask with options.
361    */
waitpid(int pid, int[] stat, int options)362   public static int waitpid(int pid, int[] stat, int options) {
363     return getOsFacade().waitpid(pid, stat, options);
364   }
365 
366   /**
367    * Returns the last known error.
368    *
369    * @return the last error number from the native system.
370    */
errno()371   public static int errno() {
372     return Native.getLastError();
373   }
374 
strerror()375   public static String strerror() {
376     return getOsFacade().strerror(errno());
377   }
378 
chdir(String dirpath)379   public static void chdir(String dirpath) {
380     getOsFacade().chdir(dirpath);
381   }
382 
execPty(String full_path, String[] argv, String[] envp, String dirpath, String pts_name, int fdm, String err_pts_name, int err_fdm, boolean console)383   public static int execPty(String full_path,
384                             String[] argv,
385                             String[] envp,
386                             String dirpath,
387                             String pts_name,
388                             int fdm,
389                             String err_pts_name,
390                             int err_fdm,
391                             boolean console) {
392     PtyExecutor executor = getPtyExecutor();
393     return executor.execPty(full_path, argv, envp, dirpath, pts_name, fdm, err_pts_name, err_fdm, console);
394   }
395 }
396