xref: /illumos-gate/usr/src/cmd/bnu/uucp.c (revision 462be471)
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  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 #include "uucp.h"
33 
34 /*
35  * uucp
36  * user id
37  * make a copy in spool directory
38  */
39 int Copy = 0;
40 static int _Transfer = 0;
41 char Nuser[32];
42 char *Ropt = " ";
43 char Optns[10];
44 char Uopts[BUFSIZ];
45 char Xopts[BUFSIZ];
46 char Sgrade[NAMESIZE];
47 int Mail = 0;
48 int Notify = 0;
49 
50 void cleanup(), ruux(), usage();
51 int eaccess(), guinfo(), vergrd(), gwd(), ckexpf(), uidstat(), uidxcp(),
52 	copy(), gtcfile();
53 void commitall(), wfabort(), mailst(), gename(), svcfile();
54 
55 char	Sfile[MAXFULLNAME];
56 
57 int
58 main(argc, argv, envp)
59 int argc;
60 char *argv[];
61 char	**envp;
62 {
63 	char *jid();
64 	int	ret;
65 	int	errors = 0;
66 	char	*fopt, *sys2p;
67 	char	sys1[MAXFULLNAME], sys2[MAXFULLNAME];
68 	char	fwd1[MAXFULLNAME], fwd2[MAXFULLNAME];
69 	char	file1[MAXFULLNAME], file2[MAXFULLNAME];
70 	short	jflag = 0;	/* -j flag  Jobid printout */
71 	extern int	split();
72 
73 
74 	/* Set locale environment variables local definitions */
75 	(void) setlocale(LC_ALL, "");
76 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
77 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it wasn't */
78 #endif
79 	(void) textdomain(TEXT_DOMAIN);
80 
81 	/* this fails in some versions, but it doesn't hurt */
82 	Uid = getuid();
83 	Euid = geteuid();
84 	if (Uid == 0)
85 		(void) setuid(UUCPUID);
86 
87 	/* choose LOGFILE */
88 	(void) strcpy(Logfile, LOGUUCP);
89 
90 	Env = envp;
91 	fopt = NULL;
92 	(void) strcpy(Progname, "uucp");
93 	Pchar = 'U';
94 	*Uopts = NULLCHAR;
95 	*Xopts = NULLCHAR;
96 	*Sgrade = NULLCHAR;
97 
98 	if (eaccess(GRADES, 0) != -1) {
99 		Grade = 'A';
100 		Sgrades = TRUE;
101 		sprintf(Sgrade, "%s", "default");
102 	}
103 
104 	/*
105 	 * find name of local system
106 	 */
107 	uucpname(Myname);
108 	Optns[0] = '-';
109 	Optns[1] = 'd';
110 	Optns[2] = 'c';
111 	Optns[3] = Nuser[0] = Sfile[0] = NULLCHAR;
112 
113 	/*
114 	 * find id of user who spawned command to
115 	 * determine
116 	 */
117 	(void) guinfo(Uid, User);
118 
119 	/*
120 	 * create/append command log
121 	 */
122 	commandlog(argc,argv);
123 
124 	while ((ret = getopt(argc, argv, "Ccdfg:jmn:rs:x:")) != EOF) {
125 		switch (ret) {
126 
127 		/*
128 		 * make a copy of the file in the spool
129 		 * directory.
130 		 */
131 		case 'C':
132 			Copy = 1;
133 			Optns[2] = 'C';
134 			break;
135 
136 		/*
137 		 * not used (default)
138 		 */
139 		case 'c':
140 			break;
141 
142 		/*
143 		 * not used (default)
144 		 */
145 		case 'd':
146 			break;
147 		case 'f':
148 			Optns[1] = 'f';
149 			break;
150 
151 		/*
152 		 * set service grade
153 		 */
154 		case 'g':
155 			snprintf(Xopts, sizeof (Xopts), "-g%s", optarg);
156 			if (!Sgrades) {
157 				if (strlen(optarg) < (size_t)2 && isalnum(*optarg))
158 					Grade = *optarg;
159 				else {
160 					(void) fprintf(stderr, gettext("No"
161 					    " administrator defined service"
162 					    " grades available on this"
163 					    " machine.\n"));
164 					(void) fprintf(stderr, gettext("UUCP"
165 					    " service grades range from"
166 					    " [A-Z][a-z] only.\n"));
167 					cleanup(-1);
168 				}
169 			}
170 			else {
171 				(void) strncpy(Sgrade, optarg, NAMESIZE-1);
172 				Sgrade[NAMESIZE-1] = NULLCHAR;
173 				if (vergrd(Sgrade) != SUCCESS)
174 					cleanup(FAIL);
175 			}
176 			break;
177 
178 		case 'j':	/* job id */
179 			jflag = 1;
180 			break;
181 
182 		/*
183 		 * send notification to local user
184 		 */
185 		case 'm':
186 			Mail = 1;
187 			(void) strcat(Optns, "m");
188 			break;
189 
190 		/*
191 		 * send notification to user on remote
192 		 * if no user specified do not send notification
193 		 */
194 		case 'n':
195 			/*
196 			 * We should add "n" option to Optns only once,
197 			 * even if multiple -n option are passed to uucp
198 			 */
199 			if (!Notify) {
200 				(void) strlcat(Optns, "n", sizeof (Optns));
201 				Notify = 1;
202 			}
203 			(void) sprintf(Nuser, "%.8s", optarg);
204 
205 			/*
206 			 * We do the copy multiple times when multiple
207 			 * -n options are specified, but
208 			 * only the last -n value is used.
209 	 		 */
210 			(void) snprintf(Uopts, sizeof (Uopts), "-n%s ", Nuser);
211 
212 			break;
213 
214 		/*
215 		 * create JCL files but do not start uucico
216 		 */
217 		case 'r':
218 			Ropt = "-r";
219 			break;
220 
221 		/*
222 		 * return status file
223 		 */
224 		case 's':
225 			fopt = optarg;
226 			/* "m" needed for compatability */
227 			(void) strcat(Optns, "mo");
228 			break;
229 
230 		/*
231 		 * turn on debugging
232 		 */
233 		case 'x':
234 			Debug = atoi(optarg);
235 			if (Debug <= 0)
236 				Debug = 1;
237 #ifdef SMALL
238 			fprintf(stderr, gettext("WARNING: uucp built with SMALL"
239 			    " flag defined -- no debug info available\n"));
240 #endif /* SMALL */
241 			break;
242 
243 		default:
244 			usage();
245 			break;
246 		}
247 	}
248 	DEBUG(4, "\n\n** %s **\n", "START");
249 	gwd(Wrkdir);
250 	if (fopt) {
251 		if (*fopt != '/')
252 			(void) snprintf(Sfile, MAXFULLNAME, "%s/%s",
253 					Wrkdir, fopt);
254 		else
255 			(void) snprintf(Sfile, MAXFULLNAME, "%s", fopt);
256 
257 	}
258 	else
259 		if (strlcpy(Sfile, "dummy", sizeof (Sfile)) >= sizeof (Sfile))
260 			return (2);
261 
262 	/*
263 	 * work in WORKSPACE directory
264 	 */
265 	ret = chdir(WORKSPACE);
266 	if (ret != 0) {
267 		(void) fprintf(stderr, gettext("No work directory - %s -"
268 		    " get help\n"), WORKSPACE);
269 		cleanup(-12);
270 	}
271 
272 	if (Nuser[0] == NULLCHAR)
273 		(void) strcpy(Nuser, User);
274 	(void) strcpy(Loginuser, User);
275 	DEBUG(4, "UID %ld, ", (long) Uid);
276 	DEBUG(4, "User %s\n", User);
277 	if (argc - optind < 2) {
278 		usage();
279 	}
280 
281 	/*
282 	 * set up "to" system and file names
283 	 */
284 
285 	(void) split(argv[argc - 1], sys2, fwd2, file2);
286 	if (*sys2 != NULLCHAR) {
287 		(void) strncpy(Rmtname, sys2, MAXBASENAME);
288 		Rmtname[MAXBASENAME] = NULLCHAR;
289 
290 		/* get real Myname - it depends on who I'm calling--Rmtname */
291 		(void) mchFind(Rmtname);
292 		myName(Myname);
293 
294 		if (versys(sys2) != 0) {
295 			(void) fprintf(stderr,
296 			    gettext("bad system: %s\n"), sys2);
297 			cleanup(-EX_NOHOST);
298 		}
299 	}
300 
301 	DEBUG(9, "sys2: %s, ", sys2);
302 	DEBUG(9, "fwd2: %s, ", fwd2);
303 	DEBUG(9, "file2: %s\n", file2);
304 
305 	/*
306 	 * if there are more than 2 argsc, file2 is a directory
307 	 */
308 	if (argc - optind > 2)
309 		(void) strcat(file2, "/");
310 
311 	/*
312 	 * do each from argument
313 	 */
314 
315 	for ( ; optind < argc - 1; optind++) {
316 	    (void) split(argv[optind], sys1, fwd1, file1);
317 	    if (*sys1 != NULLCHAR) {
318 		if (versys(sys1) != 0) {
319 			(void) fprintf(stderr,
320 			    gettext("bad system: %s\n"), sys1);
321 			cleanup(-EX_NOHOST);
322 		}
323 	    }
324 
325 	    /*  source files can have at most one ! */
326 	    if (*fwd1 != NULLCHAR) {
327 		/* syntax error */
328 	        (void) fprintf(stderr,
329 		    gettext("illegal  syntax %s\n"), argv[optind]);
330 	        exit(2);
331 	    }
332 
333 	    /*
334 	     * check for required remote expansion of file names -- generate
335 	     *	and execute a uux command
336 	     * e.g.
337 	     *		uucp   owl!~/dan/..  ~/dan/
338 	     *
339 	     * NOTE: The source file part must be full path name.
340 	     *  If ~ it will be expanded locally - it assumes the remote
341 	     *  names are the same.
342 	     */
343 
344 	    if (*sys1 != NULLCHAR)
345 		if ((strchr(file1, '*') != NULL
346 		      || strchr(file1, '?') != NULL
347 		      || strchr(file1, '[') != NULL)) {
348 		        /* do a uux command */
349 		        if (ckexpf(file1) == FAIL)
350 			    exit(6);
351 			(void) strncpy(Rmtname, sys1, MAXBASENAME);
352 			Rmtname[MAXBASENAME] = NULLCHAR;
353 			/* get real Myname - it depends on who I'm calling--Rmtname */
354 			(void) mchFind(Rmtname);
355 			myName(Myname);
356 			if (*sys2 == NULLCHAR)
357 			    sys2p = Myname;
358 		        ruux(sys1, sys1, file1, sys2p, fwd2, file2);
359 		        continue;
360 		}
361 
362 	    /*
363 	     * check for forwarding -- generate and execute a uux command
364 	     * e.g.
365 	     *		uucp uucp.c raven!owl!~/dan/
366 	     */
367 
368 	    if (*fwd2 != NULLCHAR) {
369 	        ruux(sys2, sys1, file1, "", fwd2, file2);
370 	        continue;
371 	    }
372 
373 	    /*
374 	     * check for both source and destination on other systems --
375 	     *  generate and execute a uux command
376 	     */
377 
378 	    if (*sys1 != NULLCHAR )
379 		if ( (!EQUALS(Myname, sys1))
380 	    	  && *sys2 != NULLCHAR
381 	    	  && (!EQUALS(sys2, Myname)) ) {
382 		    ruux(sys2, sys1, file1, "", fwd2, file2);
383 	            continue;
384 	        }
385 
386 
387 	    sys2p = sys2;
388 	    if (*sys1 == NULLCHAR) {
389 		if (*sys2 == NULLCHAR)
390 		    sys2p = Myname;
391 		(void) strcpy(sys1, Myname);
392 	    } else {
393 		(void) strncpy(Rmtname, sys1, MAXBASENAME);
394 		Rmtname[MAXBASENAME] = NULLCHAR;
395 		/* get real Myname - it depends on who I'm calling--Rmtname */
396 		(void) mchFind(Rmtname);
397 		myName(Myname);
398 		if (*sys2 == NULLCHAR)
399 		    sys2p = Myname;
400 	    }
401 
402 	    DEBUG(4, "sys1 - %s, ", sys1);
403 	    DEBUG(4, "file1 - %s, ", file1);
404 	    DEBUG(4, "Rmtname - %s\n", Rmtname);
405 	    if (copy(sys1, file1, sys2p, file2))
406 	    	errors++;
407 	}
408 
409 	/* move the work files to their proper places */
410 	commitall();
411 
412 	/*
413 	 * Wait for all background uux processes to finish so
414 	 * that our caller will know that we're done with all
415 	 * input files and it's safe to remove them.
416 	 */
417 	while (wait(NULL) != -1)
418 		;
419 
420 	/*
421 	 * do not spawn daemon if -r option specified
422 	 */
423 	if (*Ropt != '-') {
424 #ifndef	V7
425 		long	limit;
426 		char	msg[100];
427 		limit = ulimit(1, (long) 0);
428 		if (limit < MINULIMIT)  {
429 			(void) sprintf(msg,
430 			    "ULIMIT (%ld) < MINULIMIT (%ld)", limit, MINULIMIT);
431 			logent(msg, "Low-ULIMIT");
432 		}
433 		else
434 #endif
435 			xuucico(Rmtname);
436 	}
437 	if (jflag) {
438 		(void) strncpy(Jobid, jid(), NAMESIZE);
439 		printf("%s\n", Jobid);
440 	}
441 	cleanup(errors);
442 	/*NOTREACHED*/
443 	return (0);
444 }
445 
446 /*
447  * cleanup lock files before exiting
448  */
449 void
450 cleanup(code)
451 int	code;
452 {
453 	static int first = 1;
454 
455 	if (first) {
456 		first = 0;
457 		rmlock(CNULL);
458 		if (code != 0)
459 			wfabort();  /* this may be extreme -- abort all work */
460 	}
461 	if (code < 0) {
462 	       (void) fprintf(stderr,
463 		   gettext("uucp failed completely (%d)\n"), (-code));
464 		exit(-code);
465 	}
466 	else if (code > 0) {
467 		(void) fprintf(stderr, gettext(
468 		    "uucp failed partially: %d file(s) sent; %d error(s)\n"),
469 		 _Transfer, code);
470 		exit(code);
471 	}
472 	exit(code);
473 }
474 
475 /*
476  * generate copy files for s1!f1 -> s2!f2
477  *	Note: only one remote machine, other situations
478  *	have been taken care of in main.
479  * return:
480  *	0	-> success
481  * Non-zero     -> failure
482  */
483 int
484 copy(s1, f1, s2, f2)
485 char *s1, *f1, *s2, *f2;
486 {
487 	FILE *cfp;
488 	static FILE *syscfile();
489 	struct stat stbuf, stbuf1;
490 	int type, statret;
491 	char dfile[NAMESIZE];
492 	char cfile[NAMESIZE];
493 	char command[10+(2*MAXFULLNAME)];
494 	char file1[MAXFULLNAME], file2[MAXFULLNAME];
495 	char msg[BUFSIZ];
496 
497 	type = 0;
498 	(void) strcpy(file1, f1);
499 	(void) strcpy(file2, f2);
500 	if (!EQUALS(s1, Myname))
501 		type = 1;
502 	if (!EQUALS(s2, Myname))
503 		type = 2;
504 
505 	DEBUG(4, "copy: file1=<%s> ", file1);
506 	DEBUG(4, "file2=<%s>\n", file2);
507 	switch (type) {
508 	case 0:
509 
510 		/*
511 		 * all work here
512 		 */
513 		DEBUG(4, "all work here %d\n", type);
514 
515 		/*
516 		 * check access control permissions
517 		 */
518 		if (ckexpf(file1))
519 			 return(-6);
520 		if (ckexpf(file2))
521 			 return(-7);
522 
523 		setuid(Uid);
524 		if (chkperm(file1, file2, strchr(Optns, 'd')) &&
525 		    (access(file2, W_OK) == -1)) {
526 			(void) fprintf(stderr, gettext("permission denied\n"));
527 			cleanup(1);
528 		}
529 
530 		/*
531 		 * copy file locally
532 		 *
533 		 * Changed from uidxcp() to fic file made and owner
534 		 * being modified for existing files, and local file
535 		 * name expansion.
536 		 */
537 		DEBUG(2, "local copy: %s -> ", file1);
538 		DEBUG(2, "%s\n", file2);
539 
540 		sprintf(command, "cp %s %s", file1, file2);
541 		if ((cfp = popen(command, "r")) == NULL) {
542 			perror("popen");
543 			DEBUG(5, "popen failed - errno %d\n", errno);
544 			setuid(Euid);
545 			return (FAIL);
546 		}
547 		if (pclose(cfp) != 0) {
548 			DEBUG(5, "Copy failed - errno %d\n", errno);
549 			return (FAIL);
550 		}
551 		setuid(Euid);
552 
553 		/*
554 		 * if user specified -m, notify "local" user
555 		 */
556 		 if ( Mail ) {
557 		 	sprintf(msg,
558 		 	"REQUEST: %s!%s --> %s!%s (%s)\n(SYSTEM %s) copy succeeded\n",
559 		 	s1, file1, s2, file2, User, s2 );
560 		 	mailst(User, "copy succeeded", msg, "", "");
561 		}
562 		/*
563 		 * if user specified -n, notify "remote" user
564 		 */
565 		if ( Notify ) {
566 			sprintf(msg, "%s from %s!%s arrived\n",
567 				file2, s1, User );
568 			mailst(Nuser, msg, msg, "", "");
569 		}
570 		return(0);
571 	case 1:
572 
573 		/*
574 		 * receive file
575 		 */
576 		DEBUG(4, "receive file - %d\n", type);
577 
578 		/*
579 		 * expand source and destination file names
580 		 * and check access permissions
581 		 */
582 		if (file1[0] != '~')
583 			if (ckexpf(file1))
584 				 return(6);
585 		if (ckexpf(file2))
586 			 return(7);
587 
588 
589 		gename(DATAPRE, s2, Grade, dfile);
590 
591 		/*
592 		 * insert JCL card in file
593 		 */
594 		cfp = syscfile(cfile, s1);
595 		(void) fprintf(cfp,
596 	       	"R %s %s %s %s %s %o %s %s\n", file1, file2,
597 			User, Optns,
598 			*Sfile ? Sfile : "dummy",
599 			0777, Nuser, dfile);
600 		(void) fclose(cfp);
601 		(void) sprintf(msg, "%s!%s --> %s!%s", Rmtname, file1,
602 		    Myname, file2);
603 		logent(msg, "QUEUED");
604 		break;
605 	case 2:
606 
607 		/*
608 		 * send file
609 		 */
610 		if (ckexpf(file1))
611 			 return(6);
612 		/* XQTDIR hook enables 3rd party uux requests (cough) */
613 		DEBUG(4, "Workdir = <%s>\n", Wrkdir);
614 		if (file2[0] != '~' && !EQUALS(Wrkdir, XQTDIR))
615 			if (ckexpf(file2))
616 				 return(7);
617 		DEBUG(4, "send file - %d\n", type);
618 
619 		if (uidstat(file1, &stbuf) != 0) {
620 			(void) fprintf(stderr,
621 			    gettext("can't get status for file %s\n"), file1);
622 			return(8);
623 		}
624 		if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
625 			(void) fprintf(stderr,
626 			    gettext("directory name illegal - %s\n"), file1);
627 			return(9);
628 		}
629 		/* see if I can read this file as read uid, gid */
630 		if (access(file1, R_OK) != 0) {
631 			(void) fprintf(stderr,
632 			    gettext("uucp can't read (%s) mode (%o)\n"),
633 			    file1, stbuf.st_mode&0777);
634 			return(3);
635 		}
636 
637 		/*
638 		 * make a copy of file in spool directory
639 		 */
640 
641 		gename(DATAPRE, s2, Grade, dfile);
642 
643 		if (Copy || !READANY(file1) ) {
644 
645 			if (uidxcp(file1, dfile))
646 			    return(5);
647 
648 			(void) chmod(dfile, DFILEMODE);
649 		}
650 
651 		cfp = syscfile(cfile, s2);
652 		(void) fprintf(cfp, "S %s %s %s %s %s %lo %s %s\n",
653 		    file1, file2, User, Optns, dfile,
654 		    (long) stbuf.st_mode & LEGALMODE, Nuser, Sfile);
655 		(void) fclose(cfp);
656 		(void) sprintf(msg, "%s!%s --> %s!%s", Myname, file1,
657 		    Rmtname, file2);
658 		logent(msg, "QUEUED");
659 		break;
660 	}
661 	_Transfer++;
662 	return(0);
663 }
664 
665 
666 /*
667  *	syscfile(file, sys)
668  *	char	*file, *sys;
669  *
670  *	get the cfile for system sys (creat if need be)
671  *	return stream pointer
672  *
673  *	returns
674  *		stream pointer to open cfile
675  *
676  */
677 
678 static FILE	*
679 syscfile(file, sys)
680 char	*file, *sys;
681 {
682 	FILE	*cfp;
683 
684 	if (gtcfile(file, sys) == FAIL) {
685 		gename(CMDPRE, sys, Grade, file);
686 		ASSERT(access(file, 0) != 0, Fl_EXISTS, file, errno);
687 		cfp = fdopen(creat(file, CFILEMODE), "w");
688 		svcfile(file, sys, Sgrade);
689 	} else
690 		cfp = fopen(file, "a");
691 	ASSERT(cfp != NULL, Ct_OPEN, file, errno);
692 	return(cfp);
693 }
694 
695 
696 /*
697  * generate and execute a uux command
698  */
699 
700 void
701 ruux(rmt, sys1, file1, sys2, fwd2, file2)
702 char *rmt, *sys1, *file1, *sys2, *fwd2, *file2;
703 {
704     char cmd[BUFSIZ];
705     char xcmd[BUFSIZ];
706     char * xarg[6];
707     int narg = 0;
708     int i;
709 
710     /* get real Myname - it depends on who I'm calling--rmt */
711     (void) mchFind(rmt);
712     myName(Myname);
713 
714     xarg[narg++] = UUX;
715     xarg[narg++] = "-C";
716     if (*Xopts != NULLCHAR)
717 	xarg[narg++] = Xopts;
718     if (*Ropt  != ' ')
719 	xarg[narg++] = Ropt;
720 
721     (void) sprintf(cmd, "%s!uucp -C", rmt);
722 
723     if (*Uopts != NULLCHAR)
724 	(void) sprintf(cmd+strlen(cmd), " (%s) ", Uopts);
725 
726     if (*sys1 == NULLCHAR || EQUALS(sys1, Myname)) {
727         if (ckexpf(file1))
728   	    exit(6);
729 	(void) sprintf(cmd+strlen(cmd), " %s!%s ", sys1, file1);
730     }
731     else
732 	if (!EQUALS(rmt, sys1))
733 	    (void) sprintf(cmd+strlen(cmd), " (%s!%s) ", sys1, file1);
734 	else
735 	    (void) sprintf(cmd+strlen(cmd), " (%s) ", file1);
736 
737     if (*fwd2 != NULLCHAR) {
738 	if (*sys2 != NULLCHAR)
739 	    (void) sprintf(cmd+strlen(cmd),
740 		" (%s!%s!%s) ", sys2, fwd2, file2);
741 	else
742 	    (void) sprintf(cmd+strlen(cmd), " (%s!%s) ", fwd2, file2);
743     }
744     else {
745 	if (*sys2 == NULLCHAR || EQUALS(sys2, Myname))
746 	    if (ckexpf(file2))
747 		exit(7);
748 	(void) sprintf(cmd+strlen(cmd), " (%s!%s) ", sys2, file2);
749     }
750 
751     xarg[narg++] = cmd;
752     xarg[narg] = (char *) 0;
753 
754     xcmd[0] = NULLCHAR;
755     for (i=0; i < narg; i++) {
756 	strcat(xcmd, xarg[i]);
757 	strcat(xcmd, " ");
758     }
759     DEBUG(2, "cmd: %s\n", xcmd);
760     logent(xcmd, "QUEUED");
761 
762     if (fork() == 0) {
763 	ASSERT(setuid(getuid()) == 0, "setuid", "failed", 99);
764 	execv(UUX, xarg);
765 	exit(0);
766     }
767     return;
768 }
769 
770 void
771 usage()
772 {
773 
774 	(void) fprintf(stderr, gettext(
775 	"Usage:  %s [-c|-C] [-d|-f] [-g GRADE] [-jm] [-n USER]\\\n"
776 	"[-r] [-s FILE] [-x DEBUG_LEVEL] source-files destination-file\n"),
777 	Progname);
778 	cleanup(-2);
779 }
780