X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=ffserver.c;h=37b9a8ee1dcaeb33877a8736a909512aa416aac5;hb=2626308abb1dc59afa9a9b36cf767ea6823b6cf2;hp=c91bd640991d29fb71897ce186b51c9623d5f5c3;hpb=637b638e1eea5d3f41553637dfa8a13daf119273;p=ffmpeg diff --git a/ffserver.c b/ffserver.c index c91bd640991..37b9a8ee1dc 100644 --- a/ffserver.c +++ b/ffserver.c @@ -22,41 +22,39 @@ #define _XOPEN_SOURCE 600 #include "config.h" -#ifndef HAVE_CLOSESOCKET +#if !HAVE_CLOSESOCKET #define closesocket close #endif #include #include #include -#include "libavutil/random.h" -#include "libavutil/avstring.h" #include "libavformat/avformat.h" #include "libavformat/network.h" #include "libavformat/os_support.h" -#include "libavformat/rtp.h" +#include "libavformat/rtpdec.h" #include "libavformat/rtsp.h" +#include "libavutil/avstring.h" +#include "libavutil/lfg.h" +#include "libavutil/random_seed.h" #include "libavcodec/opt.h" #include #include #include #include -#ifdef HAVE_POLL_H +#if HAVE_POLL_H #include #endif #include #include -#undef time //needed because HAVE_AV_CONFIG_H is defined on top #include #include #include -#ifdef HAVE_DLFCN_H +#if HAVE_DLFCN_H #include #endif #include "cmdutils.h" -#undef exit - const char program_name[] = "FFserver"; const int program_birth_year = 2000; @@ -101,6 +99,11 @@ static const char *http_state[] = { #define SYNC_TIMEOUT (10 * 1000) +typedef struct RTSPActionServerSetup { + uint32_t ipaddr; + char transport_option[512]; +} RTSPActionServerSetup; + typedef struct { int64_t count1, count2; int64_t time1, time2; @@ -116,6 +119,8 @@ typedef struct HTTPContext { uint8_t *buffer_ptr, *buffer_end; int http_error; int post; + int chunked_encoding; + int chunk_size; /* 0 if it needs to be read */ struct HTTPContext *next; int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */ int64_t data_count; @@ -157,7 +162,7 @@ typedef struct HTTPContext { int seq; /* RTSP sequence number */ /* RTP state specific */ - enum RTSPProtocol rtp_protocol; + enum RTSPLowerTransport rtp_protocol; char session_id[32]; /* session id */ AVFormatContext *rtp_ctx[MAX_STREAMS]; @@ -229,6 +234,7 @@ typedef struct FFStream { int feed_opened; /* true if someone is writing to the feed */ int is_feed; /* true if it is a feed */ int readonly; /* True if writing is prohibited to the file */ + int truncate; /* True if feeder connection truncate the feed file */ int conns_served; int64_t bytes_served; int64_t feed_max_size; /* maximum storage size, zero means unlimited */ @@ -266,10 +272,10 @@ static int http_receive_data(HTTPContext *c); static int rtsp_parse_request(HTTPContext *c); static void rtsp_cmd_describe(HTTPContext *c, const char *url); static void rtsp_cmd_options(HTTPContext *c, const char *url); -static void rtsp_cmd_setup(HTTPContext *c, const char *url, RTSPHeader *h); -static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h); -static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPHeader *h); -static void rtsp_cmd_teardown(HTTPContext *c, const char *url, RTSPHeader *h); +static void rtsp_cmd_setup(HTTPContext *c, const char *url, RTSPMessageHeader *h); +static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPMessageHeader *h); +static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPMessageHeader *h); +static void rtsp_cmd_teardown(HTTPContext *c, const char *url, RTSPMessageHeader *h); /* SDP handling */ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, @@ -278,7 +284,7 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, /* RTP handling */ static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, FFStream *stream, const char *session_id, - enum RTSPProtocol rtp_protocol); + enum RTSPLowerTransport rtp_protocol); static int rtp_new_av_stream(HTTPContext *c, int stream_index, struct sockaddr_in *dest_addr, HTTPContext *rtsp_c); @@ -302,10 +308,46 @@ static uint64_t current_bandwidth; static int64_t cur_time; // Making this global saves on passing it around everywhere -static AVRandomState random_state; +static AVLFG random_state; static FILE *logfile = NULL; +/* FIXME: make ffserver work with IPv6 */ +/* resolve host with also IP address parsing */ +static int resolve_host(struct in_addr *sin_addr, const char *hostname) +{ + + if (!ff_inet_aton(hostname, sin_addr)) { +#if HAVE_GETADDRINFO + struct addrinfo *ai, *cur; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + if (getaddrinfo(hostname, NULL, &hints, &ai)) + return -1; + /* getaddrinfo returns a linked list of addrinfo structs. + * Even if we set ai_family = AF_INET above, make sure + * that the returned one actually is of the correct type. */ + for (cur = ai; cur; cur = cur->ai_next) { + if (cur->ai_family == AF_INET) { + *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr; + freeaddrinfo(ai); + return 0; + } + } + freeaddrinfo(ai); + return -1; +#else + struct hostent *hp; + hp = gethostbyname(hostname); + if (!hp) + return -1; + memcpy(sin_addr, hp->h_addr_list[0], sizeof(struct in_addr)); +#endif + } + return 0; +} + static char *ctime1(char *buf2) { time_t ti; @@ -335,7 +377,7 @@ static void http_vlog(const char *fmt, va_list vargs) } } -void __attribute__ ((format (printf, 1, 2))) http_log(const char *fmt, ...) +static void __attribute__ ((format (printf, 1, 2))) http_log(const char *fmt, ...) { va_list vargs; va_start(vargs, fmt); @@ -347,7 +389,7 @@ static void http_av_log(void *ptr, int level, const char *fmt, va_list vargs) { static int print_prefix = 1; AVClass *avc = ptr ? *(AVClass**)ptr : NULL; - if (level > av_log_level) + if (level > av_log_get_level()) return; if (print_prefix && avc) http_log("[%s @ %p]", avc->item_name(ptr), ptr); @@ -496,7 +538,7 @@ static void start_multicast(void) if (stream->is_multicast) { /* open the RTP connection */ snprintf(session_id, sizeof(session_id), "%08x%08x", - av_random(&random_state), av_random(&random_state)); + av_lfg_get(&random_state), av_lfg_get(&random_state)); /* choose a port if none given */ if (stream->multicast_port == 0) { @@ -509,7 +551,7 @@ static void start_multicast(void) dest_addr.sin_port = htons(stream->multicast_port); rtp_c = rtp_new_connection(&dest_addr, stream, session_id, - RTSP_PROTOCOL_RTP_UDP_MULTICAST); + RTSP_LOWER_TRANSPORT_UDP_MULTICAST); if (!rtp_c) continue; @@ -567,7 +609,7 @@ static int http_server(void) return -1; } - http_log("ffserver started.\n"); + http_log("FFserver started.\n"); start_children(first_feed); @@ -692,6 +734,22 @@ static void start_wait_request(HTTPContext *c, int is_rtsp) } } +static void http_send_too_busy_reply(int fd) +{ + char buffer[300]; + int len = snprintf(buffer, sizeof(buffer), + "HTTP/1.0 200 Server too busy\r\n" + "Content-type: text/html\r\n" + "\r\n" + "Too busy\r\n" + "

The server is too busy to serve your request at this time.

\r\n" + "

The number of current connections is %d, and this exceeds the limit of %d.

\r\n" + "\r\n", + nb_connections, nb_max_connections); + send(fd, buffer, len, 0); +} + + static void new_connection(int server_fd, int is_rtsp) { struct sockaddr_in from_addr; @@ -707,10 +765,10 @@ static void new_connection(int server_fd, int is_rtsp) } ff_socket_nonblock(fd, 1); - /* XXX: should output a warning page when coming - close to the connection limit */ - if (nb_connections >= nb_max_connections) + if (nb_connections >= nb_max_connections) { + http_send_too_busy_reply(fd); goto fail; + } /* add a new connection */ c = av_mallocz(sizeof(HTTPContext)); @@ -1167,6 +1225,38 @@ static void get_word(char *buf, int buf_size, const char **pp) *pp = p; } +static void get_arg(char *buf, int buf_size, const char **pp) +{ + const char *p; + char *q; + int quote; + + p = *pp; + while (isspace(*p)) p++; + q = buf; + quote = 0; + if (*p == '\"' || *p == '\'') + quote = *p++; + for(;;) { + if (quote) { + if (*p == quote) + break; + } else { + if (isspace(*p)) + break; + } + if (*p == '\0') + break; + if ((q - buf) < buf_size - 1) + *q++ = *p; + p++; + } + *q = '\0'; + if (quote && *p == quote) + p++; + *pp = p; +} + static int validate_acl(FFStream *stream, HTTPContext *c) { enum IPAddressAction last_action = IP_DENY; @@ -1256,7 +1346,7 @@ static int http_parse_request(HTTPContext *c) av_strlcpy(c->protocol, protocol, sizeof(c->protocol)); if (ffserver_debug) - http_log("New connection: %s %s\n", cmd, url); + http_log("%s - - New connection: %s %s\n", inet_ntoa(c->from_addr.sin_addr), cmd, url); /* find the filename and the optional info string in the request */ p = strchr(url, '?'); @@ -1283,20 +1373,20 @@ static int http_parse_request(HTTPContext *c) } redir_type = REDIR_NONE; - if (match_ext(filename, "asx")) { + if (av_match_ext(filename, "asx")) { redir_type = REDIR_ASX; filename[strlen(filename)-1] = 'f'; - } else if (match_ext(filename, "asf") && + } else if (av_match_ext(filename, "asf") && (!useragent || strncasecmp(useragent, "NSPlayer", 8) != 0)) { /* if this isn't WMP or lookalike, return the redirector file */ redir_type = REDIR_ASF; - } else if (match_ext(filename, "rpm,ram")) { + } else if (av_match_ext(filename, "rpm,ram")) { redir_type = REDIR_RAM; strcpy(filename + strlen(filename)-2, "m"); - } else if (match_ext(filename, "rtsp")) { + } else if (av_match_ext(filename, "rtsp")) { redir_type = REDIR_RTSP; compute_real_filename(filename, sizeof(filename) - 1); - } else if (match_ext(filename, "sdp")) { + } else if (av_match_ext(filename, "sdp")) { redir_type = REDIR_SDP; compute_real_filename(filename, sizeof(filename) - 1); } @@ -1313,6 +1403,7 @@ static int http_parse_request(HTTPContext *c) } if (stream == NULL) { snprintf(msg, sizeof(msg), "File '%s' not found", url); + http_log("File '%s' not found\n", url); goto send_error; } @@ -1341,23 +1432,23 @@ static int http_parse_request(HTTPContext *c) /* If this is WMP, get the rate information */ if (extract_rates(ratebuf, sizeof(ratebuf), c->buffer)) { if (modify_current_stream(c, ratebuf)) { - for (i = 0; i < sizeof(c->feed_streams) / sizeof(c->feed_streams[0]); i++) { + for (i = 0; i < FF_ARRAY_ELEMS(c->feed_streams); i++) { if (c->switch_feed_streams[i] >= 0) do_switch_stream(c, i); } } } + if (c->post == 0 && stream->stream_type == STREAM_TYPE_LIVE) + current_bandwidth += stream->bandwidth; + /* If already streaming this feed, do not let start another feeder. */ if (stream->feed_opened) { snprintf(msg, sizeof(msg), "This feed is already being received."); - http_log("feed %s already being received\n", stream->feed_filename); + http_log("Feed '%s' already being received\n", stream->feed_filename); goto send_error; } - if (c->post == 0 && stream->stream_type == STREAM_TYPE_LIVE) - current_bandwidth += stream->bandwidth; - if (c->post == 0 && max_bandwidth < current_bandwidth) { c->http_error = 200; q = c->buffer; @@ -1367,8 +1458,8 @@ static int http_parse_request(HTTPContext *c) "\r\n" "Too busy\r\n" "

The server is too busy to serve your request at this time.

\r\n" - "

The bandwidth being served (including your stream) is %lldkbit/sec, " - "and this exceeds the limit of %lldkbit/sec.

\r\n" + "

The bandwidth being served (including your stream) is %"PRIu64"kbit/sec, " + "and this exceeds the limit of %"PRIu64"kbit/sec.

\r\n" "\r\n", current_bandwidth, max_bandwidth); /* prepare output buffer */ c->buffer_ptr = c->buffer; @@ -1592,7 +1683,7 @@ static int http_parse_request(HTTPContext *c) if (!strcmp(c->stream->fmt->name,"asf_stream")) { /* Need to allocate a client id */ - c->wmp_client_id = av_random(&random_state) & 0x7fffffff; + c->wmp_client_id = av_lfg_get(&random_state); q += snprintf(q, q - (char *) c->buffer + c->buffer_size, "Server: Cougar 4.1.0.3923\r\nCache-Control: no-cache\r\nPragma: client-id=%d\r\nPragma: features=\"broadcast\"\r\n", c->wmp_client_id); } @@ -1612,10 +1703,10 @@ static int http_parse_request(HTTPContext *c) "HTTP/1.0 404 Not Found\r\n" "Content-type: text/html\r\n" "\r\n" - "\n" - "404 Not Found\n" - "%s\n" - "\n", msg); + "\n" + "404 Not Found\n" + "%s\n" + "\n", msg); /* prepare output buffer */ c->buffer_ptr = c->buffer; c->buffer_end = q; @@ -1660,15 +1751,15 @@ static void compute_status(HTTPContext *c) url_fprintf(pb, "Pragma: no-cache\r\n"); url_fprintf(pb, "\r\n"); - url_fprintf(pb, "%s Status\n", program_name); + url_fprintf(pb, "%s Status\n", program_name); if (c->stream->feed_filename[0]) url_fprintf(pb, "\n", c->stream->feed_filename); - url_fprintf(pb, "\n"); - url_fprintf(pb, "

%s Status

\n", program_name); + url_fprintf(pb, "\n"); + url_fprintf(pb, "

%s Status

\n", program_name); /* format status */ - url_fprintf(pb, "

Available Streams

\n"); - url_fprintf(pb, "\n"); - url_fprintf(pb, "
PathServed
Conns

bytes
FormatBit rate
kbits/s
Video
kbits/s

Codec
Audio
kbits/s

Codec
Feed\n"); + url_fprintf(pb, "

Available Streams

\n"); + url_fprintf(pb, "\n"); + url_fprintf(pb, "
PathServed
Conns

bytes
FormatBit rate
kbits/s
Video
kbits/s

Codec
Audio
kbits/s

Codec
Feed\n"); stream = first_stream; while (stream != NULL) { char sfilename[1024]; @@ -1696,7 +1787,7 @@ static void compute_status(HTTPContext *c) } } - url_fprintf(pb, "
%s ", + url_fprintf(pb, "
%s ", sfilename, stream->filename); url_fprintf(pb, " %d ", stream->conns_served); @@ -1737,26 +1828,26 @@ static void compute_status(HTTPContext *c) abort(); } } - url_fprintf(pb, " %s %d %d %s %s %d %s %s", + url_fprintf(pb, " %s %d %d %s %s %d %s %s", stream->fmt->name, stream->bandwidth, video_bit_rate / 1000, video_codec_name, video_codec_name_extra, audio_bit_rate / 1000, audio_codec_name, audio_codec_name_extra); if (stream->feed) - url_fprintf(pb, "%s", stream->feed->filename); + url_fprintf(pb, "%s", stream->feed->filename); else - url_fprintf(pb, "%s", stream->feed_filename); + url_fprintf(pb, "%s", stream->feed_filename); url_fprintf(pb, "\n"); } break; default: - url_fprintf(pb, " - - - - \n"); + url_fprintf(pb, " - - - - \n"); break; } } stream = stream->next; } - url_fprintf(pb, "
\n"); + url_fprintf(pb, "
\n"); stream = first_stream; while (stream != NULL) { @@ -1824,47 +1915,17 @@ static void compute_status(HTTPContext *c) stream = stream->next; } -#if 0 - { - float avg; - AVCodecContext *enc; - char buf[1024]; - - /* feed status */ - stream = first_feed; - while (stream != NULL) { - url_fprintf(pb, "

Feed '%s'

\n", stream->filename); - url_fprintf(pb, "\n"); - url_fprintf(pb, "
ParametersFrame countSizeAvg bitrate (kbits/s)\n"); - for(i=0;inb_streams;i++) { - AVStream *st = stream->streams[i]; - FeedData *fdata = st->priv_data; - enc = st->codec; - - avcodec_string(buf, sizeof(buf), enc); - avg = fdata->avg_frame_size * (float)enc->rate * 8.0; - if (enc->codec->type == CODEC_TYPE_AUDIO && enc->frame_size > 0) - avg /= enc->frame_size; - url_fprintf(pb, "
%s %d %"PRId64" %0.1f\n", - buf, enc->frame_number, fdata->data_count, avg / 1000.0); - } - url_fprintf(pb, "
\n"); - stream = stream->next_feed; - } - } -#endif - /* connection status */ - url_fprintf(pb, "

Connection Status

\n"); + url_fprintf(pb, "

Connection Status

\n"); - url_fprintf(pb, "Number of connections: %d / %d
\n", + url_fprintf(pb, "Number of connections: %d / %d
\n", nb_connections, nb_max_connections); - url_fprintf(pb, "Bandwidth in use: %lldk / %lldk
\n", + url_fprintf(pb, "Bandwidth in use: %"PRIu64"k / %"PRIu64"k
\n", current_bandwidth, max_bandwidth); - url_fprintf(pb, "\n"); - url_fprintf(pb, "
#FileIPProtoStateTarget bits/secActual bits/secBytes transferred\n"); + url_fprintf(pb, "\n"); + url_fprintf(pb, "
#FileIPProtoStateTarget bits/secActual bits/secBytes transferred\n"); c1 = first_http_ctx; i = 0; while (c1 != NULL) { @@ -1883,7 +1944,7 @@ static void compute_status(HTTPContext *c) i++; p = inet_ntoa(c1->from_addr.sin_addr); - url_fprintf(pb, "
%d%s%s%s%s%s", + url_fprintf(pb, "
%d%s%s%s%s%s", i, c1->stream ? c1->stream->filename : "", c1->state == HTTPSTATE_RECEIVE_DATA ? "(input)" : "", @@ -1898,13 +1959,13 @@ static void compute_status(HTTPContext *c) url_fprintf(pb, "\n"); c1 = c1->next; } - url_fprintf(pb, "
\n"); + url_fprintf(pb, "
\n"); /* date */ ti = time(NULL); p = ctime(&ti); - url_fprintf(pb, "
Generated at %s", p); - url_fprintf(pb, "\n\n"); + url_fprintf(pb, "
Generated at %s", p); + url_fprintf(pb, "\n\n"); len = url_close_dyn_buf(pb, &c->pb_buffer); c->buffer_ptr = c->pb_buffer; @@ -1963,12 +2024,6 @@ static int open_input_stream(HTTPContext *c, const char *info) if (input_filename[0] == '\0') return -1; -#if 0 - { time_t when = stream_pos / 1000000; - http_log("Stream pos = %"PRId64", time=%s", stream_pos, ctime(&when)); - } -#endif - /* open stream */ if ((ret = av_open_input_file(&s, input_filename, c->stream->ifmt, buf_size, c->stream->ap_in)) < 0) { @@ -1977,7 +2032,11 @@ static int open_input_stream(HTTPContext *c, const char *info) } s->flags |= AVFMT_FLAG_GENPTS; c->fmt_in = s; - av_find_stream_info(c->fmt_in); + if (strcmp(s->iformat->name, "ffm") && av_find_stream_info(c->fmt_in) < 0) { + http_log("Could not find stream info '%s'\n", input_filename); + av_close_input_file(s); + return -1; + } /* open each parser */ for(i=0;inb_streams;i++) @@ -2036,14 +2095,10 @@ static int http_prepare_data(HTTPContext *c) switch(c->state) { case HTTPSTATE_SEND_DATA_HEADER: memset(&c->fmt_ctx, 0, sizeof(c->fmt_ctx)); - av_strlcpy(c->fmt_ctx.author, c->stream->author, - sizeof(c->fmt_ctx.author)); - av_strlcpy(c->fmt_ctx.comment, c->stream->comment, - sizeof(c->fmt_ctx.comment)); - av_strlcpy(c->fmt_ctx.copyright, c->stream->copyright, - sizeof(c->fmt_ctx.copyright)); - av_strlcpy(c->fmt_ctx.title, c->stream->title, - sizeof(c->fmt_ctx.title)); + av_metadata_set(&c->fmt_ctx.metadata, "author" ,c->stream->author); + av_metadata_set(&c->fmt_ctx.metadata, "comment" ,c->stream->comment); + av_metadata_set(&c->fmt_ctx.metadata, "copyright",c->stream->copyright); + av_metadata_set(&c->fmt_ctx.metadata, "title" ,c->stream->title); for(i=0;istream->nb_streams;i++) { AVStream *st; @@ -2176,14 +2231,6 @@ static int http_prepare_data(HTTPContext *c) if (ist->start_time != AV_NOPTS_VALUE) c->cur_pts -= av_rescale_q(ist->start_time, ist->time_base, AV_TIME_BASE_Q); c->cur_frame_duration = av_rescale_q(pkt.duration, ist->time_base, AV_TIME_BASE_Q); -#if 0 - printf("index=%d pts=%0.3f duration=%0.6f\n", - pkt.stream_index, - (double)c->cur_pts / - AV_TIME_BASE, - (double)c->cur_frame_duration / - AV_TIME_BASE); -#endif /* find RTP context */ c->packet_stream_index = pkt.stream_index; ctx = c->rtp_ctx[c->packet_stream_index]; @@ -2202,7 +2249,7 @@ static int http_prepare_data(HTTPContext *c) if (c->is_packetized) { int max_packet_size; - if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP) + if (c->rtp_protocol == RTSP_LOWER_TRANSPORT_TCP) max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; else max_packet_size = url_get_max_packet_size(c->rtp_handles[c->packet_stream_index]); @@ -2306,7 +2353,7 @@ static int http_send_data(HTTPContext *c) if (c->stream) c->stream->bytes_served += len; - if (c->rtp_protocol == RTSP_PROTOCOL_RTP_TCP) { + if (c->rtp_protocol == RTSP_LOWER_TRANSPORT_TCP) { /* RTP packets are sent inside the RTSP TCP connection */ ByteIOContext *pb; int interleaved_index, size; @@ -2406,7 +2453,19 @@ static int http_start_receive_data(HTTPContext *c) } c->feed_fd = fd; - c->stream->feed_write_index = ffm_read_write_index(fd); + if (c->stream->truncate) { + /* truncate feed file */ + ffm_write_write_index(c->feed_fd, FFM_PACKET_SIZE); + ftruncate(c->feed_fd, FFM_PACKET_SIZE); + http_log("Truncating feed file '%s'\n", c->stream->feed_filename); + } else { + if ((c->stream->feed_write_index = ffm_read_write_index(fd)) < 0) { + http_log("Error reading write index from feed file: %s\n", strerror(errno)); + return -1; + } + } + + c->stream->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZE); c->stream->feed_size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); @@ -2414,17 +2473,46 @@ static int http_start_receive_data(HTTPContext *c) c->buffer_ptr = c->buffer; c->buffer_end = c->buffer + FFM_PACKET_SIZE; c->stream->feed_opened = 1; + c->chunked_encoding = !!av_stristr(c->buffer, "Transfer-Encoding: chunked"); return 0; } static int http_receive_data(HTTPContext *c) { HTTPContext *c1; + int len, loop_run = 0; - if (c->buffer_end > c->buffer_ptr) { - int len; + while (c->chunked_encoding && !c->chunk_size && + c->buffer_end > c->buffer_ptr) { + /* read chunk header, if present */ + len = recv(c->fd, c->buffer_ptr, 1, 0); + + if (len < 0) { + if (ff_neterrno() != FF_NETERROR(EAGAIN) && + ff_neterrno() != FF_NETERROR(EINTR)) + /* error : close connection */ + goto fail; + } else if (len == 0) { + /* end of connection : close it */ + goto fail; + } else if (c->buffer_ptr - c->buffer >= 2 && + !memcmp(c->buffer_ptr - 1, "\r\n", 2)) { + c->chunk_size = strtol(c->buffer, 0, 16); + if (c->chunk_size == 0) // end of stream + goto fail; + c->buffer_ptr = c->buffer; + break; + } else if (++loop_run > 10) { + /* no chunk header, abort */ + goto fail; + } else { + c->buffer_ptr++; + } + } - len = recv(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr, 0); + if (c->buffer_end > c->buffer_ptr) { + len = recv(c->fd, c->buffer_ptr, + FFMIN(c->chunk_size, c->buffer_end - c->buffer_ptr), 0); if (len < 0) { if (ff_neterrno() != FF_NETERROR(EAGAIN) && ff_neterrno() != FF_NETERROR(EINTR)) @@ -2434,6 +2522,7 @@ static int http_receive_data(HTTPContext *c) /* end of connection : close it */ goto fail; else { + c->chunk_size -= len; c->buffer_ptr += len; c->data_count += len; update_datarate(&c->datarate, c->data_count); @@ -2472,7 +2561,10 @@ static int http_receive_data(HTTPContext *c) feed->feed_write_index = FFM_PACKET_SIZE; /* write index */ - ffm_write_write_index(c->feed_fd, feed->feed_write_index); + if (ffm_write_write_index(c->feed_fd, feed->feed_write_index) < 0) { + http_log("Error writing index to feed file: %s\n", strerror(errno)); + goto fail; + } /* wake up any waiting connections */ for(c1 = first_http_ctx; c1 != NULL; c1 = c1->next) { @@ -2504,6 +2596,8 @@ static int http_receive_data(HTTPContext *c) if (s->nb_streams != feed->nb_streams) { av_close_input_stream(s); av_free(pb); + http_log("Feed '%s' stream number does not match registered feed\n", + c->stream->feed_filename); goto fail; } @@ -2615,7 +2709,7 @@ static int rtsp_parse_request(HTTPContext *c) char protocol[32]; char line[1024]; int len; - RTSPHeader header1, *header = &header1; + RTSPMessageHeader header1, *header = &header1; c->buffer_ptr[0] = '\0'; p = c->buffer; @@ -2641,7 +2735,7 @@ static int rtsp_parse_request(HTTPContext *c) } /* parse each header line */ - memset(header, 0, sizeof(RTSPHeader)); + memset(header, 0, sizeof(*header)); /* skip to next line */ while (*p != '\n' && *p != '\0') p++; @@ -2662,7 +2756,7 @@ static int rtsp_parse_request(HTTPContext *c) len = sizeof(line) - 1; memcpy(line, p, len); line[len] = '\0'; - rtsp_parse_line(header, line); + ff_rtsp_parse_line(header, line, NULL); p = p1 + 1; } @@ -2704,15 +2798,12 @@ static int prepare_sdp_description(FFStream *stream, uint8_t **pbuffer, AVStream avs[MAX_STREAMS]; int i; - avc = av_alloc_format_context(); + avc = avformat_alloc_context(); if (avc == NULL) { return -1; } - if (stream->title[0] != 0) { - av_strlcpy(avc->title, stream->title, sizeof(avc->title)); - } else { - av_strlcpy(avc->title, "No Title", sizeof(avc->title)); - } + av_metadata_set(&avc->metadata, "title", + stream->title[0] ? stream->title : "No Title"); avc->nb_streams = stream->nb_streams; if (stream->is_multicast) { snprintf(avc->filename, 1024, "rtp://%s:%d?multicast=1?ttl=%d", @@ -2750,7 +2841,7 @@ static void rtsp_cmd_describe(HTTPContext *c, const char *url) struct sockaddr_in my_addr; /* find which url is asked */ - url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + ff_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); path = path1; if (*path == '/') path++; @@ -2798,21 +2889,21 @@ static HTTPContext *find_rtp_session(const char *session_id) return NULL; } -static RTSPTransportField *find_transport(RTSPHeader *h, enum RTSPProtocol protocol) +static RTSPTransportField *find_transport(RTSPMessageHeader *h, enum RTSPLowerTransport lower_transport) { RTSPTransportField *th; int i; for(i=0;inb_transports;i++) { th = &h->transports[i]; - if (th->protocol == protocol) + if (th->lower_transport == lower_transport) return th; } return NULL; } static void rtsp_cmd_setup(HTTPContext *c, const char *url, - RTSPHeader *h) + RTSPMessageHeader *h) { FFStream *stream; int stream_index, port; @@ -2825,7 +2916,7 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, RTSPActionServerSetup setup; /* find which url is asked */ - url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + ff_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); path = path1; if (*path == '/') path++; @@ -2861,15 +2952,15 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, /* generate session id if needed */ if (h->session_id[0] == '\0') snprintf(h->session_id, sizeof(h->session_id), "%08x%08x", - av_random(&random_state), av_random(&random_state)); + av_lfg_get(&random_state), av_lfg_get(&random_state)); /* find rtp session, and create it if none found */ rtp_c = find_rtp_session(h->session_id); if (!rtp_c) { /* always prefer UDP */ - th = find_transport(h, RTSP_PROTOCOL_RTP_UDP); + th = find_transport(h, RTSP_LOWER_TRANSPORT_UDP); if (!th) { - th = find_transport(h, RTSP_PROTOCOL_RTP_TCP); + th = find_transport(h, RTSP_LOWER_TRANSPORT_TCP); if (!th) { rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); return; @@ -2877,7 +2968,7 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, } rtp_c = rtp_new_connection(&c->from_addr, stream, h->session_id, - th->protocol); + th->lower_transport); if (!rtp_c) { rtsp_reply_error(c, RTSP_STATUS_BANDWIDTH); return; @@ -2905,7 +2996,7 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, /* check transport */ th = find_transport(h, rtp_c->rtp_protocol); - if (!th || (th->protocol == RTSP_PROTOCOL_RTP_UDP && + if (!th || (th->lower_transport == RTSP_LOWER_TRANSPORT_UDP && th->client_port_min <= 0)) { rtsp_reply_error(c, RTSP_STATUS_TRANSPORT); return; @@ -2928,14 +3019,14 @@ static void rtsp_cmd_setup(HTTPContext *c, const char *url, url_fprintf(c->pb, "Session: %s\r\n", rtp_c->session_id); switch(rtp_c->rtp_protocol) { - case RTSP_PROTOCOL_RTP_UDP: + case RTSP_LOWER_TRANSPORT_UDP: port = rtp_get_local_port(rtp_c->rtp_handles[stream_index]); url_fprintf(c->pb, "Transport: RTP/AVP/UDP;unicast;" "client_port=%d-%d;server_port=%d-%d", th->client_port_min, th->client_port_min + 1, port, port + 1); break; - case RTSP_PROTOCOL_RTP_TCP: + case RTSP_LOWER_TRANSPORT_TCP: url_fprintf(c->pb, "Transport: RTP/AVP/TCP;interleaved=%d-%d", stream_index * 2, stream_index * 2 + 1); break; @@ -2967,7 +3058,7 @@ static HTTPContext *find_rtp_session_with_url(const char *url, return NULL; /* find which url is asked */ - url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); + ff_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); path = path1; if (*path == '/') path++; @@ -2983,7 +3074,7 @@ static HTTPContext *find_rtp_session_with_url(const char *url, return NULL; } -static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h) +static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPMessageHeader *h) { HTTPContext *rtp_c; @@ -3000,14 +3091,6 @@ static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h) return; } -#if 0 - /* XXX: seek in stream */ - if (h->range_start != AV_NOPTS_VALUE) { - printf("range_start=%0.3f\n", (double)h->range_start / AV_TIME_BASE); - av_seek_frame(rtp_c->fmt_in, -1, h->range_start); - } -#endif - rtp_c->state = HTTPSTATE_SEND_DATA; /* now everything is OK, so we can send the connection parameters */ @@ -3017,7 +3100,7 @@ static void rtsp_cmd_play(HTTPContext *c, const char *url, RTSPHeader *h) url_fprintf(c->pb, "\r\n"); } -static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPHeader *h) +static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPMessageHeader *h) { HTTPContext *rtp_c; @@ -3042,7 +3125,7 @@ static void rtsp_cmd_pause(HTTPContext *c, const char *url, RTSPHeader *h) url_fprintf(c->pb, "\r\n"); } -static void rtsp_cmd_teardown(HTTPContext *c, const char *url, RTSPHeader *h) +static void rtsp_cmd_teardown(HTTPContext *c, const char *url, RTSPMessageHeader *h) { HTTPContext *rtp_c; char session_id[32]; @@ -3071,7 +3154,7 @@ static void rtsp_cmd_teardown(HTTPContext *c, const char *url, RTSPHeader *h) static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, FFStream *stream, const char *session_id, - enum RTSPProtocol rtp_protocol) + enum RTSPLowerTransport rtp_protocol) { HTTPContext *c = NULL; const char *proto_str; @@ -3102,13 +3185,13 @@ static HTTPContext *rtp_new_connection(struct sockaddr_in *from_addr, /* protocol is shown in statistics */ switch(c->rtp_protocol) { - case RTSP_PROTOCOL_RTP_UDP_MULTICAST: + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: proto_str = "MCAST"; break; - case RTSP_PROTOCOL_RTP_UDP: + case RTSP_LOWER_TRANSPORT_UDP: proto_str = "UDP"; break; - case RTSP_PROTOCOL_RTP_TCP: + case RTSP_LOWER_TRANSPORT_TCP: proto_str = "TCP"; break; default: @@ -3147,10 +3230,10 @@ static int rtp_new_av_stream(HTTPContext *c, int max_packet_size; /* now we can open the relevant output stream */ - ctx = av_alloc_format_context(); + ctx = avformat_alloc_context(); if (!ctx) return -1; - ctx->oformat = guess_format("rtp", NULL, NULL); + ctx->oformat = av_guess_format("rtp", NULL, NULL); st = av_mallocz(sizeof(AVStream)); if (!st) @@ -3172,8 +3255,8 @@ static int rtp_new_av_stream(HTTPContext *c, ipaddr = inet_ntoa(dest_addr->sin_addr); switch(c->rtp_protocol) { - case RTSP_PROTOCOL_RTP_UDP: - case RTSP_PROTOCOL_RTP_UDP_MULTICAST: + case RTSP_LOWER_TRANSPORT_UDP: + case RTSP_LOWER_TRANSPORT_UDP_MULTICAST: /* RTP/UDP case */ /* XXX: also pass as parameter to function ? */ @@ -3195,7 +3278,7 @@ static int rtp_new_av_stream(HTTPContext *c, c->rtp_handles[stream_index] = h; max_packet_size = url_get_max_packet_size(h); break; - case RTSP_PROTOCOL_RTP_TCP: + case RTSP_LOWER_TRANSPORT_TCP: /* RTP/TCP case */ c->rtsp_c = rtsp_c; max_packet_size = RTSP_TCP_MAX_PACKET_SIZE; @@ -3374,9 +3457,10 @@ static void build_file_streams(void) stream->ap_in->mpeg2ts_compute_pcr = 1; } + http_log("Opening file '%s'\n", stream->feed_filename); if ((ret = av_open_input_file(&infile, stream->feed_filename, stream->ifmt, 0, stream->ap_in)) < 0) { - http_log("could not open %s: %d\n", stream->feed_filename, ret); + http_log("Could not open '%s': %d\n", stream->feed_filename, ret); /* remove stream (no need to spend more time on it) */ fail: remove_stream(stream); @@ -3507,7 +3591,7 @@ static void build_feed_streams(void) } } if (!url_exist(feed->feed_filename)) { - AVFormatContext s1, *s = &s1; + AVFormatContext s1 = {0}, *s = &s1; if (feed->readonly) { http_log("Unable to create feed file '%s' as it is marked readonly\n", @@ -3545,7 +3629,7 @@ static void build_feed_streams(void) exit(1); } - feed->feed_write_index = ffm_read_write_index(fd); + feed->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZE); feed->feed_size = lseek(fd, 0, SEEK_END); /* ensure that we do not wrap before the end of file */ if (feed->feed_max_size && feed->feed_max_size < feed->feed_size) @@ -3579,38 +3663,6 @@ static void compute_bandwidth(void) } } -static void get_arg(char *buf, int buf_size, const char **pp) -{ - const char *p; - char *q; - int quote; - - p = *pp; - while (isspace(*p)) p++; - q = buf; - quote = 0; - if (*p == '\"' || *p == '\'') - quote = *p++; - for(;;) { - if (quote) { - if (*p == quote) - break; - } else { - if (isspace(*p)) - break; - } - if (*p == '\0') - break; - if ((q - buf) < buf_size - 1) - *q++ = *p; - p++; - } - *q = '\0'; - if (quote && *p == quote) - p++; - *pp = p; -} - /* add a codec and set the default parameters */ static void add_codec(FFStream *stream, AVCodecContext *av) { @@ -3686,7 +3738,7 @@ static void add_codec(FFStream *stream, AVCodecContext *av) memcpy(st->codec, av, sizeof(AVCodecContext)); } -static int opt_audio_codec(const char *arg) +static enum CodecID opt_audio_codec(const char *arg) { AVCodec *p= avcodec_find_encoder_by_name(arg); @@ -3696,7 +3748,7 @@ static int opt_audio_codec(const char *arg) return p->id; } -static int opt_video_codec(const char *arg) +static enum CodecID opt_video_codec(const char *arg) { AVCodec *p= avcodec_find_encoder_by_name(arg); @@ -3708,7 +3760,7 @@ static int opt_video_codec(const char *arg) /* simplistic plugin support */ -#ifdef HAVE_DLOPEN +#if HAVE_DLOPEN static void load_module(const char *filename) { void *dll; @@ -3732,16 +3784,33 @@ static void load_module(const char *filename) } #endif -static int opt_default(const char *opt, const char *arg, +static int ffserver_opt_default(const char *opt, const char *arg, AVCodecContext *avctx, int type) { - const AVOption *o = NULL; - const AVOption *o2 = av_find_opt(avctx, opt, NULL, type, type); - if(o2) - o = av_set_string2(avctx, opt, arg, 1); - if(!o) - return -1; - return 0; + int ret = 0; + const AVOption *o = av_find_opt(avctx, opt, NULL, type, type); + if(o) + ret = av_set_string3(avctx, opt, arg, 1, NULL); + return ret; +} + +static AVOutputFormat *ffserver_guess_format(const char *short_name, const char *filename, + const char *mime_type) +{ + AVOutputFormat *fmt = av_guess_format(short_name, filename, mime_type); + + if (fmt) { + AVOutputFormat *stream_fmt; + char stream_format_name[64]; + + snprintf(stream_format_name, sizeof(stream_format_name), "%s_stream", fmt->name); + stream_fmt = av_guess_format(stream_format_name, NULL, NULL); + + if (stream_fmt) + fmt = stream_fmt; + } + + return fmt; } static int parse_ffconfig(const char *filename) @@ -3753,9 +3822,9 @@ static int parse_ffconfig(const char *filename) const char *p; int val, errors, line_num; FFStream **last_stream, *stream, *redirect; - FFStream **last_feed, *feed; + FFStream **last_feed, *feed, *s; AVCodecContext audio_enc, video_enc; - int audio_id, video_id; + enum CodecID audio_id, video_id; f = fopen(filename, "r"); if (!f) { @@ -3861,24 +3930,33 @@ static int parse_ffconfig(const char *filename) filename, line_num); } else { feed = av_mallocz(sizeof(FFStream)); - /* add in stream list */ - *last_stream = feed; - last_stream = &feed->next; - /* add in feed list */ - *last_feed = feed; - last_feed = &feed->next_feed; - get_arg(feed->filename, sizeof(feed->filename), &p); q = strrchr(feed->filename, '>'); if (*q) *q = '\0'; - feed->fmt = guess_format("ffm", NULL, NULL); + + for (s = first_feed; s; s = s->next) { + if (!strcmp(feed->filename, s->filename)) { + fprintf(stderr, "%s:%d: Feed '%s' already registered\n", + filename, line_num, s->filename); + errors++; + } + } + + feed->fmt = av_guess_format("ffm", NULL, NULL); /* defaut feed file */ snprintf(feed->feed_filename, sizeof(feed->feed_filename), "/tmp/%s.ffm", feed->filename); feed->feed_max_size = 5 * 1024 * 1024; feed->is_feed = 1; feed->feed = feed; /* self feeding :-) */ + + /* add in stream list */ + *last_stream = feed; + last_stream = &feed->next; + /* add in feed list */ + *last_feed = feed; + last_feed = &feed->next_feed; } } else if (!strcasecmp(cmd, "Launch")) { if (feed) { @@ -3914,6 +3992,11 @@ static int parse_ffconfig(const char *filename) get_arg(feed->feed_filename, sizeof(feed->feed_filename), &p); } else if (stream) get_arg(stream->feed_filename, sizeof(stream->feed_filename), &p); + } else if (!strcasecmp(cmd, "Truncate")) { + if (feed) { + get_arg(arg, sizeof(arg), &p); + feed->truncate = strtod(arg, NULL); + } } else if (!strcasecmp(cmd, "FileMaxSize")) { if (feed) { char *p1; @@ -3934,6 +4017,11 @@ static int parse_ffconfig(const char *filename) break; } feed->feed_max_size = (int64_t)fsize; + if (feed->feed_max_size < FFM_PACKET_SIZE*4) { + fprintf(stderr, "%s:%d: Feed max file size is too small, " + "must be at least %d\n", filename, line_num, FFM_PACKET_SIZE*4); + errors++; + } } } else if (!strcasecmp(cmd, "")) { if (!feed) { @@ -3950,16 +4038,23 @@ static int parse_ffconfig(const char *filename) fprintf(stderr, "%s:%d: Already in a tag\n", filename, line_num); } else { + FFStream *s; const AVClass *class; stream = av_mallocz(sizeof(FFStream)); - *last_stream = stream; - last_stream = &stream->next; - get_arg(stream->filename, sizeof(stream->filename), &p); q = strrchr(stream->filename, '>'); if (*q) *q = '\0'; - stream->fmt = guess_stream_format(NULL, stream->filename, NULL); + + for (s = first_stream; s; s = s->next) { + if (!strcmp(stream->filename, s->filename)) { + fprintf(stderr, "%s:%d: Stream '%s' already registered\n", + filename, line_num, s->filename); + errors++; + } + } + + stream->fmt = ffserver_guess_format(NULL, stream->filename, NULL); /* fetch avclass so AVOption works * FIXME try to use avcodec_get_context_defaults2 * without changing defaults too much */ @@ -3975,6 +4070,9 @@ static int parse_ffconfig(const char *filename) audio_id = stream->fmt->audio_codec; video_id = stream->fmt->video_codec; } + + *last_stream = stream; + last_stream = &stream->next; } } else if (!strcasecmp(cmd, "Feed")) { get_arg(arg, sizeof(arg), &p); @@ -4004,7 +4102,7 @@ static int parse_ffconfig(const char *filename) /* jpeg cannot be used here, so use single frame jpeg */ if (!strcmp(arg, "jpeg")) strcpy(arg, "mjpeg"); - stream->fmt = guess_stream_format(arg, NULL, NULL); + stream->fmt = ffserver_guess_format(arg, NULL, NULL); if (!stream->fmt) { fprintf(stderr, "%s:%d: Unknown Format: %s\n", filename, line_num, arg); @@ -4018,10 +4116,12 @@ static int parse_ffconfig(const char *filename) } } else if (!strcasecmp(cmd, "InputFormat")) { get_arg(arg, sizeof(arg), &p); - stream->ifmt = av_find_input_format(arg); - if (!stream->ifmt) { - fprintf(stderr, "%s:%d: Unknown input format: %s\n", - filename, line_num, arg); + if (stream) { + stream->ifmt = av_find_input_format(arg); + if (!stream->ifmt) { + fprintf(stderr, "%s:%d: Unknown input format: %s\n", + filename, line_num, arg); + } } } else if (!strcasecmp(cmd, "FaviconURL")) { if (stream && stream->stream_type == STREAM_TYPE_STATUS) { @@ -4179,14 +4279,14 @@ static int parse_ffconfig(const char *filename) avctx = &audio_enc; type = AV_OPT_FLAG_AUDIO_PARAM; } - if (opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING_PARAM)) { + if (ffserver_opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING_PARAM)) { fprintf(stderr, "AVOption error: %s %s\n", arg, arg2); errors++; } } else if (!strcasecmp(cmd, "VideoTag")) { get_arg(arg, sizeof(arg), &p); if ((strlen(arg) == 4) && stream) - video_enc.codec_tag = ff_get_fourcc(arg); + video_enc.codec_tag = MKTAG(arg[0], arg[1], arg[2], arg[3]); } else if (!strcasecmp(cmd, "BitExact")) { if (stream) video_enc.flags |= CODEC_FLAG_BITEXACT; @@ -4393,7 +4493,7 @@ static int parse_ffconfig(const char *filename) } } else if (!strcasecmp(cmd, "LoadModule")) { get_arg(arg, sizeof(arg), &p); -#ifdef HAVE_DLOPEN +#if HAVE_DLOPEN load_module(arg); #else fprintf(stderr, "%s:%d: Module support not compiled into this version: '%s'\n", @@ -4403,7 +4503,6 @@ static int parse_ffconfig(const char *filename) } else { fprintf(stderr, "%s:%d: Incorrect keyword: '%s'\n", filename, line_num, cmd); - errors++; } } @@ -4439,14 +4538,14 @@ static void handle_child_exit(int sig) need_to_start_children = 1; } -static void opt_debug() +static void opt_debug(void) { ffserver_debug = 1; ffserver_daemon = 0; logfilename[0] = '-'; } -static void opt_show_help(void) +static void show_help(void) { printf("usage: ffserver [options]\n" "Hyper fast multi format Audio/Video streaming server\n"); @@ -4455,10 +4554,7 @@ static void opt_show_help(void) } static const OptionDef options[] = { - { "h", OPT_EXIT, {(void*)opt_show_help}, "show help" }, - { "version", OPT_EXIT, {(void*)show_version}, "show version" }, - { "L", OPT_EXIT, {(void*)show_license}, "show license" }, - { "formats", OPT_EXIT, {(void*)show_formats}, "show available formats, codecs, protocols, ..." }, +#include "cmdutils_common_opts.h" { "n", OPT_BOOL, {(void *)&no_launch }, "enable no-launch mode" }, { "d", 0, {(void*)opt_debug}, "enable debug mode" }, { "f", HAS_ARG | OPT_STRING, {(void*)&config_filename }, "use configfile instead of /etc/ffserver.conf", "configfile" }, @@ -4483,7 +4579,7 @@ int main(int argc, char **argv) unsetenv("http_proxy"); /* Kill the http_proxy */ - av_init_random(av_gettime() + (getpid() << 16), &random_state); + av_lfg_init(&random_state, ff_random_get_seed()); memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = handle_child_exit;