/* 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#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);
}