LCOV - code coverage report
Current view: top level - journal - mmap-cache.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 246 307 80.1 %
Date: 2019-08-22 15:41:25 Functions: 24 26 92.3 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : 
       3             : #include <errno.h>
       4             : #include <stdlib.h>
       5             : #include <sys/mman.h>
       6             : 
       7             : #include "alloc-util.h"
       8             : #include "errno-util.h"
       9             : #include "fd-util.h"
      10             : #include "hashmap.h"
      11             : #include "list.h"
      12             : #include "log.h"
      13             : #include "macro.h"
      14             : #include "memory-util.h"
      15             : #include "mmap-cache.h"
      16             : #include "sigbus.h"
      17             : 
      18             : typedef struct Window Window;
      19             : typedef struct Context Context;
      20             : 
      21             : struct Window {
      22             :         MMapCache *cache;
      23             : 
      24             :         bool invalidated:1;
      25             :         bool keep_always:1;
      26             :         bool in_unused:1;
      27             : 
      28             :         int prot;
      29             :         void *ptr;
      30             :         uint64_t offset;
      31             :         size_t size;
      32             : 
      33             :         MMapFileDescriptor *fd;
      34             : 
      35             :         LIST_FIELDS(Window, by_fd);
      36             :         LIST_FIELDS(Window, unused);
      37             : 
      38             :         LIST_HEAD(Context, contexts);
      39             : };
      40             : 
      41             : struct Context {
      42             :         MMapCache *cache;
      43             :         unsigned id;
      44             :         Window *window;
      45             : 
      46             :         LIST_FIELDS(Context, by_window);
      47             : };
      48             : 
      49             : struct MMapFileDescriptor {
      50             :         MMapCache *cache;
      51             :         int fd;
      52             :         bool sigbus;
      53             :         LIST_HEAD(Window, windows);
      54             : };
      55             : 
      56             : struct MMapCache {
      57             :         unsigned n_ref;
      58             :         unsigned n_windows;
      59             : 
      60             :         unsigned n_hit, n_missed;
      61             : 
      62             :         Hashmap *fds;
      63             :         Context *contexts[MMAP_CACHE_MAX_CONTEXTS];
      64             : 
      65             :         LIST_HEAD(Window, unused);
      66             :         Window *last_unused;
      67             : };
      68             : 
      69             : #define WINDOWS_MIN 64
      70             : 
      71             : #if ENABLE_DEBUG_MMAP_CACHE
      72             : /* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
      73             : # define WINDOW_SIZE (page_size())
      74             : #else
      75             : # define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
      76             : #endif
      77             : 
      78         238 : MMapCache* mmap_cache_new(void) {
      79             :         MMapCache *m;
      80             : 
      81         238 :         m = new0(MMapCache, 1);
      82         238 :         if (!m)
      83           0 :                 return NULL;
      84             : 
      85         238 :         m->n_ref = 1;
      86         238 :         return m;
      87             : }
      88             : 
      89       10359 : static void window_unlink(Window *w) {
      90             :         Context *c;
      91             : 
      92       10359 :         assert(w);
      93             : 
      94       10359 :         if (w->ptr)
      95       10359 :                 munmap(w->ptr, w->size);
      96             : 
      97       10359 :         if (w->fd)
      98       10359 :                 LIST_REMOVE(by_fd, w->fd->windows, w);
      99             : 
     100       10359 :         if (w->in_unused) {
     101         485 :                 if (w->cache->last_unused == w)
     102         483 :                         w->cache->last_unused = w->unused_prev;
     103             : 
     104         485 :                 LIST_REMOVE(unused, w->cache->unused, w);
     105             :         }
     106             : 
     107       10690 :         LIST_FOREACH(by_window, c, w->contexts) {
     108         331 :                 assert(c->window == w);
     109         331 :                 c->window = NULL;
     110             :         }
     111       10359 : }
     112             : 
     113           0 : static void window_invalidate(Window *w) {
     114           0 :         assert(w);
     115             : 
     116           0 :         if (w->invalidated)
     117           0 :                 return;
     118             : 
     119             :         /* Replace the window with anonymous pages. This is useful
     120             :          * when we hit a SIGBUS and want to make sure the file cannot
     121             :          * trigger any further SIGBUS, possibly overrunning the sigbus
     122             :          * queue. */
     123             : 
     124           0 :         assert_se(mmap(w->ptr, w->size, w->prot, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == w->ptr);
     125           0 :         w->invalidated = true;
     126             : }
     127             : 
     128        9881 : static void window_free(Window *w) {
     129        9881 :         assert(w);
     130             : 
     131        9881 :         window_unlink(w);
     132        9881 :         w->cache->n_windows--;
     133        9881 :         free(w);
     134        9881 : }
     135             : 
     136     3071711 : _pure_ static bool window_matches(Window *w, int prot, uint64_t offset, size_t size) {
     137     3071711 :         assert(w);
     138     3071711 :         assert(size > 0);
     139             : 
     140             :         return
     141     6143422 :                 prot == w->prot &&
     142     6077838 :                 offset >= w->offset &&
     143     3006127 :                 offset + size <= w->offset + w->size;
     144             : }
     145             : 
     146     2983393 : _pure_ static bool window_matches_fd(Window *w, MMapFileDescriptor *f, int prot, uint64_t offset, size_t size) {
     147     2983393 :         assert(w);
     148     2983393 :         assert(f);
     149             : 
     150             :         return
     151     5966786 :                 w->fd &&
     152     5942527 :                 f->fd == w->fd->fd &&
     153     2959134 :                 window_matches(w, prot, offset, size);
     154             : }
     155             : 
     156       10359 : static Window *window_add(MMapCache *m, MMapFileDescriptor *f, int prot, bool keep_always, uint64_t offset, size_t size, void *ptr) {
     157             :         Window *w;
     158             : 
     159       10359 :         assert(m);
     160       10359 :         assert(f);
     161             : 
     162       10359 :         if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
     163             : 
     164             :                 /* Allocate a new window */
     165        9881 :                 w = new0(Window, 1);
     166        9881 :                 if (!w)
     167           0 :                         return NULL;
     168        9881 :                 m->n_windows++;
     169             :         } else {
     170             : 
     171             :                 /* Reuse an existing one */
     172         478 :                 w = m->last_unused;
     173         478 :                 window_unlink(w);
     174         478 :                 zero(*w);
     175             :         }
     176             : 
     177       10359 :         w->cache = m;
     178       10359 :         w->fd = f;
     179       10359 :         w->prot = prot;
     180       10359 :         w->keep_always = keep_always;
     181       10359 :         w->offset = offset;
     182       10359 :         w->size = size;
     183       10359 :         w->ptr = ptr;
     184             : 
     185       10359 :         LIST_PREPEND(by_fd, f->windows, w);
     186             : 
     187       10359 :         return w;
     188             : }
     189             : 
     190      176364 : static void context_detach_window(Context *c) {
     191             :         Window *w;
     192             : 
     193      176364 :         assert(c);
     194             : 
     195      176364 :         if (!c->window)
     196       88513 :                 return;
     197             : 
     198       87851 :         w = TAKE_PTR(c->window);
     199       87851 :         LIST_REMOVE(by_window, w->contexts, c);
     200             : 
     201       87851 :         if (!w->contexts && !w->keep_always) {
     202             :                 /* Not used anymore? */
     203             : #if ENABLE_DEBUG_MMAP_CACHE
     204             :                 /* Unmap unused windows immediately to expose use-after-unmap
     205             :                  * by SIGSEGV. */
     206             :                 window_free(w);
     207             : #else
     208       13300 :                 LIST_PREPEND(unused, c->cache->unused, w);
     209       13300 :                 if (!c->cache->last_unused)
     210         809 :                         c->cache->last_unused = w;
     211             : 
     212       13300 :                 w->in_unused = true;
     213             : #endif
     214             :         }
     215             : }
     216             : 
     217       88182 : static void context_attach_window(Context *c, Window *w) {
     218       88182 :         assert(c);
     219       88182 :         assert(w);
     220             : 
     221       88182 :         if (c->window == w)
     222           0 :                 return;
     223             : 
     224       88182 :         context_detach_window(c);
     225             : 
     226       88182 :         if (w->in_unused) {
     227             :                 /* Used again? */
     228       12815 :                 LIST_REMOVE(unused, c->cache->unused, w);
     229       12815 :                 if (c->cache->last_unused == w)
     230         347 :                         c->cache->last_unused = w->unused_prev;
     231             : 
     232       12815 :                 w->in_unused = false;
     233             :         }
     234             : 
     235       88182 :         c->window = w;
     236       88182 :         LIST_PREPEND(by_window, w->contexts, c);
     237             : }
     238             : 
     239       88182 : static Context *context_add(MMapCache *m, unsigned id) {
     240             :         Context *c;
     241             : 
     242       88182 :         assert(m);
     243             : 
     244       88182 :         c = m->contexts[id];
     245       88182 :         if (c)
     246       87851 :                 return c;
     247             : 
     248         331 :         c = new0(Context, 1);
     249         331 :         if (!c)
     250           0 :                 return NULL;
     251             : 
     252         331 :         c->cache = m;
     253         331 :         c->id = id;
     254             : 
     255         331 :         assert(!m->contexts[id]);
     256         331 :         m->contexts[id] = c;
     257             : 
     258         331 :         return c;
     259             : }
     260             : 
     261         331 : static void context_free(Context *c) {
     262         331 :         assert(c);
     263             : 
     264         331 :         context_detach_window(c);
     265             : 
     266         331 :         if (c->cache) {
     267         331 :                 assert(c->cache->contexts[c->id] == c);
     268         331 :                 c->cache->contexts[c->id] = NULL;
     269             :         }
     270             : 
     271         331 :         free(c);
     272         331 : }
     273             : 
     274         238 : static MMapCache *mmap_cache_free(MMapCache *m) {
     275             :         int i;
     276             : 
     277         238 :         assert(m);
     278             : 
     279        2380 :         for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++)
     280        2142 :                 if (m->contexts[i])
     281         331 :                         context_free(m->contexts[i]);
     282             : 
     283         238 :         hashmap_free(m->fds);
     284             : 
     285         238 :         while (m->unused)
     286           0 :                 window_free(m->unused);
     287             : 
     288         238 :         return mfree(m);
     289             : }
     290             : 
     291       19874 : DEFINE_TRIVIAL_REF_UNREF_FUNC(MMapCache, mmap_cache, mmap_cache_free);
     292             : 
     293           0 : static int make_room(MMapCache *m) {
     294           0 :         assert(m);
     295             : 
     296           0 :         if (!m->last_unused)
     297           0 :                 return 0;
     298             : 
     299           0 :         window_free(m->last_unused);
     300           0 :         return 1;
     301             : }
     302             : 
     303     2983724 : static int try_context(
     304             :                 MMapCache *m,
     305             :                 MMapFileDescriptor *f,
     306             :                 int prot,
     307             :                 unsigned context,
     308             :                 bool keep_always,
     309             :                 uint64_t offset,
     310             :                 size_t size,
     311             :                 void **ret,
     312             :                 size_t *ret_size) {
     313             : 
     314             :         Context *c;
     315             : 
     316     2983724 :         assert(m);
     317     2983724 :         assert(m->n_ref > 0);
     318     2983724 :         assert(f);
     319     2983724 :         assert(size > 0);
     320     2983724 :         assert(ret);
     321             : 
     322     2983724 :         c = m->contexts[context];
     323     2983724 :         if (!c)
     324         331 :                 return 0;
     325             : 
     326     2983393 :         assert(c->id == context);
     327             : 
     328     2983393 :         if (!c->window)
     329           0 :                 return 0;
     330             : 
     331     2983393 :         if (!window_matches_fd(c->window, f, prot, offset, size)) {
     332             : 
     333             :                 /* Drop the reference to the window, since it's unnecessary now */
     334       87851 :                 context_detach_window(c);
     335       87851 :                 return 0;
     336             :         }
     337             : 
     338     2895542 :         if (c->window->fd->sigbus)
     339           0 :                 return -EIO;
     340             : 
     341     2895542 :         c->window->keep_always = c->window->keep_always || keep_always;
     342             : 
     343     2895542 :         *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
     344     2895542 :         if (ret_size)
     345     2684603 :                 *ret_size = c->window->size - (offset - c->window->offset);
     346             : 
     347     2895542 :         return 1;
     348             : }
     349             : 
     350       88182 : static int find_mmap(
     351             :                 MMapCache *m,
     352             :                 MMapFileDescriptor *f,
     353             :                 int prot,
     354             :                 unsigned context,
     355             :                 bool keep_always,
     356             :                 uint64_t offset,
     357             :                 size_t size,
     358             :                 void **ret,
     359             :                 size_t *ret_size) {
     360             : 
     361             :         Window *w;
     362             :         Context *c;
     363             : 
     364       88182 :         assert(m);
     365       88182 :         assert(m->n_ref > 0);
     366       88182 :         assert(f);
     367       88182 :         assert(size > 0);
     368             : 
     369       88182 :         if (f->sigbus)
     370           0 :                 return -EIO;
     371             : 
     372      122936 :         LIST_FOREACH(by_fd, w, f->windows)
     373      112577 :                 if (window_matches(w, prot, offset, size))
     374       77823 :                         break;
     375             : 
     376       88182 :         if (!w)
     377       10359 :                 return 0;
     378             : 
     379       77823 :         c = context_add(m, context);
     380       77823 :         if (!c)
     381           0 :                 return -ENOMEM;
     382             : 
     383       77823 :         context_attach_window(c, w);
     384       77823 :         w->keep_always = w->keep_always || keep_always;
     385             : 
     386       77823 :         *ret = (uint8_t*) w->ptr + (offset - w->offset);
     387       77823 :         if (ret_size)
     388       62462 :                 *ret_size = w->size - (offset - w->offset);
     389             : 
     390       77823 :         return 1;
     391             : }
     392             : 
     393       10359 : static int mmap_try_harder(MMapCache *m, void *addr, MMapFileDescriptor *f, int prot, int flags, uint64_t offset, size_t size, void **res) {
     394             :         void *ptr;
     395             : 
     396       10359 :         assert(m);
     397       10359 :         assert(f);
     398       10359 :         assert(res);
     399             : 
     400           0 :         for (;;) {
     401             :                 int r;
     402             : 
     403       10359 :                 ptr = mmap(addr, size, prot, flags, f->fd, offset);
     404       10359 :                 if (ptr != MAP_FAILED)
     405       10359 :                         break;
     406           0 :                 if (errno != ENOMEM)
     407           0 :                         return negative_errno();
     408             : 
     409           0 :                 r = make_room(m);
     410           0 :                 if (r < 0)
     411           0 :                         return r;
     412           0 :                 if (r == 0)
     413           0 :                         return -ENOMEM;
     414             :         }
     415             : 
     416       10359 :         *res = ptr;
     417       10359 :         return 0;
     418             : }
     419             : 
     420       10359 : static int add_mmap(
     421             :                 MMapCache *m,
     422             :                 MMapFileDescriptor *f,
     423             :                 int prot,
     424             :                 unsigned context,
     425             :                 bool keep_always,
     426             :                 uint64_t offset,
     427             :                 size_t size,
     428             :                 struct stat *st,
     429             :                 void **ret,
     430             :                 size_t *ret_size) {
     431             : 
     432             :         uint64_t woffset, wsize;
     433             :         Context *c;
     434             :         Window *w;
     435             :         void *d;
     436             :         int r;
     437             : 
     438       10359 :         assert(m);
     439       10359 :         assert(m->n_ref > 0);
     440       10359 :         assert(f);
     441       10359 :         assert(size > 0);
     442       10359 :         assert(ret);
     443             : 
     444       10359 :         woffset = offset & ~((uint64_t) page_size() - 1ULL);
     445       10359 :         wsize = size + (offset - woffset);
     446       10359 :         wsize = PAGE_ALIGN(wsize);
     447             : 
     448       10359 :         if (wsize < WINDOW_SIZE) {
     449             :                 uint64_t delta;
     450             : 
     451       10359 :                 delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
     452             : 
     453       10359 :                 if (delta > offset)
     454        9872 :                         woffset = 0;
     455             :                 else
     456         487 :                         woffset -= delta;
     457             : 
     458       10359 :                 wsize = WINDOW_SIZE;
     459             :         }
     460             : 
     461       10359 :         if (st) {
     462             :                 /* Memory maps that are larger then the files
     463             :                    underneath have undefined behavior. Hence, clamp
     464             :                    things to the file size if we know it */
     465             : 
     466       10354 :                 if (woffset >= (uint64_t) st->st_size)
     467           0 :                         return -EADDRNOTAVAIL;
     468             : 
     469       10354 :                 if (woffset + wsize > (uint64_t) st->st_size)
     470          51 :                         wsize = PAGE_ALIGN(st->st_size - woffset);
     471             :         }
     472             : 
     473       10359 :         r = mmap_try_harder(m, NULL, f, prot, MAP_SHARED, woffset, wsize, &d);
     474       10359 :         if (r < 0)
     475           0 :                 return r;
     476             : 
     477       10359 :         c = context_add(m, context);
     478       10359 :         if (!c)
     479           0 :                 goto outofmem;
     480             : 
     481       10359 :         w = window_add(m, f, prot, keep_always, woffset, wsize, d);
     482       10359 :         if (!w)
     483           0 :                 goto outofmem;
     484             : 
     485       10359 :         context_attach_window(c, w);
     486             : 
     487       10359 :         *ret = (uint8_t*) w->ptr + (offset - w->offset);
     488       10359 :         if (ret_size)
     489         484 :                 *ret_size = w->size - (offset - w->offset);
     490             : 
     491       10359 :         return 1;
     492             : 
     493           0 : outofmem:
     494           0 :         (void) munmap(d, wsize);
     495           0 :         return -ENOMEM;
     496             : }
     497             : 
     498     2983724 : int mmap_cache_get(
     499             :                 MMapCache *m,
     500             :                 MMapFileDescriptor *f,
     501             :                 int prot,
     502             :                 unsigned context,
     503             :                 bool keep_always,
     504             :                 uint64_t offset,
     505             :                 size_t size,
     506             :                 struct stat *st,
     507             :                 void **ret,
     508             :                 size_t *ret_size) {
     509             : 
     510             :         int r;
     511             : 
     512     2983724 :         assert(m);
     513     2983724 :         assert(m->n_ref > 0);
     514     2983724 :         assert(f);
     515     2983724 :         assert(size > 0);
     516     2983724 :         assert(ret);
     517     2983724 :         assert(context < MMAP_CACHE_MAX_CONTEXTS);
     518             : 
     519             :         /* Check whether the current context is the right one already */
     520     2983724 :         r = try_context(m, f, prot, context, keep_always, offset, size, ret, ret_size);
     521     2983724 :         if (r != 0) {
     522     2895542 :                 m->n_hit++;
     523     2895542 :                 return r;
     524             :         }
     525             : 
     526             :         /* Search for a matching mmap */
     527       88182 :         r = find_mmap(m, f, prot, context, keep_always, offset, size, ret, ret_size);
     528       88182 :         if (r != 0) {
     529       77823 :                 m->n_hit++;
     530       77823 :                 return r;
     531             :         }
     532             : 
     533       10359 :         m->n_missed++;
     534             : 
     535             :         /* Create a new mmap */
     536       10359 :         return add_mmap(m, f, prot, context, keep_always, offset, size, st, ret, ret_size);
     537             : }
     538             : 
     539         211 : unsigned mmap_cache_get_hit(MMapCache *m) {
     540         211 :         assert(m);
     541             : 
     542         211 :         return m->n_hit;
     543             : }
     544             : 
     545         211 : unsigned mmap_cache_get_missed(MMapCache *m) {
     546         211 :         assert(m);
     547             : 
     548         211 :         return m->n_missed;
     549             : }
     550             : 
     551      138211 : static void mmap_cache_process_sigbus(MMapCache *m) {
     552      138211 :         bool found = false;
     553             :         MMapFileDescriptor *f;
     554             :         Iterator i;
     555             :         int r;
     556             : 
     557      138211 :         assert(m);
     558             : 
     559             :         /* Iterate through all triggered pages and mark their files as
     560             :          * invalidated */
     561           0 :         for (;;) {
     562             :                 bool ours;
     563             :                 void *addr;
     564             : 
     565      138211 :                 r = sigbus_pop(&addr);
     566      138211 :                 if (_likely_(r == 0))
     567      138211 :                         break;
     568           0 :                 if (r < 0) {
     569           0 :                         log_error_errno(r, "SIGBUS handling failed: %m");
     570           0 :                         abort();
     571             :                 }
     572             : 
     573           0 :                 ours = false;
     574           0 :                 HASHMAP_FOREACH(f, m->fds, i) {
     575             :                         Window *w;
     576             : 
     577           0 :                         LIST_FOREACH(by_fd, w, f->windows) {
     578           0 :                                 if ((uint8_t*) addr >= (uint8_t*) w->ptr &&
     579           0 :                                     (uint8_t*) addr < (uint8_t*) w->ptr + w->size) {
     580           0 :                                         found = ours = f->sigbus = true;
     581           0 :                                         break;
     582             :                                 }
     583             :                         }
     584             : 
     585           0 :                         if (ours)
     586           0 :                                 break;
     587             :                 }
     588             : 
     589             :                 /* Didn't find a matching window, give up */
     590           0 :                 if (!ours) {
     591           0 :                         log_error("Unknown SIGBUS page, aborting.");
     592           0 :                         abort();
     593             :                 }
     594             :         }
     595             : 
     596             :         /* The list of triggered pages is now empty. Now, let's remap
     597             :          * all windows of the triggered file to anonymous maps, so
     598             :          * that no page of the file in question is triggered again, so
     599             :          * that we can be sure not to hit the queue size limit. */
     600      138211 :         if (_likely_(!found))
     601      138211 :                 return;
     602             : 
     603           0 :         HASHMAP_FOREACH(f, m->fds, i) {
     604             :                 Window *w;
     605             : 
     606           0 :                 if (!f->sigbus)
     607           0 :                         continue;
     608             : 
     609           0 :                 LIST_FOREACH(by_fd, w, f->windows)
     610           0 :                         window_invalidate(w);
     611             :         }
     612             : }
     613             : 
     614      128364 : bool mmap_cache_got_sigbus(MMapCache *m, MMapFileDescriptor *f) {
     615      128364 :         assert(m);
     616      128364 :         assert(f);
     617             : 
     618      128364 :         mmap_cache_process_sigbus(m);
     619             : 
     620      128364 :         return f->sigbus;
     621             : }
     622             : 
     623        9847 : MMapFileDescriptor* mmap_cache_add_fd(MMapCache *m, int fd) {
     624             :         MMapFileDescriptor *f;
     625             :         int r;
     626             : 
     627        9847 :         assert(m);
     628        9847 :         assert(fd >= 0);
     629             : 
     630        9847 :         f = hashmap_get(m->fds, FD_TO_PTR(fd));
     631        9847 :         if (f)
     632           0 :                 return f;
     633             : 
     634        9847 :         r = hashmap_ensure_allocated(&m->fds, NULL);
     635        9847 :         if (r < 0)
     636           0 :                 return NULL;
     637             : 
     638        9847 :         f = new0(MMapFileDescriptor, 1);
     639        9847 :         if (!f)
     640           0 :                 return NULL;
     641             : 
     642        9847 :         f->cache = m;
     643        9847 :         f->fd = fd;
     644             : 
     645        9847 :         r = hashmap_put(m->fds, FD_TO_PTR(fd), f);
     646        9847 :         if (r < 0)
     647           0 :                 return mfree(f);
     648             : 
     649        9847 :         return f;
     650             : }
     651             : 
     652        9847 : void mmap_cache_free_fd(MMapCache *m, MMapFileDescriptor *f) {
     653        9847 :         assert(m);
     654        9847 :         assert(f);
     655             : 
     656             :         /* Make sure that any queued SIGBUS are first dispatched, so
     657             :          * that we don't end up with a SIGBUS entry we cannot relate
     658             :          * to any existing memory map */
     659             : 
     660        9847 :         mmap_cache_process_sigbus(m);
     661             : 
     662       19728 :         while (f->windows)
     663        9881 :                 window_free(f->windows);
     664             : 
     665        9847 :         if (f->cache)
     666        9847 :                 assert_se(hashmap_remove(f->cache->fds, FD_TO_PTR(f->fd)));
     667             : 
     668        9847 :         free(f);
     669        9847 : }

Generated by: LCOV version 1.14