LCOV - code coverage report
Current view: top level - common - exectool.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 182 280 65.0 %
Date: 2016-09-12 12:29:17 Functions: 8 9 88.9 %

          Line data    Source code
       1             : /* exectool.c - Utility functions to execute a helper tool
       2             :  * Copyright (C) 2015 Werner Koch
       3             :  * Copyright (C) 2016 g10 Code GmbH
       4             :  *
       5             :  * This file is part of GnuPG.
       6             :  *
       7             :  * This file is free software; you can redistribute it and/or modify
       8             :  * it under the terms of either
       9             :  *
      10             :  *   - the GNU Lesser General Public License as published by the Free
      11             :  *     Software Foundation; either version 3 of the License, or (at
      12             :  *     your option) any later version.
      13             :  *
      14             :  * or
      15             :  *
      16             :  *   - the GNU General Public License as published by the Free
      17             :  *     Software Foundation; either version 2 of the License, or (at
      18             :  *     your option) any later version.
      19             :  *
      20             :  * or both in parallel, as here.
      21             :  *
      22             :  * This file is distributed in the hope that it will be useful,
      23             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      24             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      25             :  * GNU General Public License for more details.
      26             :  *
      27             :  * You should have received a copy of the GNU General Public License
      28             :  * along with this program; if not, see <http://www.gnu.org/licenses/>.
      29             :  */
      30             : 
      31             : #include <config.h>
      32             : #include <stdio.h>
      33             : #include <stdlib.h>
      34             : #include <string.h>
      35             : #include <stdarg.h>
      36             : #include <errno.h>
      37             : #include <assert.h>
      38             : #include <gpg-error.h>
      39             : 
      40             : #include <assuan.h>
      41             : #include "i18n.h"
      42             : #include "logging.h"
      43             : #include "membuf.h"
      44             : #include "mischelp.h"
      45             : #include "exechelp.h"
      46             : #include "sysutils.h"
      47             : #include "util.h"
      48             : #include "exectool.h"
      49             : 
      50             : typedef struct
      51             : {
      52             :   const char *pgmname;
      53             :   exec_tool_status_cb_t status_cb;
      54             :   void *status_cb_value;
      55             :   int cont;
      56             :   size_t used;
      57             :   size_t buffer_size;
      58             :   char *buffer;
      59             : } read_and_log_buffer_t;
      60             : 
      61             : 
      62             : static inline gpg_error_t
      63           0 : my_error_from_syserror (void)
      64             : {
      65           0 :   return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
      66             : }
      67             : 
      68             : 
      69             : static void
      70         131 : read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr)
      71             : {
      72             :   gpg_error_t err;
      73             :   int c;
      74             : 
      75         131 :   if (!fderr)
      76             :     {
      77             :       /* Flush internal buffer.  */
      78          77 :       if (state->used)
      79             :         {
      80             :           const char *pname;
      81             :           int len;
      82             : 
      83          54 :           state->buffer[state->used] = 0;
      84          54 :           state->used = 0;
      85             : 
      86          54 :           pname = strrchr (state->pgmname, '/');
      87          54 :           if (pname && pname != state->pgmname && pname[1])
      88          54 :             pname++;
      89             :           else
      90           0 :             pname = state->pgmname;
      91          54 :           len = strlen (pname);
      92             : 
      93          54 :           if (state->status_cb
      94           0 :               && !strncmp (state->buffer, "[GNUPG:] ", 9)
      95           0 :               && state->buffer[9] >= 'A' && state->buffer[9] <= 'Z')
      96           0 :             {
      97             :               char *rest;
      98             : 
      99           0 :               rest = strchr (state->buffer + 9, ' ');
     100           0 :               if (!rest)
     101             :                 {
     102             :                   /* Set REST to an empty string.  */
     103           0 :                   rest = state->buffer + strlen (state->buffer);
     104             :                 }
     105             :               else
     106             :                 {
     107           0 :                   *rest++ = 0;
     108           0 :                   trim_spaces (rest);
     109             :                 }
     110           0 :               state->status_cb (state->status_cb_value,
     111           0 :                                 state->buffer + 9, rest);
     112             :             }
     113          54 :           else if (!state->cont
     114          54 :               && !strncmp (state->buffer, pname, len)
     115          48 :               && strlen (state->buffer) > strlen (pname)
     116          48 :               && state->buffer[len] == ':' )
     117             :             {
     118             :               /* PGMNAME plus colon is identical to the start of
     119             :                  the output: print only the output.  */
     120          48 :               log_info ("%s\n", state->buffer);
     121             :             }
     122             :           else
     123          12 :             log_info ("%s%c %s\n",
     124           6 :                       pname, state->cont? '+':':', state->buffer);
     125             :         }
     126          77 :       state->cont = 0;
     127         208 :       return;
     128             :     }
     129             :   for (;;)
     130             :     {
     131        2730 :       c = es_fgetc (fderr->stream);
     132        2730 :       if (c == EOF)
     133             :         {
     134          54 :           if (es_feof (fderr->stream))
     135             :             {
     136          23 :               fderr->ignore = 1; /* Not anymore needed.  */
     137             :             }
     138          31 :           else if (es_ferror (fderr->stream))
     139             :             {
     140           0 :               err = my_error_from_syserror ();
     141           0 :               log_error ("error reading stderr of '%s': %s\n",
     142             :                          state->pgmname, gpg_strerror (err));
     143           0 :               fderr->ignore = 1; /* Disable.  */
     144             :             }
     145             : 
     146          54 :           break;
     147             :         }
     148        2676 :       else if (c == '\n')
     149             :         {
     150          54 :           read_and_log_stderr (state, NULL);
     151             :         }
     152             :       else
     153             :         {
     154        2622 :           if (state->used >= state->buffer_size - 1)
     155             :             {
     156           0 :               if (state->status_cb)
     157             :                 {
     158             :                   /* A status callback requires that we have a full
     159             :                    * line.  Thus we need to enlarget the buffer in
     160             :                    * this case.  */
     161             :                   char *newbuffer;
     162           0 :                   size_t newsize = state->buffer_size + 256;
     163             : 
     164           0 :                   newbuffer = xtrymalloc (newsize);
     165           0 :                   if (!newbuffer)
     166             :                     {
     167           0 :                       log_error ("error allocating memory for status cb: %s\n",
     168             :                                  gpg_strerror (my_error_from_syserror ()));
     169             :                       /* We better disable the status CB in this case.  */
     170           0 :                       state->status_cb = NULL;
     171           0 :                       read_and_log_stderr (state, NULL);
     172           0 :                       state->cont = 1;
     173             :                     }
     174             :                   else
     175             :                     {
     176           0 :                       memcpy (newbuffer, state->buffer, state->used);
     177           0 :                       xfree (state->buffer);
     178           0 :                       state->buffer = newbuffer;
     179           0 :                       state->buffer_size = newsize;
     180             :                     }
     181             :                 }
     182             :               else
     183             :                 {
     184           0 :                   read_and_log_stderr (state, NULL);
     185           0 :                   state->cont = 1;
     186             :                 }
     187             :             }
     188        2622 :           state->buffer[state->used++] = c;
     189             :         }
     190        2676 :     }
     191             : }
     192             : 
     193             : 
     194             : 
     195             : /* A buffer to copy from one stream to another.  */
     196             : struct copy_buffer
     197             : {
     198             :   char buffer[4096];
     199             :   char *writep;
     200             :   size_t nread;
     201             : };
     202             : 
     203             : 
     204             : /* Initialize a copy buffer.  */
     205             : static void
     206          69 : copy_buffer_init (struct copy_buffer *c)
     207             : {
     208          69 :   c->writep = c->buffer;
     209          69 :   c->nread = 0;
     210          69 : }
     211             : 
     212             : 
     213             : /* Securely wipe a copy buffer.  */
     214             : static void
     215          69 : copy_buffer_shred (struct copy_buffer *c)
     216             : {
     217          69 :   if (c == NULL)
     218          69 :     return;
     219          69 :   wipememory (c->buffer, sizeof c->buffer);
     220          69 :   c->writep = NULL;
     221          69 :   c->nread = ~0U;
     222             : }
     223             : 
     224             : 
     225             : /* Copy data from SOURCE to SINK using copy buffer C.  */
     226             : static gpg_error_t
     227        1202 : copy_buffer_do_copy (struct copy_buffer *c, estream_t source, estream_t sink)
     228             : {
     229             :   gpg_error_t err;
     230        1202 :   size_t nwritten = 0;
     231             : 
     232        1202 :   if (c->nread == 0)
     233             :     {
     234        1188 :       c->writep = c->buffer;
     235        1188 :       err = es_read (source, c->buffer, sizeof c->buffer, &c->nread);
     236        1188 :       if (err)
     237             :         {
     238          14 :           if (errno == EAGAIN)
     239          14 :             return 0;   /* We will just retry next time.  */
     240             : 
     241           0 :           return my_error_from_syserror ();
     242             :         }
     243             : 
     244        1174 :       assert (c->nread <= sizeof c->buffer);
     245             :     }
     246             : 
     247        1188 :   if (c->nread == 0)
     248          29 :     return 0;   /* Done copying.  */
     249             : 
     250             : 
     251        1159 :   err = sink? es_write (sink, c->writep, c->nread, &nwritten) : 0;
     252        1159 :   if (err)
     253             :     {
     254           0 :       if (errno == EAGAIN)
     255           0 :         return 0;       /* We will just retry next time.  */
     256             : 
     257           0 :       return my_error_from_syserror ();
     258             :     }
     259             : 
     260        1159 :   assert (nwritten <= c->nread);
     261        1159 :   c->writep += nwritten;
     262        1159 :   c->nread -= nwritten;
     263        1159 :   assert (c->writep - c->buffer <= sizeof c->buffer);
     264             : 
     265        1159 :   if (sink && es_fflush (sink) && errno != EAGAIN)
     266           0 :     err = my_error_from_syserror ();
     267             : 
     268        1159 :   return err;
     269             : }
     270             : 
     271             : 
     272             : /* Flush the remaining data to SINK.  */
     273             : static gpg_error_t
     274          46 : copy_buffer_flush (struct copy_buffer *c, estream_t sink)
     275             : {
     276             :   gpg_error_t err;
     277             : 
     278          92 :   while (c->nread > 0)
     279             :     {
     280           0 :       err = copy_buffer_do_copy (c, NULL, sink);
     281           0 :       if (err)
     282           0 :         return err;
     283             :     }
     284             : 
     285          46 :   return 0;
     286             : }
     287             : 
     288             : 
     289             : 
     290             : /* Run the program PGMNAME with the command line arguments given in
     291             :  * the NULL terminates array ARGV.  If INPUT is not NULL it will be
     292             :  * fed to stdin of the process.  stderr is logged using log_info and
     293             :  * the process' stdout is written to OUTPUT.  If OUTPUT is NULL the
     294             :  * output is discarded.  If INEXTRA is given, an additional input
     295             :  * stream will be passed to the child; to tell the child about this
     296             :  * ARGV is scanned and the first occurrence of an argument
     297             :  * "-&@INEXTRA@" is replaced by the concatenation of "-&" and the
     298             :  * child's file descriptor of the pipe created for the INEXTRA stream.
     299             :  *
     300             :  * On error a diagnostic is printed and an error code returned.  */
     301             : gpg_error_t
     302          23 : gnupg_exec_tool_stream (const char *pgmname, const char *argv[],
     303             :                         estream_t input, estream_t inextra,
     304             :                         estream_t output,
     305             :                         exec_tool_status_cb_t status_cb,
     306             :                         void *status_cb_value)
     307             : {
     308             :   gpg_error_t err;
     309          23 :   pid_t pid = (pid_t) -1;
     310          23 :   estream_t infp = NULL;
     311          23 :   estream_t extrafp = NULL;
     312          23 :   estream_t outfp = NULL, errfp = NULL;
     313             :   es_poll_t fds[4];
     314             :   int exceptclose[2];
     315          23 :   int extrapipe[2] = {-1, -1};
     316             :   char extrafdbuf[20];
     317          23 :   const char *argsave = NULL;
     318             :   int argsaveidx;
     319             :   int count;
     320             :   read_and_log_buffer_t fderrstate;
     321          23 :   struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL;
     322             : 
     323          23 :   memset (fds, 0, sizeof fds);
     324          23 :   memset (&fderrstate, 0, sizeof fderrstate);
     325             : 
     326          23 :   cpbuf_in = xtrymalloc (sizeof *cpbuf_in);
     327          23 :   if (cpbuf_in == NULL)
     328             :     {
     329           0 :       err = my_error_from_syserror ();
     330           0 :       goto leave;
     331             :     }
     332          23 :   copy_buffer_init (cpbuf_in);
     333             : 
     334          23 :   cpbuf_out = xtrymalloc (sizeof *cpbuf_out);
     335          23 :   if (cpbuf_out == NULL)
     336             :     {
     337           0 :       err = my_error_from_syserror ();
     338           0 :       goto leave;
     339             :     }
     340          23 :   copy_buffer_init (cpbuf_out);
     341             : 
     342          23 :   cpbuf_extra = xtrymalloc (sizeof *cpbuf_extra);
     343          23 :   if (cpbuf_extra == NULL)
     344             :     {
     345           0 :       err = my_error_from_syserror ();
     346           0 :       goto leave;
     347             :     }
     348          23 :   copy_buffer_init (cpbuf_extra);
     349             : 
     350          23 :   fderrstate.pgmname = pgmname;
     351          23 :   fderrstate.status_cb = status_cb;
     352          23 :   fderrstate.status_cb_value = status_cb_value;
     353          23 :   fderrstate.buffer_size = 256;
     354          23 :   fderrstate.buffer = xtrymalloc (fderrstate.buffer_size);
     355          23 :   if (!fderrstate.buffer)
     356             :     {
     357           0 :       err = my_error_from_syserror ();
     358           0 :       goto leave;
     359             :     }
     360             : 
     361          23 :   if (inextra)
     362             :     {
     363           0 :       err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1);
     364           0 :       if (err)
     365             :         {
     366           0 :           log_error ("error running outbound pipe for extra fp: %s\n",
     367             :                      gpg_strerror (err));
     368           0 :           goto leave;
     369             :         }
     370           0 :       exceptclose[0] = extrapipe[0]; /* Do not close in child. */
     371           0 :       exceptclose[1] = -1;
     372             :       /* Now find the argument marker and replace by the pipe's fd.
     373             :          Yeah, that is an ugly non-thread safe hack but it safes us to
     374             :          create a copy of the array.  */
     375           0 :       snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", extrapipe[0]);
     376           0 :       for (argsaveidx=0; argv[argsaveidx]; argsaveidx++)
     377           0 :         if (!strcmp (argv[argsaveidx], "-&@INEXTRA@"))
     378             :           {
     379           0 :             argsave = argv[argsaveidx];
     380           0 :             argv[argsaveidx] = extrafdbuf;
     381           0 :             break;
     382             :           }
     383             :     }
     384             :   else
     385          23 :     exceptclose[0] = -1;
     386             : 
     387          23 :   err = gnupg_spawn_process (pgmname, argv,
     388             :                              exceptclose, NULL, GNUPG_SPAWN_NONBLOCK,
     389             :                              input? &infp : NULL,
     390             :                              &outfp, &errfp, &pid);
     391          23 :   if (extrapipe[0] != -1)
     392           0 :     close (extrapipe[0]);
     393          23 :   if (argsave)
     394           0 :     argv[argsaveidx] = argsave;
     395          23 :   if (err)
     396             :     {
     397           0 :       log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err));
     398           0 :       goto leave;
     399             :     }
     400             : 
     401          23 :   fds[0].stream = infp;
     402          23 :   fds[0].want_write = 1;
     403          23 :   if (!input)
     404           0 :     fds[0].ignore = 1;
     405          23 :   fds[1].stream = outfp;
     406          23 :   fds[1].want_read = 1;
     407          23 :   fds[2].stream = errfp;
     408          23 :   fds[2].want_read = 1;
     409          23 :   fds[3].stream = extrafp;
     410          23 :   fds[3].want_write = 1;
     411          23 :   if (!inextra)
     412          23 :     fds[3].ignore = 1;
     413             : 
     414             :   /* Now read as long as we have something to poll.  We continue
     415             :      reading even after EOF or error on stdout so that we get the
     416             :      other error messages or remaining outut.  */
     417        1262 :   while (! (fds[1].ignore && fds[2].ignore))
     418             :     {
     419        1216 :       count = es_poll (fds, DIM(fds), -1);
     420        1216 :       if (count == -1)
     421             :         {
     422           0 :           err = my_error_from_syserror ();
     423           0 :           log_error ("error polling '%s': %s\n", pgmname, gpg_strerror (err));
     424           0 :           goto leave;
     425             :         }
     426        1216 :       if (!count)
     427             :         {
     428           0 :           log_debug ("unexpected timeout while polling '%s'\n", pgmname);
     429           0 :           break;
     430             :         }
     431             : 
     432        1216 :       if (fds[0].got_write)
     433             :         {
     434         575 :           err = copy_buffer_do_copy (cpbuf_in, input, fds[0].stream);
     435         575 :           if (err)
     436             :             {
     437           0 :               log_error ("error feeding data to '%s': %s\n",
     438             :                          pgmname, gpg_strerror (err));
     439           0 :               goto leave;
     440             :             }
     441             : 
     442         575 :           if (es_feof (input))
     443             :             {
     444          23 :               err = copy_buffer_flush (cpbuf_in, fds[0].stream);
     445          23 :               if (err)
     446             :                 {
     447           0 :                   log_error ("error feeding data to '%s': %s\n",
     448             :                              pgmname, gpg_strerror (err));
     449           0 :                   goto leave;
     450             :                 }
     451             : 
     452          23 :               fds[0].ignore = 1; /* ready.  */
     453          23 :               es_fclose (infp); infp = NULL;
     454             :             }
     455             :         }
     456             : 
     457        1216 :       if (fds[3].got_write)
     458             :         {
     459           0 :           log_assert (inextra);
     460           0 :           err = copy_buffer_do_copy (cpbuf_extra, inextra, fds[3].stream);
     461           0 :           if (err)
     462             :             {
     463           0 :               log_error ("error feeding data to '%s': %s\n",
     464             :                          pgmname, gpg_strerror (err));
     465           0 :               goto leave;
     466             :             }
     467             : 
     468           0 :           if (es_feof (inextra))
     469             :             {
     470           0 :               err = copy_buffer_flush (cpbuf_extra, fds[3].stream);
     471           0 :               if (err)
     472             :                 {
     473           0 :                   log_error ("error feeding data to '%s': %s\n",
     474             :                              pgmname, gpg_strerror (err));
     475           0 :                   goto leave;
     476             :                 }
     477             : 
     478           0 :               fds[3].ignore = 1; /* ready.  */
     479           0 :               es_fclose (extrafp); extrafp = NULL;
     480             :             }
     481             :         }
     482             : 
     483        1216 :       if (fds[1].got_read)
     484             :         {
     485         627 :           err = copy_buffer_do_copy (cpbuf_out, fds[1].stream, output);
     486         627 :           if (err)
     487             :             {
     488           0 :               log_error ("error reading data from '%s': %s\n",
     489             :                          pgmname, gpg_strerror (err));
     490           0 :               goto leave;
     491             :             }
     492             : 
     493         627 :           if (es_feof (fds[1].stream))
     494             :             {
     495          23 :               err = copy_buffer_flush (cpbuf_out, output);
     496          23 :               if (err)
     497             :                 {
     498           0 :                   log_error ("error reading data from '%s': %s\n",
     499             :                              pgmname, gpg_strerror (err));
     500           0 :                   goto leave;
     501             :                 }
     502             : 
     503          23 :               fds[1].ignore = 1; /* ready.  */
     504             :             }
     505             :         }
     506             : 
     507        1216 :       if (fds[2].got_read)
     508          54 :         read_and_log_stderr (&fderrstate, fds + 2);
     509             :     }
     510             : 
     511          23 :   read_and_log_stderr (&fderrstate, NULL); /* Flush.  */
     512          23 :   es_fclose (infp); infp = NULL;
     513          23 :   es_fclose (extrafp); extrafp = NULL;
     514          23 :   es_fclose (outfp); outfp = NULL;
     515          23 :   es_fclose (errfp); errfp = NULL;
     516             : 
     517          23 :   err = gnupg_wait_process (pgmname, pid, 1, NULL);
     518          23 :   pid = (pid_t)(-1);
     519             : 
     520             :  leave:
     521          23 :   if (err && pid != (pid_t) -1)
     522           0 :     gnupg_kill_process (pid);
     523             : 
     524          23 :   es_fclose (infp);
     525          23 :   es_fclose (extrafp);
     526          23 :   es_fclose (outfp);
     527          23 :   es_fclose (errfp);
     528          23 :   if (pid != (pid_t)(-1))
     529           0 :     gnupg_wait_process (pgmname, pid, 1, NULL);
     530          23 :   gnupg_release_process (pid);
     531             : 
     532          23 :   copy_buffer_shred (cpbuf_in);
     533          23 :   xfree (cpbuf_in);
     534          23 :   copy_buffer_shred (cpbuf_out);
     535          23 :   xfree (cpbuf_out);
     536          23 :   copy_buffer_shred (cpbuf_extra);
     537          23 :   xfree (cpbuf_extra);
     538          23 :   xfree (fderrstate.buffer);
     539          23 :   return err;
     540             : }
     541             : 
     542             : 
     543             : /* A dummy free function to pass to 'es_mopen'.  */
     544             : static void
     545           5 : nop_free (void *ptr)
     546             : {
     547             :   (void) ptr;
     548           5 : }
     549             : 
     550             : /* Run the program PGMNAME with the command line arguments given in
     551             :    the NULL terminates array ARGV.  If INPUT_STRING is not NULL it
     552             :    will be fed to stdin of the process.  stderr is logged using
     553             :    log_info and the process' stdout is returned in a newly malloced
     554             :    buffer RESULT with the length stored at RESULTLEN if not given as
     555             :    NULL.  A hidden Nul is appended to the output.  On error NULL is
     556             :    stored at RESULT, a diagnostic is printed, and an error code
     557             :    returned.  */
     558             : gpg_error_t
     559           5 : gnupg_exec_tool (const char *pgmname, const char *argv[],
     560             :                  const char *input_string,
     561             :                  char **result, size_t *resultlen)
     562             : {
     563             :   gpg_error_t err;
     564           5 :   estream_t input = NULL;
     565             :   estream_t output;
     566             :   size_t len;
     567             :   size_t nread;
     568             : 
     569           5 :   *result = NULL;
     570           5 :   if (resultlen)
     571           5 :     *resultlen = 0;
     572             : 
     573           5 :   if (input_string)
     574             :     {
     575           5 :       len = strlen (input_string);
     576           5 :       input = es_mopen ((char *) input_string, len, len,
     577             :                         0 /* don't grow */, NULL, nop_free, "rb");
     578           5 :       if (! input)
     579           0 :         return my_error_from_syserror ();
     580             :     }
     581             : 
     582           5 :   output = es_fopenmem (0, "wb");
     583           5 :   if (! output)
     584             :     {
     585           0 :       err = my_error_from_syserror ();
     586           0 :       goto leave;
     587             :     }
     588             : 
     589           5 :   err = gnupg_exec_tool_stream (pgmname, argv, input, NULL, output, NULL, NULL);
     590           5 :   if (err)
     591           1 :     goto leave;
     592             : 
     593           4 :   len = es_ftello (output);
     594           4 :   err = es_fseek (output, 0, SEEK_SET);
     595           4 :   if (err)
     596           0 :     goto leave;
     597             : 
     598           4 :   *result = xtrymalloc (len + 1);
     599           4 :   if (!*result)
     600             :     {
     601           0 :       err = my_error_from_syserror ();
     602           0 :       goto leave;
     603             :     }
     604             : 
     605           4 :   if (len)
     606             :     {
     607           3 :       err = es_read (output, *result, len, &nread);
     608           3 :       if (err)
     609           0 :         goto leave;
     610           3 :       if (nread != len)
     611           0 :         log_fatal ("%s: short read from memstream\n", __func__);
     612             :     }
     613           4 :   (*result)[len] = 0;
     614             : 
     615           4 :   if (resultlen)
     616           4 :     *resultlen = len;
     617             : 
     618             :  leave:
     619           5 :   es_fclose (input);
     620           5 :   es_fclose (output);
     621           5 :   if (err)
     622             :     {
     623           1 :       xfree (*result);
     624           1 :       *result = NULL;
     625             :     }
     626           5 :   return err;
     627             : }

Generated by: LCOV version 1.11