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