#include #include #include #include #include #include #include #include #include #include #include "sys.h" String capture_backtrace_string(u32 max_frames, u32 skip_frames) { if(max_frames == 0) return(""); std::vector frames(max_frames); size_t size = backtrace(frames.data(), max_frames); if(size == 0) return(""); char** symbols = backtrace_symbols(frames.data(), size); if(!symbols) return(""); String trace; for(size_t i = skip_frames; i < size; i++) { trace += symbols[i]; trace += "\n"; } free(symbols); return(trace); } String signal_name(int sig) { switch(sig) { case SIGABRT: return("SIGABRT"); case SIGBUS: return("SIGBUS"); case SIGFPE: return("SIGFPE"); case SIGILL: return("SIGILL"); case SIGINT: return("SIGINT"); case SIGSEGV: return("SIGSEGV"); case SIGTERM: return("SIGTERM"); default: return(""); } } String shell_exec(String cmd) { //printf("(i) shell_exec(%s)\n", cmd.c_str()); String data; FILE * stream; const int max_buffer = 256; char buffer[max_buffer]; cmd.append(" 2>&1"); stream = popen(cmd.c_str(), "r"); if (stream) { while (!feof(stream)) if (fgets(buffer, max_buffer, stream) != NULL) data.append(buffer); pclose(stream); } return data; } String shell_escape(String raw) { // FIXME String result = ""; for(auto c : raw) { if(c == '\'') { result.append("'\\''"); } else { result.append(1, c); } } return("\'" + result + "\'"); /* ` U+0060 (Grave Accent) Backtick Command substitution ~ U+007E Tilde Tilde expansion ! U+0021 Exclamation mark History expansion # U+0023 Number sign Hash Comments $ U+0024 Dollar sign Parameter expansion & U+0026 Ampersand Background commands * U+002A Asterisk Filename expansion and globbing ( U+0028 Left Parenthesis Subshells ) U+0029 Right Parenthesis Subshells U+0009 Tab (⇥) Word splitting (whitespace) { U+007B Left Curly Bracket Left brace Brace expansion [ U+005B Left Square Bracket Filename expansion and globbing | U+007C Vertical Line Vertical bar Pipelines \ U+005C Reverse Solidus Backslash Escape character ; U+003B Semicolon Separating commands ' U+0027 Apostrophe Single quote String quoting " U+0022 Quotation Mark Double quote String quoting with interpolation ↩ U+000A Line Feed Newline Line break < U+003C Less than Input redirection > U+003E Greater than Output redirection ? U+003F Question mark Filename expansion and globbing   U+0020 Space Word splitting1 (whitespace) */ } String basename(String fn) { String result; while(fn.length() > 0) result = nibble("/", fn); //printf("basename(%s) %s\n", fn.c_str(), result.c_str()); return(result); } String dirname(String fn) { String result; auto seg = split(fn, "/"); seg.pop_back(); result = join(seg, "/"); //printf("dirname(%s) %s seg#%i\n", fn.c_str(), result.c_str(), seg.size()); return(result); } String path_join(String base, String child) { if(base == "") return(child); if(child == "") return(base); if(child[0] == '/') return(child); if(base[base.length() - 1] == '/') return(base + child); return(base + "/" + child); } bool mkdir(String path) { shell_exec(String("mkdir -p ")+" "+shell_escape(path)); return(true); } bool file_exists(String path) { std::filesystem::path fp{ path }; return(std::filesystem::exists(fp)); } String file_get_contents(String file_name) { /*std::ifstream ifs(file_name); printf("stream file desc %i\n", ifs.filedesc()); String content( (std::istreambuf_iterator(ifs) ), (std::istreambuf_iterator() ) );*/ char buf[512]; String content; s32 fd = open(file_name.c_str(), O_RDONLY); if(fd == -1) { printf("(!) Could not read %s\n", file_name.c_str()); return(""); } flock(fd, LOCK_SH); s64 bytes_read = 0; //s64 size = lseek(fd, 0, SEEK_END); //lseek(fd, 0, SEEK_SET); //content.reserve(size+1); while((bytes_read = read(fd, buf, 512)) > 0) { content.append(buf, bytes_read); } flock(fd, LOCK_UN); close(fd); return(content); } bool file_put_contents(String file_name, String content) { s32 fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); if(fd == -1) { printf("(!) Could not write %s\n", file_name.c_str()); return(false); } flock(fd, LOCK_EX); const char* data = content.data(); size_t remaining = content.length(); while(remaining > 0) { auto bytes_written = write(fd, data, remaining); if(bytes_written < 0) { flock(fd, LOCK_UN); close(fd); printf("(!) Could not fully write %s\n", file_name.c_str()); return(false); } data += bytes_written; remaining -= bytes_written; } flock(fd, LOCK_UN); close(fd); return(true); } String get_cwd() { return(std::filesystem::current_path()); } void set_cwd(String path) { chdir(path.c_str()); } time_t file_mtime(String file_name) { struct stat info; if (stat(file_name.c_str(), &info) != 0) { return(0); } else { return(info.st_mtime); } } void unlink(String file_name) { remove(file_name.c_str()); } String expand_path(String path, String relative_to_path) { String result; if(relative_to_path == "") relative_to_path = get_cwd(); auto base_path = split(relative_to_path, "/"); auto rel_path = split(path, "/"); for(auto& s : rel_path) { if(s == "..") { base_path.pop_back(); } else if(s == ".") { } else { base_path.push_back(s); } } return(join(base_path, "/")); } f64 microtime() { return ((f64)std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count()) / 1000000; } u64 time() { return(std::time(0)); } String date(String format, u64 timestamp) { String ts; String fmt; if(timestamp > 0) ts = String("-d '@")+std::to_string(timestamp)+"'"; if(format != "") fmt = String("+'"+format+"'"); return(trim(shell_exec("date "+ts+" "+fmt))); } String gmdate(String format, u64 timestamp) { String ts; String fmt; if(timestamp > 0) ts = String("-d '@")+std::to_string(timestamp)+"'"; if(format == "RFC1123") format = "%a, %d %b %Y %T GMT"; if(format != "") fmt = String("+'"+format+"'"); return(trim(shell_exec("date -u "+ts+" "+fmt))); } u64 parse_time(String time_String) { return(int_val(trim(shell_exec("date -u -d "+shell_escape(time_String)+" +'%s'")))); } u64 socket_connect(String host, short port) { /*String addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; String sockaddr *ai_addr; char *ai_canonname; String addrinfo *ai_next; };*/ auto sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sockfd < 0) { print("SOCKET ERROR (could not open socket)\n"); perror("SOCKET ERROR "); return(0); } struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(host.c_str()); if(connect(sockfd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { print("SOCKET ERROR (could not connect to address " + String(host) + ":" + std::to_string(port) + ")\n"); perror("SOCKET ERROR "); return(0); } context->resources.sockets.push_back(sockfd); return(sockfd); } void socket_close(u64 sockfd) { close(sockfd); } bool socket_write(u64 sockfd, String data) { return(write(sockfd, data.c_str(), data.length()) >= 0); } String socket_read(u64 sockfd, u32 max_length, u32 timeout) { struct timeval tv; tv.tv_sec = timeout; tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); char buf[max_length+1]; auto byte_count = recv(sockfd, buf, sizeof(buf), 0); if(byte_count > 0) { buf[byte_count] = 0; String result(buf, byte_count+1); return(result); } return(""); } String memcache_escape_key(String key) { String result; for(auto c : key) { if(isspace(c)) c = '_'; result.append(1, c); } return(result); } StringList memcache_escape_keys(StringList keys) { StringList result; for(auto s : keys) { result.push_back(memcache_escape_key(s)); } return(result); } u64 memcache_connect(String host, short port) { return(socket_connect(host, port)); } String memcache_command(u64 connection, String command) { socket_write(connection, command+"\r\n"); return(socket_read(connection)); // FIXME: do multi-chunk until END line is received! } bool memcache_set(u64 connection, String key, String value, u64 expires_in) { socket_write(connection, // set KEY META_DATA EXPIRY_TIME LENGTH_IN_BYTES String("set ") + memcache_escape_key(key) + " 0 " + std::to_string(expires_in) + " " + std::to_string(value.length()) + "\r\n" + value + "\r\n"); return("STORED" == trim(socket_read(connection))); } bool memcache_delete(u64 connection, String key) { socket_write(connection, // set KEY META_DATA EXPIRY_TIME LENGTH_IN_BYTES String("delete ") + memcache_escape_key(key) + "\r\n" ); return("DELETED" == trim(socket_read(connection))); } String memcache_get(u64 connection, String key, String default_value) { auto res = memcache_command(connection, String("get ")+memcache_escape_key(key)); String t = nibble(res, " "); if(t == "VALUE") { String key = nibble(res, " "); String meta = nibble(res, " "); u32 length = stoi(nibble(res, "\r\n")); return(res.substr(0, length)); } return(default_value); } StringMap memcache_get_multiple(u64 connection, StringList keys) { StringMap result; // to do: escape key String auto res = memcache_command(connection, String("get ")+join(memcache_escape_keys(keys), " ")); while(res.length() > 0) { String t = nibble(res, " "); if(t == "VALUE") { String key = nibble(res, " "); String meta = nibble(res, " "); u32 length = stoi(nibble(res, "\r\n")); result[key] = res.substr(0, length); res = res.substr(length+2); } } return(result); } void on_segfault(int sig) { String trace = capture_backtrace_string(32, 1); String sig_label = signal_name(sig); if(sig_label != "") fprintf(stderr, "SEG FAULT: %d (%s):\n%s", sig, sig_label.c_str(), trace.c_str()); else fprintf(stderr, "SEG FAULT: %d:\n%s", sig, trace.c_str()); exit(1); } struct Worker { pid_t pid; }; std::map workers; #include #include #include pid_t spawn_subprocess(std::function exec_after_spawn) { parent_pid = getpid(); pid_t p; p = fork(); if(p == 0) { my_pid = getpid(); //printf("(C) child procress started, PID:%i\n", my_pid); prctl(PR_SET_PDEATHSIG, SIGHUP); exec_after_spawn(); return(0); } else { Worker w; w.pid = p; workers[w.pid] = w; printf("(P) child procress spawned: PID %i\n", p); return(p); } } pid_t task_pid(String key) { String status_file_name = context->server->config["BIN_DIRECTORY"] + "/task-" + key; String status_file = file_get_contents(status_file_name); pid_t p = 0; if(status_file != "") { p = int_val(status_file); if(kill(p, 0) == 0) // process is still running return(p); unlink(status_file_name); } return(p); } pid_t task(String key, std::function exec_after_spawn, u64 timeout) { String status_file_name = context->server->config["BIN_DIRECTORY"] + "/task-" + key; String status_file = file_get_contents(status_file_name); pid_t p; if(status_file != "") { p = int_val(status_file); if(kill(p, 0) == 0) // process is still running { printf("(P) worker process '%s' already running: PID %i\n", key.c_str(), p); return(p); } //printf("(P) worker process '%s' had crashed: PID %i\n", key.c_str(), p); unlink(status_file_name); } p = fork(); if(p == 0) { my_pid = getpid(); file_put_contents(status_file_name, std::to_string(my_pid)); close(context->resources.client_socket); context->resources.client_socket = 0; //printf("(C) child procress started, PID:%i\n", my_pid); //prctl(PR_SET_PDEATHSIG, SIGHUP); exec_after_spawn(); unlink(status_file_name); printf("(P) worker process '%s' terminated: PID %i\n", key.c_str(), my_pid); exit(0); } else { printf("(P) worker process '%s' spawned: PID %i\n", key.c_str(), p); return(p); } } #include pid_t task_repeat(String key, u64 interval, std::function exec_after_spawn, u64 timeout) { auto repeater_function = [&]() { while (true) { exec_after_spawn(); printf("(P) worker process '%s' sleeping\n", key.c_str()); usleep((s64)(interval*1000000)); } }; return(task(key, repeater_function, timeout)); } void on_child_exit(int sig) { pid_t pid; int status; if ((pid = waitpid(-1, &status, WNOHANG)) != -1) { if(workers.count(pid) > 0) { workers.erase(pid); printf("(P) child terminated (PID:%i)\n", pid); //spawn_subprocess(); } } } StringList ls(String dir) { return(split(trim(shell_exec("ls -1 "+shell_escape(dir))), "\n")); } StringMap make_server_settings() { StringMap cfg; cfg["BIN_DIRECTORY"] = "/tmp/uce/work"; cfg["COMPILE_SCRIPT"] = "scripts/compile"; cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template"; cfg["LIT_ESC"] = "3d5b5_1"; cfg["CONTENT_TYPE"] = "text/html; charset=utf-8"; cfg["FCGI_SOCKET_PATH"] = "/run/uce.sock"; cfg["TMP_UPLOAD_PATH"] = "/tmp/uce/uploads"; cfg["SESSION_PATH"] = "/tmp/uce/sessions"; cfg["COMPILER_SYS_PATH"] = "."; cfg["PRECOMPILE_FILES_IN"] = "."; cfg["HTTP_PORT"] = std::to_string(8080); cfg["SESSION_TIME"] = std::to_string(60*60*24*30); cfg["WORKER_COUNT"] = std::to_string(4); cfg["MAX_MEMORY"] = std::to_string(1024*1024*16); for(auto& it : split_kv(file_get_contents("/etc/uce/settings.cfg"))) { cfg[it.first] = it.second; } if(cfg["FCGI_SOCKET_PATH"] == "" && cfg["SOCKET_PATH"] != "") cfg["FCGI_SOCKET_PATH"] = cfg["SOCKET_PATH"]; if(cfg["SOCKET_PATH"] == "" && cfg["FCGI_SOCKET_PATH"] != "") cfg["SOCKET_PATH"] = cfg["FCGI_SOCKET_PATH"]; if(cfg["FCGI_PORT"] == "" && cfg["LISTEN_PORT"] != "") cfg["FCGI_PORT"] = cfg["LISTEN_PORT"]; if(cfg["LISTEN_PORT"] == "" && cfg["FCGI_PORT"] != "") cfg["LISTEN_PORT"] = cfg["FCGI_PORT"]; return(cfg); }