diff options
Diffstat (limited to 'dirmngr/ldap-wrapper.c')
-rw-r--r-- | dirmngr/ldap-wrapper.c | 757 |
1 files changed, 453 insertions, 304 deletions
diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c index 8b53bd60f..d01c4808e 100644 --- a/dirmngr/ldap-wrapper.c +++ b/dirmngr/ldap-wrapper.c @@ -1,5 +1,5 @@ /* ldap-wrapper.c - LDAP access via a wrapper process - * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH + * Copyright (C) 2004, 2005, 2007, 2008, 2018 g10 Code GmbH * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. @@ -19,31 +19,34 @@ */ /* - We can't use LDAP directly for these reasons: - - 1. On some systems the LDAP library uses (indirectly) pthreads and - that is not compatible with PTh. - - 2. It is huge library in particular if TLS comes into play. So - problems with unfreed memory might turn up and we don't want - this in a long running daemon. - - 3. There is no easy way for timeouts. In particular the timeout - value does not work for DNS lookups (well, this is usual) and it - seems not to work while loading a large attribute like a - CRL. Having a separate process allows us to either tell the - process to commit suicide or have our own housekepping function - kill it after some time. The latter also allows proper - cancellation of a query at any point of time. - - 4. Given that we are going out to the network and usually get back - a long response, the fork/exec overhead is acceptable. - - Note that under WindowsCE the number of processes is strongly - limited (32 processes including the kernel processes) and thus we - don't use the process approach but implement a different wrapper in - ldap-wrapper-ce.c. -*/ + * We can't use LDAP directly for these reasons: + * + * 1. On some systems the LDAP library uses (indirectly) pthreads and + * that is not compatible with GNU Pth. Since 2.1 we use nPth + * instead of GNU Pth which does not have this problem anymore + * because it will use pthreads if the platform supports it. Thus + * this was a historical reasons. + * + * 2. It is huge library in particular if TLS comes into play. So + * problems with unfreed memory might turn up and we don't want + * this in a long running daemon. + * + * 3. There is no easy way for timeouts. In particular the timeout + * value does not work for DNS lookups (well, this is usual) and it + * seems not to work while loading a large attribute like a + * CRL. Having a separate process allows us to either tell the + * process to commit suicide or have our own housekepping function + * kill it after some time. The latter also allows proper + * cancellation of a query at any point of time. + * + * 4. Given that we are going out to the network and usually get back + * a long response, the fork/exec overhead is acceptable. + * + * Note that under WindowsCE the number of processes is strongly + * limited (32 processes including the kernel processes) and thus we + * don't use the process approach but implement a different wrapper in + * ldap-wrapper-ce.c. + */ #include <config.h> @@ -89,39 +92,66 @@ struct wrapper_context_s { struct wrapper_context_s *next; - pid_t pid; /* The pid of the wrapper process. */ - int printable_pid; /* Helper to print diagnostics after the process has - been cleaned up. */ - int fd; /* Connected with stdout of the ldap wrapper. */ - gpg_error_t fd_error; /* Set to the gpg_error of the last read error - if any. */ - int log_fd; /* Connected with stderr of the ldap wrapper. */ - ctrl_t ctrl; /* Connection data. */ - int ready; /* Internally used to mark to be removed contexts. */ - ksba_reader_t reader; /* The ksba reader object or NULL. */ - char *line; /* Used to print the log lines (malloced). */ - size_t linesize;/* Allocated size of LINE. */ - size_t linelen; /* Use size of LINE. */ - time_t stamp; /* The last time we noticed ativity. */ + pid_t pid; /* The pid of the wrapper process. */ + int printable_pid; /* Helper to print diagnostics after the process has + * been cleaned up. */ + estream_t fp; /* Connected with stdout of the ldap wrapper. */ + gpg_error_t fp_err; /* Set to the gpg_error of the last read error + * if any. */ + estream_t log_fp; /* Connected with stderr of the ldap wrapper. */ + ctrl_t ctrl; /* Connection data. */ + int ready; /* Internally used to mark to be removed contexts. */ + ksba_reader_t reader;/* The ksba reader object or NULL. */ + char *line; /* Used to print the log lines (malloced). */ + size_t linesize; /* Allocated size of LINE. */ + size_t linelen; /* Use size of LINE. */ + time_t stamp; /* The last time we noticed ativity. */ + int reaper_idx; /* Private to ldap_wrapper_thread. */ }; -/* We keep a global list of spawned wrapper process. A separate thread - makes use of this list to log error messages and to watch out for - finished processes. */ -static struct wrapper_context_s *wrapper_list; +/* We keep a global list of spawned wrapper process. A separate + * thread makes use of this list to log error messages and to watch + * out for finished processes. Access to list is protected by a + * mutex. The condition variable is used to wakeup the reaper + * thread. */ +static struct wrapper_context_s *reaper_list; +static npth_mutex_t reaper_list_mutex = NPTH_MUTEX_INITIALIZER; +static npth_cond_t reaper_run_cond = NPTH_COND_INITIALIZER; /* We need to know whether we are shutting down the process. */ static int shutting_down; -/* Close the pth file descriptor FD and set it to -1. */ -#define SAFE_CLOSE(fd) \ - do { int _fd = fd; if (_fd != -1) { close (_fd); fd = -1;} } while (0) + + +/* Close the estream fp and set it to NULL. */ +#define SAFE_CLOSE(fp) \ + do { estream_t _fp = fp; es_fclose (_fp); fp = NULL; } while (0) + +static void +lock_reaper_list (void) +{ + if (npth_mutex_lock (&reaper_list_mutex)) + log_fatal ("%s: failed to acquire mutex: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); +} + + +static void +unlock_reaper_list (void) +{ + if (npth_mutex_unlock (&reaper_list_mutex)) + log_fatal ("%s: failed to release mutex: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); +} + + + /* Read a fixed amount of data from READER into BUFFER. */ static gpg_error_t read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) @@ -151,8 +181,8 @@ destroy_wrapper (struct wrapper_context_s *ctx) gnupg_release_process (ctx->pid); } ksba_reader_release (ctx->reader); - SAFE_CLOSE (ctx->fd); - SAFE_CLOSE (ctx->log_fd); + SAFE_CLOSE (ctx->fp); + SAFE_CLOSE (ctx->log_fp); xfree (ctx->line); xfree (ctx); } @@ -218,25 +248,27 @@ print_log_line (struct wrapper_context_s *ctx, char *line) /* Read data from the log stream. Returns true if the log stream - indicated EOF or error. */ + * indicated EOF or error. */ static int read_log_data (struct wrapper_context_s *ctx) { - int n; + int rc; + size_t n; char line[256]; - /* We must use the npth_read function for pipes, always. */ - do - n = npth_read (ctx->log_fd, line, sizeof line - 1); - while (n < 0 && errno == EINTR); - - if (n <= 0) /* EOF or error. */ + rc = es_read (ctx->log_fp, line, sizeof line - 1, &n); + if (rc || !n) /* Error or EOF. */ { - if (n < 0) - log_error (_("error reading log from ldap wrapper %d: %s\n"), - (int)ctx->pid, strerror (errno)); - print_log_line (ctx, NULL); - SAFE_CLOSE (ctx->log_fd); + if (rc) + { + gpg_error_t err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_EAGAIN) + return 0; + log_error (_("error reading log from ldap wrapper %d: %s\n"), + (int)ctx->pid, gpg_strerror (err)); + } + print_log_line (ctx, NULL); /* Flush. */ + SAFE_CLOSE (ctx->log_fp); return 1; } @@ -251,15 +283,18 @@ read_log_data (struct wrapper_context_s *ctx) /* This function is run by a separate thread to maintain the list of wrappers and to log error messages from these wrappers. */ void * -ldap_wrapper_thread (void *dummy) +ldap_reaper_thread (void *dummy) { - int nfds; + gpg_error_t err; struct wrapper_context_s *ctx; struct wrapper_context_s *ctx_prev; struct timespec abstime; struct timespec curtime; struct timespec timeout; - fd_set fdset; + int millisecs; + gpgrt_poll_t *fparray = NULL; + int fparraysize = 0; + int count, i; int ret; time_t exptime; @@ -272,6 +307,61 @@ ldap_wrapper_thread (void *dummy) { int any_action = 0; + /* Wait until we are needed and then setup the FPARRAY. */ + /* Note: There is one unlock inside the block! */ + lock_reaper_list (); + { + while (!reaper_list && !shutting_down) + { + if (npth_cond_wait (&reaper_run_cond, &reaper_list_mutex)) + log_error ("ldap-reaper: waiting on condition failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + } + + for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next) + if (ctx->log_fp) + count++; + if (count > fparraysize || !fparray) + { + /* Need to realloc the array. We simply discard it and + * replace it by a new one. */ + xfree (fparray); + fparray = xtrycalloc (count? count : 1, sizeof *fparray); + if (!fparray) + { + err = gpg_error_from_syserror (); + log_error ("ldap-reaper can't allocate poll array: %s" + " - waiting 1s\n", gpg_strerror (err)); + /* Note: Here we unlock and continue! */ + unlock_reaper_list (); + npth_sleep (1); + continue; + } + fparraysize = count; + } + for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next) + { + if (ctx->log_fp) + { + log_assert (count < fparraysize); + fparray[count].stream = ctx->log_fp; + fparray[count].want_read = 1; + fparray[count].ignore = 0; + ctx->reaper_idx = count; + count++; + } + else + { + ctx->reaper_idx = -1; + fparray[count].ignore = 1; + } + } + for (i=count; i < fparraysize; i++) + fparray[i].ignore = 1; + } + unlock_reaper_list (); /* Note the one unlock inside the block. */ + + /* Compute the next timeout. */ npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { @@ -280,142 +370,166 @@ ldap_wrapper_thread (void *dummy) abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); + millisecs = timeout.tv_sec * 1000; + millisecs += timeout.tv_nsec / 1000000; + if (millisecs < 0) + millisecs = 1; - FD_ZERO (&fdset); - nfds = -1; - for (ctx = wrapper_list; ctx; ctx = ctx->next) + if (DBG_EXTPROG) { - if (ctx->log_fd != -1) - { - FD_SET (ctx->log_fd, &fdset); - if (ctx->log_fd > nfds) - nfds = ctx->log_fd; - } + log_debug ("ldap-reaper: next run (count=%d size=%d, timeout=%d)\n", + count, fparraysize, millisecs); + for (count=0; count < fparraysize; count++) + if (!fparray[count].ignore) + log_debug ("ldap-reaper: fp[%d] stream=%p want=%d\n", + count, fparray[count].stream,fparray[count].want_read); } - nfds++; - /* FIXME: For Windows, we have to use a reader thread on the - pipe that signals an event (and a npth_select_ev variant). */ - ret = npth_pselect (nfds + 1, &fdset, NULL, NULL, &timeout, NULL); - if (ret == -1) + ret = es_poll (fparray, fparraysize, millisecs); + if (ret < 0) { - if (errno != EINTR) - { - log_error (_("npth_select failed: %s - waiting 1s\n"), - strerror (errno)); - npth_sleep (1); - } + err = gpg_error_from_syserror (); + log_error ("ldap-reaper failed to poll: %s" + " - waiting 1s\n", gpg_strerror (err)); + /* In case the reason for the error is a too large array, we + * release it so that it will be allocated smaller in the + * next round. */ + xfree (fparray); + fparray = NULL; + fparraysize = 0; + npth_sleep (1); continue; } + if (DBG_EXTPROG) + { + for (count=0; count < fparraysize; count++) + if (!fparray[count].ignore) + log_debug ("ldap-reaper: fp[%d] stream=%p r=%d %c%c%c%c%c%c%c\n", + count, fparray[count].stream, ret, + fparray[count].got_read? 'r':'-', + fparray[count].got_write?'w':'-', + fparray[count].got_oob? 'o':'-', + fparray[count].got_rdhup?'H':'-', + fparray[count].got_err? 'e':'-', + fparray[count].got_hup? 'h':'-', + fparray[count].got_nval? 'n':'-'); + } + /* All timestamps before exptime should be considered expired. */ exptime = time (NULL); if (exptime > INACTIVITY_TIMEOUT) exptime -= INACTIVITY_TIMEOUT; - /* Note that there is no need to lock the list because we always - add entries at the head (with a pending event status) and - thus traversing the list will even work if we have a context - switch in waitpid (which should anyway only happen with Pth's - hard system call mapping). */ - for (ctx = wrapper_list; ctx; ctx = ctx->next) - { - /* Check whether there is any logging to be done. */ - if (nfds && ctx->log_fd != -1 && FD_ISSET (ctx->log_fd, &fdset)) - { - if (read_log_data (ctx)) - { - SAFE_CLOSE (ctx->log_fd); - any_action = 1; - } - } - - /* Check whether the process is still running. */ - if (ctx->pid != (pid_t)(-1)) - { - gpg_error_t err; - int status; - - err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, - &status); - if (!err) - { - log_info (_("ldap wrapper %d ready"), (int)ctx->pid); - ctx->ready = 1; - gnupg_release_process (ctx->pid); - ctx->pid = (pid_t)(-1); - any_action = 1; - } - else if (gpg_err_code (err) == GPG_ERR_GENERAL) - { - if (status == 10) - log_info (_("ldap wrapper %d ready: timeout\n"), - (int)ctx->pid); - else - log_info (_("ldap wrapper %d ready: exitcode=%d\n"), - (int)ctx->pid, status); - ctx->ready = 1; - gnupg_release_process (ctx->pid); - ctx->pid = (pid_t)(-1); - any_action = 1; - } - else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) - { - log_error (_("waiting for ldap wrapper %d failed: %s\n"), - (int)ctx->pid, gpg_strerror (err)); - any_action = 1; - } - } - - /* Check whether we should terminate the process. */ - if (ctx->pid != (pid_t)(-1) - && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime) - { - gnupg_kill_process (ctx->pid); - ctx->stamp = (time_t)(-1); - log_info (_("ldap wrapper %d stalled - killing\n"), - (int)ctx->pid); - /* We need to close the log fd because the cleanup loop - waits for it. */ - SAFE_CLOSE (ctx->log_fd); - any_action = 1; - } - } - - /* If something has been printed to the log file or we got an - EOF from a wrapper, we now print the list of active - wrappers. */ - if (any_action && DBG_LOOKUP) - { - log_info ("ldap worker stati:\n"); - for (ctx = wrapper_list; ctx; ctx = ctx->next) - log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n", - ctx, - (int)ctx->pid, (int)ctx->printable_pid, - ctx->reader, - ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, - (unsigned long)ctx->stamp, ctx->ready); - } + lock_reaper_list (); + { + for (ctx = reaper_list; ctx; ctx = ctx->next) + { + /* Check whether there is any logging to be done. We need + * to check FPARRAYSIZE because it can be 0 in case + * es_poll returned a timeout. */ + if (fparraysize && ctx->log_fp && ctx->reaper_idx >= 0) + { + log_assert (ctx->reaper_idx < fparraysize); + if (fparray[ctx->reaper_idx].got_read) + { + if (read_log_data (ctx)) + { + SAFE_CLOSE (ctx->log_fp); + any_action = 1; + } + } + } + + /* Check whether the process is still running. */ + if (ctx->pid != (pid_t)(-1)) + { + int status; + + err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, + &status); + if (!err) + { + if (DBG_EXTPROG) + log_info (_("ldap wrapper %d ready"), (int)ctx->pid); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) == GPG_ERR_GENERAL) + { + if (status == 10) + log_info (_("ldap wrapper %d ready: timeout\n"), + (int)ctx->pid); + else + log_info (_("ldap wrapper %d ready: exitcode=%d\n"), + (int)ctx->pid, status); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) + { + log_error (_("waiting for ldap wrapper %d failed: %s\n"), + (int)ctx->pid, gpg_strerror (err)); + any_action = 1; + } + } + + /* Check whether we should terminate the process. */ + if (ctx->pid != (pid_t)(-1) + && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime) + { + gnupg_kill_process (ctx->pid); + ctx->stamp = (time_t)(-1); + log_info (_("ldap wrapper %d stalled - killing\n"), + (int)ctx->pid); + /* We need to close the log stream because the cleanup + * loop waits for it. */ + SAFE_CLOSE (ctx->log_fp); + any_action = 1; + } + } + /* If something has been printed to the log file or we got an + * EOF from a wrapper, we now print the list of active + * wrappers. */ + if (any_action && DBG_EXTPROG) + { + log_debug ("ldap worker stati:\n"); + for (ctx = reaper_list; ctx; ctx = ctx->next) + log_debug (" c=%p pid=%d/%d rdr=%p logfp=%p" + " ctrl=%p/%d la=%lu rdy=%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, ctx->log_fp, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, + (unsigned long)ctx->stamp, ctx->ready); + } - /* Use a separate loop to check whether ready marked wrappers - may be removed. We may only do so if the ksba reader object - is not anymore in use or we are in shutdown state. */ - again: - for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next) - if (ctx->ready - && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down)) + /* An extra loop to check whether ready marked wrappers may be + * removed. We may only do so if the ksba reader object is + * not anymore in use or we are in shutdown state. */ + again: + for (ctx_prev=NULL, ctx=reaper_list; ctx; ctx_prev=ctx, ctx=ctx->next) { - if (ctx_prev) - ctx_prev->next = ctx->next; - else - wrapper_list = ctx->next; - destroy_wrapper (ctx); - /* We need to restart because destroy_wrapper might have - done a context switch. */ - goto again; + if (ctx->ready + && ((!ctx->log_fp && !ctx->reader) || shutting_down)) + { + if (ctx_prev) + ctx_prev->next = ctx->next; + else + reaper_list = ctx->next; + destroy_wrapper (ctx); + goto again; + } } + } + unlock_reaper_list (); } + /*NOTREACHED*/ return NULL; /* Make the compiler happy. */ } @@ -424,7 +538,7 @@ ldap_wrapper_thread (void *dummy) /* Start the reaper thread for the ldap wrapper. */ void -ldap_wrapper_launch_thread (void) +ldap_reaper_launch_thread (void) { static int done; npth_attr_t tattr; @@ -435,14 +549,21 @@ ldap_wrapper_launch_thread (void) return; done = 1; +#ifdef HAVE_W32_SYSTEM + /* Static init does not yet work in W32 nPth. */ + if (npth_cond_init (&reaper_run_cond, NULL)) + log_fatal ("%s: failed to init condition variabale: %s\n", + __func__, gpg_strerror (gpg_error_from_syserror ())); +#endif + npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); - err = npth_create (&thread, &tattr, ldap_wrapper_thread, NULL); - if (err) + if (npth_create (&thread, &tattr, ldap_reaper_thread, NULL)) { - log_error (_("error spawning ldap wrapper reaper thread: %s\n"), - strerror (err) ); + err = gpg_error_from_syserror (); + log_error ("error spawning ldap reaper reaper thread: %s\n", + gpg_strerror (err) ); dirmngr_exit (1); } npth_setname_np (thread, "ldap-reaper"); @@ -451,16 +572,20 @@ ldap_wrapper_launch_thread (void) - - /* Wait until all ldap wrappers have terminated. We assume that the kill has already been sent to all of them. */ void ldap_wrapper_wait_connections () { - shutting_down = 1; - /* FIXME: This is a busy wait. */ - while (wrapper_list) + lock_reaper_list (); + { + shutting_down = 1; + if (npth_cond_signal (&reaper_run_cond)) + log_error ("%s: Ooops: signaling condition failed: %s\n", + __func__, gpg_strerror (gpg_error_from_syserror ())); + } + unlock_reaper_list (); + while (reaper_list) npth_usleep (200); } @@ -475,30 +600,35 @@ ldap_wrapper_release_context (ksba_reader_t reader) if (!reader ) return; - for (ctx=wrapper_list; ctx; ctx=ctx->next) - if (ctx->reader == reader) - { - if (DBG_LOOKUP) - log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n", - ctx, - (int)ctx->pid, (int)ctx->printable_pid, - ctx->reader, - ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); - - ctx->reader = NULL; - SAFE_CLOSE (ctx->fd); - if (ctx->ctrl) - { - ctx->ctrl->refcount--; - ctx->ctrl = NULL; - } - if (ctx->fd_error) - log_info (_("reading from ldap wrapper %d failed: %s\n"), - ctx->printable_pid, gpg_strerror (ctx->fd_error)); - break; - } + lock_reaper_list (); + { + for (ctx=reaper_list; ctx; ctx=ctx->next) + if (ctx->reader == reader) + { + if (DBG_EXTPROG) + log_debug ("releasing ldap worker c=%p pid=%d/%d rdr=%p" + " ctrl=%p/%d\n", ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); + + ctx->reader = NULL; + SAFE_CLOSE (ctx->fp); + if (ctx->ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + } + if (ctx->fp_err) + log_info ("%s: reading from ldap wrapper %d failed: %s\n", + __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); + break; + } + } + unlock_reaper_list (); } + /* Cleanup all resources held by the connection associated with CTRL. This is used after a cancel to kill running wrappers. */ void @@ -506,41 +636,45 @@ ldap_wrapper_connection_cleanup (ctrl_t ctrl) { struct wrapper_context_s *ctx; - for (ctx=wrapper_list; ctx; ctx=ctx->next) - if (ctx->ctrl && ctx->ctrl == ctrl) - { - ctx->ctrl->refcount--; - ctx->ctrl = NULL; - if (ctx->pid != (pid_t)(-1)) - gnupg_kill_process (ctx->pid); - if (ctx->fd_error) - log_info (_("reading from ldap wrapper %d failed: %s\n"), - ctx->printable_pid, gpg_strerror (ctx->fd_error)); - } + lock_reaper_list (); + { + for (ctx=reaper_list; ctx; ctx=ctx->next) + if (ctx->ctrl && ctx->ctrl == ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + if (ctx->pid != (pid_t)(-1)) + gnupg_kill_process (ctx->pid); + if (ctx->fp_err) + log_info ("%s: reading from ldap wrapper %d failed: %s\n", + __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); + } + } + unlock_reaper_list (); } /* This is the callback used by the ldap wrapper to feed the ksba - reader with the wrappers stdout. See the description of - ksba_reader_set_cb for details. */ + * reader with the wrapper's stdout. See the description of + * ksba_reader_set_cb for details. */ static int reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) { struct wrapper_context_s *ctx = cb_value; size_t nleft = count; - int nfds; struct timespec abstime; struct timespec curtime; struct timespec timeout; - int saved_errno; - fd_set fdset, read_fdset; + int millisecs; + gpgrt_poll_t fparray[1]; int ret; + gpg_error_t err; + /* FIXME: We might want to add some internal buffering because the ksba code does not do any buffering for itself (because a ksba reader may be detached from another stream to read other data and - the it would be cumbersome to get back already buffered - stuff). */ + then it would be cumbersome to get back already buffered stuff). */ if (!buffer && !count && !nread) return -1; /* Rewind is not supported. */ @@ -548,81 +682,108 @@ reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) /* If we ever encountered a read error, don't continue (we don't want to possibly overwrite the last error cause). Bail out also if the file descriptor has been closed. */ - if (ctx->fd_error || ctx->fd == -1) + if (ctx->fp_err || !ctx->fp) { *nread = 0; return -1; } - FD_ZERO (&fdset); - FD_SET (ctx->fd, &fdset); - nfds = ctx->fd + 1; + memset (fparray, 0, sizeof fparray); + fparray[0].stream = ctx->fp; + fparray[0].want_read = 1; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; while (nleft > 0) { - int n; - gpg_error_t err; - npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { err = dirmngr_tick (ctx->ctrl); if (err) { - ctx->fd_error = err; - SAFE_CLOSE (ctx->fd); + ctx->fp_err = err; + SAFE_CLOSE (ctx->fp); return -1; } npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); + millisecs = timeout.tv_sec * 1000; + millisecs += timeout.tv_nsec / 1000000; + if (millisecs < 0) + millisecs = 1; - read_fdset = fdset; - ret = npth_pselect (nfds, &read_fdset, NULL, NULL, &timeout, NULL); - saved_errno = errno; + if (DBG_EXTPROG) + { + log_debug ("%s: fp[0] stream=%p want=%d\n", + __func__, fparray[0].stream,fparray[0].want_read); + } - if (ret == -1 && saved_errno != EINTR) + ret = es_poll (fparray, DIM (fparray), millisecs); + if (ret < 0) { - ctx->fd_error = gpg_error_from_errno (errno); - SAFE_CLOSE (ctx->fd); + ctx->fp_err = gpg_error_from_syserror (); + log_error ("error polling stdout of ldap wrapper %d: %s\n", + ctx->printable_pid, gpg_strerror (ctx->fp_err)); + SAFE_CLOSE (ctx->fp); return -1; } - if (ret <= 0) - /* Timeout. Will be handled when calculating the next timeout. */ - continue; - - /* This should not block now that select returned with a file - descriptor. So it shouldn't be necessary to use npth_read - (and it is slightly dangerous in the sense that a concurrent - thread might (accidentially?) change the status of ctx->fd - before we read. FIXME: Set ctx->fd to nonblocking? */ - n = read (ctx->fd, buffer, nleft); - if (n < 0) + if (DBG_EXTPROG) { - ctx->fd_error = gpg_error_from_errno (errno); - SAFE_CLOSE (ctx->fd); - return -1; + log_debug ("%s: fp[0] stream=%p r=%d %c%c%c%c%c%c%c\n", + __func__, fparray[0].stream, ret, + fparray[0].got_read? 'r':'-', + fparray[0].got_write?'w':'-', + fparray[0].got_oob? 'o':'-', + fparray[0].got_rdhup?'H':'-', + fparray[0].got_err? 'e':'-', + fparray[0].got_hup? 'h':'-', + fparray[0].got_nval? 'n':'-'); } - else if (!n) + if (!ret) { - if (nleft == count) - return -1; /* EOF. */ - break; + /* Timeout. Will be handled when calculating the next timeout. */ + continue; + } + + if (fparray[0].got_read) + { + size_t n; + + if (es_read (ctx->fp, buffer, nleft, &n)) + { + ctx->fp_err = gpg_error_from_syserror (); + if (gpg_err_code (ctx->fp_err) == GPG_ERR_EAGAIN) + ctx->fp_err = 0; + else + { + log_error ("%s: error reading: %s (%d)\n", + __func__, gpg_strerror (ctx->fp_err), ctx->fp_err); + SAFE_CLOSE (ctx->fp); + return -1; + } + } + else if (!n) /* EOF */ + { + if (nleft == count) + return -1; /* EOF. */ + break; + } + nleft -= n; + buffer += n; + if (n > 0 && ctx->stamp != (time_t)(-1)) + ctx->stamp = time (NULL); } - nleft -= n; - buffer += n; - if (n > 0 && ctx->stamp != (time_t)(-1)) - ctx->stamp = time (NULL); } *nread = count - nleft; return 0; } + /* Fork and exec the LDAP wrapper and return a new libksba reader object at READER. ARGV is a NULL terminated list of arguments for the wrapper. The function returns 0 on success or an error code. @@ -646,7 +807,7 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) int j; const char **arg_list; const char *pgmname; - int outpipe[2], errpipe[2]; + estream_t outfp, errfp; /* It would be too simple to connect stderr just to our logging stream. The problem is that if we are running multi-threaded @@ -656,7 +817,7 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) wrapper module to do the logging on its own. Given that we anyway need a way to reap the child process and this is best done using a general reaping thread, that thread can do the logging too. */ - ldap_wrapper_launch_thread (); + ldap_reaper_launch_thread (); *reader = NULL; @@ -696,41 +857,21 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) return err; } - err = gnupg_create_inbound_pipe (outpipe, NULL, 0); - if (!err) - { - err = gnupg_create_inbound_pipe (errpipe, NULL, 0); - if (err) - { - close (outpipe[0]); - close (outpipe[1]); - } - } - if (err) - { - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - xfree (arg_list); - xfree (ctx); - return err; - } - - err = gnupg_spawn_process_fd (pgmname, arg_list, - -1, outpipe[1], errpipe[1], &pid); + err = gnupg_spawn_process (pgmname, arg_list, + NULL, NULL, GNUPG_SPAWN_NONBLOCK, + NULL, &outfp, &errfp, &pid); xfree (arg_list); - close (outpipe[1]); - close (errpipe[1]); if (err) { - close (outpipe[0]); - close (errpipe[0]); xfree (ctx); + log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); return err; } ctx->pid = pid; ctx->printable_pid = (int) pid; - ctx->fd = outpipe[0]; - ctx->log_fd = errpipe[0]; + ctx->fp = outfp; + ctx->log_fp = errfp; ctx->ctrl = ctrl; ctrl->refcount++; ctx->stamp = time (NULL); @@ -749,12 +890,20 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) } /* Hook the context into our list of running wrappers. */ - ctx->reader = *reader; - ctx->next = wrapper_list; - wrapper_list = ctx; - if (opt.verbose) - log_info ("ldap wrapper %d started (reader %p)\n", - (int)ctx->pid, ctx->reader); + lock_reaper_list (); + { + ctx->reader = *reader; + ctx->next = reaper_list; + reaper_list = ctx; + if (npth_cond_signal (&reaper_run_cond)) + log_error ("ldap-wrapper: Ooops: signaling condition failed: %s (%d)\n", + gpg_strerror (gpg_error_from_syserror ()), errno); + } + unlock_reaper_list (); + + if (DBG_EXTPROG) + log_debug ("ldap wrapper %d started (%p, %s)\n", + (int)ctx->pid, ctx->reader, pgmname); /* Need to wait for the first byte so we are able to detect an empty output and not let the consumer see an EOF without further error |