1 /* Blue Sky: File Systems in the Cloud
3 * Copyright (C) 2011 The Regents of the University of California
4 * Written by Michael Vrable <mvrable@cs.ucsd.edu>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the University nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 /* A very simple AWS-like server, without any authentication. This is meant
32 * performance testing in a local environment. The server offers weak
33 * guarantees on data durability--data is stored directly to the file system
34 * without synchronization, so data might be lost in a crash. This should
35 * offer good performance though for benchmarking.
37 * Protocol: Each request is a whitespace-separated (typically, a single space)
38 * list of parameters terminated by a newline character. The response is a
39 * line containing a response code and a body length (again white-separated and
40 * newline-terminated) followed by the response body. Requests:
41 * GET filename offset length
56 #include <netinet/in.h>
57 #include <sys/socket.h>
59 #include <sys/types.h>
62 #define SIMPLESTORE_PORT 9541
64 /* Maximum number of connections the server will queue waiting for us to call
66 #define TCP_BACKLOG 32
68 #define WHITESPACE " \t\r\n"
71 /* Maximum length of a command that we will accept. */
72 #define MAX_CMD_LEN 4096
74 #define MAX_OBJECT_SIZE (256 << 20)
76 enum command { GET, PUT, LIST, DELETE };
78 void write_data(int fd, const char *buf, size_t len)
81 ssize_t bytes = write(fd, buf, len);
92 /* SIGCHLD handler, used to clean up processes that handle the connections. */
93 static void sigchld_handler(int signal)
98 while ((pid = waitpid(WAIT_ANY, NULL, WNOHANG)) > 0) {
103 if (errno == ECHILD && reaped) {
104 /* Don't print an error for the caes that we successfully cleaned
105 * up after all children. */
112 /* Return a text representation of a socket address. Returns a pointer to a
113 * static buffer so it is non-reentrant. */
114 const char *sockname(struct sockaddr_in *addr, socklen_t len)
116 static char buf[128];
117 if (len < sizeof(struct sockaddr_in))
119 if (addr->sin_family != AF_INET)
122 uint32_t ip = ntohl(addr->sin_addr.s_addr);
123 sprintf(buf, "%d.%d.%d.%d:%d",
124 (int)((ip >> 24) & 0xff),
125 (int)((ip >> 16) & 0xff),
126 (int)((ip >> 8) & 0xff),
128 ntohs(addr->sin_port));
133 /* Convert a path from a client to the actual filename used. Returns a string
134 * that must be freed. */
135 char *normalize_path(const char *in)
137 char *out = malloc(2 * strlen(in) + 1);
139 for (i = 0, j = 0; in[i] != '\0'; i++) {
143 } else if (in[i] == '_') {
154 void cmd_get(int fd, char *path, size_t start, ssize_t len)
158 char *response = "-1\n";
159 int file = open(path, O_RDONLY);
161 write_data(fd, response, strlen(response));
166 if (fstat(file, &statbuf) < 0) {
167 write_data(fd, response, strlen(response));
171 size_t datalen = statbuf.st_size;
173 if (start >= datalen) {
176 lseek(file, start, SEEK_SET);
180 if (len > 0 && len < datalen) {
184 sprintf(buf, "%zd\n", datalen);
185 write_data(fd, buf, strlen(buf));
187 while (datalen > 0) {
188 size_t needed = datalen > sizeof(buf) ? sizeof(buf) : datalen;
189 ssize_t bytes = read(file, buf, needed);
190 if (bytes < 0 && errno == EINTR)
193 /* Error reading necessary data, but we already told the client the
194 * file size so pad the data to the client with null bytes. */
195 memset(buf, 0, needed);
198 write_data(fd, buf, bytes);
205 void cmd_put(int fd, char *path, char *buf,
206 size_t object_size, size_t buf_used)
208 while (buf_used < object_size) {
211 bytes = read(fd, buf + buf_used, object_size - buf_used);
222 assert(bytes <= object_size - buf_used);
228 /* printf("Got %zd bytes for object '%s'\n", buf_used, path); */
230 char *response = "-1\n";
231 int file = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0600);
233 write_data(file, buf, object_size);
238 write_data(fd, response, strlen(response));
241 /* The core handler for processing requests from the client. This can be
242 * single-threaded since each connection is handled in a separate process. */
243 void handle_connection(int fd)
245 char cmdbuf[MAX_CMD_LEN];
249 /* Keep reading data until reaching a newline, so that a complete
250 * command can be parsed. */
251 if (buflen == 0 || memchr(cmdbuf, '\n', buflen) == NULL) {
254 if (buflen == MAX_CMD_LEN) {
255 /* Command is too long and thus malformed; close the
260 bytes = read(fd, cmdbuf + buflen, MAX_CMD_LEN - buflen);
270 assert(bytes <= MAX_CMD_LEN - buflen);
276 size_t cmdlen = (char *)memchr(cmdbuf, '\n', buflen) - cmdbuf + 1;
277 cmdbuf[cmdlen - 1] = '\0';
280 char *args[MAX_ARGS];
281 int arg_int[MAX_ARGS];
282 for (token = strtok(cmdbuf, WHITESPACE), arg_count = 0;
284 token = strtok(NULL, WHITESPACE), arg_count++)
286 args[arg_count] = token;
287 arg_int[arg_count] = atoi(token);
293 char *path = normalize_path(args[1]);
295 if (strcmp(args[0], "GET") == 0 && arg_count == 4) {
297 } else if (strcmp(args[0], "PUT") == 0 && arg_count == 3) {
299 } else if (strcmp(args[0], "DELETE") == 0 && arg_count == 2) {
306 memmove(cmdbuf, cmdbuf + cmdlen, buflen - cmdlen);
311 cmd_get(fd, path, arg_int[2], arg_int[3]);
314 size_t object_size = arg_int[2];
315 if (object_size > MAX_OBJECT_SIZE)
317 char *data_buf = malloc(object_size);
318 if (data_buf == NULL)
320 size_t data_buflen = buflen > object_size ? object_size : buflen;
322 memcpy(data_buf, cmdbuf, data_buflen);
323 if (data_buflen < buflen) {
324 memmove(cmdbuf, cmdbuf + data_buflen, buflen - data_buflen);
329 cmd_put(fd, path, data_buf, object_size, data_buflen);
333 //cmd_delete(fd, path);
343 /* Create a listening TCP socket on a new address (we do not use a fixed port).
344 * Return the file descriptor of the listening socket. */
348 struct sockaddr_in server_addr;
349 socklen_t addr_len = sizeof(server_addr);
351 fd = socket(PF_INET, SOCK_STREAM, 0);
358 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
360 server_addr.sin_family = AF_INET;
361 server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
362 server_addr.sin_port = htons(SIMPLESTORE_PORT);
363 if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
368 if (listen(fd, TCP_BACKLOG) < 0) {
373 if (getsockname(fd, (struct sockaddr *)&server_addr, &addr_len) < 0) {
374 perror("getsockname");
378 printf("Server listening on %s ...\n", sockname(&server_addr, addr_len));
384 /* Process-based server main loop. Wait for a connection, accept it, fork a
385 * child process to handle the connection, and repeat. Child processes are
386 * reaped in the SIGCHLD handler. */
387 void server_main(int listen_fd)
389 struct sigaction handler;
391 /* Install signal handler for SIGCHLD. */
392 handler.sa_handler = sigchld_handler;
393 sigemptyset(&handler.sa_mask);
394 handler.sa_flags = SA_RESTART;
395 if (sigaction(SIGCHLD, &handler, NULL) < 0) {
401 struct sockaddr_in client_addr;
402 socklen_t addr_len = sizeof(client_addr);
403 int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
407 /* Very simple error handling. Exit if something goes wrong. Later,
408 * might want to look into not killing off current connections abruptly
409 * if we encounter an error in the accept(). */
418 printf("Accepted connection from %s ...\n",
419 sockname(&client_addr, addr_len));
425 } else if (pid == 0) {
426 handle_connection(client_fd);
427 printf("Closing connection %s ...\n",
428 sockname(&client_addr, addr_len));
437 /* Print a help message describing command-line options to stdout. */
438 static void usage(char *argv0)
440 printf("Usage: %s [options...]\n"
441 "A simple key-value storage server.\n", argv0);
444 int main(int argc, char *argv[])
448 int display_help = 0, cmdline_error = 0;
451 for (i = 1; i < argc; i++) {
452 if (strcmp(argv[i], "-help") == 0) {
454 } else if (strcmp(argv[i], "-document_root") == 0) {
458 "Error: Argument to -document_root expected.\n");
461 document_root = argv[i];
463 } else if (strcmp(argv[i], "-port") == 0) {
467 "Error: Expected port number after -port.\n");
470 server_port = atoi(argv[i]);
471 if (server_port < 1 || server_port > 65535) {
473 "Error: Port must be between 1 and 65535.\n");
477 } else if (strcmp(argv[i], "-mime_types") == 0) {
481 "Error: Argument to -mime_types expected.\n");
484 mime_types_file = argv[i];
486 } else if (strcmp(argv[i], "-log") == 0) {
490 "Error: Argument to -log expected.\n");
496 fprintf(stderr, "Error: Unrecognized option '%s'\n", argv[i]);
502 /* Display a help message if requested, or let the user know to look at the
503 * help message if there was an error parsing the command line. In either
504 * case, the server never starts. */
508 } else if (cmdline_error) {
509 fprintf(stderr, "Run '%s -help' for a summary of options.\n", argv[0]);