|
2 | 2 | #include <stdio.h> |
3 | 3 | #include <stdlib.h> |
4 | 4 | #include <string.h> |
| 5 | +#include <signal.h> |
5 | 6 | #include <unistd.h> |
6 | 7 | #include <fcntl.h> |
7 | 8 | #include <errno.h> |
@@ -164,6 +165,177 @@ int main(int argc, char **argv, char **envp) { |
164 | 165 | failures += 2; |
165 | 166 | } |
166 | 167 |
|
| 168 | + /* ⭐⭐ THE DISPOSITION OF A SIGNAL IS TOUCHED, WHICH NOTHING HERE DID. |
| 169 | + * |
| 170 | + * This file had thirty-six observations and three of them were about |
| 171 | + * `abort'. It contained no call to `signal' or `sigaction' anywhere --- so |
| 172 | + * it examined whether `abort' ENDS the program and never whether a program |
| 173 | + * may ASK what a signal is set to. A defect that killed any program doing |
| 174 | + * the second passed every one of the thirty-six. |
| 175 | + * |
| 176 | + * ⚠️ ALL THREE FORMS, AND SIGABRT AMONG THEM. The C library takes a lock for |
| 177 | + * any change to that one disposition and blocks signals to take it, so |
| 178 | + * SIGABRT reaches code the others do not --- and the enquiry, which changes |
| 179 | + * nothing, reached it too. Two of the three forms below would have passed |
| 180 | + * while the third killed the process. */ |
| 181 | + { |
| 182 | + int survived = 1; |
| 183 | + for (int sig = 1; sig < 32; sig++) { |
| 184 | + if (sig == SIGKILL || sig == SIGSTOP) continue; |
| 185 | + struct sigaction seen; |
| 186 | + memset(&seen, 0, sizeof seen); |
| 187 | + /* An enquiry, which changes nothing. */ |
| 188 | + const int q = sigaction(sig, NULL, &seen); |
| 189 | + if (q != 0 && errno != ENOSYS) survived = 0; |
| 190 | + /* Ignoring, which every environment can express. */ |
| 191 | + errno = 0; |
| 192 | + signal(sig, SIG_IGN); |
| 193 | + if (errno != 0 && errno != ENOSYS) survived = 0; |
| 194 | + /* A handler, which this environment cannot deliver and refuses. */ |
| 195 | + errno = 0; |
| 196 | + if (signal(sig, SIG_DFL) == SIG_ERR && errno != ENOSYS) survived = 0; |
| 197 | + } |
| 198 | + check(survived, "every signal's disposition may be read and written or refused"); |
| 199 | + } |
| 200 | + |
| 201 | + /* Nodes whose content is another name, where the volume has them. */ |
| 202 | + { |
| 203 | + /* The names this block uses are its own: the file the earlier |
| 204 | + * observations made has been removed by the time this runs, and a probe |
| 205 | + * that depended on another probe's leftovers would report an absence as |
| 206 | + * a defect. */ |
| 207 | + { FILE *t = fopen("okm-link-target.tmp", "w"); if (t) { fputs("0123456789", t); fclose(t); } } |
| 208 | + unlink("okm-probe-link"); |
| 209 | + const int made = symlink("okm-link-target.tmp", "okm-probe-link"); |
| 210 | + if (made == 0) { |
| 211 | + char target[64] = { 0 }; |
| 212 | + const ssize_t got = readlink("okm-probe-link", target, sizeof target - 1); |
| 213 | + check(got == (ssize_t)strlen("okm-link-target.tmp") |
| 214 | + && strcmp(target, "okm-link-target.tmp") == 0, |
| 215 | + "a node's content reads back as it was written"); |
| 216 | + |
| 217 | + /* ⭐ THE OBSERVATION THE PORT MOST NEEDED. Asking resolves and |
| 218 | + * opening resolves, so the two agree; asking with the flag reports |
| 219 | + * the node itself. They disagreed, and a C++ library above reported |
| 220 | + * a link where a caller would have reached a file. */ |
| 221 | + struct stat followed, itself; |
| 222 | + check(stat("okm-probe-link", &followed) == 0 && S_ISREG(followed.st_mode), |
| 223 | + "stat resolves, and reports what the name finally refers to"); |
| 224 | + check(lstat("okm-probe-link", &itself) == 0 && S_ISLNK(itself.st_mode), |
| 225 | + "lstat reports the node itself"); |
| 226 | + |
| 227 | + /* ⭐⭐ AND THE THIRD QUESTION, WHICH IS NEITHER OF THOSE TWO. |
| 228 | + * |
| 229 | + * O_NOFOLLOW does not ask to open the link and does not ask to |
| 230 | + * open its target: it asks `is this name a link?' and expects |
| 231 | + * ELOOP when it is. openkal offers no opening that declines to |
| 232 | + * resolve --- by design --- so this port resolved, and for a link |
| 233 | + * to a name that is absent it answered ENOENT. |
| 234 | + * |
| 235 | + * ⚠️ THAT IS A DIFFERENT ANSWER TO A DIFFERENT QUESTION, AND |
| 236 | + * NOTHING NEARBY LOOKED WRONG. Every operation above still held. |
| 237 | + * What failed was three layers up: libc++'s `remove_all' descends |
| 238 | + * by opening each entry O_DIRECTORY|O_NOFOLLOW and reads ENOENT as |
| 239 | + * `it is already gone', so it unlinked nothing and then reported |
| 240 | + * ENOTEMPTY for a directory it had just declined to empty. The |
| 241 | + * host toolchain removed the same tree. |
| 242 | + * |
| 243 | + * The answer comes from the enquiry openkal 0.9 added: ask about |
| 244 | + * the name itself. Both cases are checked because they fail |
| 245 | + * differently --- a live target resolved to a FILE and returned a |
| 246 | + * descriptor, which is not an error at all. */ |
| 247 | + int nf = open("okm-probe-link", O_RDONLY | O_NOFOLLOW); |
| 248 | + check(nf < 0 && errno == ELOOP, |
| 249 | + "opening a link with O_NOFOLLOW reports that it is a link"); |
| 250 | + if (nf >= 0) close(nf); |
| 251 | + |
| 252 | + unlink("okm-probe-target-gone"); |
| 253 | + unlink("okm-probe-dangling"); |
| 254 | + if (symlink("okm-probe-target-gone", "okm-probe-dangling") == 0) { |
| 255 | + nf = open("okm-probe-dangling", O_RDONLY | O_NOFOLLOW); |
| 256 | + check(nf < 0 && errno == ELOOP, |
| 257 | + "and does so for a link whose target is absent, rather than ENOENT"); |
| 258 | + if (nf >= 0) close(nf); |
| 259 | + check(unlink("okm-probe-dangling") == 0, |
| 260 | + "a link whose target is absent is still removable"); |
| 261 | + } |
| 262 | + |
| 263 | + unlink("okm-probe-link"); |
| 264 | + } else if (errno == ENOSYS || errno == EPERM) { |
| 265 | + printf("ok: this volume has no nodes that name others, which it reported\n"); |
| 266 | + } else { |
| 267 | + printf("FAIL: making a node that names another (errno %d)\n", errno); |
| 268 | + failures++; |
| 269 | + } |
| 270 | + unlink("okm-link-target.tmp"); |
| 271 | + } |
| 272 | + |
| 273 | + /* ⚠️ Two different files are two different files. `st_dev' and `st_ino' |
| 274 | + * were constants, so every file compared equal to every other and a C++ |
| 275 | + * library's `equivalent' answered true with no error. |
| 276 | + * |
| 277 | + * ⚠️⚠️ WHEN THIS FAILS, THE DEFECT IS USUALLY NOT IN THIS PACKAGE. This |
| 278 | + * port copies the identity out of `kal_node_info' and puts zero there when |
| 279 | + * the implementation does not report one --- which is permitted, and which |
| 280 | + * makes every node compare equal to every other. So a failure here says |
| 281 | + * "the openkal implementation beneath this one declined to report an |
| 282 | + * identity", and the place to look is its `kal_fs_info'. |
| 283 | + * |
| 284 | + * Measured: it failed on Windows, and openkal-windows was reading a volume |
| 285 | + * serial number the object manager had written and then discarding it, |
| 286 | + * because the enquiry reported STATUS_BUFFER_OVERFLOW for a volume label |
| 287 | + * that did not fit and the implementation read that as a failure. The |
| 288 | + * conformance suite could not have said so: an implementation is allowed |
| 289 | + * to decline the field, so the suite reports the observation as one it did |
| 290 | + * not make. This is the criterion that notices, and it is two packages |
| 291 | + * away from the defect. */ |
| 292 | + { |
| 293 | + struct stat x, y; |
| 294 | + FILE *fx = fopen("okm-probe-x.tmp", "w"); if (fx) fclose(fx); |
| 295 | + FILE *fy = fopen("okm-probe-y.tmp", "w"); if (fy) fclose(fy); |
| 296 | + check(stat("okm-probe-x.tmp", &x) == 0 && stat("okm-probe-y.tmp", &y) == 0 |
| 297 | + && !(x.st_dev == y.st_dev && x.st_ino == y.st_ino), |
| 298 | + "two different files have different identities"); |
| 299 | + unlink("okm-probe-x.tmp"); |
| 300 | + unlink("okm-probe-y.tmp"); |
| 301 | + } |
| 302 | + |
| 303 | + /* A value POSIX says cannot fail is not a negated error. */ |
| 304 | + check(getpgrp() > 0, "the process group is a number and not a negated error"); |
| 305 | + |
| 306 | + /* The page is the machine's and not the build's. |
| 307 | + * |
| 308 | + * ⚠️⚠️ AND "POSITIVE POWER OF TWO" WAS TRUE OF THE VALUE THAT BROKE IT. |
| 309 | + * This library took `kal_memory_granularity()' as its page size, and an |
| 310 | + * implementation for a machine with no memory management unit answers ONE |
| 311 | + * --- correctly, since nothing there needs rounding. One is positive and |
| 312 | + * one is a power of two, so this assertion held while the allocator asked |
| 313 | + * the environment for one-byte extents and the program stopped inside the |
| 314 | + * first allocation that needed a new one. |
| 315 | + * |
| 316 | + * ⭐ SO THE CRITERION IS WHAT THE ALLOCATOR REQUIRES, NOT WHAT THE NUMBER |
| 317 | + * LOOKS LIKE. A page smaller than this library's own quantum is not a page |
| 318 | + * this library can use, whatever openkal reports. */ |
| 319 | + { |
| 320 | + const long page = sysconf(_SC_PAGESIZE); |
| 321 | + check(page >= 4096 && (page & (page - 1)) == 0, |
| 322 | + "the page size is a power of two no smaller than the allocator's quantum"); |
| 323 | + |
| 324 | + /* And the property the number exists to have. Several pages, written |
| 325 | + * end to end: the allocation this library rounds to `page' and the |
| 326 | + * memory it hands back are the same memory. */ |
| 327 | + const size_t span = (size_t)page * 4 + 17; |
| 328 | + unsigned char *big = malloc(span); |
| 329 | + int whole = big != NULL; |
| 330 | + if (big) { |
| 331 | + for (size_t i = 0; i < span; i++) big[i] = (unsigned char)(i * 31u); |
| 332 | + for (size_t i = 0; i < span; i++) |
| 333 | + if (big[i] != (unsigned char)(i * 31u)) { whole = 0; break; } |
| 334 | + free(big); |
| 335 | + } |
| 336 | + check(whole, "several pages are obtained in one allocation and every byte of it holds"); |
| 337 | + } |
| 338 | + |
167 | 339 | printf("-- failures: %d --\n", failures); |
168 | 340 | return failures ? 1 : 0; |
169 | 341 | } |
0 commit comments