diff --git a/c/sherver.c b/c/sherver.c new file mode 100644 index 0000000..fe4e995 --- /dev/null +++ b/c/sherver.c @@ -0,0 +1,404 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int sherver_bind_builtin(WORD_LIST *args) { + uint quiet = 0; + char *host = "127.0.0.1"; + uint port = 0; + char *var_name = "SHERVER_FD"; + uint arg = 0; + + reset_internal_getopt(); + + while((arg = internal_getopt(args, "qh:p:v:")) != -1) { + if(arg == 'q') { + quiet = 1; + } else if(arg == 'h') { + host = list_optarg; + } else if(arg == 'p') { + char *end = NULL; + unsigned long res = strtoul(list_optarg, &end, 10); + + if(*end != '\0' || res > 65535) { + builtin_usage(); + return EX_USAGE; + } + + port = res; + } else if(arg == 'v') { + var_name = list_optarg; + } else { + builtin_usage(); + return EX_USAGE; + } + } + + unbind_variable(var_name); + int server_sock = 0; + int on = 1; + + if((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("cannot open server socket: %s", strerror(errno)); + } + + return errno; + } else if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("cannot configure server socket: %s", strerror(errno)); + } + + return errno; + } + + int flags = fcntl(server_sock, F_GETFL, 0); + + if(flags < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("cannot obtain information on socket: %s", strerror(errno)); + } + + return errno; + } else if(fcntl(server_sock, F_SETFL, flags | O_NONBLOCK) < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("cannot make server socket non-blocking: %s", strerror(errno)); + } + + return errno; + } + + struct sockaddr_in server; + memset(&server, 0, sizeof(server)); + inet_pton(AF_INET, host, &server.sin_addr); + server.sin_family = AF_INET; + server.sin_port = htons(port); + + if(bind(server_sock, (struct sockaddr *) &server, sizeof(server)) < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("cannot bind to host: %s", strerror(errno)); + } + + return errno; + } + + int reuse_addr = 1; + setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr)); + + if(listen(server_sock, 0) < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("cannot start listening on host: %s", strerror(errno)); + } + + return errno; + } + + char ibuf[INT_STRLEN_BOUND(int) + 1] = { '\0' }; + char *sock_str = fmtulong(server_sock, 10, ibuf, sizeof(ibuf), 0); + bind_global_variable(var_name, sock_str, 0); + return EXECUTION_SUCCESS; +} + +char *sherver_bind_doc[] = { + "Open a socket on a network interface for accepting connections.", + "", + "Binds a socket to a specified interface and port for accepting incoming", + "connections. If NAME is supplied (via -v), then the server fd is stored in", + "a variable with that name. Otherwise, the name SHERVER_FD is used.", + "", + "Options:", + " -q enable quiet mode. Do not report error messages.", + " -h HOST the HOST to bind to for incoming connections.", + " -p PORT the PORT to listen for incoming connections.", + " -v NAME the NAME to bind the server fd to.", + "", + "Exit Status:", + "On a successful binding, sherver-bind exits with a status of 0.", + "Otherwise, if an intermediate network operation fails, it returns with", + "the error code from that failure. SHERVER_FD (or the variable name", + "supplied with -v) is unset in the event of a failure.", + (char *) NULL +}; + +struct builtin sherver_bind_struct = { + "sherver-bind", + sherver_bind_builtin, + BUILTIN_ENABLED, + sherver_bind_doc, + "sherver-bind [-q] [-h HOST] [-p PORT] [-v NAME]", + 0 +}; + +int sherver_select_builtin(WORD_LIST *args) { + uint quiet = 0; + uint ignore_timeout = 0; + time_t timeout_secs = 5; + char *var_name = "READY_FDS"; + uint arg = 0; + + reset_internal_getopt(); + + while((arg = internal_getopt(args, "qit:v:")) != -1) { + if(arg == 'q') { + quiet = 1; + } else if(arg == 'i') { + ignore_timeout = 1; + } else if(arg == 'v') { + var_name = list_optarg; + } else if(arg == 't') { + char *end = NULL; + unsigned long res = strtoul(list_optarg, &end, 10); + + if(*end != '\0') { + builtin_usage(); + return EX_USAGE; + } + + timeout_secs = res; + } else { + builtin_usage(); + return EX_USAGE; + } + } + + int max_fd = 0; + fd_set read_set, reads; + FD_ZERO(&read_set); + + for(args = loptend; args; args = args->next) { + char *end = NULL; + uint fd = strtoul(args->word->word, &end, 10); + + if(*end != '\0' || fd >= FD_SETSIZE) { + if(!quiet) { + builtin_error("can only monitor file descriptors less than %lu", fd); + } + + continue; + } else if(fd > max_fd) { + max_fd = fd; + } + + FD_SET(fd, &read_set); + } + + SHELL_VAR *var = find_or_make_array_variable(var_name, 1); + + if(var == 0 || readonly_p(var) || noassign_p(var)) { + if(!quiet) { + builtin_error("unable to create array variable %s", var_name); + } + + return EXECUTION_FAILURE; + } else if(array_p(var) == 0) { + if(!quiet) { + builtin_error("%s is not an indexed array", var_name); + } + + return EXECUTION_FAILURE; + } + + if(invisible_p(var)) { + VUNSETATTR(var, att_invisible); + } + + array_flush(array_cell(var)); + + while(1) { + struct timeval timeout = { + .tv_sec = timeout_secs, .tv_usec = 0 + }; + + memcpy(&reads, &read_set, sizeof(read_set)); + int result = select(max_fd + 1, &reads, NULL, NULL, &timeout); + + if(!ignore_timeout && result == 0) { + return EAGAIN; + } else if(result < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("unable to poll file descriptors: %s", strerror(errno)); + } + + return cached; + } else if(result > 0) { + break; + } + } + + int fd = 0; + int idx = 0; + + for(; fd < max_fd + 1; fd += 1) { + if(FD_ISSET(fd, &reads)) { + char fbuf[FD_SETSIZE] = { '\0' }; + char *fd_str = fmtulong(fd, 10, fbuf, sizeof(fbuf), 0); + bind_array_element(var, idx, fd_str, 0); + idx += 1; + } + } + + return EXECUTION_SUCCESS; +} + +char *sherver_select_doc[] = { + "Monitor a list of file descriptors for reading activity.", + "", + "Takes a list of 0 or more file descriptors to monitor for reading activity.", + "`sherver-select` will monitor for up to SECONDS seconds (a default of 5)", + "unless -i is provided, in which case the timeout is ignored. Any file", + "descriptors that are ready for reading are copied into the array named NAME", + "(which defaults to READY_FDS).", + "", + "Options:", + " -q enable quiet mode. Do not report error messages.", + " -i ignore the timeout and try again (effectively disables -t).", + " -t SECONDS the number of SECONDS to monitor for.", + " -v NAME the NAME to bind the server fd to.", + "", + "Exit Status:", + "If successful, sherver-select returns 0. Otherwise, it returns the error", + "code of select(2).", + (char *) NULL +}; + +struct builtin sherver_select_struct = { + "sherver-select", + sherver_select_builtin, + BUILTIN_ENABLED, + sherver_select_doc, + "sherver-select [-q] [-i] [-t SECONDS] [-v NAME] [FD ...]", + 0 +}; + +int sherver_accept_builtin(WORD_LIST *args) { + uint quiet = 0; + char *var_name = "CLIENT_INFO"; + uint arg = 0; + + reset_internal_getopt(); + + while((arg = internal_getopt(args, "qv:")) != -1) { + if(arg == 'q') { + quiet = 1; + } else if(arg == 'v') { + var_name = list_optarg; + } else { + builtin_usage(); + return EX_USAGE; + } + } + + args = loptend; + + if(!args) { + if(!quiet) { + builtin_error("no server file descriptor given."); + } + + return EXECUTION_FAILURE; + } + + char *end = NULL; + unsigned long server_fd = strtoul(args->word->word, &end, 10); + + if(*end != '\0') { + if(!quiet) { + builtin_error("'%s' is not a valid file descriptor.", args->word->word); + } + + return EXECUTION_FAILURE; + } + + struct sockaddr_in peer; + socklen_t peer_size = sizeof(peer); + int client_sock = accept(server_fd, (struct sockaddr *) &peer, &peer_size); + + if(client_sock < 0) { + int cached = errno; + + if(!quiet) { + builtin_error("could not accept connection: %s", strerror(errno)); + } + + return cached; + } + + char ip[INET_ADDRSTRLEN] = { '\0' }; + unsigned short port = ntohs(peer.sin_port); + inet_ntop(AF_INET, &peer.sin_addr, ip, INET_ADDRSTRLEN); + SHELL_VAR *var = find_or_make_array_variable(var_name, 1); + char sbuf[FD_SETSIZE] = { '\0' }; + char pbuf[6] = { '\0' }; + char *sock_str = fmtulong(client_sock, 10, sbuf, sizeof(sbuf), 0); + char *port_str = fmtulong(port, 10, pbuf, sizeof(pbuf), 0); + + if(var == 0 || readonly_p(var) || noassign_p(var)) { + if(!quiet) { + builtin_error("unable to create array variable %s", var_name); + } + + return EXECUTION_FAILURE; + } else if(array_p(var) == 0) { + if(!quiet) { + builtin_error("%s is not an indexed array", var_name); + } + + return EXECUTION_FAILURE; + } + + if(invisible_p(var)) { + VUNSETATTR(var, att_invisible); + } + + array_flush(array_cell(var)); + bind_array_element(var, 0, sock_str, 0); + bind_array_element(var, 1, ip, 0); + bind_array_element(var, 2, port_str, 0); + return EXECUTION_SUCCESS; +} + +char *sherver_accept_doc[] = { + "Accept a connection from a server socket file descriptor.", + "", + "Accepts a connection from a server file descriptor. The client file", + "descriptor, client ip address, and client port are stored in the array", + "named NAME (default CLIENT_INFO) at indexes 0, 1, and 2 respectively.", + "", + "Options:", + " -q enable quiet mode. Do not report error messages.", + " -v NAME the NAME to bind the server fd to.", + "", + "Exit Status:", + "If successful, sherver-accept returns 0. Otherwise, it returns the error", + "code of accept(2).", + (char *) NULL +}; + +struct builtin sherver_accept_struct = { + "sherver-accept", + sherver_accept_builtin, + BUILTIN_ENABLED, + sherver_accept_doc, + "sherver-accept [-q] [-v NAME] [SERVER FD]", + 0 +};