Syncing the code with what was actually in the videos.
This commit is contained in:
parent
4305a58bb9
commit
40d934686f
|
@ -1,3 +1,4 @@
|
|||
CFLAGS=-g -O2 -Wall -Wextra -I/usr/local/include -Isrc -rdynamic $(OPTFLAGS)
|
||||
LIBS=-llcthw $(OPTLIBS)
|
||||
LDFLAGS=-L/usr/local/lib $(LIBS)
|
||||
PREFIX?=/usr/local
|
||||
|
@ -17,6 +18,8 @@ all: $(TARGET) $(SO_TARGET) tests bin/statserve
|
|||
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
|
||||
dev: all
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
$(TARGET): CFLAGS += -fPIC
|
||||
$(TARGET): build $(OBJECTS)
|
||||
ar rcs $@ $(OBJECTS)
|
||||
|
@ -25,8 +28,6 @@ $(TARGET): build $(OBJECTS)
|
|||
$(SO_TARGET): $(TARGET) $(OBJECTS)
|
||||
$(CC) -shared -o $@ $(LDFLAGS) $(LIBS) $(OBJECTS)
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
build:
|
||||
@mkdir -p build
|
||||
@mkdir -p bin
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include <statserve.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -14,5 +16,6 @@ int main(int argc, char *argv[])
|
|||
return 0;
|
||||
|
||||
error:
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "net.h"
|
||||
|
@ -8,7 +15,6 @@
|
|||
struct tagbstring NL = bsStatic("\n");
|
||||
struct tagbstring CRLF = bsStatic("\r\n");
|
||||
|
||||
|
||||
int nonblock(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
|
@ -22,6 +28,31 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket)
|
||||
{
|
||||
int rc = 0;
|
||||
|
@ -73,23 +104,28 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int attempt_listen(struct addrinfo *info)
|
||||
{
|
||||
int sockfd = 0;
|
||||
int sockfd = -1; // default fail
|
||||
int rc = -1;
|
||||
int yes = 1;
|
||||
|
||||
check(info != NULL, "Invalid addrinfo.");
|
||||
|
||||
// create a socket with the addrinfo
|
||||
sockfd = socket(info->ai_family, info->ai_socktype,
|
||||
info->ai_protocol);
|
||||
check_debug(sockfd != -1, "Failed to bind to address result. Trying more.");
|
||||
check_debug(sockfd != -1, "Failed to bind to address. Trying more.");
|
||||
|
||||
// set the SO_REUSEADDR option on the socket
|
||||
rc = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||
check_debug(rc == 0, "Failed to set SO_REISEADDR.");
|
||||
check_debug(rc == 0, "Failed to set SO_REUSADDR.");
|
||||
|
||||
// attempt to bind to it
|
||||
rc = bind(sockfd, info->ai_addr, info->ai_addrlen);
|
||||
check_debug(rc == 0, "Failed to bind socket.");
|
||||
|
||||
check_debug(rc == 0, "Failed to find socket.");
|
||||
|
||||
// finally listen with a backlog
|
||||
rc = listen(sockfd, BACKLOG);
|
||||
check_debug(rc == 0, "Failed to listen to socket.");
|
||||
|
||||
|
@ -99,10 +135,11 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int server_listen(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
int sockfd = -1;
|
||||
int sockfd = -1; // default fail value
|
||||
struct addrinfo *info = NULL;
|
||||
struct addrinfo *next_p = NULL;
|
||||
struct addrinfo addr = {
|
||||
|
@ -111,51 +148,26 @@ int server_listen(const char *host, const char *port)
|
|||
.ai_flags = AI_PASSIVE
|
||||
};
|
||||
|
||||
check(host != NULL, "Must give a valid host.");
|
||||
check(port != NULL, "Must have a valid port.");
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// get the address info for host and port
|
||||
rc = getaddrinfo(NULL, port, &addr, &info);
|
||||
check(rc == 0, "Failed to get address info for connect.");
|
||||
|
||||
|
||||
// cycle through the available list to find one
|
||||
for(next_p = info; next_p != NULL; next_p = next_p->ai_next)
|
||||
{
|
||||
// attempt to listen to each one
|
||||
sockfd = attempt_listen(next_p);
|
||||
if(sockfd != -1) break;
|
||||
}
|
||||
|
||||
// either we found one and were able to listen or nothing.
|
||||
check(sockfd != -1, "All possible addresses failed.");
|
||||
|
||||
error: //fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
// this gets set by the above to either -1 or valid
|
||||
return sockfd;
|
||||
|
||||
error: // fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
#ifndef _statserve_net_h
|
||||
#define _statserve_net_h
|
||||
#ifndef _net_h
|
||||
#define _net_h
|
||||
|
||||
#include <netdb.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
|
||||
#define BACKLOG 10
|
||||
|
||||
int nonblock(int fd);
|
||||
|
||||
int client_connect(char *host, char *port);
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int write_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int attempt_listen(struct addrinfo *info);
|
||||
|
||||
int server_listen(const char *host, const char *port);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,84 +1,89 @@
|
|||
#include <lcthw/ringbuffer.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include "net.h"
|
||||
#include <netdb.h>
|
||||
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
|
||||
int client_handler(int fd)
|
||||
{
|
||||
int rc = 0;
|
||||
RingBuffer *sock_rb = RingBuffer_create(RB_SIZE);
|
||||
// child process
|
||||
|
||||
while(read_some(sock_rb, fd, 1) != -1) {
|
||||
if(write_some(sock_rb, fd, 1) == -1) {
|
||||
log_info("Client closed.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = close(fd);
|
||||
check(rc != -1, "Failed to close fd.");
|
||||
|
||||
error: // fallthrough
|
||||
if(sock_rb) RingBuffer_destroy(sock_rb);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void handle_sigchld(int sig) {
|
||||
void handle_sigchild(int sig) {
|
||||
sig = 0; // ignore it
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
void client_handler(int client_fd)
|
||||
{
|
||||
int rc = 0;
|
||||
// need a ringbuffer for the input
|
||||
RingBuffer *sock_rb = RingBuffer_create(RB_SIZE);
|
||||
|
||||
// read_some in a loop
|
||||
while(read_some(sock_rb, client_fd, 1) != -1) {
|
||||
// write_it back off the ringbuffer
|
||||
if(write_some(sock_rb, client_fd, 1) == -1) {
|
||||
debug("Client closed.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// close the socket
|
||||
rc = close(client_fd);
|
||||
check(rc != -1, "Failed to close the socket.");
|
||||
|
||||
error: // fallthrough
|
||||
if(sock_rb) RingBuffer_destroy(sock_rb);
|
||||
exit(0); // just exit the child process
|
||||
}
|
||||
|
||||
int echo_server(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t sin_size = sizeof(client_addr);
|
||||
int server_socket = 0;
|
||||
int client_fd = 0;
|
||||
int rc = 0;
|
||||
|
||||
struct sigaction sa = {
|
||||
.sa_handler = handle_sigchld,
|
||||
.sa_handler = handle_sigchild,
|
||||
.sa_flags = SA_RESTART | SA_NOCLDSTOP
|
||||
};
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// create a sigaction that handles SIGCHLD
|
||||
sigemptyset(&sa.sa_mask);
|
||||
rc = sigaction(SIGCHLD, &sa, 0);
|
||||
check(rc != -1, "Failed to setup signal handler for child processes.");
|
||||
|
||||
// listen on the given port and host
|
||||
server_socket = server_listen(host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.",
|
||||
host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.", host, port);
|
||||
|
||||
while (1) {
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
while(1) {
|
||||
// accept the connection
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
check(client_fd >= 0, "Failed to accept connection.");
|
||||
|
||||
log_info("Client connected.");
|
||||
debug("Client connected.");
|
||||
|
||||
rc = fork();
|
||||
check(rc != -1, "Failed to fork!");
|
||||
|
||||
if(rc == 0) {
|
||||
// in the child process
|
||||
close(server_socket);
|
||||
// child process
|
||||
close(server_socket); // don't need this
|
||||
// handle the client
|
||||
client_handler(client_fd);
|
||||
} else {
|
||||
// server process
|
||||
close(client_fd);
|
||||
close(client_fd); // don't need this
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
error: // fallthrough
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
int echo_server(const char *host, const char *port);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#define _minunit_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dbg.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define mu_suite_start() char *message = NULL
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
#include "minunit.h"
|
||||
#include <statserve.h>
|
||||
#include <dlfcn.h>
|
||||
#include "statserve.h"
|
||||
|
||||
|
||||
char *test_statserve()
|
||||
char *test_dummy()
|
||||
{
|
||||
|
||||
// mu_assert(echo_server("127.0.0.1", "7899") == 0, "Failed to start echo server.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
char *all_tests()
|
||||
{
|
||||
mu_suite_start();
|
||||
|
||||
mu_run_test(test_statserve);
|
||||
mu_run_test(test_dummy);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CFLAGS=-g -O2 -Wall -Wextra -Isrc -I/usr/local/include -rdynamic $(OPTFLAGS)
|
||||
CFLAGS=-g -O2 -Wall -Wextra -I/usr/local/include -Isrc -rdynamic $(OPTFLAGS)
|
||||
LIBS=-llcthw $(OPTLIBS)
|
||||
LDFLAGS=-L/usr/local/lib $(LIBS)
|
||||
PREFIX?=/usr/local
|
||||
|
@ -18,6 +18,8 @@ all: $(TARGET) $(SO_TARGET) tests bin/statserve
|
|||
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
|
||||
dev: all
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
$(TARGET): CFLAGS += -fPIC
|
||||
$(TARGET): build $(OBJECTS)
|
||||
ar rcs $@ $(OBJECTS)
|
||||
|
@ -26,8 +28,6 @@ $(TARGET): build $(OBJECTS)
|
|||
$(SO_TARGET): $(TARGET) $(OBJECTS)
|
||||
$(CC) -shared -o $@ $(LDFLAGS) $(LIBS) $(OBJECTS)
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
build:
|
||||
@mkdir -p build
|
||||
@mkdir -p bin
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include <statserve.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -14,5 +16,6 @@ int main(int argc, char *argv[])
|
|||
return 0;
|
||||
|
||||
error:
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "net.h"
|
||||
|
@ -8,7 +15,6 @@
|
|||
struct tagbstring NL = bsStatic("\n");
|
||||
struct tagbstring CRLF = bsStatic("\r\n");
|
||||
|
||||
|
||||
int nonblock(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
|
@ -22,6 +28,31 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket)
|
||||
{
|
||||
int rc = 0;
|
||||
|
@ -73,23 +104,28 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int attempt_listen(struct addrinfo *info)
|
||||
{
|
||||
int sockfd = 0;
|
||||
int sockfd = -1; // default fail
|
||||
int rc = -1;
|
||||
int yes = 1;
|
||||
|
||||
check(info != NULL, "Invalid addrinfo.");
|
||||
|
||||
// create a socket with the addrinfo
|
||||
sockfd = socket(info->ai_family, info->ai_socktype,
|
||||
info->ai_protocol);
|
||||
check_debug(sockfd != -1, "Failed to bind to address result. Trying more.");
|
||||
check_debug(sockfd != -1, "Failed to bind to address. Trying more.");
|
||||
|
||||
// set the SO_REUSEADDR option on the socket
|
||||
rc = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||
check_debug(rc == 0, "Failed to set SO_REISEADDR.");
|
||||
check_debug(rc == 0, "Failed to set SO_REUSADDR.");
|
||||
|
||||
// attempt to bind to it
|
||||
rc = bind(sockfd, info->ai_addr, info->ai_addrlen);
|
||||
check_debug(rc == 0, "Failed to bind socket.");
|
||||
|
||||
check_debug(rc == 0, "Failed to find socket.");
|
||||
|
||||
// finally listen with a backlog
|
||||
rc = listen(sockfd, BACKLOG);
|
||||
check_debug(rc == 0, "Failed to listen to socket.");
|
||||
|
||||
|
@ -99,10 +135,11 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int server_listen(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
int sockfd = -1;
|
||||
int sockfd = -1; // default fail value
|
||||
struct addrinfo *info = NULL;
|
||||
struct addrinfo *next_p = NULL;
|
||||
struct addrinfo addr = {
|
||||
|
@ -111,73 +148,54 @@ int server_listen(const char *host, const char *port)
|
|||
.ai_flags = AI_PASSIVE
|
||||
};
|
||||
|
||||
check(host != NULL, "Must give a valid host.");
|
||||
check(port != NULL, "Must have a valid port.");
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// get the address info for host and port
|
||||
rc = getaddrinfo(NULL, port, &addr, &info);
|
||||
check(rc == 0, "Failed to get address info for connect.");
|
||||
|
||||
|
||||
// cycle through the available list to find one
|
||||
for(next_p = info; next_p != NULL; next_p = next_p->ai_next)
|
||||
{
|
||||
// attempt to listen to each one
|
||||
sockfd = attempt_listen(next_p);
|
||||
if(sockfd != -1) break;
|
||||
}
|
||||
|
||||
// either we found one and were able to listen or nothing.
|
||||
check(sockfd != -1, "All possible addresses failed.");
|
||||
|
||||
error: //fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
// this gets set by the above to either -1 or valid
|
||||
return sockfd;
|
||||
|
||||
error: // fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
bstring read_line(RingBuffer *input, const char line_ending)
|
||||
{
|
||||
int i = 0;
|
||||
bstring result = NULL;
|
||||
|
||||
// not super efficient
|
||||
// read a character at a time from the ring buffer
|
||||
for(i = 0; i < RingBuffer_available_data(input); i++) {
|
||||
// if the buffer has line ending
|
||||
if(input->buffer[i] == line_ending) {
|
||||
// get that much fromt he ring buffer
|
||||
result = RingBuffer_gets(input, i);
|
||||
check(result, "Failed to get line from RingBuffer.");
|
||||
check(RingBuffer_available_data(input) >= 1, "Not enough data in the RingBuffer after reading a line.");
|
||||
// eat the \n in the buffer
|
||||
check(result, "Failed to get line from RingBuffer");
|
||||
// make sure that we got the right amount
|
||||
check(RingBuffer_available_data(input) >= 1,
|
||||
"Not enough data in the RingBuffer after reading line.");
|
||||
// and commit it
|
||||
RingBuffer_commit_read(input, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug("LINE: %s", bdata(result));
|
||||
|
||||
// notice this will fail in the cases where we get a set of data
|
||||
// on the wire that does not have a line ending yet
|
||||
return result;
|
||||
error:
|
||||
return NULL;
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
#ifndef _statserve_net_h
|
||||
#define _statserve_net_h
|
||||
#ifndef _net_h
|
||||
#define _net_h
|
||||
|
||||
#include <netdb.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
|
||||
#define BACKLOG 10
|
||||
|
||||
int nonblock(int fd);
|
||||
|
||||
int client_connect(char *host, char *port);
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int write_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int attempt_listen(struct addrinfo *info);
|
||||
|
||||
int server_listen(const char *host, const char *port);
|
||||
|
||||
bstring read_line(RingBuffer *input, const char line_ending);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
#include <lcthw/ringbuffer.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <lcthw/hashmap.h>
|
||||
#include <lcthw/stats.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
#include "net.h"
|
||||
#include <netdb.h>
|
||||
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
struct tagbstring LINE_SPLIT = bsStatic(" ");
|
||||
struct tagbstring CREATE = bsStatic("create");
|
||||
struct tagbstring STDDEV = bsStatic("stddev");
|
||||
|
@ -25,8 +23,8 @@ struct tagbstring DNE = bsStatic("DNE\n");
|
|||
struct tagbstring EXISTS = bsStatic("EXISTS\n");
|
||||
const char LINE_ENDING = '\n';
|
||||
|
||||
// this is just temporary to work out the protocol
|
||||
// it actually doesn't work in practice because forking
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
|
||||
Hashmap *DATA = NULL;
|
||||
|
||||
struct Command;
|
||||
|
@ -40,11 +38,18 @@ typedef struct Command {
|
|||
handler_cb handler;
|
||||
} Command;
|
||||
|
||||
|
||||
typedef struct Record {
|
||||
bstring name;
|
||||
Stats *stat;
|
||||
} Record;
|
||||
|
||||
void handle_sigchild(int sig) {
|
||||
sig = 0; // ignore it
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
void send_reply(RingBuffer *send_rb, bstring reply)
|
||||
{
|
||||
RingBuffer_puts(send_rb, reply);
|
||||
|
@ -52,23 +57,34 @@ void send_reply(RingBuffer *send_rb, bstring reply)
|
|||
|
||||
int handle_create(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
// if the name is in the DATA map then return exists
|
||||
if(Hashmap_get(DATA, cmd->name)) {
|
||||
send_reply(send_rb, &EXISTS);
|
||||
} else {
|
||||
log_info("create: %s %s", bdata(cmd->name), bdata(cmd->number));
|
||||
// allocate a recrod
|
||||
debug("create: %s %s", bdata(cmd->name), bdata(cmd->number));
|
||||
|
||||
Record *info = calloc(sizeof(Record), 1);
|
||||
check_mem(info);
|
||||
|
||||
// set its stat element
|
||||
info->stat = Stats_create();
|
||||
check_mem(info->stat);
|
||||
|
||||
// set its name element
|
||||
info->name = bstrcpy(cmd->name);
|
||||
check_mem(info->name);
|
||||
|
||||
// do a first sample
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
|
||||
Hashmap_set(DATA, info->name, info);
|
||||
|
||||
// add it to the hashmap
|
||||
rc = Hashmap_set(DATA, info->name, info);
|
||||
check(rc == 0, "Failed to add data to map.");
|
||||
|
||||
// send an OK
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
|
@ -77,6 +93,47 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int handle_sample(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
// get the info from the hashmap
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
|
||||
if(info == NULL) {
|
||||
// if it doesn't exist then DNE
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
// else run sample on it, return the mean
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
bstring reply = bformat("%f\n", Stats_mean(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_delete(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
log_info("delete: %s", bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
Hashmap_delete(DATA, cmd->name);
|
||||
|
||||
free(info->stat);
|
||||
bdestroy(info->name);
|
||||
free(info);
|
||||
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_mean(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
log_info("mean: %s", bdata(cmd->name));
|
||||
|
@ -93,19 +150,19 @@ int handle_mean(Command *cmd, RingBuffer *send_rb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int handle_sample(Command *cmd, RingBuffer *send_rb)
|
||||
int handle_stddev(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
log_info("sample: %s %s", bdata(cmd->name), bdata(cmd->number));
|
||||
log_info("stddev: %s", bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
bstring reply = bformat("%f\n", Stats_mean(info->stat));
|
||||
bstring reply = bformat("%f\n", Stats_stddev(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -133,43 +190,9 @@ int handle_dump(Command *cmd, RingBuffer *send_rb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int handle_delete(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
log_info("delete: %s", bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
free(info->stat);
|
||||
bdestroy(info->name);
|
||||
free(info);
|
||||
Hashmap_delete(DATA, cmd->name);
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_stddev(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
log_info("stddev: %s", bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
bstring reply = bformat("%f\n", Stats_stddev(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int parse_command(struct bstrList *splits, Command *cmd)
|
||||
{
|
||||
// get the command
|
||||
cmd->command = splits->entry[0];
|
||||
|
||||
if(biseq(cmd->command, &CREATE)) {
|
||||
|
@ -204,33 +227,32 @@ int parse_command(struct bstrList *splits, Command *cmd)
|
|||
|
||||
return 0;
|
||||
error:
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int parse_line(bstring data, RingBuffer *send_rb)
|
||||
{
|
||||
int rc = 0;
|
||||
int rc = -1;
|
||||
Command cmd = {.command = NULL};
|
||||
|
||||
// split data on line boundaries
|
||||
struct bstrList *splits = bsplits(data, &LINE_SPLIT);
|
||||
check(splits != NULL, "Bad data.");
|
||||
|
||||
// parse it into a command
|
||||
rc = parse_command(splits, &cmd);
|
||||
check(rc == 0, "Failed to parse command.");
|
||||
|
||||
// call the command handler for that command
|
||||
rc = cmd.handler(&cmd, send_rb);
|
||||
|
||||
bstrListDestroy(splits);
|
||||
|
||||
return rc;
|
||||
error:
|
||||
|
||||
error: // fallthrough
|
||||
if(splits) bstrListDestroy(splits);
|
||||
return -1;
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int client_handler(int fd)
|
||||
void client_handler(int client_fd)
|
||||
{
|
||||
int rc = 0;
|
||||
RingBuffer *recv_rb = RingBuffer_create(RB_SIZE);
|
||||
|
@ -239,82 +261,91 @@ int client_handler(int fd)
|
|||
check_mem(recv_rb);
|
||||
check_mem(send_rb);
|
||||
|
||||
while(read_some(recv_rb, fd, 1) != -1) {
|
||||
// TODO: read a line, put the rest back
|
||||
// keep reading into the recv buffer and sending on send
|
||||
while(read_some(recv_rb, client_fd, 1) != -1) {
|
||||
// read a line from the recv_rb
|
||||
bstring data = read_line(recv_rb, LINE_ENDING);
|
||||
check(data != NULL, "Client closed.");
|
||||
|
||||
// parse it, close on any protocol errors
|
||||
rc = parse_line(data, send_rb);
|
||||
bdestroy(data); // cleanup here
|
||||
check(rc == 0, "Failed to parse user. Closing.");
|
||||
|
||||
// and as long as there's something to send, send it
|
||||
if(RingBuffer_available_data(send_rb)) {
|
||||
write_some(send_rb, fd, 1);
|
||||
write_some(send_rb, client_fd, 1);
|
||||
}
|
||||
|
||||
bdestroy(data);
|
||||
}
|
||||
|
||||
rc = close(fd);
|
||||
check(rc != -1, "Failed to close fd.");
|
||||
// close the socket
|
||||
rc = close(client_fd);
|
||||
check(rc != -1, "Failed to close the socket.");
|
||||
|
||||
error: // fallthrough
|
||||
if(recv_rb) RingBuffer_destroy(recv_rb);
|
||||
exit(0);
|
||||
if(send_rb) RingBuffer_destroy(send_rb);
|
||||
exit(0); // just exit the child process
|
||||
}
|
||||
|
||||
void handle_sigchld(int sig) {
|
||||
if(sig == SIGCHLD) {
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int echo_server(const char *host, const char *port)
|
||||
int setup_data_store()
|
||||
{
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t sin_size = sizeof(client_addr);
|
||||
int server_socket = 0;
|
||||
int client_fd = 0;
|
||||
int rc = 0;
|
||||
|
||||
// a more advanced design simply wouldn't use this
|
||||
DATA = Hashmap_create(NULL, NULL);
|
||||
check_mem(DATA);
|
||||
|
||||
struct sigaction sa = {
|
||||
.sa_handler = handle_sigchld,
|
||||
.sa_flags = SA_RESTART | SA_NOCLDSTOP
|
||||
};
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
rc = sigaction(SIGCHLD, &sa, 0);
|
||||
check(rc != -1, "Failed to setup signal handler for child processes.");
|
||||
|
||||
server_socket = server_listen(host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.",
|
||||
host, port);
|
||||
|
||||
while (1) {
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
check(client_fd >= 0, "Failed to accept connection.");
|
||||
|
||||
log_info("Client connected.");
|
||||
|
||||
rc = fork();
|
||||
check(rc != -1, "Failed to fork!");
|
||||
|
||||
if(rc == 0) {
|
||||
// in the child process
|
||||
close(server_socket);
|
||||
client_handler(client_fd);
|
||||
} else {
|
||||
// server process doesn't need this
|
||||
close(client_fd);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int echo_server(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t sin_size = sizeof(client_addr);
|
||||
int server_socket = 0;
|
||||
int client_fd = 0;
|
||||
|
||||
rc = setup_data_store();
|
||||
check(rc == 0, "Failed to setup the data store.");
|
||||
|
||||
struct sigaction sa = {
|
||||
.sa_handler = handle_sigchild,
|
||||
.sa_flags = SA_RESTART | SA_NOCLDSTOP
|
||||
};
|
||||
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// create a sigaction that handles SIGCHLD
|
||||
sigemptyset(&sa.sa_mask);
|
||||
rc = sigaction(SIGCHLD, &sa, 0);
|
||||
check(rc != -1, "Failed to setup signal handler for child processes.");
|
||||
|
||||
// listen on the given port and host
|
||||
server_socket = server_listen(host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.", host, port);
|
||||
|
||||
while(1) {
|
||||
// accept the connection
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
check(client_fd >= 0, "Failed to accept connection.");
|
||||
|
||||
debug("Client connected.");
|
||||
|
||||
rc = fork();
|
||||
if(rc == 0) {
|
||||
// child process
|
||||
close(server_socket); // don't need this
|
||||
// handle the client
|
||||
client_handler(client_fd);
|
||||
} else {
|
||||
// server process
|
||||
close(client_fd); // don't need this
|
||||
}
|
||||
}
|
||||
|
||||
error: // fallthrough
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
#ifndef _statserve_h
|
||||
#define _statserve_h
|
||||
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
|
||||
struct tagbstring OK;
|
||||
|
||||
int setup_data_store();
|
||||
|
||||
int parse_line(bstring data, RingBuffer *send_rb);
|
||||
|
||||
int echo_server(const char *host, const char *port);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#define _minunit_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dbg.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define mu_suite_start() char *message = NULL
|
||||
|
|
|
@ -1,21 +1,92 @@
|
|||
#include "minunit.h"
|
||||
#include <statserve.h>
|
||||
#include <dlfcn.h>
|
||||
#include "statserve.h"
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct LineTest {
|
||||
char *line;
|
||||
bstring result;
|
||||
char *description;
|
||||
} LineTest;
|
||||
|
||||
char *test_statserve()
|
||||
int attempt_line(LineTest test)
|
||||
{
|
||||
int rc = -1;
|
||||
bstring result = NULL;
|
||||
|
||||
// mu_assert(echo_server("127.0.0.1", "7899") == 0, "Failed to start echo server.");
|
||||
bstring line = bfromcstr(test.line);
|
||||
RingBuffer *send_rb = RingBuffer_create(1024);
|
||||
|
||||
rc = parse_line(line, send_rb);
|
||||
check(rc == 0, "Failed to parse line.");
|
||||
|
||||
result = RingBuffer_get_all(send_rb);
|
||||
check(result != NULL, "Ring buffer empty.");
|
||||
check(biseq(result, test.result), "Got the wrong output: %s expected %s",
|
||||
bdata(result), bdata(test.result));
|
||||
|
||||
bdestroy(line);
|
||||
RingBuffer_destroy(send_rb);
|
||||
return 1; // using 1 for tests
|
||||
error:
|
||||
|
||||
log_err("Failed to process test %s: got %s", test.line, bdata(result));
|
||||
if(line) bdestroy(line);
|
||||
if(send_rb) RingBuffer_destroy(send_rb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int run_test_lines(LineTest *tests, int count)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for(i = 0; i < count; i++) {
|
||||
check(attempt_line(tests[i]), "Failed to run %s", tests[i].description);
|
||||
}
|
||||
|
||||
return 1;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *test_create()
|
||||
{
|
||||
LineTest tests[] = {
|
||||
{.line = "create /zed 100", .result = &OK, .description = "create zed failed"},
|
||||
{.line = "create /joe 100", .result = &OK, .description = "create joe failed"},
|
||||
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 2), "Failed to run create tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_sample()
|
||||
{
|
||||
struct tagbstring sample1 = bsStatic("100.000000\n");
|
||||
|
||||
LineTest tests[] = {
|
||||
{.line = "sample /zed 100", .result = &sample1, .description = "sample zed failed."}
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 1), "Failed to run sample tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *all_tests()
|
||||
{
|
||||
mu_suite_start();
|
||||
|
||||
mu_run_test(test_statserve);
|
||||
int rc = setup_data_store();
|
||||
mu_assert(rc == 0, "Failed to setup the data store.");
|
||||
|
||||
mu_run_test(test_create);
|
||||
mu_run_test(test_sample);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CFLAGS=-g -O2 -Wall -Wextra -Isrc -I/usr/local/include -rdynamic $(OPTFLAGS)
|
||||
CFLAGS=-g -O2 -Wall -Wextra -I/usr/local/include -Isrc -rdynamic $(OPTFLAGS)
|
||||
LIBS=-llcthw $(OPTLIBS)
|
||||
LDFLAGS=-L/usr/local/lib $(LIBS)
|
||||
PREFIX?=/usr/local
|
||||
|
@ -18,6 +18,8 @@ all: $(TARGET) $(SO_TARGET) tests bin/statserve
|
|||
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
|
||||
dev: all
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
$(TARGET): CFLAGS += -fPIC
|
||||
$(TARGET): build $(OBJECTS)
|
||||
ar rcs $@ $(OBJECTS)
|
||||
|
@ -26,8 +28,6 @@ $(TARGET): build $(OBJECTS)
|
|||
$(SO_TARGET): $(TARGET) $(OBJECTS)
|
||||
$(CC) -shared -o $@ $(LDFLAGS) $(LIBS) $(OBJECTS)
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
build:
|
||||
@mkdir -p build
|
||||
@mkdir -p bin
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include <statserve.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -9,10 +11,11 @@ int main(int argc, char *argv[])
|
|||
const char *host = argv[1];
|
||||
const char *port = argv[2];
|
||||
|
||||
check(run_server(host, port), "Failed to run the echo server.");
|
||||
check(echo_server(host, port), "Failed to run the echo server.");
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "net.h"
|
||||
|
@ -8,7 +15,6 @@
|
|||
struct tagbstring NL = bsStatic("\n");
|
||||
struct tagbstring CRLF = bsStatic("\r\n");
|
||||
|
||||
|
||||
int nonblock(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
|
@ -22,6 +28,31 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket)
|
||||
{
|
||||
int rc = 0;
|
||||
|
@ -73,23 +104,28 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int attempt_listen(struct addrinfo *info)
|
||||
{
|
||||
int sockfd = 0;
|
||||
int sockfd = -1; // default fail
|
||||
int rc = -1;
|
||||
int yes = 1;
|
||||
|
||||
check(info != NULL, "Invalid addrinfo.");
|
||||
|
||||
// create a socket with the addrinfo
|
||||
sockfd = socket(info->ai_family, info->ai_socktype,
|
||||
info->ai_protocol);
|
||||
check_debug(sockfd != -1, "Failed to bind to address result. Trying more.");
|
||||
check_debug(sockfd != -1, "Failed to bind to address. Trying more.");
|
||||
|
||||
// set the SO_REUSEADDR option on the socket
|
||||
rc = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||
check_debug(rc == 0, "Failed to set SO_REISEADDR.");
|
||||
check_debug(rc == 0, "Failed to set SO_REUSADDR.");
|
||||
|
||||
// attempt to bind to it
|
||||
rc = bind(sockfd, info->ai_addr, info->ai_addrlen);
|
||||
check_debug(rc == 0, "Failed to bind socket.");
|
||||
|
||||
check_debug(rc == 0, "Failed to find socket.");
|
||||
|
||||
// finally listen with a backlog
|
||||
rc = listen(sockfd, BACKLOG);
|
||||
check_debug(rc == 0, "Failed to listen to socket.");
|
||||
|
||||
|
@ -99,10 +135,11 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int server_listen(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
int sockfd = -1;
|
||||
int sockfd = -1; // default fail value
|
||||
struct addrinfo *info = NULL;
|
||||
struct addrinfo *next_p = NULL;
|
||||
struct addrinfo addr = {
|
||||
|
@ -111,73 +148,54 @@ int server_listen(const char *host, const char *port)
|
|||
.ai_flags = AI_PASSIVE
|
||||
};
|
||||
|
||||
check(host != NULL, "Must give a valid host.");
|
||||
check(port != NULL, "Must have a valid port.");
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// get the address info for host and port
|
||||
rc = getaddrinfo(NULL, port, &addr, &info);
|
||||
check(rc == 0, "Failed to get address info for connect.");
|
||||
|
||||
|
||||
// cycle through the available list to find one
|
||||
for(next_p = info; next_p != NULL; next_p = next_p->ai_next)
|
||||
{
|
||||
// attempt to listen to each one
|
||||
sockfd = attempt_listen(next_p);
|
||||
if(sockfd != -1) break;
|
||||
}
|
||||
|
||||
// either we found one and were able to listen or nothing.
|
||||
check(sockfd != -1, "All possible addresses failed.");
|
||||
|
||||
error: //fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
// this gets set by the above to either -1 or valid
|
||||
return sockfd;
|
||||
|
||||
error: // fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
bstring read_line(RingBuffer *input, const char line_ending)
|
||||
{
|
||||
int i = 0;
|
||||
bstring result = NULL;
|
||||
|
||||
// not super efficient
|
||||
// read a character at a time from the ring buffer
|
||||
for(i = 0; i < RingBuffer_available_data(input); i++) {
|
||||
// if the buffer has line ending
|
||||
if(input->buffer[i] == line_ending) {
|
||||
// get that much fromt he ring buffer
|
||||
result = RingBuffer_gets(input, i);
|
||||
check(result, "Failed to get line from RingBuffer.");
|
||||
check(RingBuffer_available_data(input) >= 1, "Not enough data in the RingBuffer after reading a line.");
|
||||
// eat the \n in the buffer
|
||||
check(result, "Failed to get line from RingBuffer");
|
||||
// make sure that we got the right amount
|
||||
check(RingBuffer_available_data(input) >= 1,
|
||||
"Not enough data in the RingBuffer after reading line.");
|
||||
// and commit it
|
||||
RingBuffer_commit_read(input, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug("LINE: %s", bdata(result));
|
||||
|
||||
// notice this will fail in the cases where we get a set of data
|
||||
// on the wire that does not have a line ending yet
|
||||
return result;
|
||||
error:
|
||||
return NULL;
|
||||
|
@ -188,4 +206,3 @@ void send_reply(RingBuffer *send_rb, bstring reply)
|
|||
{
|
||||
RingBuffer_puts(send_rb, reply);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
#ifndef _statserve_net_h
|
||||
#define _statserve_net_h
|
||||
#ifndef _net_h
|
||||
#define _net_h
|
||||
|
||||
#include <netdb.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
|
||||
#define BACKLOG 10
|
||||
|
||||
int nonblock(int fd);
|
||||
|
||||
int client_connect(char *host, char *port);
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int write_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int attempt_listen(struct addrinfo *info);
|
||||
|
||||
int server_listen(const char *host, const char *port);
|
||||
|
||||
bstring read_line(RingBuffer *input, const char line_ending);
|
||||
|
||||
void send_reply(RingBuffer *send_rb, bstring reply);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
#include <lcthw/ringbuffer.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <lcthw/hashmap.h>
|
||||
#include <lcthw/stats.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
#include "net.h"
|
||||
#include <netdb.h>
|
||||
#include "statserve.h"
|
||||
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
struct tagbstring LINE_SPLIT = bsStatic(" ");
|
||||
struct tagbstring CREATE = bsStatic("create");
|
||||
struct tagbstring STDDEV = bsStatic("stddev");
|
||||
|
@ -26,57 +24,55 @@ struct tagbstring EXISTS = bsStatic("EXISTS\n");
|
|||
struct tagbstring SLASH = bsStatic("/");
|
||||
const char LINE_ENDING = '\n';
|
||||
|
||||
// this is just temporary to work out the protocol
|
||||
// it actually doesn't work in practice because forking
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
|
||||
Hashmap *DATA = NULL;
|
||||
|
||||
struct Command;
|
||||
void handle_sigchild(int sig) {
|
||||
sig = 0; // ignore it
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef int (*handler_cb)(struct Command *cmd, RingBuffer *send_rb, bstring path);
|
||||
|
||||
typedef struct Command {
|
||||
bstring command;
|
||||
bstring name;
|
||||
struct bstrList *path;
|
||||
bstring number;
|
||||
handler_cb handler;
|
||||
} Command;
|
||||
|
||||
typedef struct Record {
|
||||
bstring name;
|
||||
Stats *stat;
|
||||
} Record;
|
||||
|
||||
int handle_create(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
int rc = 0;
|
||||
int is_root = biseq(path, cmd->name);
|
||||
log_info("create: %s %s %s", bdata(cmd->name), bdata(path), bdata(cmd->number));
|
||||
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
// BUG: does duplicates of children
|
||||
if(info != NULL && biseq(path, cmd->name)) {
|
||||
// report the root exists, don't report children
|
||||
if(info != NULL && is_root) {
|
||||
// report if root exists, just skip children
|
||||
send_reply(send_rb, &EXISTS);
|
||||
} else if(info != NULL) {
|
||||
// skip children and don't overwrite them
|
||||
debug("Child exists so skipping it.");
|
||||
debug("Child %s exists, skipping it.", bdata(path));
|
||||
return 0;
|
||||
} else {
|
||||
// brand spanking new so make it
|
||||
// new child so make it
|
||||
debug("create: %s %s", bdata(path), bdata(cmd->number));
|
||||
|
||||
Record *info = calloc(sizeof(Record), 1);
|
||||
check_mem(info);
|
||||
|
||||
// set its stat element
|
||||
info->stat = Stats_create();
|
||||
check_mem(info->stat);
|
||||
|
||||
// set its name element
|
||||
info->name = bstrcpy(path);
|
||||
check_mem(info->name);
|
||||
|
||||
// do a first sample
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
|
||||
Hashmap_set(DATA, info->name, info);
|
||||
|
||||
// only send the OK on the last path part
|
||||
if(cmd->path->qty == 2) {
|
||||
// add it to the hashmap
|
||||
rc = Hashmap_set(DATA, info->name, info);
|
||||
check(rc == 0, "Failed to add data to map.");
|
||||
|
||||
// only send the for the root part
|
||||
if(is_root) {
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
}
|
||||
|
@ -86,9 +82,81 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int handle_sample(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
// get the info from the hashmap
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
int is_root = biseq(path, cmd->name);
|
||||
log_info("sample %s %s %s", bdata(cmd->name), bdata(path), bdata(cmd->number));
|
||||
bstring child_path = NULL;
|
||||
|
||||
if(info == NULL) {
|
||||
// if it doesn't exist then DNE
|
||||
send_reply(send_rb, &DNE);
|
||||
return 0;
|
||||
} else {
|
||||
if(is_root) {
|
||||
// just sample the root like normal
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
} else {
|
||||
// need to do some hackery to get the child path
|
||||
// for rolling up mean-of-means on it
|
||||
|
||||
// increase the qty on path up one
|
||||
cmd->path->qty++;
|
||||
// get the "child path" (previous path?)
|
||||
child_path = bjoin(cmd->path, &SLASH);
|
||||
// get that info from the DATA
|
||||
Record *child_info = Hashmap_get(DATA, child_path);
|
||||
bdestroy(child_path);
|
||||
|
||||
// if it exists then sample on it
|
||||
if(child_info) {
|
||||
// info is /logins, child_info is /logins/zed
|
||||
// we want /logins/zed's mean to be a new sample on /logins
|
||||
Stats_sample(info->stat, Stats_mean(child_info->stat));
|
||||
}
|
||||
// drop the path back to where it was
|
||||
cmd->path->qty--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// do the reply for the mean last
|
||||
bstring reply = bformat("%f\n", Stats_mean(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_delete(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
log_info("delete: %s %s", bdata(cmd->name), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
int is_root = biseq(path, cmd->name);
|
||||
|
||||
// BUG: should just decide that this isn't scanned
|
||||
// but run once, for now just only run on root
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else if(is_root) {
|
||||
Hashmap_delete(DATA, path);
|
||||
|
||||
free(info->stat);
|
||||
bdestroy(info->name);
|
||||
free(info);
|
||||
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_mean(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("mean: %s from %s", bdata(path), bdata(cmd->name));
|
||||
log_info("mean: %s %s %s", bdata(cmd->name), bdata(path), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
if(info == NULL) {
|
||||
|
@ -102,52 +170,31 @@ int handle_mean(Command *cmd, RingBuffer *send_rb, bstring path)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int handle_sample(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
int handle_stddev(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("sample: %s %s", bdata(path), bdata(cmd->number));
|
||||
log_info("stddev: %s %s %s", bdata(cmd->name), bdata(path), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
bstring child_path = NULL;
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
debug("found info: '%s' vs. '%s'", bdata(path), bdata(cmd->name));
|
||||
|
||||
if(biseq(path, cmd->name)) {
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
} else {
|
||||
// need to do a bit of hackery to get the child path
|
||||
// then we can do a mean-of-means on it
|
||||
cmd->path->qty++;
|
||||
child_path = bjoin(cmd->path, &SLASH);
|
||||
debug("child_path: %s", bdata(child_path));
|
||||
Record *child_info = Hashmap_get(DATA, child_path);
|
||||
|
||||
if(child_info) {
|
||||
Stats_sample(info->stat, Stats_mean(child_info->stat));
|
||||
}
|
||||
|
||||
cmd->path->qty--; // drop it back down to continue
|
||||
}
|
||||
bstring reply = bformat("%f\n", Stats_stddev(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
}
|
||||
|
||||
bstring reply = bformat("%f\n", Stats_mean(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_dump(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("dump: %s from %s", bdata(path), bdata(cmd->name));
|
||||
log_info("dump: %s, %s, %s", bdata(cmd->name), bdata(path), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
bstring reply = bformat("%s %f %f %f %f %ld %f %f\n",
|
||||
bdata(info->name),
|
||||
bstring reply = bformat("%f %f %f %f %ld %f %f\n",
|
||||
Stats_mean(info->stat),
|
||||
Stats_stddev(info->stat),
|
||||
info->stat->sum,
|
||||
|
@ -163,54 +210,72 @@ int handle_dump(Command *cmd, RingBuffer *send_rb, bstring path)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int handle_delete(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
int parse_command(struct bstrList *splits, Command *cmd)
|
||||
{
|
||||
debug("delete: %s from %s", bdata(path), bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
// get the command
|
||||
cmd->command = splits->entry[0];
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
if(biseq(cmd->command, &CREATE)) {
|
||||
check(splits->qty == 3, "Failed to parse create: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_create;
|
||||
} else if(biseq(cmd->command, &MEAN)) {
|
||||
check(splits->qty == 2, "Failed to parse mean: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_mean;
|
||||
} else if(biseq(cmd->command, &SAMPLE)) {
|
||||
check(splits->qty == 3, "Failed to parse sample: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_sample;
|
||||
} else if(biseq(cmd->command, &DUMP)) {
|
||||
check(splits->qty == 2, "Failed to parse dump: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_dump;
|
||||
} else if(biseq(cmd->command, &DELETE)) {
|
||||
check(splits->qty == 2, "Failed to parse delete: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_delete;
|
||||
} else if(biseq(cmd->command, &STDDEV)) {
|
||||
check(splits->qty == 2, "Failed to parse stddev: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_stddev;
|
||||
} else {
|
||||
free(info->stat);
|
||||
bdestroy(info->name);
|
||||
free(info);
|
||||
Hashmap_delete(DATA, path);
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_stddev(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("stddev: %s from %s", bdata(path), bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
bstring reply = bformat("%f\n", Stats_stddev(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
sentinel("Failed to parse the command.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int scan_paths(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
size_t qty = cmd->path->qty;
|
||||
int rc = 0;
|
||||
check(cmd->path != NULL, "Path was not set in command.");
|
||||
|
||||
for(;cmd->path->qty > 1; cmd->path->qty--) {
|
||||
int rc = 0;
|
||||
// save the original path length
|
||||
size_t qty = cmd->path->qty;
|
||||
|
||||
// starting at the longest path, shorten it and call
|
||||
// for each one:
|
||||
for(; cmd->path->qty > 1; cmd->path->qty--) {
|
||||
// remake the path with / again
|
||||
bstring path = bjoin(cmd->path, &SLASH);
|
||||
// call the handler with the path
|
||||
rc = cmd->handler(cmd, send_rb, path);
|
||||
// if the handler returns != 0 then abort and return that
|
||||
bdestroy(path);
|
||||
|
||||
if(rc != 0) break;
|
||||
}
|
||||
|
||||
// restore path length
|
||||
cmd->path->qty = qty;
|
||||
return rc;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct bstrList *parse_name(bstring name)
|
||||
|
@ -218,81 +283,39 @@ struct bstrList *parse_name(bstring name)
|
|||
return bsplits(name, &SLASH);
|
||||
}
|
||||
|
||||
int parse_command(struct bstrList *splits, Command *cmd)
|
||||
{
|
||||
cmd->command = splits->entry[0];
|
||||
|
||||
if(biseq(cmd->command, &CREATE)) {
|
||||
check(splits->qty == 3, "Failed to parse create: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_create;
|
||||
} else if(biseq(cmd->command, &MEAN)) {
|
||||
check(splits->qty == 2, "Failed to parse mean: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_mean;
|
||||
} else if(biseq(cmd->command, &SAMPLE)) {
|
||||
check(splits->qty == 3, "Failed to parse sample: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_sample;
|
||||
} else if(biseq(cmd->command, &DUMP)) {
|
||||
check(splits->qty == 2, "Failed to parse dump: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_dump;
|
||||
} else if(biseq(cmd->command, &DELETE)) {
|
||||
check(splits->qty == 2, "Failed to parse delete: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_delete;
|
||||
} else if(biseq(cmd->command, &STDDEV)) {
|
||||
check(splits->qty == 2, "Failed to parse stddev: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_stddev;
|
||||
} else {
|
||||
sentinel("Failed to parse the command.");
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_line(bstring data, RingBuffer *send_rb)
|
||||
{
|
||||
int rc = 0;
|
||||
int rc = -1;
|
||||
Command cmd = {.command = NULL};
|
||||
|
||||
// split data on line boundaries
|
||||
struct bstrList *splits = bsplits(data, &LINE_SPLIT);
|
||||
check(splits != NULL, "Bad data.");
|
||||
|
||||
// parse it into a command
|
||||
rc = parse_command(splits, &cmd);
|
||||
check(rc == 0, "Failed to parse command.");
|
||||
check(cmd.path->qty > 1, "Didn't give a valid URL.");
|
||||
|
||||
// they used a path so break it up
|
||||
// parse the name into the path we need for scan_paths
|
||||
cmd.path = parse_name(cmd.name);
|
||||
check(cmd.path != NULL, "Invalid path.");
|
||||
|
||||
// scan the path and call the handlers
|
||||
rc = scan_paths(&cmd, send_rb);
|
||||
check(rc == 0, "Failure running command against path: %s", bdata(cmd.name));
|
||||
|
||||
bstrListDestroy(cmd.path);
|
||||
bstrListDestroy(splits);
|
||||
|
||||
return rc;
|
||||
error:
|
||||
return 0;
|
||||
|
||||
error: // fallthrough
|
||||
if(cmd.path) bstrListDestroy(cmd.path);
|
||||
if(splits) bstrListDestroy(splits);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int client_handler(int fd)
|
||||
void client_handler(int client_fd)
|
||||
{
|
||||
int rc = 0;
|
||||
RingBuffer *recv_rb = RingBuffer_create(RB_SIZE);
|
||||
|
@ -301,82 +324,91 @@ int client_handler(int fd)
|
|||
check_mem(recv_rb);
|
||||
check_mem(send_rb);
|
||||
|
||||
while(read_some(recv_rb, fd, 1) != -1) {
|
||||
// TODO: read a line, put the rest back
|
||||
// keep reading into the recv buffer and sending on send
|
||||
while(read_some(recv_rb, client_fd, 1) != -1) {
|
||||
// read a line from the recv_rb
|
||||
bstring data = read_line(recv_rb, LINE_ENDING);
|
||||
check(data != NULL, "Client closed.");
|
||||
|
||||
// parse it, close on any protocol errors
|
||||
rc = parse_line(data, send_rb);
|
||||
bdestroy(data); // cleanup here
|
||||
check(rc == 0, "Failed to parse user. Closing.");
|
||||
|
||||
// and as long as there's something to send, send it
|
||||
if(RingBuffer_available_data(send_rb)) {
|
||||
write_some(send_rb, fd, 1);
|
||||
write_some(send_rb, client_fd, 1);
|
||||
}
|
||||
|
||||
bdestroy(data);
|
||||
}
|
||||
|
||||
rc = close(fd);
|
||||
check(rc != -1, "Failed to close fd.");
|
||||
// close the socket
|
||||
rc = close(client_fd);
|
||||
check(rc != -1, "Failed to close the socket.");
|
||||
|
||||
error: // fallthrough
|
||||
if(recv_rb) RingBuffer_destroy(recv_rb);
|
||||
exit(0);
|
||||
if(send_rb) RingBuffer_destroy(send_rb);
|
||||
exit(0); // just exit the child process
|
||||
}
|
||||
|
||||
void handle_sigchld(int sig) {
|
||||
if(sig == SIGCHLD) {
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int run_server(const char *host, const char *port)
|
||||
int setup_data_store()
|
||||
{
|
||||
// a more advanced design simply wouldn't use this
|
||||
DATA = Hashmap_create(NULL, NULL);
|
||||
check_mem(DATA);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int echo_server(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t sin_size = sizeof(client_addr);
|
||||
int server_socket = 0;
|
||||
int client_fd = 0;
|
||||
int rc = 0;
|
||||
|
||||
DATA = Hashmap_create(NULL, NULL);
|
||||
check_mem(DATA);
|
||||
rc = setup_data_store();
|
||||
check(rc == 0, "Failed to setup the data store.");
|
||||
|
||||
struct sigaction sa = {
|
||||
.sa_handler = handle_sigchld,
|
||||
.sa_handler = handle_sigchild,
|
||||
.sa_flags = SA_RESTART | SA_NOCLDSTOP
|
||||
};
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// create a sigaction that handles SIGCHLD
|
||||
sigemptyset(&sa.sa_mask);
|
||||
rc = sigaction(SIGCHLD, &sa, 0);
|
||||
check(rc != -1, "Failed to setup signal handler for child processes.");
|
||||
|
||||
// listen on the given port and host
|
||||
server_socket = server_listen(host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.",
|
||||
host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.", host, port);
|
||||
|
||||
while (1) {
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
while(1) {
|
||||
// accept the connection
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
check(client_fd >= 0, "Failed to accept connection.");
|
||||
|
||||
debug("Client connected.");
|
||||
|
||||
rc = fork();
|
||||
check(rc != -1, "Failed to fork!");
|
||||
|
||||
if(rc == 0) {
|
||||
// in the child process
|
||||
close(server_socket);
|
||||
// child process
|
||||
close(server_socket); // don't need this
|
||||
// handle the client
|
||||
client_handler(client_fd);
|
||||
} else {
|
||||
// server process doesn't need this
|
||||
close(client_fd);
|
||||
// server process
|
||||
close(client_fd); // don't need this
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
error: // fallthrough
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,38 @@
|
|||
#ifndef _statserve_h
|
||||
#define _statserve_h
|
||||
|
||||
int run_server(const char *host, const char *port);
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <lcthw/stats.h>
|
||||
|
||||
struct Command;
|
||||
|
||||
typedef int (*handler_cb)(struct Command *cmd, RingBuffer *send_rb, bstring path);
|
||||
|
||||
typedef struct Command {
|
||||
bstring command;
|
||||
bstring name;
|
||||
struct bstrList *path;
|
||||
bstring number;
|
||||
handler_cb handler;
|
||||
} Command;
|
||||
|
||||
|
||||
typedef struct Record {
|
||||
bstring name;
|
||||
Stats *stat;
|
||||
} Record;
|
||||
|
||||
struct tagbstring OK;
|
||||
|
||||
int setup_data_store();
|
||||
|
||||
struct bstrList *parse_name(bstring name);
|
||||
|
||||
int scan_paths(Command *cmd, RingBuffer *send_rb);
|
||||
|
||||
int parse_line(bstring data, RingBuffer *send_rb);
|
||||
|
||||
int echo_server(const char *host, const char *port);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#define _minunit_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dbg.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define mu_suite_start() char *message = NULL
|
||||
|
|
|
@ -1,21 +1,132 @@
|
|||
#include "minunit.h"
|
||||
#include <statserve.h>
|
||||
#include <dlfcn.h>
|
||||
#include "statserve.h"
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct LineTest {
|
||||
char *line;
|
||||
bstring result;
|
||||
char *description;
|
||||
} LineTest;
|
||||
|
||||
char *test_statserve()
|
||||
int attempt_line(LineTest test)
|
||||
{
|
||||
int rc = -1;
|
||||
bstring result = NULL;
|
||||
|
||||
// mu_assert(echo_server("127.0.0.1", "7899") == 0, "Failed to start echo server.");
|
||||
bstring line = bfromcstr(test.line);
|
||||
RingBuffer *send_rb = RingBuffer_create(1024);
|
||||
|
||||
rc = parse_line(line, send_rb);
|
||||
check(rc == 0, "Failed to parse line.");
|
||||
|
||||
result = RingBuffer_get_all(send_rb);
|
||||
check(result != NULL, "Ring buffer empty.");
|
||||
check(biseq(result, test.result), "Got the wrong output: %s expected %s",
|
||||
bdata(result), bdata(test.result));
|
||||
|
||||
bdestroy(line);
|
||||
RingBuffer_destroy(send_rb);
|
||||
return 1; // using 1 for tests
|
||||
error:
|
||||
|
||||
log_err("Failed to process test %s: got %s", test.line, bdata(result));
|
||||
if(line) bdestroy(line);
|
||||
if(send_rb) RingBuffer_destroy(send_rb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int run_test_lines(LineTest *tests, int count)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for(i = 0; i < count; i++) {
|
||||
check(attempt_line(tests[i]), "Failed to run %s", tests[i].description);
|
||||
}
|
||||
|
||||
return 1;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fake_command(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
check(cmd != NULL, "Bad cmd.");
|
||||
check(cmd->path != NULL, "Bad path.");
|
||||
check(send_rb != NULL, "Bad send_rb.");
|
||||
check(path != NULL, "Bad path given.");
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *test_path_parsing()
|
||||
{
|
||||
struct bstrList *result = NULL;
|
||||
struct tagbstring slash = bsStatic("/");
|
||||
struct tagbstring logins_zed = bsStatic("/logins/zed");
|
||||
struct tagbstring command_name = bsStatic("dump");
|
||||
RingBuffer *send_rb = RingBuffer_create(1024);
|
||||
struct bstrList *path = bsplits(&logins_zed, &slash);
|
||||
int rc = 0;
|
||||
|
||||
Command fake = {
|
||||
.command = &command_name,
|
||||
.name = &logins_zed,
|
||||
.number = NULL,
|
||||
.handler = fake_command,
|
||||
.path = path
|
||||
};
|
||||
|
||||
result = parse_name(&logins_zed);
|
||||
mu_assert(result != NULL, "Failed to parse /logins/zed");
|
||||
|
||||
rc = scan_paths(&fake, send_rb);
|
||||
mu_assert(rc != -1, "scan_paths failed.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_create()
|
||||
{
|
||||
LineTest tests[] = {
|
||||
{.line = "create /zed 100", .result = &OK, .description = "create zed failed"},
|
||||
{.line = "create /joe 100", .result = &OK, .description = "create joe failed"},
|
||||
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 2), "Failed to run create tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_sample()
|
||||
{
|
||||
struct tagbstring sample1 = bsStatic("100.000000\n");
|
||||
|
||||
LineTest tests[] = {
|
||||
{.line = "sample /zed 100", .result = &sample1, .description = "sample zed failed."}
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 1), "Failed to run sample tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *all_tests()
|
||||
{
|
||||
mu_suite_start();
|
||||
|
||||
mu_run_test(test_statserve);
|
||||
int rc = setup_data_store();
|
||||
mu_assert(rc == 0, "Failed to setup the data store.");
|
||||
|
||||
mu_run_test(test_create);
|
||||
mu_run_test(test_sample);
|
||||
mu_run_test(test_path_parsing);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
build/*
|
||||
tests/*_tests
|
||||
bin/statserve
|
||||
tags
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CFLAGS=-g -O2 -Wall -Wextra -Isrc -I/usr/local/include -rdynamic $(OPTFLAGS)
|
||||
CFLAGS=-g -O2 -Wall -Wextra -I/usr/local/include -Isrc -rdynamic $(OPTFLAGS)
|
||||
LIBS=-llcthw $(OPTLIBS)
|
||||
LDFLAGS=-L/usr/local/lib $(LIBS)
|
||||
PREFIX?=/usr/local
|
||||
|
@ -18,6 +18,8 @@ all: $(TARGET) $(SO_TARGET) tests bin/statserve
|
|||
dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
|
||||
dev: all
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
|
||||
$(TARGET): CFLAGS += -fPIC
|
||||
$(TARGET): build $(OBJECTS)
|
||||
ar rcs $@ $(OBJECTS)
|
||||
|
@ -26,7 +28,7 @@ $(TARGET): build $(OBJECTS)
|
|||
$(SO_TARGET): $(TARGET) $(OBJECTS)
|
||||
$(CC) -shared -o $@ $(LDFLAGS) $(LIBS) $(OBJECTS)
|
||||
|
||||
bin/statserve: $(TARGET)
|
||||
$(TESTS): $(TARGET) $(SO_TARGET)
|
||||
|
||||
build:
|
||||
@mkdir -p build
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#include "statserve.h"
|
||||
#include <stdio.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
check(argc == 4, "USAGE: statserve host port storepath");
|
||||
check(argc == 4, "USAGE: statserve host port store_path");
|
||||
|
||||
const char *host = argv[1];
|
||||
const char *port = argv[2];
|
||||
|
@ -15,5 +17,6 @@ int main(int argc, char *argv[])
|
|||
return 0;
|
||||
|
||||
error:
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <stdio.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "net.h"
|
||||
|
@ -8,7 +15,6 @@
|
|||
struct tagbstring NL = bsStatic("\n");
|
||||
struct tagbstring CRLF = bsStatic("\r\n");
|
||||
|
||||
|
||||
int nonblock(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
|
@ -22,6 +28,31 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket)
|
||||
{
|
||||
int rc = 0;
|
||||
|
@ -73,23 +104,28 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int attempt_listen(struct addrinfo *info)
|
||||
{
|
||||
int sockfd = 0;
|
||||
int sockfd = -1; // default fail
|
||||
int rc = -1;
|
||||
int yes = 1;
|
||||
|
||||
check(info != NULL, "Invalid addrinfo.");
|
||||
|
||||
// create a socket with the addrinfo
|
||||
sockfd = socket(info->ai_family, info->ai_socktype,
|
||||
info->ai_protocol);
|
||||
check_debug(sockfd != -1, "Failed to bind to address result. Trying more.");
|
||||
check_debug(sockfd != -1, "Failed to bind to address. Trying more.");
|
||||
|
||||
// set the SO_REUSEADDR option on the socket
|
||||
rc = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||
check_debug(rc == 0, "Failed to set SO_REISEADDR.");
|
||||
check_debug(rc == 0, "Failed to set SO_REUSADDR.");
|
||||
|
||||
// attempt to bind to it
|
||||
rc = bind(sockfd, info->ai_addr, info->ai_addrlen);
|
||||
check_debug(rc == 0, "Failed to bind socket.");
|
||||
|
||||
check_debug(rc == 0, "Failed to find socket.");
|
||||
|
||||
// finally listen with a backlog
|
||||
rc = listen(sockfd, BACKLOG);
|
||||
check_debug(rc == 0, "Failed to listen to socket.");
|
||||
|
||||
|
@ -99,10 +135,11 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int server_listen(const char *host, const char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
int sockfd = -1;
|
||||
int sockfd = -1; // default fail value
|
||||
struct addrinfo *info = NULL;
|
||||
struct addrinfo *next_p = NULL;
|
||||
struct addrinfo addr = {
|
||||
|
@ -111,73 +148,54 @@ int server_listen(const char *host, const char *port)
|
|||
.ai_flags = AI_PASSIVE
|
||||
};
|
||||
|
||||
check(host != NULL, "Must give a valid host.");
|
||||
check(port != NULL, "Must have a valid port.");
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// get the address info for host and port
|
||||
rc = getaddrinfo(NULL, port, &addr, &info);
|
||||
check(rc == 0, "Failed to get address info for connect.");
|
||||
|
||||
|
||||
// cycle through the available list to find one
|
||||
for(next_p = info; next_p != NULL; next_p = next_p->ai_next)
|
||||
{
|
||||
// attempt to listen to each one
|
||||
sockfd = attempt_listen(next_p);
|
||||
if(sockfd != -1) break;
|
||||
}
|
||||
|
||||
// either we found one and were able to listen or nothing.
|
||||
check(sockfd != -1, "All possible addresses failed.");
|
||||
|
||||
error: //fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
// this gets set by the above to either -1 or valid
|
||||
return sockfd;
|
||||
|
||||
error: // fallthrough
|
||||
if(info) freeaddrinfo(info);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int client_connect(char *host, char *port)
|
||||
{
|
||||
int rc = 0;
|
||||
struct addrinfo *addr = NULL;
|
||||
|
||||
rc = getaddrinfo(host, port, NULL, &addr);
|
||||
check(rc == 0, "Failed to lookup %s:%s", host, port);
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(sock >= 0, "Cannot create a socket.");
|
||||
|
||||
rc = connect(sock, addr->ai_addr, addr->ai_addrlen);
|
||||
check(rc == 0, "Connect failed.");
|
||||
|
||||
rc = nonblock(sock);
|
||||
check(rc == 0, "Can't set nonblocking.");
|
||||
|
||||
freeaddrinfo(addr);
|
||||
return sock;
|
||||
|
||||
error:
|
||||
freeaddrinfo(addr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
bstring read_line(RingBuffer *input, const char line_ending)
|
||||
{
|
||||
int i = 0;
|
||||
bstring result = NULL;
|
||||
|
||||
// not super efficient
|
||||
// read a character at a time from the ring buffer
|
||||
for(i = 0; i < RingBuffer_available_data(input); i++) {
|
||||
// if the buffer has line ending
|
||||
if(input->buffer[i] == line_ending) {
|
||||
// get that much fromt he ring buffer
|
||||
result = RingBuffer_gets(input, i);
|
||||
check(result, "Failed to get line from RingBuffer.");
|
||||
check(RingBuffer_available_data(input) >= 1, "Not enough data in the RingBuffer after reading a line.");
|
||||
// eat the \n in the buffer
|
||||
check(result, "Failed to get line from RingBuffer");
|
||||
// make sure that we got the right amount
|
||||
check(RingBuffer_available_data(input) >= 1,
|
||||
"Not enough data in the RingBuffer after reading line.");
|
||||
// and commit it
|
||||
RingBuffer_commit_read(input, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug("LINE: %s", bdata(result));
|
||||
|
||||
// notice this will fail in the cases where we get a set of data
|
||||
// on the wire that does not have a line ending yet
|
||||
return result;
|
||||
error:
|
||||
return NULL;
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
#ifndef _statserve_net_h
|
||||
#define _statserve_net_h
|
||||
#ifndef _net_h
|
||||
#define _net_h
|
||||
|
||||
#include <netdb.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
|
||||
#define BACKLOG 10
|
||||
|
||||
int nonblock(int fd);
|
||||
|
||||
int client_connect(char *host, char *port);
|
||||
int read_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int write_some(RingBuffer * buffer, int fd, int is_socket);
|
||||
|
||||
int attempt_listen(struct addrinfo *info);
|
||||
|
||||
int server_listen(const char *host, const char *port);
|
||||
|
||||
bstring read_line(RingBuffer *input, const char line_ending);
|
||||
|
||||
void send_reply(RingBuffer *send_rb, bstring reply);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
#include <lcthw/ringbuffer.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <lcthw/hashmap.h>
|
||||
#include <lcthw/stats.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "statserve.h"
|
||||
#include "net.h"
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
#include "net.h"
|
||||
#include <netdb.h>
|
||||
#include <fcntl.h>
|
||||
#include "statserve.h"
|
||||
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
struct tagbstring LINE_SPLIT = bsStatic(" ");
|
||||
struct tagbstring CREATE = bsStatic("create");
|
||||
struct tagbstring STDDEV = bsStatic("stddev");
|
||||
|
@ -28,32 +27,18 @@ struct tagbstring EXISTS = bsStatic("EXISTS\n");
|
|||
struct tagbstring SLASH = bsStatic("/");
|
||||
const char LINE_ENDING = '\n';
|
||||
|
||||
// this is just temporary to work out the protocol
|
||||
// it actually doesn't work in practice because forking
|
||||
const int RB_SIZE = 1024 * 10;
|
||||
|
||||
Hashmap *DATA = NULL;
|
||||
bstring STORE_PATH = NULL;
|
||||
|
||||
struct Command;
|
||||
|
||||
typedef int (*handler_cb)(struct Command *cmd, RingBuffer *send_rb, bstring path);
|
||||
|
||||
typedef struct Command {
|
||||
bstring command;
|
||||
bstring name;
|
||||
struct bstrList *path;
|
||||
bstring number;
|
||||
bstring arg;
|
||||
handler_cb handler;
|
||||
} Command;
|
||||
|
||||
typedef struct Record {
|
||||
bstring name;
|
||||
Stats *stat;
|
||||
} Record;
|
||||
|
||||
// uhhhhh totally random ;-)
|
||||
const uint32_t STORE_KEY[4] = {1982343, 1133434, 47838745, 182983494};
|
||||
void handle_sigchild(int sig) {
|
||||
sig = 0; // ignore it
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
// BUG: this is stupid, use md5
|
||||
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
|
||||
unsigned int i;
|
||||
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
|
||||
|
@ -65,157 +50,105 @@ void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
|
|||
v[0]=v0; v[1]=v1;
|
||||
}
|
||||
|
||||
// used for my "ghetto" BASE64 hack to just test it out
|
||||
struct tagbstring BASE64 = bsStatic("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
/// TOTALLY RANDOM! LOL BUG: not secure
|
||||
const uint32_t STORE_KEY[4] = {18748274, 228374, 193034845, 85726348};
|
||||
struct tagbstring FAUX64 = bsStatic("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890");
|
||||
|
||||
// BUG: this all dies
|
||||
bstring encrypt_armor_name(bstring name)
|
||||
{
|
||||
// copy the name to encrypt
|
||||
bstring encname = bstrcpy(name);
|
||||
size_t i = 0;
|
||||
// point the encrypt pointer at it
|
||||
// BUG: this cast is weird, why?
|
||||
uint32_t *v = (uint32_t *)bdata(encname);
|
||||
|
||||
// do an encrypt on it with xtea and do a lame base64 quick hack
|
||||
// extend the encname so that it can hold everything
|
||||
// BUG: use a correct padding algorithm
|
||||
while(blength(encname) % (sizeof(uint32_t) * 2) > 0) {
|
||||
bconchar(encname, ' ');
|
||||
}
|
||||
|
||||
for(i = 0; i < blength(encname) / (sizeof(uint32_t) * 2); i+=2) {
|
||||
// run encipher on this
|
||||
// BUG: get rid of encipher
|
||||
for(i = 0; i < (size_t)blength(encname) / (sizeof(uint32_t) * 2); i+=2) {
|
||||
encipher(1, v+i, STORE_KEY);
|
||||
}
|
||||
|
||||
// do a lame "base 64" kind of thing on it
|
||||
// BUG: this is NOT the best way, it's a quick hack to get it working
|
||||
// replace with real BASE64 later
|
||||
for(i = 0; i < (size_t)blength(encname); i++) {
|
||||
int at = encname->data[i] % blength(&BASE64);
|
||||
encname->data[i] = BASE64.data[at];
|
||||
int at = encname->data[i] % blength(&FAUX64);
|
||||
encname->data[i] = FAUX64.data[at];
|
||||
}
|
||||
|
||||
debug("encrypted %s to %s", bdata(name), bdata(encname));
|
||||
// that's our final hack encrypted name
|
||||
return encname;
|
||||
}
|
||||
|
||||
|
||||
bstring sanitize_location(bstring base, bstring path)
|
||||
{
|
||||
char real[PATH_MAX+1] = {0};
|
||||
bstring attempt = NULL;
|
||||
bstring encpath = NULL;
|
||||
|
||||
// encrypt armore the name
|
||||
// BUG: ditch encryption, it was dumb
|
||||
encpath = encrypt_armor_name(path);
|
||||
check(encpath != NULL, "Failed to encrypt path name: %s", bdata(path));
|
||||
|
||||
bstring encpath = encrypt_armor_name(path);
|
||||
|
||||
bstring attempt = bformat("%s/%s", bdata(base), bdata(encpath));
|
||||
realpath(bdata(attempt), real);
|
||||
bdestroy(attempt);
|
||||
|
||||
if(!bisstemeqblk(base, real, blength(base))) {
|
||||
log_err("HACK ATTEMPT!");
|
||||
return NULL;
|
||||
} else {
|
||||
return bfromcstr(real);
|
||||
}
|
||||
}
|
||||
|
||||
int handle_load(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("load: from %s to %s", bdata(cmd->name), bdata(cmd->arg));
|
||||
int rc = 0;
|
||||
bstring to = cmd->arg;
|
||||
bstring from = cmd->name;
|
||||
Record *info = Hashmap_get(DATA, to);;
|
||||
bstring location = sanitize_location(STORE_PATH, from);
|
||||
int fd = 0;
|
||||
|
||||
check(location, "Failed to sanitize %s/%s", bdata(STORE_PATH), bdata(from));
|
||||
|
||||
if(info != NULL) {
|
||||
send_reply(send_rb, &EXISTS);
|
||||
} else {
|
||||
info = calloc(sizeof(Record), 1);
|
||||
check_mem(info);
|
||||
info->stat = calloc(sizeof(Stats), 1);
|
||||
check_mem(info->stat);
|
||||
|
||||
// with encrypted names this is fixed
|
||||
fd = open(bdata(location), O_RDONLY | O_EXLOCK);
|
||||
check(fd >= 0, "File does not exist: %s", bdata(location));
|
||||
|
||||
rc = read(fd, info->stat, sizeof(Stats));
|
||||
check(rc == sizeof(Stats), "Failed to read record at %s: %d", bdata(location), rc);
|
||||
|
||||
// it's unclear from the man page, but this should UNLOCK
|
||||
// and close atomically
|
||||
close(fd);
|
||||
|
||||
info->name = bstrcpy(to);
|
||||
rc = Hashmap_set(DATA, info->name, info);
|
||||
check(rc == 0, "Failed to hashmap set: %s", bdata(info->name));
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
bdestroy(location);
|
||||
return 0;
|
||||
// combine it with the base, this means that we've armored the
|
||||
// path so we can just append it
|
||||
attempt = bformat("%s/%s", bdata(base), bdata(encpath));
|
||||
bdestroy(encpath);
|
||||
return attempt;
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
bdestroy(location);
|
||||
if(info) {
|
||||
if(info->stat) free(info->stat);
|
||||
free(info);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int handle_store(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
check(STORE_PATH, "Failed to configure the storage path.");
|
||||
debug("store: %s", bdata(cmd->name));
|
||||
bstring from = cmd->name;
|
||||
int rc = 0;
|
||||
bstring location = sanitize_location(STORE_PATH, from);
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
int fd = 0;
|
||||
|
||||
check(location, "Failed to sanitize %s/%s", bdata(STORE_PATH), bdata(from));
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
fd = open(bdata(location), O_WRONLY | O_CREAT | O_EXLOCK, S_IRWXU);
|
||||
check(fd >= 0, "File does not exist: %s", bdata(location));
|
||||
|
||||
rc = write(fd, info->stat, sizeof(Stats));
|
||||
check(rc == sizeof(Stats), "Failed to write to %s", bdata(location));
|
||||
|
||||
// it's unclear from the man page, but this should UNLOCK
|
||||
// and close atomically
|
||||
close(fd);
|
||||
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
if(encpath) bdestroy(encpath);
|
||||
if(attempt) bdestroy(attempt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int handle_create(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
if(Hashmap_get(DATA, path) && biseq(path, cmd->name)) {
|
||||
// only report exists for the root, children get skipped
|
||||
int rc = 0;
|
||||
int is_root = biseq(path, cmd->name);
|
||||
log_info("create: %s %s %s", bdata(cmd->name), bdata(path), bdata(cmd->number));
|
||||
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
if(info != NULL && is_root) {
|
||||
// report if root exists, just skip children
|
||||
send_reply(send_rb, &EXISTS);
|
||||
} else if(info != NULL) {
|
||||
debug("Child %s exists, skipping it.", bdata(path));
|
||||
return 0;
|
||||
} else {
|
||||
// new child so make it
|
||||
debug("create: %s %s", bdata(path), bdata(cmd->number));
|
||||
Record *info = calloc(sizeof(Record), 1);
|
||||
|
||||
Record *info = calloc(1, sizeof(Record));
|
||||
check_mem(info);
|
||||
|
||||
// set its stat element
|
||||
info->stat = Stats_create();
|
||||
check_mem(info->stat);
|
||||
|
||||
// set its name element
|
||||
info->name = bstrcpy(path);
|
||||
check_mem(info->name);
|
||||
|
||||
// do a first sample
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
|
||||
Hashmap_set(DATA, info->name, info);
|
||||
|
||||
// only send the OK on the last path part
|
||||
if(cmd->path->qty == 2) {
|
||||
// add it to the hashmap
|
||||
rc = Hashmap_set(DATA, info->name, info);
|
||||
check(rc == 0, "Failed to add data to map.");
|
||||
|
||||
// only send the for the root part
|
||||
if(is_root) {
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
}
|
||||
|
@ -225,9 +158,83 @@ error:
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int handle_sample(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
// get the info from the hashmap
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
int is_root = biseq(path, cmd->name);
|
||||
log_info("sample %s %s %s", bdata(cmd->name), bdata(path), bdata(cmd->number));
|
||||
bstring child_path = NULL;
|
||||
|
||||
if(info == NULL) {
|
||||
// if it doesn't exist then DNE
|
||||
send_reply(send_rb, &DNE);
|
||||
return 0;
|
||||
} else {
|
||||
if(is_root) {
|
||||
// just sample the root like normal
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
} else {
|
||||
// need to do some hackery to get the child path
|
||||
// for rolling up mean-of-means on it
|
||||
|
||||
// increase the qty on path up one
|
||||
cmd->path->qty++;
|
||||
// get the "child path" (previous path?)
|
||||
child_path = bjoin(cmd->path, &SLASH);
|
||||
// get that info from the DATA
|
||||
Record *child_info = Hashmap_get(DATA, child_path);
|
||||
bdestroy(child_path);
|
||||
|
||||
// if it exists then sample on it
|
||||
if(child_info) {
|
||||
// info is /logins, child_info is /logins/zed
|
||||
// we want /logins/zed's mean to be a new sample on /logins
|
||||
Stats_sample(info->stat, Stats_mean(child_info->stat));
|
||||
}
|
||||
// drop the path back to where it was
|
||||
cmd->path->qty--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// do the reply for the mean last
|
||||
bstring reply = bformat("%f\n", Stats_mean(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_delete(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
log_info("delete: %s", bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
check(path == NULL && cmd->path == NULL, "Should be a recursive command.");
|
||||
|
||||
// BUG: should just decide that this isn't scanned
|
||||
// but run once, for now just only run on root
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
Hashmap_delete(DATA, cmd->name);
|
||||
|
||||
free(info->stat);
|
||||
bdestroy(info->name);
|
||||
free(info);
|
||||
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int handle_mean(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("mean: %s from %s", bdata(path), bdata(cmd->name));
|
||||
log_info("mean: %s %s %s", bdata(cmd->name), bdata(path), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
if(info == NULL) {
|
||||
|
@ -241,52 +248,31 @@ int handle_mean(Command *cmd, RingBuffer *send_rb, bstring path)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int handle_sample(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
int handle_stddev(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("sample: %s %s", bdata(path), bdata(cmd->number));
|
||||
log_info("stddev: %s %s %s", bdata(cmd->name), bdata(path), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
bstring child_path = NULL;
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
debug("found info: '%s' vs. '%s'", bdata(path), bdata(cmd->name));
|
||||
|
||||
if(biseq(path, cmd->name)) {
|
||||
Stats_sample(info->stat, atof(bdata(cmd->number)));
|
||||
} else {
|
||||
// need to do a bit of hackery to get the child path
|
||||
// then we can do a mean-of-means on it
|
||||
cmd->path->qty++;
|
||||
child_path = bjoin(cmd->path, &SLASH);
|
||||
debug("child_path: %s", bdata(child_path));
|
||||
Record *child_info = Hashmap_get(DATA, child_path);
|
||||
|
||||
if(child_info) {
|
||||
Stats_sample(info->stat, Stats_mean(child_info->stat));
|
||||
}
|
||||
|
||||
cmd->path->qty--; // drop it back down to continue
|
||||
}
|
||||
bstring reply = bformat("%f\n", Stats_stddev(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
}
|
||||
|
||||
bstring reply = bformat("%f\n", Stats_mean(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handle_dump(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("dump: %s from %s", bdata(path), bdata(cmd->name));
|
||||
log_info("dump: %s, %s, %s", bdata(cmd->name), bdata(path), bdata(path));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
bstring reply = bformat("%s %f %f %f %f %ld %f %f\n",
|
||||
bdata(info->name),
|
||||
bstring reply = bformat("%f %f %f %f %ld %f %f\n",
|
||||
Stats_mean(info->stat),
|
||||
Stats_stddev(info->stat),
|
||||
info->stat->sum,
|
||||
|
@ -302,55 +288,188 @@ int handle_dump(Command *cmd, RingBuffer *send_rb, bstring path)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int handle_delete(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
|
||||
int handle_store(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("delete: %s from %s", bdata(path), bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
Record *info = Hashmap_get(DATA, cmd->name);
|
||||
bstring location = NULL;
|
||||
bstring from = cmd->name;
|
||||
int rc = 0;
|
||||
int fd = -1;
|
||||
|
||||
check(cmd != NULL, "Invalid command.");
|
||||
debug("store %s", bdata(cmd->name));
|
||||
check(path == NULL && cmd->path == NULL, "Store is non-recursive.");
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
} else {
|
||||
Hashmap_delete(DATA, path);
|
||||
free(info->stat);
|
||||
bdestroy(info->name);
|
||||
free(info);
|
||||
// it exists so we sanitize the name
|
||||
location = sanitize_location(STORE_PATH, from);
|
||||
check(location, "Failed to sanitize the location.");
|
||||
|
||||
// open the file we need with EXLOCK
|
||||
fd = open(bdata(location), O_WRONLY | O_CREAT | O_EXLOCK, S_IRWXU);
|
||||
check(fd >= 0, "Cannot open file for writing: %s", bdata(location));
|
||||
|
||||
// write the Stats part of info to it
|
||||
rc = write(fd, info->stat, sizeof(Stats));
|
||||
check(rc == sizeof(Stats), "Failed to write to %s", bdata(location));
|
||||
|
||||
// close, which should release the lock
|
||||
close(fd);
|
||||
|
||||
// then send OK
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
if(fd < 0) close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int handle_stddev(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
int handle_load(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
debug("stddev: %s from %s", bdata(path), bdata(cmd->name));
|
||||
Record *info = Hashmap_get(DATA, path);
|
||||
bstring to = cmd->arg;
|
||||
bstring from = cmd->name;
|
||||
bstring location = NULL;
|
||||
Record *info = Hashmap_get(DATA, to);
|
||||
int fd = -1;
|
||||
|
||||
if(info == NULL) {
|
||||
send_reply(send_rb, &DNE);
|
||||
check(path == NULL && cmd->path == NULL, "Load is non-recursive.");
|
||||
|
||||
if(info != NULL) {
|
||||
// don't do it if the target to exists
|
||||
send_reply(send_rb, &EXISTS);
|
||||
} else {
|
||||
bstring reply = bformat("%f\n", Stats_stddev(info->stat));
|
||||
send_reply(send_rb, reply);
|
||||
bdestroy(reply);
|
||||
location = sanitize_location(STORE_PATH, from);
|
||||
check(location, "Failed to sanitize location.");
|
||||
|
||||
// make a new record for the to target
|
||||
// TODO: make regular CRUD methods for Record
|
||||
info = calloc(1, sizeof(Record));
|
||||
check_mem(info);
|
||||
|
||||
info->stat = calloc(1, sizeof(Stats));
|
||||
check_mem(info->stat);
|
||||
|
||||
// open the file to read from readonly and locked
|
||||
fd = open(bdata(location), O_RDONLY | O_EXLOCK);
|
||||
check(fd >= 0, "Error opening file: %s", bdata(location));
|
||||
|
||||
// read into the stats record
|
||||
int rc = read(fd, info->stat, sizeof(Stats));
|
||||
check(rc == sizeof(Stats), "Failed to read record at %s", bdata(location));
|
||||
|
||||
// close so we release the lock quick
|
||||
close(fd);
|
||||
|
||||
// make a copy of to as the name for the info
|
||||
info->name = bstrcpy(to);
|
||||
check_mem(info->name);
|
||||
|
||||
// put it in the hashmap
|
||||
rc = Hashmap_set(DATA, info->name, info);
|
||||
check(rc == 0, "Failed to ass to data map: %s", bdata(info->name));
|
||||
|
||||
// and send the reply
|
||||
send_reply(send_rb, &OK);
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
if(fd < 0) close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int parse_command(struct bstrList *splits, Command *cmd)
|
||||
{
|
||||
check(splits != NULL, "Invalid split line.");
|
||||
|
||||
// get the command
|
||||
cmd->command = splits->entry[0];
|
||||
|
||||
if(biseq(cmd->command, &CREATE)) {
|
||||
check(splits->qty == 3, "Failed to parse create: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_create;
|
||||
cmd->path = parse_name(cmd->name);
|
||||
} else if(biseq(cmd->command, &MEAN)) {
|
||||
check(splits->qty == 2, "Failed to parse mean: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_mean;
|
||||
cmd->path = parse_name(cmd->name);
|
||||
} else if(biseq(cmd->command, &SAMPLE)) {
|
||||
check(splits->qty == 3, "Failed to parse sample: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_sample;
|
||||
cmd->path = parse_name(cmd->name);
|
||||
} else if(biseq(cmd->command, &DUMP)) {
|
||||
check(splits->qty == 2, "Failed to parse dump: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_dump;
|
||||
cmd->path = parse_name(cmd->name);
|
||||
} else if(biseq(cmd->command, &DELETE)) {
|
||||
check(splits->qty == 2, "Failed to parse delete: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_delete;
|
||||
cmd->path = NULL;
|
||||
} else if(biseq(cmd->command, &STDDEV)) {
|
||||
check(splits->qty == 2, "Failed to parse stddev: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_stddev;
|
||||
cmd->path = parse_name(cmd->name);
|
||||
} else if(biseq(cmd->command, &STORE)) {
|
||||
// store URL
|
||||
check(splits->qty == 2, "Failed to parse store: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->handler = handle_store;
|
||||
cmd->path = NULL;
|
||||
} else if(biseq(cmd->command, &LOAD)) {
|
||||
// load FROM TO
|
||||
check(splits->qty == 3, "Failed to parse load: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->arg = splits->entry[2];
|
||||
cmd->handler = handle_load;
|
||||
cmd->path = NULL;
|
||||
} else {
|
||||
sentinel("Failed to parse the command.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int scan_paths(Command *cmd, RingBuffer *send_rb)
|
||||
{
|
||||
size_t qty = cmd->path->qty;
|
||||
int rc = 0;
|
||||
check(cmd->path != NULL, "Path was not set in command.");
|
||||
|
||||
for(;cmd->path->qty > 1; cmd->path->qty--) {
|
||||
int rc = 0;
|
||||
// save the original path length
|
||||
size_t qty = cmd->path->qty;
|
||||
|
||||
// starting at the longest path, shorten it and call
|
||||
// for each one:
|
||||
for(; cmd->path->qty > 1; cmd->path->qty--) {
|
||||
// remake the path with / again
|
||||
bstring path = bjoin(cmd->path, &SLASH);
|
||||
debug("running handler on scanned path: %s", bdata(path));
|
||||
// call the handler with the path
|
||||
rc = cmd->handler(cmd, send_rb, path);
|
||||
// if the handler returns != 0 then abort and return that
|
||||
bdestroy(path);
|
||||
|
||||
if(rc != 0) break;
|
||||
}
|
||||
|
||||
// restore path length
|
||||
cmd->path->qty = qty;
|
||||
return rc;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct bstrList *parse_name(bstring name)
|
||||
|
@ -358,97 +477,41 @@ struct bstrList *parse_name(bstring name)
|
|||
return bsplits(name, &SLASH);
|
||||
}
|
||||
|
||||
int parse_command(struct bstrList *splits, Command *cmd)
|
||||
{
|
||||
cmd->command = splits->entry[0];
|
||||
|
||||
if(biseq(cmd->command, &CREATE)) {
|
||||
check(splits->qty == 3, "Failed to parse create: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_create;
|
||||
} else if(biseq(cmd->command, &MEAN)) {
|
||||
check(splits->qty == 2, "Failed to parse mean: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_mean;
|
||||
} else if(biseq(cmd->command, &SAMPLE)) {
|
||||
check(splits->qty == 3, "Failed to parse sample: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->number = splits->entry[2];
|
||||
cmd->handler = handle_sample;
|
||||
} else if(biseq(cmd->command, &DUMP)) {
|
||||
check(splits->qty == 2, "Failed to parse dump: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_dump;
|
||||
} else if(biseq(cmd->command, &DELETE)) {
|
||||
check(splits->qty == 2, "Failed to parse delete: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_delete;
|
||||
} else if(biseq(cmd->command, &STDDEV)) {
|
||||
check(splits->qty == 2, "Failed to parse stddev: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = parse_name(cmd->name);
|
||||
cmd->handler = handle_stddev;
|
||||
} else if(biseq(cmd->command, &STORE)) {
|
||||
check(splits->qty == 2, "Failed to parse store: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = NULL;
|
||||
cmd->handler = handle_store;
|
||||
} else if(biseq(cmd->command, &LOAD)) {
|
||||
check(splits->qty == 3, "Failed to parse load: %d", splits->qty);
|
||||
cmd->name = splits->entry[1];
|
||||
cmd->path = NULL;
|
||||
cmd->arg = splits->entry[2];
|
||||
cmd->handler = handle_load;
|
||||
} else {
|
||||
sentinel("Failed to parse the command.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_line(bstring data, RingBuffer *send_rb)
|
||||
{
|
||||
int rc = 0;
|
||||
int rc = -1;
|
||||
Command cmd = {.command = NULL};
|
||||
|
||||
// split data on line boundaries
|
||||
struct bstrList *splits = bsplits(data, &LINE_SPLIT);
|
||||
check(splits != NULL, "Bad data.");
|
||||
|
||||
// parse it into a command
|
||||
rc = parse_command(splits, &cmd);
|
||||
check(rc == 0, "Failed to parse command.");
|
||||
|
||||
// they used a path so break it up
|
||||
if(cmd.path) {
|
||||
// scan the path and call the handlers
|
||||
if(cmd.path) {
|
||||
check(cmd.path->qty > 1, "Didn't give a valid URL.");
|
||||
rc = scan_paths(&cmd, send_rb);
|
||||
check(rc == 0, "Failure running command against path: %s", bdata(cmd.name));
|
||||
check(rc == 0, "Failure running recursive command against path: %s", bdata(cmd.name));
|
||||
bstrListDestroy(cmd.path);
|
||||
} else {
|
||||
// no need to recurse
|
||||
rc = cmd.handler(&cmd, send_rb, NULL);
|
||||
check(rc == 0, "Failure running command against path: %s", bdata(cmd.name));
|
||||
check(rc == 0, "Failed running command against path: %s", bdata(cmd.name));
|
||||
}
|
||||
|
||||
bstrListDestroy(cmd.path);
|
||||
bstrListDestroy(splits);
|
||||
|
||||
return rc;
|
||||
error:
|
||||
return 0;
|
||||
|
||||
error: // fallthrough
|
||||
if(cmd.path) bstrListDestroy(cmd.path);
|
||||
if(splits) bstrListDestroy(splits);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int client_handler(int fd)
|
||||
void client_handler(int client_fd)
|
||||
{
|
||||
int rc = 0;
|
||||
RingBuffer *recv_rb = RingBuffer_create(RB_SIZE);
|
||||
|
@ -457,89 +520,97 @@ int client_handler(int fd)
|
|||
check_mem(recv_rb);
|
||||
check_mem(send_rb);
|
||||
|
||||
while(read_some(recv_rb, fd, 1) != -1) {
|
||||
// TODO: read a line, put the rest back
|
||||
// keep reading into the recv buffer and sending on send
|
||||
while(read_some(recv_rb, client_fd, 1) != -1) {
|
||||
// read a line from the recv_rb
|
||||
bstring data = read_line(recv_rb, LINE_ENDING);
|
||||
check(data != NULL, "Client closed.");
|
||||
|
||||
// parse it, close on any protocol errors
|
||||
rc = parse_line(data, send_rb);
|
||||
bdestroy(data); // cleanup here
|
||||
check(rc == 0, "Failed to parse user. Closing.");
|
||||
|
||||
// and as long as there's something to send, send it
|
||||
if(RingBuffer_available_data(send_rb)) {
|
||||
write_some(send_rb, fd, 1);
|
||||
write_some(send_rb, client_fd, 1);
|
||||
}
|
||||
|
||||
bdestroy(data);
|
||||
}
|
||||
|
||||
rc = close(fd);
|
||||
check(rc != -1, "Failed to close fd.");
|
||||
// close the socket
|
||||
rc = close(client_fd);
|
||||
check(rc != -1, "Failed to close the socket.");
|
||||
|
||||
error: // fallthrough
|
||||
if(recv_rb) RingBuffer_destroy(recv_rb);
|
||||
exit(0);
|
||||
if(send_rb) RingBuffer_destroy(send_rb);
|
||||
exit(0); // just exit the child process
|
||||
}
|
||||
|
||||
void handle_sigchld(int sig) {
|
||||
if(sig == SIGCHLD) {
|
||||
while(waitpid(-1, NULL, WNOHANG) > 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int run_server(const char *host, const char *port, const char *store_path)
|
||||
int setup_data_store(const char *store_path)
|
||||
{
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t sin_size = sizeof(client_addr);
|
||||
int server_socket = 0;
|
||||
int client_fd = 0;
|
||||
int rc = 0;
|
||||
|
||||
// a more advanced design simply wouldn't use this
|
||||
DATA = Hashmap_create(NULL, NULL);
|
||||
check_mem(DATA);
|
||||
|
||||
char *path = realpath(store_path, NULL);
|
||||
check(path != NULL, "Failed to get real path for storage path: %s", store_path);
|
||||
|
||||
check(path != NULL, "Failed to get the real path for storage: %s", store_path);
|
||||
|
||||
STORE_PATH = bfromcstr(path);
|
||||
debug("Created STORE_PATH: %s", bdata(STORE_PATH));
|
||||
free(path);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int run_server(const char *host, const char *port, const char *store_path)
|
||||
{
|
||||
int rc = 0;
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t sin_size = sizeof(client_addr);
|
||||
int server_socket = 0;
|
||||
int client_fd = 0;
|
||||
|
||||
rc = setup_data_store(store_path);
|
||||
check(rc == 0, "Failed to setup the data store.");
|
||||
|
||||
struct sigaction sa = {
|
||||
.sa_handler = handle_sigchld,
|
||||
.sa_handler = handle_sigchild,
|
||||
.sa_flags = SA_RESTART | SA_NOCLDSTOP
|
||||
};
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
check(host != NULL, "Invalid host.");
|
||||
check(port != NULL, "Invalid port.");
|
||||
|
||||
// create a sigaction that handles SIGCHLD
|
||||
sigemptyset(&sa.sa_mask);
|
||||
rc = sigaction(SIGCHLD, &sa, 0);
|
||||
check(rc != -1, "Failed to setup signal handler for child processes.");
|
||||
|
||||
// listen on the given port and host
|
||||
server_socket = server_listen(host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.",
|
||||
host, port);
|
||||
check(server_socket >= 0, "bind to %s:%s failed.", host, port);
|
||||
|
||||
while (1) {
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
while(1) {
|
||||
// accept the connection
|
||||
client_fd = accept(server_socket, (struct sockaddr *)&client_addr, &sin_size);
|
||||
check(client_fd >= 0, "Failed to accept connection.");
|
||||
|
||||
debug("Client connected.");
|
||||
|
||||
rc = fork();
|
||||
check(rc != -1, "Failed to fork!");
|
||||
|
||||
if(rc == 0) {
|
||||
// in the child process
|
||||
close(server_socket);
|
||||
// child process
|
||||
close(server_socket); // don't need this
|
||||
// handle the client
|
||||
client_handler(client_fd);
|
||||
} else {
|
||||
// server process doesn't need this
|
||||
close(client_fd);
|
||||
// server process
|
||||
close(client_fd); // don't need this
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
error: // fallthrough
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,43 @@
|
|||
#ifndef _statserve_h
|
||||
#define _statserve_h
|
||||
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <lcthw/stats.h>
|
||||
|
||||
struct Command;
|
||||
|
||||
typedef int (*handler_cb)(struct Command *cmd, RingBuffer *send_rb, bstring path);
|
||||
|
||||
typedef struct Command {
|
||||
bstring command;
|
||||
bstring name;
|
||||
struct bstrList *path;
|
||||
bstring number;
|
||||
bstring arg;
|
||||
handler_cb handler;
|
||||
} Command;
|
||||
|
||||
|
||||
typedef struct Record {
|
||||
bstring name;
|
||||
Stats *stat;
|
||||
} Record;
|
||||
|
||||
struct tagbstring OK;
|
||||
|
||||
int setup_data_store(const char *store_path);
|
||||
|
||||
struct bstrList *parse_name(bstring name);
|
||||
|
||||
int scan_paths(Command *cmd, RingBuffer *send_rb);
|
||||
|
||||
int parse_line(bstring data, RingBuffer *send_rb);
|
||||
|
||||
int run_server(const char *host, const char *port, const char *store_path);
|
||||
|
||||
bstring sanitize_location(bstring base, bstring path);
|
||||
|
||||
bstring encrypt_armor_name(bstring name);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#define _minunit_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dbg.h>
|
||||
#include <lcthw/dbg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define mu_suite_start() char *message = NULL
|
||||
|
|
|
@ -1,21 +1,194 @@
|
|||
#include "minunit.h"
|
||||
#include <statserve.h>
|
||||
#include <dlfcn.h>
|
||||
#include "statserve.h"
|
||||
#include <lcthw/bstrlib.h>
|
||||
#include <lcthw/ringbuffer.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct LineTest {
|
||||
char *line;
|
||||
bstring result;
|
||||
char *description;
|
||||
} LineTest;
|
||||
|
||||
char *test_statserve()
|
||||
int attempt_line(LineTest test)
|
||||
{
|
||||
int rc = -1;
|
||||
bstring result = NULL;
|
||||
|
||||
// mu_assert(echo_server("127.0.0.1", "7899") == 0, "Failed to start echo server.");
|
||||
bstring line = bfromcstr(test.line);
|
||||
RingBuffer *send_rb = RingBuffer_create(1024);
|
||||
|
||||
rc = parse_line(line, send_rb);
|
||||
check(rc == 0, "Failed to parse line.");
|
||||
result = RingBuffer_get_all(send_rb);
|
||||
check(result != NULL, "Ring buffer empty.");
|
||||
check(biseq(result, test.result), "Got the wrong output: %s expected %s",
|
||||
bdata(result), bdata(test.result));
|
||||
|
||||
bdestroy(line);
|
||||
RingBuffer_destroy(send_rb);
|
||||
return 1; // using 1 for tests
|
||||
error:
|
||||
|
||||
log_err("Failed to process test %s: got %s", test.line, bdata(result));
|
||||
if(line) bdestroy(line);
|
||||
if(send_rb) RingBuffer_destroy(send_rb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int run_test_lines(LineTest *tests, int count)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for(i = 0; i < count; i++) {
|
||||
check(attempt_line(tests[i]), "Failed to run %s", tests[i].description);
|
||||
}
|
||||
|
||||
return 1;
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fake_command(Command *cmd, RingBuffer *send_rb, bstring path)
|
||||
{
|
||||
check(cmd != NULL, "Bad cmd.");
|
||||
check(cmd->path != NULL, "Bad path.");
|
||||
check(send_rb != NULL, "Bad send_rb.");
|
||||
check(path != NULL, "Bad path given.");
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *test_path_parsing()
|
||||
{
|
||||
struct bstrList *result = NULL;
|
||||
struct tagbstring slash = bsStatic("/");
|
||||
struct tagbstring logins_zed = bsStatic("/logins/zed");
|
||||
struct tagbstring command_name = bsStatic("dump");
|
||||
RingBuffer *send_rb = RingBuffer_create(1024);
|
||||
struct bstrList *path = bsplits(&logins_zed, &slash);
|
||||
int rc = 0;
|
||||
|
||||
Command fake = {
|
||||
.command = &command_name,
|
||||
.name = &logins_zed,
|
||||
.number = NULL,
|
||||
.handler = fake_command,
|
||||
.path = path
|
||||
};
|
||||
|
||||
result = parse_name(&logins_zed);
|
||||
mu_assert(result != NULL, "Failed to parse /logins/zed");
|
||||
|
||||
rc = scan_paths(&fake, send_rb);
|
||||
mu_assert(rc != -1, "scan_paths failed.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_create()
|
||||
{
|
||||
LineTest tests[] = {
|
||||
{.line = "create /zed 100", .result = &OK, .description = "create zed failed"},
|
||||
{.line = "create /joe 100", .result = &OK, .description = "create joe failed"},
|
||||
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 2), "Failed to run create tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_sample()
|
||||
{
|
||||
struct tagbstring sample1 = bsStatic("100.000000\n");
|
||||
|
||||
LineTest tests[] = {
|
||||
{.line = "sample /zed 100", .result = &sample1, .description = "sample zed failed."}
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 1), "Failed to run sample tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_store_load()
|
||||
{
|
||||
LineTest tests[] = {
|
||||
{.line = "delete /zed", .result = &OK, .description = "delete zed failed"},
|
||||
{.line = "create /zed 100", .result = &OK, .description = "create zed failed"},
|
||||
{.line = "store /zed", .result = &OK, .description = "store zed failed"},
|
||||
{.line = "load /zed /sam", .result = &OK, .description = "load zed failed"},
|
||||
{.line = "delete /sam", .result = &OK, .description = "load zed failed"},
|
||||
};
|
||||
|
||||
mu_assert(run_test_lines(tests, 3), "Failed to run sample tests.");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_encrypt_armor_name()
|
||||
{
|
||||
struct tagbstring test1 = bsStatic("/logins");
|
||||
struct tagbstring expect1 = bsStatic("vtmTmzNI");
|
||||
struct tagbstring test2 = bsStatic("../../../../../../../../etc/passwd");
|
||||
struct tagbstring expect2 = bsStatic("pVOBpFjHEIhB7cuT3BGUvyZGn3lvyj226mgggggg");
|
||||
|
||||
bstring result = encrypt_armor_name(&test1);
|
||||
debug("Got encrypted name %s", bdata(result));
|
||||
mu_assert(biseq(result, &expect1), "Failed to encrypt test2.");
|
||||
bdestroy(result);
|
||||
|
||||
result = encrypt_armor_name(&test2);
|
||||
debug("Got encrypted name %s", bdata(result));
|
||||
mu_assert(biseq(result, &expect2), "Failed to encrypt test2.");
|
||||
bdestroy(result);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *test_path_sanitize_armor()
|
||||
{
|
||||
struct tagbstring base = bsStatic("/tmp");
|
||||
struct tagbstring test1 = bsStatic("/somepath/here/there");
|
||||
bstring encname = encrypt_armor_name(&test1);
|
||||
bstring expect = bformat("%s/%s", bdata(&base), bdata(encname));
|
||||
struct tagbstring test2 = bsStatic("../../../../../../../../etc/passwd");
|
||||
|
||||
|
||||
bstring result = sanitize_location(&base, &test1);
|
||||
mu_assert(result != NULL, "Failed to sanitize path.");
|
||||
mu_assert(biseq(result, expect), "failed to sanitize test1");
|
||||
|
||||
// this should be pulled up into a tester function
|
||||
// BUG: just get rid of this and use md5
|
||||
encname = encrypt_armor_name(&test2);
|
||||
expect = bformat("%s/%s", bdata(&base), bdata(encname));
|
||||
result = sanitize_location(&base, &test2);
|
||||
mu_assert(result != NULL, "Failed to sanitize path.");
|
||||
mu_assert(biseq(result, expect), "failed to sanitize test1");
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *all_tests()
|
||||
{
|
||||
mu_suite_start();
|
||||
|
||||
mu_run_test(test_statserve);
|
||||
int rc = setup_data_store("/tmp");
|
||||
mu_assert(rc == 0, "Failed to setup the data store.");
|
||||
|
||||
mu_run_test(test_path_parsing);
|
||||
mu_run_test(test_encrypt_armor_name);
|
||||
mu_run_test(test_path_sanitize_armor);
|
||||
mu_run_test(test_create);
|
||||
mu_run_test(test_sample);
|
||||
mu_run_test(test_store_load);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue