Fixed extern declaration from pointer to array
[minix.git] / servers / rs / main.c
blobb61a03f20d53c5f387475d5ed6bc5aab76956fb2
1 /* Reincarnation Server. This servers starts new system services and detects
2 * they are exiting. In case of errors, system services can be restarted.
3 * The RS server periodically checks the status of all registered services
4 * services to see whether they are still alive. The system services are
5 * expected to periodically send a heartbeat message.
6 *
7 * Changes:
8 * Nov 22, 2009: rewrite of boot process (Cristiano Giuffrida)
9 * Jul 22, 2005: Created (Jorrit N. Herder)
11 #include "inc.h"
12 #include <fcntl.h>
13 #include <a.out.h>
14 #include <minix/crtso.h>
15 #include "../../kernel/const.h"
16 #include "../../kernel/type.h"
17 #include "../../kernel/proc.h"
18 #include "../pm/mproc.h"
19 #include "../pm/const.h"
21 /* Declare some local functions. */
22 FORWARD _PROTOTYPE(void exec_image_copy, ( int boot_proc_idx,
23 struct boot_image *ip, struct rproc *rp) );
24 FORWARD _PROTOTYPE(void boot_image_info_lookup, ( endpoint_t endpoint,
25 struct boot_image *image,
26 struct boot_image **ip, struct boot_image_priv **pp,
27 struct boot_image_sys **sp, struct boot_image_dev **dp) );
28 FORWARD _PROTOTYPE(void catch_boot_init_ready, (endpoint_t endpoint) );
29 FORWARD _PROTOTYPE(void sig_handler, (void) );
30 FORWARD _PROTOTYPE(void get_work, (message *m) );
31 FORWARD _PROTOTYPE(void reply, (int whom, message *m_out) );
33 /* The buffer where the boot image is copied during initialization. */
34 PRIVATE int boot_image_buffer_size;
35 PRIVATE char *boot_image_buffer;
37 /* Flag set when memory unmapping can be done. */
38 EXTERN int unmap_ok;
40 /* SEF functions and variables. */
41 FORWARD _PROTOTYPE( void sef_local_startup, (void) );
42 FORWARD _PROTOTYPE( int sef_cb_init_fresh, (int type, sef_init_info_t *info) );
44 /*===========================================================================*
45 * main *
46 *===========================================================================*/
47 PUBLIC int main(void)
49 /* This is the main routine of this service. The main loop consists of
50 * three major activities: getting new work, processing the work, and
51 * sending the reply. The loop never terminates, unless a panic occurs.
53 message m; /* request message */
54 int call_nr, who_e,who_p; /* call number and caller */
55 int result; /* result to return */
57 /* SEF local startup. */
58 sef_local_startup();
60 /* Main loop - get work and do it, forever. */
61 while (TRUE) {
63 /* Wait for request message. */
64 get_work(&m);
65 who_e = m.m_source;
66 who_p = _ENDPOINT_P(who_e);
67 if(who_p < -NR_TASKS || who_p >= NR_PROCS)
68 panic("RS","message from bogus source", who_e);
70 call_nr = m.m_type;
72 /* Now determine what to do. Four types of requests are expected:
73 * - Heartbeat messages (notifications from registered system services)
74 * - System notifications (POSIX signals or synchronous alarm)
75 * - User requests (control messages to manage system services)
76 * - Ready messages (reply messages from registered services)
79 /* Notification messages are control messages and do not need a reply.
80 * These include heartbeat messages and system notifications.
82 if (is_notify(m.m_type)) {
83 switch (who_p) {
84 case CLOCK:
85 do_period(&m); /* check services status */
86 continue;
87 case PM_PROC_NR: /* signal or PM heartbeat */
88 sig_handler();
89 default: /* heartbeat notification */
90 if (rproc_ptr[who_p] != NULL) { /* mark heartbeat time */
91 rproc_ptr[who_p]->r_alive_tm = m.NOTIFY_TIMESTAMP;
92 } else {
93 printf("Warning, RS got unexpected notify message from %d\n",
94 m.m_source);
99 /* If we get this far, this is a normal request.
100 * Handle the request and send a reply to the caller.
102 else {
103 if (call_nr != GETSYSINFO &&
104 (call_nr < RS_RQ_BASE || call_nr >= RS_RQ_BASE+0x100))
106 /* Ignore invalid requests. Do not try to reply. */
107 printf("RS: got invalid request %d from endpoint %d\n",
108 call_nr, m.m_source);
109 continue;
112 /* Handler functions are responsible for permission checking. */
113 switch(call_nr) {
114 /* User requests. */
115 case RS_UP: result = do_up(&m); break;
116 case RS_DOWN: result = do_down(&m); break;
117 case RS_REFRESH: result = do_refresh(&m); break;
118 case RS_RESTART: result = do_restart(&m); break;
119 case RS_SHUTDOWN: result = do_shutdown(&m); break;
120 case RS_UPDATE: result = do_update(&m); break;
121 case GETSYSINFO: result = do_getsysinfo(&m); break;
122 case RS_LOOKUP: result = do_lookup(&m); break;
123 /* Ready messages. */
124 case RS_INIT: result = do_init_ready(&m); break;
125 case RS_LU_PREPARE: result = do_upd_ready(&m); break;
126 default:
127 printf("Warning, RS got unexpected request %d from %d\n",
128 m.m_type, m.m_source);
129 result = EINVAL;
132 /* Finally send reply message, unless disabled. */
133 if (result != EDONTREPLY) {
134 m.m_type = result;
135 reply(who_e, &m);
141 /*===========================================================================*
142 * sef_local_startup *
143 *===========================================================================*/
144 PRIVATE void sef_local_startup()
146 /* Register init callbacks. */
147 sef_setcb_init_fresh(sef_cb_init_fresh); /* RS can only start fresh. */
149 /* Let SEF perform startup. */
150 sef_startup();
153 /*===========================================================================*
154 * sef_cb_init_fresh *
155 *===========================================================================*/
156 PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *info)
158 /* Initialize the reincarnation server. */
159 struct sigaction sa;
160 struct boot_image *ip;
161 int s,i,j;
162 int nr_image_srvs, nr_image_priv_srvs, nr_uncaught_init_srvs;
163 struct rproc *rp;
164 struct rprocpub *rpub;
165 struct boot_image image[NR_BOOT_PROCS];
166 struct mproc mproc[NR_PROCS];
167 struct exec header;
168 struct boot_image_priv *boot_image_priv;
169 struct boot_image_sys *boot_image_sys;
170 struct boot_image_dev *boot_image_dev;
172 /* See if we run in verbose mode. */
173 env_parse("rs_verbose", "d", 0, &rs_verbose, 0, 1);
175 /* Initialize the global init descriptor. */
176 rinit.rproctab_gid = cpf_grant_direct(ANY, (vir_bytes) rprocpub,
177 sizeof(rprocpub), CPF_READ);
178 if(!GRANT_VALID(rinit.rproctab_gid)) {
179 panic("RS", "unable to create rprocpub table grant", rinit.rproctab_gid);
182 /* Initialize the global update descriptor. */
183 rupdate.flags = 0;
185 /* Get a copy of the boot image table. */
186 if ((s = sys_getimage(image)) != OK) {
187 panic("RS", "unable to get copy of boot image table", s);
190 /* Determine the number of system services in the boot image table and
191 * compute the size required for the boot image buffer.
193 nr_image_srvs = 0;
194 boot_image_buffer_size = 0;
195 for(i=0;i<NR_BOOT_PROCS;i++) {
196 ip = &image[i];
198 /* System services only. */
199 if(iskerneln(_ENDPOINT_P(ip->endpoint))) {
200 continue;
202 nr_image_srvs++;
204 /* Lookup the corresponding entry in the boot image sys table. */
205 boot_image_info_lookup(ip->endpoint, image,
206 NULL, NULL, &boot_image_sys, NULL);
208 /* If we must keep a copy of this system service, read the header
209 * and increase the size of the boot image buffer.
211 if(boot_image_sys->flags & SF_USE_COPY) {
212 if((s = sys_getaoutheader(&header, i)) != OK) {
213 panic("RS", "unable to get copy of a.out header", s);
215 boot_image_buffer_size += header.a_hdrlen
216 + header.a_text + header.a_data;
220 /* Determine the number of entries in the boot image priv table and make sure
221 * it matches the number of system services in the boot image table.
223 nr_image_priv_srvs = 0;
224 for (i=0; boot_image_priv_table[i].endpoint != NULL_BOOT_NR; i++) {
225 boot_image_priv = &boot_image_priv_table[i];
227 /* System services only. */
228 if(iskerneln(_ENDPOINT_P(boot_image_priv->endpoint))) {
229 continue;
231 nr_image_priv_srvs++;
233 if(nr_image_srvs != nr_image_priv_srvs) {
234 panic("RS", "boot image table and boot image priv table mismatch",
235 NO_NUM);
238 /* Allocate boot image buffer. */
239 if(boot_image_buffer_size > 0) {
240 boot_image_buffer = rs_startup_sbrk(boot_image_buffer_size);
241 if(boot_image_buffer == (char *) -1) {
242 panic("RS", "unable to allocate boot image buffer", NO_NUM);
246 /* Reset the system process table. */
247 for (rp=BEG_RPROC_ADDR; rp<END_RPROC_ADDR; rp++) {
248 rp->r_flags = 0;
249 rp->r_pub = &rprocpub[rp - rproc];
250 rp->r_pub->in_use = FALSE;
253 /* Initialize the system process table in 4 steps, each of them following
254 * the appearance of system services in the boot image priv table.
255 * - Step 1: get a copy of the executable image of every system service that
256 * requires it while it is not yet running.
257 * In addition, set priviliges, sys properties, and dev properties (if any)
258 * for every system service.
260 for (i=0; boot_image_priv_table[i].endpoint != NULL_BOOT_NR; i++) {
261 boot_image_priv = &boot_image_priv_table[i];
263 /* System services only. */
264 if(iskerneln(_ENDPOINT_P(boot_image_priv->endpoint))) {
265 continue;
268 /* Lookup the corresponding entries in other tables. */
269 boot_image_info_lookup(boot_image_priv->endpoint, image,
270 &ip, NULL, &boot_image_sys, &boot_image_dev);
271 rp = &rproc[boot_image_priv - boot_image_priv_table];
272 rpub = rp->r_pub;
275 * Get a copy of the executable image if required.
277 rp->r_exec_len = 0;
278 rp->r_exec = NULL;
279 if(boot_image_sys->flags & SF_USE_COPY) {
280 exec_image_copy(ip - image, ip, rp);
284 * Set privileges.
286 /* Get label. */
287 strcpy(rpub->label, boot_image_priv->label);
289 if(boot_image_priv->endpoint != RS_PROC_NR) {
290 /* Force a static priv id for system services in the boot image. */
291 rp->r_priv.s_id = static_priv_id(
292 _ENDPOINT_P(boot_image_priv->endpoint));
294 /* Initialize privilege bitmaps. */
295 rp->r_priv.s_flags = boot_image_priv->flags; /* priv flags */
296 rp->r_priv.s_trap_mask = boot_image_priv->trap_mask; /* traps */
297 memcpy(&rp->r_priv.s_ipc_to, &boot_image_priv->ipc_to,
298 sizeof(rp->r_priv.s_ipc_to)); /* targets */
300 /* Initialize kernel call mask bitmap from unordered set. */
301 fill_call_mask(boot_image_priv->k_calls, NR_SYS_CALLS,
302 rp->r_priv.s_k_call_mask, KERNEL_CALL, TRUE);
304 /* Set the privilege structure. */
305 if ((s = sys_privctl(ip->endpoint, SYS_PRIV_SET_SYS, &(rp->r_priv)))
306 != OK) {
307 panic("RS", "unable to set privilege structure", s);
311 /* Synch the privilege structure with the kernel. */
312 if ((s = sys_getpriv(&(rp->r_priv), ip->endpoint)) != OK) {
313 panic("RS", "unable to synch privilege structure", s);
317 * Set sys properties.
319 rpub->sys_flags = boot_image_sys->flags; /* sys flags */
322 * Set dev properties.
324 rpub->dev_nr = boot_image_dev->dev_nr; /* major device number */
325 rpub->dev_style = boot_image_dev->dev_style; /* device style */
326 rpub->period = boot_image_dev->period; /* heartbeat period */
328 /* Get process name. */
329 strcpy(rpub->proc_name, ip->proc_name);
331 /* Get command settings. */
332 rp->r_cmd[0]= '\0';
333 rp->r_argv[0] = rp->r_cmd;
334 rp->r_argv[1] = NULL;
335 rp->r_argc = 1;
336 rp->r_script[0]= '\0';
338 /* Initialize vm call mask bitmap from unordered set. */
339 fill_call_mask(boot_image_priv->vm_calls, NR_VM_CALLS,
340 rpub->vm_call_mask, VM_RQ_BASE, TRUE);
342 /* Get some settings from the boot image table. */
343 rp->r_nice = ip->priority;
344 rpub->endpoint = ip->endpoint;
346 /* Set some defaults. */
347 rp->r_uid = 0; /* root */
348 rp->r_check_tm = 0; /* not checked yet */
349 getuptime(&rp->r_alive_tm); /* currently alive */
350 rp->r_stop_tm = 0; /* not exiting yet */
351 rp->r_restarts = 0; /* no restarts so far */
352 rp->r_set_resources = 0; /* don't set resources */
354 /* Mark as in use. */
355 rp->r_flags = RS_IN_USE;
356 rproc_ptr[_ENDPOINT_P(rpub->endpoint)]= rp;
357 rpub->in_use = TRUE;
360 /* - Step 2: allow every system service in the boot image to run.
362 nr_uncaught_init_srvs = 0;
363 for (i=0; boot_image_priv_table[i].endpoint != NULL_BOOT_NR; i++) {
364 boot_image_priv = &boot_image_priv_table[i];
366 /* System services only. */
367 if(iskerneln(_ENDPOINT_P(boot_image_priv->endpoint))) {
368 continue;
371 /* Ignore RS. */
372 if(boot_image_priv->endpoint == RS_PROC_NR) {
373 continue;
376 /* Lookup the corresponding slot in the system process table. */
377 rp = &rproc[boot_image_priv - boot_image_priv_table];
378 rpub = rp->r_pub;
380 /* Allow the service to run. */
381 if ((s = sys_privctl(rpub->endpoint, SYS_PRIV_ALLOW, NULL)) != OK) {
382 panic("RS", "unable to initialize privileges", s);
385 /* Initialize service. We assume every service will always get
386 * back to us here at boot time.
388 if(boot_image_priv->flags & SYS_PROC) {
389 if ((s = init_service(rp, SEF_INIT_FRESH)) != OK) {
390 panic("RS", "unable to initialize service", s);
392 if(rpub->sys_flags & SF_SYNCH_BOOT) {
393 /* Catch init ready message now to synchronize. */
394 catch_boot_init_ready(rpub->endpoint);
396 else {
397 /* Catch init ready message later. */
398 nr_uncaught_init_srvs++;
403 /* - Step 3: let every system service complete initialization by
404 * catching all the init ready messages left.
406 while(nr_uncaught_init_srvs) {
407 catch_boot_init_ready(ANY);
408 nr_uncaught_init_srvs--;
411 /* - Step 4: all the system services in the boot image are now running.
412 * Complete the initialization of the system process table in collaboration
413 * with other system processes.
415 if ((s = getsysinfo(PM_PROC_NR, SI_PROC_TAB, mproc)) != OK) {
416 panic("RS", "unable to get copy of PM process table", s);
418 for (i=0; boot_image_priv_table[i].endpoint != NULL_BOOT_NR; i++) {
419 boot_image_priv = &boot_image_priv_table[i];
421 /* System services only. */
422 if(iskerneln(_ENDPOINT_P(boot_image_priv->endpoint))) {
423 continue;
426 /* Lookup the corresponding slot in the system process table. */
427 rp = &rproc[boot_image_priv - boot_image_priv_table];
428 rpub = rp->r_pub;
430 /* Get pid from PM process table. */
431 rp->r_pid = NO_PID;
432 for (j = 0; j < NR_PROCS; j++) {
433 if (mproc[j].mp_endpoint == rpub->endpoint) {
434 rp->r_pid = mproc[j].mp_pid;
435 break;
438 if(j == NR_PROCS) {
439 panic("RS", "unable to get pid", NO_NUM);
444 * Now complete RS initialization process in collaboration with other
445 * system services.
447 /* Let the rest of the system know about our dynamically allocated buffer. */
448 if(boot_image_buffer_size > 0) {
449 boot_image_buffer = rs_startup_sbrk_synch(boot_image_buffer_size);
450 if(boot_image_buffer == (char *) -1) {
451 panic("RS", "unable to synch boot image buffer", NO_NUM);
455 /* Set alarm to periodically check service status. */
456 if (OK != (s=sys_setalarm(RS_DELTA_T, 0)))
457 panic("RS", "couldn't set alarm", s);
459 /* Install signal handlers. Ask PM to transform signal into message. */
460 sa.sa_handler = SIG_MESS;
461 sigemptyset(&sa.sa_mask);
462 sa.sa_flags = 0;
463 if (sigaction(SIGCHLD,&sa,NULL)<0) panic("RS","sigaction failed", errno);
464 if (sigaction(SIGTERM,&sa,NULL)<0) panic("RS","sigaction failed", errno);
466 /* Initialize the exec pipe. */
467 if (pipe(exec_pipe) == -1)
468 panic("RS", "pipe failed", errno);
469 if (fcntl(exec_pipe[0], F_SETFD,
470 fcntl(exec_pipe[0], F_GETFD) | FD_CLOEXEC) == -1)
472 panic("RS", "fcntl set FD_CLOEXEC on pipe input failed", errno);
474 if (fcntl(exec_pipe[1], F_SETFD,
475 fcntl(exec_pipe[1], F_GETFD) | FD_CLOEXEC) == -1)
477 panic("RS", "fcntl set FD_CLOEXEC on pipe output failed", errno);
479 if (fcntl(exec_pipe[0], F_SETFL,
480 fcntl(exec_pipe[0], F_GETFL) | O_NONBLOCK) == -1)
482 panic("RS", "fcntl set O_NONBLOCK on pipe input failed", errno);
485 /* Map out our own text and data. This is normally done in crtso.o
486 * but RS is an exception - we don't get to talk to VM so early on.
487 * That's why we override munmap() and munmap_text() in utility.c.
489 * _minix_unmapzero() is the same code in crtso.o that normally does
490 * it on startup. It's best that it's there as crtso.o knows exactly
491 * what the ranges are of the filler data.
493 unmap_ok = 1;
494 _minix_unmapzero();
496 return(OK);
499 /*===========================================================================*
500 * exec_image_copy *
501 *===========================================================================*/
502 PRIVATE void exec_image_copy(boot_proc_idx, ip, rp)
503 int boot_proc_idx;
504 struct boot_image *ip;
505 struct rproc *rp;
507 /* Copy the executable image of the given boot process. */
508 int s;
509 struct exec header;
510 static char *boot_image_ptr = NULL;
512 if(boot_image_ptr == NULL) {
513 boot_image_ptr = boot_image_buffer;
515 s = NO_NUM;
517 /* Get a.out header. */
518 if(boot_image_buffer+boot_image_buffer_size - boot_image_ptr < sizeof(header)
519 || (s = sys_getaoutheader(&header, boot_proc_idx)) != OK) {
520 panic("RS", "unable to get copy of a.out header", s);
522 memcpy(boot_image_ptr, &header, header.a_hdrlen);
523 boot_image_ptr += header.a_hdrlen;
525 /* Get text segment. */
526 if(boot_image_buffer+boot_image_buffer_size - boot_image_ptr < header.a_text
527 || (s = rs_startup_segcopy(ip->endpoint, T, D, (vir_bytes) boot_image_ptr,
528 header.a_text)) != OK) {
529 panic("RS", "unable to get copy of text segment", s);
531 boot_image_ptr += header.a_text;
533 /* Get data segment. */
534 if(boot_image_buffer+boot_image_buffer_size - boot_image_ptr < header.a_data
535 || (s = rs_startup_segcopy(ip->endpoint, D, D, (vir_bytes) boot_image_ptr,
536 header.a_data)) != OK) {
537 panic("RS", "unable to get copy of data segment", s);
539 boot_image_ptr += header.a_data;
541 /* Set the executable image for the given boot process. */
542 rp->r_exec_len = header.a_hdrlen + header.a_text + header.a_data;
543 rp->r_exec = boot_image_ptr - rp->r_exec_len;
546 /*===========================================================================*
547 * boot_image_info_lookup *
548 *===========================================================================*/
549 PRIVATE void boot_image_info_lookup(endpoint, image, ip, pp, sp, dp)
550 endpoint_t endpoint;
551 struct boot_image *image;
552 struct boot_image **ip;
553 struct boot_image_priv **pp;
554 struct boot_image_sys **sp;
555 struct boot_image_dev **dp;
557 /* Lookup entries in boot image tables. */
558 int i;
560 /* When requested, locate the corresponding entry in the boot image table
561 * or panic if not found.
563 if(ip) {
564 for (i=0; i < NR_BOOT_PROCS; i++) {
565 if(image[i].endpoint == endpoint) {
566 *ip = &image[i];
567 break;
570 if(i == NR_BOOT_PROCS) {
571 panic("RS", "boot image table lookup failed", NO_NUM);
575 /* When requested, locate the corresponding entry in the boot image priv table
576 * or panic if not found.
578 if(pp) {
579 for (i=0; boot_image_priv_table[i].endpoint != NULL_BOOT_NR; i++) {
580 if(boot_image_priv_table[i].endpoint == endpoint) {
581 *pp = &boot_image_priv_table[i];
582 break;
585 if(i == NULL_BOOT_NR) {
586 panic("RS", "boot image priv table lookup failed", NO_NUM);
590 /* When requested, locate the corresponding entry in the boot image sys table
591 * or resort to the default entry if not found.
593 if(sp) {
594 for (i=0; boot_image_sys_table[i].endpoint != DEFAULT_BOOT_NR; i++) {
595 if(boot_image_sys_table[i].endpoint == endpoint) {
596 *sp = &boot_image_sys_table[i];
597 break;
600 if(boot_image_sys_table[i].endpoint == DEFAULT_BOOT_NR) {
601 *sp = &boot_image_sys_table[i]; /* accept the default entry */
605 /* When requested, locate the corresponding entry in the boot image dev table
606 * or resort to the default entry if not found.
608 if(dp) {
609 for (i=0; boot_image_dev_table[i].endpoint != DEFAULT_BOOT_NR; i++) {
610 if(boot_image_dev_table[i].endpoint == endpoint) {
611 *dp = &boot_image_dev_table[i];
612 break;
615 if(boot_image_dev_table[i].endpoint == DEFAULT_BOOT_NR) {
616 *dp = &boot_image_dev_table[i]; /* accept the default entry */
621 /*===========================================================================*
622 * catch_boot_init_ready *
623 *===========================================================================*/
624 PRIVATE void catch_boot_init_ready(endpoint)
625 endpoint_t endpoint;
627 /* Block and catch an init ready message from the given source. */
628 int r;
629 message m;
630 struct rproc *rp;
631 int result;
633 /* Receive init ready message. */
634 if ((r = receive(endpoint, &m)) != OK) {
635 panic("RS", "unable to receive init reply", r);
637 if(m.m_type != RS_INIT) {
638 panic("RS", "unexpected reply from service", m.m_source);
640 result = m.RS_INIT_RESULT;
641 rp = rproc_ptr[_ENDPOINT_P(m.m_source)];
643 /* Check result. */
644 if(result != OK) {
645 panic("RS", "unable to complete init for service", m.m_source);
648 /* Mark the slot as no longer initializing. */
649 rp->r_flags &= ~RS_INITIALIZING;
650 rp->r_check_tm = 0;
651 getuptime(&rp->r_alive_tm);
654 /*===========================================================================*
655 * sig_handler *
656 *===========================================================================*/
657 PRIVATE void sig_handler()
659 sigset_t sigset;
660 int sig;
662 /* Try to obtain signal set from PM. */
663 if (getsigset(&sigset) != 0) return;
665 /* Check for known signals. */
666 if (sigismember(&sigset, SIGCHLD)) do_exit(NULL);
667 if (sigismember(&sigset, SIGTERM)) do_shutdown(NULL);
670 /*===========================================================================*
671 * get_work *
672 *===========================================================================*/
673 PRIVATE void get_work(m_in)
674 message *m_in; /* pointer to message */
676 int s; /* receive status */
677 if (OK != (s=sef_receive(ANY, m_in))) /* wait for message */
678 panic("RS", "sef_receive failed", s);
681 /*===========================================================================*
682 * reply *
683 *===========================================================================*/
684 PRIVATE void reply(who, m_out)
685 int who; /* replyee */
686 message *m_out; /* reply message */
688 int s; /* send status */
690 s = sendnb(who, m_out); /* send the message */
691 if (s != OK)
692 printf("RS: unable to send reply to %d: %d\n", who, s);