1 /* 2 * The WebDraw Project is released without copyright for use as public domain. 3 */ 4 5 #ifndef _WIN32 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include <sys/wait.h> 10 #include <signal.h> 11 #else 12 #define __USE_W32_SOCKETS 1 13 #endif 14 #include <sys/types.h> 15 #include <sys/stat.h> 16 #include <stdlib.h> 17 #include <unistd.h> 18 #include <string.h> 19 #include <stdio.h> 20 #include <fcntl.h> 21 #include <time.h> 22 23 /* GD */ 24 #include <gd.h> 25 #include <gdfonts.h> 26 27 /* Pthreads, or on win32, emulated pthreads */ 28 #include "win32-pthread-emul.h" 29 #ifdef _WIN32 30 #include <ws2tcpip.h> 31 #endif 32 33 /* Win32 requires O_BINARY as an option to open(), but noone else even has it defined */ 34 #ifndef O_BINARY 35 #define O_BINARY 0 36 #endif 37 38 struct session_info_st; 39 struct session_info_st { 40 uint32_t sessionid; 41 gdImagePtr imgptr; 42 pthread_mutex_t imgptr_mut; 43 uint16_t lastx; 44 uint16_t lasty; 45 uint32_t lastcnt; 46 int img_color_black; 47 time_t last_used_time; 48 struct session_info_st *_next; 49 }; 50 struct session_info_st *session_list = NULL; 51 pthread_mutex_t session_list_mut; 52 53 struct image_info_st { 54 void *imgbuf; 55 int32_t imgbuflen; 56 }; 57 58 typedef enum { 59 WEBDRAW_EVENT_MOVE, 60 WEBDRAW_EVENT_CLICK, 61 } webdraw_event_t; 62 63 static struct session_info_st *find_session_info(uint32_t sessionid, int createIfNotExisting) { 64 struct session_info_st *ret = NULL, *chk_session_list; 65 66 /* search for an existing session */ 67 pthread_mutex_lock(&session_list_mut); 68 69 for (chk_session_list = session_list; chk_session_list; chk_session_list = chk_session_list->_next) { 70 if (chk_session_list->sessionid == sessionid) { 71 ret = chk_session_list; 72 break; 73 } 74 } 75 76 /* create it and initialize it if needed */ 77 if (!ret && createIfNotExisting) { 78 ret = malloc(sizeof(*ret)); 79 if (ret) { 80 ret->sessionid = sessionid; 81 ret->imgptr = NULL; 82 ret->last_used_time = time(NULL); 83 ret->lastx = 65535; 84 ret->lasty = 65535; 85 ret->lastcnt = 0; 86 pthread_mutex_init(&ret->imgptr_mut, NULL); 87 88 ret->_next = session_list; 89 session_list = ret; 90 } 91 } 92 93 pthread_mutex_unlock(&session_list_mut); 94 95 return(ret); 96 } 97 98 static void cleanup_sessions(int session_age_limit) { 99 struct session_info_st *chk_session_list, *next, *prev = NULL; 100 time_t expire_time; 101 102 expire_time = time(NULL) - session_age_limit; 103 104 pthread_mutex_lock(&session_list_mut); 105 106 for (chk_session_list = session_list; chk_session_list; chk_session_list = next) { 107 next = chk_session_list->_next; 108 109 if (chk_session_list->last_used_time < expire_time) { 110 if (chk_session_list->imgptr) { 111 gdImageDestroy(chk_session_list->imgptr); 112 } 113 pthread_mutex_destroy(&chk_session_list->imgptr_mut); 114 115 if (session_list == chk_session_list) { 116 session_list = next; 117 } 118 119 if (prev) { 120 prev->_next = next; 121 } 122 123 free(chk_session_list); 124 } else { 125 prev = chk_session_list; 126 } 127 } 128 129 pthread_mutex_unlock(&session_list_mut); 130 131 return; 132 } 133 134 static struct image_info_st *get_image(uint32_t sessionid) { 135 struct image_info_st *ret; 136 struct session_info_st *curr_sess = NULL; 137 138 curr_sess = find_session_info(sessionid, 0); 139 140 if (!curr_sess) { 141 return(NULL); 142 } 143 144 ret = malloc(sizeof(*ret)); 145 if (!ret) { 146 return(NULL); 147 } 148 149 pthread_mutex_lock(&curr_sess->imgptr_mut); 150 ret->imgbuf = gdImagePngPtr(curr_sess->imgptr, &ret->imgbuflen); 151 pthread_mutex_unlock(&curr_sess->imgptr_mut); 152 153 return(ret); 154 } 155 156 static struct image_info_st *get_image_str(const char *sessionid_str) { 157 uint32_t sessionid; 158 159 sessionid = strtoul(sessionid_str, NULL, 10); 160 161 return(get_image(sessionid)); 162 } 163 164 static int handle_event(uint32_t sessionid, uint16_t x, uint16_t y, uint32_t counter, webdraw_event_t type) { 165 struct session_info_st *curr_sess = NULL; 166 FILE *pngfp; 167 168 curr_sess = find_session_info(sessionid, 1); 169 170 if (!curr_sess) { 171 return(-1); 172 } 173 174 if (type == WEBDRAW_EVENT_MOVE && counter != 0) { 175 if (counter < curr_sess->lastcnt) { 176 #ifndef NDEBUG 177 printf("Ignoring out of order move request (sess=%lu, cnt=%lu, lastcnt=%lu)\n", (unsigned long) sessionid, (unsigned long) counter, (unsigned long) curr_sess->lastcnt); 178 #endif 179 return(-1); 180 } 181 182 curr_sess->lastcnt = counter; 183 } 184 185 /* Update session with new data */ 186 pthread_mutex_lock(&curr_sess->imgptr_mut); 187 if (!curr_sess->imgptr) { 188 pngfp = fopen("blank.png", "rb"); 189 if (pngfp) { 190 curr_sess->imgptr = gdImageCreateFromPng(pngfp); 191 fclose(pngfp); 192 193 if (curr_sess->imgptr) { 194 curr_sess->img_color_black = gdImageColorAllocate(curr_sess->imgptr, 0, 0, 0); 195 gdImageSetAntiAliased(curr_sess->imgptr, curr_sess->img_color_black); 196 } 197 } 198 } 199 200 if (curr_sess->imgptr && curr_sess->lastx != 65535 && curr_sess->lasty != 65535) { 201 /* Update image */ 202 gdImageLine(curr_sess->imgptr, curr_sess->lastx, curr_sess->lasty, x, y, gdAntiAliased); 203 } 204 205 if (curr_sess->imgptr && type == WEBDRAW_EVENT_CLICK) { 206 gdImageFilledArc(curr_sess->imgptr, x, y, 6, 6, 0, 360, gdAntiAliased, gdArc); 207 } 208 209 curr_sess->lastx = x; 210 curr_sess->lasty = y; 211 pthread_mutex_unlock(&curr_sess->imgptr_mut); 212 213 /* Update session information */ 214 curr_sess->last_used_time = time(NULL); 215 216 return(0); 217 } 218 219 static int handle_event_str(char *str, webdraw_event_t type) { 220 uint32_t sessionid, counter; 221 uint16_t x, y; 222 char *sessionid_str, *x_str, *y_str, *counter_str; 223 224 sessionid_str = str; 225 x_str = strchr(sessionid_str, ','); 226 if (!x_str) { 227 return(-1); 228 } 229 *x_str = '\0'; 230 x_str++; 231 232 y_str = strchr(x_str, ','); 233 if (!y_str) { 234 return(-1); 235 } 236 *y_str = '\0'; 237 y_str++; 238 239 counter_str = strchr(y_str, ','); 240 if (counter_str) { 241 *counter_str = '\0'; 242 counter_str++; 243 counter = strtoul(counter_str, NULL, 10); 244 } else { 245 counter = 0; 246 } 247 248 sessionid = strtoul(sessionid_str, NULL, 10); 249 x = strtoul(x_str, NULL, 10); 250 y = strtoul(y_str, NULL, 10); 251 252 return(handle_event(sessionid, x, y, counter, type)); 253 } 254 255 /* Not HTTP/1.0 compliant, yet */ 256 THREAD_FUNCTION_RETURN handle_connection(void *arg) { 257 ssize_t bytes_recv; 258 size_t buflen, bufused; 259 char buf[16384], *buf_p, *request_end_p = NULL; 260 char *request_line, *request_line_end_p, *request_line_operation, *request_line_resource, *request_line_protocol; 261 char *curr_header_p, *next_header_p, *curr_header_var, *curr_header_val; 262 int fd, *fd_p; 263 int abort = 0, close_conn, invalid_request = 0; 264 265 struct image_info_st *imginfo = NULL; 266 struct stat fileinfo; 267 ssize_t sent_bytes, read_bytes; 268 size_t bytes_to_send, bytes_to_read; 269 char reply_buf[1024], *reply_buf_p, copy_buf[8192]; 270 char *http_reply_msg, *http_reply_body, *http_reply_body_file, *http_reply_content_type; 271 int http_reply_code, http_reply_content_length = 0; 272 int reply_buf_len; 273 int stat_ret, event_ret; 274 int srcfd; 275 276 /* Determine our args original values */ 277 fd_p = arg; 278 fd = *fd_p; 279 280 /* Pre-use initialization */ 281 buf_p = buf; 282 bufused = 0; 283 *buf_p = '\0'; 284 while (1) { 285 close_conn = 1; 286 imginfo = NULL; 287 while (1) { 288 buflen = sizeof(buf) - (buf_p - buf) - bufused - 1; 289 if (buflen == 0) { 290 /* We ran out of buffer space on input, oops. */ 291 abort = 1; 292 break; 293 } 294 295 buf_p[bufused] = '\0'; 296 request_end_p = strstr(buf_p, "\015\012\015\012"); 297 if (bufused == 0 || !request_end_p) { 298 bytes_recv = recv(fd, buf_p + bufused, buflen, 0); 299 if (bytes_recv <= 0) { 300 abort = 1; 301 break; 302 } 303 304 bufused += bytes_recv; 305 buf_p[bufused] = '\0'; 306 } 307 308 request_end_p = strstr(buf_p, "\015\012\015\012"); 309 if (!request_end_p) { 310 continue; 311 } 312 313 /* We do not handle POST requests */ 314 break; 315 } 316 317 if (abort) { 318 break; 319 } 320 321 if (!request_end_p) { 322 break; 323 } 324 325 /* Parse HTTP request */ 326 request_line_end_p = strstr(buf_p, "\015\012"); 327 *request_line_end_p = '\0'; 328 request_line = buf_p; 329 330 request_line_operation = request_line; 331 request_line_resource = strchr(request_line_operation, ' '); 332 if (!request_line_resource) { 333 break; 334 } 335 *request_line_resource = '\0'; 336 request_line_resource++; 337 338 request_line_protocol = strchr(request_line_resource, ' '); 339 if (!request_line_protocol) { 340 request_line_protocol = "HTTP/1.0"; 341 } else { 342 *request_line_protocol = '\0'; 343 request_line_protocol++; 344 } 345 346 if (strcasecmp(request_line_operation, "GET") != 0) { 347 /* We only support GET */ 348 invalid_request = 1; 349 break; 350 } 351 352 if (strcasecmp(request_line_protocol, "HTTP/1.1") == 0) { 353 close_conn = 0; 354 } 355 356 curr_header_p = request_line_end_p + 2; 357 while (1) { 358 if (curr_header_p == (request_end_p + 2)) { 359 break; 360 } 361 362 next_header_p = strstr(curr_header_p, "\015\012"); 363 if (next_header_p) { 364 365 *next_header_p = '\0'; 366 } 367 368 /* Parse header into variable and value components, and trim leading spaces */ 369 curr_header_var = curr_header_p; 370 curr_header_val = strchr(curr_header_p, ':'); 371 if (!curr_header_val) { 372 /* Malformed header */ 373 abort = 1; 374 break; 375 } 376 *curr_header_val = '\0'; 377 curr_header_val++; 378 while (*curr_header_val && (*curr_header_val == ' ' || *curr_header_val == '\t')) { 379 curr_header_val++; 380 } 381 382 /* Handle HTTP headers as appropriate */ 383 if (strcasecmp(curr_header_var, "connection") == 0) { 384 if (strcasecmp(curr_header_val, "close") == 0) { 385 close_conn = 1; 386 } 387 if (strcasecmp(curr_header_val, "keep-alive") == 0) { 388 close_conn = 0; 389 } 390 } 391 392 /* Iterate */ 393 if (!next_header_p) { 394 break; 395 } 396 curr_header_p = next_header_p + 2; 397 } 398 399 if (abort) { 400 break; 401 } 402 403 /* Process request */ 404 #ifndef NDEBUG 405 { 406 int gpn_ret; 407 struct sockaddr_in peerinfo; 408 socklen_t peerinfolen = sizeof(peerinfo); 409 410 gpn_ret = getpeername(fd, (struct sockaddr *) &peerinfo, &peerinfolen); 411 412 if (gpn_ret == 0) { 413 printf("[%llx] [%s] GET %s\n", (unsigned long long) pthread_self(), inet_ntoa(peerinfo.sin_addr), request_line_resource); 414 } else { 415 printf("[%llx] [??.??.??.??] GET %s\n", (unsigned long long) pthread_self(), request_line_resource); 416 } 417 } 418 #endif 419 if (strncmp(request_line_resource, "/event/", 7) == 0) { 420 /* Process an event */ 421 if (strncmp(request_line_resource + 7, "move?", 5) == 0) { 422 event_ret = handle_event_str(request_line_resource + 12, WEBDRAW_EVENT_MOVE); 423 } else if (strncmp(request_line_resource + 7, "click?", 6) == 0) { 424 event_ret = handle_event_str(request_line_resource + 13, WEBDRAW_EVENT_CLICK); 425 } else { 426 event_ret = -1; 427 } 428 429 /* Reply to event */ 430 if (event_ret == 0) { 431 http_reply_code = 200; 432 http_reply_msg = "OK"; 433 http_reply_content_type = "text/plain"; 434 http_reply_body_file = NULL; 435 436 /* Body should be a valid JavaScript command, it will be eval()'d */ 437 http_reply_body = ""; 438 http_reply_content_length = strlen(http_reply_body); 439 } else { 440 http_reply_code = 500; 441 http_reply_msg = "Event Error"; 442 http_reply_content_type = "text/plain"; 443 http_reply_body = "Event Error"; 444 http_reply_body_file = NULL; 445 http_reply_content_length = strlen(http_reply_body); 446 } 447 } else if (strncmp(request_line_resource, "/dynamic/image?", 15) == 0) { 448 /* Return an image */ 449 imginfo = get_image_str(request_line_resource + 15); 450 451 if (imginfo) { 452 http_reply_code = 200; 453 http_reply_msg = "OK"; 454 http_reply_content_type = "image/png"; 455 http_reply_body = imginfo->imgbuf; 456 http_reply_body_file = NULL; 457 http_reply_content_length = imginfo->imgbuflen; 458 } else { 459 http_reply_code = 500; 460 http_reply_msg = "Event Error"; 461 http_reply_content_type = "text/plain"; 462 http_reply_body = "Event Error"; 463 http_reply_body_file = NULL; 464 http_reply_content_length = strlen(http_reply_body); 465 } 466 } else if (strcmp(request_line_resource, "/static/page.html") == 0 || strcmp(request_line_resource, "/") == 0 || strcmp(request_line_resource, "") == 0) { 467 /* Return a file */ 468 http_reply_code = 200; 469 http_reply_msg = "OK"; 470 http_reply_content_type = "text/html"; 471 http_reply_body = NULL; 472 http_reply_body_file = "page.html"; 473 } else if (strcmp(request_line_resource, "/static/page-test.html") == 0) { 474 /* Return a file */ 475 http_reply_code = 200; 476 http_reply_msg = "OK"; 477 http_reply_content_type = "text/html"; 478 http_reply_body = NULL; 479 http_reply_body_file = "page-test.html"; 480 } else if (strcmp(request_line_resource, "/static/blank.png") == 0) { 481 /* Return a file */ 482 http_reply_code = 200; 483 http_reply_msg = "OK"; 484 http_reply_content_type = "image/png"; 485 http_reply_body = NULL; 486 http_reply_body_file = "blank.png"; 487 } else if (strcmp(request_line_resource, "/static/serv.c") == 0) { 488 /* Return a file */ 489 http_reply_code = 200; 490 http_reply_msg = "OK"; 491 http_reply_content_type = "text/plain"; 492 http_reply_body = NULL; 493 http_reply_body_file = "serv.c"; 494 } else { 495 /* Return an error */ 496 http_reply_code = 404; 497 http_reply_msg = "Resource not found"; 498 http_reply_body = "<html><head><title>Resource not found</title></head><body><h1>Resource not found</h1><br>This HTTP server offers very limited resources.</body></html>"; 499 http_reply_body_file = NULL; 500 http_reply_content_type = "text/html"; 501 http_reply_content_length = strlen(http_reply_body); 502 } 503 504 if (http_reply_body == NULL) { 505 if (http_reply_body_file != NULL) { 506 stat_ret = stat(http_reply_body_file, &fileinfo); 507 if (stat_ret != 0) { 508 break; 509 } 510 511 http_reply_content_length = fileinfo.st_size; 512 } else { 513 break; 514 } 515 } else { 516 http_reply_body_file = NULL; 517 } 518 519 /* Form reply and send it */ 520 reply_buf_len = snprintf(reply_buf, sizeof(reply_buf), "%s %i %s\015\012Date: %s\015\012Server: webdraw\015\012Connection: %s\015\012Content-Length: %i\015\012Content-Type: %s\015\012\015\012", request_line_protocol, http_reply_code, http_reply_msg, "Thu, 21 Feb 2008 08:16:03 GMT", (close_conn ? "close" : "keep-alive"), http_reply_content_length, http_reply_content_type); 521 bytes_to_send = reply_buf_len; 522 reply_buf_p = reply_buf; 523 while (bytes_to_send) { 524 sent_bytes = send(fd, reply_buf_p, bytes_to_send, 0); 525 if (sent_bytes <= 0) { 526 abort = 1; 527 break; 528 } 529 530 bytes_to_send -= sent_bytes; 531 reply_buf_p += sent_bytes; 532 } 533 if (abort) { 534 break; 535 } 536 537 /* Send body */ 538 if (http_reply_body != NULL) { 539 /* String */ 540 bytes_to_send = http_reply_content_length; 541 reply_buf_p = http_reply_body; 542 while (bytes_to_send) { 543 sent_bytes = send(fd, reply_buf_p, bytes_to_send, 0); 544 if (sent_bytes <= 0) { 545 abort = 1; 546 break; 547 } 548 549 bytes_to_send -= sent_bytes; 550 reply_buf_p += sent_bytes; 551 } 552 } else { 553 /* File */ 554 srcfd = open(http_reply_body_file, O_RDONLY | O_BINARY); 555 556 bytes_to_send = http_reply_content_length; 557 reply_buf_p = http_reply_body; 558 while (bytes_to_send) { 559 if (bytes_to_send <= sizeof(copy_buf)) { 560 bytes_to_read = bytes_to_send; 561 } else { 562 bytes_to_read = sizeof(copy_buf); 563 } 564 565 read_bytes = read(srcfd, copy_buf, bytes_to_read); 566 if (read_bytes <= 0) { 567 abort = 1; 568 break; 569 } 570 571 sent_bytes = send(fd, copy_buf, read_bytes, 0); 572 if (sent_bytes <= 0) { 573 abort = 1; 574 break; 575 } 576 577 bytes_to_send -= sent_bytes; 578 reply_buf_p += sent_bytes; 579 } 580 581 close(srcfd); 582 } 583 584 if (imginfo) { 585 if (imginfo->imgbuf) { 586 gdFree(imginfo->imgbuf); 587 imginfo->imgbuf = NULL; 588 } 589 free(imginfo); 590 imginfo = NULL; 591 } 592 593 if (abort) { 594 break; 595 } 596 597 /* Close connection if set */ 598 if (close_conn) { 599 break; 600 } 601 602 /* Prepare for the next connection */ 603 /* We support pipelining by ignoring part of the buffer, or resetting it if possible */ 604 bufused -= (request_end_p + 4) - buf_p; 605 if (bufused == 0) { 606 buf_p = buf; 607 } else { 608 buf_p = request_end_p + 4; 609 } 610 } 611 612 if (imginfo) { 613 if (imginfo->imgbuf) { 614 gdFree(imginfo->imgbuf); 615 imginfo->imgbuf = NULL; 616 } 617 free(imginfo); 618 imginfo = NULL; 619 } 620 621 #ifndef NDEBUG 622 printf("[%llx] Thread terminating\n", (unsigned long long) pthread_self()); 623 #endif 624 625 close(fd); 626 free(fd_p); 627 628 pthread_detach(pthread_self()); 629 630 return((THREAD_FUNCTION_RETURN) 0); 631 } 632 633 static void daemonize(void) { 634 #ifndef _WIN32 635 int dummyfd; 636 637 dummyfd = open("/dev/null", O_RDWR); 638 639 dup2(dummyfd, STDIN_FILENO); 640 dup2(dummyfd, STDOUT_FILENO); 641 dup2(dummyfd, STDERR_FILENO); 642 643 /* Parent */ 644 if (fork() != 0) { 645 /* Parent */ 646 while (wait(NULL) == -1) { 647 /* Busy loop */ 648 } 649 } else { 650 /* Child */ 651 if (fork() == 0) { 652 /* Grandchild */ 653 return; 654 } 655 } 656 exit(EXIT_SUCCESS); 657 #endif 658 659 return; 660 } 661 662 int main(int argc, char **argv) { 663 struct sockaddr_in localaddr; 664 pthread_t curr_thread; 665 int masterfd, currfd, *currfd_copy; 666 int bind_ret, listen_ret, pthcreate_ret; 667 uint16_t parm_port = 8013; 668 #ifdef _WIN32 669 /* Win32 Specific Crap */ 670 WSADATA wsaData; 671 672 if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { 673 fprintf(stderr, "WSAStartup() failed. Blame Microsoft.\n"); 674 return(EXIT_FAILURE); 675 } 676 if (wsaData.wVersion != MAKEWORD(2, 0)) { 677 WSACleanup(); 678 fprintf(stderr, "WSAStartup() returned unexpected version. Blame Microsoft.\n"); 679 return(EXIT_FAILURE); 680 } 681 #endif 682 683 /* Initialize session list mutex */ 684 pthread_mutex_init(&session_list_mut, NULL); 685 686 687 /* Setup listening socket on appropriate port */ 688 if (argc == 2) { 689 if (!argv[1]) { 690 fprintf(stderr, "Invalid port specification\n"); 691 return(EXIT_FAILURE); 692 } 693 694 parm_port = atoi(argv[1]); 695 if (parm_port == 0) { 696 fprintf(stderr, "Invalid port specification %s (== %i), must be between 1 and 65535\n", argv[1], parm_port); 697 return(EXIT_FAILURE); 698 } 699 } 700 701 masterfd = socket(PF_INET, SOCK_STREAM, 0); 702 if (masterfd < 0) { 703 perror("socket"); 704 return(EXIT_FAILURE); 705 } 706 707 localaddr.sin_family = AF_INET; 708 localaddr.sin_port = htons(parm_port); 709 localaddr.sin_addr.s_addr = htonl(INADDR_ANY); 710 while (1) { 711 printf("Binding to 0.0.0.0:%i\n", parm_port); 712 bind_ret = bind(masterfd, (struct sockaddr *) &localaddr, sizeof(localaddr)); 713 if (bind_ret != 0) { 714 perror("bind"); 715 #ifndef _WIN32 716 sleep(5); 717 #endif 718 continue; 719 } 720 721 break; 722 } 723 724 listen_ret = listen(masterfd, 5); 725 if (listen_ret != 0) { 726 perror("listen"); 727 return(EXIT_FAILURE); 728 } 729 730 #ifndef _WIN32 731 signal(SIGPIPE, SIG_IGN); 732 #endif 733 734 daemonize(); 735 736 /* Handle incoming connections and create worker threads to handle them */ 737 while (1) { 738 cleanup_sessions(300); 739 740 currfd = accept(masterfd, NULL, NULL); 741 if (currfd < 0) { 742 perror("accept"); 743 return(EXIT_FAILURE); 744 } 745 746 /* Duplicate child FD for worker thread -- it will be freed appropriately */ 747 currfd_copy = malloc(sizeof(*currfd_copy)); 748 if (!currfd_copy) { 749 perror("malloc"); 750 return(EXIT_FAILURE); 751 } 752 *currfd_copy = currfd; 753 754 /* Create worker thread for this request */ 755 pthcreate_ret = pthread_create(&curr_thread, NULL, handle_connection, currfd_copy); 756 if (pthcreate_ret != 0) { 757 fprintf(stderr, "Error creating thread, closing socket and aborting. pthread_create() returned %i\n", pthcreate_ret); 758 free(currfd_copy); 759 close(currfd); 760 break; 761 } 762 } 763 764 return(EXIT_FAILURE); 765 } |