THRIFT-3556 c_glib file descriptor transport
Client: C (GLib)
Patch: Chandler May <cjmay4754@gmail.com>

This closes #810
diff --git a/.gitignore b/.gitignore
index afcb8d5..1d4f4c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,6 +132,7 @@
 /lib/c_glib/test/testbufferedtransport
 /lib/c_glib/test/testcontainertest
 /lib/c_glib/test/testdebugproto
+/lib/c_glib/test/testfdtransport
 /lib/c_glib/test/testframedtransport
 /lib/c_glib/test/testmemorybuffer
 /lib/c_glib/test/testoptionalrequired
diff --git a/lib/c_glib/CMakeLists.txt b/lib/c_glib/CMakeLists.txt
index 04bdba7..dd9892c 100644
--- a/lib/c_glib/CMakeLists.txt
+++ b/lib/c_glib/CMakeLists.txt
@@ -47,6 +47,7 @@
     src/thrift/c_glib/transport/thrift_server_transport.c
     src/thrift/c_glib/transport/thrift_server_socket.c
     src/thrift/c_glib/transport/thrift_buffered_transport.c
+    src/thrift/c_glib/transport/thrift_fd_transport.c
     src/thrift/c_glib/transport/thrift_framed_transport.c
     src/thrift/c_glib/transport/thrift_memory_buffer.c
     src/thrift/c_glib/server/thrift_server.c
diff --git a/lib/c_glib/Makefile.am b/lib/c_glib/Makefile.am
index 71cb283..452f6a4 100755
--- a/lib/c_glib/Makefile.am
+++ b/lib/c_glib/Makefile.am
@@ -48,6 +48,7 @@
                               src/thrift/c_glib/transport/thrift_server_transport.c \
                               src/thrift/c_glib/transport/thrift_server_socket.c \
                               src/thrift/c_glib/transport/thrift_buffered_transport.c \
+                              src/thrift/c_glib/transport/thrift_fd_transport.c \
                               src/thrift/c_glib/transport/thrift_framed_transport.c \
                               src/thrift/c_glib/transport/thrift_memory_buffer.c \
                               src/thrift/c_glib/server/thrift_server.c \
@@ -72,6 +73,7 @@
 
 include_transportdir = $(include_thriftdir)/transport
 include_transport_HEADERS = src/thrift/c_glib/transport/thrift_buffered_transport.h \
+                            src/thrift/c_glib/transport/thrift_fd_transport.h \
                             src/thrift/c_glib/transport/thrift_framed_transport.h \
                             src/thrift/c_glib/transport/thrift_memory_buffer.h \
                             src/thrift/c_glib/transport/thrift_server_socket.h \
diff --git a/lib/c_glib/src/thrift/c_glib/transport/thrift_fd_transport.c b/lib/c_glib/src/thrift/c_glib/transport/thrift_fd_transport.c
new file mode 100644
index 0000000..14abff7
--- /dev/null
+++ b/lib/c_glib/src/thrift/c_glib/transport/thrift_fd_transport.c
@@ -0,0 +1,265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <thrift/c_glib/thrift.h>
+#include <thrift/c_glib/transport/thrift_transport.h>
+#include <thrift/c_glib/transport/thrift_fd_transport.h>
+
+/* object properties */
+enum _ThriftFDTransportProperties
+{
+  PROP_0,
+  PROP_THRIFT_FD_TRANSPORT_FD
+};
+
+G_DEFINE_TYPE (ThriftFDTransport, thrift_fd_transport, THRIFT_TYPE_TRANSPORT)
+
+/* implements thrift_transport_is_open */
+gboolean
+thrift_fd_transport_is_open (ThriftTransport *transport)
+{
+  ThriftFDTransport *t;
+  t = THRIFT_FD_TRANSPORT (transport);
+  return t->fd >= 0 && ! (fcntl (t->fd, F_GETFL) == -1 && errno == EBADF);
+}
+
+/* implements thrift_transport_open */
+gboolean
+thrift_fd_transport_open (ThriftTransport *transport, GError **error)
+{
+  THRIFT_UNUSED_VAR (error);
+  return thrift_fd_transport_is_open (transport);
+}
+
+/* implements thrift_transport_close */
+gboolean
+thrift_fd_transport_close (ThriftTransport *transport, GError **error)
+{
+  ThriftFDTransport *t;
+  t = THRIFT_FD_TRANSPORT (transport);
+
+#if GLIB_CHECK_VERSION (2, 36, 0)
+  return g_close (t->fd, error);
+#else
+  if (close (t->fd) == 0) {
+    g_clear_error (error);
+    return TRUE;
+  } else {
+    g_set_error (error,
+                 THRIFT_TRANSPORT_ERROR,
+                 THRIFT_TRANSPORT_ERROR_CLOSE,
+                 strerror (errno));
+    return FALSE;
+  }
+#endif
+}
+
+/* implements thrift_transport_read */
+gint32
+thrift_fd_transport_read (ThriftTransport *transport, gpointer buf,
+                          guint32 len, GError **error)
+{
+  ThriftFDTransport *t;
+  ssize_t n;
+
+  t = THRIFT_FD_TRANSPORT (transport);
+  n = read (t->fd, (guint8 *) buf, len);
+  if (n == -1) {
+    g_set_error (error,
+                 THRIFT_TRANSPORT_ERROR,
+                 THRIFT_TRANSPORT_ERROR_RECEIVE,
+                 "Failed to read from fd: %s",
+                 strerror (errno));
+    return -1;
+  }
+  return n;
+}
+
+/* implements thrift_transport_read_end
+ * called when write is complete.  nothing to do on our end. */
+gboolean
+thrift_fd_transport_read_end (ThriftTransport *transport, GError **error)
+{
+  /* satisfy -Wall */
+  THRIFT_UNUSED_VAR (transport);
+  THRIFT_UNUSED_VAR (error);
+  return TRUE;
+}
+
+/* implements thrift_transport_write */
+gboolean
+thrift_fd_transport_write (ThriftTransport *transport,
+                           const gpointer buf,
+                           const guint32 len, GError **error)
+{
+  ThriftFDTransport *t;
+  guint8 *_buf;
+  guint32 _len;
+  ssize_t n;
+
+  t = THRIFT_FD_TRANSPORT (transport);
+  _buf = (guint8 *) buf;
+  _len = len;
+  while (_len > 0) {
+    n = write (t->fd, _buf, _len);
+    if (n == -1) {
+      g_set_error (error,
+                   THRIFT_TRANSPORT_ERROR,
+                   THRIFT_TRANSPORT_ERROR_SEND,
+                   "Failed to write from fd: %s",
+                   strerror (errno));
+      return FALSE;
+    } else {
+      _buf += n;
+      _len -= n;
+    }
+  }
+  return TRUE;
+}
+
+/* implements thrift_transport_write_end
+ * called when write is complete.  nothing to do on our end. */
+gboolean
+thrift_fd_transport_write_end (ThriftTransport *transport, GError **error)
+{
+  THRIFT_UNUSED_VAR (transport);
+  THRIFT_UNUSED_VAR (error);
+  return TRUE;
+}
+
+/* implements thrift_transport_flush */
+gboolean
+thrift_fd_transport_flush (ThriftTransport *transport, GError **error)
+{
+  ThriftFDTransport *t;
+  t = THRIFT_FD_TRANSPORT (transport);
+  if (fsync (t->fd) == -1) {
+    g_set_error (error,
+                 THRIFT_TRANSPORT_ERROR,
+                 THRIFT_TRANSPORT_ERROR_UNKNOWN,
+                 "Failed to flush fd: %s",
+                 strerror (errno));
+    return FALSE;
+  } else {
+    return TRUE;
+  }
+}
+
+/* initializes the instance */
+static void
+thrift_fd_transport_init (ThriftFDTransport *transport)
+{
+  transport->fd = -1;
+}
+
+/* destructor */
+static void
+thrift_fd_transport_finalize (GObject *object)
+{
+  THRIFT_UNUSED_VAR (object);
+}
+
+/* property accessor */
+void
+thrift_fd_transport_get_property (GObject *object, guint property_id,
+                                  GValue *value, GParamSpec *pspec)
+{
+  ThriftFDTransport *t;
+
+  THRIFT_UNUSED_VAR (pspec);
+
+  t = THRIFT_FD_TRANSPORT (object);
+
+  switch (property_id) {
+    case PROP_THRIFT_FD_TRANSPORT_FD:
+      g_value_set_int (value, t->fd);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+/* property mutator */
+void
+thrift_fd_transport_set_property (GObject *object, guint property_id,
+                                  const GValue *value, GParamSpec *pspec)
+{
+  ThriftFDTransport *t;
+
+  THRIFT_UNUSED_VAR (pspec);
+
+  t = THRIFT_FD_TRANSPORT (object);
+
+  switch (property_id) {
+    case PROP_THRIFT_FD_TRANSPORT_FD:
+      t->fd = g_value_get_int (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+/* initializes the class */
+static void
+thrift_fd_transport_class_init (ThriftFDTransportClass *cls)
+{
+  ThriftTransportClass *ttc;
+  GObjectClass *gobject_class;
+  GParamSpec *param_spec;
+
+  ttc = THRIFT_TRANSPORT_CLASS (cls);
+  gobject_class = G_OBJECT_CLASS (cls);
+  param_spec = NULL;
+
+  /* setup accessors and mutators */
+  gobject_class->get_property = thrift_fd_transport_get_property;
+  gobject_class->set_property = thrift_fd_transport_set_property;
+
+  param_spec = g_param_spec_int ("fd",
+                                 "file descriptor (construct)",
+                                 "Set the file descriptor",
+                                 INT_MIN, /* min */
+                                 INT_MAX, /* max, 1024*1024 */
+                                 -1, /* default value */
+                                 G_PARAM_CONSTRUCT_ONLY |
+                                 G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class,
+                                   PROP_THRIFT_FD_TRANSPORT_FD,
+                                   param_spec);
+
+  gobject_class->finalize = thrift_fd_transport_finalize;
+  ttc->is_open = thrift_fd_transport_is_open;
+  ttc->open = thrift_fd_transport_open;
+  ttc->close = thrift_fd_transport_close;
+  ttc->read = thrift_fd_transport_read;
+  ttc->read_end = thrift_fd_transport_read_end;
+  ttc->write = thrift_fd_transport_write;
+  ttc->write_end = thrift_fd_transport_write_end;
+  ttc->flush = thrift_fd_transport_flush;
+}
diff --git a/lib/c_glib/src/thrift/c_glib/transport/thrift_fd_transport.h b/lib/c_glib/src/thrift/c_glib/transport/thrift_fd_transport.h
new file mode 100644
index 0000000..0e6d5c4
--- /dev/null
+++ b/lib/c_glib/src/thrift/c_glib/transport/thrift_fd_transport.h
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+#ifndef _THRIFT_FD_TRANSPORT_H
+#define _THRIFT_FD_TRANSPORT_H
+
+#include <glib-object.h>
+
+#include "thrift_transport.h"
+
+G_BEGIN_DECLS
+
+/*! \file thrift_fd_transport.h
+ *  \brief Class for Thrift file descriptor transports.
+ */
+
+/* type macros */
+#define THRIFT_TYPE_FD_TRANSPORT (thrift_fd_transport_get_type ())
+#define THRIFT_FD_TRANSPORT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), THRIFT_TYPE_FD_TRANSPORT, \
+                               ThriftFDTransport))
+#define THRIFT_IS_FD_TRANSPORT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), THRIFT_TYPE_FD_TRANSPORT))
+#define THRIFT_FD_TRANSPORT_CLASS(c) \
+  (G_TYPE_CHECK_CLASS_CAST ((c), THRIFT_TYPE_FD_TRANSPORT, \
+                            ThriftFDTransportClass))
+#define THRIFT_IS_FD_TRANSPORT_CLASS(c) \
+  (G_TYPE_CHECK_CLASS_TYPE ((c), THRIFT_TYPE_FD_TRANSPORT))
+#define THRIFT_FD_TRANSPORT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), THRIFT_TYPE_FD_TRANSPORT, \
+                              ThriftFDTransportClass))
+
+typedef struct _ThriftFDTransport ThriftFDTransport;
+
+struct _ThriftFDTransport
+{
+  ThriftTransport parent;
+
+  /* protected */
+  gint fd;
+};
+
+typedef struct _ThriftFDTransportClass ThriftFDTransportClass;
+
+/*!
+ * Thrift Transport class
+ */
+struct _ThriftFDTransportClass
+{
+  ThriftTransportClass parent;
+};
+
+/* used by THRIFT_TYPE_FD_TRANSPORT */
+GType thrift_fd_transport_get_type (void);
+
+G_END_DECLS
+
+#endif /* _THRIFT_FD_TRANSPORT_H */
diff --git a/lib/c_glib/test/CMakeLists.txt b/lib/c_glib/test/CMakeLists.txt
index 411ae95..95876ed 100644
--- a/lib/c_glib/test/CMakeLists.txt
+++ b/lib/c_glib/test/CMakeLists.txt
@@ -85,6 +85,10 @@
 target_link_libraries(testframedtransport thrift_c_glib)
 add_test(NAME testframedtransport COMMAND testframedtransport)
 
+add_executable(testfdtransport testfdtransport.c)
+target_link_libraries(testfdtransport thrift_c_glib)
+add_test(NAME testfdtransport COMMAND testfdtransport)
+
 add_executable(testmemorybuffer testmemorybuffer.c)
 target_link_libraries(testmemorybuffer thrift_c_glib)
 add_test(NAME testmemorybuffer COMMAND testmemorybuffer)
diff --git a/lib/c_glib/test/Makefile.am b/lib/c_glib/test/Makefile.am
index ab1c7f0..41ed4d1 100755
--- a/lib/c_glib/test/Makefile.am
+++ b/lib/c_glib/test/Makefile.am
@@ -51,6 +51,7 @@
   testcompactprotocol \
   testbufferedtransport \
   testframedtransport \
+  testfdtransport \
   testmemorybuffer \
   teststruct \
   testsimpleserver \
@@ -129,6 +130,11 @@
     $(top_builddir)/lib/c_glib/src/thrift/c_glib/transport/libthrift_c_glib_la-thrift_server_transport.o \
     $(top_builddir)/lib/c_glib/src/thrift/c_glib/transport/libthrift_c_glib_la-thrift_server_socket.o
 
+testfdtransport_SOURCES = testfdtransport.c
+testfdtransport_LDADD = \
+    $(top_builddir)/lib/c_glib/src/thrift/c_glib/transport/libthrift_c_glib_la-thrift_transport.o \
+    $(top_builddir)/lib/c_glib/src/thrift/c_glib/transport/libthrift_c_glib_la-thrift_fd_transport.o
+
 testmemorybuffer_SOURCES = testmemorybuffer.c
 testmemorybuffer_LDADD = \
     $(top_builddir)/lib/c_glib/src/thrift/c_glib/transport/libthrift_c_glib_la-thrift_transport.o
diff --git a/lib/c_glib/test/testfdtransport.c b/lib/c_glib/test/testfdtransport.c
new file mode 100755
index 0000000..40c1acf
--- /dev/null
+++ b/lib/c_glib/test/testfdtransport.c
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+#include <assert.h>
+
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <thrift/c_glib/transport/thrift_transport.h>
+#include <thrift/c_glib/transport/thrift_fd_transport.h>
+
+static const gchar TEST_DATA[12] = "abcde01234!";
+
+static void
+test_create_and_destroy (void)
+{
+  GObject *object;
+  object = g_object_new (THRIFT_TYPE_FD_TRANSPORT, "fd", -1, NULL);
+  assert (object != NULL);
+  g_object_unref (object);
+}
+
+static void
+test_open_and_close (void)
+{
+  ThriftTransport *transport;
+  ThriftTransportClass *klass;
+  GError *error;
+  gint fd;
+  gchar *filename;
+
+  error = NULL;
+  filename = NULL;
+
+  fd = g_file_open_tmp (NULL, &filename, &error);
+  assert (fd >= 0);
+
+  transport = THRIFT_TRANSPORT (g_object_new (THRIFT_TYPE_FD_TRANSPORT,
+                                              "fd", fd,
+                                              NULL));
+  klass = THRIFT_TRANSPORT_GET_CLASS (transport);
+
+  /* open is no-op */
+  assert (klass->is_open (transport));
+  assert (klass->peek (transport, &error));
+  assert (klass->open (transport, &error));
+  assert (klass->is_open (transport));
+  assert (klass->peek (transport, &error));
+
+  assert (klass->close (transport, &error));
+  assert (! klass->open (transport, &error));
+  assert (! klass->is_open (transport));
+  assert (! klass->peek (transport, &error));
+
+  /* already closed */
+  assert (close (fd) != 0);
+  assert (errno == EBADF);
+
+  g_object_unref (transport);
+
+  g_remove (filename);
+  g_free (filename);
+
+  /* test bad fd */
+  transport = THRIFT_TRANSPORT (g_object_new (THRIFT_TYPE_FD_TRANSPORT,
+                                              "fd", -1,
+                                              NULL));
+  klass = THRIFT_TRANSPORT_GET_CLASS (transport);
+
+  assert (! klass->is_open (transport));
+  error = NULL;
+  assert (! klass->peek (transport, &error));
+  error = NULL;
+  assert (! klass->open (transport, &error));
+  error = NULL;
+  assert (! klass->close (transport, &error));
+
+  g_object_unref (transport);
+}
+
+static void
+test_read_and_write (void)
+{
+  gchar out_buf[8];
+  gchar *b;
+  gint want, got;
+  ThriftTransport *transport;
+  ThriftTransportClass *klass;
+  GError *error;
+  gint fd;
+  gchar *filename;
+
+  error = NULL;
+  filename = NULL;
+
+  fd = g_file_open_tmp (NULL, &filename, &error);
+  assert (fd >= 0);
+
+  /* write */
+  transport = THRIFT_TRANSPORT (g_object_new (THRIFT_TYPE_FD_TRANSPORT,
+                                              "fd", fd,
+                                              NULL));
+  klass = THRIFT_TRANSPORT_GET_CLASS (transport);
+  assert (klass->is_open (transport));
+  assert (klass->write (transport, (gpointer) TEST_DATA, 11, &error));
+  assert (klass->flush (transport, &error));
+  assert (klass->close (transport, &error));
+  g_object_unref (transport);
+
+  /* read */
+  fd = open(filename, O_RDONLY, S_IRUSR | S_IWUSR);
+  assert (fd >= 0);
+
+  transport = THRIFT_TRANSPORT (g_object_new (THRIFT_TYPE_FD_TRANSPORT,
+                                              "fd", fd,
+                                              NULL));
+  klass = THRIFT_TRANSPORT_GET_CLASS (transport);
+
+  memset(out_buf, 0, 8);
+  b = out_buf;
+  want = 7;
+  while (want > 0) {
+    got = klass->read (transport, (gpointer) b, want, &error);
+    assert (got > 0 && got <= want);
+    b += got;
+    want -= got;
+  }
+  assert (memcmp (out_buf, TEST_DATA, 7) == 0);
+
+  memset(out_buf, 0, 8);
+  b = out_buf;
+  want = 4;
+  while (want > 0) {
+    got = klass->read (transport, (gpointer) b, want, &error);
+    assert (got > 0 && got <= want);
+    b += got;
+    want -= got;
+  }
+  assert (memcmp (out_buf, TEST_DATA + 7, 4) == 0);
+
+  assert (klass->close (transport, &error));
+  g_object_unref (transport);
+
+  /* clean up */
+
+  g_remove (filename);
+  g_free (filename);
+}
+
+int
+main (int argc, char *argv[])
+{
+#if (!GLIB_CHECK_VERSION (2, 36, 0))
+  g_type_init ();
+#endif
+
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/testfdtransport/CreateAndDestroy", test_create_and_destroy);
+  g_test_add_func ("/testfdtransport/OpenAndClose", test_open_and_close);
+  g_test_add_func ("/testfdtransport/ReadAndWrite", test_read_and_write);
+
+  return g_test_run ();
+}