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