Add proper per-file copyright notices/licenses and top-level license.
[bluesky.git] / simplestore / server.c
1 /* Blue Sky: File Systems in the Cloud
2  *
3  * Copyright (C) 2011  The Regents of the University of California
4  * Written by Michael Vrable <mvrable@cs.ucsd.edu>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
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.
17  *
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
28  * SUCH DAMAGE.
29  */
30
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.
36  *
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
42  *   PUT filename length
43  *   DELETE filename
44  *   LIST prefix
45  */
46
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <assert.h>
50 #include <errno.h>
51 #include <fcntl.h>
52 #include <signal.h>
53 #include <string.h>
54 #include <time.h>
55 #include <unistd.h>
56 #include <netinet/in.h>
57 #include <sys/socket.h>
58 #include <sys/stat.h>
59 #include <sys/types.h>
60 #include <sys/wait.h>
61
62 #define SIMPLESTORE_PORT 9541
63
64 /* Maximum number of connections the server will queue waiting for us to call
65  * accept(). */
66 #define TCP_BACKLOG 32
67
68 #define WHITESPACE " \t\r\n"
69 #define MAX_ARGS 4
70
71 /* Maximum length of a command that we will accept. */
72 #define MAX_CMD_LEN 4096
73
74 #define MAX_OBJECT_SIZE (256 << 20)
75
76 enum command { GET, PUT, LIST, DELETE };
77
78 void write_data(int fd, const char *buf, size_t len)
79 {
80     while (len > 0) {
81         ssize_t bytes = write(fd, buf, len);
82         if (bytes < 0) {
83             if (errno == EINTR)
84                 continue;
85             exit(1);
86         }
87         buf += bytes;
88         len -= bytes;
89     }
90 }
91
92 /* SIGCHLD handler, used to clean up processes that handle the connections. */
93 static void sigchld_handler(int signal)
94 {
95     int pid;
96     int reaped = 0;
97
98     while ((pid = waitpid(WAIT_ANY, NULL, WNOHANG)) > 0) {
99         reaped++;
100     }
101
102     if (pid < 0) {
103         if (errno == ECHILD && reaped) {
104             /* Don't print an error for the caes that we successfully cleaned
105              * up after all children. */
106         } else {
107             perror("waitpid");
108         }
109     }
110 }
111
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)
115 {
116     static char buf[128];
117     if (len < sizeof(struct sockaddr_in))
118         return NULL;
119     if (addr->sin_family != AF_INET)
120         return NULL;
121
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),
127             (int)(ip & 0xff),
128             ntohs(addr->sin_port));
129
130     return buf;
131 }
132
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)
136 {
137     char *out = malloc(2 * strlen(in) + 1);
138     int i, j;
139     for (i = 0, j = 0; in[i] != '\0'; i++) {
140         if (in[i] == '/') {
141             out[j++] = '_';
142             out[j++] = 's';
143         } else if (in[i] == '_') {
144             out[j++] = '_';
145             out[j++] = 'u';
146         } else {
147             out[j++] = in[i];
148         }
149     }
150     out[j++] = '\0';
151     return out;
152 }
153
154 void cmd_get(int fd, char *path, size_t start, ssize_t len)
155 {
156     char buf[65536];
157
158     char *response = "-1\n";
159     int file = open(path, O_RDONLY);
160     if (file < 0) {
161         write_data(fd, response, strlen(response));
162         return;
163     }
164
165     struct stat statbuf;
166     if (fstat(file, &statbuf) < 0) {
167         write_data(fd, response, strlen(response));
168         return;
169     }
170
171     size_t datalen = statbuf.st_size;
172     if (start > 0) {
173         if (start >= datalen) {
174             datalen = 0;
175         } else {
176             lseek(file, start, SEEK_SET);
177             datalen -= start;
178         }
179     }
180     if (len > 0 && len < datalen) {
181         datalen = len;
182     }
183
184     sprintf(buf, "%zd\n", datalen);
185     write_data(fd, buf, strlen(buf));
186
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)
191             continue;
192         if (bytes <= 0) {
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);
196             bytes = needed;
197         }
198         write_data(fd, buf, bytes);
199         datalen -= bytes;
200     }
201
202     close(file);
203 }
204
205 void cmd_put(int fd, char *path, char *buf,
206              size_t object_size, size_t buf_used)
207 {
208     while (buf_used < object_size) {
209         ssize_t bytes;
210
211         bytes = read(fd, buf + buf_used, object_size - buf_used);
212         if (bytes < 0) {
213             if (errno == EINTR)
214                 continue;
215             else
216                 exit(1);
217         }
218
219         if (bytes == 0)
220             exit(1);
221
222         assert(bytes <= object_size - buf_used);
223         buf_used += bytes;
224
225         continue;
226     }
227
228     /* printf("Got %zd bytes for object '%s'\n", buf_used, path); */
229
230     char *response = "-1\n";
231     int file = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0600);
232     if (file >= 0) {
233         write_data(file, buf, object_size);
234         response = "0\n";
235         close(file);
236     }
237
238     write_data(fd, response, strlen(response));
239 }
240
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)
244 {
245     char cmdbuf[MAX_CMD_LEN];
246     size_t buflen = 0;
247
248     while (1) {
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) {
252             ssize_t bytes;
253
254             if (buflen == MAX_CMD_LEN) {
255                 /* Command is too long and thus malformed; close the
256                  * connection. */
257                 return;
258             }
259
260             bytes = read(fd, cmdbuf + buflen, MAX_CMD_LEN - buflen);
261             if (bytes < 0) {
262                 if (errno == EINTR)
263                     continue;
264                 else
265                     return;
266             }
267             if (bytes == 0)
268                 return;
269
270             assert(bytes <= MAX_CMD_LEN - buflen);
271             buflen += bytes;
272
273             continue;
274         }
275
276         size_t cmdlen = (char *)memchr(cmdbuf, '\n', buflen) - cmdbuf + 1;
277         cmdbuf[cmdlen - 1] = '\0';
278         char *token;
279         int arg_count;
280         char *args[MAX_ARGS];
281         int arg_int[MAX_ARGS];
282         for (token = strtok(cmdbuf, WHITESPACE), arg_count = 0;
283              token != NULL;
284              token = strtok(NULL, WHITESPACE), arg_count++)
285         {
286             args[arg_count] = token;
287             arg_int[arg_count] = atoi(token);
288         }
289
290         if (arg_count < 2) {
291             return;
292         }
293         char *path = normalize_path(args[1]);
294         enum command cmd;
295         if (strcmp(args[0], "GET") == 0 && arg_count == 4) {
296             cmd = GET;
297         } else if (strcmp(args[0], "PUT") == 0 && arg_count == 3) {
298             cmd = PUT;
299         } else if (strcmp(args[0], "DELETE") == 0 && arg_count == 2) {
300             cmd = DELETE;
301         } else {
302             return;
303         }
304
305         if (cmdlen < buflen)
306             memmove(cmdbuf, cmdbuf + cmdlen, buflen - cmdlen);
307         buflen -= cmdlen;
308
309         switch (cmd) {
310         case GET:
311             cmd_get(fd, path, arg_int[2], arg_int[3]);
312             break;
313         case PUT: {
314             size_t object_size = arg_int[2];
315             if (object_size > MAX_OBJECT_SIZE)
316                 return;
317             char *data_buf = malloc(object_size);
318             if (data_buf == NULL)
319                 return;
320             size_t data_buflen = buflen > object_size ? object_size : buflen;
321             if (data_buflen > 0)
322                 memcpy(data_buf, cmdbuf, data_buflen);
323             if (data_buflen < buflen) {
324                 memmove(cmdbuf, cmdbuf + data_buflen, buflen - data_buflen);
325                 buflen -= cmdlen;
326             } else {
327                 buflen = 0;
328             }
329             cmd_put(fd, path, data_buf, object_size, data_buflen);
330             break;
331         }
332         case DELETE:
333             //cmd_delete(fd, path);
334             break;
335         default:
336             return;
337         }
338
339         free(path);
340     }
341 }
342
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. */
345 int server_init()
346 {
347     int fd;
348     struct sockaddr_in server_addr;
349     socklen_t addr_len = sizeof(server_addr);
350
351     fd = socket(PF_INET, SOCK_STREAM, 0);
352     if (fd < 0) {
353         perror("socket");
354         exit(1);
355     }
356
357     int val = 1;
358     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
359
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) {
364         perror("bind");
365         exit(1);
366     }
367
368     if (listen(fd, TCP_BACKLOG) < 0) {
369         perror("listen");
370         exit(1);
371     }
372
373     if (getsockname(fd, (struct sockaddr *)&server_addr, &addr_len) < 0) {
374         perror("getsockname");
375         exit(1);
376     }
377
378     printf("Server listening on %s ...\n", sockname(&server_addr, addr_len));
379     fflush(stdout);
380
381     return fd;
382 }
383
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)
388 {
389     struct sigaction handler;
390
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) {
396         perror("sigaction");
397         exit(1);
398     }
399
400     while (1) {
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,
404                                &addr_len);
405         int pid;
406
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(). */
410         if (client_fd < 0) {
411             if (errno == EINTR)
412                 continue;
413
414             perror("accept");
415             exit(1);
416         }
417
418         printf("Accepted connection from %s ...\n",
419                sockname(&client_addr, addr_len));
420         fflush(stdout);
421
422         pid = fork();
423         if (pid < 0) {
424             perror("fork");
425         } else if (pid == 0) {
426             handle_connection(client_fd);
427             printf("Closing connection %s ...\n",
428                    sockname(&client_addr, addr_len));
429             close(client_fd);
430             exit(0);
431         }
432
433         close(client_fd);
434     }
435 }
436
437 /* Print a help message describing command-line options to stdout. */
438 static void usage(char *argv0)
439 {
440     printf("Usage: %s [options...]\n"
441            "A simple key-value storage server.\n", argv0);
442 }
443
444 int main(int argc, char *argv[])
445 {
446     int fd;
447     //int i;
448     int display_help = 0, cmdline_error = 0;
449
450 #if 0
451     for (i = 1; i < argc; i++) {
452         if (strcmp(argv[i], "-help") == 0) {
453             display_help = 1;
454         } else if (strcmp(argv[i], "-document_root") == 0) {
455             i++;
456             if (i >= argc) {
457                 fprintf(stderr,
458                         "Error: Argument to -document_root expected.\n");
459                 cmdline_error = 1;
460             } else {
461                 document_root = argv[i];
462             }
463         } else if (strcmp(argv[i], "-port") == 0) {
464             i++;
465             if (i >= argc) {
466                 fprintf(stderr,
467                         "Error: Expected port number after -port.\n");
468                 cmdline_error = 1;
469             } else {
470                 server_port = atoi(argv[i]);
471                 if (server_port < 1 || server_port > 65535) {
472                     fprintf(stderr,
473                             "Error: Port must be between 1 and 65535.\n");
474                     cmdline_error = 1;
475                 }
476             }
477         } else if (strcmp(argv[i], "-mime_types") == 0) {
478             i++;
479             if (i >= argc) {
480                 fprintf(stderr,
481                         "Error: Argument to -mime_types expected.\n");
482                 cmdline_error = 1;
483             } else {
484                 mime_types_file = argv[i];
485             }
486         } else if (strcmp(argv[i], "-log") == 0) {
487             i++;
488             if (i >= argc) {
489                 fprintf(stderr,
490                         "Error: Argument to -log expected.\n");
491                 cmdline_error = 1;
492             } else {
493                 log_fname = argv[i];
494             }
495         } else {
496             fprintf(stderr, "Error: Unrecognized option '%s'\n", argv[i]);
497             cmdline_error = 1;
498         }
499     }
500 #endif
501
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. */
505     if (display_help) {
506         usage(argv[0]);
507         exit(0);
508     } else if (cmdline_error) {
509         fprintf(stderr, "Run '%s -help' for a summary of options.\n", argv[0]);
510         exit(2);
511     }
512
513     fd = server_init();
514     if (fd >= 0) {
515         server_main(fd);
516     }
517
518     return 0;
519 }