809641c3e68228382d941c78ae861063776e494c
[gnupg.git] / common / exechelp-w32ce.c
1 /* exechelp-w32.c - Fork and exec helpers for W32CE.
2  * Copyright (C) 2004, 2007, 2008, 2009,
3  *               2010 Free Software Foundation, Inc.
4  *
5  * This file is part of GnuPG.
6  *
7  * GnuPG is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * GnuPG is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include <config.h>
22
23 #if !defined(HAVE_W32_SYSTEM) && !defined (HAVE_W32CE_SYSTEM)
24 #error This code is only used on W32CE.
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <assert.h>
32 #ifdef HAVE_SIGNAL_H
33 # include <signal.h>
34 #endif
35 #include <unistd.h> 
36 #include <fcntl.h>
37
38 #ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth.  */
39 #undef HAVE_PTH
40 #undef USE_GNU_PTH
41 #endif
42
43 #ifdef USE_GNU_PTH      
44 #include <pth.h>
45 #endif
46
47 #ifdef HAVE_STAT
48 # include <sys/stat.h>
49 #endif
50
51 #include <assuan.h>
52
53 #include "util.h"
54 #include "i18n.h"
55 #include "sysutils.h"
56 #include "exechelp.h"
57
58
59 /* It seems Vista doesn't grok X_OK and so fails access() tests.
60    Previous versions interpreted X_OK as F_OK anyway, so we'll just
61    use F_OK directly. */
62 #undef X_OK
63 #define X_OK F_OK
64
65
66 /* We assume that a HANDLE can be represented by an int which should
67    be true for all i386 systems (HANDLE is defined as void *) and
68    these are the only systems for which Windows is available.  Further
69    we assume that -1 denotes an invalid handle.  */
70 #define fd_to_handle(a)  ((HANDLE)(a))
71 #define handle_to_fd(a)  ((int)(a))
72 #define pid_to_handle(a) ((HANDLE)(a))
73 #define handle_to_pid(a) ((int)(a))
74
75 \f
76 #ifdef USE_GNU_PTH      
77 /* The data passed to the feeder_thread.  */ 
78 struct feeder_thread_parms
79 {
80   estream_t stream;
81   int fd;
82   int direction;
83 };
84
85
86 /* The thread started by start_feeded.  */
87 static void *
88 feeder_thread (void *arg)
89 {
90   struct feeder_thread_parms *parm = arg;
91   char buffer[4096];
92
93   if (parm->direction)
94     {
95       size_t nread;
96       DWORD nwritten;
97
98       while (!es_read (parm->stream, buffer, sizeof buffer, &nread))
99         {
100           do
101             {
102               if (!WriteFile (fd_to_handle (parm->fd), 
103                               buffer, nread, &nwritten, NULL))
104                 {
105                   log_debug ("feeder(%d): WriteFile error: rc=%d\n",
106                              parm->fd, (int)GetLastError ());
107                   goto leave;
108                 }
109               nread -= nwritten;
110             }
111           while (nread);
112         }
113       if (nread)
114         log_debug ("feeder(%d): es_read error: %s\n",
115                    parm->fd, strerror (errno));
116     }
117   else
118     {
119       DWORD nread;
120       size_t nwritten;
121
122       while (ReadFile (fd_to_handle (parm->fd),
123                        buffer, sizeof buffer, &nread, NULL) && nread)
124         {
125           do 
126             {
127               if (es_write (parm->stream, buffer, nread, &nwritten))
128                 {
129                   log_debug ("feeder(%d): es_write error: %s\n",
130                              parm->fd, strerror (errno));
131                   goto leave;
132                 }
133               nread -= nwritten;
134             }
135           while (nread);
136         }
137       if (nread)
138         log_debug ("feeder(%d): ReadFile error: rc=%d\n",
139                    parm->fd, (int)GetLastError ());
140       else
141         log_debug ("feeder(%d): eof\n", parm->fd);
142     }
143
144 leave:
145   CloseHandle (fd_to_handle (parm->fd));
146   xfree (parm);
147   return NULL;
148 }
149 #endif /*USE_GNU_PTH*/
150
151 /* Fire up a thread to copy data between STREAM and a pipe's
152    descriptor FD.  With DIRECTION set to true the copy takes place
153    from the stream to the pipe, otherwise from the pipe to the
154    stream.  */
155 static gpg_error_t
156 start_feeder (estream_t stream, int fd, int direction)
157 {
158 #ifdef USE_GNU_PTH      
159   gpg_error_t err;
160   struct feeder_thread_parms *parm;
161   pth_attr_t tattr;
162   
163   parm = xtrymalloc (sizeof *parm);
164   if (!parm)
165     return gpg_error_from_syserror ();
166   parm->stream = stream;
167   parm->fd = fd;
168   parm->direction = direction;
169   
170   tattr = pth_attr_new ();
171   pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
172   pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 64*1024);
173   pth_attr_set (tattr, PTH_ATTR_NAME, "exec-feeder");
174   
175   log_error ("spawning new feeder(%p, %d, %d)\n", stream, fd, direction);
176   if(!pth_spawn (tattr, feeder_thread, parm))
177     {
178       err = gpg_error_from_syserror ();
179       log_error ("error spawning feeder: %s\n", gpg_strerror (err));
180       xfree (parm);
181     }
182   else
183     err = 0;
184   pth_attr_destroy (tattr);
185
186   return err;
187 #else
188   (void)stream;
189   (void)fd;
190   (void)direction;
191   return gpg_error (GPG_ERR_NOT_IMPLEMENTED);  /* No Pth.  */
192 #endif
193 }
194
195
196 \f
197 /* Return the maximum number of currently allowed open file
198    descriptors.  Only useful on POSIX systems but returns a value on
199    other systems too.  */
200 int
201 get_max_fds (void)
202 {
203   int max_fds = -1;
204
205 #ifdef OPEN_MAX
206   if (max_fds == -1)
207     max_fds = OPEN_MAX;
208 #endif
209
210   if (max_fds == -1)
211     max_fds = 256;  /* Arbitrary limit.  */
212
213   return max_fds;
214 }
215
216
217 /* Close all file descriptors starting with descriptor FIRST.  If
218    EXCEPT is not NULL, it is expected to be a list of file descriptors
219    which shall not be closed.  This list shall be sorted in ascending
220    order with the end marked by -1.  */
221 void
222 close_all_fds (int first, int *except)
223 {
224   int max_fd = get_max_fds ();
225   int fd, i, except_start;
226
227   if (except)
228     {
229       except_start = 0;
230       for (fd=first; fd < max_fd; fd++)
231         {
232           for (i=except_start; except[i] != -1; i++)
233             {
234               if (except[i] == fd)
235                 {
236                   /* If we found the descriptor in the exception list
237                      we can start the next compare run at the next
238                      index because the exception list is ordered.  */
239                 except_start = i + 1;
240                 break;
241                 }
242             }
243           if (except[i] == -1)
244             close (fd);
245         }
246     }
247   else
248     {
249       for (fd=first; fd < max_fd; fd++)
250         close (fd);
251     }
252
253   gpg_err_set_errno (0);
254 }
255
256
257 /* Returns an array with all currently open file descriptors.  The end
258    of the array is marked by -1.  The caller needs to release this
259    array using the *standard free* and not with xfree.  This allow the
260    use of this fucntion right at startup even before libgcrypt has
261    been initialized.  Returns NULL on error and sets ERRNO
262    accordingly.  */
263 int *
264 get_all_open_fds (void)
265 {
266   int *array;
267   size_t narray;
268   int fd, max_fd, idx;
269 #ifndef HAVE_STAT
270   array = calloc (1, sizeof *array);
271   if (array)
272     array[0] = -1;
273 #else /*HAVE_STAT*/
274   struct stat statbuf;
275
276   max_fd = get_max_fds ();
277   narray = 32;  /* If you change this change also t-exechelp.c.  */
278   array = calloc (narray, sizeof *array);
279   if (!array)
280     return NULL;
281   
282   /* Note:  The list we return is ordered.  */
283   for (idx=0, fd=0; fd < max_fd; fd++)
284     if (!(fstat (fd, &statbuf) == -1 && errno == EBADF))
285       {
286         if (idx+1 >= narray)
287           {
288             int *tmp;
289
290             narray += (narray < 256)? 32:256;
291             tmp = realloc (array, narray * sizeof *array);
292             if (!tmp)
293               {
294                 free (array);
295                 return NULL;
296               }
297             array = tmp;
298           }
299         array[idx++] = fd;
300       }
301   array[idx] = -1;
302 #endif /*HAVE_STAT*/
303   return array;
304 }
305
306
307
308 static char *
309 copy_quoted (char *p, const char *string)
310 {
311   const char *s;
312
313   if (!*string) /* Empty string. */
314     p = stpcpy (p, "\"\"");
315   else if (strpbrk (string, " \t\n\v\f\"")) /* Need quotes.  */
316     {
317       p = stpcpy (p, "\"");
318       for (s = string; *s; s++)
319         {
320           *p++ = *s;
321           if (*s == '\"')
322             *p++ = *s;
323         }
324       *p++ = '\"';
325       *p = 0;
326     }
327   else /* Copy verbatim.  */
328     p = stpcpy (p, string);
329
330   return p;
331 }
332
333
334 /* Build a command line for use with W32's CreateProcess.  On success
335    CMDLINE gets the address of a newly allocated string.  */
336 static int
337 build_w32_commandline (const char * const *argv,
338                        int fd0, int fd0_isnull,
339                        int fd1, int fd1_isnull,
340                        int fd2, int fd2_isnull,
341                        char **cmdline)
342 {
343   int i, n;
344   const char *s;
345   char *buf, *p;
346   char fdbuf[3*30];
347
348   p = fdbuf;
349   *p = 0;
350   if (fd0)
351     {
352       if (fd0_isnull)
353         strcpy (p, "-&S0=null ");
354       else
355         snprintf (p, 25, "-&S0=%d ", fd0);
356       p += strlen (p);
357     }
358   if (fd1)
359     {
360       if (fd1_isnull)
361         strcpy (p, "-&S1=null ");
362       else
363         snprintf (p, 25, "-&S1=%d ", fd1);
364       p += strlen (p);
365     }
366   if (fd2)
367     {
368       if (fd2_isnull)
369         strcpy (p, "-&S2=null ");
370       else
371         snprintf (p, 25, "-&S2=%d ", fd2);
372       p += strlen (p);
373     }
374   
375   *cmdline = NULL;
376   n = strlen (fdbuf);
377   for (i=0; (s = argv[i]); i++)
378     {
379       n += strlen (s) + 1 + 2;  /* (1 space, 2 quoting) */
380       for (; *s; s++)
381         if (*s == '\"')
382           n++;  /* Need to double inner quotes.  */
383     }
384   n++;
385
386   buf = p = xtrymalloc (n);
387   if (! buf)
388     return -1;
389
390   p = stpcpy (p, fdbuf);
391   for (i = 0; argv[i]; i++) 
392     {
393       *p++ = ' ';
394       p = copy_quoted (p, argv[i]);
395     }
396
397   *cmdline = buf;
398   return 0;
399 }
400
401
402 /* Create pipe where one end is inheritable: With an INHERIT_IDX of 0
403    the read end is inheritable, with 1 the write end is inheritable.
404    Note that the inheritable ends are rendezvous ids and no file
405    descriptors or handles. */
406 static gpg_error_t
407 create_inheritable_pipe (int filedes[2], int inherit_idx)
408 {
409   HANDLE hd;
410   int rvid;
411
412   filedes[0] = filedes[1] = -1;
413   hd = _assuan_w32ce_prepare_pipe (&rvid, !inherit_idx);
414   if (hd == INVALID_HANDLE_VALUE)
415     {
416       log_error ("_assuan_w32ce_prepare_pipe failed: %s\n", w32_strerror (-1));
417       gpg_err_set_errno (EIO);
418       return gpg_error_from_syserror ();
419     }
420
421   if (inherit_idx)
422     {
423       filedes[0] = handle_to_fd (hd);
424       filedes[1] = rvid;
425     }
426   else
427     {
428       filedes[0] = rvid;
429       filedes[1] = handle_to_fd (hd);
430     }
431   return 0;
432 }
433
434
435 /* Portable function to create a pipe.  Under Windows the write end is
436    inheritable (i.e. an rendezvous id).  */
437 gpg_error_t
438 gnupg_create_inbound_pipe (int filedes[2])
439 {
440   return create_inheritable_pipe (filedes, 1);
441 }
442
443
444 /* Portable function to create a pipe.  Under Windows the read end is
445    inheritable (i.e. an rendezvous id).  */
446 gpg_error_t
447 gnupg_create_outbound_pipe (int filedes[2])
448 {
449   return create_inheritable_pipe (filedes, 0);
450 }
451
452
453 static int
454 create_process (const char *pgmname, const char *cmdline,
455                 PROCESS_INFORMATION *pi)
456 {
457   int res;
458   wchar_t *wpgmname, *wcmdline;
459
460   wpgmname = utf8_to_wchar (pgmname);
461   if (!wpgmname)
462     return 0;
463   wcmdline = utf8_to_wchar (cmdline);
464   if (!wcmdline)
465     {
466       xfree (wpgmname);
467       return 0;
468     }
469   res = CreateProcess (wpgmname,      /* Program to start.  */
470                        wcmdline,      /* Command line arguments.  */
471                        NULL,          /* Process security attributes.  */
472                        NULL,          /* Thread security attributes.  */
473                        FALSE,          /* Inherit handles.  */
474                        CREATE_SUSPENDED, /* Creation flags.  */
475                        NULL,          /* Environment.  */
476                        NULL,          /* Use current drive/directory.  */
477                        NULL,          /* Startup information. */
478                        pi);           /* Returns process information.  */
479   xfree (wcmdline);
480   xfree (wpgmname);
481   return res;
482 }
483
484
485 /* Fork and exec the PGMNAME, see exechelp.h for details.  */
486 gpg_error_t
487 gnupg_spawn_process (const char *pgmname, const char *argv[],
488                      estream_t infile, estream_t outfile,
489                      void (*preexec)(void), unsigned int flags,
490                      estream_t *statusfile, pid_t *pid)
491 {
492   gpg_error_t err;
493   PROCESS_INFORMATION pi = {NULL };
494   char *cmdline;
495   int inpipe[2], outpipe[2], errpipe[2];
496
497   (void)preexec;
498   (void)flags;
499   
500   /* Setup return values.  */
501   *statusfile = NULL;
502   *pid = (pid_t)(-1);
503
504   es_fflush (infile);
505   es_rewind (infile);
506
507   /* Create a pipe to copy our infile to the stdin of the child
508      process.  On success inpipe[1] is owned by the feeder.  */
509   err = create_inheritable_pipe (inpipe, 0);
510   if (err)
511     {
512       log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
513       return err;
514     }
515   err = start_feeder (infile, inpipe[1], 1);
516   if (err)
517     {
518       log_error (_("error spawning feeder: %s\n"), gpg_strerror (err));
519       CloseHandle (fd_to_handle (inpipe[1]));
520       return err;
521     }
522
523   /* Create a pipe to copy stdout of the child process to our
524      outfile. On success outpipe[0] is owned by the feeded.  */
525   err = create_inheritable_pipe (outpipe, 1);
526   if (err)
527     {
528       log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
529       return err;
530     }
531   err = start_feeder (outfile, outpipe[0], 0);
532   if (err)
533     {
534       log_error (_("error spawning feeder: %s\n"), gpg_strerror (err));
535       CloseHandle (fd_to_handle (outpipe[0]));
536       return err;
537     }
538
539
540   /* Create a pipe for use with stderr of the child process.  */
541   err = create_inheritable_pipe (errpipe, 1);
542   if (err)
543     {
544       log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
545       return err;
546     }
547
548   /* Build the command line.  */
549   err = build_w32_commandline (argv,
550                                inpipe[0], 0,
551                                outpipe[1], 0,
552                                errpipe[1], 0,
553                                &cmdline);
554   if (err)
555     {
556       CloseHandle (fd_to_handle (errpipe[0]));
557       return err; 
558     }
559
560   
561   log_debug ("CreateProcess, path=`%s' cmdline=`%s'\n", pgmname, cmdline);
562   if (!create_process (pgmname, cmdline, &pi))
563     {
564       log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
565       xfree (cmdline);
566       CloseHandle (fd_to_handle (errpipe[0]));
567       return gpg_error (GPG_ERR_GENERAL);
568     }
569   xfree (cmdline);
570   cmdline = NULL;
571
572   /* Note: The other end of the pipe is a rendezvous id and thus there
573      is no need to close.  */
574
575   log_debug ("CreateProcess ready: hProcess=%p hThread=%p"
576              " dwProcessID=%d dwThreadId=%d\n",
577              pi.hProcess, pi.hThread,
578              (int) pi.dwProcessId, (int) pi.dwThreadId);
579   
580
581   /* Process has been created suspended; resume it now. */
582   ResumeThread (pi.hThread);
583   CloseHandle (pi.hThread); 
584
585   *statusfile = es_fdopen (handle_to_fd (errpipe[0]), "r");
586   if (!*statusfile)
587     {
588       err = gpg_error_from_syserror ();
589       log_error (_("can't fdopen pipe for reading: %s\n"), gpg_strerror (err));
590       CloseHandle (pi.hProcess);
591       return err;
592     }
593
594   *pid = handle_to_pid (pi.hProcess);
595   return 0;
596
597 }
598
599
600
601 /* Simplified version of gnupg_spawn_process.  This function forks and
602    then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
603    and ERRFD to stderr (any of them may be -1 to connect them to
604    /dev/null).  The arguments for the process are expected in the NULL
605    terminated array ARGV.  The program name itself should not be
606    included there.  Calling gnupg_wait_process is required.
607
608    Returns 0 on success or an error code. */
609 gpg_error_t
610 gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
611                         int infd, int outfd, int errfd, pid_t *pid)
612 {
613   gpg_error_t err;
614   PROCESS_INFORMATION pi = {NULL};
615   char *cmdline;
616
617   /* Setup return values.  */
618   *pid = (pid_t)(-1);
619
620   if (infd != -1 || outfd != -1 || errfd != -1)
621     return gpg_error (GPG_ERR_NOT_SUPPORTED);
622
623   /* Build the command line.  */
624   err = build_w32_commandline (argv, -1, 1, -1, 1, -1, 1, &cmdline);
625   if (err)
626     return err; 
627
628   log_debug ("CreateProcess, path=`%s' cmdline=`%s'\n", pgmname, cmdline);
629   if (!create_process (pgmname, cmdline, &pi))
630     {
631       log_error ("CreateProcess(fd) failed: %s\n", w32_strerror (-1));
632       xfree (cmdline);
633       return gpg_error (GPG_ERR_GENERAL);
634     }
635   xfree (cmdline);
636   cmdline = NULL;
637
638   log_debug ("CreateProcess(fd) ready: hProcess=%p hThread=%p"
639              " dwProcessID=%d dwThreadId=%d\n",
640              pi.hProcess, pi.hThread,
641              (int) pi.dwProcessId, (int) pi.dwThreadId);
642   
643   /* Process has been created suspended; resume it now. */
644   ResumeThread (pi.hThread);
645   CloseHandle (pi.hThread); 
646
647   *pid = handle_to_pid (pi.hProcess);
648   return 0;
649 }
650
651 /* Wait for the process identified by PID to terminate. PGMNAME should
652    be the same as supplied to the spawn function and is only used for
653    diagnostics. Returns 0 if the process succeeded, GPG_ERR_GENERAL
654    for any failures of the spawned program or other error codes.  If
655    EXITCODE is not NULL the exit code of the process is stored at this
656    address or -1 if it could not be retrieved. */
657 gpg_error_t
658 gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode)
659 {
660   gpg_err_code_t ec;
661   HANDLE proc = fd_to_handle (pid);
662   int code;
663   DWORD exc;
664
665   if (exitcode)
666     *exitcode = -1;
667
668   if (pid == (pid_t)(-1))
669     return gpg_error (GPG_ERR_INV_VALUE);
670
671   /* FIXME: We should do a pth_waitpid here.  However this has not yet
672      been implemented.  A special W32 pth system call would even be
673      better.  */
674   code = WaitForSingleObject (proc, INFINITE);
675   switch (code) 
676     {
677       case WAIT_FAILED:
678         log_error (_("waiting for process %d to terminate failed: %s\n"),
679                    (int)pid, w32_strerror (-1));
680         ec = GPG_ERR_GENERAL;
681         break;
682
683       case WAIT_OBJECT_0:
684         if (!GetExitCodeProcess (proc, &exc))
685           {
686             log_error (_("error getting exit code of process %d: %s\n"),
687                          (int)pid, w32_strerror (-1) );
688             ec = GPG_ERR_GENERAL;
689           }
690         else if (exc)
691           {
692             log_error (_("error running `%s': exit status %d\n"),
693                        pgmname, (int)exc );
694             if (exitcode)
695               *exitcode = (int)exc;
696             ec = GPG_ERR_GENERAL;
697           }
698         else
699           {
700             if (exitcode)
701               *exitcode = 0;
702             ec = 0;
703           }
704         CloseHandle (proc);
705         break;
706
707       default:
708         log_error ("WaitForSingleObject returned unexpected "
709                    "code %d for pid %d\n", code, (int)pid );
710         ec = GPG_ERR_GENERAL;
711         break;
712     }
713
714   return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
715 }
716
717
718 /* Spawn a new process and immediatley detach from it.  The name of
719    the program to exec is PGMNAME and its arguments are in ARGV (the
720    programname is automatically passed as first argument).
721    Environment strings in ENVP are set.  An error is returned if
722    pgmname is not executable; to make this work it is necessary to
723    provide an absolute file name.  All standard file descriptors are
724    connected to /dev/null. */
725 gpg_error_t
726 gnupg_spawn_process_detached (const char *pgmname, const char *argv[],
727                               const char *envp[] )
728 {
729   gpg_error_t err;
730   char *cmdline;
731   PROCESS_INFORMATION pi = {NULL };
732
733   (void)envp;
734   
735   /* Build the command line.  */
736   err = build_w32_commandline (argv, -1, 1, -1, 1, -1, 1, &cmdline);
737   if (err)
738     return err; 
739
740   /* Note: There is no detached flag under CE.  */
741   log_debug ("CreateProcess, path=`%s' cmdline=`%s'\n", pgmname, cmdline);
742   if (!create_process (pgmname, cmdline, &pi))
743     {
744       log_error ("CreateProcess(detached) failed: %s\n", w32_strerror (-1));
745       xfree (cmdline);
746       return gpg_error (GPG_ERR_GENERAL);
747     }
748   xfree (cmdline);
749   cmdline = NULL;
750
751   log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p"
752              " dwProcessID=%d dwThreadId=%d\n",
753              pi.hProcess, pi.hThread,
754              (int) pi.dwProcessId, (int) pi.dwThreadId);
755   
756   /* Process has been created suspended; resume it now. */
757   ResumeThread (pi.hThread);
758   CloseHandle (pi.hThread); 
759
760   return 0;
761 }
762
763
764 /* Kill a process; that is send an appropriate signal to the process.
765    gnupg_wait_process must be called to actually remove the process
766    from the system.  An invalid PID is ignored.  */
767 void
768 gnupg_kill_process (pid_t pid)
769 {
770   if (pid != (pid_t) INVALID_HANDLE_VALUE)
771     {
772       HANDLE process = (HANDLE) pid;
773       
774       /* Arbitrary error code.  */
775       TerminateProcess (process, 1);
776     }
777 }