|  | @@ -164,7 +164,7 @@ typedef HANDLE pthread_mutex_t;
 | 
	
		
			
				|  |  |  typedef struct {
 | 
	
		
			
				|  |  |      HANDLE signal, broadcast;
 | 
	
		
			
				|  |  |  } pthread_cond_t;
 | 
	
		
			
				|  |  | -typedef DWORD pthread_t;
 | 
	
		
			
				|  |  | +typedef HANDLE pthread_t;
 | 
	
		
			
				|  |  |  #define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int pthread_mutex_lock(pthread_mutex_t *);
 | 
	
	
		
			
				|  | @@ -267,7 +267,7 @@ typedef int SOCKET;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #ifdef _WIN32
 | 
	
		
			
				|  |  |  static CRITICAL_SECTION global_log_file_lock;
 | 
	
		
			
				|  |  | -static pthread_t pthread_self(void)
 | 
	
		
			
				|  |  | +static DWORD pthread_self(void)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      return GetCurrentThreadId();
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -507,6 +507,8 @@ struct mg_request_handler_info {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  struct mg_context {
 | 
	
		
			
				|  |  |      volatile int stop_flag;         // Should we stop event loop
 | 
	
		
			
				|  |  | +    void *ssllib_dll_handle;        // Store the ssl library handle.
 | 
	
		
			
				|  |  | +    void *cryptolib_dll_handle;     // Store the crypto library handle.
 | 
	
		
			
				|  |  |      SSL_CTX *ssl_ctx;               // SSL context
 | 
	
		
			
				|  |  |      char *config[NUM_OPTIONS];      // Civetweb configuration parameters
 | 
	
		
			
				|  |  |      struct mg_callbacks callbacks;  // User-defined callback function
 | 
	
	
		
			
				|  | @@ -524,6 +526,9 @@ struct mg_context {
 | 
	
		
			
				|  |  |      volatile int sq_tail;      // Tail of the socket queue
 | 
	
		
			
				|  |  |      pthread_cond_t sq_full;    // Signaled when socket is produced
 | 
	
		
			
				|  |  |      pthread_cond_t sq_empty;   // Signaled when socket is consumed
 | 
	
		
			
				|  |  | +    pthread_t masterthreadid;  // The master thread ID.
 | 
	
		
			
				|  |  | +    int workerthreadcount;     // The amount of worker threads.
 | 
	
		
			
				|  |  | +    pthread_t *workerthreadids;// The worker thread IDs.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // linked list of uri handlers
 | 
	
		
			
				|  |  |      struct mg_request_handler_info *request_handlers;
 | 
	
	
		
			
				|  | @@ -655,7 +660,7 @@ void mg_cry(struct mg_connection *conn, const char *fmt, ...)
 | 
	
		
			
				|  |  |      time_t timestamp;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      va_start(ap, fmt);
 | 
	
		
			
				|  |  | -    (void) vsnprintf(buf, sizeof(buf), fmt, ap);
 | 
	
		
			
				|  |  | +    IGNORE_UNUSED_RESULT(vsnprintf(buf, sizeof(buf), fmt, ap));
 | 
	
		
			
				|  |  |      va_end(ap);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Do not lock when getting the callback value, here and below.
 | 
	
	
		
			
				|  | @@ -663,7 +668,7 @@ void mg_cry(struct mg_connection *conn, const char *fmt, ...)
 | 
	
		
			
				|  |  |      // same way string option can.
 | 
	
		
			
				|  |  |      if (conn->ctx->callbacks.log_message == NULL ||
 | 
	
		
			
				|  |  |          conn->ctx->callbacks.log_message(conn, buf) == 0) {
 | 
	
		
			
				|  |  | -        fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
 | 
	
		
			
				|  |  | +        fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
 | 
	
		
			
				|  |  |               fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          if (fp != NULL) {
 | 
	
	
		
			
				|  | @@ -1341,8 +1346,9 @@ static int poll(struct pollfd *pfd, int n, int milliseconds)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  #endif // HAVE_POLL
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void set_close_on_exec(SOCKET sock)
 | 
	
		
			
				|  |  | +static void set_close_on_exec(SOCKET sock, struct mg_connection *conn)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | +    (void) conn; /* Unused. */
 | 
	
		
			
				|  |  |      (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1351,6 +1357,50 @@ int mg_start_thread(mg_thread_func_t f, void *p)
 | 
	
		
			
				|  |  |      return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/* Start a thread storing the thread context. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static int mg_start_thread_with_id(unsigned (__stdcall *f)(void *), void *p,
 | 
	
		
			
				|  |  | +                                   pthread_t *threadidptr)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    uintptr_t uip;
 | 
	
		
			
				|  |  | +    HANDLE threadhandle;
 | 
	
		
			
				|  |  | +    int result;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    uip = _beginthreadex(NULL, 0, (unsigned (__stdcall *)(void *)) f, p, 0,
 | 
	
		
			
				|  |  | +                         NULL);
 | 
	
		
			
				|  |  | +    threadhandle = (HANDLE) uip;
 | 
	
		
			
				|  |  | +    if (threadidptr != NULL) {
 | 
	
		
			
				|  |  | +        *threadidptr = threadhandle;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    result = (threadhandle == NULL) ? -1 : 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/* Wait for a thread to finish. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static int mg_join_thread(pthread_t threadid)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    int result;
 | 
	
		
			
				|  |  | +    DWORD dwevent;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    result = -1;
 | 
	
		
			
				|  |  | +    dwevent = WaitForSingleObject(threadid, INFINITE);
 | 
	
		
			
				|  |  | +    if (dwevent == WAIT_FAILED) {
 | 
	
		
			
				|  |  | +        int err;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        err = GetLastError();
 | 
	
		
			
				|  |  | +        DEBUG_TRACE(("WaitForSingleObject() failed, error %d", err));
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +        if (dwevent == WAIT_OBJECT_0) {
 | 
	
		
			
				|  |  | +            CloseHandle(threadid);
 | 
	
		
			
				|  |  | +            result = 0;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  static HANDLE dlopen(const char *dll_name, int flags)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      wchar_t wbuf[PATH_MAX];
 | 
	
	
		
			
				|  | @@ -1359,6 +1409,19 @@ static HANDLE dlopen(const char *dll_name, int flags)
 | 
	
		
			
				|  |  |      return LoadLibraryW(wbuf);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +static int dlclose(void *handle)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    int result;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (FreeLibrary(handle) != 0) {
 | 
	
		
			
				|  |  | +        result = 0;
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +        result = -1;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  #if !defined(NO_CGI)
 | 
	
		
			
				|  |  |  #define SIGKILL 0
 | 
	
		
			
				|  |  |  static int kill(pid_t pid, int sig_num)
 | 
	
	
		
			
				|  | @@ -1473,9 +1536,12 @@ static int mg_stat(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |      return filep->membuf != NULL || filep->modification_time != (time_t) 0;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void set_close_on_exec(int fd)
 | 
	
		
			
				|  |  | +static void set_close_on_exec(int fd, struct mg_connection *conn)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -    fcntl(fd, F_SETFD, FD_CLOEXEC);
 | 
	
		
			
				|  |  | +    if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
 | 
	
		
			
				|  |  | +        mg_cry(conn, "%s: fcntl(F_SETFD FD_CLOEXEC) failed: %s",
 | 
	
		
			
				|  |  | +               __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  int mg_start_thread(mg_thread_func_t func, void *param)
 | 
	
	
		
			
				|  | @@ -1487,14 +1553,48 @@ int mg_start_thread(mg_thread_func_t func, void *param)
 | 
	
		
			
				|  |  |      (void) pthread_attr_init(&attr);
 | 
	
		
			
				|  |  |      (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#if USE_STACK_SIZE > 1
 | 
	
		
			
				|  |  | +#if defined(USE_STACK_SIZE) && USE_STACK_SIZE > 1
 | 
	
		
			
				|  |  |      // Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
 | 
	
		
			
				|  |  |      (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
 | 
	
		
			
				|  |  | -#endif
 | 
	
		
			
				|  |  | +#endif /* defined(USE_STACK_SIZE) && USE_STACK_SIZE > 1 */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    result = pthread_create(&thread_id, &attr, func, param);
 | 
	
		
			
				|  |  | +    pthread_attr_destroy(&attr);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/* Start a thread storing the thread context. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static int mg_start_thread_with_id(mg_thread_func_t func, void *param,
 | 
	
		
			
				|  |  | +                                   pthread_t *threadidptr)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    pthread_t thread_id;
 | 
	
		
			
				|  |  | +    pthread_attr_t attr;
 | 
	
		
			
				|  |  | +    int result;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    (void) pthread_attr_init(&attr);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#if defined(USE_STACK_SIZE) && USE_STACK_SIZE > 1
 | 
	
		
			
				|  |  | +    // Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
 | 
	
		
			
				|  |  | +    (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
 | 
	
		
			
				|  |  | +#endif /* defined(USE_STACK_SIZE) && USE_STACK_SIZE > 1 */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      result = pthread_create(&thread_id, &attr, func, param);
 | 
	
		
			
				|  |  |      pthread_attr_destroy(&attr);
 | 
	
		
			
				|  |  | +    if (threadidptr != NULL) {
 | 
	
		
			
				|  |  | +        *threadidptr = thread_id;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/* Wait for a thread to finish. */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +static int mg_join_thread(pthread_t threadid)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    int result;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    result = pthread_join(threadid, NULL);
 | 
	
		
			
				|  |  |      return result;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1732,6 +1832,7 @@ static int alloc_vprintf2(char **buf, const char *fmt, va_list ap)
 | 
	
		
			
				|  |  |          if (!*buf) break;
 | 
	
		
			
				|  |  |          va_copy(ap_copy, ap);
 | 
	
		
			
				|  |  |          len = vsnprintf(*buf, size, fmt, ap_copy);
 | 
	
		
			
				|  |  | +        va_end(ap_copy);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return len;
 | 
	
	
		
			
				|  | @@ -1752,24 +1853,29 @@ static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap)
 | 
	
		
			
				|  |  |      // On second pass, actually print the message.
 | 
	
		
			
				|  |  |      va_copy(ap_copy, ap);
 | 
	
		
			
				|  |  |      len = vsnprintf(NULL, 0, fmt, ap_copy);
 | 
	
		
			
				|  |  | +    va_end(ap_copy);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (len < 0) {
 | 
	
		
			
				|  |  |          // C runtime is not standard compliant, vsnprintf() returned -1.
 | 
	
		
			
				|  |  |          // Switch to alternative code path that uses incremental allocations.
 | 
	
		
			
				|  |  |          va_copy(ap_copy, ap);
 | 
	
		
			
				|  |  |          len = alloc_vprintf2(buf, fmt, ap);
 | 
	
		
			
				|  |  | +        va_end(ap_copy);
 | 
	
		
			
				|  |  |      } else if (len > (int) size &&
 | 
	
		
			
				|  |  |                 (size = len + 1) > 0 &&
 | 
	
		
			
				|  |  |                 (*buf = (char *) malloc(size)) == NULL) {
 | 
	
		
			
				|  |  |          len = -1;  // Allocation failed, mark failure
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |          va_copy(ap_copy, ap);
 | 
	
		
			
				|  |  | -        vsnprintf(*buf, size, fmt, ap_copy);
 | 
	
		
			
				|  |  | +        IGNORE_UNUSED_RESULT(vsnprintf(*buf, size, fmt, ap_copy));
 | 
	
		
			
				|  |  | +        va_end(ap_copy);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return len;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      char mem[MG_BUF_LEN], *buf = mem;
 | 
	
	
		
			
				|  | @@ -1788,8 +1894,13 @@ int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap)
 | 
	
		
			
				|  |  |  int mg_printf(struct mg_connection *conn, const char *fmt, ...)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      va_list ap;
 | 
	
		
			
				|  |  | +    int result;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      va_start(ap, fmt);
 | 
	
		
			
				|  |  | -    return mg_vprintf(conn, fmt, ap);
 | 
	
		
			
				|  |  | +    result = mg_vprintf(conn, fmt, ap);
 | 
	
		
			
				|  |  | +    va_end(ap);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    return result;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  int mg_url_decode(const char *src, int src_len, char *dst,
 | 
	
	
		
			
				|  | @@ -2046,7 +2157,8 @@ static time_t parse_date_string(const char *datetime)
 | 
	
		
			
				|  |  |          leap_days = num_leap_years(year) - num_leap_years(1970);
 | 
	
		
			
				|  |  |          year -= 1970;
 | 
	
		
			
				|  |  |          days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
 | 
	
		
			
				|  |  | -        result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
 | 
	
		
			
				|  |  | +        result = (time_t) days * 24 * 3600 + (time_t) hour * 3600 +
 | 
	
		
			
				|  |  | +                 minute * 60 + second;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return result;
 | 
	
	
		
			
				|  | @@ -2258,7 +2370,9 @@ static void open_auth_file(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |      } else if (mg_stat(conn, path, &file) && file.is_directory) {
 | 
	
		
			
				|  |  |          mg_snprintf(conn, name, sizeof(name), "%s%c%s",
 | 
	
		
			
				|  |  |                      path, '/', PASSWORDS_FILE_NAME);
 | 
	
		
			
				|  |  | -        mg_fopen(conn, name, "r", filep);
 | 
	
		
			
				|  |  | +        if (!mg_fopen(conn, name, "r", filep)) {
 | 
	
		
			
				|  |  | +            mg_cry(conn, "fopen(%s): %s", name, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |          // Try to find .htpasswd in requested directory.
 | 
	
		
			
				|  |  |          for (p = path, e = p + strlen(p) - 1; e > p; e--)
 | 
	
	
		
			
				|  | @@ -2266,7 +2380,9 @@ static void open_auth_file(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |                  break;
 | 
	
		
			
				|  |  |          mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",
 | 
	
		
			
				|  |  |                      (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
 | 
	
		
			
				|  |  | -        mg_fopen(conn, name, "r", filep);
 | 
	
		
			
				|  |  | +        if (!mg_fopen(conn, name, "r", filep)) {
 | 
	
		
			
				|  |  | +            mg_cry(conn, "fopen(%s): %s", name, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2510,16 +2626,16 @@ int mg_modify_passwords_file(const char *fname, const char *domain,
 | 
	
		
			
				|  |  |      fclose(fp2);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Put the temp file in place of real file
 | 
	
		
			
				|  |  | -    remove(fname);
 | 
	
		
			
				|  |  | +    IGNORE_UNUSED_RESULT(remove(fname));
 | 
	
		
			
				|  |  |      IGNORE_UNUSED_RESULT(rename(tmp, fname));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return 1;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static SOCKET conn2(const char *host, int port, int use_ssl,
 | 
	
		
			
				|  |  | -                    char *ebuf, size_t ebuf_len)
 | 
	
		
			
				|  |  | +static SOCKET conn2(struct mg_context *ctx, const char *host, int port,
 | 
	
		
			
				|  |  | +                    int use_ssl, char *ebuf, size_t ebuf_len)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -    struct sockaddr_in sin;
 | 
	
		
			
				|  |  | +    struct sockaddr_in sain;
 | 
	
		
			
				|  |  |      struct hostent *he;
 | 
	
		
			
				|  |  |      SOCKET sock = INVALID_SOCKET;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2533,11 +2649,12 @@ static SOCKET conn2(const char *host, int port, int use_ssl,
 | 
	
		
			
				|  |  |      } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
 | 
	
		
			
				|  |  |          snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -        set_close_on_exec(sock);
 | 
	
		
			
				|  |  | -        sin.sin_family = AF_INET;
 | 
	
		
			
				|  |  | -        sin.sin_port = htons((uint16_t) port);
 | 
	
		
			
				|  |  | -        sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
 | 
	
		
			
				|  |  | -        if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
 | 
	
		
			
				|  |  | +        set_close_on_exec(sock, fc(ctx));
 | 
	
		
			
				|  |  | +        memset(&sain, '\0', sizeof(sain));
 | 
	
		
			
				|  |  | +        sain.sin_family = AF_INET;
 | 
	
		
			
				|  |  | +        sain.sin_port = htons((uint16_t) port);
 | 
	
		
			
				|  |  | +        sain.sin_addr = * (struct in_addr *) he->h_addr_list[0];
 | 
	
		
			
				|  |  | +        if (connect(sock, (struct sockaddr *) &sain, sizeof(sain)) != 0) {
 | 
	
		
			
				|  |  |              snprintf(ebuf, ebuf_len, "connect(%s:%d): %s",
 | 
	
		
			
				|  |  |                       host, port, strerror(ERRNO));
 | 
	
		
			
				|  |  |              closesocket(sock);
 | 
	
	
		
			
				|  | @@ -2575,6 +2692,7 @@ int mg_url_encode(const char *src, char *dst, size_t dst_len)
 | 
	
		
			
				|  |  |  static void print_dir_entry(struct de *de)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      char size[64], mod[64], href[PATH_MAX];
 | 
	
		
			
				|  |  | +    struct tm *tm;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (de->file.is_directory) {
 | 
	
		
			
				|  |  |          mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]");
 | 
	
	
		
			
				|  | @@ -2594,8 +2712,13 @@ static void print_dir_entry(struct de *de)
 | 
	
		
			
				|  |  |                          "%.1fG", (double) de->file.size / 1073741824);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
 | 
	
		
			
				|  |  | -             localtime(&de->file.modification_time));
 | 
	
		
			
				|  |  | +    tm = localtime(&de->file.modification_time);
 | 
	
		
			
				|  |  | +    if (tm != NULL) {
 | 
	
		
			
				|  |  | +        strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", tm);
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +        strncpy(mod, "01-Jan-1970 00:00", sizeof(mod));
 | 
	
		
			
				|  |  | +        mod[sizeof(mod) - 1] = '\0';
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |      mg_url_encode(de->file_name, href, sizeof(href));
 | 
	
		
			
				|  |  |      de->conn->num_bytes_sent += mg_printf(de->conn,
 | 
	
		
			
				|  |  |                                            "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
 | 
	
	
		
			
				|  | @@ -2672,7 +2795,10 @@ static int scan_directory(struct mg_connection *conn, const char *dir,
 | 
	
		
			
				|  |  |              // fails. For more details, see
 | 
	
		
			
				|  |  |              // http://code.google.com/p/civetweb/issues/detail?id=79
 | 
	
		
			
				|  |  |              memset(&de.file, 0, sizeof(de.file));
 | 
	
		
			
				|  |  | -            mg_stat(conn, path, &de.file);
 | 
	
		
			
				|  |  | +            if (mg_stat(conn, path, &de.file) != 0) {
 | 
	
		
			
				|  |  | +                mg_cry(conn, "%s: mg_stat(%s) failed: %s",
 | 
	
		
			
				|  |  | +                       __func__, path, strerror(ERRNO));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              de.file_name = dp->d_name;
 | 
	
		
			
				|  |  |              cb(&de, data);
 | 
	
	
		
			
				|  | @@ -2709,7 +2835,10 @@ static int remove_directory(struct mg_connection *conn, const char *dir)
 | 
	
		
			
				|  |  |              // fails. For more details, see
 | 
	
		
			
				|  |  |              // http://code.google.com/p/civetweb/issues/detail?id=79
 | 
	
		
			
				|  |  |              memset(&de.file, 0, sizeof(de.file));
 | 
	
		
			
				|  |  | -            mg_stat(conn, path, &de.file);
 | 
	
		
			
				|  |  | +            if (mg_stat(conn, path, &de.file) != 0) {
 | 
	
		
			
				|  |  | +                mg_cry(conn, "%s: mg_stat(%s) failed: %s",
 | 
	
		
			
				|  |  | +                       __func__, path, strerror(ERRNO));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              if(de.file.modification_time) {
 | 
	
		
			
				|  |  |                  if(de.file.is_directory) {
 | 
	
		
			
				|  |  |                      remove_directory(conn, path);
 | 
	
	
		
			
				|  | @@ -2802,13 +2931,15 @@ static void handle_directory_request(struct mg_connection *conn,
 | 
	
		
			
				|  |  |                                        conn->request_info.uri, "..", "Parent directory", "-", "-");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Sort and print directory entries
 | 
	
		
			
				|  |  | -    qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
 | 
	
		
			
				|  |  | -          compare_dir_entries);
 | 
	
		
			
				|  |  | -    for (i = 0; i < data.num_entries; i++) {
 | 
	
		
			
				|  |  | -        print_dir_entry(&data.entries[i]);
 | 
	
		
			
				|  |  | -        free(data.entries[i].file_name);
 | 
	
		
			
				|  |  | +    if (data.entries != NULL) {
 | 
	
		
			
				|  |  | +        qsort(data.entries, (size_t) data.num_entries,
 | 
	
		
			
				|  |  | +              sizeof(data.entries[0]), compare_dir_entries);
 | 
	
		
			
				|  |  | +        for (i = 0; i < data.num_entries; i++) {
 | 
	
		
			
				|  |  | +            print_dir_entry(&data.entries[i]);
 | 
	
		
			
				|  |  | +            free(data.entries[i].file_name);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        free(data.entries);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    free(data.entries);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");
 | 
	
		
			
				|  |  |      conn->status_code = 200;
 | 
	
	
		
			
				|  | @@ -2830,7 +2961,10 @@ static void send_file_data(struct mg_connection *conn, struct file *filep,
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          mg_write(conn, filep->membuf + offset, (size_t) len);
 | 
	
		
			
				|  |  |      } else if (len > 0 && filep->fp != NULL) {
 | 
	
		
			
				|  |  | -        fseeko(filep->fp, offset, SEEK_SET);
 | 
	
		
			
				|  |  | +        if (fseeko(filep->fp, offset, SEEK_SET) != 0) {
 | 
	
		
			
				|  |  | +            mg_cry(conn, "%s: fseeko() failed: %s",
 | 
	
		
			
				|  |  | +                   __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          while (len > 0) {
 | 
	
		
			
				|  |  |              // Calculate how much to read from the file in the buffer
 | 
	
		
			
				|  |  |              to_read = sizeof(buf);
 | 
	
	
		
			
				|  | @@ -2862,7 +2996,15 @@ static int parse_range_header(const char *header, int64_t *a, int64_t *b)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void gmt_time_string(char *buf, size_t buf_len, time_t *t)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -    strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
 | 
	
		
			
				|  |  | +    struct tm *tm;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    tm = gmtime(t);
 | 
	
		
			
				|  |  | +    if (tm != NULL) {
 | 
	
		
			
				|  |  | +        strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", tm);
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +        strncpy(buf, "Thu, 01 Jan 1970 00:00:00 GMT", buf_len);
 | 
	
		
			
				|  |  | +        buf[buf_len - 1] = '\0';
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void construct_etag(char *buf, size_t buf_len,
 | 
	
	
		
			
				|  | @@ -2872,11 +3014,16 @@ static void construct_etag(char *buf, size_t buf_len,
 | 
	
		
			
				|  |  |               (unsigned long) filep->modification_time, filep->size);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void fclose_on_exec(struct file *filep)
 | 
	
		
			
				|  |  | +static void fclose_on_exec(struct file *filep, struct mg_connection *conn)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      if (filep != NULL && filep->fp != NULL) {
 | 
	
		
			
				|  |  | -#ifndef _WIN32
 | 
	
		
			
				|  |  | -        fcntl(fileno(filep->fp), F_SETFD, FD_CLOEXEC);
 | 
	
		
			
				|  |  | +#ifdef _WIN32
 | 
	
		
			
				|  |  | +        (void) conn; /* Unused. */
 | 
	
		
			
				|  |  | +#else
 | 
	
		
			
				|  |  | +        if (fcntl(fileno(filep->fp), F_SETFD, FD_CLOEXEC) != 0) {
 | 
	
		
			
				|  |  | +            mg_cry(conn, "%s: fcntl(F_SETFD FD_CLOEXEC) failed: %s",
 | 
	
		
			
				|  |  | +                   __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -2913,7 +3060,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |          return;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    fclose_on_exec(filep);
 | 
	
		
			
				|  |  | +    fclose_on_exec(filep, conn);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // If Range: header specified, act accordingly
 | 
	
		
			
				|  |  |      r1 = r2 = 0;
 | 
	
	
		
			
				|  | @@ -2921,9 +3068,10 @@ static void handle_file_request(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |      if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
 | 
	
		
			
				|  |  |          r1 >= 0 && r2 >= 0) {
 | 
	
		
			
				|  |  |          // actually, range requests don't play well with a pre-gzipped
 | 
	
		
			
				|  |  | -        // file (since the range is specified in the uncmpressed space)
 | 
	
		
			
				|  |  | +        // file (since the range is specified in the uncompressed space)
 | 
	
		
			
				|  |  |          if (filep->gzipped) {
 | 
	
		
			
				|  |  |              send_http_error(conn, 501, "Not Implemented", "range requests in gzipped files are not supported");
 | 
	
		
			
				|  |  | +            mg_fclose(filep);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          conn->status_code = 206;
 | 
	
	
		
			
				|  | @@ -3347,15 +3495,19 @@ static void prepare_cgi_environment(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | +    char *buf;
 | 
	
		
			
				|  |  | +    size_t buflen;
 | 
	
		
			
				|  |  |      int headers_len, data_len, i, fdin[2] = { 0, 0 }, fdout[2] = { 0, 0 };
 | 
	
		
			
				|  |  |      const char *status, *status_text;
 | 
	
		
			
				|  |  | -    char buf[MAX_REQUEST_SIZE], *pbuf, dir[PATH_MAX], *p;
 | 
	
		
			
				|  |  | +    char *pbuf, dir[PATH_MAX], *p;
 | 
	
		
			
				|  |  |      struct mg_request_info ri;
 | 
	
		
			
				|  |  |      struct cgi_env_block blk;
 | 
	
		
			
				|  |  |      FILE *in = NULL, *out = NULL;
 | 
	
		
			
				|  |  |      struct file fout = STRUCT_FILE_INITIALIZER;
 | 
	
		
			
				|  |  |      pid_t pid = (pid_t) -1;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    buf = NULL;
 | 
	
		
			
				|  |  | +    buflen = 16384;
 | 
	
		
			
				|  |  |      prepare_cgi_environment(conn, prog, &blk);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // CGI must be executed in its own directory. 'dir' must point to the
 | 
	
	
		
			
				|  | @@ -3383,10 +3535,10 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Make sure child closes all pipe descriptors. It must dup them to 0,1
 | 
	
		
			
				|  |  | -    set_close_on_exec(fdin[0]);
 | 
	
		
			
				|  |  | -    set_close_on_exec(fdin[1]);
 | 
	
		
			
				|  |  | -    set_close_on_exec(fdout[0]);
 | 
	
		
			
				|  |  | -    set_close_on_exec(fdout[1]);
 | 
	
		
			
				|  |  | +    set_close_on_exec(fdin[0], conn);
 | 
	
		
			
				|  |  | +    set_close_on_exec(fdin[1], conn);
 | 
	
		
			
				|  |  | +    set_close_on_exec(fdout[0], conn);
 | 
	
		
			
				|  |  | +    set_close_on_exec(fdout[1], conn);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Parent closes only one side of the pipes.
 | 
	
		
			
				|  |  |      // If we don't mark them as closed, close() attempt before
 | 
	
	
		
			
				|  | @@ -3424,12 +3576,19 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 | 
	
		
			
				|  |  |      // Do not send anything back to client, until we buffer in all
 | 
	
		
			
				|  |  |      // HTTP headers.
 | 
	
		
			
				|  |  |      data_len = 0;
 | 
	
		
			
				|  |  | -    headers_len = read_request(out, conn, buf, sizeof(buf), &data_len);
 | 
	
		
			
				|  |  | +    buf = malloc(buflen);
 | 
	
		
			
				|  |  | +    if (buf == NULL) {
 | 
	
		
			
				|  |  | +        send_http_error(conn, 500, http_500_error,
 | 
	
		
			
				|  |  | +                        "Not enough memory for buffer (%u bytes)",
 | 
	
		
			
				|  |  | +                        (unsigned int) buflen);
 | 
	
		
			
				|  |  | +        goto done;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    headers_len = read_request(out, conn, buf, buflen, &data_len);
 | 
	
		
			
				|  |  |      if (headers_len <= 0) {
 | 
	
		
			
				|  |  |          send_http_error(conn, 500, http_500_error,
 | 
	
		
			
				|  |  |                          "CGI program sent malformed or too big (>%u bytes) "
 | 
	
		
			
				|  |  |                          "HTTP headers: [%.*s]",
 | 
	
		
			
				|  |  | -                        (unsigned) sizeof(buf), data_len, buf);
 | 
	
		
			
				|  |  | +                        (unsigned) buflen, data_len, buf);
 | 
	
		
			
				|  |  |          goto done;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      pbuf = buf;
 | 
	
	
		
			
				|  | @@ -3498,6 +3657,9 @@ done:
 | 
	
		
			
				|  |  |      } else if (fdout[0] != -1) {
 | 
	
		
			
				|  |  |          close(fdout[0]);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    if (buf != NULL) {
 | 
	
		
			
				|  |  | +        free(buf);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  #endif // !NO_CGI
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -3541,7 +3703,10 @@ static void mkcol(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |      int rc, body_len;
 | 
	
		
			
				|  |  |      struct de de;
 | 
	
		
			
				|  |  |      memset(&de.file, 0, sizeof(de.file));
 | 
	
		
			
				|  |  | -    mg_stat(conn, path, &de.file);
 | 
	
		
			
				|  |  | +    if (mg_stat(conn, path, &de.file) != 0) {
 | 
	
		
			
				|  |  | +        mg_cry(conn, "%s: mg_stat(%s) failed: %s",
 | 
	
		
			
				|  |  | +               __func__, path, strerror(ERRNO));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if(de.file.modification_time) {
 | 
	
		
			
				|  |  |          send_http_error(conn, 405, "Method Not Allowed",
 | 
	
	
		
			
				|  | @@ -3596,7 +3761,7 @@ static void put_file(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |          send_http_error(conn, 500, http_500_error,
 | 
	
		
			
				|  |  |                          "fopen(%s): %s", path, strerror(ERRNO));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -        fclose_on_exec(&file);
 | 
	
		
			
				|  |  | +        fclose_on_exec(&file, conn);
 | 
	
		
			
				|  |  |          range = mg_get_header(conn, "Content-Range");
 | 
	
		
			
				|  |  |          r1 = r2 = 0;
 | 
	
		
			
				|  |  |          if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
 | 
	
	
		
			
				|  | @@ -3649,7 +3814,7 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi,
 | 
	
		
			
				|  |  |          mg_cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
 | 
	
		
			
				|  |  |                 tag, path, strerror(ERRNO));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -        fclose_on_exec(&file);
 | 
	
		
			
				|  |  | +        fclose_on_exec(&file, conn);
 | 
	
		
			
				|  |  |          if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
 | 
	
		
			
				|  |  |                           (int)strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) {
 | 
	
		
			
				|  |  |              send_ssi_file(conn, path, &file, include_level + 1);
 | 
	
	
		
			
				|  | @@ -3762,7 +3927,7 @@ static void handle_ssi_file_request(struct mg_connection *conn,
 | 
	
		
			
				|  |  |                          strerror(ERRNO));
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |          conn->must_close = 1;
 | 
	
		
			
				|  |  | -        fclose_on_exec(&file);
 | 
	
		
			
				|  |  | +        fclose_on_exec(&file, conn);
 | 
	
		
			
				|  |  |          mg_printf(conn, "HTTP/1.1 200 OK\r\n"
 | 
	
		
			
				|  |  |                    "Content-Type: text/html\r\nConnection: %s\r\n\r\n",
 | 
	
		
			
				|  |  |                    suggest_connection_header(conn));
 | 
	
	
		
			
				|  | @@ -4476,22 +4641,33 @@ static int is_put_or_delete_request(const struct mg_connection *conn)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int get_first_ssl_listener_index(const struct mg_context *ctx)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -    int i, index = -1;
 | 
	
		
			
				|  |  | -    for (i = 0; index == -1 && i < ctx->num_listening_sockets; i++) {
 | 
	
		
			
				|  |  | -        index = ctx->listening_sockets[i].is_ssl ? i : -1;
 | 
	
		
			
				|  |  | +    int i, idx = -1;
 | 
	
		
			
				|  |  | +    for (i = 0; idx == -1 && i < ctx->num_listening_sockets; i++) {
 | 
	
		
			
				|  |  | +        idx = ctx->listening_sockets[i].is_ssl ? i : -1;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    return index;
 | 
	
		
			
				|  |  | +    return idx;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void redirect_to_https_port(struct mg_connection *conn, int ssl_index)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      char host[1025];
 | 
	
		
			
				|  |  |      const char *host_header;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if ((host_header = mg_get_header(conn, "Host")) == NULL ||
 | 
	
		
			
				|  |  | -        sscanf(host_header, "%1024[^:]", host) == 0) {
 | 
	
		
			
				|  |  | +    size_t hostlen;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    host_header = mg_get_header(conn, "Host");
 | 
	
		
			
				|  |  | +    hostlen = sizeof(host);
 | 
	
		
			
				|  |  | +    if (host_header != NULL) {
 | 
	
		
			
				|  |  | +        char *pos;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        strncpy(host, host_header, hostlen);
 | 
	
		
			
				|  |  | +        host[hostlen - 1] = '\0';
 | 
	
		
			
				|  |  | +        pos = strchr(host, ':');
 | 
	
		
			
				|  |  | +        if (pos != NULL) {
 | 
	
		
			
				|  |  | +            *pos = '\0';
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  |          // Cannot get host from the Host: header. Fallback to our IP address.
 | 
	
		
			
				|  |  | -        sockaddr_to_string(host, sizeof(host), &conn->client.lsa);
 | 
	
		
			
				|  |  | +        sockaddr_to_string(host, hostlen, &conn->client.lsa);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      mg_printf(conn, "HTTP/1.1 302 Found\r\nLocation: https://%s:%d%s\r\n\r\n",
 | 
	
	
		
			
				|  | @@ -4610,6 +4786,7 @@ static void handle_request(struct mg_connection *conn)
 | 
	
		
			
				|  |  |      uri_len = (int) strlen(ri->uri);
 | 
	
		
			
				|  |  |      mg_url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0);
 | 
	
		
			
				|  |  |      remove_double_dots_and_double_slashes((char *) ri->uri);
 | 
	
		
			
				|  |  | +    path[0] = '\0';
 | 
	
		
			
				|  |  |      convert_uri_to_file_name(conn, path, sizeof(path), &file);
 | 
	
		
			
				|  |  |      conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
 | 
	
		
			
				|  |  |                                    get_remote_ip(conn), ri->uri);
 | 
	
	
		
			
				|  | @@ -4720,7 +4897,7 @@ static void close_all_listening_sockets(struct mg_context *ctx)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static int is_valid_port(unsigned int port)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -    return port > 0 && port < 0xffff;
 | 
	
		
			
				|  |  | +    return port < 0xffff;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // Valid listening port specification is: [ip_address:]port[s]
 | 
	
	
		
			
				|  | @@ -4801,7 +4978,9 @@ static int set_ports_option(struct mg_context *ctx)
 | 
	
		
			
				|  |  |                     listen(so.sock, SOMAXCONN) != 0) {
 | 
	
		
			
				|  |  |              mg_cry(fc(ctx), "%s: cannot bind to %.*s: %d (%s)", __func__,
 | 
	
		
			
				|  |  |                     (int) vec.len, vec.ptr, ERRNO, strerror(errno));
 | 
	
		
			
				|  |  | -            closesocket(so.sock);
 | 
	
		
			
				|  |  | +            if (so.sock != INVALID_SOCKET) {
 | 
	
		
			
				|  |  | +                closesocket(so.sock);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              success = 0;
 | 
	
		
			
				|  |  |          } else if ((ptr = (struct socket *) realloc(ctx->listening_sockets,
 | 
	
		
			
				|  |  |                            (ctx->num_listening_sockets + 1) *
 | 
	
	
		
			
				|  | @@ -4809,7 +4988,7 @@ static int set_ports_option(struct mg_context *ctx)
 | 
	
		
			
				|  |  |              closesocket(so.sock);
 | 
	
		
			
				|  |  |              success = 0;
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  | -            set_close_on_exec(so.sock);
 | 
	
		
			
				|  |  | +            set_close_on_exec(so.sock, fc(ctx));
 | 
	
		
			
				|  |  |              ctx->listening_sockets = ptr;
 | 
	
		
			
				|  |  |              ctx->listening_sockets[ctx->num_listening_sockets] = so;
 | 
	
		
			
				|  |  |              ctx->num_listening_sockets++;
 | 
	
	
		
			
				|  | @@ -4840,6 +5019,7 @@ static void log_access(const struct mg_connection *conn)
 | 
	
		
			
				|  |  |      const struct mg_request_info *ri;
 | 
	
		
			
				|  |  |      FILE *fp;
 | 
	
		
			
				|  |  |      char date[64], src_addr[IP_ADDR_STR_LEN];
 | 
	
		
			
				|  |  | +    struct tm *tm;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ?  NULL :
 | 
	
		
			
				|  |  |           fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
 | 
	
	
		
			
				|  | @@ -4847,8 +5027,13 @@ static void log_access(const struct mg_connection *conn)
 | 
	
		
			
				|  |  |      if (fp == NULL)
 | 
	
		
			
				|  |  |          return;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
 | 
	
		
			
				|  |  | -             localtime(&conn->birth_time));
 | 
	
		
			
				|  |  | +    tm = localtime(&conn->birth_time);
 | 
	
		
			
				|  |  | +    if (tm != NULL) {
 | 
	
		
			
				|  |  | +        strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z", tm);
 | 
	
		
			
				|  |  | +    } else {
 | 
	
		
			
				|  |  | +        strncpy(date, "01/Jan/1970:00:00:00 +0000", sizeof(date));
 | 
	
		
			
				|  |  | +        date[sizeof(date) - 1] = '\0';
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      ri = &conn->request_info;
 | 
	
		
			
				|  |  |      flockfile(fp);
 | 
	
	
		
			
				|  | @@ -4958,8 +5143,8 @@ static unsigned long ssl_id_callback(void)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if !defined(NO_SSL_DL)
 | 
	
		
			
				|  |  | -static int load_dll(struct mg_context *ctx, const char *dll_name,
 | 
	
		
			
				|  |  | -                    struct ssl_func *sw)
 | 
	
		
			
				|  |  | +static void *load_dll(struct mg_context *ctx, const char *dll_name,
 | 
	
		
			
				|  |  | +                      struct ssl_func *sw)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      union {
 | 
	
		
			
				|  |  |          void *p;
 | 
	
	
		
			
				|  | @@ -4970,7 +5155,7 @@ static int load_dll(struct mg_context *ctx, const char *dll_name,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
 | 
	
		
			
				|  |  |          mg_cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
 | 
	
		
			
				|  |  | -        return 0;
 | 
	
		
			
				|  |  | +        return NULL;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      for (fp = sw; fp->name != NULL; fp++) {
 | 
	
	
		
			
				|  | @@ -4984,13 +5169,14 @@ static int load_dll(struct mg_context *ctx, const char *dll_name,
 | 
	
		
			
				|  |  |  #endif // _WIN32
 | 
	
		
			
				|  |  |          if (u.fp == NULL) {
 | 
	
		
			
				|  |  |              mg_cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);
 | 
	
		
			
				|  |  | -            return 0;
 | 
	
		
			
				|  |  | +            dlclose(dll_handle);
 | 
	
		
			
				|  |  | +            return NULL;
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  |              fp->ptr = u.fp;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    return 1;
 | 
	
		
			
				|  |  | +    return dll_handle;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  #endif // NO_SSL_DL
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -5008,8 +5194,9 @@ static int set_ssl_option(struct mg_context *ctx)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if !defined(NO_SSL_DL)
 | 
	
		
			
				|  |  | -    if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
 | 
	
		
			
				|  |  | -        !load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
 | 
	
		
			
				|  |  | +    ctx->ssllib_dll_handle = load_dll(ctx, SSL_LIB, ssl_sw);
 | 
	
		
			
				|  |  | +    ctx->cryptolib_dll_handle = load_dll(ctx, CRYPTO_LIB, crypto_sw);
 | 
	
		
			
				|  |  | +    if (!ctx->ssllib_dll_handle || !ctx->cryptolib_dll_handle) {
 | 
	
		
			
				|  |  |          return 0;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  #endif // NO_SSL_DL
 | 
	
	
		
			
				|  | @@ -5105,8 +5292,11 @@ static void close_socket_gracefully(struct mg_connection *conn)
 | 
	
		
			
				|  |  |      // ephemeral port exhaust problem under high QPS.
 | 
	
		
			
				|  |  |      linger.l_onoff = 1;
 | 
	
		
			
				|  |  |      linger.l_linger = 1;
 | 
	
		
			
				|  |  | -    setsockopt(conn->client.sock, SOL_SOCKET, SO_LINGER,
 | 
	
		
			
				|  |  | -               (char *) &linger, sizeof(linger));
 | 
	
		
			
				|  |  | +    if (setsockopt(conn->client.sock, SOL_SOCKET, SO_LINGER,
 | 
	
		
			
				|  |  | +                   (char *) &linger, sizeof(linger)) != 0) {
 | 
	
		
			
				|  |  | +        mg_cry(conn, "%s: setsockopt(SOL_SOCKET SO_LINGER) failed: %s",
 | 
	
		
			
				|  |  | +               __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Send FIN to the client
 | 
	
		
			
				|  |  |      shutdown(conn->client.sock, SHUT_WR);
 | 
	
	
		
			
				|  | @@ -5161,13 +5351,17 @@ void mg_close_connection(struct mg_connection *conn)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
 | 
	
		
			
				|  |  | +                                 char *ebuf, size_t ebuf_len);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
 | 
	
		
			
				|  |  |                                   char *ebuf, size_t ebuf_len)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      static struct mg_context fake_ctx;
 | 
	
		
			
				|  |  |      struct mg_connection *conn = NULL;
 | 
	
		
			
				|  |  |      SOCKET sock;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    if ((sock = conn2(host, port, use_ssl, ebuf, ebuf_len)) == INVALID_SOCKET) {
 | 
	
		
			
				|  |  | +    if ((sock = conn2(&fake_ctx, host, port, use_ssl, ebuf,
 | 
	
		
			
				|  |  | +                      ebuf_len)) == INVALID_SOCKET) {
 | 
	
		
			
				|  |  |      } else if ((conn = (struct mg_connection *)
 | 
	
		
			
				|  |  |                         calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
 | 
	
		
			
				|  |  |          snprintf(ebuf, ebuf_len, "calloc(): %s", strerror(ERRNO));
 | 
	
	
		
			
				|  | @@ -5186,7 +5380,10 @@ struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
 | 
	
		
			
				|  |  |          conn->buf = (char *) (conn + 1);
 | 
	
		
			
				|  |  |          conn->ctx = &fake_ctx;
 | 
	
		
			
				|  |  |          conn->client.sock = sock;
 | 
	
		
			
				|  |  | -        getsockname(sock, &conn->client.rsa.sa, &len);
 | 
	
		
			
				|  |  | +        if (getsockname(sock, &conn->client.rsa.sa, &len) != 0) {
 | 
	
		
			
				|  |  | +            mg_cry(conn, "%s: getsockname() failed: %s",
 | 
	
		
			
				|  |  | +                   __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          conn->client.is_ssl = use_ssl;
 | 
	
		
			
				|  |  |          (void) pthread_mutex_init(&conn->mutex, NULL);
 | 
	
		
			
				|  |  |  #ifndef NO_SSL
 | 
	
	
		
			
				|  | @@ -5260,6 +5457,7 @@ struct mg_connection *mg_download(const char *host, int port, int use_ssl,
 | 
	
		
			
				|  |  |          mg_close_connection(conn);
 | 
	
		
			
				|  |  |          conn = NULL;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    va_end(ap);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return conn;
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -5353,7 +5551,7 @@ static int consume_socket(struct mg_context *ctx, struct socket *sp)
 | 
	
		
			
				|  |  |      return !ctx->stop_flag;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void *worker_thread(void *thread_func_param)
 | 
	
		
			
				|  |  | +static void *worker_thread_run(void *thread_func_param)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      struct mg_context *ctx = (struct mg_context *) thread_func_param;
 | 
	
		
			
				|  |  |      struct mg_connection *conn;
 | 
	
	
		
			
				|  | @@ -5409,6 +5607,22 @@ static void *worker_thread(void *thread_func_param)
 | 
	
		
			
				|  |  |      return NULL;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/* Threads have different return types on Windows and Unix. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifdef _WIN32
 | 
	
		
			
				|  |  | +static unsigned __stdcall worker_thread(void *thread_func_param)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    worker_thread_run(thread_func_param);
 | 
	
		
			
				|  |  | +    return 0;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +#else
 | 
	
		
			
				|  |  | +static void *worker_thread(void *thread_func_param)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    worker_thread_run(thread_func_param);
 | 
	
		
			
				|  |  | +    return NULL;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +#endif /* _WIN32 */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // Master thread adds accepted socket to a queue
 | 
	
		
			
				|  |  |  static void produce_socket(struct mg_context *ctx, const struct socket *sp)
 | 
	
		
			
				|  |  |  {
 | 
	
	
		
			
				|  | @@ -5460,27 +5674,36 @@ static void accept_new_connection(const struct socket *listener,
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |          // Put so socket structure into the queue
 | 
	
		
			
				|  |  |          DEBUG_TRACE(("Accepted socket %d", (int) so.sock));
 | 
	
		
			
				|  |  | -        set_close_on_exec(so.sock);
 | 
	
		
			
				|  |  | +        set_close_on_exec(so.sock, fc(ctx));
 | 
	
		
			
				|  |  |          so.is_ssl = listener->is_ssl;
 | 
	
		
			
				|  |  |          so.ssl_redir = listener->ssl_redir;
 | 
	
		
			
				|  |  | -        getsockname(so.sock, &so.lsa.sa, &len);
 | 
	
		
			
				|  |  | +        if (getsockname(so.sock, &so.lsa.sa, &len) != 0) {
 | 
	
		
			
				|  |  | +            mg_cry(fc(ctx), "%s: getsockname() failed: %s",
 | 
	
		
			
				|  |  | +                   __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          // Set TCP keep-alive. This is needed because if HTTP-level keep-alive
 | 
	
		
			
				|  |  |          // is enabled, and client resets the connection, server won't get
 | 
	
		
			
				|  |  |          // TCP FIN or RST and will keep the connection open forever. With TCP
 | 
	
		
			
				|  |  |          // keep-alive, next keep-alive handshake will figure out that the client
 | 
	
		
			
				|  |  |          // is down and will close the server end.
 | 
	
		
			
				|  |  |          // Thanks to Igor Klopov who suggested the patch.
 | 
	
		
			
				|  |  | -        setsockopt(so.sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &on, sizeof(on));
 | 
	
		
			
				|  |  | +        if (setsockopt(so.sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &on,
 | 
	
		
			
				|  |  | +                       sizeof(on)) != 0) {
 | 
	
		
			
				|  |  | +            mg_cry(fc(ctx),
 | 
	
		
			
				|  |  | +                   "%s: setsockopt(SOL_SOCKET SO_KEEPALIVE) failed: %s",
 | 
	
		
			
				|  |  | +                   __func__, strerror(ERRNO));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          set_sock_timeout(so.sock, atoi(ctx->config[REQUEST_TIMEOUT]));
 | 
	
		
			
				|  |  |          produce_socket(ctx, &so);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static void *master_thread(void *thread_func_param)
 | 
	
		
			
				|  |  | +static void master_thread_run(void *thread_func_param)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      struct mg_context *ctx = (struct mg_context *) thread_func_param;
 | 
	
		
			
				|  |  |      struct pollfd *pfd;
 | 
	
		
			
				|  |  |      int i;
 | 
	
		
			
				|  |  | +    int workerthreadcount;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Increase priority of the master thread
 | 
	
		
			
				|  |  |  #if defined(_WIN32)
 | 
	
	
		
			
				|  | @@ -5528,6 +5751,12 @@ static void *master_thread(void *thread_func_param)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      (void) pthread_mutex_unlock(&ctx->mutex);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // Join all worker threads to avoid leaking threads.
 | 
	
		
			
				|  |  | +    workerthreadcount = ctx->workerthreadcount;
 | 
	
		
			
				|  |  | +    for (i = 0; i < workerthreadcount; i++) {
 | 
	
		
			
				|  |  | +        mg_join_thread(ctx->workerthreadids[i]);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // All threads exited, no sync is needed. Destroy mutex and condvars
 | 
	
		
			
				|  |  |      (void) pthread_mutex_destroy(&ctx->mutex);
 | 
	
		
			
				|  |  |      (void) pthread_cond_destroy(&ctx->cond);
 | 
	
	
		
			
				|  | @@ -5543,8 +5772,23 @@ static void *master_thread(void *thread_func_param)
 | 
	
		
			
				|  |  |      // WARNING: This must be the very last thing this
 | 
	
		
			
				|  |  |      // thread does, as ctx becomes invalid after this line.
 | 
	
		
			
				|  |  |      ctx->stop_flag = 2;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/* Threads have different return types on Windows and Unix. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#ifdef _WIN32
 | 
	
		
			
				|  |  | +static unsigned __stdcall master_thread(void *thread_func_param)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    master_thread_run(thread_func_param);
 | 
	
		
			
				|  |  | +    return 0;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +#else
 | 
	
		
			
				|  |  | +static void *master_thread(void *thread_func_param)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    master_thread_run(thread_func_param);
 | 
	
		
			
				|  |  |      return NULL;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +#endif /* _WIN32 */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void free_context(struct mg_context *ctx)
 | 
	
		
			
				|  |  |  {
 | 
	
	
		
			
				|  | @@ -5582,6 +5826,11 @@ static void free_context(struct mg_context *ctx)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  #endif // !NO_SSL
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // Deallocate worker thread ID array
 | 
	
		
			
				|  |  | +    if (ctx->workerthreadids != NULL) {
 | 
	
		
			
				|  |  | +        free(ctx->workerthreadids);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // Deallocate context itself
 | 
	
		
			
				|  |  |      free(ctx);
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -5594,6 +5843,7 @@ void mg_stop(struct mg_context *ctx)
 | 
	
		
			
				|  |  |      while (ctx->stop_flag != 2) {
 | 
	
		
			
				|  |  |          (void) mg_sleep(10);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +    mg_join_thread(ctx->masterthreadid);
 | 
	
		
			
				|  |  |      free_context(ctx);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if defined(_WIN32) && !defined(__SYMBIAN32__)
 | 
	
	
		
			
				|  | @@ -5608,6 +5858,7 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
 | 
	
		
			
				|  |  |      struct mg_context *ctx;
 | 
	
		
			
				|  |  |      const char *name, *value, *default_value;
 | 
	
		
			
				|  |  |      int i;
 | 
	
		
			
				|  |  | +    int workerthreadcount;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if defined(_WIN32) && !defined(__SYMBIAN32__)
 | 
	
		
			
				|  |  |      WSADATA data;
 | 
	
	
		
			
				|  | @@ -5677,15 +5928,31 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
 | 
	
		
			
				|  |  |      (void) pthread_cond_init(&ctx->sq_empty, NULL);
 | 
	
		
			
				|  |  |      (void) pthread_cond_init(&ctx->sq_full, NULL);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    workerthreadcount = atoi(ctx->config[NUM_THREADS]);
 | 
	
		
			
				|  |  | +    if (workerthreadcount > 0) {
 | 
	
		
			
				|  |  | +        ctx->workerthreadcount = workerthreadcount;
 | 
	
		
			
				|  |  | +        ctx->workerthreadids = calloc(workerthreadcount, sizeof(pthread_t));
 | 
	
		
			
				|  |  | +        if (ctx->workerthreadids == NULL) {
 | 
	
		
			
				|  |  | +            mg_cry(fc(ctx), "Not enough memory for worker thread ID array");
 | 
	
		
			
				|  |  | +            free_context(ctx);
 | 
	
		
			
				|  |  | +            return NULL;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // Start master (listening) thread
 | 
	
		
			
				|  |  | -    mg_start_thread(master_thread, ctx);
 | 
	
		
			
				|  |  | +    mg_start_thread_with_id(master_thread, ctx, &ctx->masterthreadid);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // Start worker threads
 | 
	
		
			
				|  |  | -    for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
 | 
	
		
			
				|  |  | -        if (mg_start_thread(worker_thread, ctx) != 0) {
 | 
	
		
			
				|  |  | +    for (i = 0; i < workerthreadcount; i++) {
 | 
	
		
			
				|  |  | +        (void) pthread_mutex_lock(&ctx->mutex);
 | 
	
		
			
				|  |  | +        ctx->num_threads++;
 | 
	
		
			
				|  |  | +        (void) pthread_mutex_unlock(&ctx->mutex);
 | 
	
		
			
				|  |  | +        if (mg_start_thread_with_id(worker_thread, ctx,
 | 
	
		
			
				|  |  | +                                    &ctx->workerthreadids[i]) != 0) {
 | 
	
		
			
				|  |  | +            (void) pthread_mutex_lock(&ctx->mutex);
 | 
	
		
			
				|  |  | +            ctx->num_threads--;
 | 
	
		
			
				|  |  | +            (void) pthread_mutex_unlock(&ctx->mutex);
 | 
	
		
			
				|  |  |              mg_cry(fc(ctx), "Cannot start worker thread: %ld", (long) ERRNO);
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            ctx->num_threads++;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 |