1 /* -*-C-*-
2
3 Copyright (C) 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994,
4 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
5 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Massachusetts
6 Institute of Technology
7
8 This file is part of MIT/GNU Scheme.
9
10 MIT/GNU Scheme is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or (at
13 your option) any later version.
14
15 MIT/GNU Scheme is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with MIT/GNU Scheme; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
23 USA.
24
25 */
26
27 #include "nt.h"
28 #include "ntproc.h"
29 #include "ntio.h"
30 #include "ntscreen.h"
31 #include "ntgui.h"
32
33 extern const char * OS_working_dir_pathname (void);
34
35 typedef struct
36 {
37 PROCESS_INFORMATION handles;
38 unsigned long tick;
39 unsigned long sync_tick;
40 DWORD raw_reason;
41 DWORD reason;
42 enum process_status raw_status;
43 enum process_status status;
44 } process_t;
45 #define _PROCESS(process) (process_table [(process)])
46 #define PROCESS_HANDLES(process) ((_PROCESS(process)) . handles)
47 #define PROCESS_TICK(process) ((_PROCESS(process)) . tick)
48 #define PROCESS_SYNC_TICK(process) ((_PROCESS(process)) . sync_tick)
49 #define PROCESS_RAW_REASON(process) ((_PROCESS(process)) . raw_reason)
50 #define PROCESS_REASON(process) ((_PROCESS(process)) . reason)
51 #define PROCESS_RAW_STATUS(process) ((_PROCESS(process)) . raw_status)
52 #define PROCESS_STATUS(process) ((_PROCESS(process)) . status)
53
54 #define PROCESS_HANDLE(process) ((PROCESS_HANDLES (process)) . hProcess)
55 #define PROCESS_ID(process) ((PROCESS_HANDLES (process)) . dwProcessId)
56
57 #ifndef NT_DEFAULT_PROCESS_TABLE_SIZE
58 #define NT_DEFAULT_PROCESS_TABLE_SIZE 4096
59 #endif
60 Tprocess OS_process_table_size;
61 enum process_jc_status scheme_jc_status;
62
63 static process_t * process_table;
64 static unsigned long process_tick;
65 static unsigned long sync_tick;
66
67 #undef USE_PROCESS_TABLE_LOCK
68 #ifdef USE_PROCESS_TABLE_LOCK
69 static CRITICAL_SECTION process_table_lock;
70 #define GRAB_PROCESS_TABLE() EnterCriticalSection (&process_table_lock)
71 #define RELEASE_PROCESS_TABLE() LeaveCriticalSection (&process_table_lock)
72 #else
73 #define GRAB_PROCESS_TABLE()
74 #define RELEASE_PROCESS_TABLE()
75 #endif
76
77 #define PROCESS_STATUS_SYNC(process) \
78 { \
79 (PROCESS_STATUS (process)) = (PROCESS_RAW_STATUS (process)); \
80 (PROCESS_REASON (process)) = (PROCESS_RAW_REASON (process)); \
81 (PROCESS_SYNC_TICK (process)) = (PROCESS_TICK (process)); \
82 }
83
84 /* I have no idea what a good value for this might be, so I've picked
85 a random non-zero value. */
86 #define TERMINATE_PROCESS_EXIT_CODE 0xFF
87
88 #undef TRACE_NTPROC
89 #ifdef TRACE_NTPROC
90 FILE * trace_file;
91 #ifndef TRACE_NTPROC_FILENAME
92 #define TRACE_NTPROC_FILENAME "nttrace.out"
93 #endif
94 #endif
95
96 static void lock_process_table (void);
97 static void lock_process_table_1 (void *);
98 static HANDLE stdio_handle (DWORD, Tchannel, enum process_channel_type);
99 static HANDLE copy_handle (HANDLE);
100 static Tprocess allocate_process (void);
101 static void allocate_process_abort (void *);
102 static HWND find_child_console (DWORD);
103 static BOOL CALLBACK find_child_console_1 (HWND, LPARAM);
104 static void process_wait_1 (Tprocess, DWORD);
105 static void process_death (Tprocess);
106
107 void
NT_initialize_processes(void)108 NT_initialize_processes (void)
109 {
110 #ifdef TRACE_NTPROC
111 trace_file = (fopen (TRACE_NTPROC_FILENAME, "w"));
112 #endif
113 OS_process_table_size = NT_DEFAULT_PROCESS_TABLE_SIZE;
114 process_table = (OS_malloc (OS_process_table_size * (sizeof (process_t))));
115 {
116 Tprocess process;
117 for (process = 0; (process < OS_process_table_size); process += 1)
118 OS_process_deallocate (process);
119 }
120 #ifdef USE_PROCESS_TABLE_LOCK
121 InitializeCriticalSection (&process_table_lock);
122 #endif
123 scheme_jc_status = process_jc_status_no_ctty;
124 process_tick = 0;
125 sync_tick = 0;
126 }
127
128 static void
lock_process_table(void)129 lock_process_table (void)
130 {
131 GRAB_PROCESS_TABLE ();
132 transaction_record_action (tat_always, lock_process_table_1, 0);
133 }
134
135 static void
lock_process_table_1(void * ignore)136 lock_process_table_1 (void * ignore)
137 {
138 RELEASE_PROCESS_TABLE ();
139 }
140
141 Tprocess
NT_make_subprocess(const char * filename,const char * command_line,const char * environment,const char * working_directory,enum process_channel_type channel_in_type,Tchannel channel_in,enum process_channel_type channel_out_type,Tchannel channel_out,enum process_channel_type channel_err_type,Tchannel channel_err,int hide_windows_p)142 NT_make_subprocess (const char * filename,
143 const char * command_line,
144 const char * environment,
145 const char * working_directory,
146 enum process_channel_type channel_in_type,
147 Tchannel channel_in,
148 enum process_channel_type channel_out_type,
149 Tchannel channel_out,
150 enum process_channel_type channel_err_type,
151 Tchannel channel_err,
152 int hide_windows_p)
153 {
154 Tprocess child;
155 SECURITY_ATTRIBUTES sap;
156 SECURITY_DESCRIPTOR sdp;
157 STARTUPINFO si;
158
159 transaction_begin ();
160
161 /* Explicitly specify no security */
162 STD_BOOL_API_CALL
163 (InitializeSecurityDescriptor, ((&sdp), SECURITY_DESCRIPTOR_REVISION));
164 STD_BOOL_API_CALL (SetSecurityDescriptorDacl, ((&sdp), TRUE, 0, FALSE));
165 (sap . nLength) = (sizeof (sap));
166 (sap . lpSecurityDescriptor) = (&sdp);
167 (sap . bInheritHandle) = FALSE;
168
169 memset ((&si), 0, (sizeof (si)));
170 (si . cb) = (sizeof (si));
171 /* Specify the handles to be used by the child process. */
172 (si . dwFlags) = STARTF_USESTDHANDLES;
173 (si . hStdInput)
174 = (stdio_handle (STD_INPUT_HANDLE, channel_in, channel_in_type));
175 (si . hStdOutput)
176 = (stdio_handle (STD_OUTPUT_HANDLE, channel_out, channel_out_type));
177 (si . hStdError)
178 = (stdio_handle (STD_ERROR_HANDLE, channel_err, channel_err_type));
179 /* If requested, hide the top-level window of the child process. */
180 if (hide_windows_p)
181 {
182 (si . dwFlags) |= STARTF_USESHOWWINDOW;
183 (si . wShowWindow) |= SW_HIDE;
184 }
185
186 lock_process_table ();
187 child = (allocate_process ());
188 STD_BOOL_API_CALL
189 (CreateProcess,
190 (((LPCTSTR) filename),
191 ((LPSTR) command_line),
192 (&sap),
193 0,
194 TRUE,
195 (CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_CONSOLE),
196 ((LPVOID) environment),
197 ((LPCTSTR) working_directory),
198 (&si),
199 (& (PROCESS_HANDLES (child)))));
200 (PROCESS_RAW_STATUS (child)) = process_status_running;
201 (PROCESS_RAW_REASON (child)) = STILL_ACTIVE;
202 (PROCESS_TICK (child)) = process_tick;
203 PROCESS_STATUS_SYNC (child);
204 STD_BOOL_API_CALL (CloseHandle, (si . hStdInput));
205 STD_BOOL_API_CALL (CloseHandle, (si . hStdOutput));
206 STD_BOOL_API_CALL (CloseHandle, (si . hStdError));
207 transaction_commit ();
208 STD_BOOL_API_CALL (CloseHandle, ((PROCESS_HANDLES (child)) . hThread));
209 ((PROCESS_HANDLES (child)) . hThread) = INVALID_HANDLE_VALUE;
210 #ifdef TRACE_NTPROC
211 fprintf (trace_file, "Subprocess created: id=0x%x\n", (PROCESS_ID (child)));
212 fflush (trace_file);
213 #endif
214 return (child);
215 }
216
217 static HANDLE
stdio_handle(DWORD target,Tchannel channel,enum process_channel_type type)218 stdio_handle (DWORD target, Tchannel channel, enum process_channel_type type)
219 {
220 return
221 (copy_handle ((type == process_channel_type_explicit)
222 ? (CHANNEL_HANDLE (channel))
223 : (GetStdHandle (target))));
224 }
225
226 static HANDLE
copy_handle(HANDLE handle)227 copy_handle (HANDLE handle)
228 {
229 HANDLE parent = (GetCurrentProcess ());
230 HANDLE copy;
231 STD_BOOL_API_CALL
232 (DuplicateHandle,
233 (parent, handle, parent, (©), 0, TRUE, DUPLICATE_SAME_ACCESS));
234 NT_handle_close_on_abort (copy);
235 return (copy);
236 }
237
238 static Tprocess
allocate_process(void)239 allocate_process (void)
240 {
241 unsigned int process;
242 for (process = 0; (process < OS_process_table_size); process += 1)
243 if ((PROCESS_RAW_STATUS (process)) == process_status_free)
244 {
245 Tprocess * pp = (dstack_alloc (sizeof (Tprocess)));
246 (*pp) = process;
247 transaction_record_action (tat_abort, allocate_process_abort, pp);
248 (PROCESS_RAW_STATUS (process)) = process_status_allocated;
249 return (process);
250 }
251 error_out_of_processes ();
252 return (NO_PROCESS);
253 }
254
255 static void
allocate_process_abort(void * environment)256 allocate_process_abort (void * environment)
257 {
258 Tprocess process = (* ((Tprocess *) environment));
259 OS_process_deallocate (process);
260 }
261
262 struct fcc_info
263 {
264 DWORD pid;
265 HWND hwnd;
266 };
267
268 static HWND
find_child_console(DWORD pid)269 find_child_console (DWORD pid)
270 {
271 struct fcc_info fi;
272 (fi . pid) = pid;
273 (fi . hwnd) = INVALID_HANDLE_VALUE;
274 EnumWindows (find_child_console_1, ((LPARAM) (&fi)));
275 return (fi . hwnd);
276 }
277
278 static BOOL CALLBACK
find_child_console_1(HWND hwnd,LPARAM lfi)279 find_child_console_1 (HWND hwnd, LPARAM lfi)
280 {
281 struct fcc_info * fi = ((struct fcc_info *) lfi);
282 DWORD pid;
283
284 (void) GetWindowThreadProcessId (hwnd, (&pid));
285 if (pid == (fi -> pid))
286 {
287 char window_class [32];
288 unsigned int n
289 = (GetClassName (hwnd, window_class, (sizeof (window_class))));
290 const char * console_class
291 = ((NT_windows_type == wintype_95) ? "tty" : "ConsoleWindowClass");
292 if ((n == (strlen (console_class)))
293 && ((strcmp (window_class, console_class)) == 0))
294 {
295 (fi -> hwnd) = hwnd;
296 return (FALSE);
297 }
298 }
299 /* keep looking */
300 return (TRUE);
301 }
302
303 void
OS_process_deallocate(Tprocess process)304 OS_process_deallocate (Tprocess process)
305 {
306 (PROCESS_RAW_STATUS (process)) = process_status_free;
307 }
308
309 int
OS_process_valid_p(Tprocess process)310 OS_process_valid_p (Tprocess process)
311 {
312 if (process > OS_process_table_size)
313 return (0);
314 switch (PROCESS_RAW_STATUS (process))
315 {
316 case process_status_exited:
317 case process_status_signalled:
318 case process_status_running:
319 return (1);
320 default:
321 return (0);
322 }
323 }
324
325 int
OS_process_continuable_p(Tprocess process)326 OS_process_continuable_p (Tprocess process)
327 {
328 return ((PROCESS_RAW_STATUS (process)) == process_status_running);
329 }
330
331 int
OS_process_foregroundable_p(Tprocess process)332 OS_process_foregroundable_p (Tprocess process)
333 {
334 return (0);
335 }
336
337 pid_t
OS_process_id(Tprocess process)338 OS_process_id (Tprocess process)
339 {
340 return (PROCESS_ID (process));
341 }
342
343 enum process_jc_status
OS_process_jc_status(Tprocess process)344 OS_process_jc_status (Tprocess process)
345 {
346 return (process_jc_status_no_ctty);
347 }
348
349 int
OS_process_status_sync(Tprocess process)350 OS_process_status_sync (Tprocess process)
351 {
352 #ifdef TRACE_NTPROC
353 fprintf (trace_file, "OS_process_status_sync: id=0x%x\n",
354 (PROCESS_ID (process)));
355 fflush (trace_file);
356 #endif
357 transaction_begin ();
358 lock_process_table ();
359 {
360 int result = ((PROCESS_TICK (process)) != (PROCESS_SYNC_TICK (process)));
361 if (result)
362 {
363 #ifdef TRACE_NTPROC
364 fprintf (trace_file, "(status=0x%x raw_status=0x%x)\n",
365 (PROCESS_STATUS (process)),
366 (PROCESS_RAW_STATUS (process)));
367 fflush (trace_file);
368 #endif
369 PROCESS_STATUS_SYNC (process);
370 }
371 transaction_commit ();
372 return (result);
373 }
374 }
375
376 int
OS_process_status_sync_all(void)377 OS_process_status_sync_all (void)
378 {
379 #ifdef TRACE_NTPROC
380 fprintf (trace_file, "OS_process_status_sync_all\n");
381 fflush (trace_file);
382 #endif
383 transaction_begin ();
384 lock_process_table ();
385 {
386 int result = (process_tick != sync_tick);
387 if (result)
388 {
389 #ifdef TRACE_NTPROC
390 fprintf (trace_file, "(status change)\n");
391 fflush (trace_file);
392 #endif
393 sync_tick = process_tick;
394 }
395 transaction_commit ();
396 return (result);
397 }
398 }
399
400 enum process_status
OS_process_status(Tprocess process)401 OS_process_status (Tprocess process)
402 {
403 #ifdef TRACE_NTPROC
404 fprintf (trace_file, "OS_process_status: id=0x%x status=0x%x\n",
405 (PROCESS_ID (process)),
406 (PROCESS_STATUS (process)));
407 fflush (trace_file);
408 #endif
409 return (PROCESS_STATUS (process));
410 }
411
412 unsigned short
OS_process_reason(Tprocess process)413 OS_process_reason (Tprocess process)
414 {
415 return ((unsigned short) (PROCESS_REASON (process)));
416 }
417
418 void
OS_process_send_signal(Tprocess process,int sig)419 OS_process_send_signal (Tprocess process, int sig)
420 {
421 error_unimplemented_primitive ();
422 }
423
424 void
OS_process_kill(Tprocess process)425 OS_process_kill (Tprocess process)
426 {
427 #ifdef TRACE_NTPROC
428 fprintf (trace_file, "OS_process_kill: id=0x%x\n", (PROCESS_ID (process)));
429 fflush (trace_file);
430 #endif
431 if (NT_windows_type == wintype_nt)
432 {
433 HWND hwnd = (find_child_console (PROCESS_ID (process)));
434 if (hwnd != INVALID_HANDLE_VALUE)
435 {
436 PostMessage (hwnd, WM_CLOSE, 0, 0);
437 return;
438 }
439 }
440 #ifdef TRACE_NTPROC
441 fprintf (trace_file, "(using TerminateProcess)\n");
442 fflush (trace_file);
443 #endif
444 if (!TerminateProcess ((PROCESS_HANDLE (process)),
445 TERMINATE_PROCESS_EXIT_CODE))
446 {
447 DWORD code = (GetLastError ());
448 if (code != ERROR_ACCESS_DENIED)
449 NT_error_api_call ((GetLastError ()), apicall_TerminateProcess);
450 #ifdef TRACE_NTPROC
451 fprintf (trace_file, "(ERROR_ACCESS_DENIED)\n");
452 fflush (trace_file);
453 #endif
454 }
455 }
456
457 void
OS_process_stop(Tprocess process)458 OS_process_stop (Tprocess process)
459 {
460 error_unimplemented_primitive ();
461 }
462
463 void
OS_process_interrupt(Tprocess process)464 OS_process_interrupt (Tprocess process)
465 {
466 HWND hwnd;
467 BYTE control_scan_code;
468 BYTE vk_break_code;
469 BYTE break_scan_code;
470 /* BYTE keyboard_state [256]; */
471 HWND foreground_window;
472
473 #ifdef TRACE_NTPROC
474 fprintf (trace_file, "OS_process_interrupt: id=0x%x\n",
475 (PROCESS_ID (process)));
476 fflush (trace_file);
477 #endif
478 hwnd = (find_child_console (PROCESS_ID (process)));
479 if (hwnd == INVALID_HANDLE_VALUE)
480 return;
481 control_scan_code = ((BYTE) (MapVirtualKey (VK_CONTROL, 0)));
482 vk_break_code = VK_CANCEL;
483 break_scan_code = ((BYTE) (MapVirtualKey (vk_break_code, 0)));
484 if (break_scan_code == 0)
485 {
486 /* Fake Ctrl-C if we can't manage Ctrl-Break. */
487 vk_break_code = 'C';
488 break_scan_code = ((BYTE) (MapVirtualKey (vk_break_code, 0)));
489 }
490 /* STD_BOOL_API_CALL (GetKeyboardState, (keyboard_state)); */
491 foreground_window = (GetForegroundWindow ());
492 if (SetForegroundWindow (hwnd))
493 {
494 /* Generate keystrokes as if user had typed Ctrl-Break or Ctrl-C. */
495 keybd_event (VK_CONTROL, control_scan_code, 0, 0);
496 keybd_event (vk_break_code, break_scan_code, 0, 0);
497 keybd_event (vk_break_code, break_scan_code, KEYEVENTF_KEYUP, 0);
498 keybd_event (VK_CONTROL, control_scan_code, KEYEVENTF_KEYUP, 0);
499 if (foreground_window)
500 (void) SetForegroundWindow (foreground_window);
501 }
502 /* STD_BOOL_API_CALL (SetKeyboardState, (keyboard_state)); */
503 }
504
505 void
OS_process_quit(Tprocess process)506 OS_process_quit (Tprocess process)
507 {
508 error_unimplemented_primitive ();
509 }
510
511 void
OS_process_hangup(Tprocess process)512 OS_process_hangup (Tprocess process)
513 {
514 /* Edwin assumes that this primitive works. Under unix, the default
515 behavior of SIGHUP is to kill the process, so we will emulate
516 SIGHUP by killing the process. */
517 OS_process_kill (process);
518 }
519
520 void
OS_process_continue_background(Tprocess process)521 OS_process_continue_background (Tprocess process)
522 {
523 /* A no-op, this should only be called when OS_process_continuable_p
524 is true, i.e. when the process is already running. */
525 }
526
527 void
OS_process_continue_foreground(Tprocess process)528 OS_process_continue_foreground (Tprocess process)
529 {
530 error_unimplemented_primitive ();
531 }
532
533 #ifndef WIN32_WAIT_INTERVAL
534 #define WIN32_WAIT_INTERVAL 50
535 #endif
536
537 void
OS_process_wait(Tprocess process)538 OS_process_wait (Tprocess process)
539 {
540 process_wait_1 (process, 0);
541 while (1)
542 {
543 if (((PROCESS_RAW_STATUS (process)) != process_status_running)
544 || (pending_interrupts_p ()))
545 break;
546 process_wait_1 (process, WIN32_WAIT_INTERVAL);
547 }
548 }
549
550 static void
process_wait_1(Tprocess process,DWORD interval)551 process_wait_1 (Tprocess process, DWORD interval)
552 {
553 #ifdef TRACE_NTPROC
554 fprintf (trace_file, "process_wait_1: id=0x%x raw_status=0x%x\n",
555 (PROCESS_ID (process)),
556 (PROCESS_RAW_STATUS (process)));
557 fflush (trace_file);
558 #endif
559 if ((PROCESS_RAW_STATUS (process)) == process_status_running)
560 {
561 DWORD code
562 = (MsgWaitForMultipleObjects (1,
563 (& (PROCESS_HANDLE (process))),
564 FALSE,
565 interval,
566 QS_ALLINPUT));
567 #ifdef TRACE_NTPROC
568 fprintf (trace_file, "(wait result = 0x%x)\n", code);
569 fflush (trace_file);
570 #endif
571 switch (code)
572 {
573 case WAIT_OBJECT_0:
574 process_death (process);
575 break;
576 case WAIT_FAILED:
577 NT_error_api_call ((GetLastError ()),
578 apicall_MsgWaitForMultipleObjects);
579 break;
580 }
581 }
582 }
583
584 int
OS_process_any_status_change(void)585 OS_process_any_status_change (void)
586 {
587 Tprocess process;
588 #ifdef TRACE_NTPROC
589 fprintf (trace_file, "OS_process_any_status_change\n");
590 fflush (trace_file);
591 #endif
592 for (process = 0; (process < OS_process_table_size); process += 1)
593 if ((PROCESS_RAW_STATUS (process)) == process_status_running)
594 switch (WaitForSingleObject ((PROCESS_HANDLE (process)), 0))
595 {
596 case WAIT_OBJECT_0:
597 process_death (process);
598 break;
599 case WAIT_FAILED:
600 NT_error_api_call ((GetLastError ()),
601 apicall_MsgWaitForMultipleObjects);
602 break;
603 }
604 #ifdef TRACE_NTPROC
605 if (process_tick != sync_tick)
606 {
607 fprintf (trace_file, "(status change)\n");
608 fflush (trace_file);
609 }
610 #endif
611 return (process_tick != sync_tick);
612 }
613
614 static void
process_death(Tprocess process)615 process_death (Tprocess process)
616 {
617 DWORD exit_code;
618 #ifdef TRACE_NTPROC
619 fprintf (trace_file, "process_death: id=0x%x\n", (PROCESS_ID (process)));
620 fflush (trace_file);
621 #endif
622 STD_BOOL_API_CALL
623 (GetExitCodeProcess, ((PROCESS_HANDLE (process)), (&exit_code)));
624 #ifdef TRACE_NTPROC
625 fprintf (trace_file, "(exit_code = 0x%x)\n", exit_code);
626 fflush (trace_file);
627 #endif
628 GRAB_PROCESS_TABLE ();
629 (PROCESS_RAW_STATUS (process))
630 = ((exit_code == STATUS_CONTROL_C_EXIT)
631 ? process_status_signalled
632 : process_status_exited);
633 (PROCESS_RAW_REASON (process)) = exit_code;
634 (PROCESS_TICK (process)) = (++process_tick);
635 STD_BOOL_API_CALL (CloseHandle, (PROCESS_HANDLE (process)));
636 (PROCESS_HANDLE (process)) = INVALID_HANDLE_VALUE;
637 RELEASE_PROCESS_TABLE ();
638 }
639
640 Tprocess
OS_make_subprocess(const char * filename,const char ** argv,const char ** envp,const char * working_directory,enum process_ctty_type ctty_type,char * ctty_name,enum process_channel_type channel_in_type,Tchannel channel_in,enum process_channel_type channel_out_type,Tchannel channel_out,enum process_channel_type channel_err_type,Tchannel channel_err)641 OS_make_subprocess (const char * filename,
642 const char ** argv,
643 const char ** envp,
644 const char * working_directory,
645 enum process_ctty_type ctty_type,
646 char * ctty_name,
647 enum process_channel_type channel_in_type,
648 Tchannel channel_in,
649 enum process_channel_type channel_out_type,
650 Tchannel channel_out,
651 enum process_channel_type channel_err_type,
652 Tchannel channel_err)
653 {
654 error_unimplemented_primitive ();
655 return (NO_PROCESS);
656 }
657