xref: /titanic_50/usr/src/cmd/rexd/on.c (revision 0b5ce10aee80822ecc7df77df92a5e24078ba196)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * on - user interface program for remote execution service
24  *
25  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #define	BSD_COMP
32 
33 #include <ctype.h>
34 #include <errno.h>
35 #include <netdb.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include <netinet/in.h>
43 #include <rpc/rpc.h>
44 #include <rpc/clnt_soc.h>
45 #include <rpc/key_prot.h>
46 #include <sys/fcntl.h>
47 #include <sys/ioctl.h>
48 #include <sys/param.h>
49 #include <sys/socket.h>
50 #include <sys/sockio.h>
51 #include <sys/stat.h>
52 #include <sys/time.h>
53 
54 
55 #include <sys/ttold.h>
56 
57 
58 #include "rex.h"
59 
60 #include <stropts.h>
61 #include <sys/stream.h>
62 #include <sys/ttcompat.h>
63 
64 
65 #define	bcmp(b1, b2, len)	memcmp(b1, b2, len)
66 #define	bzero(b, len)		memset(b, '\0', len)
67 #define	bcopy(b1, b2, len)	memcpy(b2, b1, len)
68 
69 #define	CommandName "on"	/* given as argv[0] */
70 #define	AltCommandName "dbon"
71 
72 extern int errno;
73 
74 /*
75  * Note - the following must be long enough for at least two portmap
76  * timeouts on the other side.
77  */
78 struct timeval LongTimeout = { 123, 0 };
79 struct timeval testtimeout = { 5, 0 };
80 
81 int Debug = 0;			/* print extra debugging information */
82 int Only2 = 0;			/* stdout and stderr are the same */
83 int Interactive = 0;		/* use a pty on server */
84 int NoInput = 0;		/* don't read standard input */
85 int child = 0;			/* pid of the executed process */
86 int ChildDied = 0;		/* true when above is valid */
87 int HasHelper = 0;		/* must kill helpers (interactive mode) */
88 
89 int InOut;			/* socket for stdin/stdout */
90 int Err;			/* socket for stderr */
91 
92 struct sgttyb OldFlags;		/* saved tty flags */
93 struct sgttyb NewFlags;		/* for stop/continue job control */
94 CLIENT *Client;			/* RPC client handle */
95 struct rex_ttysize WindowSize;	/* saved window size */
96 
97 static int Argc;
98 static char **Argv;		/* saved argument vector (for ps) */
99 static char *LastArgv;		/* saved end-of-argument vector */
100 
101 void	usage(void);
102 void	Die(int stat);
103 void	doaccept(int *fdp);
104 u_short makeport(int *fdp);
105 
106 
107 /*
108  * window change handler - propagate to remote server
109  */
110 void
111 sigwinch(int junk)
112 {
113 	struct	winsize	newsize; /* the modern way to get row and col	*/
114 	struct	rex_ttysize	size;	/* the old way no body */
115 					/* bothered to change */
116 	enum	clnt_stat	clstat;
117 
118 	ioctl(0, TIOCGWINSZ, &newsize);
119 
120 	/*
121 	 * compensate for the struct change
122 	 */
123 	size.ts_lines = (int)newsize.ws_row; /* typecast important! */
124 	size.ts_cols = (int)newsize.ws_col;
125 
126 	if (bcmp(&size, &WindowSize, sizeof (size)) == 0)
127 		return;
128 
129 	WindowSize = size;
130 	if (clstat = clnt_call(Client, REXPROC_WINCH,
131 				xdr_rex_ttysize, (caddr_t)&size, xdr_void,
132 				NULL, LongTimeout)) {
133 		fprintf(stderr, "on (size): ");
134 		clnt_perrno(clstat);
135 		fprintf(stderr, "\r\n");
136 	}
137 }
138 
139 /*
140  * signal handler - propagate to remote server
141  */
142 void
143 sendsig(int sig)
144 {
145 	enum clnt_stat clstat;
146 
147 	if (clstat = clnt_call(Client, REXPROC_SIGNAL,
148 				xdr_int, (caddr_t) &sig, xdr_void,
149 				NULL, LongTimeout)) {
150 		fprintf(stderr, "on (signal): ");
151 		clnt_perrno(clstat);
152 		fprintf(stderr, "\r\n");
153 	}
154 }
155 
156 
157 void
158 cont(int junk)
159 {
160 	/*
161 	 * Put tty modes back the way they were and tell the rexd server
162 	 * to send the command a SIGCONT signal.
163 	 */
164 	if (Interactive) {
165 		ioctl(0, TIOCSETN, &NewFlags);
166 		(void) send(InOut, "", 1, MSG_OOB);
167 	}
168 }
169 
170 /*
171  * oob -- called when the command invoked by the rexd server is stopped
172  *	  with a SIGTSTP or SIGSTOP signal.
173  */
174 void
175 oob(int junk)
176 {
177 	int atmark;
178 	char waste[BUFSIZ], mark;
179 
180 	for (;;) {
181 		if (ioctl(InOut, SIOCATMARK, &atmark) < 0) {
182 			perror("ioctl");
183 			break;
184 		}
185 		if (atmark)
186 			break;
187 		(void) read(InOut, waste, sizeof (waste));
188 	}
189 	(void) recv(InOut, &mark, 1, MSG_OOB);
190 	/*
191 	 * Reset tty modes to something sane and stop myself
192 	 */
193 	if (Interactive) {
194 		ioctl(0, TIOCSETN, &OldFlags);
195 		printf("\r\n");
196 	}
197 	kill(getpid(), SIGSTOP);
198 }
199 
200 
201 
202 int
203 main(int argc, char **argv)
204 {
205 	struct	winsize	newsize; /* the modern way to get row and col	*/
206 	char *rhost, **cmdp;
207 	char curdir[MAXPATHLEN];
208 	char wdhost[MAXHOSTNAMELEN];
209 	char fsname[MAXPATHLEN];
210 	char dirwithin[MAXPATHLEN];
211 	struct rex_start rst;
212 	struct rex_result result;
213 	extern char **environ;
214 	enum clnt_stat clstat;
215 	struct hostent *hp;
216 	struct sockaddr_in server_addr;
217 	int sock = RPC_ANYSOCK;
218 	fd_set selmask, zmask, remmask;
219 	int nfds, cc;
220 	char *chi, *cho;
221 	int trying_authdes;
222 	char netname[MAXNETNAMELEN+1];
223 	char hostname[MAXHOSTNAMELEN+1];
224 	char publickey[HEXKEYBYTES+1];
225 	int i;
226 	char *domain;
227 	static char buf[4096];
228 
229 	/*
230 	 * we check the invoked command name to see if it should
231 	 * really be a host name.
232 	 */
233 	if ((rhost = strrchr(argv[0], '/')) == NULL) {
234 		rhost = argv[0];
235 	} else {
236 		rhost++;
237 	}
238 
239 	/*
240 	 * argv start and extent for setproctitle()
241 	 */
242 	Argc = argc;
243 	Argv = argv;
244 	if (argc > 0)
245 		LastArgv = argv[argc-1] + strlen(argv[argc-1]);
246 	else
247 		LastArgv = NULL;
248 
249 	while (argc > 1 && argv[1][0] == '-') {
250 		switch (argv[1][1]) {
251 		case 'd': Debug = 1;
252 			break;
253 		case 'i': Interactive = 1;
254 			break;
255 		case 'n': NoInput = 1;
256 			break;
257 		default:
258 			printf("Unknown option %s\n", argv[1]);
259 		}
260 		argv++;
261 		argc--;
262 	}
263 
264 	if (strcmp(rhost, CommandName) && strcmp(rhost, AltCommandName)) {
265 		cmdp = &argv[1];
266 		Interactive = 1;
267 	} else {
268 		if (argc < 2)
269 			usage();
270 		rhost = argv[1];
271 		cmdp = &argv[2];
272 	}
273 
274 	/*
275 	 * Can only have one of these
276 	 */
277 	if (Interactive && NoInput)
278 		usage();
279 
280 	if ((hp = gethostbyname(rhost)) == NULL) {
281 		fprintf(stderr, "on: unknown host %s\n", rhost);
282 		exit(1);
283 	}
284 
285 	bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr, hp->h_length);
286 	server_addr.sin_family = AF_INET;
287 	server_addr.sin_port = 0; /* use pmapper */
288 
289 	if (Debug)
290 		printf("Got the host named %s (%s)\n",
291 			rhost, inet_ntoa(server_addr.sin_addr));
292 	trying_authdes = 1;
293 
294 try_auth_unix:
295 	sock = RPC_ANYSOCK;
296 
297 	if (Debug)
298 		printf("clnt_create: Server_Addr %u Prog %d Vers %d Sock %d\n",
299 			&server_addr, REXPROG, REXVERS, sock);
300 
301 	if ((Client = clnttcp_create(&server_addr, REXPROG, REXVERS, &sock,
302 					0, 0)) == NULL) {
303 		fprintf(stderr, "on: cannot connect to server on %s\n",
304 			rhost);
305 		clnt_pcreateerror("on:");
306 		exit(1);
307 	}
308 
309 	if (Debug)
310 		printf("TCP RPC connection created\n");
311 
312 	if (trying_authdes) {
313 		yp_get_default_domain(&domain);
314 
315 		cho =  hostname;
316 		*cho = 0;
317 		chi =  hp->h_name;
318 
319 		for (i = 0; (*chi && (i < MAXHOSTNAMELEN)); i++)
320 			{
321 				if (isupper(*chi))
322 					*cho = tolower(*chi);
323 				else
324 					*cho = *chi;
325 				cho++;
326 				chi++;
327 			}
328 		*cho = 0;
329 
330 		if (domain != NULL)	{
331 			if (host2netname(netname, hostname, domain) == 0) {
332 				trying_authdes = 0;
333 				if (Debug)
334 					printf("host2netname failed %s\n",
335 						hp->h_name);
336 			}
337 			/* #ifdef	NOWAY */
338 			else {
339 
340 				if (getpublickey(netname, publickey) == 0) {
341 					trying_authdes = 0;
342 					cho = strchr(hostname, '.');
343 
344 					if (cho) {
345 						*cho = 0;
346 
347 						if (!host2netname(netname,
348 						    hostname,
349 						    domain)) {
350 							if (Debug)
351 				printf("host2netname failed %s\n", hp->h_name);
352 						} else {
353 							if (getpublickey(
354 							    netname,
355 							    publickey) != 0)
356 							trying_authdes = 1;
357 						}
358 					}
359 				}
360 			}
361 		} else {
362 			trying_authdes = 0;
363 			if (Debug)
364 				printf("yp_get_default_domain failed \n");
365 		}
366 	}
367 
368 	if (trying_authdes) {
369 		Client->cl_auth = (AUTH *)authdes_create(netname, 60*60,
370 						&server_addr, NULL);
371 
372 		if (Client->cl_auth == NULL) {
373 
374 			if (Debug)
375 				printf("authdes_create failed %s\n", netname);
376 			trying_authdes = 0;
377 		}
378 	}
379 
380 
381 	if (trying_authdes == 0)
382 		if ((Client->cl_auth = authsys_create_default()) == NULL) {
383 			clnt_destroy(Client);
384 			fprintf(stderr,"on: can't create authunix structure.\n");
385 			exit(1);
386 		}
387 
388 
389 	/*
390 	 * Now that we have created the TCP connection, we do some
391 	 * work while the server daemon is being swapped in.
392 	 */
393 	if (getcwd(curdir, MAXPATHLEN) == (char *)NULL) {
394 		fprintf(stderr, "on: can't find . (%s)\n", curdir);
395 		exit(1);
396 	}
397 
398 	if (findmount(curdir, wdhost, fsname, dirwithin) == 0) {
399 
400 		if (Debug) {
401 			fprintf(stderr,
402 				"findmount failed: curdir %s\twdhost %s\t",
403 				curdir, wdhost);
404 			fprintf(stderr, "fsname %s\tdirwithin %s\n",
405 				fsname, dirwithin);
406 		}
407 
408 		fprintf(stderr, "on: can't locate mount point for %s (%s)\n",
409 			curdir, dirwithin);
410 		exit(1);
411 	}
412 
413 	if (Debug) {
414 		printf("findmount suceeds: cwd= %s, wd host %s, fs %s,",
415 			curdir, wdhost, fsname);
416 		printf("dir within %s\n", dirwithin);
417 	}
418 
419 	Only2 = samefd(1, 2);
420 
421 	rst.rst_cmd = (void *)(cmdp);
422 	rst.rst_host = (void *)wdhost;
423 	rst.rst_fsname = (void *)fsname;
424 	rst.rst_dirwithin = (void *)dirwithin;
425 	rst.rst_env = (void *)environ;
426 	rst.rst_port0 = makeport(&InOut);
427 	rst.rst_port1 =  rst.rst_port0;	/* same port for stdin */
428 	rst.rst_flags = 0;
429 
430 	if (Debug)
431 		printf("before Interactive flags\n");
432 
433 	if (Interactive) {
434 		rst.rst_flags |= REX_INTERACTIVE;
435 		ioctl(0, TIOCGETP, &OldFlags);
436 		NewFlags = OldFlags;
437 		NewFlags.sg_flags |= (u_int)RAW;
438 		NewFlags.sg_flags &= (u_int)~ECHO;
439 		ioctl(0, TIOCSETN, &NewFlags);
440 	}
441 
442 	if (Only2) {
443 		rst.rst_port2 = rst.rst_port1;
444 	} else {
445 		rst.rst_port2 = makeport(&Err);
446 	}
447 
448 	if (Debug)
449 		printf("before client call REXPROC_START\n");
450 
451 	(void) memset(&result, '\0', sizeof(result));
452 
453 	if (clstat = clnt_call(Client, REXPROC_START,
454 			       xdr_rex_start, (caddr_t)&rst,
455 			       xdr_rex_result, (caddr_t)&result, LongTimeout)) {
456 
457 		if (Debug)
458 			printf("Client call failed for REXPROC_START\r\n");
459 
460 		if (trying_authdes) {
461 			auth_destroy(Client->cl_auth);
462 			clnt_destroy(Client);
463 			trying_authdes = 0;
464 			if (Interactive)
465 				ioctl(0, TIOCSETN, &OldFlags);
466 			goto try_auth_unix;
467 		} else {
468 			fprintf(stderr, "on %s: ", rhost);
469 			clnt_perrno(clstat);
470 			fprintf(stderr, "\n");
471 			Die(1);
472 		}
473 	}
474 
475 	if (result.rlt_stat != 0) {
476 		fprintf(stderr, "on %s: %s\n\r", rhost, result.rlt_message);
477 		Die(1);
478 	}
479 
480 	clnt_freeres(Client, xdr_rex_result, (caddr_t)&result);
481 
482 	if (Debug)
483 		printf("Client call suceeded for REXPROC_START\r\n");
484 
485 	if (Interactive) {
486 		/*
487 		 * Pass the tty modes along to the server
488 		 */
489 		struct rex_ttymode mode;
490 		int err;
491 
492 		mode.basic.sg_ispeed = OldFlags.sg_ispeed;
493 		mode.basic.sg_ospeed = OldFlags.sg_ospeed;
494 		mode.basic.sg_erase = OldFlags.sg_erase;
495 		mode.basic.sg_kill = OldFlags.sg_kill;
496 		mode.basic.sg_flags = (short) (OldFlags.sg_flags & 0xFFFF);
497 		err =  (ioctl(0, TIOCGETC, &mode.more) < 0 ||
498 			ioctl(0, TIOCGLTC, &mode.yetmore) < 0 ||
499 			ioctl(0, TIOCLGET, &mode.andmore) < 0);
500 		if (Debug)
501 			printf("Before clnt_call(REXPROC_MODES) err=%d\n", err);
502 
503 		if (!err && (clstat = clnt_call(Client, REXPROC_MODES,
504 					xdr_rex_ttymode, (caddr_t)&mode,
505 					xdr_void, NULL, LongTimeout))) {
506 
507 			fprintf(stderr, "on (modes) %s: ", rhost);
508 			clnt_perrno(clstat);
509 			fprintf(stderr, "\r\n");
510 		}
511 
512 		err = ioctl(0, TIOCGWINSZ, &newsize) < 0;
513 		/* typecast important in following lines */
514 		WindowSize.ts_lines = (int)newsize.ws_row;
515 		WindowSize.ts_cols = (int)newsize.ws_col;
516 
517 		if (Debug)
518 			printf("Before client call REXPROC_WINCH\n");
519 
520 		if (!err && (clstat = clnt_call(Client, REXPROC_WINCH,
521 					xdr_rex_ttysize, (caddr_t)&WindowSize,
522 					xdr_void, NULL, LongTimeout))) {
523 
524 			fprintf(stderr, "on (size) %s: ", rhost);
525 			clnt_perrno(clstat);
526 			fprintf(stderr, "\r\n");
527 		}
528 
529 		sigset(SIGWINCH, sigwinch);
530 		sigset(SIGINT, sendsig);
531 		sigset(SIGQUIT, sendsig);
532 		sigset(SIGTERM, sendsig);
533 	}
534 	sigset(SIGCONT, cont);
535 	sigset(SIGURG, oob);
536 	doaccept(&InOut);
537 	(void) fcntl(InOut, F_SETOWN, getpid());
538 	FD_ZERO(&remmask);
539 	FD_SET(InOut, &remmask);
540 	if (Debug)
541 		printf("accept on stdout\r\n");
542 
543 	if (!Only2) {
544 
545 		doaccept(&Err);
546 		shutdown(Err, 1); /* 1=> further sends disallowed */
547 		if (Debug)
548 			printf("accept on stderr\r\n");
549 		FD_SET(Err, &remmask);
550 	}
551 
552 	FD_ZERO(&zmask);
553 	if (NoInput) {
554 
555 		/*
556 		 * no input - simulate end-of-file instead
557 		 */
558 		shutdown(InOut, 1); /* 1=> further sends disallowed */
559 	} else {
560 		/*
561 		 * set up to read standard input, send to remote
562 		 */
563 		FD_SET(0, &zmask);
564 	}
565 
566 	FD_ZERO(&selmask);
567 	while (FD_ISSET(InOut, &remmask) || FD_ISSET(Err, &remmask)) {
568 		if (FD_ISSET(InOut, &remmask))
569 			FD_SET(InOut, &selmask);
570 		else
571 			FD_CLR(InOut, &selmask);
572 		if (FD_ISSET(Err, &remmask))
573 			FD_SET(Err, &selmask);
574 		else
575 			FD_CLR(Err, &selmask);
576 		if (FD_ISSET(0, &zmask))
577 			FD_SET(0, &selmask);
578 		else
579 			FD_CLR(0, &selmask);
580 		nfds = select(FD_SETSIZE, &selmask, (fd_set *) 0, (fd_set *) 0,
581 			      (struct timeval *) 0);
582 
583 
584  		if (nfds <= 0) {
585 			if (errno == EINTR) continue;
586 			perror("on: select");
587 			Die(1);
588 		}
589 		if (FD_ISSET(InOut, &selmask)) {
590 
591 			cc = read(InOut, buf, sizeof buf);
592 			if (cc > 0)
593 				write(1, buf, cc);
594 			else
595 				FD_CLR(InOut, &remmask);
596 		}
597 
598 		if (!Only2 && FD_ISSET(Err, &selmask)) {
599 
600 			cc = read(Err, buf, sizeof buf);
601 			if (cc > 0)
602 				write(2, buf, cc);
603 			else
604 				FD_CLR(Err, &remmask);
605 		}
606 
607 		if (!NoInput && FD_ISSET(0, &selmask)) {
608 
609 			cc = read(0, buf, sizeof buf);
610 			if (cc > 0)
611 				write(InOut, buf, cc);
612 			else {
613 				/*
614 				 * End of standard input - shutdown outgoing
615 				 * direction of the TCP connection.
616 				 */
617 				if (Debug)
618 					printf("Got EOF - shutting down connection\n");
619 				FD_CLR(0, &zmask);
620 				shutdown(InOut, 1); /* further sends disallowed */
621 			}
622 		}
623 	}
624 
625 	close(InOut);
626 	if (!Only2)
627 		close(Err);
628 
629 	(void) memset(&result, '\0', sizeof(result));
630 
631 	if (clstat = clnt_call(Client, REXPROC_WAIT,
632 			       xdr_void, 0, xdr_rex_result, (caddr_t)&result,
633 			       LongTimeout)) {
634 
635 		fprintf(stderr, "on: ");
636 		clnt_perrno(clstat);
637 		fprintf(stderr, "\r\n");
638 		Die(1);
639 	}
640 	Die(result.rlt_stat);
641 	return (0);	/* Should never get here. */
642 }
643 
644 /*
645  * like exit, but resets the terminal state first
646  */
647 void
648 Die(int stat)
649 
650 {
651 	if (Interactive) {
652 		ioctl(0, TIOCSETN, &OldFlags);
653 		printf("\r\n");
654 	}
655 	exit(stat);
656 }
657 
658 
659 void
660 remstop()
661 
662 {
663 	Die(23);
664 }
665 
666 /*
667  * returns true if we can safely say that the two file descriptors
668  * are the "same" (both are same file).
669  */
670 int
671 samefd(a, b)
672 {
673 	struct stat astat, bstat;
674 
675 	if (fstat(a, &astat) || fstat(b, &bstat))
676 		return (0);
677 	if (astat.st_ino == 0 || bstat.st_ino == 0)
678 		return (0);
679 	return (!bcmp(&astat, &bstat, sizeof (astat)));
680 }
681 
682 
683 /*
684  * accept the incoming connection on the given
685  * file descriptor, and return the new file descritpor
686  */
687 void
688 doaccept(fdp)
689 	int *fdp;
690 {
691 	int fd;
692 
693 	fd = accept(*fdp, 0, 0);
694 
695 	if (fd < 0) {
696 		perror("accept");
697 		remstop();
698 	}
699 	close(*fdp);
700 	*fdp = fd;
701 }
702 
703 /*
704  * create a socket, and return its the port number.
705  */
706 u_short
707 makeport(fdp)
708 	int *fdp;
709 {
710 	struct sockaddr_in sin;
711 	socklen_t len = (socklen_t)sizeof (sin);
712 	int fd;
713 
714 	fd = socket(AF_INET, SOCK_STREAM, 0);
715 
716 	if (fd < 0) {
717 		perror("socket");
718 		exit(1);
719 	}
720 
721 	bzero((char *)&sin, sizeof (sin));
722 	sin.sin_family = AF_INET;
723 	bind(fd, (struct sockaddr *)&sin, sizeof (sin));
724 	getsockname(fd, (struct sockaddr *)&sin, &len);
725 	listen(fd, 1);
726 	*fdp = fd;
727 	return (htons(sin.sin_port));
728 }
729 
730 void
731 usage(void)
732 {
733 	fprintf(stderr, "Usage: on [-i|-n] [-d] machine cmd [args]...\n");
734 	exit(1);
735 }
736 
737 /*
738  *  SETPROCTITLE -- set the title of this process for "ps"
739  *
740  *	Does nothing if there were not enough arguments on the command
741  * 	line for the information.
742  *
743  *	Side Effects:
744  *		Clobbers argv[] of our main procedure.
745  */
746 void
747 setproctitle(user, host)
748 	char *user, *host;
749 {
750 	register char *tohere;
751 
752 	tohere = Argv[0];
753 	if ((int)LastArgv == (int)((char *)NULL) ||
754 	    (int)(strlen(user) + strlen(host)+3) > (int)(LastArgv - tohere))
755 		return;
756 	*tohere++ = '-';		/* So ps prints (rpc.rexd) */
757 	sprintf(tohere, "%s@%s", user, host);
758 	while (*tohere++)		/* Skip to end of printf output	*/
759 		;
760 	while (tohere < LastArgv)	/* Avoid confusing ps		*/
761 		*tohere++ = ' ';
762 }
763