Annotation of embedaddon/miniupnpd/miniupnpc/src/minihttptestserver.c, revision 1.1
1.1 ! misho 1: /* $Id: minihttptestserver.c,v 1.25 2020/05/29 21:14:22 nanard Exp $ */
! 2: /* Project : miniUPnP
! 3: * Author : Thomas Bernard
! 4: * Copyright (c) 2011-2018 Thomas Bernard
! 5: * This software is subject to the conditions detailed in the
! 6: * LICENCE file provided in this distribution.
! 7: * */
! 8: #include <stdio.h>
! 9: #include <stdlib.h>
! 10: #include <string.h>
! 11: #include <unistd.h>
! 12: #include <sys/types.h>
! 13: #include <sys/socket.h>
! 14: #include <sys/wait.h>
! 15: #include <arpa/inet.h>
! 16: #include <netinet/in.h>
! 17: #include <signal.h>
! 18: #include <time.h>
! 19: #include <errno.h>
! 20:
! 21: #ifndef INADDR_LOOPBACK
! 22: #define INADDR_LOOPBACK 0x7f000001
! 23: #endif
! 24:
! 25: #define CRAP_LENGTH (2048)
! 26:
! 27: static int server(unsigned short port, const char * expected_file_name, int ipv6);
! 28:
! 29: volatile sig_atomic_t quit = 0;
! 30: volatile sig_atomic_t child_to_wait_for = 0;
! 31:
! 32: /**
! 33: * signal handler for SIGCHLD (child status has changed)
! 34: */
! 35: void handle_signal_chld(int sig)
! 36: {
! 37: (void)sig;
! 38: /* printf("handle_signal_chld(%d)\n", sig); */
! 39: ++child_to_wait_for;
! 40: }
! 41:
! 42: /**
! 43: * signal handler for SIGINT (CRTL C)
! 44: */
! 45: void handle_signal_int(int sig)
! 46: {
! 47: (void)sig;
! 48: /* printf("handle_signal_int(%d)\n", sig); */
! 49: quit = 1;
! 50: }
! 51:
! 52: /**
! 53: * build a text/plain content of the specified length
! 54: */
! 55: void build_content(char * p, size_t n)
! 56: {
! 57: char line_buffer[80];
! 58: int k;
! 59: int i = 0;
! 60:
! 61: while(n > 0) {
! 62: k = snprintf(line_buffer, sizeof(line_buffer),
! 63: "%04d_ABCDEFGHIJKL_This_line_is_64_bytes_long_ABCDEFGHIJKL_%04d\r\n",
! 64: i, i);
! 65: if(k != 64) {
! 66: fprintf(stderr, "snprintf() returned %d in build_content()\n", k);
! 67: }
! 68: ++i;
! 69: if(n >= 64) {
! 70: memcpy(p, line_buffer, 64);
! 71: p += 64;
! 72: n -= 64;
! 73: } else {
! 74: memcpy(p, line_buffer, n);
! 75: p += n;
! 76: n = 0;
! 77: }
! 78: }
! 79: }
! 80:
! 81: /**
! 82: * build crappy content
! 83: */
! 84: void build_crap(char * p, size_t n)
! 85: {
! 86: static const char crap[] = "_CRAP_\r\n";
! 87: size_t i;
! 88:
! 89: while(n > 0) {
! 90: i = sizeof(crap) - 1;
! 91: if(i > n)
! 92: i = n;
! 93: memcpy(p, crap, i);
! 94: p += i;
! 95: n -= i;
! 96: }
! 97: }
! 98:
! 99: /**
! 100: * build chunked response.
! 101: * return a malloc'ed buffer
! 102: */
! 103: char * build_chunked_response(size_t content_length, size_t * response_len)
! 104: {
! 105: char * response_buffer;
! 106: char * content_buffer;
! 107: size_t buffer_length;
! 108: size_t i;
! 109: unsigned int n;
! 110:
! 111: /* allocate to have some margin */
! 112: buffer_length = 256 + content_length + (content_length >> 4);
! 113: response_buffer = malloc(buffer_length);
! 114: if(response_buffer == NULL)
! 115: return NULL;
! 116: *response_len = snprintf(response_buffer, buffer_length,
! 117: "HTTP/1.1 200 OK\r\n"
! 118: "Content-Type: text/plain\r\n"
! 119: "Transfer-Encoding: chunked\r\n"
! 120: "\r\n");
! 121:
! 122: /* build the content */
! 123: content_buffer = malloc(content_length);
! 124: if(content_buffer == NULL) {
! 125: free(response_buffer);
! 126: return NULL;
! 127: }
! 128: build_content(content_buffer, content_length);
! 129:
! 130: /* chunk it */
! 131: i = 0;
! 132: while(i < content_length) {
! 133: n = (rand() % 199) + 1;
! 134: if(i + n > content_length) {
! 135: n = content_length - i;
! 136: }
! 137: /* TODO : check buffer size ! */
! 138: *response_len += snprintf(response_buffer + *response_len,
! 139: buffer_length - *response_len,
! 140: "%x\r\n", n);
! 141: memcpy(response_buffer + *response_len, content_buffer + i, n);
! 142: *response_len += n;
! 143: i += n;
! 144: response_buffer[(*response_len)++] = '\r';
! 145: response_buffer[(*response_len)++] = '\n';
! 146: }
! 147: /* the last chunk : "0\r\n" a empty body and then
! 148: * the final "\r\n" */
! 149: memcpy(response_buffer + *response_len, "0\r\n\r\n", 5);
! 150: *response_len += 5;
! 151: free(content_buffer);
! 152:
! 153: printf("resp_length=%lu buffer_length=%lu content_length=%lu\n",
! 154: *response_len, buffer_length, content_length);
! 155: return response_buffer;
! 156: }
! 157:
! 158: /* favicon.ico generator */
! 159: #ifdef OLD_HEADER
! 160: #define FAVICON_LENGTH (6 + 16 + 12 + 8 + 32 * 4)
! 161: #else
! 162: #define FAVICON_LENGTH (6 + 16 + 40 + 8 + 32 * 4)
! 163: #endif
! 164: void build_favicon_content(unsigned char * p, size_t n)
! 165: {
! 166: int i;
! 167: if(n < FAVICON_LENGTH)
! 168: return;
! 169: /* header : 6 bytes */
! 170: *p++ = 0;
! 171: *p++ = 0;
! 172: *p++ = 1; /* type : ICO */
! 173: *p++ = 0;
! 174: *p++ = 1; /* number of images in file */
! 175: *p++ = 0;
! 176: /* image directory (1 entry) : 16 bytes */
! 177: *p++ = 16; /* width */
! 178: *p++ = 16; /* height */
! 179: *p++ = 2; /* number of colors in the palette. 0 = no palette */
! 180: *p++ = 0; /* reserved */
! 181: *p++ = 1; /* color planes */
! 182: *p++ = 0; /* " */
! 183: *p++ = 1; /* bpp */
! 184: *p++ = 0; /* " */
! 185: #ifdef OLD_HEADER
! 186: *p++ = 12 + 8 + 32 * 4; /* bmp size */
! 187: #else
! 188: *p++ = 40 + 8 + 32 * 4; /* bmp size */
! 189: #endif
! 190: *p++ = 0; /* " */
! 191: *p++ = 0; /* " */
! 192: *p++ = 0; /* " */
! 193: *p++ = 6 + 16; /* bmp offset */
! 194: *p++ = 0; /* " */
! 195: *p++ = 0; /* " */
! 196: *p++ = 0; /* " */
! 197: /* BMP */
! 198: #ifdef OLD_HEADER
! 199: /* BITMAPCOREHEADER */
! 200: *p++ = 12; /* size of this header */
! 201: *p++ = 0; /* " */
! 202: *p++ = 0; /* " */
! 203: *p++ = 0; /* " */
! 204: *p++ = 16; /* width */
! 205: *p++ = 0; /* " */
! 206: *p++ = 16 * 2; /* height x 2 ! */
! 207: *p++ = 0; /* " */
! 208: *p++ = 1; /* color planes */
! 209: *p++ = 0; /* " */
! 210: *p++ = 1; /* bpp */
! 211: *p++ = 0; /* " */
! 212: #else
! 213: /* BITMAPINFOHEADER */
! 214: *p++ = 40; /* size of this header */
! 215: *p++ = 0; /* " */
! 216: *p++ = 0; /* " */
! 217: *p++ = 0; /* " */
! 218: *p++ = 16; /* width */
! 219: *p++ = 0; /* " */
! 220: *p++ = 0; /* " */
! 221: *p++ = 0; /* " */
! 222: *p++ = 16 * 2; /* height x 2 ! */
! 223: *p++ = 0; /* " */
! 224: *p++ = 0; /* " */
! 225: *p++ = 0; /* " */
! 226: *p++ = 1; /* color planes */
! 227: *p++ = 0; /* " */
! 228: *p++ = 1; /* bpp */
! 229: *p++ = 0; /* " */
! 230: /* compression method, image size, ppm x, ppm y */
! 231: /* colors in the palette ? */
! 232: /* important colors */
! 233: for(i = 4 * 6; i > 0; --i)
! 234: *p++ = 0;
! 235: #endif
! 236: /* palette */
! 237: *p++ = 0; /* b */
! 238: *p++ = 0; /* g */
! 239: *p++ = 0; /* r */
! 240: *p++ = 0; /* reserved */
! 241: *p++ = 255; /* b */
! 242: *p++ = 255; /* g */
! 243: *p++ = 255; /* r */
! 244: *p++ = 0; /* reserved */
! 245: /* pixel data */
! 246: for(i = 16; i > 0; --i) {
! 247: if(i & 1) {
! 248: *p++ = 0125;
! 249: *p++ = 0125;
! 250: } else {
! 251: *p++ = 0252;
! 252: *p++ = 0252;
! 253: }
! 254: *p++ = 0;
! 255: *p++ = 0;
! 256: }
! 257: /* Opacity MASK */
! 258: for(i = 16 * 4; i > 0; --i) {
! 259: *p++ = 0;
! 260: }
! 261: }
! 262:
! 263: enum modes {
! 264: MODE_INVALID, MODE_CHUNKED, MODE_ADDCRAP, MODE_NORMAL, MODE_FAVICON, MODE_MALFORMED
! 265: };
! 266:
! 267: const struct {
! 268: const enum modes mode;
! 269: const char * text;
! 270: } modes_array[] = {
! 271: {MODE_CHUNKED, "chunked"},
! 272: {MODE_ADDCRAP, "addcrap"},
! 273: {MODE_NORMAL, "normal"},
! 274: {MODE_FAVICON, "favicon.ico"},
! 275: {MODE_MALFORMED, "malformed"},
! 276: {MODE_INVALID, NULL}
! 277: };
! 278:
! 279: /**
! 280: * write the response with random behaviour !
! 281: */
! 282: void send_response(int c, const char * buffer, size_t len)
! 283: {
! 284: ssize_t n;
! 285: while(len > 0) {
! 286: n = (rand() % 99) + 1;
! 287: if((size_t)n > len)
! 288: n = len;
! 289: n = write(c, buffer, n);
! 290: if(n < 0) {
! 291: if(errno != EINTR) {
! 292: perror("write");
! 293: return;
! 294: }
! 295: /* if errno == EINTR, try again */
! 296: } else {
! 297: len -= n;
! 298: buffer += n;
! 299: usleep(10000); /* 10ms */
! 300: }
! 301: }
! 302: }
! 303:
! 304: /**
! 305: * handle the HTTP connection
! 306: */
! 307: void handle_http_connection(int c)
! 308: {
! 309: char request_buffer[2048];
! 310: size_t request_len = 0;
! 311: int headers_found = 0;
! 312: ssize_t n, m;
! 313: size_t i;
! 314: char request_method[16];
! 315: char request_uri[256];
! 316: char http_version[16];
! 317: char * p;
! 318: char * response_buffer;
! 319: size_t response_len;
! 320: enum modes mode;
! 321: size_t content_length = 16*1024;
! 322:
! 323: /* read the request */
! 324: while(request_len < sizeof(request_buffer) && !headers_found) {
! 325: n = read(c,
! 326: request_buffer + request_len,
! 327: sizeof(request_buffer) - request_len);
! 328: if(n < 0) {
! 329: if(errno == EINTR)
! 330: continue;
! 331: perror("read");
! 332: return;
! 333: } else if(n==0) {
! 334: /* remote host closed the connection */
! 335: break;
! 336: } else {
! 337: request_len += n;
! 338: for(i = 0; i < request_len - 3; i++) {
! 339: if(0 == memcmp(request_buffer + i, "\r\n\r\n", 4)) {
! 340: /* found the end of headers */
! 341: headers_found = 1;
! 342: break;
! 343: }
! 344: }
! 345: }
! 346: }
! 347: if(!headers_found) {
! 348: /* error */
! 349: printf("no HTTP header found in the request\n");
! 350: return;
! 351: }
! 352: printf("headers :\n%.*s", (int)request_len, request_buffer);
! 353: /* the request have been received, now parse the request line */
! 354: p = request_buffer;
! 355: for(i = 0; i < sizeof(request_method) - 1; i++) {
! 356: if(*p == ' ' || *p == '\r')
! 357: break;
! 358: request_method[i] = *p;
! 359: ++p;
! 360: }
! 361: request_method[i] = '\0';
! 362: while(*p == ' ')
! 363: p++;
! 364: for(i = 0; i < (int)sizeof(request_uri) - 1; i++) {
! 365: if(*p == ' ' || *p == '\r')
! 366: break;
! 367: request_uri[i] = *p;
! 368: ++p;
! 369: }
! 370: request_uri[i] = '\0';
! 371: while(*p == ' ')
! 372: p++;
! 373: for(i = 0; i < (int)sizeof(http_version) - 1; i++) {
! 374: if(*p == ' ' || *p == '\r')
! 375: break;
! 376: http_version[i] = *p;
! 377: ++p;
! 378: }
! 379: http_version[i] = '\0';
! 380: printf("Method = %s, URI = %s, %s\n",
! 381: request_method, request_uri, http_version);
! 382: /* check if the request method is allowed */
! 383: if(0 != strcmp(request_method, "GET")) {
! 384: const char response405[] = "HTTP/1.1 405 Method Not Allowed\r\n"
! 385: "Allow: GET\r\n\r\n";
! 386: const char * pc;
! 387: /* 405 Method Not Allowed */
! 388: /* The response MUST include an Allow header containing a list
! 389: * of valid methods for the requested resource. */
! 390: n = sizeof(response405) - 1;
! 391: pc = response405;
! 392: while(n > 0) {
! 393: m = write(c, pc, n);
! 394: if(m<0) {
! 395: if(errno != EINTR) {
! 396: perror("write");
! 397: return;
! 398: }
! 399: } else {
! 400: n -= m;
! 401: pc += m;
! 402: }
! 403: }
! 404: return;
! 405: }
! 406:
! 407: mode = MODE_INVALID;
! 408: /* use the request URI to know what to do */
! 409: for(i = 0; modes_array[i].mode != MODE_INVALID; i++) {
! 410: if(strstr(request_uri, modes_array[i].text)) {
! 411: mode = modes_array[i].mode; /* found */
! 412: break;
! 413: }
! 414: }
! 415:
! 416: switch(mode) {
! 417: case MODE_MALFORMED:
! 418: response_len = 2048;
! 419: response_buffer = malloc(response_len);
! 420: if(!response_buffer)
! 421: break;
! 422: n = snprintf(response_buffer, response_len,
! 423: "HTTP/1.1 \r\n"
! 424: "\r\n"
! 425: /*"0000\r\n"*/);
! 426: for (i = n; i < response_len; i++) {
! 427: response_buffer[i] = ' ';
! 428: }
! 429: response_len = n;
! 430: break;
! 431: case MODE_CHUNKED:
! 432: response_buffer = build_chunked_response(content_length, &response_len);
! 433: break;
! 434: case MODE_ADDCRAP:
! 435: response_len = content_length+256;
! 436: response_buffer = malloc(response_len);
! 437: if(!response_buffer)
! 438: break;
! 439: n = snprintf(response_buffer, response_len,
! 440: "HTTP/1.1 200 OK\r\n"
! 441: "Server: minihttptestserver\r\n"
! 442: "Content-Type: text/plain\r\n"
! 443: "Content-Length: %lu\r\n"
! 444: "\r\n", content_length);
! 445: response_len = content_length+n+CRAP_LENGTH;
! 446: p = realloc(response_buffer, response_len);
! 447: if(p == NULL) {
! 448: /* error 500 */
! 449: free(response_buffer);
! 450: response_buffer = NULL;
! 451: break;
! 452: }
! 453: response_buffer = p;
! 454: build_content(response_buffer + n, content_length);
! 455: build_crap(response_buffer + n + content_length, CRAP_LENGTH);
! 456: break;
! 457: case MODE_FAVICON:
! 458: content_length = FAVICON_LENGTH;
! 459: response_len = content_length + 256;
! 460: response_buffer = malloc(response_len);
! 461: if(!response_buffer)
! 462: break;
! 463: n = snprintf(response_buffer, response_len,
! 464: "HTTP/1.1 200 OK\r\n"
! 465: "Server: minihttptestserver\r\n"
! 466: "Content-Type: image/vnd.microsoft.icon\r\n"
! 467: "Content-Length: %lu\r\n"
! 468: "\r\n", content_length);
! 469: /* image/x-icon */
! 470: build_favicon_content((unsigned char *)(response_buffer + n), content_length);
! 471: response_len = content_length + n;
! 472: break;
! 473: default:
! 474: response_len = content_length+256;
! 475: response_buffer = malloc(response_len);
! 476: if(!response_buffer)
! 477: break;
! 478: n = snprintf(response_buffer, response_len,
! 479: "HTTP/1.1 200 OK\r\n"
! 480: "Server: minihttptestserver\r\n"
! 481: "Content-Type: text/plain\r\n"
! 482: "\r\n");
! 483: response_len = content_length+n;
! 484: p = realloc(response_buffer, response_len);
! 485: if(p == NULL) {
! 486: /* Error 500 */
! 487: free(response_buffer);
! 488: response_buffer = NULL;
! 489: break;
! 490: }
! 491: response_buffer = p;
! 492: build_content(response_buffer + n, response_len - n);
! 493: }
! 494:
! 495: if(response_buffer) {
! 496: send_response(c, response_buffer, response_len);
! 497: free(response_buffer);
! 498: } else {
! 499: /* Error 500 */
! 500: }
! 501: }
! 502:
! 503: /**
! 504: */
! 505: int main(int argc, char * * argv) {
! 506: int ipv6 = 0;
! 507: int r, i;
! 508: unsigned short port = 0;
! 509: const char * expected_file_name = NULL;
! 510:
! 511: for(i = 1; i < argc; i++) {
! 512: if(argv[i][0] == '-') {
! 513: switch(argv[i][1]) {
! 514: case '6':
! 515: ipv6 = 1;
! 516: break;
! 517: case 'e':
! 518: /* write expected file ! */
! 519: expected_file_name = argv[++i];
! 520: break;
! 521: case 'p':
! 522: /* port */
! 523: if(++i < argc) {
! 524: port = (unsigned short)atoi(argv[i]);
! 525: }
! 526: break;
! 527: default:
! 528: fprintf(stderr, "unknown command line switch '%s'\n", argv[i]);
! 529: }
! 530: } else {
! 531: fprintf(stderr, "unknown command line argument '%s'\n", argv[i]);
! 532: }
! 533: }
! 534:
! 535: srand(time(NULL));
! 536:
! 537: r = server(port, expected_file_name, ipv6);
! 538: if(r != 0) {
! 539: printf("*** ERROR ***\n");
! 540: }
! 541: return r;
! 542: }
! 543:
! 544: static int server(unsigned short port, const char * expected_file_name, int ipv6)
! 545: {
! 546: int s, c;
! 547: int i;
! 548: struct sockaddr_storage server_addr;
! 549: socklen_t server_addrlen;
! 550: struct sockaddr_storage client_addr;
! 551: socklen_t client_addrlen;
! 552: pid_t pid;
! 553: int child = 0;
! 554: int status;
! 555: struct sigaction sa;
! 556:
! 557: memset(&sa, 0, sizeof(struct sigaction));
! 558:
! 559: /*signal(SIGCHLD, handle_signal_chld);*/
! 560: sa.sa_handler = handle_signal_chld;
! 561: if(sigaction(SIGCHLD, &sa, NULL) < 0) {
! 562: perror("sigaction");
! 563: return 1;
! 564: }
! 565: /*signal(SIGINT, handle_signal_int);*/
! 566: sa.sa_handler = handle_signal_int;
! 567: if(sigaction(SIGINT, &sa, NULL) < 0) {
! 568: perror("sigaction");
! 569: return 1;
! 570: }
! 571:
! 572: s = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0);
! 573: if(s < 0) {
! 574: perror("socket");
! 575: return 1;
! 576: }
! 577: memset(&server_addr, 0, sizeof(struct sockaddr_storage));
! 578: memset(&client_addr, 0, sizeof(struct sockaddr_storage));
! 579: if(ipv6) {
! 580: struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr;
! 581: addr->sin6_family = AF_INET6;
! 582: addr->sin6_port = htons(port);
! 583: addr->sin6_addr = in6addr_loopback;
! 584: } else {
! 585: struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr;
! 586: addr->sin_family = AF_INET;
! 587: addr->sin_port = htons(port);
! 588: addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
! 589: }
! 590: if(bind(s, (struct sockaddr *)&server_addr,
! 591: ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) < 0) {
! 592: perror("bind");
! 593: return 1;
! 594: }
! 595: if(listen(s, 5) < 0) {
! 596: perror("listen");
! 597: }
! 598: if(port == 0) {
! 599: server_addrlen = sizeof(struct sockaddr_storage);
! 600: if(getsockname(s, (struct sockaddr *)&server_addr, &server_addrlen) < 0) {
! 601: perror("getsockname");
! 602: return 1;
! 603: }
! 604: if(ipv6) {
! 605: struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr;
! 606: port = ntohs(addr->sin6_port);
! 607: } else {
! 608: struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr;
! 609: port = ntohs(addr->sin_port);
! 610: }
! 611: printf("Listening on port %hu\n", port);
! 612: fflush(stdout);
! 613: }
! 614:
! 615: /* write expected file */
! 616: if(expected_file_name) {
! 617: FILE * f;
! 618: f = fopen(expected_file_name, "wb");
! 619: if(f) {
! 620: char * buffer;
! 621: buffer = malloc(16*1024);
! 622: if(buffer == NULL) {
! 623: fprintf(stderr, "memory allocation error\n");
! 624: } else {
! 625: build_content(buffer, 16*1024);
! 626: i = fwrite(buffer, 1, 16*1024, f);
! 627: if(i != 16*1024) {
! 628: fprintf(stderr, "error writing to file %s : %dbytes written (out of %d)\n", expected_file_name, i, 16*1024);
! 629: }
! 630: free(buffer);
! 631: }
! 632: fclose(f);
! 633: } else {
! 634: fprintf(stderr, "error opening file %s for writing\n", expected_file_name);
! 635: }
! 636: }
! 637:
! 638: /* fork() loop */
! 639: while(!child && !quit) {
! 640: while(child_to_wait_for > 0) {
! 641: pid = wait(&status);
! 642: if(pid < 0) {
! 643: perror("wait");
! 644: } else {
! 645: printf("child(%d) terminated with status %d\n", (int)pid, status);
! 646: }
! 647: --child_to_wait_for;
! 648: }
! 649: client_addrlen = sizeof(struct sockaddr_storage);
! 650: c = accept(s, (struct sockaddr *)&client_addr,
! 651: &client_addrlen);
! 652: if(c < 0) {
! 653: if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
! 654: continue;
! 655: perror("accept");
! 656: return 1;
! 657: }
! 658: printf("accept...\n");
! 659: pid = fork();
! 660: if(pid < 0) {
! 661: perror("fork");
! 662: return 1;
! 663: } else if(pid == 0) {
! 664: /* child */
! 665: child = 1;
! 666: close(s);
! 667: s = -1;
! 668: handle_http_connection(c);
! 669: }
! 670: close(c);
! 671: }
! 672: if(s >= 0) {
! 673: close(s);
! 674: s = -1;
! 675: }
! 676: if(!child) {
! 677: while(child_to_wait_for > 0) {
! 678: pid = wait(&status);
! 679: if(pid < 0) {
! 680: perror("wait");
! 681: } else {
! 682: printf("child(%d) terminated with status %d\n", (int)pid, status);
! 683: }
! 684: --child_to_wait_for;
! 685: }
! 686: printf("Bye...\n");
! 687: }
! 688: return 0;
! 689: }
! 690:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>