1 /*------------------------------------------------------------------------------
2  *
3  * Copyright (c) 2011-2021, EURid vzw. All rights reserved.
4  * The YADIFA TM software product is provided under the BSD 3-clause license:
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  *        * Redistributions of source code must retain the above copyright
11  *          notice, this list of conditions and the following disclaimer.
12  *        * Redistributions in binary form must reproduce the above copyright
13  *          notice, this list of conditions and the following disclaimer in the
14  *          documentation and/or other materials provided with the distribution.
15  *        * Neither the name of EURid nor the names of its contributors may be
16  *          used to endorse or promote products derived from this software
17  *          without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  *------------------------------------------------------------------------------
32  *
33  */
34 
35 /** @defgroup dnscoretools Generic Tools
36  *  @ingroup dnscore
37  *  @brief
38  *
39  * @{
40  */
41 
42 #include "dnscore/dnscore-config.h"
43 #include <stdlib.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <fcntl.h>
47 #include <unistd.h>
48 #include <dirent.h>
49 #include <sys/socket.h>
50 
51 #define FDTOOLS_C_ 1
52 
53 #include "dnscore/fdtools.h"
54 #include "dnscore/zalloc.h"
55 #include "dnscore/ptr_set.h"
56 #include "dnscore/timems.h"
57 #include "dnscore/logger.h"
58 #include "dnscore/mutex.h"
59 
60  /* GLOBAL VARIABLES */
61 
62 extern logger_handle *g_system_logger;
63 #define MODULE_MSG_HANDLE g_system_logger
64 
65 #if DEBUG
66 #define DEBUG_FD_OPEN_CLOSE_MONITOR 1
67 #else
68 #define DEBUG_FD_OPEN_CLOSE_MONITOR 0
69 #endif
70 
71 #define FDTRACK_TAG 0x4b434152544446
72 #define MTIMESET_TAG 0x544553454d49544d
73 
74 #if DEBUG
75 // avoids logging these operations in the logger.
76 // this prevents a self-deadlock if the logger limit is reached (2^20 lines)
77 bool logger_is_self();
78 #endif
79 
80 #if DEBUG
81 static void
admin_warn(const char * pathname)82 admin_warn(const char *pathname)
83 {
84     uid_t uid = getuid();
85     uid_t euid = getuid();
86     gid_t gid = getgid();
87     gid_t egid = getegid();
88 
89     bool is_admin = (uid==0)||(euid==0)||(gid==0)||(egid==0);
90 
91     if(is_admin)
92     {
93         printf("It is unlikely file or directory creation should be made as an admin '%s' (DEBUG)\n", pathname);
94         fflush(NULL);
95     }
96 }
97 #endif
98 
99 #if DEBUG_FD_OPEN_CLOSE_MONITOR
100 
101 // file descriptor track enabled
102 
103 #include "dnscore/u32_set.h"
104 #include "dnscore/zalloc.h"
105 
106 static group_mutex_t fd_mtx = GROUP_MUTEX_INITIALIZER;
107 
108 static u32_set fd_to_name = U32_SET_EMPTY;
109 
110 struct fd_track
111 {
112     stacktrace opener;
113     stacktrace closer;
114     char name[256];
115 };
116 
117 typedef struct fd_track fd_track;
118 
fd_set_name(int fd,const char * name)119 static void fd_set_name(int fd, const char *name)
120 {
121     fd_track *track;
122 
123     group_mutex_lock(&fd_mtx, GROUP_MUTEX_WRITE);
124 
125     log_debug6("file descriptor: %i is '%s'", fd, name);
126 
127     u32_node *node = u32_set_insert(&fd_to_name, fd);
128 
129     if(node->value == NULL)
130     {
131         ZALLOC_OBJECT_OR_DIE(track, fd_track, FDTRACK_TAG);
132         node->value = track;
133         track->opener = 0;
134         track->closer = 0;
135         track->name[0] = '\0';
136     }
137     else
138     {
139         track = (fd_track*)node->value;
140         if(track->name[0] != '\0')
141         {
142             log_err("file descriptor: %i was associated with '%s'", fd, track->name);
143         }
144     }
145 
146     strcpy_ex(track->name, name, sizeof(track->name));
147     track->opener = debug_stacktrace_get();
148     track->name[sizeof(track->name) - 1] = '\0';
149 
150     group_mutex_unlock(&fd_mtx, GROUP_MUTEX_WRITE);
151 }
152 
fd_clear_name(int fd)153 static void fd_clear_name(int fd)
154 {
155     group_mutex_lock(&fd_mtx, GROUP_MUTEX_WRITE);
156 
157     u32_node *node = u32_set_find(&fd_to_name, fd);
158     if(node != NULL)
159     {
160         yassert(node->value != NULL);
161 
162         fd_track *track = (fd_track*)node->value;
163         stacktrace st = debug_stacktrace_get();
164 
165         if(track->name[0] != '\0')
166         {
167             log_debug6("file descriptor: %i is '%s' no more", fd, track->name);
168         }
169         else
170         {
171             log_debug6("file descriptor: %i is being closed by", fd);
172             debug_stacktrace_log(MODULE_MSG_HANDLE, MSG_DEBUG6, st);
173             log_debug6("file descriptor: %i was closed already by", fd);
174             debug_stacktrace_log(MODULE_MSG_HANDLE, MSG_DEBUG6, track->closer);
175             log_debug6("file descriptor: %i was last opened by", fd);
176             debug_stacktrace_log(MODULE_MSG_HANDLE, MSG_DEBUG6, track->opener);
177         }
178 
179         track->closer = st;
180 
181         track->name[0] = '\0';
182     }
183     else
184     {
185         stacktrace st = debug_stacktrace_get();
186 
187         log_debug6("file descriptor: %i is untracked and is being closed by", fd);
188         debug_stacktrace_log(MODULE_MSG_HANDLE, MSG_DEBUG6, st);
189     }
190 
191     group_mutex_unlock(&fd_mtx, GROUP_MUTEX_WRITE);
192 }
193 #endif
194 
195 /**
196  * Writes fully the buffer to the fd
197  * It will only return a short count for system errors.
198  * ie: fs full, non-block would block, fd invalid/closed, ...
199  */
200 
201 ssize_t
writefully(int fd,const void * buf,size_t count)202 writefully(int fd, const void *buf, size_t count)
203 {
204     const u8* start = (const u8*)buf;
205     const u8* current = start;
206     ssize_t n;
207 
208     while(count > 0)
209     {
210         if((n = write(fd, current, count)) <= 0)
211         {
212             if(n == 0)
213             {
214                 break;
215             }
216 
217             int err = errno;
218 
219             if(err == EINTR)
220             {
221                 continue;
222             }
223 
224             if(err == EAGAIN) /** @note It is nonsense to call writefully with a non-blocking fd */
225             {
226                 if(current - start > 0)
227                 {
228                     break;
229                 }
230 
231                 return MAKE_ERRNO_ERROR(ETIMEDOUT);
232             }
233 
234             if(err == ENOSPC)
235             {
236                 // the disk is full : wait a bit, hope the admin catches it, try again later
237                 sleep((rand()&7) + 1);
238                 continue;
239             }
240 
241             if(current - start > 0)
242             {
243                 break;
244             }
245 
246             return MAKE_ERRNO_ERROR(err);
247         }
248 
249         current += n;
250         count -= n;
251     }
252 
253     return current - start;
254 }
255 
256 /**
257  * Reads fully the buffer from the fd
258  * It will only return a short count for system errors.
259  * ie: fs full, non-block would block, fd invalid/closed, ...
260  */
261 
262 ssize_t
readfully(int fd,void * buf,size_t length)263 readfully(int fd, void *buf, size_t length)
264 {
265     u8* start = (u8*)buf;
266     u8* current = start;
267     ssize_t n;
268 
269     while(length > 0)
270     {
271         if((n = read(fd, current, length)) <= 0)
272         {
273             if(n == 0) // end of file
274             {
275                 break;
276             }
277 
278             int err = errno;
279 
280             if(err == EINTR)
281             {
282                 continue;
283             }
284 
285             if(err == EAGAIN) /** @note It is nonsense to call readfully with a non-blocking fd */
286             {
287                 break;
288             }
289 
290             if(current - start > 0)
291             {
292                 break;
293             }
294 
295             return -1;
296         }
297 
298         current += n;
299         length -= n;
300     }
301 
302     return current - start;
303 }
304 
305 /**
306  * Writes fully the buffer to the fd
307  * It will only return a short count for system errors.
308  * ie: fs full, non-block would block, fd invalid/closed, ...
309  */
310 
311 ssize_t
writefully_limited(int fd,const void * buf,size_t count,double minimum_rate_us)312 writefully_limited(int fd, const void *buf, size_t count, double minimum_rate_us)
313 {
314     const u8* start = (const u8*)buf;
315     const u8* current = start;
316     ssize_t n;
317 
318     u64 tstart = timeus();
319 
320     // ASSUMED : timeout set on fd for read & write
321 
322     while(count > 0)
323     {
324         if((n = write(fd, current, count)) <= 0)
325         {
326             if(n == 0)
327             {
328                 break;
329             }
330 
331             int err = errno;
332 
333             if(err == EINTR)
334             {
335                 continue;
336             }
337 
338             if(err == EAGAIN)
339             {
340                 /*
341                  * Measure the current elapsed time
342                  * Measure the current bytes
343                  * compare with the minimum rate
344                  * act on it
345                  */
346 
347                 /* t is in us */
348 
349                 u64 now = timeus();
350 
351                 u64 time_elapsed_u64 = now - tstart;
352 
353                 if(time_elapsed_u64 >= ONE_SECOND_US)
354                 {
355                     double time_elapsed_us = time_elapsed_u64;
356 
357                     double bytes_written = (current - (u8*)buf)  * ONE_SECOND_US_F;
358 
359                     double expected_bytes_written = minimum_rate_us * time_elapsed_us;
360 
361                     if(bytes_written < expected_bytes_written)  /* bytes/time < minimum_rate */
362                     {
363 #if DEBUG
364                         log_warn("writefully_limited: rate of %fBps < %fBps (%fµs)", bytes_written, expected_bytes_written, time_elapsed_us);
365 #else
366                         log_debug("writefully_limited: rate of %fBps < %fBps (%fµs)", bytes_written, expected_bytes_written, time_elapsed_us);
367 #endif
368                         return TCP_RATE_TOO_SLOW;
369                     }
370                 }
371 
372                 continue;
373             }
374 
375             if(err == ENOSPC)
376             {
377                 // the disk is full : wait a bit, hope the admin catches it, try again later
378                 sleep((rand()&7) + 1);
379                 continue;
380             }
381 
382             if(current - start > 0)
383             {
384                 break;
385             }
386 
387             return -1;
388         }
389 
390         current += n;
391         count -= n;
392     }
393 
394     return current - start;
395 }
396 
397 /**
398  * Reads fully the buffer from the fd
399  * It will only return a short count for system errors.
400  * ie: fs full, non-block would block, fd invalid/closed, ...
401  */
402 
403 ssize_t
readfully_limited(int fd,void * buf,size_t count,double minimum_rate_us)404 readfully_limited(int fd, void *buf, size_t count, double minimum_rate_us)
405 {
406     u8* start = (u8*)buf;
407     u8* current = start;
408     ssize_t n;
409 
410     u64 tstart = timeus();
411 
412     // ASSUME : timeout set on fd for read & write
413 
414     while(count > 0)
415     {
416         if((n = read(fd, current, count)) <= 0)
417         {
418             if(n == 0)
419             {
420                 break;
421             }
422 
423             int err = errno;
424 
425             if(err == EINTR)
426             {
427                 continue;
428             }
429 
430             if(err == EAGAIN) /** @note It is nonsense to call readfully with a non-blocking fd */
431             {
432                 /*
433                  * Measure the current elapsed time
434                  * Measure the current bytes
435                  * compare with the minimum rate
436                  * act on it
437                  */
438 
439                 u64 now = timeus();
440 
441                 /* t is in us */
442 
443                 u64 time_elapsed_u64 = now - tstart;
444 
445                 double time_elapsed_us = time_elapsed_u64;
446 
447                 if(time_elapsed_u64 >= ONE_SECOND_US)
448                 {
449                     double bytes_read = (current - (u8*)buf) * ONE_SECOND_US_F;
450 
451                     double expected_bytes_read = minimum_rate_us * time_elapsed_us;
452 
453                     if(bytes_read < expected_bytes_read)  // bytes/time < minimum_rate
454                     {
455                         time_elapsed_us /= 1000000.0;
456 #if DEBUG
457                         log_warn("readfully_limited: rate of %fBps < %fBps (%fµs) (DEBUG)", bytes_read, expected_bytes_read, time_elapsed_us);
458 #else
459                         log_debug("readfully_limited: rate of %fBps < %fBps (%fµs)", bytes_read, expected_bytes_read, time_elapsed_us);
460 #endif
461                         return TCP_RATE_TOO_SLOW;
462                     }
463                 }
464 
465                 continue;
466             }
467 
468             if(current - start > 0)
469             {
470                 break;
471             }
472 
473             return -1;  /* EOF */
474         }
475 
476         current += n;
477         count -= n;
478     }
479 
480     return current - start;
481 }
482 
483 /**
484  * Reads fully the buffer from the fd
485  * It will only return a short count for system errors.
486  * ie: fs full, non-block would block, fd invalid/closed, ...
487  */
488 
489 ssize_t
readfully_limited_ex(int fd,void * buf,size_t count,s64 timeout_us,double minimum_rate_us)490 readfully_limited_ex(int fd, void *buf, size_t count, s64 timeout_us, double minimum_rate_us)
491 {
492     u8* start = (u8*)buf;
493     u8* current = start;
494     ssize_t n;
495 
496     if(timeout_us <= 0)
497     {
498         timeout_us = 1;
499     }
500 
501     s64 tstart = timeus();
502 
503     // ASSUME : timeout set on fd for read & write
504 
505     while(count > 0)
506     {
507         if((n = read(fd, current, count)) <= 0)
508         {
509             if(n == 0)
510             {
511                 break;
512             }
513 
514             int err = errno;
515 
516             if(err == EINTR)
517             {
518                 continue;
519             }
520 
521             if(err == EAGAIN) /** @note It is nonsense to call readfully with a non-blocking fd */
522             {
523                 /*
524                  * Measure the current elapsed time
525                  * Measure the current bytes
526                  * compare with the minimum rate
527                  * act on it
528                  */
529 
530                 s64 now = timeus();
531 
532                 /* t is in us */
533 
534                 s64 time_elapsed_u64 = now - tstart;
535 
536                 double time_elapsed_us = time_elapsed_u64;
537 
538                 if(time_elapsed_u64 >= timeout_us)
539                 {
540                     double bytes_read = (current - (u8*)buf);
541                     bytes_read *= ONE_SECOND_US_F;
542                     bytes_read /= time_elapsed_u64;
543 
544                     double expected_bytes_read = minimum_rate_us * time_elapsed_us;
545 
546                     if(bytes_read < expected_bytes_read)  // bytes/time < minimum_rate
547                     {
548                         time_elapsed_us /= 1000000.0;
549 #if DEBUG
550                         log_warn("readfully_limited: rate of %fBps < %fBps (%fµs) (DEBUG)", bytes_read, expected_bytes_read, time_elapsed_us);
551 #else
552                         log_debug("readfully_limited: rate of %fBps < %fBps (%fµs)", bytes_read, expected_bytes_read, time_elapsed_us);
553 #endif
554                         return TCP_RATE_TOO_SLOW;
555                     }
556                 }
557 
558                 continue;
559             }
560 
561             if(current - start > 0)
562             {
563                 break;
564             }
565 
566             return -1;  /* EOF */
567         }
568 
569         current += n;
570         count -= n;
571     }
572 
573     return current - start;
574 }
575 
576 /**
577  * Reads an ASCII text line from fd, stops at EOF or '\n'
578  */
579 
580 ssize_t
readtextline(int fd,char * start,size_t count)581 readtextline(int fd, char *start, size_t count)
582 {
583     char *current = start;
584     const char * const limit = &start[count];
585 
586     while(current < limit)
587     {
588         ssize_t n;
589 
590         if((n = read(fd, current, 1)) > 0)
591         {
592             u8 c = *current;
593 
594             current++;
595             count --;
596 
597             if(c == '\n')
598             {
599                 break;
600             }
601         }
602         else
603         {
604             if(n == 0)
605             {
606                 break;
607             }
608 
609             int err = errno;
610 
611             if(err == EINTR)
612             {
613                 continue;
614             }
615 
616             if(err == EAGAIN)
617             {
618                 continue;
619             }
620 
621             if(current - start > 0)
622             {
623                 break;
624             }
625 
626             return -1;
627         }
628     }
629 
630     return current - start;
631 }
632 
633 /**
634  * Deletes a file (see man 2 unlink).
635  * Handles EINTR and other retry errors.
636  * Safe to use in the logger thread as it only logs (debug) if the current
637  * thread is not the logger's
638  *
639  * @param fd
640  * @return
641  */
642 
643 int
unlink_ex(const char * folder,const char * filename)644 unlink_ex(const char *folder, const char *filename)
645 {
646     char fullpath[PATH_MAX];
647 
648     size_t l0 = strlen(folder);
649     size_t l1 = strlen(filename);
650     if(l0 + l1 < sizeof(fullpath))
651     {
652         memcpy(fullpath, folder, l0);
653         fullpath[l0] = '/';
654         memcpy(&fullpath[l0 + 1], filename, l1);
655         fullpath[l0 + 1 + l1] = '\0';
656 
657         return unlink(fullpath);
658     }
659     else
660     {
661         errno = ENOMEM;
662         return -1;
663     }
664 }
665 
666 /**
667  * Copies the absolute path of a file into a buffer.
668  *
669  * @param filename the file name
670  * @param buffer the output buffer
671  * @param buffer_size the size of the output buffer
672  * @return the string length (without the terminator)
673  */
674 
675 ya_result
file_get_absolute_path(const char * filename,char * buffer,size_t buffer_size)676 file_get_absolute_path(const char *filename, char *buffer, size_t buffer_size)
677 {
678     ya_result ret;
679 
680     if(filename[0] == '/')
681     {
682         strcpy_ex(buffer, filename, buffer_size);
683         ret = strlen(buffer);
684         return ret;
685     }
686     else
687     {
688         if(getcwd(buffer, buffer_size) == NULL)
689         {
690             return ERRNO_ERROR;
691         }
692 
693         size_t n = strlen(buffer);
694 
695         if(n < buffer_size)
696         {
697             ret = n + 1;
698 
699             buffer += n;
700             buffer_size -= n;
701 
702             *buffer++ = '/';
703             --buffer_size;
704 
705             n = strlen(filename);
706             if(n < buffer_size)
707             {
708                 memcpy(buffer, filename, n);
709                 buffer[n] = '\0';
710 
711                 return ret + n;
712             }
713         }
714 
715         return ERROR; // not enough room
716     }
717 }
718 
719 #if DEBUG_BENCH_FD
720 static debug_bench_s debug_open;
721 static debug_bench_s debug_open_create;
722 static debug_bench_s debug_close;
723 static bool fdtools_debug_bench_register_done = FALSE;
724 
fdtools_debug_bench_register()725 static inline void fdtools_debug_bench_register()
726 {
727     if(!fdtools_debug_bench_register_done)
728     {
729         fdtools_debug_bench_register_done = TRUE;
730         debug_bench_register(&debug_open, "open");
731         debug_bench_register(&debug_open_create, "open_create");
732         debug_bench_register(&debug_close, "close");
733     }
734 }
735 #endif
736 
737 /**
738  * Opens a file. (see man 2 open)
739  * Handles EINTR and other retry errors.
740  * Safe to use in the logger thread as it only logs (debug) if the current
741  * thread is not the logger's
742  *
743  * @param fd
744  * @return
745  */
746 
747 ya_result
open_ex(const char * pathname,int flags)748 open_ex(const char *pathname, int flags)
749 {
750     int fd;
751 
752 #if DEBUG
753     if(!logger_is_self() && logger_is_running())
754     {
755         log_debug6("open_ex(%s,%o)", STRNULL(pathname), flags);
756         errno = 0;
757     }
758 #endif
759 
760     yassert(pathname != NULL);
761 
762 #if DEBUG_BENCH_FD
763     fdtools_debug_bench_register();
764     u64 bench = debug_bench_start(&debug_open);
765 #endif
766 
767 #if DNSCORE_FDTOOLS_CLOEXEC
768     bool cloexec = (flags & O_CLOEXEC) != 0;
769     flags &= ~O_CLOEXEC;
770 #endif
771 
772     while((fd = open(pathname, flags)) < 0)
773     {
774         int err = errno;
775 
776         if(err != EINTR)
777         {
778             break;
779         }
780     }
781 
782 #if DNSCORE_FDTOOLS_CLOEXEC
783     if((fd >= 0) && cloexec)
784     {
785         ya_result ret;
786 
787         if(FAIL(ret = fd_setcloseonexec(fd)))
788         {
789             log_warn("open_ex(%s,%o): failed to set CLOEXEC: %r", STRNULL(pathname), flags|O_CLOEXEC, ret);
790         }
791     }
792 #endif
793 
794 #if DEBUG_BENCH_FD
795     debug_bench_stop(&debug_open, bench);
796 #endif
797 
798 #if DEBUG
799     if(!logger_is_self() && logger_is_running())
800     {
801         log_debug6("open_ex(%s,%o): %r (%i)", STRNULL(pathname), flags, ERRNO_ERROR, fd);
802 #if DEBUG_FD_OPEN_CLOSE_MONITOR
803         if(fd > 0)
804         {
805             fd_set_name(fd, pathname);
806         }
807 #endif
808     }
809 #endif
810 
811     return fd;
812 }
813 
814 /**
815  * Opens a file, create if it does not exist. (see man 2 open with O_CREAT)
816  * Handles EINTR and other retry errors.
817  * Safe to use in the logger thread as it only logs (debug) if the current
818  * thread is not the logger's
819  *
820  * @param fd
821  * @return
822  */
823 
824 ya_result
open_create_ex(const char * pathname,int flags,mode_t mode)825 open_create_ex(const char *pathname, int flags, mode_t mode)
826 {
827     int fd;
828 
829 #if DEBUG
830     if(!logger_is_self() && logger_is_running())
831     {
832         log_debug6("open_create_ex(%s,%o,%o)", STRNULL(pathname), flags, mode);
833         errno = 0;
834     }
835 #endif
836 
837 #if DEBUG
838     admin_warn(pathname);
839 #endif
840 
841     yassert(pathname != NULL);
842 
843 #if DEBUG_BENCH_FD
844     fdtools_debug_bench_register();
845     u64 bench = debug_bench_start(&debug_open_create);
846 #endif
847 
848     while((fd = open(pathname, flags, mode)) < 0)
849     {
850         int err = errno;
851 
852         if(err != EINTR)
853         {
854             // do NOT set this to an error code other than -1
855             //fd = MAKE_ERRNO_ERROR(err);
856             break;
857         }
858     }
859 
860 #if DEBUG_BENCH_FD
861     debug_bench_stop(&debug_open_create, bench);
862 #endif
863 
864 #if DEBUG
865     if(!logger_is_self() && logger_is_running())
866     {
867         log_debug6("open_create_ex(%s,%o,%o): %r (%i)", STRNULL(pathname), flags, mode, ERRNO_ERROR, fd);
868 #if DEBUG_FD_OPEN_CLOSE_MONITOR
869         if(fd > 0)
870         {
871             fd_set_name(fd, pathname);
872         }
873 #endif
874     }
875 #endif
876 
877     return fd;
878 }
879 
mkstemp_ex(char * tmp_name_template)880 int mkstemp_ex(char * tmp_name_template)
881 {
882 #ifndef WIN32
883     int fd = mkstemp(tmp_name_template);
884 #if DEBUG
885     if(!logger_is_self() && logger_is_running())
886     {
887 #if DEBUG_FD_OPEN_CLOSE_MONITOR
888         if(fd > 0)
889         {
890             fd_set_name(fd, tmp_name_template);
891         }
892 #endif
893     }
894 #endif
895     return fd;
896 #else
897     return -1;
898 #endif
899 }
900 
901 /**
902  * Opens a file, create if it does not exist. (see man 2 open with O_CREAT)
903  * Handles EINTR and other retry errors.
904  * This version of open_create_ex does NOT log anything, which is very important sometimes in the logger thread
905  *
906  * @param fd
907  * @return
908  */
909 
910 ya_result
open_create_ex_nolog(const char * pathname,int flags,mode_t mode)911 open_create_ex_nolog(const char *pathname, int flags, mode_t mode)
912 {
913     int fd;
914 #if DEBUG
915     admin_warn(pathname);
916 #endif
917     while((fd = open(pathname, flags, mode)) < 0)
918     {
919         int err = errno;
920 
921         if(err != EINTR)
922         {
923             // do NOT set this to an error code other than -1
924             //fd = MAKE_ERRNO_ERROR(err);
925             break;
926         }
927     }
928 
929     return fd;
930 }
931 
932 /**
933  * Closes a file descriptor (see man 2 close)
934  * Handles EINTR and other retry errors.
935  * At return the file will be closed or not closable.
936  *
937  * @param fd
938  * @return
939  */
940 
941 ya_result
942 #if !DNSCORE_HAS_CLOSE_EX_REF
close_ex(int fd)943 close_ex(int fd)
944 #else
945 close_ex_ref(int* fdp)
946 #endif
947 {
948     ya_result return_value = SUCCESS;
949 
950 #if DNSCORE_HAS_CLOSE_EX_REF
951     int fd = *fdp;
952 
953     if(fd == -2)
954     {
955         abort();
956     }
957 
958     *fdp = -2;
959 #endif
960 
961 #if DEBUG
962     if(!logger_is_self() && logger_is_running())
963     {
964 #if DEBUG_FD_OPEN_CLOSE_MONITOR
965         if(fd > 0)
966         {
967             fd_clear_name(fd);
968         }
969 #endif
970 
971         log_debug6("close_ex(%i)", fd);
972         errno = 0;
973     }
974 #endif
975 
976 #if DEBUG_BENCH_FD
977     fdtools_debug_bench_register();
978     u64 bench = debug_bench_start(&debug_close);
979 #endif
980 
981     while(close(fd) < 0)
982     {
983         int err = errno;
984 
985         if(err != EINTR)
986         {
987             return_value = MAKE_ERRNO_ERROR(err);
988             break;
989         }
990     }
991 #if DEBUG_BENCH_FD
992     debug_bench_stop(&debug_close, bench);
993 #endif
994 
995 #if DEBUG
996     if(!logger_is_self() && logger_is_running())
997     {
998         log_debug6("close_ex(%i): %r", fd, return_value);
999         if(FAIL(return_value))
1000         {
1001             logger_flush();
1002         }
1003     }
1004 #endif
1005 
1006     return return_value;
1007 }
1008 
1009 /**
1010  * Closes a file descriptor (see man 2 close)
1011  * Handles EINTR and other retry errors.
1012  * At return the file will be closed or not closable.
1013  *
1014  * @param fd
1015  * @return
1016  */
1017 
1018 ya_result
1019 #if !DNSCORE_HAS_CLOSE_EX_REF
close_ex_nolog(int fd)1020 close_ex_nolog(int fd)
1021 #else
1022 close_ex_nolog_ref(int* fdp)
1023 #endif
1024 {
1025     ya_result return_value = SUCCESS;
1026 
1027 #if DNSCORE_HAS_CLOSE_EX_REF
1028     int fd = *fdp;
1029 
1030     if(fd == -2)
1031     {
1032         abort();
1033     }
1034 
1035     *fdp = -2;
1036 #endif
1037 
1038     while(close(fd) < 0)
1039     {
1040         int err = errno;
1041 
1042         if(err != EINTR)
1043         {
1044             return_value = MAKE_ERRNO_ERROR(err);
1045             break;
1046         }
1047     }
1048 
1049     return return_value;
1050 }
1051 
1052 int
fsync_ex(int fd)1053 fsync_ex(int fd)
1054 {
1055 #ifndef WIN32
1056     while(fsync(fd) < 0)
1057     {
1058         int err = errno;
1059         if(err != EINTR)
1060         {
1061             return ERRNO_ERROR;
1062         }
1063     }
1064 #else
1065     FlushFileBuffers(fd);
1066 #endif
1067     return SUCCESS;
1068 }
1069 
1070 int
fdatasync_ex(int fd)1071 fdatasync_ex(int fd)
1072 {
1073 #ifndef WIN32
1074 #if defined(__linux__)
1075     while(fdatasync(fd) < 0)
1076 #else
1077     while(fsync(fd) < 0)
1078 #endif
1079     {
1080         int err = errno;
1081         if(err != EINTR)
1082         {
1083             return ERRNO_ERROR;
1084         }
1085     }
1086 #else
1087     FlushFileBuffers(fd);
1088 #endif
1089     return SUCCESS;
1090 }
1091 
dup_ex(int fd)1092 int dup_ex(int fd)
1093 {
1094     int ret;
1095     while((ret = dup(fd)) < 0)
1096     {
1097         int err = errno;
1098         if(err != EINTR)
1099         {
1100             return ERRNO_ERROR;
1101         }
1102     }
1103 
1104     return ret;
1105 }
1106 
dup2_ex(int old_fd,int new_fd)1107 int dup2_ex(int old_fd, int new_fd)
1108 {
1109     int ret;
1110     while((ret = dup2(old_fd, new_fd)) < 0)
1111     {
1112         int err = errno;
1113         if(err != EINTR)
1114         {
1115             return ERRNO_ERROR;
1116         }
1117     }
1118 
1119     return ret;
1120 }
1121 
truncate_ex(const char * path,off_t len)1122 int truncate_ex(const char *path, off_t len)
1123 {
1124     int ret;
1125     while((ret = truncate(path, len)) < 0)
1126     {
1127         int err = errno;
1128         if(err != EINTR)
1129         {
1130             return ERRNO_ERROR;
1131         }
1132     }
1133 
1134     return ret;
1135 }
1136 
ftruncate_ex(int fd,off_t len)1137 int ftruncate_ex(int fd, off_t len)
1138 {
1139     int ret;
1140     while((ret = ftruncate(fd, len)) < 0)
1141     {
1142         int err = errno;
1143         if(err != EINTR)
1144         {
1145             return ERRNO_ERROR;
1146         }
1147     }
1148 
1149     return ret;
1150 }
1151 
1152 /**
1153  * Returns the type of socket.
1154  *
1155  * @param fd the file descriptor of the socket
1156  * @return SOCK_STREAM, SOCK_DGRAM, SOCK_RAW or an errno error code like MAKE_ERRON_ERROR(EBADF) or MAKE_ERRON_ERROR(ENOTSOCK)
1157  */
1158 
1159 ya_result
fd_getsockettype(int fd)1160 fd_getsockettype(int fd)
1161 {
1162     int stype;
1163     socklen_t stype_len = sizeof(stype);
1164     int ret = getsockopt(fd, SOL_SOCKET, SO_TYPE, &stype, &stype_len);
1165     if(ret >= 0)
1166     {
1167         return stype; // SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, ...
1168     }
1169     else
1170     {
1171         return ERRNO_ERROR; // expecting EBADF or ENOTSOCK
1172     }
1173 }
1174 
1175 s64
filesize(const char * name)1176 filesize(const char *name)
1177 {
1178     struct stat s;
1179     if(stat(name, &s) >= 0) // MUST be stat
1180     {
1181         if(S_ISREG(s.st_mode))
1182         {
1183             return s.st_size;
1184         }
1185     }
1186 
1187     return (s64)ERRNO_ERROR;
1188 }
1189 
1190 /**
1191  * Checks for existence of a file/dir/link
1192  *
1193  * @param name the file name
1194  *
1195  * @return 1 if the file exists, 0 if the file does not exists, an error otherwise
1196  */
1197 
1198 ya_result
file_exists(const char * name)1199 file_exists(const char *name)
1200 {
1201     struct stat s;
1202 #ifndef WIN32
1203     if(lstat(name, &s) >= 0)    // MUST be lstat
1204     {
1205         return 1;
1206     }
1207 #else
1208     if(stat(name, &s) >= 0)    // MUST be lstat
1209     {
1210         return 1;
1211     }
1212 #endif
1213 
1214     int err = errno;
1215 
1216     if(err == ENOENT)
1217     {
1218         return 0;
1219     }
1220 
1221     return MAKE_ERRNO_ERROR(err);
1222 }
1223 
1224 /**
1225  *
1226  * Checks if a file exists and is a link
1227  *
1228  * @param name the file name
1229  *
1230  * @return  0 : not a link
1231  *          1 : a link
1232  *        < 0 : error
1233  */
1234 
1235 ya_result
file_is_link(const char * name)1236 file_is_link(const char *name)
1237 {
1238 #ifndef WIN32
1239     struct stat s;
1240     if(lstat(name, &s) >= 0)    // MUST be lstat
1241     {
1242         return S_ISLNK(s.st_mode)?SUCCESS:ERROR;
1243     }
1244 
1245     return ERRNO_ERROR;
1246 #else
1247     return 0;
1248 #endif
1249 }
1250 
1251 /**
1252  *
1253  * Checks if a file exists and is a directory
1254  *
1255  * @param name the file name
1256  *
1257  * @return  0 : not a link
1258  *          1 : a link
1259  *        < 0 : error
1260  */
1261 
1262 ya_result
file_is_directory(const char * name)1263 file_is_directory(const char *name)
1264 {
1265 #ifndef WIN32
1266     struct stat s;
1267     if(lstat(name, &s) >= 0)    // MUST be lstat
1268     {
1269         return S_ISDIR(s.st_mode)?SUCCESS:ERROR;
1270     }
1271 
1272     return ERRNO_ERROR;
1273 #else
1274     return 0;
1275 #endif
1276 }
1277 
1278 /**
1279  *
1280  * Creates all directories on pathname.
1281  *
1282  * Could be optimised a bit :
1283  *
1284  *      try the biggest path first,
1285  *      going down until it works,
1286  *      then create back up.
1287  *
1288  * @param pathname
1289  * @param mode
1290  * @param flags
1291  *
1292  * @return
1293  */
1294 
1295 int
mkdir_ex(const char * pathname,mode_t mode,u32 flags)1296 mkdir_ex(const char *pathname, mode_t mode, u32 flags)
1297 {
1298 #if DEBUG
1299     log_debug("mkdir_ex(%s,%o)", pathname, mode);
1300 #endif
1301 
1302 #if DEBUG
1303     admin_warn(pathname);
1304 #endif
1305 
1306     const char *s;
1307     char *t;
1308 
1309     char dir_path[PATH_MAX];
1310 
1311     s = pathname;
1312     t = dir_path;
1313 
1314     if(pathname[0] == '/')
1315     {
1316         t[0] = '/';
1317         t++;
1318         s++;
1319     }
1320 
1321     for(;;)
1322     {
1323         const char *p = (const char*)strchr(s, '/');
1324 
1325         bool last = (p == NULL);
1326 
1327         if(last)
1328         {
1329             if((flags & MKDIR_EX_PATH_TO_FILE) != 0)
1330             {
1331                 return s - pathname;
1332             }
1333 
1334             p = s + strlen(s);
1335         }
1336 
1337         intptr n = (p - s);
1338         memcpy(t, s, n);
1339         t[n] = '\0';
1340 
1341         struct stat file_stat;
1342         if(stat(dir_path, &file_stat) < 0)
1343         {
1344             int err = errno;
1345 
1346             if(err != ENOENT)
1347             {
1348 #if DEBUG
1349                 log_debug("mkdir_ex(%s,%o): stat returned %r", pathname, mode, MAKE_ERRNO_ERROR(err));
1350 #endif
1351 
1352                 return MAKE_ERRNO_ERROR(err);
1353             }
1354 
1355             if(mkdir(dir_path, mode) < 0)
1356             {
1357 #if DEBUG
1358                 log_debug("mkdir_ex(%s,%o): mkdir(%s, %o) returned %r", pathname, mode, dir_path, mode, MAKE_ERRNO_ERROR(err));
1359 #endif
1360 
1361                 return ERRNO_ERROR;
1362             }
1363         }
1364 
1365         if(last)
1366         {
1367             s = &s[n];
1368             return s - pathname;
1369         }
1370 
1371         t[n++] = '/';
1372 
1373         t = &t[n];
1374         s = &s[n];
1375     }
1376 }
1377 
1378 /**
1379  * Returns the modification time of the file in microseconds
1380  * This does not mean the precision of the time is that high.
1381  * This is only to simplify reading the time on a file.
1382  *
1383  * @param name the file name
1384  * @param timestamp a pointer to the timestamp
1385  * @return an error code
1386  */
1387 
1388 ya_result
file_mtime(const char * name,s64 * timestamp)1389 file_mtime(const char *name, s64 *timestamp)
1390 {
1391     struct stat st;
1392     yassert(name != NULL);
1393     yassert(timestamp != NULL);
1394     if(stat(name, &st) >= 0)
1395     {
1396 #ifdef WIN32
1397         s64 ts = ONE_SECOND_US * st.st_mtime;
1398 #elif !__APPLE__
1399         s64 ts = (ONE_SECOND_US * st.st_mtim.tv_sec) + (st.st_mtim.tv_nsec / 1000LL);
1400 #else
1401         s64 ts = (ONE_SECOND_US * st.st_mtimespec.tv_sec) + (st.st_mtimespec.tv_nsec / 1000LL);
1402 #endif
1403         *timestamp = ts;
1404         return SUCCESS;
1405     }
1406     else
1407     {
1408         *timestamp = 0;
1409         return ERRNO_ERROR;
1410     }
1411 }
1412 
1413 /**
1414  * Returns the modification time of the file in microseconds
1415  * This does not mean the precision of the time is that high.
1416  * This is only to simplify reading the time on a file.
1417  *
1418  * @param name the file name
1419  * @param timestamp a pointer to the timestamp
1420  * @return an error code
1421  */
1422 
1423 ya_result
fd_mtime(int fd,s64 * timestamp)1424 fd_mtime(int fd, s64 *timestamp)
1425 {
1426     struct stat st;
1427     yassert(timestamp != NULL);
1428     if(fstat(fd, &st) >= 0)
1429     {
1430 #ifdef WIN32
1431         s64 ts = ONE_SECOND_US * st.st_mtime;
1432 #elif !__APPLE__
1433         s64 ts = (ONE_SECOND_US * st.st_mtim.tv_sec) + (st.st_mtim.tv_nsec / 1000LL);
1434 #else
1435         s64 ts = (ONE_SECOND_US * st.st_mtimespec.tv_sec) + (st.st_mtimespec.tv_nsec / 1000LL);
1436 #endif
1437         *timestamp = ts;
1438         return SUCCESS;
1439     }
1440     else
1441     {
1442         *timestamp = 0;
1443         return ERRNO_ERROR;
1444     }
1445 }
1446 
1447 ya_result
fd_setcloseonexec(int fd)1448 fd_setcloseonexec(int fd)
1449 {
1450 #ifndef WIN32
1451     int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
1452     if(FAIL(ret))
1453     {
1454         ret = ERRNO_ERROR;
1455     }
1456     return ret;
1457 #else
1458     return SUCCESS;
1459 #endif
1460 }
1461 
1462 ya_result
fd_setnonblocking(int fd)1463 fd_setnonblocking(int fd)
1464 {
1465 #ifndef WIN32
1466     int ret;
1467     if(ISOK(ret = fcntl(fd, F_GETFL, 0)))
1468     {
1469         fcntl(fd, F_SETFL, ret | O_NONBLOCK);
1470     }
1471     else
1472     {
1473         ret = ERRNO_ERROR;
1474     }
1475 
1476     return ret;
1477 #else
1478     return SUCCESS;
1479 #endif
1480 }
1481 
1482 /**
1483  * Fixes an issue with the dirent not always set as expected.
1484  *
1485  * The type can be set to DT_UNKNOWN instead of file or directory.
1486  * In that case the function will call stats to get the type.
1487  */
1488 
1489 u8
dirent_get_type_from_fullpath(const char * fullpath)1490 dirent_get_type_from_fullpath(const char *fullpath)
1491 {
1492     struct stat file_stat;
1493     u8 d_type;
1494 
1495     d_type = DT_UNKNOWN;
1496 
1497     while(stat(fullpath, &file_stat) < 0)
1498     {
1499         int e = errno;
1500 
1501         if(e != EINTR)
1502         {
1503             log_err("stat(%s): %r", fullpath, ERRNO_ERROR);
1504             break;
1505         }
1506     }
1507 
1508     if(S_ISREG(file_stat.st_mode))
1509     {
1510         d_type = DT_REG;
1511     }
1512     else if(S_ISDIR(file_stat.st_mode))
1513     {
1514         d_type = DT_DIR;
1515     }
1516 
1517     return d_type;
1518 }
1519 
1520 u8
dirent_get_file_type(const char * folder,const char * name)1521 dirent_get_file_type(const char *folder, const char *name)
1522 {
1523     u8 d_type;
1524     char fullpath[PATH_MAX];
1525 
1526     d_type = DT_UNKNOWN;
1527 
1528     /*
1529      * If the FS OR the OS does not support d_type :
1530      */
1531 
1532     if(ISOK(snprintf(fullpath, sizeof(fullpath), "%s/%s", folder, name)))
1533     {
1534         d_type = dirent_get_type_from_fullpath(fullpath);
1535     }
1536 
1537     return d_type;
1538 }
1539 
1540 // typedef ya_result readdir_callback(const char *basedir, const char* file, u8 filetype, void *args);
1541 
1542 static group_mutex_t readdir_mutex = GROUP_MUTEX_INITIALIZER;
1543 
1544 ya_result
readdir_forall(const char * basedir,readdir_callback * func,void * args)1545 readdir_forall(const char *basedir, readdir_callback *func, void *args)
1546 {
1547     DIR *dir;
1548     ya_result ret;
1549     size_t basedir_len = strlen(basedir);
1550     char *name;
1551     char path[PATH_MAX];
1552     memcpy(path, basedir, basedir_len);
1553     path[basedir_len] = '/';
1554     name = &path[basedir_len + 1];
1555 
1556     dir = opendir(basedir);
1557 
1558     if(dir == NULL)
1559     {
1560         return ERRNO_ERROR;
1561     }
1562 
1563     for(;;)
1564     {
1565         group_mutex_lock(&readdir_mutex, GROUP_MUTEX_WRITE);
1566         struct dirent *tmp = readdir(dir);
1567 
1568         if(tmp == NULL)
1569         {
1570             group_mutex_unlock(&readdir_mutex, GROUP_MUTEX_WRITE);
1571 
1572             ret = SUCCESS;
1573 
1574             break;
1575         }
1576 
1577         // ignore names "." and ".."
1578 
1579 #ifndef WIN32
1580         const char* tmp_name = tmp->d_name;
1581 #else
1582         const char* tmp_name = tmp->name;
1583 #endif
1584 
1585         if(tmp_name[0] == '.')
1586         {
1587             if(
1588                 ((tmp_name[1] == '.') && (tmp_name[2] == '\0')) ||
1589                 (tmp_name[1] == '\0')
1590                 )
1591             {
1592                 group_mutex_unlock(&readdir_mutex, GROUP_MUTEX_WRITE);
1593                 continue;
1594             }
1595         }
1596 
1597         size_t name_len = strlen(tmp_name);
1598 
1599         if(name_len + basedir_len + 1 + 1 > sizeof(path))
1600         {
1601             group_mutex_unlock(&readdir_mutex, GROUP_MUTEX_WRITE);
1602             log_err("readdir_forall: '%s/%s' is bigger than expected (%i): skipping", basedir, tmp_name, sizeof(path));
1603             continue;
1604         }
1605 
1606         memcpy(name, tmp_name, name_len + 1);
1607 
1608         group_mutex_unlock(&readdir_mutex, GROUP_MUTEX_WRITE);
1609 
1610         u8 d_type = dirent_get_type_from_fullpath(path);
1611 
1612         if(FAIL(ret = func(basedir, name, d_type, args)))
1613         {
1614             return ret;
1615         }
1616 
1617         switch(ret)
1618         {
1619             case READDIR_CALLBACK_CONTINUE:
1620             {
1621                 break;
1622             }
1623             case READDIR_CALLBACK_ENTER:
1624             {
1625                 if(d_type == DT_DIR)
1626                 {
1627                     if(FAIL(ret = readdir_forall(path, func, args)))
1628                     {
1629                         return ret;
1630                     }
1631 
1632                     if(ret == READDIR_CALLBACK_EXIT)
1633                     {
1634                         return ret;
1635                     }
1636                 }
1637                 break;
1638             }
1639             case READDIR_CALLBACK_EXIT:
1640             {
1641                 return ret;
1642             }
1643             default:
1644             {
1645                 // unhandled code
1646                 break;
1647             }
1648         }
1649     }
1650 
1651     closedir(dir);
1652 
1653     return ret;
1654 }
1655 
1656 struct file_mtime_set_s
1657 {
1658     ptr_set files_mtime;
1659     char *name;
1660     bool is_new;
1661 };
1662 
1663 typedef struct file_mtime_set_s file_mtime_set_t;
1664 
1665 static ptr_set file_mtime_sets = { NULL, ptr_set_asciizp_node_compare};
1666 static mutex_t file_mtime_sets_mtx;
1667 
1668 file_mtime_set_t*
file_mtime_set_get_for_file(const char * filename)1669 file_mtime_set_get_for_file(const char *filename)
1670 {
1671     file_mtime_set_t *ret;
1672     mutex_lock(&file_mtime_sets_mtx);
1673     ptr_node *sets_node = ptr_set_insert(&file_mtime_sets, (char*)filename);
1674     if(sets_node->value != NULL)
1675     {
1676         ret = (file_mtime_set_t*)sets_node->value;
1677     }
1678     else
1679     {
1680         sets_node->key = strdup(filename);
1681         ZALLOC_OBJECT_OR_DIE(ret, file_mtime_set_t, MTIMESET_TAG);
1682         ret->files_mtime.root = NULL;
1683         ret->files_mtime.compare = ptr_set_asciizp_node_compare;
1684         ret->name = strdup(filename);
1685         ret->is_new = TRUE;
1686         sets_node->value = ret;
1687         file_mtime_set_add_file(ret, filename);
1688     }
1689     mutex_unlock(&file_mtime_sets_mtx);
1690     return ret;
1691 }
1692 
1693 void
file_mtime_set_add_file(file_mtime_set_t * ctx,const char * filename)1694 file_mtime_set_add_file(file_mtime_set_t *ctx, const char *filename)
1695 {
1696     s64 mtime;
1697 
1698     if(FAIL(file_mtime(filename, &mtime)))
1699     {
1700         mtime = MIN_S64;
1701     }
1702 
1703     ptr_node *node = ptr_set_insert(&ctx->files_mtime, (char*)filename);
1704     if(node->value == NULL)
1705     {
1706         node->key = strdup(filename);
1707         node->value_s64 = mtime;
1708     }
1709 }
1710 
1711 bool
file_mtime_set_modified(file_mtime_set_t * ctx)1712 file_mtime_set_modified(file_mtime_set_t *ctx)
1713 {
1714     if(ctx->is_new)
1715     {
1716         ctx->is_new = FALSE;
1717         return TRUE;
1718     }
1719 
1720     ptr_set_iterator iter;
1721     ptr_set_iterator_init(&ctx->files_mtime, &iter);
1722     while(ptr_set_iterator_hasnext(&iter))
1723     {
1724         ptr_node *node = ptr_set_iterator_next_node(&iter);
1725         const char *filename = (const char*)node->key;
1726         s64 mtime;
1727         if(ISOK(file_mtime(filename, &mtime)))
1728         {
1729             if(node->value_s64 < mtime)
1730             {
1731                 return TRUE;
1732             }
1733         }
1734         else
1735         {
1736             return TRUE;
1737         }
1738     }
1739     return FALSE;
1740 }
1741 
1742 void
file_mtime_set_clear(file_mtime_set_t * ctx)1743 file_mtime_set_clear(file_mtime_set_t *ctx)
1744 {
1745     ptr_set_iterator iter;
1746     ptr_set_iterator_init(&ctx->files_mtime, &iter);
1747     while(ptr_set_iterator_hasnext(&iter))
1748     {
1749         ptr_node *node = ptr_set_iterator_next_node(&iter);
1750         free(node->key);
1751     }
1752     ptr_set_destroy(&ctx->files_mtime);
1753     file_mtime_set_add_file(ctx, ctx->name);
1754 }
1755 
1756 void
file_mtime_set_delete(file_mtime_set_t * ctx)1757 file_mtime_set_delete(file_mtime_set_t *ctx)
1758 {
1759     mutex_lock(&file_mtime_sets_mtx);
1760     ptr_set_delete(&file_mtime_sets, ctx->name);
1761     mutex_unlock(&file_mtime_sets_mtx);
1762     free(ctx->name);
1763     file_mtime_set_clear(ctx);
1764     ZFREE_OBJECT(ctx);
1765 }
1766 
1767 /** @} */
1768