aboutsummaryrefslogtreecommitdiffstats
path: root/src/fdtable.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fdtable.c')
-rw-r--r--src/fdtable.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/fdtable.c b/src/fdtable.c
new file mode 100644
index 00000000..2e682dea
--- /dev/null
+++ b/src/fdtable.c
@@ -0,0 +1,205 @@
+/* fdtable.c - Keep track of file descriptors.
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GPGME.
+ *
+ * GPGME is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * GPGME is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <https://gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <assert.h>
+
+#include "gpgme.h"
+#include "debug.h"
+#include "context.h"
+#include "fdtable.h"
+
+
+/* The table to hold information about file descriptors. Currently we
+ * use a linear search and extend the table as needed. Eventually we
+ * may swicth to a hash table and allocate items on the fly. */
+struct fdtable_item_s
+{
+ int fd; /* -1 indicates an unused entry. */
+
+ /* The callback to be called before the descriptor is actually closed. */
+ struct {
+ fdtable_handler_t handler;
+ void *value;
+ } close_notify;
+};
+typedef struct fdtable_item_s *fdtable_item_t;
+
+/* The actual table, its size and the lock to guard access. */
+static fdtable_item_t fdtable;
+static unsigned int fdtablesize;
+DEFINE_STATIC_LOCK (fdtable_lock);
+
+
+
+/* Insert FD into our file descriptor table. This function checks
+ * that FD is not yet in the table. On success 0 is returned; if FD
+ * is already in the table GPG_ERR_DUP_KEY is returned. Other error
+ * codes may also be returned. */
+gpg_error_t
+_gpgme_fdtable_insert (int fd)
+{
+ gpg_error_t err;
+ int firstunused, idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd);
+
+ if (fd < 0 )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ firstunused = -1;
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == -1)
+ {
+ if (firstunused == -1)
+ firstunused = idx;
+ }
+ else if (fdtable[idx].fd == fd)
+ {
+ err = gpg_error (GPG_ERR_DUP_KEY);
+ goto leave;
+ }
+
+ if (firstunused == -1)
+ {
+ /* We need to increase the size of the table. The approach we
+ * take is straightforward to minimize the risk of bugs. */
+ fdtable_item_t newtbl;
+ size_t newsize = fdtablesize + 64;
+
+ newtbl = calloc (newsize, sizeof *newtbl);
+ if (!newtbl)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (idx=0; idx < fdtablesize; idx++)
+ newtbl[idx] = fdtable[idx];
+ for (; idx < newsize; idx++)
+ newtbl[idx].fd = -1;
+
+ free (fdtable);
+ fdtable = newtbl;
+ idx = fdtablesize;
+ fdtablesize = newsize;
+ }
+ else
+ idx = firstunused;
+
+ fdtable[idx].fd = fd;
+ fdtable[idx].close_notify.handler = NULL;
+ fdtable[idx].close_notify.value = NULL;
+ err = 0;
+
+ leave:
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (err);
+}
+
+
+/* Add the close notification HANDLER to the table under the key FD.
+ * FD must exist. VALUE is a pointer passed to the handler along with
+ * the FD. */
+gpg_error_t
+_gpgme_fdtable_add_close_notify (int fd,
+ fdtable_handler_t handler, void *value)
+{
+ gpg_error_t err;
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd);
+
+ if (fd < 0 )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == fd)
+ break;
+ if (idx == fdtablesize)
+ {
+ err = gpg_error (GPG_ERR_NO_KEY);
+ goto leave;
+ }
+
+ if (fdtable[idx].close_notify.handler)
+ {
+ err = gpg_error (GPG_ERR_DUP_VALUE);
+ goto leave;
+ }
+
+ fdtable[idx].close_notify.handler = handler;
+ fdtable[idx].close_notify.value = value;
+ err = 0;
+
+ leave:
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (err);
+}
+
+
+/* Remove FD from the table after calling the close handler. Note
+ * that at the time the close handler is called the FD has been
+ * removed form the table. Thus the close handler may not access the
+ * fdtable anymore and assume that FD is still there. Callers may
+ * want to handle the error code GPG_ERR_NO_KEY which indicates that
+ * FD is not anymore or not yet in the table. */
+gpg_error_t
+_gpgme_fdtable_remove (int fd)
+{
+ gpg_error_t err;
+ int idx;
+ fdtable_handler_t handler;
+ void *handlervalue;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd);
+
+ if (fd < 0 )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == fd)
+ break;
+ if (idx == fdtablesize)
+ {
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (gpg_error (GPG_ERR_NO_KEY));
+ }
+
+ handler = fdtable[idx].close_notify.handler;
+ fdtable[idx].close_notify.handler = NULL;
+ handlervalue = fdtable[idx].close_notify.value;
+ fdtable[idx].close_notify.value = NULL;
+ fdtable[idx].fd = -1;
+
+ UNLOCK (fdtable_lock);
+
+ err = handler? handler (fd, handlervalue) : 0;
+
+ return TRACE_ERR (err);
+}