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