The Pedigree Project 0.1
inotify-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 */
8
9#include "pedigree/kernel/Atomic.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/errors.h"
12#include "pedigree/kernel/process/Process.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/Semaphore.h"
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/processor/Processor.h"
17
18#include <fcntl.h>
19
20#include "modules/subsys/posix/FileDescriptor.h"
21#include "modules/subsys/posix/PosixSubsystem.h"
22#include "modules/subsys/posix/epoll-syscalls.h"
23#include "modules/subsys/posix/file-syscalls.h"
24#include "modules/subsys/posix/inotify-syscalls.h"
25#include "modules/system/vfs/Directory.h"
26#include "modules/system/vfs/File.h"
27#include "modules/system/vfs/VFS.h"
28
29namespace {
30constexpr size_t HostedAttempts = 10000;
31constexpr uint64_t InotifyEpollData = 0x494E4F5449465901ULL;
32
33class InotifyLifetimeFile final : public File {
34 public:
35 explicit InotifyLifetimeFile(Atomic<size_t>& destructions)
36 : File(String("oneshot"), 0, 0, 0, 0, nullptr, 0, nullptr), m_Destructions(destructions) {}
37
38 ~InotifyLifetimeFile() override {
39 m_Destructions += 1;
40 }
41
42 private:
43 Atomic<size_t>& m_Destructions;
44};
45
46class FileEventCloseProbe final : public File {
47 public:
48 FileEventCloseProbe() : File(String("close-probe"), 0, 0, 0, 0, nullptr, 0, nullptr) {}
49
50 void closeEventsForTest() {
51 closeFileEvents();
52 }
53};
54
55class BlockingFileEventObserver final : public FileEventObserver {
56 public:
57 BlockingFileEventObserver()
58 : entered(0, false), release(0, false), calls(0), lastMask(FileEvents::None) {}
59
60 void fileEvent(const FileEvent& event) override {
61 calls += 1;
62 lastMask = event.mask;
63 entered.release();
64 const bool released = release.acquireForCompletion();
65 (void)released;
66 }
67
68 Semaphore entered;
69 Semaphore release;
70 Atomic<size_t> calls;
71 Atomic<FileEventMask> lastMask;
72};
73
74bool readEvent(const SharedPointer<InotifyInstance>& instance, int expectedWd,
75 uint32_t expectedMask, bool exactMask = true) {
76 uint64_t storage[8] = {};
77 const int result =
78 instance->readEvents(reinterpret_cast<uint8_t*>(storage), sizeof(storage), false);
79 if (result != static_cast<int>(sizeof(LinuxInotifyEvent))) {
80 return false;
81 }
82 const LinuxInotifyEvent* event = reinterpret_cast<const LinuxInotifyEvent*>(storage);
83 const bool maskMatches =
84 exactMask ? event->mask == expectedMask : (event->mask & expectedMask) == expectedMask;
85 return event->wd == expectedWd && maskMatches && event->cookie == 0 && event->len == 0;
86}
87
88bool readEventPair(const SharedPointer<InotifyInstance>& instance, int expectedWd,
89 uint32_t firstMask, uint32_t secondMask) {
90 uint64_t storage[8] = {};
91 const int result =
92 instance->readEvents(reinterpret_cast<uint8_t*>(storage), sizeof(storage), false);
93 if (result != static_cast<int>(2 * sizeof(LinuxInotifyEvent))) {
94 return false;
95 }
96 const LinuxInotifyEvent* first = reinterpret_cast<const LinuxInotifyEvent*>(storage);
97 const LinuxInotifyEvent* second = reinterpret_cast<const LinuxInotifyEvent*>(
98 reinterpret_cast<const uint8_t*>(storage) + sizeof(LinuxInotifyEvent));
99 return first->wd == expectedWd && first->mask == firstMask && first->cookie == 0 &&
100 first->len == 0 && second->wd == expectedWd && second->mask == secondMask &&
101 second->cookie == 0 && second->len == 0;
102}
103
104struct InotifyRegressionContext {
105 InotifyRegressionContext(File* watchedFile, Directory* watchedDirectory,
106 InotifyLifetimeFile* oneShotFile, InotifyLifetimeFile* deletedFile,
107 File* overflowFile, const SharedPointer<EpollInstance>& epollInstance)
108 : watchedFile(watchedFile),
109 watchedDirectory(watchedDirectory),
110 oneShotFile(oneShotFile),
111 deletedFile(deletedFile),
112 overflowFile(overflowFile),
113 epoll(epollInstance),
114 waitEntryGate(0, false),
115 setupPassed(0),
116 waitEntered(0),
117 returned(0),
118 passed(false),
119 inotifyFd(-1),
120 oneShotWd(-1) {}
121
122 File* watchedFile;
123 Directory* watchedDirectory;
124 InotifyLifetimeFile* oneShotFile;
125 InotifyLifetimeFile* deletedFile;
126 File* overflowFile;
128 Semaphore waitEntryGate;
129 Atomic<size_t> setupPassed;
130 Atomic<size_t> waitEntered;
131 Atomic<size_t> returned;
132 bool passed;
133 int inotifyFd;
134 int oneShotWd;
135};
136
137int exerciseInotify(void* parameter) {
138 InotifyRegressionContext* context = reinterpret_cast<InotifyRegressionContext*>(parameter);
139 Thread* thread = Processor::information().getCurrentThread();
140 bool passed = true;
141
142 thread->setErrno(0);
143 const int invalid = posix_inotify_init1(0x40000000);
144 passed &= invalid == -1 && thread->getErrno() == Error::InvalidArgument;
145
146 context->inotifyFd = posix_inotify_init1(LinuxInotify::NonBlock | LinuxInotify::CloseOnExec);
147 DescriptorLease descriptor;
149 if (context->inotifyFd >= 0 && static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem())
150 ->acquireFileDescriptor(context->inotifyFd, descriptor)) {
151 instance = descriptor->getInotifyImpl();
152 passed &= descriptor->getFlags() == FD_CLOEXEC;
153 passed &= descriptor->getStatusFlags() == (O_RDONLY | O_NONBLOCK);
154 } else {
155 passed = false;
156 }
157 descriptor.reset();
158
159 int fileWd = -1;
160 int directoryWd = -1;
161 if (instance) {
162 uint8_t shortBuffer[1] = {};
163 thread->setErrno(0);
164 passed &= instance->readEvents(shortBuffer, sizeof(shortBuffer), false) == -1 &&
165 thread->getErrno() == Error::NoMoreProcesses;
166 thread->setErrno(0);
167 passed &= posix_read(context->inotifyFd, reinterpret_cast<char*>(1),
168 sizeof(LinuxInotifyEvent)) == -1 &&
169 thread->getErrno() == Error::NoMoreProcesses;
170 thread->setErrno(0);
171 passed &= instance->addWatch(context->watchedFile, 0) == -1 &&
172 thread->getErrno() == Error::InvalidArgument;
173 thread->setErrno(0);
174 passed &= instance->addWatch(context->watchedFile, LinuxInotify::Modify | 0x00800000U) == -1 &&
175 thread->getErrno() == Error::InvalidArgument;
176 const int outputOnlyWd = instance->addWatch(context->watchedFile, LinuxInotify::QueueOverflow);
177 passed &= outputOnlyWd > 0 && instance->removeWatch(outputOnlyWd) == 0;
178 passed &= readEvent(instance, outputOnlyWd, LinuxInotify::Ignored);
179 fileWd = instance->addWatch(context->watchedFile, LinuxInotify::Modify);
180 thread->setErrno(0);
181 passed &= instance->addWatch(context->watchedFile,
182 LinuxInotify::Modify | LinuxInotify::MaskCreate) == -1 &&
183 thread->getErrno() == Error::FileExists;
184 thread->setErrno(0);
185 passed &=
186 instance->addWatch(context->watchedFile, LinuxInotify::Modify | LinuxInotify::MaskCreate |
187 LinuxInotify::MaskAdd) == -1 &&
188 thread->getErrno() == Error::InvalidArgument;
189 const int mergedWd =
190 instance->addWatch(context->watchedFile, LinuxInotify::Attributes | LinuxInotify::MaskAdd);
191 passed &= fileWd > 0 && mergedWd == fileWd;
192
193 context->watchedFile->publishEvent(FileEvents::Attributes);
194 context->watchedFile->publishEvent(FileEvents::Modify);
195 thread->setErrno(0);
196 passed &= instance->readEvents(shortBuffer, sizeof(shortBuffer), false) == -1 &&
197 thread->getErrno() == Error::InvalidArgument;
198 thread->setErrno(0);
199 passed &= posix_read(context->inotifyFd, reinterpret_cast<char*>(1),
200 sizeof(LinuxInotifyEvent)) == -1 &&
201 thread->getErrno() == Error::BadAddress && instance->queryReady() == ReadyRead;
202 uint64_t remainingStorage[4] = {};
203 thread->setErrno(0);
204 const int remainingResult = posix_read(
205 context->inotifyFd, reinterpret_cast<char*>(remainingStorage), sizeof(remainingStorage));
206 const LinuxInotifyEvent* remainingEvent =
207 reinterpret_cast<const LinuxInotifyEvent*>(remainingStorage);
208 passed &= remainingResult == static_cast<int>(sizeof(LinuxInotifyEvent)) &&
209 thread->getErrno() == 0 && remainingEvent->wd == fileWd &&
210 remainingEvent->mask == LinuxInotify::Modify && remainingEvent->cookie == 0 &&
211 remainingEvent->len == 0 && instance->queryReady() == ReadyNone;
212 context->watchedFile->publishEvent(FileEvents::Attributes);
213 passed &= readEvent(instance, fileWd, LinuxInotify::Attributes);
214
215 // IN_MASK_ADD must preserve the old mask without broadening it or
216 // accidentally adding IN_ONESHOT.
217 context->watchedFile->publishEvent(FileEvents::Open);
218 passed &= instance->queryReady() == ReadyNone;
219
220 LinuxEpollEvent interest = {
221 LinuxEpoll::In | LinuxEpoll::EdgeTriggered,
222 InotifyEpollData,
223 };
224 passed &= context->epoll->control(LinuxEpoll::ControlAdd, context->inotifyFd, &interest) == 0;
225 }
226
227 context->setupPassed = passed ? 1 : 0;
228 context->waitEntered = passed ? 1 : 0;
229 context->waitEntryGate.release();
230 if (!passed) {
231 context->returned += 1;
232 return 1;
233 }
234
235 LinuxEpollEvent ready = {};
236 const int waitResult = context->epoll->wait(&ready, 1, 5000);
237 passed &= waitResult == 1 && ready.events == LinuxEpoll::In && ready.data == InotifyEpollData;
238 passed &= readEvent(instance, fileWd, LinuxInotify::Modify);
239
240 context->watchedFile->publishEvent(FileEvents::Attributes);
241 passed &= readEvent(instance, fileWd, LinuxInotify::Attributes);
242 context->watchedFile->publishEvent(FileEvents::Modify);
243 context->watchedFile->publishEvent(FileEvents::Modify);
244 passed &= readEvent(instance, fileWd, LinuxInotify::Modify);
245 const int replacedWd = instance->addWatch(context->watchedFile, LinuxInotify::Attributes);
246 context->watchedFile->publishEvent(FileEvents::Modify);
247 passed &= replacedWd == fileWd && instance->queryReady() == ReadyNone;
248 context->watchedFile->publishEvent(FileEvents::Attributes);
249 passed &= readEvent(instance, fileWd, LinuxInotify::Attributes);
250
251 directoryWd = instance->addWatch(context->watchedDirectory,
252 LinuxInotify::Attributes | LinuxInotify::DeleteSelf);
253 context->watchedDirectory->publishEvent(FileEvents::Attributes);
254 passed &= directoryWd > 0 &&
255 readEvent(instance, directoryWd, LinuxInotify::Attributes | LinuxInotify::IsDirectory);
256 context->watchedDirectory->publishEvent(FileEvents::DeletedSelf);
257 passed &= readEventPair(instance, directoryWd, LinuxInotify::DeleteSelf, LinuxInotify::Ignored);
258 thread->setErrno(0);
259 passed &=
260 instance->removeWatch(directoryWd) == -1 && thread->getErrno() == Error::InvalidArgument;
261
262 const int deletedWd = instance->addWatch(context->deletedFile, LinuxInotify::Modify);
263 context->deletedFile->publishEvent(FileEvents::DeletedSelf);
264 passed &= deletedWd > 0 && readEvent(instance, deletedWd, LinuxInotify::Ignored);
265 thread->setErrno(0);
266 passed &= instance->addWatch(context->deletedFile, LinuxInotify::Modify) == -1 &&
267 thread->getErrno() == Error::DoesNotExist;
268
269 context->oneShotWd =
270 instance->addWatch(context->oneShotFile, LinuxInotify::Modify | LinuxInotify::OneShot);
271 context->oneShotFile->publishEvent(FileEvents::Modify);
272 context->oneShotFile->publishEvent(FileEvents::Modify);
273 passed &= context->oneShotWd > 0 && readEventPair(instance, context->oneShotWd,
274 LinuxInotify::Modify, LinuxInotify::Ignored);
275 thread->setErrno(0);
276 passed &= instance->removeWatch(context->oneShotWd) == -1 &&
277 thread->getErrno() == Error::InvalidArgument;
278
279 passed &= instance->removeWatch(fileWd) == 0;
280 passed &= readEvent(instance, fileWd, LinuxInotify::Ignored);
281
283 const int overflowWd =
284 overflow->addWatch(context->overflowFile, LinuxInotify::Modify | LinuxInotify::Attributes);
285 for (size_t i = 0; i < 16384 && overflowWd > 0; ++i) {
286 context->overflowFile->publishEvent(i & 1 ? FileEvents::Attributes : FileEvents::Modify);
287 }
288 // Linux checks the queue limit before duplicate merging, so an event that is
289 // identical to the full queue's tail must still generate overflow.
290 context->overflowFile->publishEvent(FileEvents::Attributes);
291 size_t normalEvents = 0;
292 size_t overflowEvents = 0;
293 while (normalEvents + overflowEvents < 16385) {
294 uint64_t storage[128] = {};
295 const int amount =
296 overflow->readEvents(reinterpret_cast<uint8_t*>(storage), sizeof(storage), false);
297 if (amount <= 0 || amount % static_cast<int>(sizeof(LinuxInotifyEvent))) {
298 passed = false;
299 break;
300 }
301 for (size_t offset = 0; offset < static_cast<size_t>(amount);
302 offset += sizeof(LinuxInotifyEvent)) {
303 const LinuxInotifyEvent* event = reinterpret_cast<const LinuxInotifyEvent*>(
304 reinterpret_cast<const uint8_t*>(storage) + offset);
305 if (event->wd == -1 && event->mask == LinuxInotify::QueueOverflow) {
306 ++overflowEvents;
307 } else if (event->wd == overflowWd &&
308 (event->mask == LinuxInotify::Modify || event->mask == LinuxInotify::Attributes)) {
309 ++normalEvents;
310 } else {
311 passed = false;
312 }
313 }
314 }
315 passed &= normalEvents == 16384 && overflowEvents == 1;
316 passed &= overflow->removeWatch(overflowWd) == 0;
317 passed &= readEvent(overflow, overflowWd, LinuxInotify::Ignored);
318 overflow.reset();
319
320 context->passed = passed;
321 context->returned += 1;
322 return passed ? 0 : 1;
323}
324
325struct FileEventCloseContext {
326 FileEventCloseContext(FileEventCloseProbe* source, bool terminal)
327 : source(source), terminal(terminal), publishReturned(0), closeEntered(0), closeReturned(0) {}
328
329 FileEventCloseProbe* source;
330 bool terminal;
331 Atomic<size_t> publishReturned;
332 Atomic<size_t> closeEntered;
333 Atomic<size_t> closeReturned;
334};
335
336int publishBlockingFileEvent(void* parameter) {
337 FileEventCloseContext* context = reinterpret_cast<FileEventCloseContext*>(parameter);
338 context->source->publishEvent(context->terminal ? FileEvents::DeletedSelf : FileEvents::Modify);
339 context->publishReturned += 1;
340 return 0;
341}
342
343int closeBlockingFileEvents(void* parameter) {
344 FileEventCloseContext* context = reinterpret_cast<FileEventCloseContext*>(parameter);
345 context->closeEntered += 1;
346 context->source->closeEventsForTest();
347 context->closeReturned += 1;
348 return 0;
349}
350
351bool fileEventCloseDrain(Process* kernelProcess, bool terminal) {
352 FileEventCloseProbe* source = new FileEventCloseProbe;
353 BlockingFileEventObserver* concreteObserver = new BlockingFileEventObserver;
354 SharedPointer<FileEventObserver> observer(concreteObserver);
355 FileEventSubscription subscription;
356 const FileEventMask expectedMask = terminal ? FileEvents::DeletedSelf : FileEvents::Modify;
357 bool passed = source->subscribeFileEvents(expectedMask, observer, subscription);
358 FileEventCloseContext context(source, terminal);
359 Thread* publisher =
360 new Thread(kernelProcess, publishBlockingFileEvent, &context, nullptr, false, true, true);
361 publisher->setName("hosted file-event blocking publisher");
362 const bool publisherStarted = publisher->start();
363 const bool callbackEntered = publisherStarted && concreteObserver->entered.acquireForCompletion();
364
365 Thread* closer = nullptr;
366 bool closerStarted = false;
367 if (callbackEntered) {
368 closer =
369 new Thread(kernelProcess, closeBlockingFileEvents, &context, nullptr, false, true, true);
370 closer->setName("hosted file-event closer");
371 closerStarted = closer->start();
372 }
373 bool closeBlocked = false;
374 for (size_t attempt = 0; attempt < HostedAttempts && closerStarted; ++attempt) {
375 Thread::WaitDebugInfo info = {};
376 if (context.closeEntered && !context.closeReturned && closer->getWaitDebugInfo(info) &&
377 info.queue && info.queued && closer->getStatus() == Thread::Sleeping) {
378 closeBlocked = true;
379 break;
380 }
382 }
383
384 concreteObserver->release.release();
385 const bool publisherJoined = publisherStarted && publisher->joinForCompletion();
386 const bool closerJoined = closerStarted && closer->joinForCompletion();
387 if (!publisherStarted) {
388 delete publisher;
389 }
390 if (closer && !closerStarted) {
391 delete closer;
392 }
393 source->publishEvent(FileEvents::Modify);
394 subscription.reset();
395 passed &= publisherStarted && callbackEntered && closerStarted && closeBlocked &&
396 publisherJoined && closerJoined && context.publishReturned == static_cast<size_t>(1) &&
397 context.closeReturned == static_cast<size_t>(1) &&
398 concreteObserver->calls == static_cast<size_t>(1) &&
399 concreteObserver->lastMask == expectedMask;
400 observer.reset();
401 delete source;
402 return passed;
403}
404
405struct InotifyReadCloseContext {
406 InotifyReadCloseContext()
407 : ready(0, false), returned(0), fd(-1), alias(-1), result(-2), error(0) {}
408
409 Semaphore ready;
410 Atomic<size_t> returned;
411 int fd;
412 int alias;
413 int result;
414 int error;
415};
416
417int blockOnInotifyRead(void* parameter) {
418 InotifyReadCloseContext* context = reinterpret_cast<InotifyReadCloseContext*>(parameter);
419 Thread* thread = Processor::information().getCurrentThread();
420 context->fd = posix_inotify_init();
421 context->alias = context->fd >= 0 ? posix_dup(context->fd) : -1;
422 context->ready.release();
423 uint64_t storage[8] = {};
424 thread->setErrno(0);
425 context->result = posix_read(context->fd, reinterpret_cast<char*>(storage), sizeof(storage));
426 context->error = thread->getErrno();
427 context->returned += 1;
428 return 0;
429}
430
431bool blockingReadFinalClose(Process* kernelProcess) {
432 Process* process = new Process(kernelProcess);
433 PosixSubsystem* subsystem = new PosixSubsystem;
434 process->setSubsystem(subsystem);
435 InotifyReadCloseContext context;
436 Thread* reader = new Thread(process, blockOnInotifyRead, &context, nullptr, false, true, true);
437 reader->setName("hosted inotify close waiter");
438 const bool started = reader->start();
439 const bool ready = started && context.ready.acquireForCompletion();
440
441 bool readBlocked = false;
442 for (size_t attempt = 0; attempt < HostedAttempts && ready; ++attempt) {
443 Thread::WaitDebugInfo info = {};
444 if (reader->getWaitDebugInfo(info) && info.queue && info.queued &&
445 reader->getStatus() == Thread::Sleeping) {
446 readBlocked = true;
447 break;
448 }
450 }
451
452 auto closeDescriptor = [&](int fd) {
453 DescriptorLease descriptor;
454 const bool acquired = subsystem->acquireFileDescriptor(fd, descriptor);
455 const bool closed = acquired && subsystem->closeFileDescriptor(fd, descriptor);
456 descriptor.reset();
457 return closed;
458 };
459 const bool originalClosed = readBlocked && closeDescriptor(context.fd);
460 for (size_t attempt = 0; attempt < 256 && !context.returned; ++attempt) {
462 }
463 const bool aliasKeptOpen = !context.returned;
464 const bool aliasClosed = aliasKeptOpen && closeDescriptor(context.alias);
465 const bool joined = started && reader->joinForCompletion();
466 if (!started) {
467 delete reader;
468 }
469
470 const bool passed = started && ready && readBlocked && originalClosed && aliasKeptOpen &&
471 aliasClosed && joined && context.returned == static_cast<size_t>(1) &&
472 context.result == -1 && context.error == Error::BadFileDescriptor;
473 delete process;
474 return passed;
475}
476
477struct InotifyBlockingWakeContext {
478 explicit InotifyBlockingWakeContext(File* watched)
479 : watched(watched), ready(0, false), returned(0), result(-2), wd(-1), event() {}
480
481 File* watched;
482 Semaphore ready;
483 Atomic<size_t> returned;
484 int result;
485 int wd;
486 LinuxInotifyEvent event;
487};
488
489int blockUntilInotifyEvent(void* parameter) {
490 InotifyBlockingWakeContext* context = reinterpret_cast<InotifyBlockingWakeContext*>(parameter);
491 Thread* thread = Processor::information().getCurrentThread();
492 const int fd = posix_inotify_init();
493 DescriptorLease descriptor;
495 PosixSubsystem* subsystem = static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem());
496 if (fd >= 0 && subsystem->acquireFileDescriptor(fd, descriptor)) {
497 instance = descriptor->getInotifyImpl();
498 }
499 if (instance) {
500 context->wd = instance->addWatch(context->watched, LinuxInotify::Modify);
501 }
502 descriptor.reset();
503 context->ready.release();
504 if (fd >= 0 && context->wd > 0) {
505 context->result =
506 posix_read(fd, reinterpret_cast<char*>(&context->event), sizeof(context->event));
507 }
508 context->returned += 1;
509 return 0;
510}
511
512bool blockingReadEventWake(Process* kernelProcess) {
513 File* watched = new File(String("blocking-read"), 0, 0, 0, 0, nullptr, 0, nullptr);
514 VFS::instance().trackFile(watched);
515 Process* process = new Process(kernelProcess);
516 process->setSubsystem(new PosixSubsystem);
517 InotifyBlockingWakeContext context(watched);
518 Thread* reader =
519 new Thread(process, blockUntilInotifyEvent, &context, nullptr, false, true, true);
520 reader->setName("hosted inotify event waiter");
521 const bool started = reader->start();
522 const bool ready = started && context.ready.acquireForCompletion();
523
524 bool readBlocked = false;
525 for (size_t attempt = 0; attempt < HostedAttempts && ready && context.wd > 0; ++attempt) {
526 Thread::WaitDebugInfo info = {};
527 if (reader->getWaitDebugInfo(info) && info.queue && info.queued &&
528 reader->getStatus() == Thread::Sleeping) {
529 readBlocked = true;
530 break;
531 }
533 }
534 if (ready) {
535 watched->publishEvent(FileEvents::Modify);
536 }
537 const bool joined = started && reader->joinForCompletion();
538 if (!started) {
539 delete reader;
540 }
541 const bool passed =
542 started && ready && readBlocked && joined && context.returned == static_cast<size_t>(1) &&
543 context.result == static_cast<int>(sizeof(LinuxInotifyEvent)) &&
544 context.event.wd == context.wd && context.event.mask == LinuxInotify::Modify &&
545 context.event.cookie == 0 && context.event.len == 0;
546 delete process;
547 VFS::instance().untrackFile(watched);
548 return passed;
549}
550} // namespace
551
552bool runHostedInotifyRegressions(Process* kernelProcess) {
553 Atomic<size_t> oneShotDestructions(0);
554 Atomic<size_t> deletedDestructions(0);
555 File* watchedFile = new File(String("watched"), 0, 0, 0, 0, nullptr, 0, nullptr);
556 Directory* watchedDirectory =
557 new Directory(String("watched-dir"), 0, 0, 0, 0, nullptr, 0, nullptr);
558 InotifyLifetimeFile* oneShotFile = new InotifyLifetimeFile(oneShotDestructions);
559 InotifyLifetimeFile* deletedFile = new InotifyLifetimeFile(deletedDestructions);
560 File* overflowFile = new File(String("overflow"), 0, 0, 0, 0, nullptr, 0, nullptr);
561 VFS::instance().trackFile(watchedFile);
562 VFS::instance().trackFile(watchedDirectory);
563 VFS::instance().trackFile(oneShotFile);
564 VFS::instance().trackFile(deletedFile);
565 VFS::instance().trackFile(overflowFile);
566
567 Process* process = new Process(kernelProcess);
568 process->setSubsystem(new PosixSubsystem);
570 InotifyRegressionContext context(watchedFile, watchedDirectory, oneShotFile, deletedFile,
571 overflowFile, epoll);
572 Thread* worker = new Thread(process, exerciseInotify, &context, nullptr, false, true, true);
573 worker->setName("hosted inotify regression worker");
574 const bool started = worker->start();
575 const bool waitEntered = started && context.waitEntryGate.acquireForCompletion();
576
577 bool waitBlocked = false;
578 for (size_t attempt = 0; attempt < HostedAttempts && waitEntered && context.setupPassed;
579 ++attempt) {
580 Thread::WaitDebugInfo info = {};
581 if (worker->getWaitDebugInfo(info) && info.queue && info.queued &&
582 worker->getStatus() == Thread::Sleeping) {
583 waitBlocked = true;
584 break;
585 }
587 }
588
589 if (waitEntered && context.waitEntered) {
590 watchedFile->publishEvent(FileEvents::Modify);
591 }
592 const bool joined = started && worker->joinForCompletion();
593 if (!started) {
594 delete worker;
595 }
596
597 // A successful one-shot read must have reaped the subscription and its VFS
598 // lease even while the inotify descriptor itself remains open.
599 const bool oneShotWasFinal = VFS::instance().untrackFile(oneShotFile);
600 const bool oneShotRetired = oneShotWasFinal && oneShotDestructions == static_cast<size_t>(1);
601 const bool deletedWasFinal = VFS::instance().untrackFile(deletedFile);
602 const bool deletedRetired = deletedWasFinal && deletedDestructions == static_cast<size_t>(1);
603
604 const bool passed = started && waitEntered && waitBlocked && joined &&
605 context.returned == static_cast<size_t>(1) && context.passed &&
606 oneShotRetired && deletedRetired &&
607 fileEventCloseDrain(kernelProcess, false) &&
608 fileEventCloseDrain(kernelProcess, true) &&
609 blockingReadFinalClose(kernelProcess) && blockingReadEventWake(kernelProcess);
610 delete process;
611 epoll.reset();
612 VFS::instance().untrackFile(watchedFile);
613 VFS::instance().untrackFile(watchedDirectory);
614 VFS::instance().untrackFile(overflowFile);
615
616 if (!passed) {
617 ERROR(
618 "HOSTED-SYSCALL-TEST: FAIL inotify-vfs-epoll-lifetime: "
619 "ABI, queue, epoll wakeup, terminal event, callback drain, or close lifetime failed");
620 return false;
621 }
622
623 NOTICE("HOSTED-SYSCALL-TEST: PASS inotify-vfs-epoll-lifetime");
624 return true;
625}
SharedPointer< InotifyInstance > getInotifyImpl() const
int getFlags() const
Get current descriptor flags.
int getStatusFlags() const
Get current status flags.
virtual void fileEvent(const FileEvent &event)=0
Definition File.h:74
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
Definition File.cc:749
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void setErrno(size_t err)
Definition Thread.h:478
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
size_t getErrno()
Definition Thread.h:473
bool joinForCompletion()
Definition Thread.cc:2771
Status getStatus() const
Definition Thread.h:431
Process * getParent() const
Definition Thread.h:338
bool start()
Definition Thread.cc:794
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1477
static VFS & instance()
Definition VFS.cc:291
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
Definition VFS.cc:1440
Definition waits.c:9