Syncing the code with what was actually in the videos.

This commit is contained in:
lcthw 2015-08-23 20:12:20 -07:00
parent 4305a58bb9
commit 40d934686f
33 changed files with 1524 additions and 900 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -3,5 +3,4 @@
int echo_server(const char *host, const char *port);
#endif

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -6,3 +6,4 @@
build/*
tests/*_tests
bin/statserve
tags

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}