chiark / gitweb /
Stupid thing doesn't automatically distribute manpages.
[sw-tools] / src / sw_rsh.c
1 /* -*-c-*-
2  *
3  * $Id: sw_rsh.c,v 1.2 1999/06/02 17:03:29 mdw Exp $
4  *
5  * Run remote commands
6  *
7  * (c) 1999 EBI
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of sw-tools.
13  *
14  * sw-tools is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * sw-tools is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with sw-tools; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: sw_rsh.c,v $
32  * Revision 1.2  1999/06/02 17:03:29  mdw
33  * Fix use of `octet' now that mLib includes `bits.h' (as of version 1.3.5
34  * release).  Also use the mLib load and store macros rather than doing it
35  * by hand.
36  *
37  * Revision 1.1.1.1  1999/06/02  16:53:34  mdw
38  * Initial import.
39  *
40  */
41
42 /*----- Header files ------------------------------------------------------*/
43
44 #include "config.h"
45
46 #include <ctype.h>
47 #include <errno.h>
48 #include <signal.h>
49 #include <stdarg.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53
54 #include <sys/types.h>
55 #include <sys/time.h>
56 #include <fcntl.h>
57 #include <unistd.h>
58
59 #include <sys/socket.h>
60 #include <sys/wait.h>
61
62 #ifndef DECL_ENVIRON
63   extern char **environ;
64 #endif
65
66 #include <mLib/alloc.h>
67 #include <mLib/bits.h>
68 #include <mLib/dstr.h>
69 #include <mLib/exc.h>
70 #include <mLib/mdwopt.h>
71 #include <mLib/quis.h>
72 #include <mLib/report.h>
73 #include <mLib/sym.h>
74
75 #define RCMD_LINK 0
76 #include "sw_arch.h"
77 #include "sw_build.h"
78 #include "sw_env.h"
79 #include "sw_rsh.h"
80
81 /*----- Data structures ---------------------------------------------------*/
82
83 #define PKHEADSZ 3
84
85 typedef struct pkhead {
86   octet len[2];
87   octet type;
88 } pkhead;
89
90 /*----- Static variables --------------------------------------------------*/
91
92 static int handler = 0;
93 static rcmd *rcmds = RCMD_LINK;
94
95 /*----- Packet interface --------------------------------------------------*/
96
97 /* --- @pksend@ --- *
98  *
99  * Arguments:   @sw_remote@ = pointer to the remote block
100  *              @int type@ = packet type to send
101  *              @const void *p@ = pointer to packet data
102  *              @size_t sz@ = size of data to send
103  *
104  * Returns:     Zero if it worked, nonzero otherwise.
105  *
106  * Use:         Sends a data packet.  If the type is `data', then `sz' may be
107  *              arbitrarily large and is divided into small eenough chunks.
108  *              Otherwise it's an error to send a packet that's too big.
109  */
110
111 int pksend(sw_remote *r, int type, const void *p, size_t sz)
112 {
113   pkhead h;
114   const char *q = p;
115   size_t chunk;
116
117   /* --- Sort out error conditions --- */
118
119   if (sz > PKMAX && type != PKTYPE_DATA) {
120     errno = E2BIG;
121     return (-1);
122   }
123
124   /* --- Main output loop --- */
125
126   h.type = type;
127   do {
128
129     /* --- Set up the packet header --- */
130
131     chunk = (sz > PKMAX ? PKMAX : sz);
132     STORE16(h.len, chunk);
133
134     /* --- Write the packet header --- */
135
136   try_again:
137     if (write(r->fdout, &h, PKHEADSZ) < PKHEADSZ) {
138       if (errno == EINTR)
139         goto try_again;
140       return (-1);
141     }
142
143     /* --- Write the payload, if there is one --- *
144      *
145      * Maybe the OS won't want to bite it all off in one go.
146      */
147
148     while (chunk) {
149       ssize_t n = write(r->fdout, q, chunk);
150       if (n < 0 && errno != EINTR)
151         return (-1);
152       if (n > 0) {
153         q += n;
154         chunk -= n;
155         sz -= n;
156       }
157     }
158   } while (sz);
159
160   /* --- Done --- */
161
162   return (0);
163 }
164
165 /* --- @pkrecv@ --- *
166  *
167  * Arguments:   @sw_remote *r@ = remote block
168  *
169  * Returns:     Packet type received, or @-1@ for an error.
170  *
171  * Use:         Receives a packet from the remote host.  The packet data is
172  *              placed in the block's buffer, the block's packet length is
173  *              diddled appropriately.
174  */
175
176 int pkrecv(sw_remote *r)
177 {
178   pkhead h;
179   size_t sz;
180   char *p;
181   ssize_t n;
182
183   /* --- Read the packet header --- */
184
185   sz = PKHEADSZ;
186   p = (char *)&h;
187   while (sz) {
188     n = read(r->fdin, p, sz);
189     if (n < 0 && errno != EINTR)
190       return (-1);
191     if (n == 0)
192       return (PKTYPE_EOF);
193     if (n > 0) {
194       p += n;
195       sz -= n;
196     }
197   }
198
199   /* --- Hack for error messages --- *
200    *
201    * If it doesn't look like a valid packet, read a `chunk' and pretend it's
202    * data.  This isn't too bad, because all the packet types are control
203    * codes, and are unlikely to be in a textual message.
204    *
205    * Normally what happens here is that something sitting before the `sw'
206    * program fails, reports a plain textual error, and exits.  Grabbing the
207    * `last gasp' like this, then, traps that error message and allows
208    * something to report it.  The rest ought to be completely silent, so I
209    * get an `unexpected eof' and then drop everything.
210    *
211    * This is certainly better than the behaviour otherwise, which is an
212    * @E2BIG@ message reported when the packet size is really ASCII
213    * characters.
214    */
215
216   if (h.type >= PKTYPE_BOGUS) {
217     memcpy(r->buf, &h, PKHEADSZ);
218     n = read(r->fdin, r->buf + PKHEADSZ, sizeof(r->buf) - PKHEADSZ);
219     if (n < 0)
220       return (-1);
221     r->sz = n + PKHEADSZ;
222     return (PKTYPE_DATA);
223   }
224
225   /* --- Sort out what's going on --- */
226
227   sz = LOAD16(h.len);
228   r->sz = sz;
229   if (!sz)
230     return (h.type);
231   if (sz > PKMAX) {
232     errno = E2BIG;
233     return (-1);
234   }
235
236   /* --- Read the packet payload --- */
237
238   p = r->buf;
239   while (sz) {
240     n = read(r->fdin, p, sz);
241     if (n < 0 && errno != EINTR)
242       return (-1);
243     if (n == 0)
244       return (PKTYPE_EOF);
245     if (n > 0) {
246       p += n;
247       sz -= n;
248     }
249   }
250
251   return (h.type);
252 }
253
254 /*----- Error reporting and exit statuses --------------------------------*/
255
256 /* --- @swexit@ --- *
257  *
258  * Arguments:   @sw_remote *r@ = remote context
259  *              @int status@ = exit status to return
260  *
261  * Returns:     Doesn't.
262  *
263  * Use:         Reports the exit status via packet protocol and quits.
264  */
265
266 void swexit(sw_remote *r, int status)
267 {
268   unsigned char s = status;
269   pksend(r, PKTYPE_STATUS, &s, 1);
270   _exit(status);
271 }
272
273 /* --- @swsignal@ --- *
274  *
275  * Arguments:   @sw_remote *r@ = remote context
276  *              @int sig@ = signal ocurrence to return
277  *
278  * Returns:     Doesn't.
279  *
280  * Use:         Reports a signalled-to-death status via packet protocol and
281  *              quits.
282  */
283
284 void swsignal(sw_remote *r, int sig)
285 {
286 #if defined(HAVE_STRSIGNAL)
287   char *s = strsignal(sig);
288 #elif defined(HAVE__SYS_SIGLIST)
289   char *s = _sys_siglist[sig];
290 #else
291   char s[16];
292   sprintf(s, "signal %i", sig);
293 #endif
294
295   pksend(r, PKTYPE_STATUS, s, strlen(s) + 1);
296   _exit(127);
297 }
298
299 /* --- @swwait@ --- *
300  *
301  * Arguments:   @sw_remote *r@ = remote context
302  *              @int status@ = status answer from @wait@(2)
303  *
304  * Returns:     Doesn't.
305  *
306  * Use:         Reports a child's demise appropriately, and quits.
307  */
308
309 void swwait(sw_remote *r, int status)
310 {
311   if (WIFEXITED(status))
312     swexit(r, WEXITSTATUS(status));
313   else if (WIFSIGNALED(status))
314     swsignal(r, WTERMSIG(status));
315   else
316     swexit(r, 126);
317 }
318
319 /* --- @swvprintf@ --- *
320  *
321  * Arguments:   @sw_remote *r@ = remote context
322  *              @const char *format@ = format string
323  *              @va_list ap@ = things to format
324  *
325  * Returns:     ---
326  *
327  * Use:         Writes a string to the remote end.  This is the low-level bit
328  *              of @swprintf@.
329  */
330
331 void swvprintf(sw_remote *r, const char *format, va_list ap)
332 {
333   dstr d = DSTR_INIT;
334   dstr_vputf(&d, format, ap);
335   pksend(r, PKTYPE_DATA, d.buf, d.len);
336   dstr_destroy(&d);
337 }
338
339 /* --- @swprintf@ --- *
340  *
341  * Arguments:   @sw_remote *r@ = remote context
342  *              @const char *format@ = format string
343  *              @...@ = other arguments
344  *
345  * Returns:     ---
346  *
347  * Use:         Writes a string to the remote end.
348  */
349
350 void swprintf(sw_remote *r, const char *format, ...)
351 {
352   va_list ap;
353   va_start(ap, format);
354   swvprintf(r, format, ap);
355   va_end(ap);
356 }
357
358 /* --- @swdie@ --- *
359  *
360  * Arguments:   @sw_remote *r@ = remote context
361  *              @int status@ = exit status to report
362  *              @const char *format@ = format string to fill in
363  *              @...@ = other arguments
364  *
365  * Returns:     Doesn't.
366  *
367  * Use:         Reports a message and quits.
368  */
369
370 void swdie(sw_remote *r, int status, const char *format, ...)
371 {
372   va_list ap;
373   dstr d = DSTR_INIT;
374
375   va_start(ap, format);
376   dstr_putf(&d, "%s [remote]: ", QUIS);
377   dstr_vputf(&d, format, ap);
378   dstr_putc(&d, '\n');
379   dstr_putz(&d);
380   va_end(ap);
381   pksend(r, PKTYPE_DATA, d.buf, d.len);
382   dstr_destroy(&d);
383   swexit(r, status);
384 }
385
386 /*----- Command handling and dispatch -------------------------------------*/
387
388 /* --- @remote@ --- *
389  *
390  * Arguments:   @sw_remote *r@ = pointer to remote block
391  *              @const char *cmd@ = command to run
392  *              @char *argv[]@ = argument array
393  *              @char *env[]@ = environment variables
394  *
395  * Returns:     Doesn't.  Reports an exit status through packet protocol and
396  *              quits.
397  *
398  * Use:         Dispatches a remote command.  At this point, the two code
399  *              paths for local and remote invokation have joined again.
400  */
401
402 static void remote(sw_remote *r, const char *cmd, char *argv[], char *env[])
403 {
404   struct rcmd *p, *chosen = 0;
405   size_t sz = strlen(cmd);
406
407   /* --- Make sure that I can get the exit status of children --- */
408
409   signal(SIGCHLD, SIG_DFL);
410
411   /* --- Fix up the environment --- */
412
413   {
414     sym_table t;
415     sym_create(&t);
416     env_import(&t, env);
417     if (env != environ) {
418       free(env);
419       env_import(&t, environ);
420     }
421     env_put(&t, "SW_ARCH", ARCH);
422     env_file(&t, DATADIR "/sw-env");
423     env = env_export(&t);
424   }
425
426   /* --- Dispatch to the command handler --- */
427
428   for (p = rcmds; p; p = p->next) {
429     if (strncmp(cmd, p->name, sz) == 0) {
430       if (p->name[sz] == 0) {
431         chosen = p;
432         break;
433       } else if (chosen)
434         swdie(r, 1, "ambiguous remote command name `%s'", cmd);
435       else
436         chosen = p;
437     }
438   }
439   if (!chosen)
440     swdie(r, 1, "unknown remote command name `%s'", cmd);
441   chosen->rcmd(r, argv, env);
442 }
443
444 /*----- Remote invocation -------------------------------------------------*/
445
446 /* --- @sendargv@ --- *
447  *
448  * Arguments:   @sw_remote *r@ = pointer to the remote context
449  *              @int type@ = packet type to send with
450  *              @char *v[]@ = pointer to the array to send
451  *
452  * Returns:     Zero if OK, nonzero if it failed.
453  *
454  * Use:         Sends something @argv@-shaped; i.e., an array of strings
455  *              terminated by a null pointer.
456  */
457
458 static int sendargv(sw_remote *r, int type, char *v[])
459 {
460   dstr d = DSTR_INIT;
461   int e;
462
463   while (*v) {
464     dstr_puts(&d, *v);
465     d.len++;                            /* Make the null `real' */
466     v++;
467   }
468   e = pksend(r, type, d.buf, d.len);
469   dstr_destroy(&d);
470   return (e);
471 }
472
473 /* --- @snarfargv@ --- *
474  *
475  * Arguments:   @const char *buf@ = pointer to buffer
476  *              @size_t sz@ = size of buffer
477  *
478  * Returns:     Pointer to argument array (allocated with @malloc@).
479  *
480  * Use:         Snarfs the null-terminated strings in the buffer and returns
481  *              an array of them.  The whole lot, strings and array, is
482  *              returned in one big chunk allocated from the heap.  Note that
483  *              this means it's OK to throw the initial buffer away.
484  */
485
486 static char **snarfargv(const char *buf, size_t sz)
487 {
488   /* --- Initial setup --- */
489
490   const char *p, *lim;
491   char *q;
492   size_t c;
493   char **v, **w;
494
495   /* --- Pass one: count the number of arguments --- */
496
497   c = 0;
498   p = buf;
499   lim = p + sz;
500   while (p < lim) {
501     c++;
502     while (*p) {
503       p++;
504       if (p >= lim)
505         goto done_pass1;
506     }
507     p++;
508   }
509 done_pass1:
510
511   /* --- Allocate memory for everything --- */
512
513   v = xmalloc((c + 1) * sizeof(char *) + sz + 1);
514   q = (char *)(v + c + 1);
515   memcpy(q, buf, sz);
516
517   /* --- Pass two: set up the arrays --- */
518
519   lim = q + sz;
520   w = v;
521   while (q < lim) {
522     *w++ = q;
523     while (*q) {
524       q++;
525       if (q >= lim)
526         goto done_pass2;
527     }
528     q++;
529   }
530 done_pass2:
531   *w++ = 0;
532   *q++ = 0;
533
534   /* --- Done --- */
535
536   return (v);
537 }
538
539 /* --- @swrsh_remote@ --- *
540  *
541  * Arguments:   @const char *cmd@ = the command to perform
542  *
543  * Returns:     Doesn't.  Reports an exit status through packet protocol and
544  *              quits.
545  *
546  * Use:         Handles the remote end of a remote job invokation.
547  */
548
549 void swrsh_remote(const char *cmd)
550 {
551   sw_remote r;
552   static char *dummy = 0;
553   char **argv = 0;
554   char **env = 0;
555   char *dir = 0;
556   r.fdin = 0;
557   r.fdout = 1;
558
559  /* --- Read packets from the remote host --- */
560
561   for (;;) {
562     int t = pkrecv(&r);
563     switch (t) {
564       case -1:
565         swdie(&r, 1, "error reading packet: %s", strerror(errno));
566         break;
567       case PKTYPE_EOF:
568         goto done;
569       case PKTYPE_ARGS:
570         if (argv)
571           free(argv);
572         argv = snarfargv(r.buf, r.sz);
573         break;
574       case PKTYPE_ENV:
575         if (env)
576           free(env);
577         env = snarfargv(r.buf, r.sz);
578         break;
579       case PKTYPE_DIR:
580         if (dir)
581           free(dir);
582         r.buf[r.sz] = 0;
583         dir = xstrdup(r.buf);
584         break;
585       case PKTYPE_GO:
586         goto done;
587       case PKTYPE_DATA:
588       case PKTYPE_STATUS:
589         swdie(&r, 1, "internal error: unexpected packet");
590         break;
591     }
592   }
593
594   /* --- Sort out any missing arguments --- */
595
596 done:
597   if (!argv)
598     argv = &dummy;
599   if (!env)
600     env = &dummy;
601   if (dir)
602     chdir(dir);
603
604   /* --- Run the command --- */
605
606   TRY
607     remote(&r, cmd, argv, env);
608   CATCH switch (exc_type) {
609     case EXC_NOMEM: {
610       static char msg[] = "\nsw [remote]: not enough memory\n";
611       pksend(&r, PKTYPE_DATA, msg, sizeof(msg) - 1);
612       swexit(&r, 1);
613     } break;
614     default:
615       swdie(&r, 1, "uncaught exception, type = %lx", exc_type);
616   } END_TRY;
617 }
618
619 /*----- Starting remote jobs ----------------------------------------------*/
620
621 /* --- @sigchld@ --- *
622  *
623  * Arguments:   @int sig@ = the signal number
624  *
625  * Returns:     ---
626  *
627  * Use:         Catches @SIGCHLD@ and reaps any children that have lost.
628  */
629
630 static void sigchld(int sig)
631 {
632 #ifdef DEBUG_SIGCHLD
633   int status;
634   while (waitpid(-1, &status, WNOHANG) > 0) {
635     if (WIFEXITED(status)) {
636       fprintf(stderr, "reap child with exit status %i\n",
637               WEXITSTATUS(status));
638     } else if (WIFSIGNALED(status)) {
639       fprintf(stderr, "reap child killed by signal %s\n",
640               strsignal(WTERMSIG(status)));
641     } else
642       fprintf(stderr, "reaped bizarre child which is still alive\n");
643   }
644 #else
645   while (waitpid(-1, 0, WNOHANG) > 0)
646     ;
647 #endif
648 }
649
650 /* --- @swrsh@ --- *
651  *
652  * Arguments:   @sw_remote *r@ = remote process block to look after
653  *              @const char *host@ = host to run on (0 for this one)
654  *              @const char *cmd@ = remote command to run
655  *              @char *argv[]@ = arguments to pass on
656  *
657  * Returns:     Zero if it worked, nonzero if not.
658  *
659  * Use:         Runs a command on a remote host.  The argument array is
660  *              mangled to come out OK at the far end.  The environment and
661  *              current directory are also passed along, and pop out the
662  *              other end unmolested.
663  */
664
665 int swrsh(sw_remote *r, const char *host, const char *cmd, char *argv[])
666 {
667   int sk[2];
668   pid_t kid;
669
670   /* --- Get a socket pair for communicating with the other end --- */
671
672   if (socketpair(PF_UNIX, SOCK_STREAM, 0, sk))
673     goto tidy_0;
674
675   /* --- Set up a signal handler --- */
676
677   if (!handler) {
678     struct sigaction sa;
679     sa.sa_handler = sigchld;
680     sa.sa_flags = 0;
681     sigemptyset(&sa.sa_mask);
682     sigaction(SIGCHLD, &sa, 0);
683     handler = 1;
684   }
685
686   /* --- Fork off a child to cope with stuff --- */
687
688   kid = fork();
689   if (kid < 0)
690     goto tidy_1;
691
692   /* --- Handle the child process --- *
693    *
694    * If this is a local job, then just loop around inside to handle the
695    * `remote' command.  Otherwise crank up `rsh' and pass the command over to
696    * a remote copy of myself.
697    *
698    * (Why do I need a separate process for local jobs?  I don't really, but
699    * it makes everything much simpler when running multiple jobs at the same
700    * time.)
701    */
702
703   if (kid == 0) {
704     close(sk[0]);
705
706     /* --- Child end of a local job --- */
707
708     if (!host) {
709       r->fdin = r->fdout = sk[1];
710       remote(r, cmd, argv, environ);
711     }
712
713     /* --- Local child end of a remote job --- */
714
715     else {
716       const char *rsh;
717       dup2(sk[1], 0);
718       dup2(sk[1], 1);
719       dup2(sk[1], 2);
720       if (sk[1] > 2)
721         close(sk[1]);
722       rsh = getenv("SW_RSH");
723       if (!rsh)
724         rsh = RSH;
725       execlp(rsh, rsh, host, PATH_SW, "--remote", cmd, (char *)0);
726     }
727
728     /* --- I don't expect either to come back --- */
729
730     _exit(1);
731   }
732
733   /* --- Local sort out of what to do --- *
734    *
735    * Either way, I've now got a socket tied to something which speaks my
736    * communication protocol.  However, if this is a local job, then I can get
737    * going right away; otherwise, I've got to transmit various bits of
738    * information over the protocol.
739    */
740
741   r->fdin = r->fdout = sk[0];
742   close(sk[1]);
743
744   if (host) {
745     char buf[PKMAX];
746     if (!getcwd(buf, sizeof(buf)))
747       goto tidy_1;
748     sendargv(r, PKTYPE_ARGS, argv);
749     sendargv(r, PKTYPE_ENV, environ);
750     pksend(r, PKTYPE_DIR, buf, strlen(buf) + 1);
751     pksend(r, PKTYPE_GO, 0, 0);
752   }
753
754   /* --- Ready to rock'n'roll --- */
755
756   r->sz = 0;
757   return (0);
758
759   /* --- Tidy up if it failed --- */
760
761 tidy_1:
762   close(sk[0]);
763   close(sk[1]);
764 tidy_0:
765   return (-1);
766 }
767
768 /*----- Subcommands -------------------------------------------------------*/
769
770 /* --- @swrsh_rsh@ --- */
771
772 void rsw_rsh(sw_remote *r, char *argv[], char *env[])
773 {
774   pid_t kid, k;
775   int pfd[2];
776   int status;
777
778   /* --- Create a pipe --- */
779
780   if (pipe(pfd))
781     swdie(r, 1, "couldn't create pipe: %s", strerror(errno));
782
783   /* --- Start the child process up --- */
784
785   kid = fork();
786   if (kid < 0)
787     swdie(r, 1, "fork failed: %s", strerror(errno));
788   else if (kid == 0) {
789     int fd;
790
791     /* --- Use my new environment --- */
792
793     environ = env; /* Yuk. */
794
795     /* --- Fiddle with pipe file descriptors --- */
796
797     close(pfd[0]);
798     dup2(pfd[1], 1);
799     dup2(pfd[1], 2);
800     if (pfd[1] > 2)
801       close(pfd[1]);
802
803     /* --- Make sure it doesn't get any input --- */
804
805     close(0);
806     fd = open("/dev/null", O_RDONLY);
807     if (fd != 0) {
808       dup2(fd, 0);
809       close(fd);
810     }
811
812     /* --- Run the program --- */
813
814     execvp(argv[0], argv);
815     die(1, "couldn't exec `%s': %s", argv[0], strerror(errno));
816   }
817
818   /* --- Read the data from the pipe until it closes --- */
819
820   close(pfd[1]);
821   for (;;) {
822     ssize_t n = read(pfd[0], r->buf, sizeof(r->buf));
823     if (n < 0)
824       swdie(r, 1, "read error: %s", strerror(errno));
825     else if (!n)
826       break;
827     else
828       pksend(r, PKTYPE_DATA, r->buf, n);
829   }
830   close(pfd[0]);
831
832   /* --- Finally, reap the exit status and pass it on --- */
833
834   for (;;) {
835     k = wait(&status);
836     if (k == kid)
837       break;
838     if (k < 0)
839       swdie(r, 1, "error reaping child: %s", strerror(errno));
840   }
841   swwait(r, status);
842 }
843
844 /* --- @sw_rsh@ --- */
845
846 int sw_rsh(int argc, char *argv[])
847 {
848   sw_remote r;
849   char *h;
850   int status = 1;
851
852   /* --- Check the arguments --- */
853
854   if (argc < 3)
855     die(1, "Usage: rsh HOST|ARCH COMMAND [ARGS...]");
856
857   /* --- Translate architecture names into hostnames --- */
858
859   if (strcmp(argv[1], "-") == 0)
860     h = 0;
861   else {
862     archent *a = arch_lookup(argv[1], 0);
863     if (!a)
864       h = argv[1];
865     else if (a->flags & archFlag_home)
866       h = 0;
867     else
868       h = a->host;
869   }
870
871   /* --- Start the remote process --- */
872
873   if (swrsh(&r, h, "rsh", argv + 2))
874     die(1, "remote shell failed: %s", strerror(errno));
875
876   /* --- Cope with packets from the remote process --- */
877
878   fflush(stdout);
879   for (;;) {
880     int t = pkrecv(&r);
881     switch (t) {
882       case -1:
883         die(1, "error reading packet: %s", strerror(errno));
884       case PKTYPE_DATA:
885         write(STDOUT_FILENO, r.buf, r.sz);
886         break;
887       case PKTYPE_STATUS:
888         r.buf[r.sz] = 0;
889         if (r.sz > 1) {
890           status = 127;
891           moan("command exited due to signal: %s", r.buf);
892         } else {
893           status = r.buf[0];
894             moan("command exited with status %i", r.buf[0]);
895         }
896         goto done;
897       case PKTYPE_EOF:
898         moan("command exited unexpectedly");
899         goto done;
900       default:
901         die(1, "unexpected packet type");
902     }
903   }
904
905   /* --- Finished --- */
906
907 done:
908   close(r.fdin);
909   return (status);
910 }
911
912 /*----- That's all, folks -------------------------------------------------*/