unison

Fork of Unison, a bi-directional file synchronization tool
git clone git://git.laack.co/unison.git
Log | Files | Refs | README | LICENSE

pty.c (10663B)


      1 /* Stub code for controlling terminals. */
      2 
      3 #ifdef _WIN32
      4 
      5 #define WINVER 0x0600
      6 #define _WIN32_WINNT 0x0600
      7 
      8 #ifndef UNICODE
      9 #define UNICODE
     10 #endif
     11 
     12 #endif /* _WIN32 */
     13 
     14 #include <caml/mlvalues.h>
     15 #include <caml/alloc.h>    // alloc_tuple
     16 #include <caml/memory.h>   // Store_field
     17 #include <caml/fail.h>     // failwith
     18 #include <caml/unixsupport.h> // uerror, unix_error
     19 #include <errno.h>         // ENOSYS
     20 #include <caml/version.h>
     21 #if OCAML_VERSION < 41300
     22 #define CAML_INTERNALS /* was needed from OCaml 4.06 to 4.12 */
     23 #endif
     24 #include <caml/osdeps.h>
     25 
     26 #if OCAML_VERSION_MAJOR < 5
     27 #define caml_unix_error unix_error
     28 #define caml_uerror uerror
     29 #define caml_win32_maperr win32_maperr
     30 #define caml_win32_alloc_handle win_alloc_handle
     31 #endif
     32 
     33 // openpty
     34 #if defined(__linux) || defined(__CYGWIN__)
     35 #include <pty.h>
     36 #define HAS_OPENPTY 1
     37 #endif
     38 
     39 #if defined(__APPLE__) || defined(__NetBSD__)
     40 #include <util.h>
     41 #define HAS_OPENPTY 1
     42 #endif
     43 
     44 #ifdef __FreeBSD__
     45 #include <sys/types.h>
     46 #include <libutil.h>
     47 #define HAS_OPENPTY 1
     48 #endif
     49 
     50 #ifdef HAS_OPENPTY
     51 
     52 #include <sys/ioctl.h>
     53 #include <sys/types.h>
     54 
     55 CAMLprim value setControllingTerminal(value fdVal) {
     56   CAMLparam1(fdVal);
     57 #if defined(__CYGWIN__)
     58   /* [Unix.setsid] is not implemented on Cygwin; call it here instead */
     59   if (setsid() < 0) caml_uerror("setsid", Nothing);
     60 #endif
     61   int fd = Int_val(fdVal);
     62   if (ioctl(fd, TIOCSCTTY, (char *) 0) < 0)
     63     caml_uerror("ioctl", (value) 0);
     64   CAMLreturn(Val_unit);
     65 }
     66 
     67 /* c_openpty: unit -> (int * Unix.file_descr) */
     68 CAMLprim value c_openpty(value unit) {
     69   CAMLparam0();
     70   CAMLlocal1(pair);
     71   int master, slave;
     72   if (openpty(&master,&slave,NULL,NULL,NULL) < 0)
     73     caml_uerror("openpty", (value) 0);
     74   pair = caml_alloc_tuple(2);
     75   Store_field(pair,0,Val_int(master));
     76   Store_field(pair,1,Val_int(slave));
     77   CAMLreturn(pair);
     78 }
     79 
     80 #else // not HAS_OPENPTY
     81 
     82 CAMLprim value setControllingTerminal(value fdVal) {
     83   caml_unix_error(ENOSYS, "setControllingTerminal", Nothing);
     84 }
     85 
     86 CAMLprim value c_openpty(value unit) {
     87   caml_unix_error(ENOSYS, "openpty", Nothing);
     88 }
     89 
     90 #endif
     91 
     92 #ifdef _WIN32
     93 
     94 #include <windows.h>
     95 
     96 #define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
     97 
     98 typedef VOID* HPCON;
     99 
    100 typedef HRESULT (WINAPI *sCreatePseudoConsole)
    101           (COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
    102 
    103 typedef void (WINAPI *sClosePseudoConsole) (HPCON hPC);
    104 
    105 sCreatePseudoConsole pCreatePseudoConsole;
    106 sClosePseudoConsole pClosePseudoConsole;
    107 
    108 CAMLprim value win_openpty(value unit)
    109 {
    110   CAMLparam0();
    111   CAMLlocal4(tup, tmp1, tmp2, tmp3);
    112   HPCON pty;
    113   HANDLE i1, i2, o1, o2;
    114   COORD size = { .X = 80, .Y = 25 };
    115 
    116   HMODULE kernel32_module = GetModuleHandleW(L"kernel32.dll");
    117   if (kernel32_module == NULL) {
    118     caml_unix_error(ENOSYS, "openpty", Nothing);
    119   }
    120 
    121   /* This is the only way to use the new API while remaining compatible
    122    * with older Windows versions. */
    123   pCreatePseudoConsole = (sCreatePseudoConsole)
    124       GetProcAddress(kernel32_module, "CreatePseudoConsole");
    125   if (pCreatePseudoConsole == NULL) {
    126     caml_unix_error(ENOSYS, "openpty", Nothing);
    127   }
    128 
    129   /* Read-write pipes don't seem to work well with PTY and cause deadlocks.
    130    * socketpair() is not supported by Windows and emulation code is just too
    131    * much to carry around (also, emulation by PF_INET sockets is a bit messy;
    132    * emulation by PF_UNIX sockets is only supported starting Windows 10 1803).
    133    * Simpler to use two separate pipes then. */
    134   if (!CreatePipe(&i1, &o1, NULL, 0)) {
    135     caml_win32_maperr(GetLastError());
    136     caml_uerror("openpty", Nothing);
    137   }
    138   if (!CreatePipe(&i2, &o2, NULL, 0)) {
    139     caml_win32_maperr(GetLastError());
    140     if (o1 != INVALID_HANDLE_VALUE) CloseHandle(o1);
    141     if (i1 != INVALID_HANDLE_VALUE) CloseHandle(i1);
    142     caml_uerror("openpty", Nothing);
    143   }
    144 
    145   if (pCreatePseudoConsole(size, i1, o2, 0, &pty) != S_OK) {
    146     caml_win32_maperr(GetLastError());
    147     if (o1 != INVALID_HANDLE_VALUE) CloseHandle(o1);
    148     if (i1 != INVALID_HANDLE_VALUE) CloseHandle(i1);
    149     if (o2 != INVALID_HANDLE_VALUE) CloseHandle(o2);
    150     if (i2 != INVALID_HANDLE_VALUE) CloseHandle(i2);
    151     caml_uerror("openpty", Nothing);
    152   }
    153 
    154   tmp1 = caml_alloc_tuple(2);
    155   Store_field(tmp1, 0, caml_win32_alloc_handle(i2));
    156   Store_field(tmp1, 1, caml_win32_alloc_handle(o1));
    157 
    158   tmp2 = caml_alloc(1, Abstract_tag);
    159   *((HPCON *) Data_abstract_val(tmp2)) = pty;
    160 
    161   tmp3 = caml_alloc_tuple(2);
    162   Store_field(tmp3, 0, caml_win32_alloc_handle(i1));
    163   Store_field(tmp3, 1, caml_win32_alloc_handle(o2));
    164 
    165   tup = caml_alloc_tuple(3);
    166   Store_field(tup, 0, tmp1);
    167   Store_field(tup, 1, tmp2);
    168   Store_field(tup, 2, tmp3);
    169 
    170   CAMLreturn(tup);
    171 }
    172 
    173 CAMLprim value win_closepty(value pty)
    174 {
    175   CAMLparam1(pty);
    176 
    177   HMODULE kernel32_module = GetModuleHandleW(L"kernel32.dll");
    178   if (kernel32_module == NULL) {
    179     caml_unix_error(ENOSYS, "closepty", Nothing);
    180   }
    181 
    182   /* This is the only way to use the new API while remaining compatible
    183    * with older Windows versions. */
    184   pClosePseudoConsole = (sClosePseudoConsole)
    185       GetProcAddress(kernel32_module, "ClosePseudoConsole");
    186   if (pClosePseudoConsole == NULL) {
    187     caml_unix_error(ENOSYS, "closepty", Nothing);
    188   }
    189 
    190   pClosePseudoConsole(*((HPCON *) Data_abstract_val(pty)));
    191 
    192   CAMLreturn(Val_unit);
    193 }
    194 
    195 static void prepareSiWithPty(value prog, STARTUPINFOEX *si, HPCON pty)
    196 {
    197 #ifndef PROC_THREAD_ATTRIBUTE_HANDLE_LIST
    198   caml_unix_error(ENOSYS, "create_process_pty", prog);
    199 #else
    200   SIZE_T size;
    201 
    202   ZeroMemory(si, sizeof(*si));
    203   si->StartupInfo.cb = sizeof(STARTUPINFOEX);
    204 
    205   /* 2 attributes, as PROC_THREAD_ATTRIBUTE_HANDLE_LIST will be added later. */
    206   InitializeProcThreadAttributeList(NULL, 2, 0, &size);
    207   si->lpAttributeList = malloc(size);
    208   if (!si->lpAttributeList) {
    209     caml_unix_error(ENOMEM, "create_process_pty", prog);
    210   }
    211 
    212   if (!InitializeProcThreadAttributeList(si->lpAttributeList, 2, 0, &size)) {
    213     caml_win32_maperr(GetLastError());
    214     free(si->lpAttributeList);
    215     caml_uerror("create_process_pty", prog);
    216   }
    217 
    218   if (!UpdateProcThreadAttribute(si->lpAttributeList, 0,
    219       PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pty, sizeof(pty), NULL, NULL)) {
    220     caml_win32_maperr(GetLastError());
    221     DeleteProcThreadAttributeList(si->lpAttributeList);
    222     free(si->lpAttributeList);
    223     caml_uerror("create_process_pty", prog);
    224   }
    225 #endif
    226 }
    227 
    228 CAMLprim value w_create_process_pty
    229   (value prog, value args, value pty, value fd1, value fd2)
    230 {
    231   CAMLparam5(prog, args, pty, fd1, fd2);
    232   int res, flags;
    233   BOOL err = FALSE;
    234   PROCESS_INFORMATION pi;
    235   STARTUPINFOEX si;
    236   wchar_t fullname[MAX_PATH];
    237   wchar_t *wprog, *wargs;
    238   HANDLE hp;
    239   HANDLE inherit[2];
    240   HPCON hpc = *((HPCON *) Data_abstract_val(pty));
    241 
    242 #ifndef PROC_THREAD_ATTRIBUTE_HANDLE_LIST
    243   caml_unix_error(ENOSYS, "create_process_pty", prog);
    244 #else
    245   wprog = caml_stat_strdup_to_utf16(String_val(prog));
    246 
    247   res = SearchPathW(NULL, wprog, L".exe", MAX_PATH, fullname, NULL);
    248   caml_stat_free(wprog);
    249   if (res == 0) {
    250     caml_win32_maperr(GetLastError());
    251     caml_uerror("create_process_pty", prog);
    252   }
    253 
    254   prepareSiWithPty(prog, &si, hpc);
    255 
    256   hp = GetCurrentProcess();
    257   if (!DuplicateHandle(hp, Handle_val(fd1), hp, &(si.StartupInfo.hStdInput),
    258       0, TRUE, DUPLICATE_SAME_ACCESS)) {
    259     caml_win32_maperr(GetLastError());
    260     err = TRUE;
    261     goto clean1;
    262   }
    263   if (!DuplicateHandle(hp, Handle_val(fd2), hp, &(si.StartupInfo.hStdOutput),
    264       0, TRUE, DUPLICATE_SAME_ACCESS)) {
    265     caml_win32_maperr(GetLastError());
    266     err = TRUE;
    267     goto clean2;
    268   }
    269   /* hStdError is set to NULL because of Cygwin-linked child processes
    270      otherwise not detecting the controlling terminal. This would not be
    271      relevant for a native Windows binary but it may happen that Windows users
    272      have Cygwin or MSYS2 (or Git for Windows) in path before Windows system
    273      dirs, so the ssh client ends up being a Cygwin/MSYS2 binary. If hStdError
    274      is not set to NULL then a Cygwin/MSYS2 ssh executed with a Windows pty
    275      will not have functions that require a controlling terminal (such as
    276      interactive auth). */
    277   si.StartupInfo.hStdError = NULL;
    278   si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
    279 
    280   inherit[0] = si.StartupInfo.hStdInput;
    281   inherit[1] = si.StartupInfo.hStdOutput;
    282   if (!UpdateProcThreadAttribute(si.lpAttributeList, 0,
    283       PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherit, sizeof(HANDLE) * 2,
    284       NULL, NULL)) {
    285     caml_win32_maperr(GetLastError());
    286     err = TRUE;
    287     goto clean4;
    288   }
    289 
    290   flags = GetPriorityClass(GetCurrentProcess());
    291   flags |= EXTENDED_STARTUPINFO_PRESENT;
    292 
    293   wargs = caml_stat_strdup_to_utf16(String_val(args));
    294   res = CreateProcessW(fullname, wargs, NULL, NULL, TRUE, flags,
    295           NULL, NULL, &si.StartupInfo, &pi);
    296   caml_stat_free(wargs);
    297   if (res == 0) {
    298     caml_win32_maperr(GetLastError());
    299     err = TRUE;
    300   }
    301 
    302 clean4:
    303 clean3:
    304   CloseHandle(si.StartupInfo.hStdOutput);
    305 clean2:
    306   CloseHandle(si.StartupInfo.hStdInput);
    307 clean1:
    308   DeleteProcThreadAttributeList(si.lpAttributeList);
    309   free(si.lpAttributeList);
    310 
    311   if (err) {
    312     caml_uerror("create_process_pty", prog);
    313   }
    314 
    315   CloseHandle(pi.hThread);
    316   CAMLreturn(Val_long(pi.hProcess));
    317 #endif
    318 }
    319 
    320 CAMLprim value win_alloc_console(value unit)
    321 {
    322   CAMLparam0();
    323   CAMLlocal1(some);
    324   HANDLE stderr_orig;
    325   FILE *ign;
    326 
    327   stderr_orig = (HANDLE) GetStdHandle(STD_ERROR_HANDLE);
    328 
    329   if (!AllocConsole()) {
    330     caml_win32_maperr(GetLastError());
    331     caml_uerror("alloc_console", Nothing);
    332   }
    333 
    334   /* If a new console was allocated then we need to make sure that both the
    335    * Windows C runtime stderr and the STD_ERROR_HANDLE for the child process
    336    * are associated with the new console, unless already redirected or
    337    * associated elsewhere.
    338    * We are not interested in stdin and stdout for this specific scenario
    339    * (as a fallback for the real pty). */
    340   if (_fileno(stderr) < 0) freopen_s(&ign, "CONOUT$", "w", stderr);
    341 
    342   if (!GetFileType(stderr_orig)) {
    343     some = caml_alloc(1, 0);
    344     Store_field(some, 0,
    345       caml_win32_alloc_handle((HANDLE) GetStdHandle(STD_ERROR_HANDLE)));
    346     CAMLreturn(some);
    347   }
    348 
    349   CAMLreturn(Val_int(0));
    350 }
    351 
    352 #else // not _WIN32
    353 
    354 CAMLprim value w_create_process_pty_native
    355   (value prog, value args, value pty, value fd1, value fd2, value fd3)
    356 {
    357   caml_unix_error(ENOSYS, "create_process_pty", Nothing);
    358 }
    359 
    360 CAMLprim value w_create_process_pty(value *argv, int argn)
    361 {
    362   caml_unix_error(ENOSYS, "create_process_pty", Nothing);
    363 }
    364 
    365 CAMLprim value win_openpty()
    366 {
    367   caml_unix_error(ENOSYS, "openpty", Nothing);
    368 }
    369 
    370 CAMLprim value win_closepty(value pty)
    371 {
    372   caml_unix_error(ENOSYS, "closepty", Nothing);
    373 }
    374 
    375 CAMLprim value win_alloc_console()
    376 {
    377   caml_unix_error(ENOSYS, "alloc_console", Nothing);
    378 }
    379 
    380 #endif