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