00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #ifndef _XOPEN_SOURCE
00027 #define _XOPEN_SOURCE 500
00028 #endif
00029 #include <stdlib.h>
00030 #include <stdio.h>
00031 #include <unistd.h>
00032 #include <string.h>
00033 #include <errno.h>
00034 #include <poll.h>
00035 #include <fcntl.h>
00036 #include <sys/time.h>
00037 #include <sys/wait.h>
00038 #include <signal.h>
00039 #include <stdio.h>
00040 #include <sched.h>
00041 #include <libxml/xmlstring.h>
00042
00043 #include "remote_executor.h"
00044 #include "executor.h"
00045 #include "log.h"
00046
00047
00048
00049
00050
00051
00052
00053 extern int bail_out;
00054 extern char *global_failure;
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074 static volatile sig_atomic_t timer_value = 0;
00075 static struct sigaction default_alarm_action = { .sa_handler = NULL };
00076 static testrunner_lite_options *options;
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088 #if 0
00089 static void parse_command_args(const char* command, char* argv[], int max_args);
00090 static void free_args(char* argv[]);
00091 #endif
00092 static pid_t fork_process_redirect(int* stdout_fd, int* stderr_fd,
00093 const char *command);
00094 static pid_t fork_process(const char *command);
00095 static void* stream_data_realloc(stream_data* data, int size);
00096 static void stream_data_free(stream_data* data);
00097 static void stream_data_append(stream_data* data, char* src);
00098 static int read_and_append(int fd, stream_data* data);
00099 static void timer_handler(int signum);
00100 static int set_timer(long secs);
00101 static void reset_timer();
00102 static int execution_terminated(exec_data* data);
00103 static void process_output_streams(int stdout_fd, int stderr_fd,
00104 exec_data* data);
00105 static void communicate(int stdout_fd, int stderr_fd, exec_data* data);
00106 static void strip_ctrl_chars (stream_data* data);
00107 static void utf8_check (stream_data* data, const char *id, pid_t pid);
00108
00109
00110
00111
00112
00113
00114
00115 #if 0
00116
00121 static void parse_command_args(const char* command, char* argv[], int max_args)
00122 {
00123 int n = 0;
00124 int size = strlen(command) + 1;
00125 char* buff = NULL;
00126 char* str = NULL;
00127
00128 buff = (char*)malloc(size);
00129
00130 if (buff != NULL) {
00131
00132
00133
00134 strncpy(buff, command, size);
00135 buff[size - 1] = '\0';
00136
00137
00138 str = strtok(buff, " ");
00139
00140 for ( ;str != NULL && n < max_args; n++) {
00141 argv[n] = (char*)malloc(strlen(str) + 1);
00142 if (argv[n]) {
00143 strcpy(argv[n], str);
00144 }
00145 str = strtok(NULL, " ");
00146 }
00147 }
00148 argv[n] = NULL;
00149
00150 free(buff);
00151 }
00152
00156 static void free_args(char* argv[]) {
00157 while(*argv != NULL) {
00158 free(*argv);
00159 argv++;
00160 }
00161 }
00162 #endif
00163
00167 static int exec_wrapper(const char *command)
00168 {
00169 int ret = 0;
00170
00171 if (options->target_address) {
00172 ret = ssh_execute (options->target_address, command);
00173 }
00174 else
00175
00176 ret = execl(SHELLCMD, SHELLCMD, SHELLCMD_ARGS,
00177 command, (char*)NULL);
00178
00179 return ret;
00180 }
00181
00190 static pid_t fork_process_redirect(int* stdout_fd, int* stderr_fd, const char *command) {
00191 int out_pipe[2];
00192 int err_pipe[2];
00193 pid_t pid;
00194
00195 if (pipe(out_pipe) < 0)
00196 goto error_out;
00197
00198 if (pipe(err_pipe) < 0)
00199 goto error_err;
00200
00201 pid = fork();
00202 if (pid > 0) {
00203 LOG_MSG(LOG_DEBUG, "Forked new process %d", pid);
00204
00205 close(out_pipe[1]);
00206 close(err_pipe[1]);
00207 *stdout_fd = out_pipe[0];
00208 *stderr_fd = err_pipe[0];
00209
00210 } else if (pid == 0) {
00211
00212
00213
00214 setsid();
00215
00216 close(out_pipe[0]);
00217 close(err_pipe[0]);
00218
00219
00220 close(1);
00221 if (dup(out_pipe[1]) < 0) {
00222 perror("dup(out_pipe[1]");
00223 }
00224
00225
00226 close(2);
00227
00228 if (dup(err_pipe[1]) < 0) {
00229 perror("dup(err_pipe[1])");
00230 }
00231
00232 exec_wrapper(command);
00233
00234 exit(1);
00235 } else {
00236 LOG_MSG(LOG_ERR, "Fork failed: %s", strerror(errno));
00237 goto error_fork;
00238 }
00239
00240 return pid;
00241
00242 error_fork:
00243 close(err_pipe[0]);
00244 close(err_pipe[1]);
00245 error_err:
00246 close(out_pipe[0]);
00247 close(out_pipe[1]);
00248 error_out:
00249 return -1;
00250 }
00251
00256 static pid_t fork_process(const char *command) {
00257 pid_t pid = fork();
00258
00259 if (pid > 0) {
00260 LOG_MSG(LOG_DEBUG, "Forked new process %d", pid);
00261 } else if (pid == 0) {
00262
00263
00264
00265 setsid();
00266
00267 exec_wrapper(command);
00268
00269 exit(1);
00270 } else {
00271 LOG_MSG(LOG_ERR, "Fork failed: %s", strerror(errno));
00272 }
00273
00274 return pid;
00275 }
00276
00282 static void* stream_data_realloc(stream_data* data, int size) {
00283 unsigned char* newptr = (unsigned char*)malloc(size);
00284 unsigned char* oldptr = data->buffer;
00285 int length = data->size <= size ? data->size : size;
00286
00287 if (newptr) {
00288 if (data->buffer != NULL) {
00289 memcpy(newptr, oldptr, length);
00290 }
00291 data->buffer = newptr;
00292 data->size = size;
00293 free(oldptr);
00294 } else {
00295 LOG_MSG(LOG_ERR, "Stream data memory allocation failed");
00296 }
00297
00298 return (void*)newptr;
00299 }
00300
00304 static void stream_data_free(stream_data* data) {
00305 free(data->buffer);
00306 data->buffer = NULL;
00307 data->size = 0;
00308 data->length = 0;
00309 }
00310
00315 static void stream_data_append(stream_data* data, char* src) {
00316 int length = strlen(src);
00317
00318 if (data->size - data->length >= length + 1 ||
00319 stream_data_realloc(data, data->length + length + 1) != NULL) {
00320 strcpy((char *)&data->buffer[data->length], src);
00321 data->length += length;
00322 }
00323
00324 }
00325
00332 static int read_and_append(int fd, stream_data* data) {
00333 const int read_size = 255;
00334 int ret = 0;
00335
00336 do {
00337
00338
00339
00340
00341 if (data->size - data->length < read_size + 1) {
00342 if (stream_data_realloc(data, data->size + 1024)
00343 == NULL) {
00344
00345 return -3;
00346 }
00347 }
00348
00349 ret = read(fd, &data->buffer[data->length], read_size);
00350
00351 if (ret > 0) {
00352
00353 data->length += ret;
00354 data->buffer[data->length] = '\0';
00355 }
00356 }
00357 while (ret > 0);
00358
00359 return ret;
00360 }
00361
00365 static void timer_handler(int signum) {
00366 if (signum == SIGALRM) {
00367 timer_value = 1;
00368 }
00369 }
00370
00376 static int set_timer(long secs) {
00377 struct sigaction act;
00378 struct itimerval timer;
00379
00380 act.sa_handler = timer_handler;
00381 sigemptyset(&act.sa_mask);
00382 act.sa_flags = 0;
00383
00384 timer.it_interval.tv_sec = 0;
00385 timer.it_interval.tv_usec = 0;
00386 timer.it_value.tv_sec = secs;
00387 timer.it_value.tv_usec = 0;
00388
00389 timer_value = 0;
00390
00391
00392
00393 if (sigaction(SIGALRM, &act, &default_alarm_action) < 0) {
00394 perror("sigaction");
00395 goto erraction;
00396 }
00397
00398 if (setitimer(ITIMER_REAL, &timer, NULL) < 0) {
00399 perror("setitimer");
00400 goto errtimer;
00401 }
00402
00403 LOG_MSG(LOG_DEBUG, "Set timeout timer to %lu seconds", secs);
00404
00405 return 0;
00406
00407 errtimer:
00408 if (sigaction(SIGALRM, &default_alarm_action, NULL) < 0) {
00409 perror("sigaction");
00410 }
00411 erraction:
00412 return -1;
00413 }
00414
00417 static void reset_timer() {
00418 struct itimerval timer;
00419
00420 timer.it_interval.tv_sec = 0;
00421 timer.it_interval.tv_usec = 0;
00422 timer.it_value.tv_sec = 0;
00423 timer.it_value.tv_usec = 0;
00424
00425 if (setitimer(ITIMER_REAL, &timer, NULL) < 0) {
00426 perror("setitimer");
00427 }
00428
00429
00430 if (default_alarm_action.sa_handler != NULL &&
00431 sigaction(SIGALRM, &default_alarm_action, NULL) < 0) {
00432 perror("sigaction");
00433 }
00434
00435 timer_value = 0;
00436
00437 LOG_MSG(LOG_DEBUG, "Reset timeout timer");
00438 }
00439
00445 static int execution_terminated(exec_data* data) {
00446 pid_t pid = 0;
00447 pid_t pgroup = getpgid(data->pid);
00448 int ret = 0;
00449 int status = 0;
00450 char fail_str [100];
00451
00452 pid = waitpid(-pgroup, &status, WNOHANG);
00453
00454 switch (pid) {
00455 case -1:
00456 if (errno == ECHILD) {
00457
00458 LOG_MSG(LOG_DEBUG, "waitpid reported no more children");
00459 ret = 1;
00460 } else {
00461 LOG_MSG(LOG_ERR, "waitpid: %s", strerror (errno));
00462 }
00463
00464 break;
00465 case 0:
00466
00467 break;
00468 default:
00469
00470
00471 if (pid != data->pid) {
00472 break;
00473 }
00474
00475 if (WIFEXITED(status)) {
00476
00477 data->result = WEXITSTATUS(status);
00478 if (options->target_address) {
00479 if (data->result== 255 ||
00480 (data->result > 64 && data->result < 80) ||
00481 (data->result > 128 && data->result < 144))
00482 {
00483
00484
00485 if (ssh_check_conn (options->
00486 target_address)) {
00487 bail_out = data->result;
00488 global_failure = "connection "
00489 "fail";
00490 LOG_MSG(LOG_ERR,
00491 "ssh connection "
00492 "failure");
00493
00494 }
00495 }
00496 }
00497 LOG_MSG(LOG_DEBUG,
00498 "Process %d exited with status %d",
00499 pid, WEXITSTATUS(status));
00500 } else if (WIFSIGNALED(status)) {
00501
00502 data->result = WTERMSIG(status);
00503 sprintf (fail_str, " terminated by signal %d ",
00504 WTERMSIG(status));
00505 stream_data_append(&data->failure_info,
00506 fail_str);
00507 LOG_MSG(LOG_DEBUG,
00508 "Process %d was terminated by signal %d",
00509 pid, WTERMSIG(status));
00510
00511
00512
00513 if (options->target_address &&
00514 (ret = ssh_check_conn (options->target_address))) {
00515 LOG_MSG(LOG_ERR, "ssh connection failure "
00516 "(%d)", ret);
00517
00518 bail_out = ret;
00519 global_failure = "connection fail";
00520 }
00521
00522 } else {
00523 data->result = -1;
00524 LOG_MSG(LOG_ERR,
00525 "Unexpected return status %d from process %d",
00526 status, pid);
00527 }
00528 break;
00529 }
00530
00531 return ret;
00532 }
00533
00539 static void process_output_streams(int stdout_fd, int stderr_fd,
00540 exec_data* data)
00541 {
00542 struct pollfd fds[2];
00543 int i = 0;
00544
00545 fds[0].fd = stdout_fd;
00546 fds[0].events = POLLIN;
00547 fds[1].fd = stderr_fd;
00548 fds[1].events = POLLIN;
00549
00550 switch(poll(fds, 2, POLL_TIMEOUT_MS)) {
00551 case 0:
00552
00553 break;
00554 case -1:
00555
00556 break;
00557 default:
00558 for(i = 0; i < 2; i++) {
00559 if (fds[i].revents & POLLIN) {
00560 if (fds[i].fd == stdout_fd) {
00561 LOG_MSG(LOG_DEBUG,
00562 "Reading stdout of process %d",
00563 data->pid);
00564 read_and_append(stdout_fd,
00565 &data->stdout_data);
00566 } else if (fds[i].fd == stderr_fd) {
00567 LOG_MSG(LOG_DEBUG,
00568 "Reading stderr of process %d",
00569 data->pid);
00570 read_and_append(stderr_fd,
00571 &data->stderr_data);
00572 }
00573 }
00574 }
00575 break;
00576 }
00577
00578 }
00579
00585 static void communicate(int stdout_fd, int stderr_fd, exec_data* data) {
00586 pid_t pgroup = 0;
00587 int terminated = 0;
00588 int killed = 0;
00589 int ready = 0;
00590
00591 LOG_MSG(LOG_DEBUG, "Communicating with process %d", data->pid);
00592
00593 if (data->redirect_output == REDIRECT_OUTPUT) {
00594
00595 fcntl(stdout_fd, F_SETFL, O_NONBLOCK);
00596 fcntl(stderr_fd, F_SETFL, O_NONBLOCK);
00597 }
00598
00599 set_timer(data->soft_timeout);
00600
00601 while (!ready) {
00602 ready = execution_terminated(data);
00603
00604 if (data->redirect_output == REDIRECT_OUTPUT) {
00605
00606 process_output_streams(stdout_fd, stderr_fd, data);
00607 } else {
00608 usleep(POLL_TIMEOUT_US);
00609 }
00610
00611 if (timer_value && !terminated) {
00612
00613 LOG_MSG(LOG_DEBUG, "Timeout, terminating process %d",
00614 data->pid);
00615
00616 pgroup = getpgid(data->pid);
00617 kill_pgroup(pgroup, SIGTERM);
00618
00619 if (options->target_address && !bail_out) {
00620 ssh_kill (options->target_address, data->pid);
00621 }
00622 stream_data_append(&data->failure_info,
00623 FAILURE_INFO_TIMEOUT);
00624
00625 terminated = 1;
00626 reset_timer();
00627 set_timer(data->hard_timeout);
00628 } else if (timer_value && !killed) {
00629
00630 LOG_MSG(LOG_DEBUG, "Timeout, killing process %d",
00631 data->pid);
00632
00633 pgroup = getpgid(data->pid);
00634 kill_pgroup(pgroup, SIGKILL);
00635
00636 if (options->target_address && !bail_out) {
00637 ssh_kill (options->target_address, data->pid);
00638 }
00639
00640 killed = 1;
00641 }
00642 }
00643
00644
00645
00646 if (pgroup > 0) {
00647 kill_pgroup(pgroup, SIGKILL);
00648 }
00649
00650 reset_timer();
00651
00652 if (options->target_address && !terminated && !killed && !bail_out) {
00653
00654
00655 ;
00656 }
00657 if (data->redirect_output == REDIRECT_OUTPUT) {
00658 close(stdout_fd);
00659 close(stderr_fd);
00660 }
00661 }
00662
00663
00667 static void strip_ctrl_chars (stream_data* data)
00668 {
00669 size_t clean_len = 0, tmp;
00670 char *p = (char *)data->buffer, *endp;
00671
00672
00673 const char rej[] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
00674 0x0B,
00675 0x0C,
00676 0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
00677 0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
00678 0x7F, '\0'};
00679
00680
00681 do {
00682 tmp = strcspn(p, rej);
00683 clean_len += tmp;
00684 p += tmp;
00685 if (clean_len < data->length)
00686 *p = ' ';
00687 } while (clean_len < data->length);
00688
00689
00690 endp = (char *)&data->buffer [data->length];
00691 do {
00692 p = rawmemchr(data->buffer, '\0');
00693
00694 if (p && p != endp)
00695 *p = ' ';
00696 } while (p != endp);
00697
00698 }
00699
00700
00706 static void utf8_check (stream_data* data, const char *id, pid_t pid)
00707 {
00708 char *fname = NULL;
00709 FILE *ofile = NULL;
00710 size_t written = 0;
00711
00712 if (xmlCheckUTF8 (data->buffer)) {
00713 return;
00714 }
00715
00716 fname = (char *)malloc (strlen (options->output_folder) +
00717 strlen ("id") + 10 + 1 + 1);
00718 if (!fname) {
00719 LOG_MSG(LOG_ERR, "OOM");
00720 goto error;
00721 }
00722 sprintf (fname, "%s/%s.%d", options->output_folder,
00723 id, pid);
00724
00725 ofile = fopen (fname, "w+");
00726 if (!ofile) {
00727 LOG_MSG (LOG_ERR, "%s:%s:failed to open file %s %s\n",
00728 PROGNAME, __FUNCTION__, fname,
00729 strerror(errno));
00730 goto error;
00731 }
00732 written = fwrite (data->buffer, 1, data->length, ofile);
00733 if (written != data->length) {
00734 LOG_MSG(LOG_WARNING, "failed to write full stdout to"
00735 "%s\n", fname, strerror(errno));
00736 }
00737 data->buffer = (unsigned char *)realloc (data->buffer,
00738 strlen("non utf-8 output "
00739 "detected - see file ")
00740 + strlen (fname) + 1);
00741 if (!data->buffer) {
00742 LOG_MSG(LOG_ERR, "OOM");
00743 goto error;
00744 }
00745
00746 sprintf ((char *)data->buffer,
00747 "non utf-8 output detected - see file %s.%d", id, pid);
00748
00749 LOG_MSG (LOG_DEBUG, "non utf-8 ouput from test step - wrote to "
00750 "%s instead of results xml", fname);
00751
00752 fclose (ofile);
00753 free (fname);
00754 return;
00755 error:
00756 if (ofile) fclose (ofile);
00757 if (fname) free (fname);
00758
00759 memset (data->buffer, 'a', data->length - 1);
00760 return;
00761 }
00762
00763
00764
00765
00766
00772 int execute(const char* command, exec_data* data) {
00773 int stdout_fd = -1;
00774 int stderr_fd = -1;
00775 pid_t ppgid;
00776
00777 data->start_time = time(NULL);
00778
00779 if (command != NULL) {
00780 LOG_MSG(LOG_DEBUG, "Executing command \'%s\'", command);
00781 }
00782
00783 if (data->redirect_output == REDIRECT_OUTPUT) {
00784 data->pid = fork_process_redirect(&stdout_fd,
00785 &stderr_fd,
00786 command);
00787 } else {
00788 data->pid = fork_process(command);
00789 }
00790
00791 if (data->pid > 0) {
00792
00793 ppgid = getpgid (0);
00794 if (ppgid == -1)
00795 LOG_MSG (LOG_ERR, "getpgid() failed %s",
00796 strerror (errno));
00797 else
00798 while (ppgid == getpgid(data->pid)) sched_yield();
00799
00800 data->pgid = getpgid(data->pid);
00801 LOG_MSG(LOG_DEBUG, "Process %d got process group %d",
00802 data->pid, data->pgid);
00803 communicate(stdout_fd, stderr_fd, data);
00804 }
00805
00806 data->end_time = time(NULL);
00807 if (data->stdout_data.length) strip_ctrl_chars (&data->stdout_data);
00808 if (data->stderr_data.length) strip_ctrl_chars (&data->stderr_data);
00809 if (data->stdout_data.length) utf8_check (&data->stdout_data, "stdout",
00810 data->pid);
00811 if (data->stderr_data.length) utf8_check (&data->stderr_data, "stderr",
00812 data->pid);
00813
00814
00815 return 0;
00816 }
00817
00821 void init_exec_data(exec_data* data) {
00822
00823 data->redirect_output = REDIRECT_OUTPUT;
00824 data->soft_timeout = COMMON_SOFT_TIMEOUT;
00825 data->hard_timeout = COMMON_HARD_TIMEOUT;
00826 data->pid = 0;
00827 data->pgid = 0;
00828 init_stream_data(&data->stdout_data, 1024);
00829 init_stream_data(&data->stderr_data, 1024);
00830 init_stream_data(&data->failure_info, 0);
00831 }
00832
00833 void clean_exec_data(exec_data* data) {
00834 clean_stream_data(&data->stdout_data);
00835 clean_stream_data(&data->stderr_data);
00836 clean_stream_data(&data->failure_info);
00837 }
00838
00843 void init_stream_data(stream_data* data, int allocate) {
00844 data->buffer = NULL;
00845 data->size = 0;
00846 data->length = 0;
00847
00848
00849 if (allocate && stream_data_realloc(data, allocate)) {
00850
00851 data->buffer[0] = '\0';
00852 }
00853 }
00854
00858 void clean_stream_data(stream_data* data) {
00859 stream_data_free(data);
00860 }
00861
00866 void kill_pgroup(int pgroup, int sig) {
00867 if (pgroup <= 1) {
00868 LOG_MSG(LOG_ERR, "Invalid pgid %d", pgroup);
00869 return;
00870 }
00871
00872
00873 if (pgroup == getpgid(0)) {
00874 LOG_MSG(LOG_ERR, "Pgid equals the pgid of testrunner-lite");
00875 return;
00876 }
00877
00878 LOG_MSG(LOG_DEBUG, "Sending signal %d to process group %d",
00879 sig, pgroup);
00880
00881 if (killpg(pgroup, sig) < 0 && errno != ESRCH) {
00882 LOG_MSG(LOG_ERR, "killpg failed: %s", strerror(errno));
00883 }
00884 }
00885
00889 void executor_init (testrunner_lite_options *opts)
00890 {
00891
00892 options = opts;
00893 if (options->target_address)
00894 ssh_executor_init(options->target_address);
00895 }
00896
00899 void executor_close ()
00900 {
00901
00902 if (options->target_address && !bail_out)
00903 ssh_executor_close(options->target_address);
00904 }
00905
00906
00907
00908
00909
00910
00911