LCOV - code coverage report
Current view: top level - common - exectool.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 183 281 65.1 %
Date: 2016-11-29 15:00:56 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 <https://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         134 : 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         134 :   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         211 :       return;
     128             :     }
     129             :   for (;;)
     130             :     {
     131        2871 :       c = es_fgetc (fderr->stream);
     132        2871 :       if (c == EOF)
     133             :         {
     134          57 :           if (es_feof (fderr->stream))
     135             :             {
     136          23 :               fderr->ignore = 1; /* Not anymore needed.  */
     137             :             }
     138          34 :           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          57 :           break;
     147             :         }
     148        2814 :       else if (c == '\n')
     149             :         {
     150          54 :           read_and_log_stderr (state, NULL);
     151             :         }
     152             :       else
     153             :         {
     154        2760 :           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        2760 :           state->buffer[state->used++] = c;
     189             :         }
     190        2814 :     }
     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        1210 : copy_buffer_do_copy (struct copy_buffer *c, estream_t source, estream_t sink)
     228             : {
     229             :   gpg_error_t err;
     230        1210 :   size_t nwritten = 0;
     231             : 
     232        1210 :   if (c->nread == 0)
     233             :     {
     234        1192 :       c->writep = c->buffer;
     235        1192 :       err = es_read (source, c->buffer, sizeof c->buffer, &c->nread);
     236        1192 :       if (err)
     237             :         {
     238          18 :           if (errno == EAGAIN)
     239          18 :             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        1192 :   if (c->nread == 0)
     248          31 :     return 0;   /* Done copying.  */
     249             : 
     250             : 
     251        1161 :   nwritten = 0;
     252        1161 :   err = sink? es_write (sink, c->writep, c->nread, &nwritten) : 0;
     253             : 
     254        1161 :   assert (nwritten <= c->nread);
     255        1161 :   c->writep += nwritten;
     256        1161 :   c->nread -= nwritten;
     257        1161 :   assert (c->writep - c->buffer <= sizeof c->buffer);
     258             : 
     259        1161 :   if (err)
     260             :     {
     261           0 :       if (errno == EAGAIN)
     262           0 :         return 0;       /* We will just retry next time.  */
     263             : 
     264           0 :       return my_error_from_syserror ();
     265             :     }
     266             : 
     267        1161 :   if (sink && es_fflush (sink) && errno != EAGAIN)
     268           0 :     err = my_error_from_syserror ();
     269             : 
     270        1161 :   return err;
     271             : }
     272             : 
     273             : 
     274             : /* Flush the remaining data to SINK.  */
     275             : static gpg_error_t
     276          46 : copy_buffer_flush (struct copy_buffer *c, estream_t sink)
     277             : {
     278             :   gpg_error_t err;
     279             : 
     280          92 :   while (c->nread > 0)
     281             :     {
     282           0 :       err = copy_buffer_do_copy (c, NULL, sink);
     283           0 :       if (err)
     284           0 :         return err;
     285             :     }
     286             : 
     287          46 :   return 0;
     288             : }
     289             : 
     290             : 
     291             : 
     292             : /* Run the program PGMNAME with the command line arguments given in
     293             :  * the NULL terminates array ARGV.  If INPUT is not NULL it will be
     294             :  * fed to stdin of the process.  stderr is logged using log_info and
     295             :  * the process' stdout is written to OUTPUT.  If OUTPUT is NULL the
     296             :  * output is discarded.  If INEXTRA is given, an additional input
     297             :  * stream will be passed to the child; to tell the child about this
     298             :  * ARGV is scanned and the first occurrence of an argument
     299             :  * "-&@INEXTRA@" is replaced by the concatenation of "-&" and the
     300             :  * child's file descriptor of the pipe created for the INEXTRA stream.
     301             :  *
     302             :  * On error a diagnostic is printed and an error code returned.  */
     303             : gpg_error_t
     304          23 : gnupg_exec_tool_stream (const char *pgmname, const char *argv[],
     305             :                         estream_t input, estream_t inextra,
     306             :                         estream_t output,
     307             :                         exec_tool_status_cb_t status_cb,
     308             :                         void *status_cb_value)
     309             : {
     310             :   gpg_error_t err;
     311          23 :   pid_t pid = (pid_t) -1;
     312          23 :   estream_t infp = NULL;
     313          23 :   estream_t extrafp = NULL;
     314          23 :   estream_t outfp = NULL, errfp = NULL;
     315             :   es_poll_t fds[4];
     316             :   int exceptclose[2];
     317          23 :   int extrapipe[2] = {-1, -1};
     318             :   char extrafdbuf[20];
     319          23 :   const char *argsave = NULL;
     320             :   int argsaveidx;
     321             :   int count;
     322             :   read_and_log_buffer_t fderrstate;
     323          23 :   struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL;
     324             : 
     325          23 :   memset (fds, 0, sizeof fds);
     326          23 :   memset (&fderrstate, 0, sizeof fderrstate);
     327             : 
     328          23 :   cpbuf_in = xtrymalloc (sizeof *cpbuf_in);
     329          23 :   if (cpbuf_in == NULL)
     330             :     {
     331           0 :       err = my_error_from_syserror ();
     332           0 :       goto leave;
     333             :     }
     334          23 :   copy_buffer_init (cpbuf_in);
     335             : 
     336          23 :   cpbuf_out = xtrymalloc (sizeof *cpbuf_out);
     337          23 :   if (cpbuf_out == NULL)
     338             :     {
     339           0 :       err = my_error_from_syserror ();
     340           0 :       goto leave;
     341             :     }
     342          23 :   copy_buffer_init (cpbuf_out);
     343             : 
     344          23 :   cpbuf_extra = xtrymalloc (sizeof *cpbuf_extra);
     345          23 :   if (cpbuf_extra == NULL)
     346             :     {
     347           0 :       err = my_error_from_syserror ();
     348           0 :       goto leave;
     349             :     }
     350          23 :   copy_buffer_init (cpbuf_extra);
     351             : 
     352          23 :   fderrstate.pgmname = pgmname;
     353          23 :   fderrstate.status_cb = status_cb;
     354          23 :   fderrstate.status_cb_value = status_cb_value;
     355          23 :   fderrstate.buffer_size = 256;
     356          23 :   fderrstate.buffer = xtrymalloc (fderrstate.buffer_size);
     357          23 :   if (!fderrstate.buffer)
     358             :     {
     359           0 :       err = my_error_from_syserror ();
     360           0 :       goto leave;
     361             :     }
     362             : 
     363          23 :   if (inextra)
     364             :     {
     365           0 :       err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1);
     366           0 :       if (err)
     367             :         {
     368           0 :           log_error ("error running outbound pipe for extra fp: %s\n",
     369             :                      gpg_strerror (err));
     370           0 :           goto leave;
     371             :         }
     372           0 :       exceptclose[0] = extrapipe[0]; /* Do not close in child. */
     373           0 :       exceptclose[1] = -1;
     374             :       /* Now find the argument marker and replace by the pipe's fd.
     375             :          Yeah, that is an ugly non-thread safe hack but it safes us to
     376             :          create a copy of the array.  */
     377           0 :       snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", extrapipe[0]);
     378           0 :       for (argsaveidx=0; argv[argsaveidx]; argsaveidx++)
     379           0 :         if (!strcmp (argv[argsaveidx], "-&@INEXTRA@"))
     380             :           {
     381           0 :             argsave = argv[argsaveidx];
     382           0 :             argv[argsaveidx] = extrafdbuf;
     383           0 :             break;
     384             :           }
     385             :     }
     386             :   else
     387          23 :     exceptclose[0] = -1;
     388             : 
     389          23 :   err = gnupg_spawn_process (pgmname, argv,
     390             :                              exceptclose, NULL, GNUPG_SPAWN_NONBLOCK,
     391             :                              input? &infp : NULL,
     392             :                              &outfp, &errfp, &pid);
     393          23 :   if (extrapipe[0] != -1)
     394           0 :     close (extrapipe[0]);
     395          23 :   if (argsave)
     396           0 :     argv[argsaveidx] = argsave;
     397          23 :   if (err)
     398             :     {
     399           0 :       log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err));
     400           0 :       goto leave;
     401             :     }
     402             : 
     403          23 :   fds[0].stream = infp;
     404          23 :   fds[0].want_write = 1;
     405          23 :   if (!input)
     406           0 :     fds[0].ignore = 1;
     407          23 :   fds[1].stream = outfp;
     408          23 :   fds[1].want_read = 1;
     409          23 :   fds[2].stream = errfp;
     410          23 :   fds[2].want_read = 1;
     411          23 :   fds[3].stream = extrafp;
     412          23 :   fds[3].want_write = 1;
     413          23 :   if (!inextra)
     414          23 :     fds[3].ignore = 1;
     415             : 
     416             :   /* Now read as long as we have something to poll.  We continue
     417             :      reading even after EOF or error on stdout so that we get the
     418             :      other error messages or remaining outut.  */
     419        1290 :   while (! (fds[1].ignore && fds[2].ignore))
     420             :     {
     421        1244 :       count = es_poll (fds, DIM(fds), -1);
     422        1244 :       if (count == -1)
     423             :         {
     424           0 :           err = my_error_from_syserror ();
     425           0 :           log_error ("error polling '%s': %s\n", pgmname, gpg_strerror (err));
     426           0 :           goto leave;
     427             :         }
     428        1244 :       if (!count)
     429             :         {
     430           0 :           log_debug ("unexpected timeout while polling '%s'\n", pgmname);
     431           0 :           break;
     432             :         }
     433             : 
     434        1244 :       if (fds[0].got_write)
     435             :         {
     436         575 :           err = copy_buffer_do_copy (cpbuf_in, input, fds[0].stream);
     437         575 :           if (err)
     438             :             {
     439           0 :               log_error ("error feeding data to '%s': %s\n",
     440             :                          pgmname, gpg_strerror (err));
     441           0 :               goto leave;
     442             :             }
     443             : 
     444         575 :           if (es_feof (input))
     445             :             {
     446          23 :               err = copy_buffer_flush (cpbuf_in, fds[0].stream);
     447          23 :               if (err)
     448             :                 {
     449           0 :                   log_error ("error feeding data to '%s': %s\n",
     450             :                              pgmname, gpg_strerror (err));
     451           0 :                   goto leave;
     452             :                 }
     453             : 
     454          23 :               fds[0].ignore = 1; /* ready.  */
     455          23 :               es_fclose (infp); infp = NULL;
     456             :             }
     457             :         }
     458             : 
     459        1244 :       if (fds[3].got_write)
     460             :         {
     461           0 :           log_assert (inextra);
     462           0 :           err = copy_buffer_do_copy (cpbuf_extra, inextra, fds[3].stream);
     463           0 :           if (err)
     464             :             {
     465           0 :               log_error ("error feeding data to '%s': %s\n",
     466             :                          pgmname, gpg_strerror (err));
     467           0 :               goto leave;
     468             :             }
     469             : 
     470           0 :           if (es_feof (inextra))
     471             :             {
     472           0 :               err = copy_buffer_flush (cpbuf_extra, fds[3].stream);
     473           0 :               if (err)
     474             :                 {
     475           0 :                   log_error ("error feeding data to '%s': %s\n",
     476             :                              pgmname, gpg_strerror (err));
     477           0 :                   goto leave;
     478             :                 }
     479             : 
     480           0 :               fds[3].ignore = 1; /* ready.  */
     481           0 :               es_fclose (extrafp); extrafp = NULL;
     482             :             }
     483             :         }
     484             : 
     485        1244 :       if (fds[1].got_read)
     486             :         {
     487         635 :           err = copy_buffer_do_copy (cpbuf_out, fds[1].stream, output);
     488         635 :           if (err)
     489             :             {
     490           0 :               log_error ("error reading data from '%s': %s\n",
     491             :                          pgmname, gpg_strerror (err));
     492           0 :               goto leave;
     493             :             }
     494             : 
     495         635 :           if (es_feof (fds[1].stream))
     496             :             {
     497          23 :               err = copy_buffer_flush (cpbuf_out, output);
     498          23 :               if (err)
     499             :                 {
     500           0 :                   log_error ("error reading data from '%s': %s\n",
     501             :                              pgmname, gpg_strerror (err));
     502           0 :                   goto leave;
     503             :                 }
     504             : 
     505          23 :               fds[1].ignore = 1; /* ready.  */
     506             :             }
     507             :         }
     508             : 
     509        1244 :       if (fds[2].got_read)
     510          57 :         read_and_log_stderr (&fderrstate, fds + 2);
     511             :     }
     512             : 
     513          23 :   read_and_log_stderr (&fderrstate, NULL); /* Flush.  */
     514          23 :   es_fclose (infp); infp = NULL;
     515          23 :   es_fclose (extrafp); extrafp = NULL;
     516          23 :   es_fclose (outfp); outfp = NULL;
     517          23 :   es_fclose (errfp); errfp = NULL;
     518             : 
     519          23 :   err = gnupg_wait_process (pgmname, pid, 1, NULL);
     520          23 :   pid = (pid_t)(-1);
     521             : 
     522             :  leave:
     523          23 :   if (err && pid != (pid_t) -1)
     524           0 :     gnupg_kill_process (pid);
     525             : 
     526          23 :   es_fclose (infp);
     527          23 :   es_fclose (extrafp);
     528          23 :   es_fclose (outfp);
     529          23 :   es_fclose (errfp);
     530          23 :   if (pid != (pid_t)(-1))
     531           0 :     gnupg_wait_process (pgmname, pid, 1, NULL);
     532          23 :   gnupg_release_process (pid);
     533             : 
     534          23 :   copy_buffer_shred (cpbuf_in);
     535          23 :   xfree (cpbuf_in);
     536          23 :   copy_buffer_shred (cpbuf_out);
     537          23 :   xfree (cpbuf_out);
     538          23 :   copy_buffer_shred (cpbuf_extra);
     539          23 :   xfree (cpbuf_extra);
     540          23 :   xfree (fderrstate.buffer);
     541          23 :   return err;
     542             : }
     543             : 
     544             : 
     545             : /* A dummy free function to pass to 'es_mopen'.  */
     546             : static void
     547           5 : nop_free (void *ptr)
     548             : {
     549             :   (void) ptr;
     550           5 : }
     551             : 
     552             : /* Run the program PGMNAME with the command line arguments given in
     553             :    the NULL terminates array ARGV.  If INPUT_STRING is not NULL it
     554             :    will be fed to stdin of the process.  stderr is logged using
     555             :    log_info and the process' stdout is returned in a newly malloced
     556             :    buffer RESULT with the length stored at RESULTLEN if not given as
     557             :    NULL.  A hidden Nul is appended to the output.  On error NULL is
     558             :    stored at RESULT, a diagnostic is printed, and an error code
     559             :    returned.  */
     560             : gpg_error_t
     561           5 : gnupg_exec_tool (const char *pgmname, const char *argv[],
     562             :                  const char *input_string,
     563             :                  char **result, size_t *resultlen)
     564             : {
     565             :   gpg_error_t err;
     566           5 :   estream_t input = NULL;
     567             :   estream_t output;
     568             :   size_t len;
     569             :   size_t nread;
     570             : 
     571           5 :   *result = NULL;
     572           5 :   if (resultlen)
     573           5 :     *resultlen = 0;
     574             : 
     575           5 :   if (input_string)
     576             :     {
     577           5 :       len = strlen (input_string);
     578           5 :       input = es_mopen ((char *) input_string, len, len,
     579             :                         0 /* don't grow */, NULL, nop_free, "rb");
     580           5 :       if (! input)
     581           0 :         return my_error_from_syserror ();
     582             :     }
     583             : 
     584           5 :   output = es_fopenmem (0, "wb");
     585           5 :   if (! output)
     586             :     {
     587           0 :       err = my_error_from_syserror ();
     588           0 :       goto leave;
     589             :     }
     590             : 
     591           5 :   err = gnupg_exec_tool_stream (pgmname, argv, input, NULL, output, NULL, NULL);
     592           5 :   if (err)
     593           1 :     goto leave;
     594             : 
     595           4 :   len = es_ftello (output);
     596           4 :   err = es_fseek (output, 0, SEEK_SET);
     597           4 :   if (err)
     598           0 :     goto leave;
     599             : 
     600           4 :   *result = xtrymalloc (len + 1);
     601           4 :   if (!*result)
     602             :     {
     603           0 :       err = my_error_from_syserror ();
     604           0 :       goto leave;
     605             :     }
     606             : 
     607           4 :   if (len)
     608             :     {
     609           3 :       err = es_read (output, *result, len, &nread);
     610           3 :       if (err)
     611           0 :         goto leave;
     612           3 :       if (nread != len)
     613           0 :         log_fatal ("%s: short read from memstream\n", __func__);
     614             :     }
     615           4 :   (*result)[len] = 0;
     616             : 
     617           4 :   if (resultlen)
     618           4 :     *resultlen = len;
     619             : 
     620             :  leave:
     621           5 :   es_fclose (input);
     622           5 :   es_fclose (output);
     623           5 :   if (err)
     624             :     {
     625           1 :       xfree (*result);
     626           1 :       *result = NULL;
     627             :     }
     628           5 :   return err;
     629             : }

Generated by: LCOV version 1.11