grimoire/c/sherver.c

402 lines
9.8 KiB
C

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <loadables.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
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;
}
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
};