Backport: Test: add a unit test for the fd tracker
[lttng-tools.git] / tests / unit / test_fd_tracker.c
1 /*
2 * Copyright (c) - 2018 Jérémie Galarneau <jeremie.galarneau@efficios.com>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by as
6 * published by the Free Software Foundation; only version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program; if not, write to the Free Software Foundation, Inc., 51
15 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 */
17
18 #include <stdlib.h>
19 #include <inttypes.h>
20 #include <stdbool.h>
21 #include <assert.h>
22 #include <string.h>
23 #include <stdarg.h>
24 #include <tap/tap.h>
25 #include <sys/types.h>
26 #include <dirent.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/stat.h>
32
33 #include <common/fd-tracker/fd-tracker.h>
34 #include <common/error.h>
35
36 /* For error.h */
37 int lttng_opt_quiet = 1;
38 int lttng_opt_verbose;
39 int lttng_opt_mi;
40
41 /* Number of TAP tests in this file */
42 #define NUM_TESTS 35
43 /* 3 for stdin, stdout, and stderr */
44 #define STDIO_FD_COUNT 3
45 #define TRACKER_FD_LIMIT 50
46 #define TMP_DIR_PATTERN "/tmp/fd-tracker-XXXXXX"
47
48 /*
49 * Count of fds, beyond stdin, stderr, stdout that were open
50 * at the launch of the test. This allows the test to succeed when
51 * run by automake's test runner or valgrind which both open
52 * fds behind our back.
53 */
54 int unknown_fds_count;
55
56 const char file_contents[] = "Bacon ipsum dolor amet jerky drumstick sirloin "
57 "strip steak venison boudin filet mignon picanha doner shoulder. "
58 "Strip steak brisket alcatra, venison beef chuck cupim pastrami. "
59 "Landjaeger tri-tip salami leberkas ball tip, ham hock chuck sausage "
60 "flank jerky cupim. Pig bacon chuck pancetta andouille.";
61
62 int fd_count(void)
63 {
64 DIR *dir;
65 struct dirent *entry;
66 int count = 0;
67
68 dir = opendir("/proc/self/fd");
69 if (!dir) {
70 perror("# Failed to enumerate /proc/self/fd/ to count the number of used file descriptors");
71 count = -1;
72 goto end;
73 }
74
75 while ((entry = readdir(dir)) != NULL) {
76 if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
77 continue;
78 }
79 count++;
80 }
81 /* Don't account for the file descriptor opened by opendir(). */
82 count--;
83 closedir(dir);
84 end:
85 return count;
86 }
87
88 static
89 void check_fd_count(int expected_count)
90 {
91 int count = 0;
92
93 count = fd_count();
94 ok(count == expected_count, "Expected %d open file descriptors (%d are open)",
95 expected_count, count);
96 }
97
98 static
99 int noop_open(void *data, int *fds)
100 {
101 *fds = *((int *) data);
102 return 0;
103 }
104
105 static
106 int noop_close(void *data, int *fds)
107 {
108 return 0;
109 }
110
111 static
112 void track_std_fds(struct fd_tracker *tracker)
113 {
114 int i;
115 struct { int fd; const char *name; } files[] = {
116 { .fd = fileno(stdin), .name = "stdin" },
117 { .fd = fileno(stdout), .name = "stdout" },
118 { .fd = fileno(stderr), .name = "stderr" },
119 };
120
121 for (i = 0; i < sizeof(files) / sizeof(*files); i++) {
122 int out_fd, ret;
123
124 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
125 &files[i].name, 1, noop_open, &files[i].fd);
126 assert(out_fd == files[i].fd);
127
128 ok(ret == 0, "Track unsuspendable fd %d (%s)", files[i].fd,
129 files[i].name);
130 }
131 }
132
133 static
134 void untrack_std_fds(struct fd_tracker *tracker)
135 {
136 int i;
137 struct { int fd; const char *name; } files[] = {
138 { .fd = fileno(stdin), .name = "stdin" },
139 { .fd = fileno(stdout), .name = "stdout" },
140 { .fd = fileno(stderr), .name = "stderr" },
141 };
142 unsigned int fds_set_to_minus_1 = 0;
143
144 for (i = 0; i < sizeof(files) / sizeof(*files); i++) {
145 int fd = files[i].fd;
146 int ret = fd_tracker_close_unsuspendable_fd(tracker,
147 &files[i].fd, 1, noop_close, NULL);
148
149 ok(ret == 0, "Untrack unsuspendable fd %d (%s)", fd,
150 files[i].name);
151 fds_set_to_minus_1 += (files[i].fd == -1);
152 }
153 }
154
155 /*
156 * Basic test opening and closing three unsuspendable fds.
157 */
158 static
159 void test_unsuspendable_basic(void)
160 {
161 struct fd_tracker *tracker;
162
163 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
164 ok(tracker, "Created an fd tracker with a limit of %d simulateously opened file descriptors",
165 TRACKER_FD_LIMIT);
166 if (!tracker) {
167 return;
168 }
169
170 track_std_fds(tracker);
171 untrack_std_fds(tracker);
172
173 fd_tracker_destroy(tracker);
174 }
175
176 static
177 int error_open(void *data, int *fds)
178 {
179 return *((int *) data);
180 }
181
182 static
183 int error_close(void *data, int *fds)
184 {
185 return *((int *) data);
186 }
187
188 /*
189 * Validate that user callback return values are returned to the
190 * caller of the fd tracker.
191 */
192 static
193 void test_unsuspendable_cb_return(void)
194 {
195 int ret, stdout_fd = fileno(stdout), out_fd = 42;
196 struct fd_tracker *tracker;
197 int expected_error = -ENETDOWN;
198
199 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
200 assert(tracker);
201
202 /* The error_open callback should fail and return 'expected_error'. */
203 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
204 NULL, 1, error_open, &expected_error);
205 ok(ret == expected_error, "fd_tracker_open_unsuspendable_fd() forwards the user callback's error code");
206 ok(out_fd == 42, "Output fd parameter is unaffected on error of fd_tracker_open_unsuspendable_fd()");
207
208 /*
209 * Track a valid fd since we don't want the tracker to fail with an
210 * invalid fd error for this test.
211 */
212 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
213 NULL, 1, noop_open, &stdout_fd);
214 ok(out_fd == stdout_fd, "fd_tracker_open_unsuspendable_fd() sets the output fd parameter to the newly-tracked fd's value");
215 assert(!ret);
216
217 ret = fd_tracker_close_unsuspendable_fd(tracker,
218 &stdout_fd, 1, error_close, &expected_error);
219 ok(ret == expected_error, "fd_tracker_close_unsuspendable_fd() forwards the user callback's error code");
220 ret = fd_tracker_close_unsuspendable_fd(tracker,
221 &stdout_fd, 1, noop_close, &expected_error);
222 assert(!ret);
223
224 fd_tracker_destroy(tracker);
225 }
226
227 /*
228 * Validate that the tracker refuses to track two identical unsuspendable
229 * file descriptors.
230 */
231 static
232 void test_unsuspendable_duplicate(void)
233 {
234 int ret, stdout_fd = fileno(stdout), out_fd;
235 struct fd_tracker *tracker;
236
237 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
238 assert(tracker);
239
240 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
241 NULL, 1, noop_open, &stdout_fd);
242 assert(!ret);
243 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
244 NULL, 1, noop_open, &stdout_fd);
245 ok(ret == -EEXIST, "EEXIST reported on open of an already tracked file descriptor");
246
247 ret = fd_tracker_close_unsuspendable_fd(tracker,
248 &stdout_fd, 1, noop_close, NULL);
249 assert(!ret);
250
251 fd_tracker_destroy(tracker);
252 }
253
254 static
255 int open_pipes(void *data, int *out_fds)
256 {
257 unsigned int i;
258 const unsigned int pipe_count = TRACKER_FD_LIMIT / 2;
259
260 for (i = 0; i < pipe_count; i++) {
261 int ret = pipe(&out_fds[i * 2]);
262
263 if (ret) {
264 return -errno;
265 }
266 }
267 return 0;
268 }
269
270 static
271 int close_pipes(void *data, int *fds)
272 {
273 int i;
274 int *pipes = fds;
275
276 for (i = 0; i < TRACKER_FD_LIMIT; i++) {
277 int ret = close(pipes[i]);
278
279 if (ret) {
280 return -errno;
281 }
282 }
283 return 0;
284 }
285
286 /*
287 * Validate that the tracker enforces the open file descriptor limit
288 * when unsuspendable file descritptors are being opened.
289 */
290 static
291 void test_unsuspendable_limit(void)
292 {
293 struct fd_tracker *tracker;
294 int ret, stdout_fd = fileno(stdout), out_fd;
295 int fds[TRACKER_FD_LIMIT];
296
297 /* This test assumes TRACKER_FD_LIMIT is a multiple of 2. */
298 assert((TRACKER_FD_LIMIT % 2 == 0) && TRACKER_FD_LIMIT);
299
300 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
301 assert(tracker);
302
303 ret = fd_tracker_open_unsuspendable_fd(tracker, fds,
304 NULL, TRACKER_FD_LIMIT, open_pipes, NULL);
305 ok(ret == 0, "File descriptor tracker allowed the user to meet its limit with unsuspendable file descritptors (%d)",
306 TRACKER_FD_LIMIT);
307
308 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
309 NULL, 1, noop_open, &stdout_fd);
310 ok(ret == -EMFILE, "EMFILE reported when exceeding the file descriptor limit while opening an unsuspendable fd");
311
312 ret = fd_tracker_close_unsuspendable_fd(tracker,
313 fds, TRACKER_FD_LIMIT, close_pipes, NULL);
314 assert(!ret);
315
316 fd_tracker_destroy(tracker);
317 }
318
319 /*
320 * Validate that the tracker refuses to track two identical unsuspendable
321 * file descriptors.
322 */
323 static
324 void test_unsuspendable_close_untracked(void)
325 {
326 int ret, stdout_fd = fileno(stdout), unknown_fds[2], out_fd;
327 struct fd_tracker *tracker;
328
329 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
330 if (!tracker) {
331 return;
332 }
333
334 ret = pipe(unknown_fds);
335 assert(!ret);
336 assert(close(unknown_fds[0]) == 0);
337 assert(close(unknown_fds[1]) == 0);
338
339 ret = fd_tracker_open_unsuspendable_fd(tracker, &out_fd,
340 NULL, 1, noop_open, &stdout_fd);
341 assert(!ret);
342
343 ret = fd_tracker_close_unsuspendable_fd(tracker,
344 unknown_fds, 1, noop_close, NULL);
345 ok(ret == -EINVAL, "EINVAL reported on close of an untracked file descriptor");
346
347 ret = fd_tracker_close_unsuspendable_fd(tracker,
348 &stdout_fd, 1, noop_close, NULL);
349 assert(!ret);
350
351 fd_tracker_destroy(tracker);
352 }
353
354 static
355 int open_files(struct fd_tracker *tracker, const char *dir, unsigned int count,
356 struct fs_handle **handles, char **file_paths)
357 {
358 int ret = 0;
359 unsigned int i;
360
361 for (i = 0; i < count; i++) {
362 int ret;
363 char *file_path;
364 struct fs_handle *handle;
365 mode_t mode = S_IWUSR | S_IRUSR;
366
367 ret = asprintf(&file_path, "%s/file-%u", dir, i);
368 assert(ret >= 0);
369 file_paths[i] = file_path;
370
371 handle = fd_tracker_open_fs_handle(tracker, file_path,
372 O_RDWR | O_CREAT, &mode);
373 if (!handle) {
374 ret = -1;
375 break;
376 }
377 handles[i] = handle;
378 }
379 return ret;
380 }
381
382 static
383 int cleanup_files(struct fd_tracker *tracker, const char *dir,
384 unsigned int count, struct fs_handle **handles,
385 char **file_paths)
386 {
387 int ret = 0;
388 unsigned int i;
389
390 for (i = 0; i < count; i++) {
391 char *file_path = file_paths[i];
392
393 if (!file_path) {
394 break;
395 }
396 (void) unlink(file_path);
397 free(file_path);
398 if (fs_handle_close(handles[i])) {
399 ret = -1;
400 }
401 }
402 return ret;
403 }
404
405 static
406 void test_suspendable_limit(void)
407 {
408 int ret;
409 const int files_to_create = TRACKER_FD_LIMIT * 10;
410 struct fd_tracker *tracker;
411 char tmp_path_pattern[] = TMP_DIR_PATTERN;
412 const char *output_dir;
413 char *output_files[files_to_create];
414 struct fs_handle *handles[files_to_create];
415
416 memset(output_files, 0, sizeof(output_files));
417 memset(handles, 0, sizeof(handles));
418
419 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
420 if (!tracker) {
421 return;
422 }
423
424 output_dir = mkdtemp(tmp_path_pattern);
425 if (!output_dir) {
426 diag("Failed to create temporary path of the form %s",
427 TMP_DIR_PATTERN);
428 assert(0);
429 }
430
431 ret = open_files(tracker, output_dir, files_to_create, handles,
432 output_files);
433 ok(!ret, "Created %d files with a limit of %d simultaneously-opened file descriptor",
434 files_to_create, TRACKER_FD_LIMIT);
435 check_fd_count(TRACKER_FD_LIMIT + STDIO_FD_COUNT + unknown_fds_count);
436
437 ret = cleanup_files(tracker, output_dir, files_to_create, handles,
438 output_files);
439 ok(!ret, "Close all opened filesystem handles");
440 (void) rmdir(output_dir);
441 fd_tracker_destroy(tracker);
442 }
443
444 static
445 void test_mixed_limit(void)
446 {
447 int ret;
448 const int files_to_create = TRACKER_FD_LIMIT;
449 struct fd_tracker *tracker;
450 char tmp_path_pattern[] = TMP_DIR_PATTERN;
451 const char *output_dir;
452 char *output_files[files_to_create];
453 struct fs_handle *handles[files_to_create];
454
455 memset(output_files, 0, sizeof(output_files));
456 memset(handles, 0, sizeof(handles));
457
458 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
459 if (!tracker) {
460 return;
461 }
462
463 output_dir = mkdtemp(tmp_path_pattern);
464 if (!output_dir) {
465 diag("Failed to create temporary path of the form %s",
466 TMP_DIR_PATTERN);
467 assert(0);
468 }
469
470 ret = open_files(tracker, output_dir, files_to_create, handles,
471 output_files);
472 ok(!ret, "Created %d files with a limit of %d simultaneously-opened file descriptor",
473 files_to_create, TRACKER_FD_LIMIT);
474 diag("Check file descriptor count after opening %u files", files_to_create);
475 check_fd_count(TRACKER_FD_LIMIT + STDIO_FD_COUNT + unknown_fds_count);
476
477 /*
478 * Open unsuspendable fds (stdin, stdout, stderr) and verify that the fd
479 * cap is still respected.
480 */
481 diag("Check file descriptor count after adding %d unsuspendable fds",
482 STDIO_FD_COUNT);
483 track_std_fds(tracker);
484 check_fd_count(TRACKER_FD_LIMIT + unknown_fds_count);
485 diag("Untrack unsuspendable file descriptors");
486 untrack_std_fds(tracker);
487 check_fd_count(TRACKER_FD_LIMIT + unknown_fds_count);
488
489 ret = cleanup_files(tracker, output_dir, files_to_create, handles,
490 output_files);
491 ok(!ret, "Close all opened filesystem handles");
492 (void) rmdir(output_dir);
493 fd_tracker_destroy(tracker);
494 }
495
496 /*
497 * Open more files than allowed by the fd tracker's cap and write,
498 * byte-by-byte, and in round-robin, a string. The goal is to force
499 * the fd tracker to suspend and resume the fs_handles often and
500 * verify that the fd cap is always respected.
501 *
502 * The content of the files is also verified at the end.
503 */
504 static
505 void test_suspendable_restore(void)
506 {
507 int ret;
508 const int files_to_create = TRACKER_FD_LIMIT * 10;
509 struct fd_tracker *tracker;
510 char tmp_path_pattern[] = TMP_DIR_PATTERN;
511 const char *output_dir;
512 char *output_files[files_to_create];
513 struct fs_handle *handles[files_to_create];
514 size_t content_index;
515 int handle_index;
516 bool write_success = true;
517 bool fd_cap_respected = true;
518 bool content_ok = true;
519
520 memset(output_files, 0, sizeof(output_files));
521 memset(handles, 0, sizeof(handles));
522
523 tracker = fd_tracker_create(TRACKER_FD_LIMIT);
524 if (!tracker) {
525 return;
526 }
527
528 output_dir = mkdtemp(tmp_path_pattern);
529 if (!output_dir) {
530 diag("Failed to create temporary path of the form %s",
531 TMP_DIR_PATTERN);
532 assert(0);
533 }
534
535 ret = open_files(tracker, output_dir, files_to_create, handles,
536 output_files);
537 ok(!ret, "Created %d files with a limit of %d simultaneously-opened file descriptor",
538 files_to_create, TRACKER_FD_LIMIT);
539 diag("Check file descriptor count after opening %u files", files_to_create);
540 check_fd_count(TRACKER_FD_LIMIT + STDIO_FD_COUNT + unknown_fds_count);
541
542 for (content_index = 0; content_index < sizeof(file_contents); content_index++) {
543 for (handle_index = 0; handle_index < files_to_create; handle_index++) {
544 int fd;
545 struct fs_handle *handle = handles[handle_index];
546 const char *path = output_files[handle_index];
547
548 fd = fs_handle_get_fd(handle);
549 if (fd < 0) {
550 write_success = false;
551 diag("Failed to restore fs_handle to %s",
552 path);
553 goto skip_write;
554 }
555
556 do {
557 ret = write(fd, file_contents + content_index, 1);
558 } while (ret < 0 && errno == EINTR);
559
560 if (ret != 1) {
561 write_success = false;
562 PERROR("write() to %s failed", path);
563 goto skip_write;
564 }
565
566 if (fd_count() >
567 (TRACKER_FD_LIMIT + STDIO_FD_COUNT +
568 unknown_fds_count)) {
569 fd_cap_respected = false;
570 }
571
572 fs_handle_put_fd(handle);
573 }
574 }
575 skip_write:
576 ok(write_success, "Wrote reference string to %d files",
577 files_to_create);
578 ok(fd_cap_respected, "FD tracker enforced the file descriptor cap");
579
580 /* Validate the contents of the files. */
581 for (handle_index = 0; handle_index < files_to_create; handle_index++) {
582 struct stat fd_stat;
583 const char *path = output_files[handle_index];
584 char read_buf[sizeof(file_contents)];
585 char *read_pos;
586 size_t to_read = sizeof(read_buf);
587 int fd;
588
589 fd = open(path, O_RDONLY);
590 assert(fd >= 0);
591 ret = fstat(fd, &fd_stat);
592 assert(!ret);
593 if (fd_stat.st_size != sizeof(file_contents)) {
594 diag("Content size of file %s doesn't match, got %" PRId64 ", expected %zu",
595 path, (int64_t) fd_stat.st_size,
596 sizeof(file_contents));
597 content_ok = false;
598 (void) close(fd);
599 break;
600 }
601
602 read_pos = read_buf;
603 do {
604 ret = read(fd, read_pos, to_read);
605 if (ret > 0) {
606 to_read -= ret;
607 read_pos += ret;
608 }
609 } while (to_read && (ret < 0 && errno == EINTR));
610 if (ret < 0) {
611 content_ok = false;
612 PERROR("Failed to read file %s", path);
613 (void) close(fd);
614 break;
615 }
616
617 if (strcmp(file_contents, read_buf)) {
618 content_ok = false;
619 diag("File content doesn't match the expectated string");
620 (void) close(fd);
621 break;
622 }
623 (void) close(fd);
624 }
625 ok(content_ok, "Files contain the expected content");
626 ret = cleanup_files(tracker, output_dir, files_to_create, handles,
627 output_files);
628 ok(!ret, "Close all opened filesystem handles");
629 (void) rmdir(output_dir);
630 fd_tracker_destroy(tracker);
631 }
632
633 int main(int argc, char **argv)
634 {
635 plan_tests(NUM_TESTS);
636 diag("File descriptor tracker unit tests");
637
638 unknown_fds_count = fd_count() - STDIO_FD_COUNT;
639 assert(unknown_fds_count >= 0);
640
641 diag("Unsuspendable - basic");
642 test_unsuspendable_basic();
643 diag("Unsuspendable - callback return values");
644 test_unsuspendable_cb_return();
645 diag("Unsuspendable - duplicate file descriptors");
646 test_unsuspendable_duplicate();
647 diag("Unsuspendable - closing an untracked file descriptor");
648 test_unsuspendable_close_untracked();
649 diag("Unsuspendable - check that file descritptor limit is enforced");
650 test_unsuspendable_limit();
651
652 diag("Suspendable - check that file descritptor limit is enforced");
653 test_suspendable_limit();
654 diag("Suspendable - restoration test");
655 test_suspendable_restore();
656
657 diag("Mixed - check that file descritptor limit is enforced");
658 test_mixed_limit();
659
660 return exit_status();
661 }
This page took 0.044586 seconds and 5 git commands to generate.