#include #include #include #include #include #include #include #include #include /*************************************** * Private Static Inline ***************************************/ /** * Start the module as a server listening at module->port * @param module **/ static inline void module__initialize_as_server(struct module *module) { // Allocate a new socket int socket_descriptor = socket(AF_INET, SOCK_STREAM, 0); assert(socket_descriptor > 0); // Configure socket address as [all addresses]:[module->port] module->socket_address.sin_family = AF_INET; module->socket_address.sin_addr.s_addr = htonl(INADDR_ANY); module->socket_address.sin_port = htons((unsigned short)module->port); // Configure the socket to allow multiple sockets to bind to the same host and port int optval = 1; setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); optval = 1; setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); // Bind to the interface if (bind(socket_descriptor, (struct sockaddr *)&module->socket_address, sizeof(module->socket_address)) < 0) { perror("bind"); assert(0); } // Listen to the interface? Check that it is live? if (listen(socket_descriptor, MOD_BACKLOG) < 0) assert(0); // Set the socket descriptor and register with our global epoll instance to monitor for incoming HTTP requests module->socket_descriptor = socket_descriptor; struct epoll_event accept_evt; accept_evt.data.ptr = (void *)module; accept_evt.events = EPOLLIN; if (epoll_ctl(runtime__epoll_file_descriptor, EPOLL_CTL_ADD, module->socket_descriptor, &accept_evt) < 0) assert(0); } /*************************************** * Public Methods ***************************************/ /** * Module Mega Teardown Function * Closes the socket and dynamic library, and then frees the module * Returns harmlessly if there are outstanding references * @param module - the module to teardown **/ void module__free(struct module *module) { if (module == NULL) return; if (module->dynamic_library_handle == NULL) return; // Do not free if we still have oustanding references if (module->reference_count) return; // TODO: What about the module database? Do we need to do any cleanup there? close(module->socket_descriptor); dlclose(module->dynamic_library_handle); free(module); } /** * Module Contructor * Creates a new module, invokes initialize_tables to initialize the indirect table, adds it to the module DB, and starts *listening for HTTP Requests * * @param name * @param path * @param argument_count * @param stack_size * @param max_memory * @param timeout * @param port * @param request_size * @returns A new module or NULL in case of failure **/ struct module * module__new(char *name, char *path, i32 argument_count, u32 stack_size, u32 max_memory, u32 timeout, int port, int request_size, int response_size) { struct module *module = (struct module *)malloc(sizeof(struct module)); if (!module) return NULL; memset(module, 0, sizeof(struct module)); // Load the dynamic library *.so file with lazy function call binding and deep binding module->dynamic_library_handle = dlopen(path, RTLD_LAZY | RTLD_DEEPBIND); if (module->dynamic_library_handle == NULL) goto dl_open_error; // Resolve the symbols in the dynamic library *.so file module->main = (mod_main_fn_t)dlsym(module->dynamic_library_handle, MOD_MAIN_FN); if (module->main == NULL) goto dl_error; module->initialize_globals = (mod_glb_fn_t)dlsym(module->dynamic_library_handle, MOD_GLB_FN); if (module->initialize_globals == NULL) goto dl_error; module->initialize_memory = (mod_mem_fn_t)dlsym(module->dynamic_library_handle, MOD_MEM_FN); if (module->initialize_memory == NULL) goto dl_error; module->initialize_tables = (mod_tbl_fn_t)dlsym(module->dynamic_library_handle, MOD_TBL_FN); if (module->initialize_tables == NULL) goto dl_error; module->initialize_libc = (mod_libc_fn_t)dlsym(module->dynamic_library_handle, MOD_LIBC_FN); if (module->initialize_libc == NULL) goto dl_error; // Set fields in the module struct strncpy(module->name, name, MOD_NAME_MAX); strncpy(module->path, path, MOD_PATH_MAX); module->argument_count = argument_count; module->stack_size = round_up_to_page(stack_size == 0 ? WASM_STACK_SIZE : stack_size); module->max_memory = max_memory == 0 ? ((u64)WASM_PAGE_SIZE * WASM_MAX_PAGES) : max_memory; module->timeout = timeout; module->socket_descriptor = -1; module->port = port; if (request_size == 0) request_size = MOD_REQ_RESP_DEFAULT; if (response_size == 0) response_size = MOD_REQ_RESP_DEFAULT; module->max_request_size = request_size; module->max_response_size = response_size; module->max_request_or_response_size = round_up_to_page(request_size > response_size ? request_size : response_size); // module_indirect_table is a thread-local struct struct indirect_table_entry *cache_tbl = module_indirect_table; // assumption: All modules are created at program start before we enable preemption or enable the execution of // any worker threads We are checking that thread-local module_indirect_table is NULL to prove that we aren't // yet preempting If we want to be able to do this later, we can possibly defer module__initialize_table until the // first invocation assert(cache_tbl == NULL); // TODO: determine why we have to set the module_indirect_table state before calling table init and then restore // the existing value What is the relationship between these things? module_indirect_table = module->indirect_table; module__initialize_table(module); module_indirect_table = cache_tbl; // Add the module to the in-memory module DB module_database__add(module); // Start listening for requests module__initialize_as_server(module); return module; dl_error: dlclose(module->dynamic_library_handle); dl_open_error: free(module); debuglog("%s\n", dlerror()); return NULL; } /** * Parses a JSON file and allocates one or more new modules * @param file_name The path of the JSON file * @return RC 0 on Success. -1 on Error */ int module__new_from_json(char *file_name) { // Use stat to get file attributes and make sure file is there and OK struct stat stat_buffer; memset(&stat_buffer, 0, sizeof(struct stat)); if (stat(file_name, &stat_buffer) < 0) { perror("stat"); return -1; } // Open the file FILE *module_file = fopen(file_name, "r"); if (!module_file) { perror("fopen"); return -1; } // Initialize a Buffer, Read the file into the buffer, and then check that the buffer size equals the file size char *file_buffer = malloc(stat_buffer.st_size); memset(file_buffer, 0, stat_buffer.st_size); int total_chars_read = fread(file_buffer, sizeof(char), stat_buffer.st_size, module_file); debuglog("size read: %d content: %s\n", total_chars_read, file_buffer); if (total_chars_read != stat_buffer.st_size) { perror("fread"); return -1; } // Close the file fclose(module_file); // Initialize the Jasmine Parser and an array to hold the tokens jsmn_parser module_parser; jsmn_init(&module_parser); jsmntok_t tokens[MOD_MAX * JSON_ELE_MAX]; // Use Jasmine to parse the JSON int total_tokens = jsmn_parse(&module_parser, file_buffer, strlen(file_buffer), tokens, sizeof(tokens) / sizeof(tokens[0])); if (total_tokens < 0) { debuglog("jsmn_parse: invalid JSON?\n"); return -1; } int module_count = 0; for (int i = 0; i < total_tokens; i++) { assert(tokens[i].type == JSMN_OBJECT); char module_name[MOD_NAME_MAX] = { 0 }; char module_path[MOD_PATH_MAX] = { 0 }; char *request_headers = (char *)malloc(HTTP_HEADER_MAXSZ * HTTP_HEADERS_MAX); memset(request_headers, 0, HTTP_HEADER_MAXSZ * HTTP_HEADERS_MAX); char *reponse_headers = (char *)malloc(HTTP_HEADER_MAXSZ * HTTP_HEADERS_MAX); memset(reponse_headers, 0, HTTP_HEADER_MAXSZ * HTTP_HEADERS_MAX); i32 request_size = 0; i32 response_size = 0; i32 argument_count = 0; u32 port = 0; i32 is_active = 0; i32 request_count = 0; i32 response_count = 0; int j = 1; int ntoks = 2 * tokens[i].size; char request_content_type[HTTP_HEADERVAL_MAXSZ] = { 0 }; char response_content_type[HTTP_HEADERVAL_MAXSZ] = { 0 }; for (; j < ntoks;) { int ntks = 1; char key[32] = { 0 }; char val[256] = { 0 }; sprintf(val, "%.*s", tokens[j + i + 1].end - tokens[j + i + 1].start, file_buffer + tokens[j + i + 1].start); sprintf(key, "%.*s", tokens[j + i].end - tokens[j + i].start, file_buffer + tokens[j + i].start); if (strcmp(key, "name") == 0) { strcpy(module_name, val); } else if (strcmp(key, "path") == 0) { strcpy(module_path, val); } else if (strcmp(key, "port") == 0) { port = atoi(val); } else if (strcmp(key, "argsize") == 0) { argument_count = atoi(val); } else if (strcmp(key, "active") == 0) { is_active = (strcmp(val, "yes") == 0); } else if (strcmp(key, "http-req-headers") == 0) { assert(tokens[i + j + 1].type == JSMN_ARRAY); assert(tokens[i + j + 1].size <= HTTP_HEADERS_MAX); request_count = tokens[i + j + 1].size; ntks += request_count; ntoks += request_count; for (int k = 1; k <= tokens[i + j + 1].size; k++) { jsmntok_t *g = &tokens[i + j + k + 1]; char * r = request_headers + ((k - 1) * HTTP_HEADER_MAXSZ); assert(g->end - g->start < HTTP_HEADER_MAXSZ); strncpy(r, file_buffer + g->start, g->end - g->start); } } else if (strcmp(key, "http-resp-headers") == 0) { assert(tokens[i + j + 1].type == JSMN_ARRAY); assert(tokens[i + j + 1].size <= HTTP_HEADERS_MAX); response_count = tokens[i + j + 1].size; ntks += response_count; ntoks += response_count; for (int k = 1; k <= tokens[i + j + 1].size; k++) { jsmntok_t *g = &tokens[i + j + k + 1]; char * r = reponse_headers + ((k - 1) * HTTP_HEADER_MAXSZ); assert(g->end - g->start < HTTP_HEADER_MAXSZ); strncpy(r, file_buffer + g->start, g->end - g->start); } } else if (strcmp(key, "http-req-size") == 0) { request_size = atoi(val); } else if (strcmp(key, "http-resp-size") == 0) { response_size = atoi(val); } else if (strcmp(key, "http-req-content-type") == 0) { strcpy(request_content_type, val); } else if (strcmp(key, "http-resp-content-type") == 0) { strcpy(response_content_type, val); } else { debuglog("Invalid (%s,%s)\n", key, val); } j += ntks; } i += ntoks; // do not load if it is not active if (is_active == 0) continue; // Allocate a module based on the values from the JSON struct module *module = module__new(module_name, module_path, argument_count, 0, 0, 0, port, request_size, response_size); assert(module); module__set_http_info(module, request_count, request_headers, request_content_type, response_count, reponse_headers, response_content_type); module_count++; free(request_headers); free(reponse_headers); } free(file_buffer); assert(module_count); debuglog("Loaded %d module%s!\n", module_count, module_count > 1 ? "s" : ""); return 0; }