1/*	$NetBSD: rcp.c,v 1.53 2023/08/01 08:47:24 mrg Exp $	*/
2
3/*
4 * Copyright (c) 1983, 1990, 1992, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34__COPYRIGHT("@(#) Copyright (c) 1983, 1990, 1992, 1993\
35 The Regents of the University of California.  All rights reserved.");
36#endif /* not lint */
37
38#ifndef lint
39#if 0
40static char sccsid[] = "@(#)rcp.c	8.2 (Berkeley) 4/2/94";
41#else
42__RCSID("$NetBSD: rcp.c,v 1.53 2023/08/01 08:47:24 mrg Exp $");
43#endif
44#endif /* not lint */
45
46#include <sys/param.h>
47#include <sys/stat.h>
48#include <sys/time.h>
49#include <sys/socket.h>
50#include <netinet/in.h>
51#include <netinet/in_systm.h>
52#include <netinet/ip.h>
53
54#include <ctype.h>
55#include <dirent.h>
56#include <err.h>
57#include <errno.h>
58#include <fcntl.h>
59#include <locale.h>
60#include <netdb.h>
61#include <paths.h>
62#include <pwd.h>
63#include <signal.h>
64#include <stdio.h>
65#include <stdlib.h>
66#include <string.h>
67#include <unistd.h>
68
69#include "pathnames.h"
70#include "extern.h"
71
72#define	OPTIONS "46dfprt"
73
74struct passwd *pwd;
75char *pwname;
76u_short	port;
77uid_t	userid;
78int errs, rem;
79int pflag, iamremote, iamrecursive, targetshouldbedirectory;
80int family = AF_UNSPEC;
81static char dot[] = ".";
82
83static sig_atomic_t print_info = 0;
84
85#define	CMDNEEDS	64
86char cmd[CMDNEEDS];		/* must hold "rcp -r -p -d\0" */
87
88int	 response(void);
89void	 rsource(char *, struct stat *);
90void	 sink(int, char *[]);
91void	 source(int, char *[]);
92void	 tolocal(int, char *[]);
93void	 toremote(char *, int, char *[]);
94void	 usage(void);
95static void	got_siginfo(int);
96static void	progress(const char *, uintmax_t, uintmax_t);
97
98int
99main(int argc, char *argv[])
100{
101	struct servent *sp;
102	int ch, fflag, tflag;
103	char *targ;
104	const char *shell;
105
106	setprogname(argv[0]);
107	(void)setlocale(LC_ALL, "");
108
109	fflag = tflag = 0;
110	while ((ch = getopt(argc, argv, OPTIONS)) != -1)
111		switch(ch) {			/* User-visible flags. */
112		case '4':
113			family = AF_INET;
114			break;
115		case '6':
116			family = AF_INET6;
117			break;
118		case 'K':
119			break;
120		case 'p':
121			pflag = 1;
122			break;
123		case 'r':
124			iamrecursive = 1;
125			break;
126						/* Server options. */
127		case 'd':
128			targetshouldbedirectory = 1;
129			break;
130		case 'f':			/* "from" */
131			iamremote = 1;
132			fflag = 1;
133			break;
134		case 't':			/* "to" */
135			iamremote = 1;
136			tflag = 1;
137			break;
138		case '?':
139		default:
140			usage();
141		}
142	argc -= optind;
143	argv += optind;
144
145	sp = getservbyname(shell = "shell", "tcp");
146	if (sp == NULL)
147		errx(1, "%s/tcp: unknown service", shell);
148	port = sp->s_port;
149
150	if ((pwd = getpwuid(userid = getuid())) == NULL)
151		errx(1, "unknown user %d", (int)userid);
152
153	if ((pwname = strdup(pwd->pw_name)) == NULL)
154		err(1, NULL);
155
156	rem = STDIN_FILENO;		/* XXX */
157
158	if (fflag) {			/* Follow "protocol", send data. */
159		(void)response();
160		source(argc, argv);
161		exit(errs);
162	}
163
164	if (tflag) {			/* Receive data. */
165		sink(argc, argv);
166		exit(errs);
167	}
168
169	if (argc < 2)
170		usage();
171	if (argc > 2)
172		targetshouldbedirectory = 1;
173
174	rem = -1;
175	/* Command to be executed on remote system using "rsh". */
176	(void)snprintf(cmd, sizeof(cmd), "rcp%s%s%s",
177	    iamrecursive ? " -r" : "", pflag ? " -p" : "",
178	    targetshouldbedirectory ? " -d" : "");
179
180	(void)signal(SIGPIPE, lostconn);
181	(void)signal(SIGINFO, got_siginfo);
182
183	if ((targ = colon(argv[argc - 1])) != NULL)/* Dest is remote host. */
184		toremote(targ, argc, argv);
185	else {
186		tolocal(argc, argv);		/* Dest is local host. */
187		if (targetshouldbedirectory)
188			verifydir(argv[argc - 1]);
189	}
190	exit(errs);
191	/* NOTREACHED */
192}
193
194void
195toremote(char *targ, int argc, char *argv[])
196{
197	int i;
198	size_t len;
199	char *bp, *host, *src, *suser, *thost, *tuser;
200
201	*targ++ = 0;
202	if (*targ == 0)
203		targ = dot;
204
205	if ((thost = strchr(argv[argc - 1], '@')) != NULL) {
206		/* user@host */
207		*thost++ = 0;
208		tuser = argv[argc - 1];
209		if (*tuser == '\0')
210			tuser = NULL;
211		else if (!okname(tuser))
212			exit(1);
213	} else {
214		thost = argv[argc - 1];
215		tuser = NULL;
216	}
217	thost = unbracket(thost);
218
219	for (i = 0; i < argc - 1; i++) {
220		src = colon(argv[i]);
221		if (src) {			/* remote to remote */
222			*src++ = 0;
223			if (*src == 0)
224				src = dot;
225			host = strchr(argv[i], '@');
226			len = strlen(_PATH_RSH) + strlen(argv[i]) +
227			    strlen(src) + (tuser ? strlen(tuser) : 0) +
228			    strlen(thost) + strlen(targ) + CMDNEEDS + 20;
229			if (!(bp = malloc(len)))
230				err(1, NULL);
231			if (host) {
232				*host++ = 0;
233				host = unbracket(host);
234				suser = argv[i];
235				if (*suser == '\0')
236					suser = pwname;
237				else if (!okname(suser)) {
238					(void)free(bp);
239					continue;
240				}
241				(void)snprintf(bp, len,
242				    "%s %s -l %s -n %s %s '%s%s%s:%s'",
243				    _PATH_RSH, host, suser, cmd, src,
244				    tuser ? tuser : "", tuser ? "@" : "",
245				    thost, targ);
246			} else {
247				host = unbracket(argv[i]);
248				(void)snprintf(bp, len,
249				    "exec %s %s -n %s %s '%s%s%s:%s'",
250				    _PATH_RSH, argv[i], cmd, src,
251				    tuser ? tuser : "", tuser ? "@" : "",
252				    thost, targ);
253			}
254			(void)susystem(bp);
255			(void)free(bp);
256		} else {			/* local to remote */
257			if (rem == -1) {
258				len = strlen(targ) + CMDNEEDS + 20;
259				if (!(bp = malloc(len)))
260					err(1, NULL);
261				(void)snprintf(bp, len, "%s -t %s", cmd, targ);
262				host = thost;
263					rem = rcmd_af(&host, port, pwname,
264					    tuser ? tuser : pwname,
265					    bp, NULL, family);
266				if (rem < 0)
267					exit(1);
268				if (response() < 0)
269					exit(1);
270				(void)free(bp);
271			}
272			source(1, argv+i);
273		}
274	}
275}
276
277void
278tolocal(int argc, char *argv[])
279{
280	int i;
281	size_t len;
282	char *bp, *host, *src, *suser;
283
284	for (i = 0; i < argc - 1; i++) {
285		if (!(src = colon(argv[i]))) {		/* Local to local. */
286			len = strlen(_PATH_CP) + strlen(argv[i]) +
287			    strlen(argv[argc - 1]) + 20;
288			if (!(bp = malloc(len)))
289				err(1, NULL);
290			(void)snprintf(bp, len, "exec %s%s%s %s %s", _PATH_CP,
291			    iamrecursive ? " -r" : "", pflag ? " -p" : "",
292			    argv[i], argv[argc - 1]);
293			if (susystem(bp))
294				++errs;
295			(void)free(bp);
296			continue;
297		}
298		*src++ = 0;
299		if (*src == 0)
300			src = dot;
301		if ((host = strchr(argv[i], '@')) == NULL) {
302			host = argv[i];
303			suser = pwname;
304		} else {
305			*host++ = 0;
306			suser = argv[i];
307			if (*suser == '\0')
308				suser = pwname;
309			else if (!okname(suser))
310				continue;
311		}
312		host = unbracket(host);
313		len = strlen(src) + CMDNEEDS + 20;
314		if ((bp = malloc(len)) == NULL)
315			err(1, NULL);
316		(void)snprintf(bp, len, "%s -f %s", cmd, src);
317		rem =
318			rcmd_af(&host, port, pwname, suser, bp, NULL, family);
319		(void)free(bp);
320		if (rem < 0) {
321			++errs;
322			continue;
323		}
324		sink(1, argv + argc - 1);
325		(void)close(rem);
326		rem = -1;
327	}
328}
329
330void
331source(int argc, char *argv[])
332{
333	struct stat stb;
334	static BUF buffer;
335	BUF *bp;
336	off_t i;
337	off_t amt;
338	size_t resid;
339	ssize_t result;
340	int fd, haderr, indx;
341	char *last, *name, *cp, buf[BUFSIZ];
342
343	for (indx = 0; indx < argc; ++indx) {
344		name = argv[indx];
345		if ((fd = open(name, O_RDONLY, 0)) < 0)
346			goto syserr;
347		if (fstat(fd, &stb)) {
348syserr:			run_err("%s: %s", name, strerror(errno));
349			goto next;
350		}
351		switch (stb.st_mode & S_IFMT) {
352		case S_IFREG:
353			break;
354		case S_IFDIR:
355			if (iamrecursive) {
356				rsource(name, &stb);
357				goto next;
358			}
359			/* FALLTHROUGH */
360		default:
361			run_err("%s: not a regular file", name);
362			goto next;
363		}
364		if ((last = strrchr(name, '/')) == NULL)
365			last = name;
366		else
367			++last;
368		if (pflag) {
369			/*
370			 * Make it compatible with possible future
371			 * versions expecting microseconds.
372			 */
373			(void)snprintf(buf, sizeof(buf), "T%lld %ld %lld %ld\n",
374			    (long long)stb.st_mtimespec.tv_sec,
375			    (long)stb.st_mtimespec.tv_nsec / 1000,
376			    (long long)stb.st_atimespec.tv_sec,
377			    (long)stb.st_atimespec.tv_nsec / 1000);
378			(void)write(rem, buf, strlen(buf));
379			if (response() < 0)
380				goto next;
381		}
382#define	RCPMODEMASK	(S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO)
383		(void)snprintf(buf, sizeof(buf), "C%04o %lld %s\n",
384		    stb.st_mode & RCPMODEMASK, (long long)stb.st_size, last);
385		(void)write(rem, buf, strlen(buf));
386		if (response() < 0)
387			goto next;
388		if ((bp = allocbuf(&buffer, fd, BUFSIZ)) == NULL) {
389next:			(void)close(fd);
390			continue;
391		}
392
393		/* Keep writing after an error so that we stay sync'd up. */
394		haderr = 0;
395		for (i = 0; i < stb.st_size; i += bp->cnt) {
396			if (print_info)
397				progress(name, i, stb.st_size);
398			amt = bp->cnt;
399			if (i + amt > stb.st_size)
400				amt = stb.st_size - i;
401			for (resid = (size_t)amt, cp = bp->buf; resid > 0;
402			    resid -= result, cp += result) {
403				result = read(fd, cp, resid);
404				if (result == -1) {
405					haderr = errno;
406					goto error;
407				}
408			}
409			for (resid = (size_t)amt, cp = bp->buf; resid > 0;
410			    resid -= result, cp += result) {
411				result = write(rem, cp, resid);
412				if (result == -1) {
413					haderr = errno;
414					goto error;
415				}
416			}
417		}
418 error:
419		if (close(fd) && !haderr)
420			haderr = errno;
421		if (!haderr)
422			(void)write(rem, "", 1);
423		else
424			run_err("%s: %s", name, strerror(haderr));
425		(void)response();
426	}
427}
428
429void
430rsource(char *name, struct stat *statp)
431{
432	DIR *dirp;
433	struct dirent *dp;
434	char *last, *vect[1], path[MAXPATHLEN];
435
436	if (!(dirp = opendir(name))) {
437		run_err("%s: %s", name, strerror(errno));
438		return;
439	}
440	last = strrchr(name, '/');
441	if (last == 0)
442		last = name;
443	else
444		last++;
445	if (pflag) {
446		(void)snprintf(path, sizeof(path), "T%lld %ld %lld %ld\n",
447		    (long long)statp->st_mtimespec.tv_sec,
448		    (long)statp->st_mtimespec.tv_nsec / 1000,
449		    (long long)statp->st_atimespec.tv_sec,
450		    (long)statp->st_atimespec.tv_nsec / 1000);
451		(void)write(rem, path, strlen(path));
452		if (response() < 0) {
453			(void)closedir(dirp);
454			return;
455		}
456	}
457	(void)snprintf(path, sizeof(path),
458	    "D%04o %d %s\n", statp->st_mode & RCPMODEMASK, 0, last);
459	(void)write(rem, path, strlen(path));
460	if (response() < 0) {
461		(void)closedir(dirp);
462		return;
463	}
464	while ((dp = readdir(dirp)) != NULL) {
465		if (dp->d_ino == 0)
466			continue;
467		if (!strcmp(dp->d_name, dot) || !strcmp(dp->d_name, ".."))
468			continue;
469		if (snprintf(path, sizeof(path), "%s/%s", name, dp->d_name) >=
470		    sizeof(path)) {
471			run_err("%s/%s: name too long", name, dp->d_name);
472			continue;
473		}
474		vect[0] = path;
475		source(1, vect);
476	}
477	(void)closedir(dirp);
478	(void)write(rem, "E\n", 2);
479	(void)response();
480}
481
482void
483sink(int argc, char *argv[])
484{
485	static BUF buffer;
486	struct stat stb;
487	struct timeval tv[2];
488	BUF *bp;
489	size_t resid;
490	ssize_t result;
491	off_t i;
492	off_t amt;
493	int exists, first, ofd;
494	mode_t mask;
495	mode_t mode;
496	mode_t omode;
497	int setimes, targisdir, wrerr;
498	int wrerrno = 0;	/* pacify gcc */
499	const char *wrcontext = NULL;
500	char ch, *cp, *np, *targ, *vect[1], buf[BUFSIZ];
501	const char *why;
502	off_t size;
503	char *namebuf = NULL;
504	size_t cursize = 0;
505
506#define	atime	tv[0]
507#define	mtime	tv[1]
508#define	SCREWUP(str)	{ why = str; goto screwup; }
509
510	setimes = targisdir = 0;
511	mask = umask(0);
512	if (!pflag)
513		(void)umask(mask);
514	if (argc != 1) {
515		run_err("ambiguous target");
516		exit(1);
517	}
518	targ = *argv;
519	if (targetshouldbedirectory)
520		verifydir(targ);
521	(void)write(rem, "", 1);
522	if (stat(targ, &stb) == 0 && S_ISDIR(stb.st_mode))
523		targisdir = 1;
524	for (first = 1;; first = 0) {
525		cp = buf;
526		if (read(rem, cp, 1) <= 0)
527			goto out;
528		if (*cp++ == '\n')
529			SCREWUP("unexpected <newline>");
530		do {
531			if (read(rem, &ch, sizeof(ch)) != sizeof(ch))
532				SCREWUP("lost connection");
533			*cp++ = ch;
534		} while (cp < &buf[BUFSIZ - 1] && ch != '\n');
535		*cp = 0;
536
537		if (buf[0] == '\01' || buf[0] == '\02') {
538			if (iamremote == 0)
539				(void)write(STDERR_FILENO,
540				    buf + 1, strlen(buf + 1));
541			if (buf[0] == '\02')
542				exit(1);
543			++errs;
544			continue;
545		}
546		if (buf[0] == 'E') {
547			(void)write(rem, "", 1);
548			goto out;
549		}
550
551		if (ch == '\n')
552			*--cp = 0;
553
554#define getnum(t) (t) = 0; while (isdigit((unsigned char)*cp)) (t) = (t) * 10 + (*cp++ - '0');
555		cp = buf;
556		if (*cp == 'T') {
557			setimes++;
558			cp++;
559			getnum(mtime.tv_sec);
560			if (*cp++ != ' ')
561				SCREWUP("mtime.sec not delimited");
562			getnum(mtime.tv_usec);
563			if (*cp++ != ' ')
564				SCREWUP("mtime.usec not delimited");
565			getnum(atime.tv_sec);
566			if (*cp++ != ' ')
567				SCREWUP("atime.sec not delimited");
568			getnum(atime.tv_usec);
569			if (*cp++ != '\0')
570				SCREWUP("atime.usec not delimited");
571			(void)write(rem, "", 1);
572			continue;
573		}
574		if (*cp != 'C' && *cp != 'D') {
575			/*
576			 * Check for the case "rcp remote:foo\* local:bar".
577			 * In this case, the line "No match." can be returned
578			 * by the shell before the rcp command on the remote is
579			 * executed so the ^Aerror_message convention isn't
580			 * followed.
581			 */
582			if (first) {
583				run_err("%s", cp);
584				exit(1);
585			}
586			SCREWUP("expected control record");
587		}
588		mode = 0;
589		for (++cp; cp < buf + 5; cp++) {
590			if (*cp < '0' || *cp > '7')
591				SCREWUP("bad mode");
592			mode = (mode << 3) | (*cp - '0');
593		}
594		if (*cp++ != ' ')
595			SCREWUP("mode not delimited");
596
597		for (size = 0; isdigit((unsigned char)*cp);)
598			size = size * 10 + (*cp++ - '0');
599		if (*cp++ != ' ')
600			SCREWUP("size not delimited");
601		if (targisdir) {
602			char *newnamebuf;
603			size_t need;
604
605			need = strlen(targ) + strlen(cp) + 2;
606			if (need > cursize) {
607				need += 256;
608				newnamebuf = realloc(namebuf, need);
609				if (newnamebuf != NULL) {
610					namebuf = newnamebuf;
611					cursize = need;
612				} else {
613					run_err("%s", strerror(errno));
614					exit(1);
615				}
616			}
617			(void)snprintf(namebuf, cursize, "%s%s%s", targ,
618			    *targ ? "/" : "", cp);
619			np = namebuf;
620		} else
621			np = targ;
622		exists = stat(np, &stb) == 0;
623		if (buf[0] == 'D') {
624			int mod_flag = pflag;
625			if (exists) {
626				if (!S_ISDIR(stb.st_mode)) {
627					errno = ENOTDIR;
628					goto bad;
629				}
630				if (pflag)
631					(void)chmod(np, mode);
632			} else {
633				/* Handle copying from a read-only directory */
634				mod_flag = 1;
635				if (mkdir(np, mode | S_IRWXU) < 0)
636					goto bad;
637			}
638			vect[0] = np;
639			sink(1, vect);
640			if (setimes) {
641				setimes = 0;
642				(void) utimes(np, tv);
643			}
644			if (mod_flag)
645				(void)chmod(np, mode);
646			continue;
647		}
648		omode = mode;
649		mode |= S_IWRITE;
650		if ((ofd = open(np, O_WRONLY|O_CREAT, mode)) < 0) {
651bad:			run_err("%s: %s", np, strerror(errno));
652			continue;
653		}
654		(void)write(rem, "", 1);
655		if ((bp = allocbuf(&buffer, ofd, BUFSIZ)) == NULL) {
656			(void)close(ofd);
657			continue;
658		}
659		wrerr = 0;
660
661/*
662 * Like run_err(), but don't send any message to the remote end.
663 * Instead, record the first error and send that in the end.
664 */
665#define RUN_ERR(w_context) do { \
666	if (!wrerr) {							\
667		wrerrno = errno;					\
668		wrcontext = w_context;					\
669		wrerr = 1;						\
670	}								\
671} while(0)
672
673		for (i = 0; i < size; i += bp->cnt) {
674			if (print_info)
675				progress(np, i, size);
676			amt = bp->cnt;
677			if (i + amt > size)
678				amt = size - i;
679			for (resid = (size_t)amt, cp = bp->buf; resid > 0;
680			    resid -= result, cp += result) {
681				result = read(rem, cp, resid);
682				if (result == -1) {
683					run_err("%s", strerror(errno));
684					exit(1);
685				}
686			}
687			for (resid = (size_t)amt, cp = bp->buf; resid > 0;
688			    resid -= result, cp += result) {
689				/* Keep reading so we stay sync'd up. */
690				if (!wrerr) {
691					result = write(ofd, cp, resid);
692					if (result == -1) {
693						RUN_ERR("write");
694						goto error;
695					}
696				}
697			}
698		}
699 error:
700		if (ftruncate(ofd, size))
701			RUN_ERR("truncate");
702
703		if (pflag) {
704			if (exists || omode != mode)
705				if (fchmod(ofd, omode))
706					RUN_ERR("set mode");
707		} else {
708			if (!exists && omode != mode)
709				if (fchmod(ofd, omode & ~mask))
710					RUN_ERR("set mode");
711		}
712#ifndef __SVR4
713		if (setimes && !wrerr) {
714			setimes = 0;
715			if (futimes(ofd, tv) < 0)
716				RUN_ERR("set times");
717		}
718#endif
719		(void)close(ofd);
720#ifdef __SVR4
721		if (setimes && !wrerr) {
722			setimes = 0;
723			if (utimes(np, tv) < 0)
724				RUN_ERR("set times");
725		}
726#endif
727		(void)response();
728		if (wrerr)
729			run_err("%s: %s: %s", np, wrcontext, strerror(wrerrno));
730		else
731			(void)write(rem, "", 1);
732	}
733
734out:
735	if (namebuf) {
736		free(namebuf);
737	}
738	return;
739
740screwup:
741	run_err("protocol error: %s", why);
742	exit(1);
743	/* NOTREACHED */
744}
745
746
747int
748response(void)
749{
750	char ch, *cp, resp, rbuf[BUFSIZ];
751
752	if (read(rem, &resp, sizeof(resp)) != sizeof(resp))
753		lostconn(0);
754
755	cp = rbuf;
756	switch(resp) {
757	case 0:				/* ok */
758		return (0);
759	default:
760		*cp++ = resp;
761		/* FALLTHROUGH */
762	case 1:				/* error, followed by error msg */
763	case 2:				/* fatal error, "" */
764		do {
765			if (read(rem, &ch, sizeof(ch)) != sizeof(ch))
766				lostconn(0);
767			*cp++ = ch;
768		} while (cp < &rbuf[BUFSIZ] && ch != '\n');
769
770		if (!iamremote)
771			(void)write(STDERR_FILENO, rbuf, (size_t)(cp - rbuf));
772		++errs;
773		if (resp == 1)
774			return (-1);
775		exit(1);
776	}
777	/* NOTREACHED */
778}
779
780void
781usage(void)
782{
783	(void)fprintf(stderr,
784	    "usage: rcp [-46p] f1 f2; or: rcp [-46pr] f1 ... fn directory\n");
785	exit(1);
786	/* NOTREACHED */
787}
788
789#include <stdarg.h>
790
791
792void
793run_err(const char *fmt, ...)
794{
795	static FILE *fp;
796	va_list ap;
797
798	++errs;
799	if (fp == NULL && !(fp = fdopen(rem, "w")))
800		return;
801
802	va_start(ap, fmt);
803
804	(void)fprintf(fp, "%c", 0x01);
805	(void)fprintf(fp, "rcp: ");
806	(void)vfprintf(fp, fmt, ap);
807	(void)fprintf(fp, "\n");
808	(void)fflush(fp);
809	va_end(ap);
810
811	if (!iamremote) {
812		va_start(ap, fmt);
813		vwarnx(fmt, ap);
814		va_end(ap);
815	}
816}
817
818static void
819got_siginfo(int signo)
820{
821
822	print_info = 1;
823}
824
825static void
826progress(const char *file, uintmax_t done, uintmax_t total)
827{
828	static int ttyfd = -2;
829	const double pcent = (100.0 * done) / total;
830	char buf[2048];
831	int n;
832
833	if (ttyfd == -2)
834		ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC);
835
836	if (ttyfd == -1)
837		return;
838
839	n = snprintf(buf, sizeof(buf),
840	    "%s: %s: %ju/%ju bytes %3.2f%% written\n",
841	    getprogname(), file, done, total, pcent);
842
843	if (n < 0)
844		return;
845
846	write(ttyfd, buf, (size_t)n);
847
848	print_info = 0;
849}
850