summaryrefslogtreecommitdiff
path: root/src/mongo-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo-client.c')
-rw-r--r--src/mongo-client.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/src/mongo-client.c b/src/mongo-client.c
new file mode 100644
index 0000000..a46cc0d
--- /dev/null
+++ b/src/mongo-client.c
@@ -0,0 +1,331 @@
+/* mongo-client.c - libmongo-client user API
+ * Copyright 2011, 2012 Gergely Nagy <algernon@balabit.hu>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** @file src/mongo-client.c
+ * MongoDB client API implementation.
+ */
+
+#include "config.h"
+#include "mongo-client.h"
+#include "bson.h"
+#include "mongo-wire.h"
+#include "libmongo-private.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#ifndef HAVE_MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+
+static const int one = 1;
+
+mongo_connection *
+mongo_tcp_connect (const char *host, int port)
+{
+ struct addrinfo *res = NULL, *r;
+ struct addrinfo hints;
+ int e, fd = -1;
+ gchar *port_s;
+ mongo_connection *conn;
+
+ if (!host)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_socktype = SOCK_STREAM;
+
+#ifdef __linux__
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+ port_s = g_strdup_printf ("%d", port);
+ e = getaddrinfo (host, port_s, &hints, &res);
+ if (e != 0)
+ {
+ int err = errno;
+
+ g_free (port_s);
+ errno = err;
+ return NULL;
+ }
+ g_free (port_s);
+
+ for (r = res; r != NULL; r = r->ai_next)
+ {
+ fd = socket (r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (fd != -1 && connect (fd, r->ai_addr, r->ai_addrlen) == 0)
+ break;
+ if (fd != -1)
+ {
+ close (fd);
+ fd = -1;
+ }
+ }
+ freeaddrinfo (res);
+
+ if (fd == -1)
+ {
+ errno = EADDRNOTAVAIL;
+ return NULL;
+ }
+
+ setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof (one));
+
+ conn = g_new0 (mongo_connection, 1);
+ conn->fd = fd;
+
+ return conn;
+}
+
+static mongo_connection *
+mongo_unix_connect (const char *path)
+{
+ int fd = -1;
+ mongo_connection *conn;
+ struct sockaddr_un remote;
+
+ if (!path || strlen (path) >= sizeof (remote.sun_path))
+ {
+ errno = path ? ENAMETOOLONG : EINVAL;
+ return NULL;
+ }
+
+ fd = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ errno = EADDRNOTAVAIL;
+ return NULL;
+ }
+
+ remote.sun_family = AF_UNIX;
+ strncpy (remote.sun_path, path, sizeof (remote.sun_path));
+ if (connect (fd, (struct sockaddr *)&remote, sizeof (remote)) == -1)
+ {
+ close (fd);
+ errno = EADDRNOTAVAIL;
+ return NULL;
+ }
+
+ conn = g_new0 (mongo_connection, 1);
+ conn->fd = fd;
+
+ return conn;
+}
+
+mongo_connection *
+mongo_connect (const char *address, int port)
+{
+ if (port == MONGO_CONN_LOCAL)
+ return mongo_unix_connect (address);
+
+ return mongo_tcp_connect (address, port);
+}
+
+#if VERSIONED_SYMBOLS
+__asm__(".symver mongo_tcp_connect,mongo_connect@LMC_0.1.0");
+#endif
+
+void
+mongo_disconnect (mongo_connection *conn)
+{
+ if (!conn)
+ {
+ errno = ENOTCONN;
+ return;
+ }
+
+ if (conn->fd >= 0)
+ close (conn->fd);
+
+ g_free (conn);
+ errno = 0;
+}
+
+gboolean
+mongo_packet_send (mongo_connection *conn, const mongo_packet *p)
+{
+ const guint8 *data;
+ gint32 data_size;
+ mongo_packet_header h;
+ struct iovec iov[2];
+ struct msghdr msg;
+
+ if (!conn)
+ {
+ errno = ENOTCONN;
+ return FALSE;
+ }
+ if (!p)
+ {
+ errno = EINVAL;
+ return FALSE;
+ }
+
+ if (conn->fd < 0)
+ {
+ errno = EBADF;
+ return FALSE;
+ }
+
+ if (!mongo_wire_packet_get_header_raw (p, &h))
+ return FALSE;
+
+ data_size = mongo_wire_packet_get_data (p, &data);
+
+ if (data_size == -1)
+ return FALSE;
+
+ iov[0].iov_base = (void *)&h;
+ iov[0].iov_len = sizeof (h);
+ iov[1].iov_base = (void *)data;
+ iov[1].iov_len = data_size;
+
+ memset (&msg, 0, sizeof (struct msghdr));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 2;
+
+ if (sendmsg (conn->fd, &msg, MSG_NOSIGNAL) != (gint32)sizeof (h) + data_size)
+ return FALSE;
+
+ conn->request_id = h.id;
+
+ return TRUE;
+}
+
+mongo_packet *
+mongo_packet_recv (mongo_connection *conn)
+{
+ mongo_packet *p;
+ guint8 *data;
+ guint32 size;
+ mongo_packet_header h;
+
+ if (!conn)
+ {
+ errno = ENOTCONN;
+ return NULL;
+ }
+
+ if (conn->fd < 0)
+ {
+ errno = EBADF;
+ return NULL;
+ }
+
+ memset (&h, 0, sizeof (h));
+ if (recv (conn->fd, &h, sizeof (mongo_packet_header),
+ MSG_NOSIGNAL | MSG_WAITALL) != sizeof (mongo_packet_header))
+ {
+ return NULL;
+ }
+
+ h.length = GINT32_FROM_LE (h.length);
+ h.id = GINT32_FROM_LE (h.id);
+ h.resp_to = GINT32_FROM_LE (h.resp_to);
+ h.opcode = GINT32_FROM_LE (h.opcode);
+
+ p = mongo_wire_packet_new ();
+
+ if (!mongo_wire_packet_set_header_raw (p, &h))
+ {
+ int e = errno;
+
+ mongo_wire_packet_free (p);
+ errno = e;
+ return NULL;
+ }
+
+ size = h.length - sizeof (mongo_packet_header);
+ data = g_new0 (guint8, size);
+ if ((guint32)recv (conn->fd, data, size, MSG_NOSIGNAL | MSG_WAITALL) != size)
+ {
+ int e = errno;
+
+ g_free (data);
+ mongo_wire_packet_free (p);
+ errno = e;
+ return NULL;
+ }
+
+ if (!mongo_wire_packet_set_data (p, data, size))
+ {
+ int e = errno;
+
+ g_free (data);
+ mongo_wire_packet_free (p);
+ errno = e;
+ return NULL;
+ }
+
+ g_free (data);
+
+ return p;
+}
+
+gint32
+mongo_connection_get_requestid (const mongo_connection *conn)
+{
+ if (!conn)
+ {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ return conn->request_id;
+}
+
+gboolean
+mongo_connection_set_timeout (mongo_connection *conn, gint timeout)
+{
+ struct timeval tv;
+
+ if (!conn)
+ {
+ errno = ENOTCONN;
+ return FALSE;
+ }
+ if (timeout < 0)
+ {
+ errno = ERANGE;
+ return FALSE;
+ }
+
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = (timeout % 1000) * 1000;
+
+ if (setsockopt (conn->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof (tv)) == -1)
+ return FALSE;
+ if (setsockopt (conn->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof (tv)) == -1)
+ return FALSE;
+ return TRUE;
+}