Add libconfig implementation and tests
[lttng-tools.git] / src / common / utils.c
1 /*
2 * Copyright (C) 2012 - David Goulet <dgoulet@efficios.com>
3 * Copyright (C) 2013 - Raphaƫl Beamonte <raphael.beamonte@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License, version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 51
16 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #define _GNU_SOURCE
20 #include <assert.h>
21 #include <ctype.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <inttypes.h>
30 #include <regex.h>
31 #include <grp.h>
32
33 #include <common/common.h>
34 #include <common/runas.h>
35
36 #include "utils.h"
37 #include "defaults.h"
38
39 /*
40 * Return a partial realpath(3) of the path even if the full path does not
41 * exist. For instance, with /tmp/test1/test2/test3, if test2/ does not exist
42 * but the /tmp/test1 does, the real path for /tmp/test1 is concatened with
43 * /test2/test3 then returned. In normal time, realpath(3) fails if the end
44 * point directory does not exist.
45 * In case resolved_path is NULL, the string returned was allocated in the
46 * function and thus need to be freed by the caller. The size argument allows
47 * to specify the size of the resolved_path argument if given, or the size to
48 * allocate.
49 */
50 LTTNG_HIDDEN
51 char *utils_partial_realpath(const char *path, char *resolved_path, size_t size)
52 {
53 char *cut_path, *try_path = NULL, *try_path_prev = NULL;
54 const char *next, *prev, *end;
55
56 /* Safety net */
57 if (path == NULL) {
58 goto error;
59 }
60
61 /*
62 * Identify the end of the path, we don't want to treat the
63 * last char if it is a '/', we will just keep it on the side
64 * to be added at the end, and return a value coherent with
65 * the path given as argument
66 */
67 end = path + strlen(path);
68 if (*(end-1) == '/') {
69 end--;
70 }
71
72 /* Initiate the values of the pointers before looping */
73 next = path;
74 prev = next;
75 /* Only to ensure try_path is not NULL to enter the while */
76 try_path = (char *)next;
77
78 /* Resolve the canonical path of the first part of the path */
79 while (try_path != NULL && next != end) {
80 /*
81 * If there is not any '/' left, we want to try with
82 * the full path
83 */
84 next = strpbrk(next + 1, "/");
85 if (next == NULL) {
86 next = end;
87 }
88
89 /* Cut the part we will be trying to resolve */
90 cut_path = strndup(path, next - path);
91
92 /* Try to resolve this part */
93 try_path = realpath((char *)cut_path, NULL);
94 if (try_path == NULL) {
95 /*
96 * There was an error, we just want to be assured it
97 * is linked to an unexistent directory, if it's another
98 * reason, we spawn an error
99 */
100 switch (errno) {
101 case ENOENT:
102 /* Ignore the error */
103 break;
104 default:
105 PERROR("realpath (partial_realpath)");
106 goto error;
107 break;
108 }
109 } else {
110 /* Save the place we are before trying the next step */
111 free(try_path_prev);
112 try_path_prev = try_path;
113 prev = next;
114 }
115
116 /* Free the allocated memory */
117 free(cut_path);
118 };
119
120 /* Allocate memory for the resolved path if necessary */
121 if (resolved_path == NULL) {
122 resolved_path = zmalloc(size);
123 if (resolved_path == NULL) {
124 PERROR("zmalloc resolved path");
125 goto error;
126 }
127 }
128
129 /*
130 * If we were able to solve at least partially the path, we can concatenate
131 * what worked and what didn't work
132 */
133 if (try_path_prev != NULL) {
134 /* If we risk to concatenate two '/', we remove one of them */
135 if (try_path_prev[strlen(try_path_prev) - 1] == '/' && prev[0] == '/') {
136 try_path_prev[strlen(try_path_prev) - 1] = '\0';
137 }
138
139 /*
140 * Duplicate the memory used by prev in case resolved_path and
141 * path are pointers for the same memory space
142 */
143 cut_path = strdup(prev);
144
145 /* Concatenate the strings */
146 snprintf(resolved_path, size, "%s%s", try_path_prev, cut_path);
147
148 /* Free the allocated memory */
149 free(cut_path);
150 free(try_path_prev);
151 /*
152 * Else, we just copy the path in our resolved_path to
153 * return it as is
154 */
155 } else {
156 strncpy(resolved_path, path, size);
157 }
158
159 /* Then we return the 'partially' resolved path */
160 return resolved_path;
161
162 error:
163 free(resolved_path);
164 return NULL;
165 }
166
167 /*
168 * Make a full resolution of the given path even if it doesn't exist.
169 * This function uses the utils_partial_realpath function to resolve
170 * symlinks and relatives paths at the start of the string, and
171 * implements functionnalities to resolve the './' and '../' strings
172 * in the middle of a path. This function is only necessary because
173 * realpath(3) does not accept to resolve unexistent paths.
174 * The returned string was allocated in the function, it is thus of
175 * the responsibility of the caller to free this memory.
176 */
177 LTTNG_HIDDEN
178 char *utils_expand_path(const char *path)
179 {
180 char *next, *previous, *slash, *start_path, *absolute_path = NULL;
181 char *last_token;
182 int is_dot, is_dotdot;
183
184 /* Safety net */
185 if (path == NULL) {
186 goto error;
187 }
188
189 /* Allocate memory for the absolute_path */
190 absolute_path = zmalloc(PATH_MAX);
191 if (absolute_path == NULL) {
192 PERROR("zmalloc expand path");
193 goto error;
194 }
195
196 /*
197 * If the path is not already absolute nor explicitly relative,
198 * consider we're in the current directory
199 */
200 if (*path != '/' && strncmp(path, "./", 2) != 0 &&
201 strncmp(path, "../", 3) != 0) {
202 snprintf(absolute_path, PATH_MAX, "./%s", path);
203 /* Else, we just copy the path */
204 } else {
205 strncpy(absolute_path, path, PATH_MAX);
206 }
207
208 /* Resolve partially our path */
209 absolute_path = utils_partial_realpath(absolute_path,
210 absolute_path, PATH_MAX);
211
212 /* As long as we find '/./' in the working_path string */
213 while ((next = strstr(absolute_path, "/./"))) {
214
215 /* We prepare the start_path not containing it */
216 start_path = strndup(absolute_path, next - absolute_path);
217
218 /* And we concatenate it with the part after this string */
219 snprintf(absolute_path, PATH_MAX, "%s%s", start_path, next + 2);
220
221 free(start_path);
222 }
223
224 /* As long as we find '/../' in the working_path string */
225 while ((next = strstr(absolute_path, "/../"))) {
226 /* We find the last level of directory */
227 previous = absolute_path;
228 while ((slash = strpbrk(previous, "/")) && slash != next) {
229 previous = slash + 1;
230 }
231
232 /* Then we prepare the start_path not containing it */
233 start_path = strndup(absolute_path, previous - absolute_path);
234
235 /* And we concatenate it with the part after the '/../' */
236 snprintf(absolute_path, PATH_MAX, "%s%s", start_path, next + 4);
237
238 /* We can free the memory used for the start path*/
239 free(start_path);
240
241 /* Then we verify for symlinks using partial_realpath */
242 absolute_path = utils_partial_realpath(absolute_path,
243 absolute_path, PATH_MAX);
244 }
245
246 /* Identify the last token */
247 last_token = strrchr(absolute_path, '/');
248
249 /* Verify that this token is not a relative path */
250 is_dotdot = (strcmp(last_token, "/..") == 0);
251 is_dot = (strcmp(last_token, "/.") == 0);
252
253 /* If it is, take action */
254 if (is_dot || is_dotdot) {
255 /* For both, remove this token */
256 *last_token = '\0';
257
258 /* If it was a reference to parent directory, go back one more time */
259 if (is_dotdot) {
260 last_token = strrchr(absolute_path, '/');
261
262 /* If there was only one level left, we keep the first '/' */
263 if (last_token == absolute_path) {
264 last_token++;
265 }
266
267 *last_token = '\0';
268 }
269 }
270
271 return absolute_path;
272
273 error:
274 free(absolute_path);
275 return NULL;
276 }
277
278 /*
279 * Create a pipe in dst.
280 */
281 LTTNG_HIDDEN
282 int utils_create_pipe(int *dst)
283 {
284 int ret;
285
286 if (dst == NULL) {
287 return -1;
288 }
289
290 ret = pipe(dst);
291 if (ret < 0) {
292 PERROR("create pipe");
293 }
294
295 return ret;
296 }
297
298 /*
299 * Create pipe and set CLOEXEC flag to both fd.
300 *
301 * Make sure the pipe opened by this function are closed at some point. Use
302 * utils_close_pipe().
303 */
304 LTTNG_HIDDEN
305 int utils_create_pipe_cloexec(int *dst)
306 {
307 int ret, i;
308
309 if (dst == NULL) {
310 return -1;
311 }
312
313 ret = utils_create_pipe(dst);
314 if (ret < 0) {
315 goto error;
316 }
317
318 for (i = 0; i < 2; i++) {
319 ret = fcntl(dst[i], F_SETFD, FD_CLOEXEC);
320 if (ret < 0) {
321 PERROR("fcntl pipe cloexec");
322 goto error;
323 }
324 }
325
326 error:
327 return ret;
328 }
329
330 /*
331 * Create pipe and set fd flags to FD_CLOEXEC and O_NONBLOCK.
332 *
333 * Make sure the pipe opened by this function are closed at some point. Use
334 * utils_close_pipe(). Using pipe() and fcntl rather than pipe2() to
335 * support OSes other than Linux 2.6.23+.
336 */
337 LTTNG_HIDDEN
338 int utils_create_pipe_cloexec_nonblock(int *dst)
339 {
340 int ret, i;
341
342 if (dst == NULL) {
343 return -1;
344 }
345
346 ret = utils_create_pipe(dst);
347 if (ret < 0) {
348 goto error;
349 }
350
351 for (i = 0; i < 2; i++) {
352 ret = fcntl(dst[i], F_SETFD, FD_CLOEXEC);
353 if (ret < 0) {
354 PERROR("fcntl pipe cloexec");
355 goto error;
356 }
357 /*
358 * Note: we override any flag that could have been
359 * previously set on the fd.
360 */
361 ret = fcntl(dst[i], F_SETFL, O_NONBLOCK);
362 if (ret < 0) {
363 PERROR("fcntl pipe nonblock");
364 goto error;
365 }
366 }
367
368 error:
369 return ret;
370 }
371
372 /*
373 * Close both read and write side of the pipe.
374 */
375 LTTNG_HIDDEN
376 void utils_close_pipe(int *src)
377 {
378 int i, ret;
379
380 if (src == NULL) {
381 return;
382 }
383
384 for (i = 0; i < 2; i++) {
385 /* Safety check */
386 if (src[i] < 0) {
387 continue;
388 }
389
390 ret = close(src[i]);
391 if (ret) {
392 PERROR("close pipe");
393 }
394 }
395 }
396
397 /*
398 * Create a new string using two strings range.
399 */
400 LTTNG_HIDDEN
401 char *utils_strdupdelim(const char *begin, const char *end)
402 {
403 char *str;
404
405 str = zmalloc(end - begin + 1);
406 if (str == NULL) {
407 PERROR("zmalloc strdupdelim");
408 goto error;
409 }
410
411 memcpy(str, begin, end - begin);
412 str[end - begin] = '\0';
413
414 error:
415 return str;
416 }
417
418 /*
419 * Set CLOEXEC flag to the give file descriptor.
420 */
421 LTTNG_HIDDEN
422 int utils_set_fd_cloexec(int fd)
423 {
424 int ret;
425
426 if (fd < 0) {
427 ret = -EINVAL;
428 goto end;
429 }
430
431 ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
432 if (ret < 0) {
433 PERROR("fcntl cloexec");
434 ret = -errno;
435 }
436
437 end:
438 return ret;
439 }
440
441 /*
442 * Create pid file to the given path and filename.
443 */
444 LTTNG_HIDDEN
445 int utils_create_pid_file(pid_t pid, const char *filepath)
446 {
447 int ret;
448 FILE *fp;
449
450 assert(filepath);
451
452 fp = fopen(filepath, "w");
453 if (fp == NULL) {
454 PERROR("open pid file %s", filepath);
455 ret = -1;
456 goto error;
457 }
458
459 ret = fprintf(fp, "%d\n", pid);
460 if (ret < 0) {
461 PERROR("fprintf pid file");
462 }
463
464 fclose(fp);
465 DBG("Pid %d written in file %s", pid, filepath);
466 error:
467 return ret;
468 }
469
470 /*
471 * Recursively create directory using the given path and mode.
472 *
473 * On success, return 0 else a negative error code.
474 */
475 LTTNG_HIDDEN
476 int utils_mkdir_recursive(const char *path, mode_t mode)
477 {
478 char *p, tmp[PATH_MAX];
479 size_t len;
480 int ret;
481
482 assert(path);
483
484 ret = snprintf(tmp, sizeof(tmp), "%s", path);
485 if (ret < 0) {
486 PERROR("snprintf mkdir");
487 goto error;
488 }
489
490 len = ret;
491 if (tmp[len - 1] == '/') {
492 tmp[len - 1] = 0;
493 }
494
495 for (p = tmp + 1; *p; p++) {
496 if (*p == '/') {
497 *p = 0;
498 if (tmp[strlen(tmp) - 1] == '.' &&
499 tmp[strlen(tmp) - 2] == '.' &&
500 tmp[strlen(tmp) - 3] == '/') {
501 ERR("Using '/../' is not permitted in the trace path (%s)",
502 tmp);
503 ret = -1;
504 goto error;
505 }
506 ret = mkdir(tmp, mode);
507 if (ret < 0) {
508 if (errno != EEXIST) {
509 PERROR("mkdir recursive");
510 ret = -errno;
511 goto error;
512 }
513 }
514 *p = '/';
515 }
516 }
517
518 ret = mkdir(tmp, mode);
519 if (ret < 0) {
520 if (errno != EEXIST) {
521 PERROR("mkdir recursive last piece");
522 ret = -errno;
523 } else {
524 ret = 0;
525 }
526 }
527
528 error:
529 return ret;
530 }
531
532 /*
533 * Create the stream tracefile on disk.
534 *
535 * Return 0 on success or else a negative value.
536 */
537 LTTNG_HIDDEN
538 int utils_create_stream_file(const char *path_name, char *file_name, uint64_t size,
539 uint64_t count, int uid, int gid, char *suffix)
540 {
541 int ret, out_fd, flags, mode;
542 char full_path[PATH_MAX], *path_name_suffix = NULL, *path;
543 char *extra = NULL;
544
545 assert(path_name);
546 assert(file_name);
547
548 ret = snprintf(full_path, sizeof(full_path), "%s/%s",
549 path_name, file_name);
550 if (ret < 0) {
551 PERROR("snprintf create output file");
552 goto error;
553 }
554
555 /* Setup extra string if suffix or/and a count is needed. */
556 if (size > 0 && suffix) {
557 ret = asprintf(&extra, "_%" PRIu64 "%s", count, suffix);
558 } else if (size > 0) {
559 ret = asprintf(&extra, "_%" PRIu64, count);
560 } else if (suffix) {
561 ret = asprintf(&extra, "%s", suffix);
562 }
563 if (ret < 0) {
564 PERROR("Allocating extra string to name");
565 goto error;
566 }
567
568 /*
569 * If we split the trace in multiple files, we have to add the count at the
570 * end of the tracefile name
571 */
572 if (extra) {
573 ret = asprintf(&path_name_suffix, "%s%s", full_path, extra);
574 if (ret < 0) {
575 PERROR("Allocating path name with extra string");
576 goto error_free_suffix;
577 }
578 path = path_name_suffix;
579 } else {
580 path = full_path;
581 }
582
583 flags = O_WRONLY | O_CREAT | O_TRUNC;
584 /* Open with 660 mode */
585 mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
586
587 if (uid < 0 || gid < 0) {
588 out_fd = open(path, flags, mode);
589 } else {
590 out_fd = run_as_open(path, flags, mode, uid, gid);
591 }
592 if (out_fd < 0) {
593 PERROR("open stream path %s", path);
594 goto error_open;
595 }
596 ret = out_fd;
597
598 error_open:
599 free(path_name_suffix);
600 error_free_suffix:
601 free(extra);
602 error:
603 return ret;
604 }
605
606 /*
607 * Change the output tracefile according to the given size and count The
608 * new_count pointer is set during this operation.
609 *
610 * From the consumer, the stream lock MUST be held before calling this function
611 * because we are modifying the stream status.
612 *
613 * Return 0 on success or else a negative value.
614 */
615 LTTNG_HIDDEN
616 int utils_rotate_stream_file(char *path_name, char *file_name, uint64_t size,
617 uint64_t count, int uid, int gid, int out_fd, uint64_t *new_count,
618 int *stream_fd)
619 {
620 int ret;
621
622 assert(new_count);
623 assert(stream_fd);
624
625 ret = close(out_fd);
626 if (ret < 0) {
627 PERROR("Closing tracefile");
628 goto error;
629 }
630
631 if (count > 0) {
632 *new_count = (*new_count + 1) % count;
633 } else {
634 (*new_count)++;
635 }
636
637 ret = utils_create_stream_file(path_name, file_name, size, *new_count,
638 uid, gid, 0);
639 if (ret < 0) {
640 goto error;
641 }
642 *stream_fd = ret;
643
644 /* Success. */
645 ret = 0;
646
647 error:
648 return ret;
649 }
650
651 /**
652 * Prints the error message corresponding to a regex error code.
653 *
654 * @param errcode The error code.
655 * @param regex The regex object that produced the error code.
656 */
657 static void regex_print_error(int errcode, regex_t *regex)
658 {
659 /* Get length of error message and allocate accordingly */
660 size_t length;
661 char *buffer;
662
663 assert(regex != NULL);
664
665 length = regerror(errcode, regex, NULL, 0);
666 if (length == 0) {
667 ERR("regerror returned a length of 0");
668 return;
669 }
670
671 buffer = zmalloc(length);
672 if (!buffer) {
673 ERR("regex_print_error: zmalloc failed");
674 return;
675 }
676
677 /* Get and print error message */
678 regerror(errcode, regex, buffer, length);
679 ERR("regex error: %s\n", buffer);
680 free(buffer);
681
682 }
683
684 /**
685 * Parse a string that represents a size in human readable format. It
686 * supports decimal integers suffixed by 'k', 'M' or 'G'.
687 *
688 * The suffix multiply the integer by:
689 * 'k': 1024
690 * 'M': 1024^2
691 * 'G': 1024^3
692 *
693 * @param str The string to parse.
694 * @param size Pointer to a size_t that will be filled with the
695 * resulting size.
696 *
697 * @return 0 on success, -1 on failure.
698 */
699 LTTNG_HIDDEN
700 int utils_parse_size_suffix(char *str, uint64_t *size)
701 {
702 regex_t regex;
703 int ret;
704 const int nmatch = 3;
705 regmatch_t suffix_match, matches[nmatch];
706 unsigned long long base_size;
707 long shift = 0;
708
709 if (!str) {
710 return 0;
711 }
712
713 /* Compile regex */
714 ret = regcomp(&regex, "^\\(0x\\)\\{0,1\\}[0-9][0-9]*\\([kKMG]\\{0,1\\}\\)$", 0);
715 if (ret != 0) {
716 regex_print_error(ret, &regex);
717 ret = -1;
718 goto end;
719 }
720
721 /* Match regex */
722 ret = regexec(&regex, str, nmatch, matches, 0);
723 if (ret != 0) {
724 ret = -1;
725 goto free;
726 }
727
728 /* There is a match ! */
729 errno = 0;
730 base_size = strtoull(str, NULL, 0);
731 if (errno != 0) {
732 PERROR("strtoull");
733 ret = -1;
734 goto free;
735 }
736
737 /* Check if there is a suffix */
738 suffix_match = matches[2];
739 if (suffix_match.rm_eo - suffix_match.rm_so == 1) {
740 switch (*(str + suffix_match.rm_so)) {
741 case 'K':
742 case 'k':
743 shift = KIBI_LOG2;
744 break;
745 case 'M':
746 shift = MEBI_LOG2;
747 break;
748 case 'G':
749 shift = GIBI_LOG2;
750 break;
751 default:
752 ERR("parse_human_size: invalid suffix");
753 ret = -1;
754 goto free;
755 }
756 }
757
758 *size = base_size << shift;
759
760 /* Check for overflow */
761 if ((*size >> shift) != base_size) {
762 ERR("parse_size_suffix: oops, overflow detected.");
763 ret = -1;
764 goto free;
765 }
766
767 ret = 0;
768
769 free:
770 regfree(&regex);
771 end:
772 return ret;
773 }
774
775 /*
776 * fls: returns the position of the most significant bit.
777 * Returns 0 if no bit is set, else returns the position of the most
778 * significant bit (from 1 to 32 on 32-bit, from 1 to 64 on 64-bit).
779 */
780 #if defined(__i386) || defined(__x86_64)
781 static inline unsigned int fls_u32(uint32_t x)
782 {
783 int r;
784
785 asm("bsrl %1,%0\n\t"
786 "jnz 1f\n\t"
787 "movl $-1,%0\n\t"
788 "1:\n\t"
789 : "=r" (r) : "rm" (x));
790 return r + 1;
791 }
792 #define HAS_FLS_U32
793 #endif
794
795 #ifndef HAS_FLS_U32
796 static __attribute__((unused)) unsigned int fls_u32(uint32_t x)
797 {
798 unsigned int r = 32;
799
800 if (!x) {
801 return 0;
802 }
803 if (!(x & 0xFFFF0000U)) {
804 x <<= 16;
805 r -= 16;
806 }
807 if (!(x & 0xFF000000U)) {
808 x <<= 8;
809 r -= 8;
810 }
811 if (!(x & 0xF0000000U)) {
812 x <<= 4;
813 r -= 4;
814 }
815 if (!(x & 0xC0000000U)) {
816 x <<= 2;
817 r -= 2;
818 }
819 if (!(x & 0x80000000U)) {
820 x <<= 1;
821 r -= 1;
822 }
823 return r;
824 }
825 #endif
826
827 /*
828 * Return the minimum order for which x <= (1UL << order).
829 * Return -1 if x is 0.
830 */
831 LTTNG_HIDDEN
832 int utils_get_count_order_u32(uint32_t x)
833 {
834 if (!x) {
835 return -1;
836 }
837
838 return fls_u32(x - 1);
839 }
840
841 /**
842 * Obtain the value of LTTNG_HOME environment variable, if exists.
843 * Otherwise returns the value of HOME.
844 */
845 LTTNG_HIDDEN
846 char *utils_get_home_dir(void)
847 {
848 char *val = NULL;
849 val = getenv(DEFAULT_LTTNG_HOME_ENV_VAR);
850 if (val != NULL) {
851 return val;
852 }
853 return getenv(DEFAULT_LTTNG_FALLBACK_HOME_ENV_VAR);
854 }
855
856 /*
857 * With the given format, fill dst with the time of len maximum siz.
858 *
859 * Return amount of bytes set in the buffer or else 0 on error.
860 */
861 LTTNG_HIDDEN
862 size_t utils_get_current_time_str(const char *format, char *dst, size_t len)
863 {
864 size_t ret;
865 time_t rawtime;
866 struct tm *timeinfo;
867
868 assert(format);
869 assert(dst);
870
871 /* Get date and time for session path */
872 time(&rawtime);
873 timeinfo = localtime(&rawtime);
874 ret = strftime(dst, len, format, timeinfo);
875 if (ret == 0) {
876 ERR("Unable to strftime with format %s at dst %p of len %lu", format,
877 dst, len);
878 }
879
880 return ret;
881 }
882
883 /*
884 * Return the group ID matching name, else 0 if it cannot be found.
885 */
886 LTTNG_HIDDEN
887 gid_t utils_get_group_id(const char *name)
888 {
889 struct group *grp;
890
891 grp = getgrnam(name);
892 if (!grp) {
893 static volatile int warn_once;
894
895 if (!warn_once) {
896 WARN("No tracing group detected");
897 warn_once = 1;
898 }
899 return 0;
900 }
901 return grp->gr_gid;
902 }
This page took 0.046773 seconds and 5 git commands to generate.