Skip to content
Snippets Groups Projects
mongoose.c 171 KiB
Newer Older
Sergey Lyubka's avatar
Sergey Lyubka committed
}

static void *worker_thread(void *thread_func_param) {
  struct mg_context *ctx = (struct mg_context *) thread_func_param;
  struct mg_connection *conn;

  conn = (struct mg_connection *) calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE);
  if (conn == NULL) {
    cry(fc(ctx), "%s", "Cannot create new connection struct, OOM");
  } else {
    conn->buf_size = MAX_REQUEST_SIZE;
    conn->buf = (char *) (conn + 1);
    conn->ctx = ctx;
    conn->event.user_data = ctx->user_data;

    call_user(MG_THREAD_BEGIN, conn, NULL);

    // Call consume_socket() even when ctx->stop_flag > 0, to let it signal
    // sq_empty condvar to wake up the master waiting in produce_socket()
    while (consume_socket(ctx, &conn->client)) {
      conn->birth_time = time(NULL);

      // Fill in IP, port info early so even if SSL setup below fails,
      // error handler would have the corresponding info.
      // Thanks to Johannes Winkelmann for the patch.
      // TODO(lsm): Fix IPv6 case
      conn->request_info.remote_port = ntohs(conn->client.rsa.sin.sin_port);
      memcpy(&conn->request_info.remote_ip,
             &conn->client.rsa.sin.sin_addr.s_addr, 4);
      conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip);
      conn->request_info.is_ssl = conn->client.is_ssl;

      if (!conn->client.is_ssl
#ifndef NO_SSL
          || sslize(conn, conn->ctx->ssl_ctx, SSL_accept)
#endif
         ) {
        process_new_connection(conn);
      }

      close_connection(conn);
    }
    call_user(MG_THREAD_END, conn, NULL);
    free(conn);
  }

  // Signal master that we're done with connection and exiting
  send(ctx->ctl[1], "x", 1, 0);
Sergey Lyubka's avatar
Sergey Lyubka committed

  DEBUG_TRACE(("exiting"));
  return NULL;
}

// Master thread adds accepted socket to a queue
static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
  send(ctx->ctl[0], (void *) sp, sizeof(*sp), 0);
Sergey Lyubka's avatar
Sergey Lyubka committed
}

static int set_sock_timeout(SOCKET sock, int milliseconds) {
#ifdef _WIN32
  DWORD t = milliseconds;
#else
  struct timeval t;
  t.tv_sec = milliseconds / 1000;
  t.tv_usec = (milliseconds * 1000) % 1000000;
#endif
  return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *) &t, sizeof(t)) ||
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (void *) &t, sizeof(t));
}

static void accept_new_connection(const struct socket *listener,
                                  struct mg_context *ctx) {
  struct socket so;
  char src_addr[IP_ADDR_STR_LEN];
  socklen_t len = sizeof(so.rsa);
  int on = 1;

  if ((so.sock = accept(listener->sock, &so.rsa.sa, &len)) == INVALID_SOCKET) {
  } else if (!check_acl(ctx, ntohl(* (uint32_t *) &so.rsa.sin.sin_addr))) {
    sockaddr_to_string(src_addr, sizeof(src_addr), &so.rsa);
    cry(fc(ctx), "%s: %s is not allowed to connect", __func__, src_addr);
    closesocket(so.sock);
  } else {
    // Put so socket structure into the queue
    DEBUG_TRACE(("Accepted socket %d", (int) so.sock));
    set_close_on_exec(so.sock);
    so.is_ssl = listener->is_ssl;
    so.ssl_redir = listener->ssl_redir;
    getsockname(so.sock, &so.lsa.sa, &len);
    // 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));
    set_sock_timeout(so.sock, atoi(ctx->config[REQUEST_TIMEOUT]));
    produce_socket(ctx, &so);
  }
}

static void *master_thread(void *thread_func_param) {
  struct mg_context *ctx = (struct mg_context *) thread_func_param;
  struct pollfd *pfd;
  int i;

  // Increase priority of the master thread
#if defined(_WIN32)
  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
#endif

#if defined(ISSUE_317)
  struct sched_param sched_param;
  sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
  pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
#endif

  call_user(MG_THREAD_BEGIN, fc(ctx), NULL);

  pfd = (struct pollfd *) calloc(ctx->num_listening_sockets, sizeof(pfd[0]));
  while (pfd != NULL && ctx->stop_flag == 0) {
    for (i = 0; i < ctx->num_listening_sockets; i++) {
      pfd[i].fd = ctx->listening_sockets[i].sock;
      pfd[i].events = POLLIN;
    }

    if (poll(pfd, ctx->num_listening_sockets, 200) > 0) {
      for (i = 0; i < ctx->num_listening_sockets; i++) {
        // NOTE(lsm): on QNX, poll() returns POLLRDNORM after the
        // successfull poll, and POLLIN is defined as (POLLRDNORM | POLLRDBAND)
        // Therefore, we're checking pfd[i].revents & POLLIN, not
        // pfd[i].revents == POLLIN.
        if (ctx->stop_flag == 0 && (pfd[i].revents & POLLIN)) {
          accept_new_connection(&ctx->listening_sockets[i], ctx);
        }
      }
    }
  }
  free(pfd);
  DEBUG_TRACE(("stopping workers"));

  // Stop signal received: somebody called mg_stop. Quit.
  close_all_listening_sockets(ctx);

  // Wakeup workers that are waiting for connections to handle.
  for (i = 0; i < ctx->num_threads; i++) {
    struct socket dummy;
    send(ctx->ctl[0], (void *) &dummy, sizeof(dummy), 0);
  }
Sergey Lyubka's avatar
Sergey Lyubka committed

  // Wait until all threads finish
  for (i = 0; i < ctx->num_threads; i++) {
    char ch;
    recv(ctx->ctl[0], &ch, 1, 0);
Sergey Lyubka's avatar
Sergey Lyubka committed
  }

#if !defined(NO_SSL)
  uninitialize_ssl(ctx);
#endif
  DEBUG_TRACE(("exiting"));

  call_user(MG_THREAD_END, fc(ctx), NULL);

  // Signal mg_stop() that we're done.
  // WARNING: This must be the very last thing this
  // thread does, as ctx becomes invalid after this line.
  ctx->stop_flag = 2;
  return NULL;
}

static void free_context(struct mg_context *ctx) {
  int i;

  // Deallocate config parameters
  for (i = 0; i < NUM_OPTIONS; i++) {
    if (ctx->config[i] != NULL)
      free(ctx->config[i]);
  }

#ifndef NO_SSL
  // Deallocate SSL context
  if (ctx->ssl_ctx != NULL) {
    SSL_CTX_free(ctx->ssl_ctx);
  }
  if (ssl_mutexes != NULL) {
    free(ssl_mutexes);
    ssl_mutexes = NULL;
  }
#endif // !NO_SSL

  // Deallocate context itself
  free(ctx);
}

void mg_stop(struct mg_context *ctx) {
  ctx->stop_flag = 1;

  // Wait until mg_fini() stops
  while (ctx->stop_flag != 2) {
    (void) mg_sleep(10);
  }
  free_context(ctx);

#if defined(_WIN32) && !defined(__SYMBIAN32__)
  (void) WSACleanup();
#endif // _WIN32
}

struct mg_context *mg_start(const char **options,
                            mg_event_handler_t func,
                            void *user_data) {
  struct mg_context *ctx;
  const char *name, *value, *default_value;
  int i;

#if defined(_WIN32) && !defined(__SYMBIAN32__)
  WSADATA data;
  WSAStartup(MAKEWORD(2,2), &data);
#endif // _WIN32

  // Allocate context and initialize reasonable general case defaults.
  // TODO(lsm): do proper error handling here.
  if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
    return NULL;
  }
  ctx->event_handler = func;
  ctx->user_data = user_data;

  while (options && (name = *options++) != NULL) {
    if ((i = get_option_index(name)) == -1) {
      cry(fc(ctx), "Invalid option: %s", name);
      free_context(ctx);
      return NULL;
    } else if ((value = *options++) == NULL) {
      cry(fc(ctx), "%s: option value cannot be NULL", name);
      free_context(ctx);
      return NULL;
    }
    if (ctx->config[i] != NULL) {
      cry(fc(ctx), "warning: %s: duplicate option", name);
      free(ctx->config[i]);
    }
    ctx->config[i] = mg_strdup(value);
    DEBUG_TRACE(("[%s] -> [%s]", name, value));
  }

  // Set default value if needed
  for (i = 0; config_options[i * 2] != NULL; i++) {
    default_value = config_options[i * 2 + 1];
    if (ctx->config[i] == NULL && default_value != NULL) {
      ctx->config[i] = mg_strdup(default_value);
    }
  }

  // NOTE(lsm): order is important here. SSL certificates must
  // be initialized before listening ports. UID must be set last.
  if (!set_gpass_option(ctx) ||
#if !defined(NO_SSL)
      !set_ssl_option(ctx) ||
#endif
      !set_ports_option(ctx) ||
#if !defined(_WIN32)
      !set_uid_option(ctx) ||
#endif
      !set_acl_option(ctx)) {
    free_context(ctx);
    return NULL;
  }

#if !defined(_WIN32) && !defined(__SYMBIAN32__)
  // Ignore SIGPIPE signal, so if browser cancels the request, it
  // won't kill the whole process.
  (void) signal(SIGPIPE, SIG_IGN);
#endif // !_WIN32

  mg_socketpair(ctx->ctl);
Sergey Lyubka's avatar
Sergey Lyubka committed

  // Start master (listening) thread
  mg_start_thread(master_thread, ctx);

  // Start worker threads
  for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
    if (mg_start_thread(worker_thread, ctx) != 0) {
      cry(fc(ctx), "Cannot start worker thread: %ld", (long) ERRNO);
    } else {
      ctx->num_threads++;
    }
  }

  return ctx;
}

#ifdef USE_LUA
#ifdef _WIN32
static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
                  int offset) {
  HANDLE fh = (HANDLE) _get_osfhandle(fd);
  HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
  void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len);
  CloseHandle(mh);
  return p;
}
#define munmap(x, y)  UnmapViewOfFile(x)
#define MAP_FAILED NULL
#define MAP_PRIVATE 0
#define PROT_READ 0
#else
#include <sys/mman.h>
#endif

static const char *LUASOCKET = "luasocket";

// Forward declarations
static int handle_lsp_request(struct mg_connection *, const char *,
                              struct file *, struct lua_State *);

static void reg_string(struct lua_State *L, const char *name, const char *val) {
  lua_pushstring(L, name);
  lua_pushstring(L, val);
  lua_rawset(L, -3);
}

static void reg_int(struct lua_State *L, const char *name, int val) {
  lua_pushstring(L, name);
  lua_pushinteger(L, val);
  lua_rawset(L, -3);
}

static void reg_function(struct lua_State *L, const char *name,
                         lua_CFunction func, struct mg_connection *conn) {
  lua_pushstring(L, name);
  lua_pushlightuserdata(L, conn);
  lua_pushcclosure(L, func, 1);
  lua_rawset(L, -3);
}

static int lsp_sock_close(lua_State *L) {
  if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
    lua_getfield(L, -1, "sock");
    closesocket((SOCKET) lua_tonumber(L, -1));
  } else {
    return luaL_error(L, "invalid :close() call");
  }
  return 1;
}

static int lsp_sock_recv(lua_State *L) {
  char buf[2000];
  int n;

  if (lua_gettop(L) > 0 && lua_istable(L, -1)) {
    lua_getfield(L, -1, "sock");
    n = recv((SOCKET) lua_tonumber(L, -1), buf, sizeof(buf), 0);
    if (n <= 0) {
      lua_pushnil(L);
    } else {
      lua_pushlstring(L, buf, n);
    }
  } else {
    return luaL_error(L, "invalid :close() call");
  }
  return 1;
}

static int lsp_sock_send(lua_State *L) {
  const char *buf;
  size_t len, sent = 0;
  int n, sock;

  if (lua_gettop(L) > 1 && lua_istable(L, -2) && lua_isstring(L, -1)) {
    buf = lua_tolstring(L, -1, &len);
    lua_getfield(L, -2, "sock");
    sock = (int) lua_tonumber(L, -1);
    while (sent < len) {
      if ((n = send(sock, buf + sent, len - sent, 0)) <= 0) {
        break;
      }
      sent += n;
    }
    lua_pushnumber(L, sent);
5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680
  } else {
    return luaL_error(L, "invalid :close() call");
  }
  return 1;
}

static const struct luaL_Reg luasocket_methods[] = {
  {"close", lsp_sock_close},
  {"send", lsp_sock_send},
  {"recv", lsp_sock_recv},
  {NULL, NULL}
};

static int lsp_connect(lua_State *L) {
  char ebuf[100];
  SOCKET sock;

  if (lua_isstring(L, -3) && lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
    sock = conn2(lua_tostring(L, -3), (int) lua_tonumber(L, -2),
                 (int) lua_tonumber(L, -1), ebuf, sizeof(ebuf));
    if (sock == INVALID_SOCKET) {
      return luaL_error(L, ebuf);
    } else {
      lua_newtable(L);
      reg_int(L, "sock", sock);
      reg_string(L, "host", lua_tostring(L, -4));
      luaL_getmetatable(L, LUASOCKET);
      lua_setmetatable(L, -2);
    }
  } else {
    return luaL_error(L, "connect(host,port,is_ssl): invalid parameter given.");
  }
  return 1;
}

static int lsp_error(lua_State *L) {
  lua_getglobal(L, "mg");
  lua_getfield(L, -1, "onerror");
  lua_pushvalue(L, -3);
  lua_pcall(L, 1, 0, 0);
  return 0;
}

// Silently stop processing chunks.
static void lsp_abort(lua_State *L) {
  int top = lua_gettop(L);
  lua_getglobal(L, "mg");
  lua_pushnil(L);
  lua_setfield(L, -2, "onerror");
  lua_settop(L, top);
  lua_pushstring(L, "aborting");
  lua_error(L);
}

static int lsp(struct mg_connection *conn, const char *path,
               const char *p, int64_t len, lua_State *L) {
  int i, j, pos = 0, lines = 1, lualines = 0;
  char chunkname[MG_BUF_LEN];

  for (i = 0; i < len; i++) {
    if (p[i] == '\n') lines++;
    if (p[i] == '<' && p[i + 1] == '?') {
      for (j = i + 1; j < len ; j++) {
        if (p[j] == '\n') lualines++;
        if (p[j] == '?' && p[j + 1] == '>') {
          mg_write(conn, p + pos, i - pos);

          snprintf(chunkname, sizeof(chunkname), "@%s+%i", path, lines);
          lua_pushlightuserdata(L, conn);
          lua_pushcclosure(L, lsp_error, 1);
          if (luaL_loadbuffer(L, p + (i + 2), j - (i + 2), chunkname)) {
            // Syntax error or OOM. Error message is pushed on stack.
            lua_pcall(L, 1, 0, 0);
          } else {
            // Success loading chunk. Call it.
            lua_pcall(L, 0, 0, 1);
          }

          pos = j + 2;
          i = pos - 1;
          break;
        }
      }
      if (lualines > 0) {
        lines += lualines;
        lualines = 0;
      }
    }
  }

  if (i > pos) {
    mg_write(conn, p + pos, i - pos);
  }

  return 0;
}

static int lsp_write(lua_State *L) {
  int i, num_args;
  const char *str;
  size_t size;
  struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));

  num_args = lua_gettop(L);
  for (i = 1; i <= num_args; i++) {
    if (lua_isstring(L, i)) {
      str = lua_tolstring(L, i, &size);
      mg_write(conn, str, size);
    }
  }

  return 0;
}

static int lsp_read(lua_State *L) {
  struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
  char buf[1024];
  int len = mg_read(conn, buf, sizeof(buf));

  if (len <= 0) return 0;
  lua_pushlstring(L, buf, len);

  return 1;
}

// mg.include: Include another .lp file
static int lsp_include(lua_State *L) {
  struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
  struct file file = STRUCT_FILE_INITIALIZER;
  if (handle_lsp_request(conn, lua_tostring(L, -1), &file, L)) {
    // handle_lsp_request returned an error code, meaning an error occured in
    // the included page and mg.onerror returned non-zero. Stop processing.
    lsp_abort(L);
  }
  return 0;
}

// mg.cry: Log an error. Default value for mg.onerror.
static int lsp_cry(lua_State *L){
  struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
  cry(conn, "%s", lua_tostring(L, -1));
  return 0;
}

// mg.redirect: Redirect the request (internally).
static int lsp_redirect(lua_State *L) {
  struct mg_connection *conn = lua_touserdata(L, lua_upvalueindex(1));
  conn->request_info.uri = lua_tostring(L, -1);
  handle_request(conn);
  lsp_abort(L);
  return 0;
}

static void prepare_lua_environment(struct mg_connection *conn, lua_State *L) {
  const struct mg_request_info *ri = &conn->request_info;
  extern void luaL_openlibs(lua_State *);
  int i;

  luaL_openlibs(L);
#ifdef USE_LUA_SQLITE3
  { extern int luaopen_lsqlite3(lua_State *); luaopen_lsqlite3(L); }
#endif

  luaL_newmetatable(L, LUASOCKET);
  lua_pushliteral(L, "__index");
  luaL_newlib(L, luasocket_methods);
  lua_rawset(L, -3);
  lua_pop(L, 1);
  lua_register(L, "connect", lsp_connect);

  if (conn == NULL) return;

  // Register mg module
  lua_newtable(L);

  reg_function(L, "read", lsp_read, conn);
  reg_function(L, "write", lsp_write, conn);
  reg_function(L, "cry", lsp_cry, conn);
  reg_function(L, "include", lsp_include, conn);
  reg_function(L, "redirect", lsp_redirect, conn);
  reg_string(L, "version", MONGOOSE_VERSION);

  // Export request_info
  lua_pushstring(L, "request_info");
  lua_newtable(L);
  reg_string(L, "request_method", ri->request_method);
  reg_string(L, "uri", ri->uri);
  reg_string(L, "http_version", ri->http_version);
  reg_string(L, "query_string", ri->query_string);
  reg_int(L, "remote_ip", ri->remote_ip);
  reg_int(L, "remote_port", ri->remote_port);
  reg_int(L, "num_headers", ri->num_headers);
  lua_pushstring(L, "http_headers");
  lua_newtable(L);
  for (i = 0; i < ri->num_headers; i++) {
    reg_string(L, ri->http_headers[i].name, ri->http_headers[i].value);
  }
  lua_rawset(L, -3);
  lua_rawset(L, -3);

  lua_setglobal(L, "mg");

  // Register default mg.onerror function
  luaL_dostring(L, "mg.onerror = function(e) mg.write('\\nLua error:\\n', "
                "debug.traceback(e, 1)) end");
}

static int lua_error_handler(lua_State *L) {
  const char *error_msg =  lua_isstring(L, -1) ?  lua_tostring(L, -1) : "?\n";

  lua_getglobal(L, "mg");
  if (!lua_isnil(L, -1)) {
    lua_getfield(L, -1, "write");   // call mg.write()
    lua_pushstring(L, error_msg);
    lua_pushliteral(L, "\n");
    lua_call(L, 2, 0);
    luaL_dostring(L, "mg.write(debug.traceback(), '\\n')");
  } else {
    printf("Lua error: [%s]\n", error_msg);
    luaL_dostring(L, "print(debug.traceback(), '\\n')");
  }
  // TODO(lsm): leave the stack balanced

  return 0;
}

void mg_exec_lua_script(struct mg_connection *conn, const char *path,
                        const void **exports) {
  int i;
  lua_State *L;

  if (path != NULL && (L = luaL_newstate()) != NULL) {
    prepare_lua_environment(conn, L);
    lua_pushcclosure(L, &lua_error_handler, 0);

    lua_pushglobaltable(L);
    if (exports != NULL) {
      for (i = 0; exports[i] != NULL && exports[i + 1] != NULL; i += 2) {
        lua_pushstring(L, exports[i]);
        lua_pushcclosure(L, (lua_CFunction) exports[i + 1], 0);
        lua_rawset(L, -3);
      }
    }

    if (luaL_loadfile(L, path) != 0) {
      lua_error_handler(L);
    }
    lua_pcall(L, 0, 0, -2);
    lua_close(L);
  }
}

static void lsp_send_err(struct mg_connection *conn, struct lua_State *L,
                         const char *fmt, ...) {
  char buf[MG_BUF_LEN];
  va_list ap;

  va_start(ap, fmt);
  vsnprintf(buf, sizeof(buf), fmt, ap);
  va_end(ap);

  if (L == NULL) {
    send_http_error(conn, 500, http_500_error, "%s", buf);
  } else {
    lua_pushstring(L, buf);
    lua_error(L);
  }
}

static int handle_lsp_request(struct mg_connection *conn, const char *path,
                               struct file *filep, struct lua_State *ls) {
  void *p = NULL;
  lua_State *L = NULL;
  FILE *fp = NULL;
  int error = 1;

  // We need both mg_stat to get file size, and mg_fopen to get fd
  if (!mg_stat(path, filep) || (fp = mg_fopen(path, "r")) == NULL) {
    lsp_send_err(conn, ls, "File [%s] not found", path);
  } else if ((p = mmap(NULL, (size_t) filep->size, PROT_READ, MAP_PRIVATE,
                       fileno(fp), 0)) == MAP_FAILED) {
    lsp_send_err(conn, ls, "mmap(%s, %zu, %d): %s", path, (size_t) filep->size,
              fileno(fp), strerror(errno));
  } else if ((L = ls != NULL ? ls : luaL_newstate()) == NULL) {
    send_http_error(conn, 500, http_500_error, "%s", "luaL_newstate failed");
  } else {
    // We're not sending HTTP headers here, Lua page must do it.
    if (ls == NULL) {
      prepare_lua_environment(conn, L);
    }
    error = lsp(conn, path, p, filep->size, L);
  }

  if (L != NULL && ls == NULL) lua_close(L);
  if (p != NULL) munmap(p, filep->size);
  fclose(fp);

  return error;
}
#endif // USE_LUA