Code

Merged branch 'master' of git://git.tokkee.org/sysdb.
[sysdb.git] / src / frontend / connection.c
1 /*
2  * SysDB - src/frontend/connection.c
3  * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
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  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #include "sysdb.h"
29 #include "core/error.h"
30 #include "core/object.h"
31 #include "frontend/connection-private.h"
32 #include "utils/strbuf.h"
34 #include <assert.h>
35 #include <errno.h>
37 #include <arpa/inet.h>
38 #include <fcntl.h>
40 #include <string.h>
42 /*
43  * private data types
44  */
46 /* name of connection objects */
47 #define CONN_FD_PREFIX "conn#"
48 #define CONN_FD_PLACEHOLDER "XXXXXXX"
50 static int
51 connection_init(sdb_object_t *obj, va_list ap)
52 {
53         sdb_conn_t *conn;
54         int sock_fd;
55         int sock_fl;
57         assert(obj);
58         conn = CONN(obj);
60         sock_fd = va_arg(ap, int);
62         conn->buf = sdb_strbuf_create(/* size = */ 128);
63         if (! conn->buf) {
64                 sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate a read buffer "
65                                 "for a new connection");
66                 return -1;
67         }
68         conn->errbuf = sdb_strbuf_create(0);
69         if (! conn->errbuf) {
70                 sdb_log(SDB_LOG_ERR, "frontend: Failed to allocate an error buffer "
71                                 "for a new connection");
72                 return -1;
73         }
75         conn->client_addr_len = sizeof(conn->client_addr);
76         conn->fd = accept(sock_fd, (struct sockaddr *)&conn->client_addr,
77                         &conn->client_addr_len);
79         if (conn->fd < 0) {
80                 char buf[1024];
81                 sdb_log(SDB_LOG_ERR, "frontend: Failed to accept remote "
82                                 "connection: %s", sdb_strerror(errno,
83                                         buf, sizeof(buf)));
84                 return -1;
85         }
87         if (conn->client_addr.ss_family != AF_UNIX) {
88                 sdb_log(SDB_LOG_ERR, "frontend: Accepted connection using "
89                                 "unexpected family type %d", conn->client_addr.ss_family);
90                 return -1;
91         }
93         sock_fl = fcntl(conn->fd, F_GETFL);
94         if (fcntl(conn->fd, F_SETFL, sock_fl | O_NONBLOCK)) {
95                 char buf[1024];
96                 sdb_log(SDB_LOG_ERR, "frontend: Failed to switch connection conn#%i "
97                                 "to non-blocking mode: %s", conn->fd,
98                                 sdb_strerror(errno, buf, sizeof(buf)));
99                 return -1;
100         }
102         sdb_log(SDB_LOG_DEBUG, "frontend: Accepted connection on fd=%i",
103                         conn->fd);
105         conn->cmd = CONNECTION_IDLE;
106         conn->cmd_len = 0;
108         /* update the object name */
109         snprintf(obj->name + strlen(CONN_FD_PREFIX),
110                         strlen(CONN_FD_PLACEHOLDER), "%i", conn->fd);
111         return 0;
112 } /* connection_init */
114 static void
115 connection_destroy(sdb_object_t *obj)
117         sdb_conn_t *conn;
118         size_t len;
120         assert(obj);
121         conn = CONN(obj);
123         if (conn->buf) {
124                 len = sdb_strbuf_len(conn->buf);
125                 if (len)
126                         sdb_log(SDB_LOG_INFO, "frontend: Discarding incomplete command "
127                                         "(%zu byte%s left in buffer)", len, len == 1 ? "" : "s");
128         }
130         sdb_log(SDB_LOG_DEBUG, "frontend: Closing connection on fd=%i",
131                         conn->fd);
132         close(conn->fd);
133         conn->fd = -1;
135         sdb_strbuf_destroy(conn->buf);
136         conn->buf = NULL;
137         sdb_strbuf_destroy(conn->errbuf);
138         conn->errbuf = NULL;
139 } /* connection_destroy */
141 static sdb_type_t connection_type = {
142         /* size = */ sizeof(sdb_conn_t),
143         /* init = */ connection_init,
144         /* destroy = */ connection_destroy,
145 };
147 /*
148  * connection handler functions
149  */
151 static uint32_t
152 connection_get_int32(sdb_conn_t *conn, size_t offset)
154         const char *data;
155         uint32_t n;
157         assert(conn && (sdb_strbuf_len(conn->buf) >= offset + sizeof(uint32_t)));
159         data = sdb_strbuf_string(conn->buf);
160         memcpy(&n, data + offset, sizeof(n));
161         n = ntohl(n);
162         return n;
163 } /* connection_get_int32 */
165 static int
166 command_handle(sdb_conn_t *conn)
168         int status = -1;
170         assert(conn && (conn->cmd != CONNECTION_IDLE));
172         sdb_log(SDB_LOG_DEBUG, "frontend: Handling command %u (len: %u)",
173                         conn->cmd, conn->cmd_len);
175         /* reset */
176         sdb_strbuf_sprintf(conn->errbuf, "");
178         switch (conn->cmd) {
179                 case CONNECTION_PING:
180                         status = sdb_connection_ping(conn);
181                         break;
182                 case CONNECTION_STARTUP:
183                         status = sdb_fe_session_start(conn);
184                         break;
186                 case CONNECTION_LIST:
187                         status = sdb_fe_list(conn);
188                         break;
190                 default:
191                 {
192                         sdb_log(SDB_LOG_WARNING, "frontend: Ignoring invalid command");
193                         sdb_strbuf_sprintf(conn->errbuf, "Invalid command %#x", conn->cmd);
194                         status = -1;
195                         break;
196                 }
197         }
199         if (status)
200                 sdb_connection_send(conn, CONNECTION_ERROR,
201                                 (uint32_t)sdb_strbuf_len(conn->errbuf),
202                                 sdb_strbuf_string(conn->errbuf));
204         /* remove the command from the buffer */
205         if (conn->cmd_len)
206                 sdb_strbuf_skip(conn->buf, conn->cmd_len);
207         conn->cmd = CONNECTION_IDLE;
208         conn->cmd_len = 0;
209         return status;
210 } /* command_handle */
212 /* initialize the connection state information */
213 static int
214 command_init(sdb_conn_t *conn)
216         size_t len;
218         assert(conn && (conn->cmd == CONNECTION_IDLE) && (! conn->cmd_len));
220         conn->cmd = connection_get_int32(conn, 0);
221         conn->cmd_len = connection_get_int32(conn, sizeof(uint32_t));
223         len = 2 * sizeof(uint32_t);
224         if (conn->cmd == CONNECTION_IDLE)
225                 len += conn->cmd_len;
226         sdb_strbuf_skip(conn->buf, len);
227         return 0;
228 } /* command_init */
230 /* returns negative value on error, 0 on EOF, number of octets else */
231 static ssize_t
232 connection_read(sdb_conn_t *conn)
234         ssize_t n = 0;
236         while (42) {
237                 ssize_t status;
239                 errno = 0;
240                 status = sdb_strbuf_read(conn->buf, conn->fd, 1024);
241                 if (status < 0) {
242                         if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
243                                 break;
244                         return (int)status;
245                 }
246                 else if (! status) /* EOF */
247                         break;
249                 n += status;
250         }
252         return n;
253 } /* connection_read */
255 /*
256  * public API
257  */
259 sdb_conn_t *
260 sdb_connection_accept(int fd)
262         if (fd < 0)
263                 return NULL;
265         /* the placeholder will be replaced with the accepted file
266          * descriptor when initializing the object */
267         return CONN(sdb_object_create(CONN_FD_PREFIX CONN_FD_PLACEHOLDER,
268                                 connection_type, fd));
269 } /* sdb_connection_create */
271 void
272 sdb_connection_close(sdb_conn_t *conn)
274         sdb_object_deref(SDB_OBJ(conn));
275 } /* sdb_connection_close */
277 ssize_t
278 sdb_connection_read(sdb_conn_t *conn)
280         ssize_t n = 0;
282         while (42) {
283                 ssize_t status = connection_read(conn);
285                 if ((conn->cmd == CONNECTION_IDLE) && (! conn->cmd_len)
286                                 && (sdb_strbuf_len(conn->buf) >= 2 * sizeof(int32_t)))
287                         command_init(conn);
288                 if ((conn->cmd != CONNECTION_IDLE)
289                                 && (sdb_strbuf_len(conn->buf) >= conn->cmd_len))
290                         command_handle(conn);
292                 if (status <= 0)
293                         break;
295                 n += status;
296         }
297         return n;
298 } /* sdb_connection_read */
300 ssize_t
301 sdb_connection_send(sdb_conn_t *conn, uint32_t code,
302                 uint32_t msg_len, const char *msg)
304         size_t len = 2 * sizeof(uint32_t) + msg_len;
305         char buffer[len];
306         char *buf;
308         uint32_t tmp;
310         if ((! conn) || (conn->fd < 0))
311                 return -1;
313         tmp = htonl(code);
314         memcpy(buffer, &tmp, sizeof(uint32_t));
316         tmp = htonl(msg_len);
317         memcpy(buffer + sizeof(uint32_t), &tmp, sizeof(uint32_t));
319         if (msg_len)
320                 memcpy(buffer + 2 * sizeof(uint32_t), msg, msg_len);
322         buf = buffer;
323         while (len > 0) {
324                 ssize_t status;
326                 /* XXX: use select() */
328                 errno = 0;
329                 status = write(conn->fd, buf, len);
330                 if (status < 0) {
331                         char errbuf[1024];
333                         if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
334                                 continue;
335                         if (errno == EINTR)
336                                 continue;
338                         sdb_log(SDB_LOG_ERR, "frontend: Failed to send msg "
339                                         "(code: %u, len: %u) to client: %s", code, msg_len,
340                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
341                         return status;
342                 }
344                 len -= (size_t)status;
345                 buf += status;
346         }
347         return (ssize_t)len;
348 } /* sdb_connection_send */
350 int
351 sdb_connection_ping(sdb_conn_t *conn)
353         if ((! conn) || (conn->cmd != CONNECTION_PING))
354                 return -1;
356         /* we're alive */
357         sdb_connection_send(conn, CONNECTION_OK, 0, NULL);
358         return 0;
359 } /* sdb_connection_ping */
361 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */