1 /* pperl - run perl scripts persistently */
2
3 #include <stdio.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdarg.h>
7 #include <memory.h>
8 #include <errno.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 #include <sys/socket.h>
12 #include <sys/un.h>
13 #include <sys/stat.h>
14 #include <sys/time.h>
15 #include <fcntl.h>
16 #include <limits.h>
17 #include <signal.h>
18 #include <unistd.h>
19 #include "pperl.h"
20
21 #include "pass_fd.h" /* the stuff borrowed from stevens */
22
23 #define DEBUG 1
24
25 /* must never be less than 3 */
26 #define BUF_SIZE 4096
27
28
29 #ifdef ENOBUFS
30 # define NO_BUFSPC(e) ((e) == ENOBUFS || (e) == ENOMEM)
31 #else
32 # define NO_BUFSPC(e) ((e) == ENOMEM)
33 #endif
34
35 static void Usage( char *pName );
36 static void DecodeParm( char *pArg );
37 static int DispatchCall( char *scriptname, int argc, char **argv );
38
39 char *pVersion = PPERL_VERSION;
40 char perl_options[1024];
41 extern char **environ;
42 pid_t connected_to;
43 int kill_script = 0;
44 int any_user = 0;
45 int prefork = 5;
46 int maxclients = 100;
47 int path_max;
48 int no_cleanup = 0;
49 FILE *log_fd = NULL;
50
51 #if DEBUG
52 #define Dx(x) (x)
53 #else
54 #define Dx(x)
55 #endif
56
57 static
Debug(const char * format,...)58 void Debug( const char * format, ...)
59 {
60 va_list args;
61
62 if (!log_fd)
63 return;
64
65 va_start(args, format);
66 vfprintf(log_fd, format, args);
67 va_end(args);
68 fflush(log_fd);
69 }
70
71
main(int argc,char ** argv)72 int main( int argc, char **argv )
73 {
74 int i;
75 char *pArg;
76 int pperl_section = 0;
77 int return_code = 0;
78
79 if( argc < 2 )
80 Usage( argv[0] );
81
82 #ifdef PATH_MAX
83 path_max = PATH_MAX;
84 #else
85 path_max = pathconf (path, _PC_PATH_MAX);
86 if (path_max <= 0) {
87 path_max = 4096;
88 }
89 #endif
90
91 pperl_section = 0;
92 for ( i = 1; i < argc; i++ ) {
93 pArg = argv[i];
94 /* fprintf(stderr, "Parsing arg: %s\n", pArg); */
95 if (*pArg != '-') break;
96 if ( !strcmp(pArg, "-k") || !strcmp(pArg, "--kill") )
97 kill_script = 1;
98 else if (!strncmp(pArg, "--prefork", 9) ) {
99 int newval;
100 if (pArg[9] == '=') /* "--prefork=20" */
101 pArg += 10;
102 else /* "--prefork" "20" */
103 pArg = argv[++i];
104
105 newval = atoi(pArg);
106 if (newval > 0) prefork = newval;
107 }
108 else if (!strncmp(pArg, "--logfile", 9) ) {
109 int newval;
110 char *filename;
111 if (pArg[9] == '=') /* --logfile=.... */
112 pArg += 10;
113 else
114 pArg = argv[++i];
115
116 filename = pArg;
117 newval = atoi(pArg);
118 if (newval == 0) {
119 fprintf(stderr, "opening log_fd: %s\n", filename);
120 log_fd = fopen(filename, "a");
121 if (!log_fd) {
122 perror("Cannot open logfile");
123 exit(1);
124 }
125 }
126 else {
127 log_fd = fdopen(newval, "a");
128 if (!log_fd) {
129 perror("fd for --logfile error");
130 exit(1);
131 }
132 }
133 }
134 else if (!strncmp(pArg, "--no-cleanup", 12) ) {
135 no_cleanup = 1;
136 }
137 else if (!strncmp(pArg, "--maxclients", 12) ) {
138 int newval;
139 if (pArg[12] == '=') /* "--maxclients=20" */
140 pArg += 13;
141 else /* "--maxclients" "20" */
142 pArg = argv[++i];
143
144 newval = atoi(pArg);
145 if (newval > 0) maxclients = newval;
146 }
147 else if ( !strcmp(pArg, "-z") || !strcmp(pArg, "--anyuser") )
148 any_user = 1;
149 else if ( !strcmp(pArg, "-h") || !strcmp(pArg, "--help") )
150 Usage( NULL );
151 else if ( !strncmp(pArg, "--", 2) )
152 ; /* do nothing - backward compatibility */
153 else {
154 DecodeParm( pArg );
155 }
156 }
157
158 i++;
159 return_code = DispatchCall( pArg, argc - i, (char**)(argv + i) );
160 Dx(Debug("done, returning %d\n", return_code));
161 if (log_fd) fclose(log_fd);
162 return return_code;
163 }
164
DecodeParm(char * pArg)165 static void DecodeParm( char *pArg )
166 {
167 if ( (strlen(perl_options) + strlen(pArg) + 1) > 1000 ) {
168 fprintf(stderr, "param list too long. Sorry.");
169 exit(1);
170 }
171 strcat(perl_options, pArg);
172 strcat(perl_options, " ");
173 }
174
Usage(char * pName)175 static void Usage( char *pName )
176 {
177 printf( "pperl version %s\n", pVersion );
178
179 if( pName == NULL )
180 {
181 printf( "Usage: pperl [options] filename\n" );
182 }
183 else
184 {
185 printf( "Usage: %.255s [options] filename\n", pName );
186 }
187 printf("perl options are passed to your perl executable (see the perlrun man page).\n"
188 "pperl options control the persistent perl behaviour\n"
189 "\n"
190 "PPerl Options:\n"
191 " -k or --kill Kill the currently running pperl for that script\n"
192 " -h or --help This page\n"
193 " --prefork The number of child processes to prefork (default=5)\n"
194 " --maxclients The number of client connections each child\n"
195 " will process (default=100)\n"
196 " -z or --anyuser Allow any user (after the first) to access the socket\n"
197 " WARNING: This has severe security implications. Use\n"
198 " at your own risk\n"
199 " --no-cleanup Skip the cleanup stage at the end of running your script\n"
200 " this may make your code run faster, but if you forget\n"
201 " to close files then they will remain unflushed and unclosed\n"
202 );
203 exit( 1 );
204 }
205
206 static void *
my_malloc(size_t size)207 my_malloc(size_t size)
208 {
209 void *mem = malloc(size);
210 if (mem == NULL) {
211 perror("malloc failed");
212 exit(-1);
213 }
214 return mem;
215 }
216
217 /* make socket name from scriptname, switching / for _ */
218 static char *
MakeSockName(char * scriptname)219 MakeSockName(char * scriptname )
220 {
221 char * sockname;
222 char * save;
223 /* strict C compilers can't/won't do char foo[variant]; */
224 char *fullpath = my_malloc(path_max);
225 int i = 0;
226 char euid[sizeof(uid_t)+10+2];
227 sprintf(euid, "_%d", geteuid());
228
229 if (realpath(scriptname, fullpath) == NULL) {
230 perror("pperl: resolving full pathname to script failed");
231 exit(1);
232 }
233 Dx(Debug("realpath returned: %s\n", fullpath));
234 /* Ugh. I am a terrible C programmer! */
235 sockname = my_malloc(strlen(P_tmpdir) + strlen(fullpath) + 3 + strlen(euid));
236 save = sockname;
237 sprintf(sockname, "%s/", P_tmpdir);
238 sockname += strlen(P_tmpdir) + 1;
239 while (fullpath[i] != '\0') {
240 if (fullpath[i] == '/') {
241 *sockname = '_';
242 }
243 else if (fullpath[i] == '.') {
244 *sockname = '_';
245 }
246 else {
247 *sockname = fullpath[i];
248 }
249 sockname++; i++;
250 }
251 strcat(sockname, euid);
252 free(fullpath);
253 return save;
254 }
255
256
257 static void
sig_handler(int sig)258 sig_handler(int sig)
259 {
260 kill(connected_to, sig);
261 signal(sig, sig_handler);
262 /* skreech_to_a_halt++; */
263 }
264
265 static int handle_socket(int sd, int argc, char **argv );
DispatchCall(char * scriptname,int argc,char ** argv)266 static int DispatchCall( char *scriptname, int argc, char **argv )
267 {
268 register int i, sd, len;
269 int error_number;
270 ssize_t readlen;
271 struct sockaddr_un saun;
272 struct stat stat_buf;
273 struct stat sock_stat;
274 char *sock_name;
275 char buf[BUF_SIZE];
276 int respawn_script = 0;
277 sd = 0;
278
279 /* create socket name */
280 Dx(Debug("pperl: %s\n", scriptname));
281 sock_name = MakeSockName(scriptname);
282 Dx(Debug("got socket: %s\n", sock_name));
283
284 if (!stat(sock_name, &sock_stat) && !stat(scriptname, &stat_buf)) {
285 if (stat_buf.st_mtime >= sock_stat.st_mtime) {
286 respawn_script = 1;
287 Dx(Debug("respawning slave - top level script changed\n"));
288 }
289 }
290
291 if (kill_script || respawn_script) {
292 int pid_fd, sock_name_len;
293 char *pid_file;
294 pid_t pid = 0;
295
296 respawn_script = 0; /* reset so we can use it later :-) */
297
298 sock_name_len = strlen(sock_name);
299 pid_file = my_malloc(sock_name_len + 5);
300 strncpy(pid_file, sock_name, sock_name_len);
301 pid_file[sock_name_len] = '.';
302 pid_file[sock_name_len+1] = 'p';
303 pid_file[sock_name_len+2] = 'i';
304 pid_file[sock_name_len+3] = 'd';
305 pid_file[sock_name_len+4] = '\0';
306
307 Dx(Debug("opening pid_file: %s\n", pid_file));
308 pid_fd = open(pid_file, O_RDONLY);
309 if (pid_fd == -1) {
310 Dx(Debug("Cannot open pid file (perhaps PPerl wasn't running for that script?)\n"));
311 write(1, "No process killed - no pid file\n", 32);
312 goto killed;
313 }
314
315 readlen = read(pid_fd, buf, BUF_SIZE);
316 if (readlen == -1) {
317 perror("pperl: nothing in pid file?");
318 goto killed;
319 }
320 buf[readlen] = '\0';
321
322 close(pid_fd);
323
324 pid = atoi(buf);
325 Dx(Debug("got pid %d (%s)\n", pid, buf));
326 if (kill(pid, SIGINT) == -1) {
327 if (errno == ESRCH) {
328 perror("pperl kill");
329 Dx(Debug("Process didn't exist. Unlinking %s and %s\n", pid_file, sock_name));
330 unlink(pid_file);
331 unlink(sock_name);
332 }
333 else {
334 perror("pperl: could not kill process");
335 }
336 }
337
338 free(pid_file);
339
340 killed:
341
342 if (kill_script) {
343 free(sock_name); /* Hmm, should probably do this everywhere else we return too */
344 return 0;
345 }
346
347 if (pid != 0) {
348 /* cheesy - let the child go away proper */
349 while (!kill(pid, 0)) {}
350 }
351 }
352
353 for (i = 0; i < 10; i++) {
354 sd = socket(PF_UNIX, SOCK_STREAM, PF_UNSPEC);
355 if (sd != -1) {
356 break;
357 }
358 else if (NO_BUFSPC(errno)) {
359 sleep(1);
360 }
361 else {
362 perror("pperl: Couldn't create socket");
363 return 1;
364 }
365 }
366
367 saun.sun_family = PF_UNIX;
368 strcpy(saun.sun_path, sock_name);
369
370 len = sizeof(saun.sun_family) + strlen(saun.sun_path) + 1;
371
372 Dx(Debug("%d connecting\n", getpid()));
373
374 if (stat((const char*)sock_name, &stat_buf)) {
375 if (errno == ENOENT) {
376 /* socket doesn't exist. good */
377 Dx(Debug("socket doesn't exist yet (good)\n"));
378 }
379 else {
380 perror("Socket stat error");
381 exit(1);
382 }
383 }
384
385 /* is there a race between stat() and connect() here? Or is it irrelevant? */
386
387 if (connect(sd, (struct sockaddr *)&saun, len) < 0) {
388 /* Consider spawning Perl here and try again */
389 FILE *source;
390 int tmp_fd;
391 char temp_file[BUF_SIZE];
392 char *lock_file;
393 int sock_name_len;
394 int lock_fd;
395 int start_checked = 0;
396 int wrote_footer = 0; /* we may encounter __END__ or __DATA__ */
397 int line;
398 int retry_connect = 0;
399 int exit_code = 0;
400
401 int pid, itmp, exitstatus;
402 sigset_t mask, omask;
403
404 Dx(Debug("Couldn't connect, spawning new server: %s\n", strerror(errno)));
405
406 sock_name_len = strlen(sock_name);
407 lock_file = my_malloc(sock_name_len + 6);
408 strncpy(lock_file, sock_name, sock_name_len);
409 lock_file[sock_name_len] = '.';
410 lock_file[sock_name_len+1] = 'l';
411 lock_file[sock_name_len+2] = 'o';
412 lock_file[sock_name_len+3] = 'c';
413 lock_file[sock_name_len+4] = 'k';
414 lock_file[sock_name_len+5] = '\0';
415
416 Dx(Debug("opening lock_file: %s\n", lock_file));
417 lock_fd = open(lock_file, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR);
418 if (lock_fd == -1) {
419 perror("Cannot open lock file");
420 exit_code = 1;
421 goto cleanup;
422 }
423 while (flock(lock_fd, LOCK_EX|LOCK_NB) == -1) {
424 Dx(Debug("flock failed - someone else is probably waiting to spawn - sleeping\n"));
425 retry_connect = 1;
426 sleep(1);
427 }
428
429 if (retry_connect) {
430 if (connect(sd, (struct sockaddr *)&saun, len) >= 0) {
431 goto cleanup; /* everything is now OK! */
432 }
433 /* otherwise we try ourselves to re-spawn */
434 }
435
436 /*
437 if (unlink(sock_name) != 0 && errno != ENOENT) {
438 perror("pperl: removal of old socket failed");
439 exit_code = 1;
440 goto cleanup;
441 }
442 */
443
444 /* Create temp file with adjusted script... */
445 if (!(source = fopen(scriptname, "r"))) {
446 perror("pperl: Cannot open perl script");
447 exit_code = 1;
448 goto cleanup;
449 }
450
451 snprintf(temp_file, BUF_SIZE, "%s/%s", P_tmpdir, "pperlXXXXXX");
452 tmp_fd = mkstemp(temp_file);
453 if (tmp_fd == -1) {
454 perror("pperl: Cannot create temporary file");
455 exit_code = 1;
456 goto cleanup;
457 }
458
459 write(tmp_fd, "### Temp File ###\n", 18);
460 write(tmp_fd, perl_header, strlen(perl_header));
461
462 /* rewrite the perl script with pperl.h.header contents wrapper
463 and do some other fixups in the process */
464 line = 0;
465 while ( fgets( buf, BUF_SIZE, source ) ) {
466 readlen = strlen(buf);
467 Dx(Debug("read '%s' %d \n", buf, readlen));
468
469 if (!start_checked) { /* first line */
470 start_checked = 1;
471
472 if (buf[0] == '#' && buf[1] == '!') {
473 char *args;
474 /* solaris sometimes doesn't propogate all the
475 * shebang line - so we do that here */
476 if ( (args = strstr(buf, " ")) ) {
477 strncat(perl_options, args, strlen(args) - 1);
478 }
479
480 write(tmp_fd, "\n#line 2 ", 9);
481 write(tmp_fd, scriptname, strlen(scriptname));
482 write(tmp_fd, "\n", 1);
483
484 line = 2;
485 continue;
486 }
487 else {
488 write(tmp_fd, "\n#line 1 ", 9);
489 write(tmp_fd, scriptname, strlen(scriptname));
490 write(tmp_fd, "\n", 1);
491 }
492 }
493 if ((!strcmp(buf, "__END__\n") ||
494 !strcmp(buf, "__DATA__\n")) &&
495 !wrote_footer) {
496 char text_line[BUF_SIZE];
497 wrote_footer = 1;
498 write(tmp_fd, perl_footer, strlen(perl_footer));
499 snprintf(text_line, BUF_SIZE, "package main;\n#line %d %s\n", line, scriptname);
500 write(tmp_fd, text_line, strlen(text_line));
501 }
502 write(tmp_fd, buf, readlen);
503 if (buf[readlen] == '\n') ++line;
504 }
505
506 if (fclose(source)) {
507 perror("pperl: Error reading perl script");
508 exit_code = 1;
509 goto cleanup;
510 }
511
512 if (!wrote_footer)
513 write(tmp_fd, perl_footer, strlen(perl_footer));
514
515 Dx(Debug("wrote file %s\n", temp_file));
516
517 close(tmp_fd);
518
519 /*** Temp file creation done ***/
520
521 snprintf(buf, BUF_SIZE, "%s %s %s %s %d %d %d %d %s",
522 PERL_INTERP, perl_options, temp_file,
523 sock_name, prefork, maxclients,
524 any_user, no_cleanup, scriptname);
525 Dx(Debug("syscall: %s\n", buf));
526
527 /* block SIGCHLD so noone else can wait() on the child before we do */
528 sigemptyset(&mask);
529 sigaddset(&mask, SIGCHLD);
530 sigprocmask(SIG_BLOCK, &mask, &omask);
531
532 if ((pid = system(buf)) != 0) {
533 unlink(temp_file);
534 if (stat((const char*)sock_name, &stat_buf) == 0) {
535 /* socket exists - perhaps we should just try and connect to it? */
536 /* possible cause is a race condition. So ignore this and just try
537 the connect() call again. */
538 perror("pperl: perl script failed to start, but lets be gung-ho and try and connect again anyway!");
539 }
540 perror("pperl: perl script failed to start");
541 exit_code = 1;
542 goto cleanup;
543 }
544 else {
545 Dx(Debug("waiting for perl to return...\n"));
546 while ((itmp = waitpid(0, &exitstatus, 0)) == -1 && errno == EINTR)
547 ;
548 sigprocmask(SIG_SETMASK, &omask, NULL);
549 Dx(Debug("returned.\n"));
550
551 /* now remove the perl script */
552 unlink(temp_file);
553 }
554
555 /* try and connect to the new socket */
556 while ((i++ <= 30) && (connect(sd, (struct sockaddr *)&saun, len) < 0))
557 {
558 Dx(Debug("."));
559 sleep(1);
560 }
561 if (i >= 30) {
562 /* If we really *really* couldn't connect, try and delete the socket if it exists */
563 if (unlink(sock_name) != 0 && errno != ENOENT) {
564 perror("pperl: removal of old socket failed");
565 }
566 perror("pperl: persistent perl process failed to start after 30 seconds");
567 exit_code = 1;
568 goto cleanup;
569 }
570
571 Dx(Debug("Connected\n"));
572
573 cleanup:
574 flock(lock_fd, LOCK_UN);
575 close(lock_fd);
576 free(lock_file);
577 if (exit_code > 0) {
578 free(sock_name);
579 exit(exit_code);
580 }
581 }
582
583 free(sock_name);
584 return handle_socket(sd, argc, argv);
585 }
586
587 static
588 int
handle_socket(int sd,int argc,char ** argv)589 handle_socket(int sd, int argc, char **argv) {
590 long max_fd;
591 char **env;
592 int i;
593 char buf[BUF_SIZE];
594
595 Dx(Debug("connected over %d\n", sd));
596
597 read(sd, buf, 10);
598 buf[10] = '\0';
599 connected_to = atoi(buf);
600 Dx(Debug("chatting to %d, hooking signals\n", connected_to));
601
602 /* bad magic number, there only seem to be 30 signals on a linux
603 * box -- richardc*/
604 for (i = 1; i < 32; i++)
605 signal(i, sig_handler);
606
607
608 Dx(Debug("sending fds\n"));
609 if ((max_fd = sysconf(_SC_OPEN_MAX)) < 0) {
610 perror("pperl: dunno how many fds to check");
611 exit(1);
612 }
613
614 for (i = 0; i < max_fd; i++) {
615 if (fcntl(i, F_GETFL, -1) >= 0 && i != sd) {
616 int ret;
617 write(sd, &i, sizeof(int));
618 ret = send_fd(sd, i);
619 Dx(Debug("send_fd %d %d\n", i, ret));
620 }
621 }
622 i = -1;
623 write(sd, &i, sizeof(int));
624 Dx(Debug("fds sent\n"));
625
626 write(sd, "[PID]", 6);
627 snprintf(buf, BUF_SIZE, "%d", getpid());
628 write(sd, buf, strlen(buf) + 1);
629
630
631 /* print to socket... */
632 write(sd, "[ENV]", 6);
633 for (i= 0, env = environ; *env; i++, env++);
634 snprintf(buf, BUF_SIZE, "%d", i);
635 write(sd, buf, strlen(buf) + 1);
636
637 while ( *environ != NULL ) {
638 size_t len = strlen(*environ) + 1;
639 /* Dx(Debug("sending environ: %s\n", *environ)); */
640 write(sd, *environ, len);
641 environ++;
642 }
643
644 write(sd, "[CWD]", 6);
645 if (getcwd(buf, BUF_SIZE) == NULL) {
646 perror("pperl: getcwd");
647 exit (1);
648 }
649 write(sd, buf, strlen(buf) + 1);
650
651 Dx(Debug("sending %d args\n", argc));
652 write(sd, "[ARGV]", 7);
653 snprintf(buf, BUF_SIZE, "%d", argc);
654 write(sd, buf, strlen(buf) + 1);
655 for (i = 0; i < argc; i++) {
656 size_t len = strlen(argv[i]) + 1;
657 Dx(Debug("sending argv[%d]: '%s'\n", i, argv[i]));
658 write(sd, argv[i], len);
659 }
660
661 write(sd, "[DONE]", 7);
662
663 Dx(Debug("waiting for OK message from %d\n", sd));
664 if (read(sd, buf, 3) != 3) {
665 perror("pperl: failed to read 3 bytes for an OK message");
666 exit(1);
667 }
668 if (strncmp(buf, "OK\n", 3)) {
669 i = read(sd, buf, BUF_SIZE - 1);
670 buf[i] = '\0';
671 fprintf(stderr, "pperl: expected 'OK\\n', got: '%s'\n", buf);
672 exit(1);
673 }
674 Dx(Debug("got it\n"));
675
676 Dx(Debug("reading return code\n"));
677 i = read(sd, buf, BUF_SIZE - 1);
678 if (i == -1) {
679 perror("Nothing read back from socket!");
680 }
681 buf[i] = '\0';
682 Dx(Debug("socket read '%s'\n", buf));
683
684 for (i = 0; i < max_fd; i++) {
685 close(i);
686 }
687
688 exit (atoi(buf));
689 }
690
691
692
693 /*
694 Local Variables:
695 mode: C
696 c-basic-offset: 4
697 tab-width: 4
698 indent-tabs-mode: nil
699 End:
700 */
701