/* b01902062 藍ĉŒşç‘‹ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define CHTTPD_SERVER_ENABLE_ERRMSG #include "memwrap.h" #include "chttpd-log.h" #include "chttpd-server.h" #include "chttpd-socket.h" #include "chttpd-conn.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NULLPTR ((void*)0) #define SOCKADDR(x) ((struct sockaddr*)(x)) #define SOCKADDR_UN(x) ((struct sockaddr_un*)(x)) #define SOCKADDR_IN(x) ((struct sockaddr_in*)(x)) #define SOCKADDR_IN6(x) ((struct sockaddr_in6*)(x)) static inline void report_unix_error (int rval) { int errno_backup = errno; switch (rval) { case CHTTPD_SOCKET_NEW_ERROR_SOCKET: fputs ("socket: ", stdout); break; case CHTTPD_SOCKET_NEW_ERROR_BIND: fputs ("bind: ", stdout); break; case CHTTPD_SOCKET_NEW_ERROR_LISTEN: fputs ("listen: ", stdout); break; } puts (strerror (errno_backup)); errno = errno_backup; } static inline bool init_runtime_dir (ChttpdServer* server, const char* dir, const char* ctrl, const char* pid) { printf ("Trying %s ... ", ctrl); fflush (stdout); mkdir (dir, 0700); struct stat st; char* chttpd_force = getenv ("CGISH_HTTPD_FORCE"); if (chttpd_force != NULL && *chttpd_force != '\0') { if (stat (ctrl, &st) >= 0) { if (S_ISSOCK (st.st_mode)) { unlink (ctrl); } } } server->sockfd = chttpd_socket_new_unix (ctrl, SOCKADDR_UN (&server->addr), &server->addrlen); if (server->sockfd < 0) { report_unix_error (server->sockfd); return false; } int pidfd = open (pid, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (pidfd >= 0) { dprintf (pidfd, "%ld", ((long)getpid ())); close (pidfd); } puts ("OK"); return true; } static inline void report_inet_error (int rval, int gai_error) { int errno_backup = errno; switch (rval) { case CHTTPD_SOCKET_NEW_ERROR_GETADDRINFO: fputs ("getaddrinfo: ", stdout); puts (gai_strerror (gai_error)); break; case CHTTPD_SOCKET_NEW_ERROR_SOCKET: fputs ("socket: ", stdout); puts (strerror (errno_backup)); break; case CHTTPD_SOCKET_NEW_ERROR_BIND: fputs ("bind: ", stdout); puts (strerror (errno_backup)); break; case CHTTPD_SOCKET_NEW_ERROR_LISTEN: fputs ("listen: ", stdout); puts (strerror (errno_backup)); break; case CHTTPD_SOCKET_NEW_ERROR_UNEXPECTED: puts ("Unexpected error! Is your system broken?"); break; } errno = errno_backup; } static LbsListMeta* chttpd_main_init (ChttpdLog* hlog, const char* service) { ChttpdServer* server; LbsListMeta* slist = lbs_list_meta_new (chttpd_server_dtor); int gai_error; /* Default: Unix socket (ADMIN) */ bool unix_ok = false; uid_t uid = getuid (); server = xmalloc (sizeof (ChttpdServer)); if (uid == 0) { /* System runtime */ char dir[] = "/var/run/" PACKAGE_NAME; char ctrl[] = "/var/run/" PACKAGE_NAME "/control"; char pid[] = "/var/run/" PACKAGE_NAME "/pid"; unix_ok = init_runtime_dir (server, dir, ctrl, pid); } if (!unix_ok) { /* User XDG runtime */ char* xdg_dir = getenv ("XDG_RUNTIME_DIR"); if (xdg_dir != NULL && *xdg_dir != '\0') { char* dir = lbs_posix_strcat (xdg_dir, "/" PACKAGE_NAME, NULLPTR); char* ctrl = lbs_posix_strcat (dir, "/control", NULLPTR); char* pid = lbs_posix_strcat (dir, "/pid", NULLPTR); unix_ok = init_runtime_dir (server, dir, ctrl, pid); free (dir); free (ctrl); free (pid); } } if (!unix_ok) { /* Just guess user XDG dir ! */ char* xdg_dir = lbs_str_printf ("/run/user/%lu", (unsigned long)uid); char* dir = lbs_posix_strcat (xdg_dir, "/" PACKAGE_NAME, NULLPTR); char* ctrl = lbs_posix_strcat (dir, "/control", NULLPTR); char* pid = lbs_posix_strcat (dir, "/pid", NULLPTR); unix_ok = init_runtime_dir (server, dir, ctrl, pid); free (xdg_dir); free (dir); free (ctrl); free (pid); } if (!unix_ok) { /* Fallback to XDG_CONFIG_HOME and current working directory */ char* xdg_dir = getenv ("XDG_CONFIG_HOME"); bool xdg_dir_alloc = false; if (xdg_dir == NULL || *xdg_dir == '\0') { char* home = getenv ("HOME"); if (home == NULL || *home == '\0') { home = "."; } xdg_dir = lbs_posix_strcat (home, "/.config", LBS_COMMON_NULL_PTR); xdg_dir_alloc = true; } char* dir = lbs_posix_strcat (xdg_dir, "/" PACKAGE_NAME, NULLPTR); char* ctrl = lbs_posix_strcat (dir, "/control", NULLPTR); char* pid = lbs_posix_strcat (dir, "/pid", NULLPTR); unix_ok = init_runtime_dir (server, dir, ctrl, pid); if (xdg_dir_alloc) { free (xdg_dir); } free (dir); free (ctrl); free (pid); } if (unix_ok) { chttpd_server_ctor (server, hlog, true); lbs_list_push_back (slist, server, NULL); } else { free (server); } /* Default: IPv4 socket (HTTP) */ server = xmalloc (sizeof (ChttpdServer)); printf ("Trying IPv4 any host, service %s ... ", service); server->sockfd = chttpd_socket_new_inet (NULL, service, AF_INET, SOCKADDR (&server->addr), &server->addrlen, &gai_error); if (server->sockfd >= 0) { puts ("OK"); chttpd_server_ctor (server, hlog, false); lbs_list_push_back (slist, server, NULL); } else { report_inet_error (server->sockfd, gai_error); free (server); } /* Default: IPv6 socket (HTTP) */ server = xmalloc (sizeof (ChttpdServer)); printf ("Trying IPv6 any host, service %s ... ", service); server->sockfd = chttpd_socket_new_inet (NULL, service, AF_INET6, SOCKADDR (&server->addr), &server->addrlen, &gai_error); if (server->sockfd >= 0) { puts ("OK"); chttpd_server_ctor (server, hlog, false); lbs_list_push_back (slist, server, NULL); } else { report_inet_error (server->sockfd, gai_error); free (server); } return slist; } int chttpd_server_notify; pthread_mutex_t chttpd_server_notify_mutex; ChttpdServer chttpd_server_notify_data; unsigned long long chttpd_server_count; pthread_rwlock_t chttpd_server_count_lock; static volatile sig_atomic_t server_shutdown; static void server_shutdown_setter (int signo) { server_shutdown = 1; } static volatile sig_atomic_t server_notify; static void server_notify_setter (int signo) { server_notify = 1; } static volatile sig_atomic_t server_info; static void server_info_setter (int signo) { server_info = 1; } #define ERRLEN 256 static void chttpd_main_loop (ChttpdLog* hlog, LbsListMeta* slist) { chttpd_server_notify = 1; chttpd_server_notify_data.attr_close = true; pthread_mutex_init (&chttpd_server_notify_mutex, NULL); chttpd_server_count = 0; pthread_rwlock_init (&chttpd_server_count_lock, NULL); sigset_t crit_mask; sigemptyset (&crit_mask); sigaddset (&crit_mask, SIGINT); sigaddset (&crit_mask, SIGTERM); sigaddset (&crit_mask, SIGHUP); sigaddset (&crit_mask, SIGUSR1); sigaddset (&crit_mask, SIGUSR2); pthread_attr_t padetach; pthread_attr_init (&padetach); pthread_attr_setdetachstate (&padetach, PTHREAD_CREATE_DETACHED); struct pollfd* pfds = NULL; nfds_t nfds = 0; char errmsg[ERRLEN]; while (lbs_list_meta_get_len (slist) > 0) { pthread_sigmask (SIG_BLOCK, &crit_mask, NULL); /* Check whether we are going to shutdown the server */ if (server_shutdown) { server_shutdown++; pthread_mutex_lock (&chttpd_server_notify_mutex); chttpd_server_notify = 1; pthread_mutex_unlock (&chttpd_server_notify_mutex); for (LbsList* iter = slist->first; iter != NULL; iter = iter->next) { ChttpdServer* server = iter->data; pthread_rwlock_wrlock (&server->lock); server->attr_close = true; pthread_rwlock_unlock (&server->lock); } } /* Show info */ if (server_info) { server_info = 0; pthread_rwlock_rdlock (&chttpd_server_count_lock); printf ("%llu connections has been accepted\n", chttpd_server_count); pthread_rwlock_unlock (&chttpd_server_count_lock); puts ("PID of running processes: (-1 means not available)"); unsigned c = 0; LbsList* iter = slist->first; for (; iter != NULL; iter = iter->next, c++) { ChttpdServer* server = iter->data; printf (" Server %u:", c); pthread_rwlock_rdlock (&server->lock); for (LbsList* ci = server->conn->first; ci != NULL; ci = ci->next) { ChttpdConn* conn = ci->data; printf (" %ld", (long)(conn->pid)); } pthread_rwlock_unlock (&server->lock); putchar ('\n'); } } /* Check whether there are notifications */ pthread_mutex_lock (&chttpd_server_notify_mutex); if (chttpd_server_notify || server_notify) { chttpd_server_notify = 0; server_notify = 0; if (chttpd_server_notify_data.attr_close == false) { /* TODO: Add a server */ ChttpdServer* new_server = xmalloc (sizeof (ChttpdServer)); *new_server = chttpd_server_notify_data; lbs_list_push_back (slist, new_server, NULL); } pthread_mutex_unlock (&chttpd_server_notify_mutex); /* Remove unused servers */ LbsList* iter = slist->first; LbsList* next; for (; iter != NULL; iter = next) { ChttpdServer* server = iter->data; next = iter->next; if (pthread_rwlock_trywrlock (&server->lock) == 0) { if (server->attr_close && server->conn->len <= 0) { pthread_rwlock_unlock (&server->lock); lbs_list_remove (slist, iter); } else { pthread_rwlock_unlock (&server->lock); } } } /* Rebuild poll fd list */ if (slist->len > 0) { pfds = xrealloc (pfds, sizeof (struct pollfd) * slist->len); unsigned c = 0; for (iter = slist->first; iter != NULL; iter = iter->next) { ChttpdServer* server = iter->data; if (server->attr_close) { continue; } pfds[c++] = (struct pollfd) { .fd = server->sockfd, .events = POLLIN | POLLPRI, .revents = 0 }; } nfds = c; } else { free (pfds); pfds = NULL; nfds = 0; } /* Log current server info */ if (server_notify || server_shutdown < 1) { chttpd_log_write_str (hlog, "[Server status change notification]"); unsigned long long count; pthread_rwlock_rdlock (&chttpd_server_count_lock); count = chttpd_server_count; pthread_rwlock_unlock (&chttpd_server_count_lock); chttpd_log_write (hlog, "%llu connections has been accepted", count); unsigned c = 0; for (iter = slist->first; iter != NULL; iter = iter->next, c++) { ChttpdServer* server = iter->data; struct sockaddr_storage addr; char* type; size_t conn_count; pthread_rwlock_rdlock (&server->lock); addr = server->addr; type = server->attr_admin ? "ADMIN" : "HTTP"; conn_count = server->conn->len; pthread_rwlock_unlock (&server->lock); char ipstr[INET6_ADDRSTRLEN]; _Static_assert (INET6_ADDRSTRLEN >= INET_ADDRSTRLEN, "Why IPv6 address is shorter than IPv4 address?"); switch (addr.ss_family) { case AF_UNIX: CHTTPD_SOCKET_SOCKADDR_UN_SET_NULL (SOCKADDR_UN (&addr)); chttpd_log_write (hlog, "Server %u: type %s, UNIX socket, path %s" " (%zu active connections)", c, type, SOCKADDR_UN (&addr)->sun_path, conn_count); break; case AF_INET: if (!inet_ntop (AF_INET, &(SOCKADDR_IN (&addr)->sin_addr), ipstr, INET_ADDRSTRLEN)) { strcpy (ipstr, "unknown"); } chttpd_log_write (hlog, "Server %u: type %s, " "IPv4 socket, address %s, port %" PRIu16 " (%zu active connections)", c, type, ipstr, ntohs (SOCKADDR_IN (&addr)->sin_port), conn_count); break; case AF_INET6: if (!inet_ntop (AF_INET6, &(SOCKADDR_IN6 (&addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN)) { strcpy (ipstr, "unknown"); } chttpd_log_write (hlog, "Server %u: type %s, " "IPv6 socket, address %s, port %" PRIu16 " (%zu active connections)", c, type, ipstr, ntohs (SOCKADDR_IN6 (&addr)->sin6_port), conn_count); break; default: chttpd_log_write (hlog, "Server %u: type %s, unknown socket", c, type); break; } } chttpd_log_write (hlog, "%u servers are active", slist->len); } } else { pthread_mutex_unlock (&chttpd_server_notify_mutex); } pthread_sigmask (SIG_UNBLOCK, &crit_mask, NULL); /* Poll for new request */ if (pfds == NULL || nfds <= 0) { if (slist->len > 0) { nanosleep (&(struct timespec){ 0, 500000000 }, NULL); chttpd_log_write_str (hlog, "[main] waiting for remaining connections"); } continue; } int pollrv = poll (pfds, nfds, -1); if (pollrv < 0) { chttpd_log_write (hlog, "[main] poll: %s", get_errmsg (errno, errmsg, ERRLEN)); continue; } LbsList* iter = slist->first; for (int i = 0; i < nfds; i++, iter = iter->next) { ChttpdServer* server = iter->data; for (; server->attr_close; iter = iter->next, server = iter->data); if (pfds[i].revents & POLLIN || pfds[i].revents & POLLPRI) { pfds[i].revents = 0; /* Reset revents */ if (server->attr_close) { continue; } int connfd = accept (server->sockfd, NULL, NULL); if (connfd < 0) { chttpd_log_write (hlog, "[main] accept: %s", get_errmsg (errno, errmsg, ERRLEN)); continue; } unsigned long long count; pthread_rwlock_wrlock (&chttpd_server_count_lock); count = chttpd_server_count++; pthread_rwlock_unlock (&chttpd_server_count_lock); char* peername = lbs_posix_socket_peername (connfd); chttpd_log_write (hlog, "[main] connection %llu from %s " "accepted", count, peername); free (peername); /* Note: the callee should free the object! */ ChttpdConn* conn = xmalloc (sizeof (ChttpdConn)); chttpd_conn_ctor (conn, count, connfd, hlog, server, slist); pthread_rwlock_wrlock (&server->lock); LbsList* node = lbs_list_push_back (server->conn, conn, NULL); pthread_rwlock_unlock (&server->lock); conn->conn_node = node; pthread_t ptid; int pterr; if (server->attr_admin) { pterr = pthread_create (&ptid, &padetach, chttpd_conn_admin, conn); } else { pterr = pthread_create (&ptid, &padetach, chttpd_conn_http, conn); } if (pterr != 0) { chttpd_log_write (hlog, "[main] cannot create a thread for" "connection %llu: %s", count, get_errmsg (pterr, errmsg, ERRLEN)); pthread_rwlock_wrlock (&server->lock); lbs_list_remove (server->conn, node); pthread_rwlock_unlock (&server->lock); } } } } free (pfds); pthread_attr_destroy (&padetach); } #undef ERRLEN void chttpd_server_ctor (void* server_generic, ChttpdLog* hlog, bool is_admin) { ChttpdServer* server = server_generic; server->attr_admin = is_admin; server->attr_close = false; server->hlog = chttpd_log_ref (hlog); server->conn = lbs_list_meta_new (chttpd_conn_dtor); pthread_rwlock_init (&server->lock, NULL); sigemptyset (&server->conn_mask); sigaddset (&server->conn_mask, SIGINT); sigaddset (&server->conn_mask, SIGTERM); sigaddset (&server->conn_mask, SIGHUP); sigaddset (&server->conn_mask, SIGUSR1); sigaddset (&server->conn_mask, SIGUSR2); } void chttpd_server_dtor (void* server_generic) { ChttpdServer* server = server_generic; if (server->addr.ss_family == AF_UNIX) { CHTTPD_SOCKET_SOCKADDR_UN_SET_NULL (SOCKADDR_UN (&server->addr)); unlink (SOCKADDR_UN (&server->addr)->sun_path); } close (server->sockfd); pthread_rwlock_destroy (&server->lock); lbs_list_meta_free (server->conn); chttpd_log_unref (server->hlog); free (server); } int main (int argc, char* argv[]) { setlocale (LC_ALL, ""); tzset (); if (argc < 3) { fprintf (stderr, "Usage: %s port logfile\n", argv[0]); return 1; } ChttpdLog* hlog = chttpd_log_new_file (argv[0], argv[2]); if (hlog == NULL) { fprintf (stderr, "Error while opening %s: %s\n", argv[2], strerror (errno)); return 2; } struct sigaction sa_pipe, sa_term, sa_usr1, sa_usr2; sa_pipe.sa_handler = SIG_IGN; sa_pipe.sa_flags = 0; sigemptyset (&sa_pipe.sa_mask); sigaction (SIGPIPE, &sa_pipe, NULL); /* Use errno, no SIGPIPE */ sa_term.sa_handler = server_shutdown_setter; sa_term.sa_flags = 0; sigemptyset (&sa_term.sa_mask); sigaction (SIGINT, &sa_term, NULL); /* Handled by main thread in loop */ sigaction (SIGTERM, &sa_term, NULL); /* Handled by main thread in loop */ sa_usr1.sa_handler = server_info_setter; sa_usr1.sa_flags = 0; sigemptyset (&sa_usr1.sa_mask); sigaction (SIGUSR1, &sa_usr1, NULL); /* Handled by main thread in loop */ sa_usr2.sa_handler = server_notify_setter; sa_usr2.sa_flags = 0; sigemptyset (&sa_usr2.sa_mask); sigaction (SIGHUP, &sa_usr2, NULL); /* Handled by main thread to interrupt */ sigaction (SIGUSR2, &sa_usr2, NULL); /* Handled by main thread to interrupt */ puts ("Starting " PACKAGE_NAME " version " PACKAGE_VERSION " ..."); LbsListMeta* slist = chttpd_main_init (hlog, argv[1]); if (lbs_list_meta_get_len (slist) > 0) { chttpd_main_loop (hlog, slist); } else { puts ("No server is active! Exiting ..."); } lbs_list_meta_free (slist); chttpd_log_write_str (hlog, "Waiting for all threads to terminate"); pthread_mutex_lock (&hlog->ref_mutex); while (hlog->ref_count > 1) { pthread_cond_wait (&hlog->ref_change, &hlog->ref_mutex); } pthread_mutex_unlock (&hlog->ref_mutex); chttpd_log_write_str (hlog, "All threads terminated. Exit now!"); chttpd_log_unref (hlog); return 0; }