Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | /* * Regression test for rdar://89017007 */ #include <errno.h> #include <fcntl.h> #include <fts.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <darwintest.h> #include <darwintest_utils.h> /* * Count number of free file descriptors up to the specified limit by * repeatedly trying to allocate one until we hit the specified limit or * run out. */ static int fdcountfree(int, int); static int fdcountfree(int expect, int max) { int count, fd; fd = dup(0); if (fd < 0) { if (errno != EMFILE) T_ASSERT_FAIL("dup(): %s", strerror(errno)); return (0); } while (fd > expect) { T_LOG("file descriptor %d is in use", expect); expect++; } count = 1; if (fd < max) count += fdcountfree(fd + 1, max); close(fd); return (count); } /* * Exercise the bug in FTS. */ static void fts_exercise(char *path, int fts_options) { char *paths[] = { path, NULL }; FTS *fts; FTSENT *ent; int dcount, fcount, indent; fts = fts_open(paths, fts_options, NULL); T_ASSERT_POSIX_NOTNULL(fts, "fts_open()"); dcount = fcount = 0; T_LOG("%s\n", path); indent = 1; for (;;) { if ((ent = fts_read(fts)) == NULL) { T_EXPECT_POSIX_ZERO(errno, "fts_read()"); break; } switch (ent->fts_info) { case FTS_D: T_LOG("%*s%s/\n", indent, "", ent->fts_name); dcount++; indent++; break; case FTS_DP: indent--; break; default: T_LOG("%*s%s\n", indent, "", ent->fts_name); fcount++; break; } if (strcmp(ent->fts_name, "stop") == 0) { T_LOG("stopping"); break; } } T_EXPECT_POSIX_SUCCESS(fts_close(fts), "fts_close()"); T_LOG("%s: %d directories and %d files\n", path, dcount, fcount); } /* * When changing to a directory belonging to a different filesystem than * the current directory, FTS (without the FTS_NOCHDIR option) stores a * file descriptor to the current directory in the child's node, so it can * later reliably return to the previous directory. Verify that this file * descriptor is always closed, regardless of whether it is used. */ T_DECL(fts_fd_leak_xdev, "FTS fd leak when crossing filesystem boundaries", T_META_NAMESPACE("Libc.regression")) { char *tmpdir; int count, saved_count; T_QUIET; count = fdcountfree(3, getdtablesize() - 1); /* move to a temporary directory */ T_SETUPBEGIN; T_ASSERT_POSIX_SUCCESS(asprintf(&tmpdir, "%s/%s-XXXXXX", dt_tmpdir(), T_NAME), NULL); T_ASSERT_POSIX_NOTNULL(mkdtemp(tmpdir), NULL); T_LOG("tmpdir: %s", tmpdir); T_ASSERT_POSIX_SUCCESS(chdir(tmpdir), NULL); T_SETUPEND; /* traverse /dev/fd, which will bring us back to cwd */ fts_exercise("/dev/fd", FTS_PHYSICAL); saved_count = count; count = fdcountfree(3, getdtablesize() - 1); T_EXPECT_EQ(count, saved_count, "file descriptor leak"); /* clean up after us */ (void)remove(tmpdir); free(tmpdir); } /* * When changing to a directory through a symbolic link, FTS (without the * FTS_NOCHDIR option) stores a file descriptor to the current directory * in the child's node, so it can later reliably return to the previous * directory. Verify that this file descriptor is always closed, * regardless of whether it is used. */ T_DECL(fts_fd_leak_symlink, "FTS fd leak when following symbolic links", T_META_NAMESPACE("Libc.regression")) { char *tmpdir; char *dir, *subdir, *file, *link; int count, saved_count; int fd; T_QUIET; count = fdcountfree(3, getdtablesize() - 1); /* move to a temporary directory */ T_SETUPBEGIN; T_ASSERT_POSIX_SUCCESS(asprintf(&tmpdir, "%s/%s-XXXXXX", dt_tmpdir(), T_NAME), NULL); T_ASSERT_POSIX_NOTNULL(mkdtemp(tmpdir), NULL); T_LOG("tmpdir: %s", tmpdir); T_ASSERT_POSIX_SUCCESS(chdir(tmpdir), NULL); T_SETUPEND; /* prepare hierarchy */ T_SETUPBEGIN; T_ASSERT_POSIX_SUCCESS(asprintf(&dir, "%s/dir", tmpdir), NULL); T_LOG("creating %s", dir); T_ASSERT_POSIX_SUCCESS(mkdir(dir, 0755), NULL); T_ASSERT_POSIX_SUCCESS(asprintf(&subdir, "%s/subdir", dir), NULL); T_LOG("creating %s", subdir); T_ASSERT_POSIX_SUCCESS(mkdir(subdir, 0755), NULL); T_ASSERT_POSIX_SUCCESS(asprintf(&link, "%s/link", dir), NULL); T_LOG("creating %s -> %s", link, subdir); T_ASSERT_POSIX_SUCCESS(symlink(subdir, link), NULL); T_SETUPEND; /* traverse a directory that contains a symlink to another directory */ fts_exercise(dir, FTS_LOGICAL); saved_count = count; count = fdcountfree(3, getdtablesize() - 1); T_EXPECT_EQ(count, saved_count, "file descriptor leak"); /* traverse a directory which you reach through a symlink */ fts_exercise(link, FTS_LOGICAL); saved_count = count; count = fdcountfree(3, getdtablesize() - 1); T_EXPECT_EQ(count, saved_count, "file descriptor leak"); /* follow a symlink, then fts_close() */ T_SETUPBEGIN; T_ASSERT_POSIX_SUCCESS(asprintf(&file, "%s/stop", subdir), NULL); T_LOG("creating %s", file); T_ASSERT_POSIX_SUCCESS(fd = creat(file, 0644), NULL); T_ASSERT_POSIX_SUCCESS(close(fd), NULL); T_SETUPEND; fts_exercise(dir, FTS_LOGICAL); saved_count = count; count = fdcountfree(3, getdtablesize() - 1); T_EXPECT_EQ(count, saved_count, "file descriptor leak"); /* clean up after us */ (void)remove(file); free(file); (void)remove(link); free(link); (void)remove(subdir); free(subdir); (void)remove(dir); free(dir); (void)remove(tmpdir); free(tmpdir); } |