1 /* $Id: pcp.c,v 1.31 2008/02/27 18:52:39 garbled Exp $ */
2 /*
3  * Copyright (c) 1998, 1999, 2000
4  *	Tim Rightnour.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. All advertising materials mentioning features or use of this software
15  *    must display the following acknowledgement:
16  *	This product includes software developed by Tim Rightnour.
17  * 4. The name of Tim Rightnour may not be used to endorse or promote
18  *    products derived from this software without specific prior written
19  *    permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY TIM RIGHTNOUR ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL TIM RIGHTNOUR BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <fcntl.h>
35 #include <poll.h>
36 #include <libgen.h>
37 #include <signal.h>
38 #include <unistd.h>
39 #include <sys/time.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <sys/resource.h>
43 #include <sys/wait.h>
44 #include "../common/common.h"
45 
46 #if !defined(lint) && defined(__NetBSD__)
47 __COPYRIGHT(
48 "@(#) Copyright (c) 1998, 1999, 2000\n\
49         Tim Rightnour.  All rights reserved.\n");
50 __RCSID("$Id: pcp.c,v 1.31 2008/02/27 18:52:39 garbled Exp $");
51 #endif
52 
53 extern int errno;
54 
55 void do_copy(char **argv, int recurse, int preserve, char *username);
56 void paralell_copy(char *rcp, int nrof, char *username, char *source_file,
57     char *destination_file);
58 void serial_copy(char *rcp, char *username, char *source_file,
59     char *destination_file);
60 void reverse_copy(char *rcp, char *username, char *source_file,
61     char *destination_file);
62 
63 char **lumplist;
64 char **rungroup;
65 char *progname;
66 int fanout, concurrent, quiet, debug, grouping, exclusion, nrofrungroups;
67 int testflag, rshport, porttimeout, bflag;
68 node_t *nodelink;
69 group_t *grouplist;
70 volatile sig_atomic_t alarmtime;
71 
72 /*
73  *  pcp is a cluster management tool based on the IBM tool of the
74  *  same name.  It allows a user, or system administrator to copy files
75  *  to a cluster of machines with a single command.
76  */
77 
78 int
main(int argc,char ** argv)79 main(int argc, char **argv)
80 {
81     extern char *optarg;
82     extern int optind;
83     extern char *version;
84 
85     int someflag, ch, i, preserve, recurse;
86     char *p, *nodename, *username, *group;
87     char **exclude;
88     node_t *nodeptr;
89 
90     fanout = 0;
91     quiet = 1;
92     concurrent = 0;
93     someflag = 0;
94     preserve = 0;
95     recurse = 0;
96     exclusion = 0;
97     grouping = 0;
98     nrofrungroups = 0;
99     testflag = 0;
100     rshport = 0;
101     bflag = 0;
102     porttimeout = 5; /* 5 seconds to port timeout */
103     username = NULL;
104     group = NULL;
105     nodeptr = NULL;
106     nodelink = NULL;
107     exclude = NULL;
108 
109     rungroup = calloc(GROUP_MALLOC, sizeof(char **));
110     if (rungroup == NULL)
111 	bailout();
112 
113     progname = strdup(basename(argv[0]));
114 
115 #if defined(__linux__)
116     while ((ch = getopt(argc, argv, "+?bcdeprtvf:g:l:n:o:w:x:")) != -1)
117 #else
118     while ((ch = getopt(argc, argv, "?bcdeprtvf:g:l:n:o:w:x:")) != -1)
119 #endif
120 	switch (ch) {
121 	case 'b':               /* set reverse/backwards mode */
122 	   bflag = 1;
123 	   break;
124 	case 'c':		/* set concurrent mode */
125 	    concurrent = 1;
126 	    break;
127 	case 'd':		/* hidden debug mode */
128 	    debug = 1;
129 	    break;
130 	case 'e':		/* display error messages */
131 	    quiet = 0;
132 	    break;
133 	case 'p':		/* preserve file modes */
134 	    preserve = 1;
135 	    break;
136 	case 'r':		/* recursive directory operations */
137 	    recurse = 1;
138 	    break;
139 	case 't':           /* test the nodes before connecting */
140 	    testflag = 1;
141 	    break;
142 	case 'n':           /* what is the rsh port number? */
143 	    rshport = atoi(optarg);
144 	    break;
145 	case 'o':               /* set the test timeout in seconds */
146 	    porttimeout = atoi(optarg);
147 	    break;
148 	case 'l':               /* invoke me as some other user */
149 	    username = strdup(optarg);
150 	    break;
151 	case 'f':		/* set the fanout size */
152 	    fanout = atoi(optarg);
153 	    break;
154 	case 'g':		/* pick a group to run on */
155 	    grouping = 1;
156 	    nrofrungroups = parse_gopt(optarg);
157 	    break;
158 	case 'x':		/* exclude nodes, w overrides this */
159 	    exclusion = 1;
160 	    exclude = parse_xopt(optarg);
161 	    break;
162 	case 'w':		/* perform operation on these nodes */
163 	    someflag = 1;
164 	    i = 0;
165 	    for (p = optarg; p != NULL; ) {
166 		nodename = (char *)strsep(&p, ",");
167 		if (nodename != NULL)
168 		    (void)nodealloc(nodename);
169 	    }
170 	    break;
171 	case 'v':
172 	    (void)printf("%s: %s\n", progname, version);
173 	    exit(EXIT_SUCCESS);
174 	    break;
175 	case '?':
176 	    (void)fprintf(stderr,
177 		"usage: %s [-ceprv] [-f fanout] [-g rungroup1,...,rungroupN] "
178 		"[-l username] [-x node1,...,nodeN] [-w node1,..,nodeN] "
179 		"source_file1 [source_file2 ... source_fileN] "
180 		"[desitination_file]\n", progname);
181 	    return(EXIT_FAILURE);
182 	    /* NOTREACHED */
183 	    break;
184 	default:
185 	    break;
186 	}
187     if (fanout == 0 && getenv("FANOUT"))
188 	    fanout = atoi(getenv("FANOUT"));
189     else
190 	    fanout = DEFAULT_FANOUT;
191     if (username == NULL && getenv("RCP_USER"))
192 	    username = strdup(getenv("RCP_USER"));
193     if (!rshport && getenv("RCP_PORT"))
194 	    rshport = atoi(getenv("RCP_PORT"));
195     if (!testflag && getenv("RCMD_TEST"))
196 	    testflag = 1;
197     if (porttimeout == 5 && getenv("RCMD_TEST_TIMEOUT"))
198 	    porttimeout = atoi(getenv("RCMD_TEST_TIMEOUT"));
199 
200     rshport = get_rshport(testflag, rshport, "RCP_CMD");
201 
202     if (!someflag)
203 	parse_cluster(exclude);
204 
205     argc -= optind;
206     argv += optind;
207     do_copy(argv, recurse, preserve, username);
208     return(EXIT_SUCCESS);
209 }
210 
211 /*
212  * Do the actual dirty work of the program, now that the arguments
213  * have all been parsed out.
214  */
215 
do_copy(char ** argv,int recurse,int preserve,char * username)216 void do_copy(char **argv, int recurse, int preserve, char *username)
217 {
218     int numsource, j, nrofargs;
219     size_t len, rem;
220     char *source_file, *destination_file, *tempstore, **rcp, *rcpstring;
221     char **argvp;
222     node_t *nodeptr;
223 
224     if (debug) {
225 	j = 0;
226 	if (username != NULL)
227 	    (void)printf("As User: %s\n", username);
228 	(void)printf("On nodes:\n");
229 	for (nodeptr = nodelink; nodeptr; nodeptr = nodeptr->next) {
230 	    if (!(j % 4) && j > 0)
231 		(void)printf("\n");
232 	    (void)printf("%s\t", nodeptr->name);
233 	    j++;
234 	}
235     }
236     if (*argv == (char *)NULL) {
237 	(void)fprintf(stderr, "Must specify at least one file to copy\n");
238 	exit(EXIT_FAILURE);
239     }
240     argvp = argv;
241     len = strlen(*argvp) + 2;
242     while (*++argvp != NULL) {
243 	    len += 1; /* space */
244 	    len += strlen(*argvp);
245     }
246 
247     source_file = calloc(len, sizeof(char));
248     rem = len;
249 
250     tempstore = NULL;
251     strncpy(source_file, *argv, rem);
252     rem -= strlen(*argv);
253     numsource = 1;
254     while (*++argv != NULL) {
255 	numsource++;
256 	if (tempstore != NULL) {
257 	    (void)strncat(source_file, " ", rem);
258 	    rem -= 1;
259 	    (void)strncat(source_file, tempstore, rem);
260 	    rem -= strlen(tempstore);
261 	}
262 	tempstore = *argv;
263     }
264     if (numsource == 1)
265 	destination_file = strdup(source_file);
266     else
267 	destination_file = strdup(tempstore);
268 
269     if (bflag && numsource > 2) {
270 	fprintf(stderr, "Can only specify one file for reverse copy\n");
271 	bailout();
272     }
273 
274     if (bflag && numsource == 1) {
275 	    free(destination_file);
276 	    destination_file = strdup(".");
277     }
278 
279     if (debug)
280 	printf("\nDo Copy: %s %s\n", source_file, destination_file);
281 
282     rcp = parse_rcmd("RCP_CMD", "RCP_CMD_ARGS", &nrofargs);
283     j = nrofargs;
284     rcpstring = build_rshstring(rcp, nrofargs);
285     if (recurse) {
286 	    strcat(rcpstring, " -r ");
287 	    nrofargs++;
288     }
289     if (preserve) {
290 	    strcat(rcpstring, " -p ");
291 	    nrofargs++;
292     }
293 
294     if (bflag)
295 	    reverse_copy(rcpstring, username, source_file, destination_file);
296     else if (concurrent)
297 	    paralell_copy(rcpstring, nrofargs + numsource, username,
298 		source_file, destination_file);
299     else
300 	    serial_copy(rcpstring, username, source_file, destination_file);
301     free(source_file);
302     free(destination_file);
303     for (nrofargs=0; nrofargs < j-2; nrofargs++) {
304 	    if (rcp[nrofargs] == NULL)
305 		break;
306 	    free(rcp[nrofargs]);
307 	}
308     free(rcp);
309 }
310 
311 /* Copy files in paralell.  This is preferred with smaller files, because
312    the initial connection and authentication latency is longer than the
313    file transfer.  With large files, you generate more collisions than
314    good packets, and actually slow it down, thus serial is faster. */
315 
316 void
paralell_copy(char * rcp,int nrof,char * username,char * source_file,char * destination_file)317 paralell_copy(char *rcp, int nrof, char *username, char *source_file,
318 	      char *destination_file)
319 {
320     int i, j, n, g, status, pollret, fdf;
321     char *rcpstring, pipebuf[2048], *cd;
322     FILE *fd, *fda, *in;
323     char **argz, **aps;
324     node_t *nodeptr, *nodehold;
325     size_t maxnodelen, rcplen;
326     pid_t currentchild;
327     struct pollfd fds[2];
328 
329     j = i = maxnodelen = 0;
330     in = NULL;
331     cd = pipebuf;
332 
333     argz = calloc(nrof + 3, sizeof(char *));
334     if (argz == NULL)
335 	    bailout();
336 
337     for (nodeptr = nodelink; nodeptr; nodeptr = nodeptr->next) {
338 	if (strlen(nodeptr->name) > maxnodelen)
339 	    maxnodelen = strlen(nodeptr->name);
340 	j++;
341     }
342 
343     if (username)
344 	    rcplen = strlen(username) + 128;
345     else
346 	    rcplen = 128;
347     rcplen += strlen(source_file) + strlen(rcp) + maxnodelen +
348 	strlen(destination_file);
349     rcpstring = calloc(rcplen, sizeof(char));
350     if (rcpstring == NULL)
351 	    bailout();
352 
353     i = j;
354     j = i / fanout;
355     if (i % fanout)
356 	j++;
357 
358     g = 0;
359     nodeptr = nodelink;
360     for (n=0; n <= j; n++) {
361 	nodehold = nodeptr;
362 	for (i=0; (i < fanout && nodeptr != NULL); i++) {
363 	    g++;
364 	    /*
365 	     * we set up pipes for each node, to prepare for the oncoming
366 	     * barrage of data. Close on exec must be set here, otherwise
367 	     * children spawned after other children, inherit the open file
368 	     * descriptors, and cause the pipes to remain  open forever.
369 	     */
370 	    if (pipe(nodeptr->out.fds) != 0)
371 		bailout();
372 	    if (pipe(nodeptr->err.fds) != 0)
373 		bailout();
374 	    if (fcntl(nodeptr->out.fds[0], F_SETFD, 1) == -1)
375 		bailout();
376 	    if (fcntl(nodeptr->out.fds[1], F_SETFD, 1) == -1)
377 		bailout();
378 	    if (fcntl(nodeptr->err.fds[0], F_SETFD, 1) == -1)
379 		bailout();
380 	    if (fcntl(nodeptr->err.fds[1], F_SETFD, 1) == -1)
381 		bailout();
382 	    if (username != NULL)
383 		(void)snprintf(rcpstring, rcplen, "%s %s %s@%s:%s", rcp,
384 			       source_file, username, nodeptr->name,
385 			       destination_file);
386 	    else
387 		(void)snprintf(rcpstring, rcplen, "%s %s %s:%s", rcp,
388 			       source_file, nodeptr->name, destination_file);
389 	    if (debug)
390 		printf("Running command: %s\n", rcpstring);
391 	    nodeptr->childpid = fork();
392 	    switch (nodeptr->childpid) {
393 	    case -1:
394 		bailout();
395 		break;
396 	    case 0:
397 		if (dup2(nodeptr->out.fds[1], STDOUT_FILENO) != STDOUT_FILENO)
398 		    bailout();
399 		if (dup2(nodeptr->err.fds[1], STDERR_FILENO) != STDERR_FILENO)
400 		    bailout();
401 		if (close(nodeptr->out.fds[0]) != 0)
402 		    bailout();
403 		if (close(nodeptr->err.fds[0]) != 0)
404 		    bailout();
405 		/* stdin & stderr non-blocking */
406 		fdf = fcntl(nodeptr->out.fds[0], F_GETFL);
407 		fcntl(nodeptr->out.fds[0], F_SETFL, fdf|O_NONBLOCK);
408 		fdf = fcntl(nodeptr->err.fds[0], F_GETFL);
409 		fcntl(nodeptr->err.fds[0], F_SETFL, fdf|O_NONBLOCK);
410 
411 		if (testflag && rshport > 0 && porttimeout > 0)
412 			if (!test_node_connection(rshport, porttimeout,
413 						  nodeptr))
414 			    _exit(EXIT_SUCCESS);
415 		for (aps = argz; (*aps = strsep(&rcpstring, " ")) != NULL;)
416 		    if (**aps != '\0')
417 			++aps;
418 		execvp(argz[0], argz);
419 		bailout();
420 	    default:
421 		break;
422 	    } /* switch */
423 	    nodeptr = nodeptr->next;
424 	} /* for i */
425 	nodeptr = nodehold;
426 	for (i=0; (i < fanout && nodeptr != NULL); i++) {
427 	    currentchild = nodeptr->childpid;
428 	    /* now close off the useless stuff, and read the goodies */
429 	    if (close(nodeptr->out.fds[1]) != 0)
430 		bailout();
431 	    if (close(nodeptr->err.fds[1]) != 0)
432 		bailout();
433 	    fda = fdopen(nodeptr->out.fds[0], "r"); /* stdout */
434 	    if (fda == NULL)
435 		bailout();
436 	    fd = fdopen(nodeptr->err.fds[0], "r"); /* stderr */
437 	    if (fd == NULL)
438 		bailout();
439 	    fds[0].fd = nodeptr->out.fds[0];
440 	    fds[1].fd = nodeptr->err.fds[0];
441 	    fds[0].events = POLLIN|POLLPRI;
442 	    fds[1].events = POLLIN|POLLPRI;
443 	    pollret = 1;
444 
445 	    while (pollret >= 0) {
446 		int gotdata;
447 
448 		pollret = poll(fds, 2, 5);
449 		gotdata = 0;
450 		if ((fds[0].revents&POLLIN) == POLLIN ||
451 		    (fds[0].revents&POLLHUP) == POLLHUP ||
452 		    (fds[0].revents&POLLPRI) == POLLPRI) {
453 #ifdef __linux__
454 		    cd = fgets(pipebuf, sizeof(pipebuf), fda);
455 		    if (cd != NULL) {
456 #else
457 		    while ((cd = fgets(pipebuf, sizeof(pipebuf), fda))) {
458 #endif
459 			if (!quiet)
460 			    (void)printf("%*s: %s", -maxnodelen,
461 				nodeptr->name, cd);
462 			gotdata++;
463 		    }
464 		}
465 		if ((fds[1].revents&POLLIN) == POLLIN ||
466 		    (fds[1].revents&POLLHUP) == POLLHUP ||
467 		    (fds[1].revents&POLLPRI) == POLLPRI) {
468 #ifdef __linux__
469 		    cd = fgets(pipebuf, sizeof(pipebuf), fd);
470 		    if (cd != NULL) {
471 #else
472 		    while ((cd = fgets(pipebuf, sizeof(pipebuf), fd))) {
473 #endif
474 			if (!quiet)
475 				(void)printf("%*s: %s", -maxnodelen,
476 				    nodeptr->name, cd);
477 			gotdata++;
478 		    }
479 		}
480 		if (!gotdata)
481 		    if (((fds[0].revents&POLLHUP) == POLLHUP ||
482 			 (fds[0].revents&POLLERR) == POLLERR ||
483 			 (fds[0].revents&POLLNVAL) == POLLNVAL) &&
484 			((fds[1].revents&POLLHUP) == POLLHUP ||
485 			 (fds[1].revents&POLLERR) == POLLERR ||
486 			 (fds[1].revents&POLLNVAL) == POLLNVAL))
487 			break;
488 	    }
489 	    fclose(fda);
490 	    fclose(fd);
491 	    (void)wait(&status);
492 	    nodeptr = nodeptr->next;
493 	} /* pipe read */
494     } /* for n */
495     free(argz);
496     free(rcpstring);
497 }
498 
499 /* serial copy */
500 
501 void
502 serial_copy(char *rcp, char *username, char *source_file,
503 	    char *destination_file)
504 {
505 	node_t *nodeptr;
506 	char *command;
507 	size_t maxnodelen, rcplen;
508 
509 	maxnodelen = 0;
510 
511 	for (nodeptr = nodelink; nodeptr; nodeptr = nodeptr->next) {
512 		if (strlen(nodeptr->name) > maxnodelen)
513 			maxnodelen = strlen(nodeptr->name);
514 	}
515 	if (username)
516 		rcplen = strlen(username) + 128;
517 	else
518 		rcplen = 128;
519 	rcplen += strlen(source_file) + strlen(rcp) + maxnodelen +
520 	    strlen(destination_file);
521 	command = calloc(rcplen, sizeof(char));
522 	if (command == NULL)
523 		bailout();
524 
525 	for (nodeptr=nodelink; nodeptr != NULL; nodeptr = nodeptr->next) {
526 		if (testflag && rshport > 0 && porttimeout > 0)
527 			if (!test_node_connection(rshport, porttimeout,
528 				nodeptr))
529 			    continue;
530 		if (username != NULL)
531 			(void)snprintf(command, rcplen, "%s %s %s@%s:%s", rcp,
532 			    source_file, username, nodeptr->name,
533 			    destination_file);
534 		else
535 			(void)snprintf(command, rcplen, "%s %s %s:%s", rcp,
536 			    source_file, nodeptr->name, destination_file);
537 		if (debug)
538 			printf("Running command: %s\n", command);
539 		system(command);
540 	}
541 	free(command);
542 }
543 
544 /* Reverse copy */
545 
546 void
547 reverse_copy(char *rcp, char *username, char *source_file,
548 	    char *destination_file)
549 {
550 	node_t *nodeptr;
551 	char *command, *bname;
552 	struct stat st;
553 	size_t maxnodelen, rcplen;
554 	int isdir=0;
555 
556 	maxnodelen = 0;
557 
558 	for (nodeptr = nodelink; nodeptr; nodeptr = nodeptr->next) {
559 		if (strlen(nodeptr->name) > maxnodelen)
560 			maxnodelen = strlen(nodeptr->name);
561 	}
562 
563 	if (stat(destination_file, &st) == 0)
564 		if (st.st_mode && S_IFDIR)
565 			isdir = 1;
566 
567 	bname = basename(source_file);
568 
569 	if (username)
570 		rcplen = strlen(username) + 128;
571 	else
572 		rcplen = 128;
573 	rcplen += strlen(source_file) + strlen(rcp) + maxnodelen +
574 	    maxnodelen + strlen(destination_file);
575 	command = calloc(rcplen, sizeof(char));
576 	if (command == NULL)
577 		bailout();
578 
579 	for (nodeptr=nodelink; nodeptr != NULL; nodeptr = nodeptr->next) {
580 		if (testflag && rshport > 0 && porttimeout > 0)
581 			if (!test_node_connection(rshport, porttimeout,
582 				nodeptr))
583 			    continue;
584 		if (isdir && username != NULL)
585 			(void)snprintf(command, rcplen, "%s %s@%s:%s %s/%s.%s",
586 			    rcp, username, nodeptr->name, source_file,
587 			    destination_file, bname, nodeptr->name);
588 		else if (!isdir && username != NULL)
589 			(void)snprintf(command, rcplen, "%s %s@%s:%s %s.%s",
590 			    rcp, username, nodeptr->name, source_file,
591 			    destination_file, nodeptr->name);
592 		else if (isdir)
593 			(void)snprintf(command, rcplen, "%s %s:%s %s/%s.%s",
594 			    rcp, nodeptr->name, source_file, destination_file,
595 			    bname, nodeptr->name);
596 		else
597 			(void)snprintf(command, rcplen, "%s %s:%s %s.%s", rcp,
598 			    nodeptr->name, source_file, destination_file,
599 			    nodeptr->name);
600 		if (debug)
601 			printf("Running command: %s\n", command);
602 		system(command);
603 	}
604 	free(command);
605 }
606