LCOV - code coverage report
Current view: top level - util - secmod_common.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 70.9 % 223 158
Test Date: 2026-04-14 15:39:31 Functions: 100.0 % 11 11

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2020, 2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file util/secmod_common.c
      18              :  * @brief Common functions for the exchange security modules
      19              :  * @author Florian Dold <dold@taler.net>
      20              :  */
      21              : #include "taler/platform.h"
      22              : #include "taler/taler_util.h"
      23              : #include "secmod_common.h"
      24              : #include <poll.h>
      25              : #ifdef __linux__
      26              : #include <sys/eventfd.h>
      27              : #endif
      28              : 
      29              : 
      30              : /**
      31              :  * Head of DLL of clients connected to us.
      32              :  */
      33              : struct TES_Client *TES_clients_head;
      34              : 
      35              : /**
      36              :  * Tail of DLL of clients connected to us.
      37              :  */
      38              : struct TES_Client *TES_clients_tail;
      39              : 
      40              : /**
      41              :  * Lock for the client queue.
      42              :  */
      43              : pthread_mutex_t TES_clients_lock;
      44              : 
      45              : /**
      46              :  * Private key of this security module. Used to sign denomination key
      47              :  * announcements.
      48              :  */
      49              : struct TALER_SecurityModulePrivateKeyP TES_smpriv;
      50              : 
      51              : /**
      52              :  * Public key of this security module.
      53              :  */
      54              : struct TALER_SecurityModulePublicKeyP TES_smpub;
      55              : 
      56              : /**
      57              :  * Our listen socket.
      58              :  */
      59              : static struct GNUNET_NETWORK_Handle *unix_sock;
      60              : 
      61              : /**
      62              :  * Path where we are listening.
      63              :  */
      64              : static char *unixpath;
      65              : 
      66              : /**
      67              :  * Task run to accept new inbound connections.
      68              :  */
      69              : static struct GNUNET_SCHEDULER_Task *listen_task;
      70              : 
      71              : /**
      72              :  * Set once we are in shutdown and workers should terminate.
      73              :  */
      74              : static volatile bool in_shutdown;
      75              : 
      76              : 
      77              : enum GNUNET_GenericReturnValue
      78         4945 : TES_transmit_raw (int sock,
      79              :                   size_t end,
      80              :                   const void *pos)
      81              : {
      82         4945 :   size_t off = 0;
      83              : 
      84         4945 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
      85              :               "Sending message of length %u\n",
      86              :               (unsigned int) end);
      87         9877 :   while (off < end)
      88              :   {
      89         4929 :     ssize_t ret = send (sock,
      90              :                         pos,
      91              :                         end - off,
      92              :                         0 /* no flags => blocking! */);
      93              : 
      94         4936 :     if ( (-1 == ret) &&
      95            0 :          ( (EAGAIN == errno) ||
      96            0 :            (EINTR == errno) ) )
      97              :     {
      98            5 :       GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG,
      99              :                            "send");
     100            0 :       continue;
     101              :     }
     102         4931 :     if (-1 == ret)
     103              :     {
     104            0 :       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     105              :                            "send");
     106            0 :       return GNUNET_SYSERR;
     107              :     }
     108         4931 :     if (0 == ret)
     109              :     {
     110            0 :       GNUNET_break (0);
     111            0 :       return GNUNET_SYSERR;
     112              :     }
     113         4931 :     off += ret;
     114         4931 :     pos += ret;
     115              :   }
     116         4948 :   return GNUNET_OK;
     117              : }
     118              : 
     119              : 
     120              : enum GNUNET_GenericReturnValue
     121         4885 : TES_transmit (int sock,
     122              :               const struct GNUNET_MessageHeader *hdr)
     123              : {
     124         4885 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     125              :               "Sending message of type %u and length %u\n",
     126              :               (unsigned int) ntohs (hdr->type),
     127              :               (unsigned int) ntohs (hdr->size));
     128         9772 :   return TES_transmit_raw (sock,
     129         4887 :                            ntohs (hdr->size),
     130              :                            hdr);
     131              : }
     132              : 
     133              : 
     134              : struct GNUNET_NETWORK_Handle *
     135           54 : TES_open_socket (const char *my_unixpath)
     136              : {
     137              :   int sock;
     138              :   mode_t old_umask;
     139           54 :   struct GNUNET_NETWORK_Handle *ret = NULL;
     140              : 
     141              :   /* Change permissions so that group read/writes are allowed.
     142              :    * We need this for multi-user exchange deployment with privilege
     143              :    * separation, where taler-exchange-httpd is part of a group
     144              :    * that allows it to talk to secmod.
     145              :    */
     146           54 :   old_umask = umask (S_IROTH | S_IWOTH | S_IXOTH);
     147              : 
     148           54 :   sock = socket (PF_UNIX,
     149              :                  SOCK_STREAM,
     150              :                  0);
     151           54 :   if (-1 == sock)
     152              :   {
     153            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     154              :                          "socket");
     155            0 :     goto cleanup;
     156              :   }
     157              :   {
     158              :     struct sockaddr_un un;
     159              : 
     160           54 :     if (GNUNET_OK !=
     161           54 :         GNUNET_DISK_directory_create_for_file (my_unixpath))
     162              :     {
     163            0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     164              :                                 "mkdir(dirname)",
     165              :                                 my_unixpath);
     166              :     }
     167           54 :     if (0 != unlink (my_unixpath))
     168              :     {
     169           54 :       if (ENOENT != errno)
     170            0 :         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     171              :                                   "unlink",
     172              :                                   my_unixpath);
     173              :     }
     174           54 :     memset (&un,
     175              :             0,
     176              :             sizeof (un));
     177           54 :     un.sun_family = AF_UNIX;
     178           54 :     strncpy (un.sun_path,
     179              :              my_unixpath,
     180              :              sizeof (un.sun_path) - 1);
     181           54 :     if (0 != bind (sock,
     182              :                    (const struct sockaddr *) &un,
     183              :                    sizeof (un)))
     184              :     {
     185            0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     186              :                                 "bind",
     187              :                                 my_unixpath);
     188            0 :       GNUNET_break (0 == close (sock));
     189            0 :       goto cleanup;
     190              :     }
     191           54 :     ret = GNUNET_NETWORK_socket_box_native (sock);
     192           54 :     if (GNUNET_OK !=
     193           54 :         GNUNET_NETWORK_socket_listen (ret,
     194              :                                       512))
     195              :     {
     196            0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     197              :                                 "listen",
     198              :                                 my_unixpath);
     199            0 :       GNUNET_break (GNUNET_OK ==
     200              :                     GNUNET_NETWORK_socket_close (ret));
     201            0 :       ret = NULL;
     202              :     }
     203              :   }
     204           54 : cleanup:
     205           54 :   (void) umask (old_umask);
     206           54 :   return ret;
     207              : }
     208              : 
     209              : 
     210              : void
     211          101 : TES_wake_clients (void)
     212              : {
     213          101 :   uint64_t num = 1;
     214              : 
     215          101 :   GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
     216          101 :   for (struct TES_Client *client = TES_clients_head;
     217          188 :        NULL != client;
     218           87 :        client = client->next)
     219              :   {
     220              : #ifdef __linux__
     221           87 :     if (-1 == client->esock)
     222            0 :       continue;
     223           87 :     GNUNET_assert (sizeof (num) ==
     224              :                    write (client->esock,
     225              :                           &num,
     226              :                           sizeof (num)));
     227              : #else
     228              :     if (-1 == client->esock_in)
     229              :       continue;
     230              :     GNUNET_assert (sizeof (num) ==
     231              :                    write (client->esock_in,
     232              :                           &num,
     233              :                           sizeof (num)));
     234              : #endif
     235              :   }
     236          101 :   GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
     237          101 : }
     238              : 
     239              : 
     240              : enum GNUNET_GenericReturnValue
     241         3270 : TES_read_work (void *cls,
     242              :                TES_MessageDispatch dispatch)
     243              : {
     244         3270 :   struct TES_Client *client = cls;
     245         3270 :   char *buf = client->iobuf;
     246         3270 :   size_t off = 0;
     247         3270 :   uint16_t msize = 0;
     248         3270 :   const struct GNUNET_MessageHeader *hdr = NULL;
     249              :   enum GNUNET_GenericReturnValue ret;
     250              : 
     251              :   do
     252              :   {
     253              :     ssize_t recv_size;
     254              : 
     255         3270 :     recv_size = recv (client->csock,
     256         3270 :                       &buf[off],
     257              :                       sizeof (client->iobuf) - off,
     258              :                       0);
     259         3284 :     if (-1 == recv_size)
     260              :     {
     261            1 :       if ( (0 == off) &&
     262            0 :            (EAGAIN == errno) )
     263            0 :         return GNUNET_NO;
     264            1 :       if ( (EINTR == errno) ||
     265            0 :            (EAGAIN == errno) )
     266              :       {
     267            1 :         GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG,
     268              :                              "recv");
     269            0 :         continue;
     270              :       }
     271            0 :       if (ECONNRESET != errno)
     272            0 :         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     273              :                              "recv");
     274            0 :       return GNUNET_SYSERR;
     275              :     }
     276         3283 :     if (0 == recv_size)
     277              :     {
     278              :       /* regular disconnect? */
     279           30 :       GNUNET_break_op (0 == off);
     280           30 :       return GNUNET_SYSERR;
     281              :     }
     282         3253 :     off += recv_size;
     283         3253 : more:
     284         3253 :     if (off < sizeof (struct GNUNET_MessageHeader))
     285            0 :       continue;
     286         3253 :     hdr = (const struct GNUNET_MessageHeader *) buf;
     287         3253 :     msize = ntohs (hdr->size);
     288              : #if 0
     289              :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     290              :                 "Received message of type %u with %u bytes\n",
     291              :                 (unsigned int) ntohs (hdr->type),
     292              :                 (unsigned int) msize);
     293              : #endif
     294         3253 :     if (msize < sizeof (struct GNUNET_MessageHeader))
     295              :     {
     296            0 :       GNUNET_break_op (0);
     297            0 :       return GNUNET_SYSERR;
     298              :     }
     299         3253 :   } while (off < msize);
     300              : 
     301         3253 :   ret = dispatch (client,
     302              :                   hdr);
     303         3240 :   if ( (GNUNET_OK != ret) ||
     304         3240 :        (off == msize) )
     305         3240 :     return ret;
     306            0 :   memmove (buf,
     307            0 :            &buf[msize],
     308              :            off - msize);
     309            0 :   off -= msize;
     310            0 :   goto more;
     311              : }
     312              : 
     313              : 
     314              : bool
     315         3329 : TES_await_ready (struct TES_Client *client)
     316              : {
     317              :   /* wait for reply with 1s timeout */
     318         3329 :   struct pollfd pfds[] = {
     319              :     {
     320         3329 :       .fd = client->csock,
     321              :       .events = POLLIN
     322              :     },
     323              :     {
     324              : #ifdef __linux__
     325         3329 :       .fd = client->esock,
     326              : #else
     327              :       .fd = client->esock_out,
     328              : #endif
     329              :       .events = POLLIN
     330              :     },
     331              :   };
     332              :   int ret;
     333              : 
     334         3329 :   ret = poll (pfds,
     335              :               2,
     336              :               -1);
     337         3344 :   if ( (-1 == ret) &&
     338            0 :        (EINTR != errno) )
     339            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     340              :                          "poll");
     341         9929 :   for (int i = 0; i<2; i++)
     342              :   {
     343         6654 :     if (
     344              : #ifdef __linux__
     345         6654 :       (pfds[i].fd == client->esock) &&
     346              : #else
     347              :       (pfds[i].fd == client->esock_out) &&
     348              : #endif
     349         3332 :       (POLLIN == pfds[i].revents) )
     350              :     {
     351              :       uint64_t num;
     352              : 
     353              : #ifdef __linux__
     354           60 :       GNUNET_assert (sizeof (num) ==
     355              :                      read (client->esock,
     356              :                            &num,
     357              :                            sizeof (num)));
     358              : #else
     359              :       GNUNET_assert (sizeof (num) ==
     360              :                      read (client->esock_out,
     361              :                            &num,
     362              :                            sizeof (num)));
     363              : #endif
     364           60 :       return true;
     365              :     }
     366              :   }
     367         3275 :   return false;
     368              : }
     369              : 
     370              : 
     371              : /**
     372              :  * Main function of a worker thread that signs.
     373              :  *
     374              :  * @param cls the client we are working on
     375              :  * @return NULL
     376              :  */
     377              : static void *
     378           78 : sign_worker (void *cls)
     379              : {
     380           78 :   struct TES_Client *client = cls;
     381              : 
     382           78 :   if (GNUNET_OK !=
     383           78 :       client->cb.init (client))
     384              :   {
     385            0 :     GNUNET_break (0);
     386            0 :     return NULL;
     387              :   }
     388         3376 :   while (! in_shutdown)
     389              :   {
     390         3328 :     if (TES_await_ready (client))
     391              :     {
     392           60 :       if (GNUNET_OK !=
     393           60 :           client->cb.updater (client))
     394            0 :         break;
     395              :     }
     396              :     else
     397              :     {
     398         3268 :       if (GNUNET_SYSERR ==
     399         3271 :           TES_read_work (client,
     400              :                          client->cb.dispatch))
     401           30 :         break;
     402              :     }
     403              :   }
     404           78 :   GNUNET_break (0 == close (client->csock));
     405           78 :   client->csock = -1;
     406           78 :   return NULL;
     407              : }
     408              : 
     409              : 
     410              : /**
     411              :  * Clean up @a pos, joining the thread and closing the
     412              :  * file descriptors.
     413              :  *
     414              :  * @param[in] pos client to clean up
     415              :  */
     416              : static void
     417           78 : join_client (struct TES_Client *pos)
     418              : {
     419              :   void *rval;
     420              : 
     421           78 :   GNUNET_CONTAINER_DLL_remove (TES_clients_head,
     422              :                                TES_clients_tail,
     423              :                                pos);
     424           78 :   GNUNET_break (0 ==
     425              :                 pthread_join (pos->worker,
     426              :                               &rval));
     427              : #ifdef __linux__
     428           78 :   GNUNET_break (0 == close (pos->esock));
     429           78 :   pos->esock = -1;
     430              : #else
     431              :   GNUNET_break (0 == close (pos->esock_in));
     432              :   pos->esock_in = -1;
     433              :   GNUNET_break (0 == close (pos->esock_out));
     434              :   pos->esock_out = -1;
     435              : #endif
     436           78 :   GNUNET_free (pos);
     437           78 : }
     438              : 
     439              : 
     440              : /**
     441              :  * Task that listens for incoming clients.
     442              :  *
     443              :  * @param cls a `struct TES_Callbacks`
     444              :  */
     445              : static void
     446           78 : listen_job (void *cls)
     447              : {
     448           78 :   const struct TES_Callbacks *cb = cls;
     449              :   int s;
     450              : #ifdef __linux__
     451              :   int e;
     452              : #else
     453              :   int e[2];
     454              : #endif
     455              :   struct sockaddr_storage sa;
     456           78 :   socklen_t sa_len = sizeof (sa);
     457              : 
     458           78 :   listen_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
     459              :                                                unix_sock,
     460              :                                                &listen_job,
     461              :                                                cls);
     462           78 :   s = accept (GNUNET_NETWORK_get_fd (unix_sock),
     463              :               (struct sockaddr *) &sa,
     464              :               &sa_len);
     465           78 :   if (-1 == s)
     466              :   {
     467            0 :     bool st = ( (ENFILE == errno) ||
     468            0 :                 (EMFILE == errno) );
     469            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     470              :                          "accept");
     471            0 :     if (st)
     472              :     {
     473            0 :       GNUNET_SCHEDULER_cancel (listen_task);
     474            0 :       listen_task = NULL;
     475              :     }
     476            0 :     return;
     477              :   }
     478              : #ifdef __linux__
     479           78 :   e = eventfd (0,
     480              :                EFD_CLOEXEC);
     481           78 :   if (-1 == e)
     482              :   {
     483            0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     484              :                          "eventfd");
     485            0 :     GNUNET_break (0 == close (s));
     486            0 :     return;
     487              :   }
     488              : #else
     489              :   if (0 != pipe (e))
     490              :   {
     491              :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     492              :                          "pipe");
     493              :     GNUNET_break (0 == close (s));
     494              :     return;
     495              :   }
     496              : #endif
     497              :   {
     498              :     struct TES_Client *client;
     499              :     struct TES_Client *nxt;
     500              : 
     501           78 :     client = GNUNET_new (struct TES_Client);
     502           78 :     client->cb = *cb;
     503           78 :     client->csock = s;
     504              : #ifdef __linux__
     505           78 :     client->esock = e;
     506              : #else
     507              :     client->esock_in = e[1];
     508              :     client->esock_out = e[0];
     509              : #endif
     510           78 :     GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
     511           78 :     for (struct TES_Client *pos = TES_clients_head;
     512          165 :          NULL != pos;
     513           87 :          pos = nxt)
     514              :     {
     515           87 :       nxt = pos->next;
     516           87 :       if (-1 == pos->csock)
     517              :       {
     518            3 :         join_client (pos);
     519              :       }
     520              :     }
     521           78 :     GNUNET_CONTAINER_DLL_insert (TES_clients_head,
     522              :                                  TES_clients_tail,
     523              :                                  client);
     524           78 :     GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
     525           78 :     if (0 !=
     526           78 :         pthread_create (&client->worker,
     527              :                         NULL,
     528              :                         &sign_worker,
     529              :                         client))
     530              :     {
     531            0 :       GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     532              :                            "pthread_create");
     533            0 :       GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
     534            0 :       GNUNET_CONTAINER_DLL_remove (TES_clients_head,
     535              :                                    TES_clients_tail,
     536              :                                    client);
     537            0 :       GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
     538            0 :       GNUNET_break (0 == close (client->csock));
     539              : #ifdef __linux__
     540            0 :       GNUNET_break (0 == close (client->esock));
     541              : #else
     542              :       GNUNET_break (0 == close (client->esock_in));
     543              :       GNUNET_break (0 == close (client->esock_out));
     544              : #endif
     545            0 :       GNUNET_free (client);
     546              :     }
     547              :   }
     548              : }
     549              : 
     550              : 
     551              : int
     552           54 : TES_listen_start (const struct GNUNET_CONFIGURATION_Handle *cfg,
     553              :                   const char *section,
     554              :                   const struct TES_Callbacks *cb)
     555              : {
     556              :   {
     557              :     char *pfn;
     558              : 
     559           54 :     if (GNUNET_OK !=
     560           54 :         GNUNET_CONFIGURATION_get_value_filename (cfg,
     561              :                                                  section,
     562              :                                                  "SM_PRIV_KEY",
     563              :                                                  &pfn))
     564              :     {
     565            0 :       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     566              :                                  section,
     567              :                                  "SM_PRIV_KEY");
     568            0 :       return EXIT_NOTCONFIGURED;
     569              :     }
     570           54 :     if (GNUNET_SYSERR ==
     571           54 :         GNUNET_CRYPTO_eddsa_key_from_file (pfn,
     572              :                                            GNUNET_YES,
     573              :                                            &TES_smpriv.eddsa_priv))
     574              :     {
     575            0 :       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
     576              :                                  section,
     577              :                                  "SM_PRIV_KEY",
     578              :                                  "Could not use file to persist private key");
     579            0 :       GNUNET_free (pfn);
     580            0 :       return EXIT_NOPERMISSION;
     581              :     }
     582           54 :     GNUNET_free (pfn);
     583           54 :     GNUNET_CRYPTO_eddsa_key_get_public (&TES_smpriv.eddsa_priv,
     584              :                                         &TES_smpub.eddsa_pub);
     585              :   }
     586              : 
     587           54 :   if (GNUNET_OK !=
     588           54 :       GNUNET_CONFIGURATION_get_value_filename (cfg,
     589              :                                                section,
     590              :                                                "UNIXPATH",
     591              :                                                &unixpath))
     592              :   {
     593            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     594              :                                section,
     595              :                                "UNIXPATH");
     596            0 :     return EXIT_NOTCONFIGURED;
     597              :   }
     598           54 :   GNUNET_assert (NULL != unixpath);
     599           54 :   unix_sock = TES_open_socket (unixpath);
     600           54 :   if (NULL == unix_sock)
     601              :   {
     602            0 :     GNUNET_free (unixpath);
     603            0 :     GNUNET_break (0);
     604            0 :     return EXIT_NOPERMISSION;
     605              :   }
     606              :   /* start job to accept incoming requests on 'sock' */
     607           54 :   listen_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
     608              :                                                unix_sock,
     609              :                                                &listen_job,
     610              :                                                (void *) cb);
     611           54 :   return 0;
     612              : }
     613              : 
     614              : 
     615              : void
     616           54 : TES_listen_stop (void)
     617              : {
     618              :   struct TES_Client *client;
     619              : 
     620           54 :   if (NULL != listen_task)
     621              :   {
     622           54 :     GNUNET_SCHEDULER_cancel (listen_task);
     623           54 :     listen_task = NULL;
     624              :   }
     625           54 :   if (NULL != unix_sock)
     626              :   {
     627           54 :     GNUNET_break (GNUNET_OK ==
     628              :                   GNUNET_NETWORK_socket_close (unix_sock));
     629           54 :     unix_sock = NULL;
     630              :   }
     631           54 :   if (0 != unlink (unixpath))
     632              :   {
     633            0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     634              :                               "unlink",
     635              :                               unixpath);
     636              :   }
     637           54 :   GNUNET_free (unixpath);
     638           54 :   in_shutdown = true;
     639           54 :   TES_wake_clients ();
     640           54 :   GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock));
     641          129 :   while (NULL != (client = TES_clients_head))
     642           75 :     join_client (client);
     643           54 :   GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock));
     644           54 : }
        

Generated by: LCOV version 2.0-1