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"
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"
30constexpr size_t HostedAttempts = 10000;
31constexpr uint64_t InotifyEpollData = 0x494E4F5449465901ULL;
33class InotifyLifetimeFile final :
public File {
36 :
File(
String(
"oneshot"), 0, 0, 0, 0, nullptr, 0, nullptr), m_Destructions(destructions) {}
38 ~InotifyLifetimeFile()
override {
46class FileEventCloseProbe final :
public File {
48 FileEventCloseProbe() :
File(
String(
"close-probe"), 0, 0, 0, 0, nullptr, 0, nullptr) {}
50 void closeEventsForTest() {
57 BlockingFileEventObserver()
58 : entered(0, false), release(0, false), calls(0), lastMask(FileEvents::None) {}
62 lastMask =
event.mask;
64 const bool released = release.acquireForCompletion();
75 uint32_t expectedMask,
bool exactMask =
true) {
76 uint64_t storage[8] = {};
78 instance->readEvents(
reinterpret_cast<uint8_t*
>(storage),
sizeof(storage),
false);
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;
89 uint32_t firstMask, uint32_t secondMask) {
90 uint64_t storage[8] = {};
92 instance->readEvents(
reinterpret_cast<uint8_t*
>(storage),
sizeof(storage),
false);
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;
104struct InotifyRegressionContext {
105 InotifyRegressionContext(
File* watchedFile,
Directory* watchedDirectory,
106 InotifyLifetimeFile* oneShotFile, InotifyLifetimeFile* deletedFile,
108 : watchedFile(watchedFile),
109 watchedDirectory(watchedDirectory),
110 oneShotFile(oneShotFile),
111 deletedFile(deletedFile),
112 overflowFile(overflowFile),
113 epoll(epollInstance),
114 waitEntryGate(0, false),
124 InotifyLifetimeFile* oneShotFile;
125 InotifyLifetimeFile* deletedFile;
137int exerciseInotify(
void* parameter) {
138 InotifyRegressionContext* context =
reinterpret_cast<InotifyRegressionContext*
>(parameter);
143 const int invalid = posix_inotify_init1(0x40000000);
144 passed &= invalid == -1 && thread->
getErrno() == Error::InvalidArgument;
146 context->inotifyFd = posix_inotify_init1(LinuxInotify::NonBlock | LinuxInotify::CloseOnExec);
152 passed &= descriptor->
getFlags() == FD_CLOEXEC;
153 passed &= descriptor->
getStatusFlags() == (O_RDONLY | O_NONBLOCK);
160 int directoryWd = -1;
162 uint8_t shortBuffer[1] = {};
164 passed &= instance->readEvents(shortBuffer,
sizeof(shortBuffer),
false) == -1 &&
165 thread->
getErrno() == Error::NoMoreProcesses;
167 passed &= posix_read(context->inotifyFd,
reinterpret_cast<char*
>(1),
169 thread->
getErrno() == Error::NoMoreProcesses;
171 passed &= instance->addWatch(context->watchedFile, 0) == -1 &&
172 thread->
getErrno() == Error::InvalidArgument;
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);
181 passed &= instance->addWatch(context->watchedFile,
182 LinuxInotify::Modify | LinuxInotify::MaskCreate) == -1 &&
183 thread->
getErrno() == Error::FileExists;
186 instance->addWatch(context->watchedFile, LinuxInotify::Modify | LinuxInotify::MaskCreate |
187 LinuxInotify::MaskAdd) == -1 &&
188 thread->
getErrno() == Error::InvalidArgument;
190 instance->addWatch(context->watchedFile, LinuxInotify::Attributes | LinuxInotify::MaskAdd);
191 passed &= fileWd > 0 && mergedWd == fileWd;
193 context->watchedFile->publishEvent(FileEvents::Attributes);
194 context->watchedFile->publishEvent(FileEvents::Modify);
196 passed &= instance->readEvents(shortBuffer,
sizeof(shortBuffer),
false) == -1 &&
197 thread->
getErrno() == Error::InvalidArgument;
199 passed &= posix_read(context->inotifyFd,
reinterpret_cast<char*
>(1),
201 thread->
getErrno() == Error::BadAddress && instance->queryReady() == ReadyRead;
202 uint64_t remainingStorage[4] = {};
204 const int remainingResult = posix_read(
205 context->inotifyFd,
reinterpret_cast<char*
>(remainingStorage),
sizeof(remainingStorage));
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);
217 context->watchedFile->publishEvent(FileEvents::Open);
218 passed &= instance->queryReady() == ReadyNone;
221 LinuxEpoll::In | LinuxEpoll::EdgeTriggered,
224 passed &= context->epoll->control(LinuxEpoll::ControlAdd, context->inotifyFd, &interest) == 0;
227 context->setupPassed = passed ? 1 : 0;
228 context->waitEntered = passed ? 1 : 0;
229 context->waitEntryGate.release();
231 context->returned += 1;
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);
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);
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);
260 instance->removeWatch(directoryWd) == -1 && thread->
getErrno() == Error::InvalidArgument;
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);
266 passed &= instance->addWatch(context->deletedFile, LinuxInotify::Modify) == -1 &&
267 thread->
getErrno() == Error::DoesNotExist;
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);
276 passed &= instance->removeWatch(context->oneShotWd) == -1 &&
277 thread->
getErrno() == Error::InvalidArgument;
279 passed &= instance->removeWatch(fileWd) == 0;
280 passed &= readEvent(instance, fileWd, LinuxInotify::Ignored);
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);
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] = {};
296 overflow->readEvents(
reinterpret_cast<uint8_t*
>(storage),
sizeof(storage),
false);
301 for (
size_t offset = 0; offset < static_cast<size_t>(amount);
304 reinterpret_cast<const uint8_t*
>(storage) + offset);
305 if (event->wd == -1 && event->mask == LinuxInotify::QueueOverflow) {
307 }
else if (event->wd == overflowWd &&
308 (event->mask == LinuxInotify::Modify || event->mask == LinuxInotify::Attributes)) {
315 passed &= normalEvents == 16384 && overflowEvents == 1;
316 passed &= overflow->removeWatch(overflowWd) == 0;
317 passed &= readEvent(overflow, overflowWd, LinuxInotify::Ignored);
320 context->passed = passed;
321 context->returned += 1;
322 return passed ? 0 : 1;
325struct FileEventCloseContext {
326 FileEventCloseContext(FileEventCloseProbe* source,
bool terminal)
327 : source(source), terminal(terminal), publishReturned(0), closeEntered(0), closeReturned(0) {}
329 FileEventCloseProbe* source;
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;
343int closeBlockingFileEvents(
void* parameter) {
344 FileEventCloseContext* context =
reinterpret_cast<FileEventCloseContext*
>(parameter);
345 context->closeEntered += 1;
346 context->source->closeEventsForTest();
347 context->closeReturned += 1;
351bool fileEventCloseDrain(
Process* kernelProcess,
bool terminal) {
352 FileEventCloseProbe* source =
new FileEventCloseProbe;
353 BlockingFileEventObserver* concreteObserver =
new BlockingFileEventObserver;
356 const FileEventMask expectedMask = terminal ? FileEvents::DeletedSelf : FileEvents::Modify;
357 bool passed = source->subscribeFileEvents(expectedMask, observer, subscription);
358 FileEventCloseContext context(source, terminal);
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();
366 bool closerStarted =
false;
367 if (callbackEntered) {
369 new Thread(kernelProcess, closeBlockingFileEvents, &context,
nullptr,
false,
true,
true);
370 closer->setName(
"hosted file-event closer");
371 closerStarted = closer->
start();
373 bool closeBlocked =
false;
374 for (
size_t attempt = 0; attempt < HostedAttempts && closerStarted; ++attempt) {
376 if (context.closeEntered && !context.closeReturned && closer->
getWaitDebugInfo(info) &&
377 info.queue && info.queued && closer->
getStatus() == Thread::Sleeping) {
384 concreteObserver->release.release();
387 if (!publisherStarted) {
390 if (closer && !closerStarted) {
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;
405struct InotifyReadCloseContext {
406 InotifyReadCloseContext()
407 : ready(0, false), returned(0), fd(-1), alias(-1), result(-2), error(0) {}
417int blockOnInotifyRead(
void* parameter) {
418 InotifyReadCloseContext* context =
reinterpret_cast<InotifyReadCloseContext*
>(parameter);
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] = {};
425 context->result = posix_read(context->fd,
reinterpret_cast<char*
>(storage),
sizeof(storage));
426 context->error = thread->
getErrno();
427 context->returned += 1;
431bool blockingReadFinalClose(
Process* kernelProcess) {
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();
441 bool readBlocked =
false;
442 for (
size_t attempt = 0; attempt < HostedAttempts && ready; ++attempt) {
444 if (
reader->getWaitDebugInfo(info) && info.queue && info.queued &&
445 reader->getStatus() == Thread::Sleeping) {
452 auto closeDescriptor = [&](
int fd) {
459 const bool originalClosed = readBlocked && closeDescriptor(context.fd);
460 for (
size_t attempt = 0; attempt < 256 && !context.returned; ++attempt) {
463 const bool aliasKeptOpen = !context.returned;
464 const bool aliasClosed = aliasKeptOpen && closeDescriptor(context.alias);
465 const bool joined = started &&
reader->joinForCompletion();
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;
477struct InotifyBlockingWakeContext {
478 explicit InotifyBlockingWakeContext(
File* watched)
479 : watched(watched), ready(0, false), returned(0), result(-2), wd(-1), event() {}
489int blockUntilInotifyEvent(
void* parameter) {
490 InotifyBlockingWakeContext* context =
reinterpret_cast<InotifyBlockingWakeContext*
>(parameter);
492 const int fd = posix_inotify_init();
500 context->wd = instance->addWatch(context->watched, LinuxInotify::Modify);
503 context->ready.release();
504 if (fd >= 0 && context->wd > 0) {
506 posix_read(fd,
reinterpret_cast<char*
>(&context->event),
sizeof(context->event));
508 context->returned += 1;
512bool blockingReadEventWake(
Process* kernelProcess) {
513 File* watched =
new File(
String(
"blocking-read"), 0, 0, 0, 0,
nullptr, 0,
nullptr);
517 InotifyBlockingWakeContext context(watched);
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();
524 bool readBlocked =
false;
525 for (
size_t attempt = 0; attempt < HostedAttempts && ready && context.wd > 0; ++attempt) {
527 if (
reader->getWaitDebugInfo(info) && info.queue && info.queued &&
528 reader->getStatus() == Thread::Sleeping) {
537 const bool joined = started &&
reader->joinForCompletion();
542 started && ready && readBlocked && joined && context.returned ==
static_cast<size_t>(1) &&
544 context.event.wd == context.wd && context.event.mask == LinuxInotify::Modify &&
545 context.event.cookie == 0 && context.event.len == 0;
552bool runHostedInotifyRegressions(
Process* kernelProcess) {
555 File* watchedFile =
new File(
String(
"watched"), 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);
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();
577 bool waitBlocked =
false;
578 for (
size_t attempt = 0; attempt < HostedAttempts && waitEntered && context.setupPassed;
581 if (
worker->getWaitDebugInfo(info) && info.queue && info.queued &&
582 worker->getStatus() == Thread::Sleeping) {
589 if (waitEntered && context.waitEntered) {
592 const bool joined = started &&
worker->joinForCompletion();
600 const bool oneShotRetired = oneShotWasFinal && oneShotDestructions ==
static_cast<size_t>(1);
602 const bool deletedRetired = deletedWasFinal && deletedDestructions ==
static_cast<size_t>(1);
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);
618 "HOSTED-SYSCALL-TEST: FAIL inotify-vfs-epoll-lifetime: "
619 "ABI, queue, epoll wakeup, terminal event, callback drain, or close lifetime failed");
623 NOTICE(
"HOSTED-SYSCALL-TEST: PASS inotify-vfs-epoll-lifetime");
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
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
static ProcessorInformation & information()
static Scheduler & instance()
void setErrno(size_t err)
bool getWaitDebugInfo(WaitDebugInfo &info)
Process * getParent() const
bool untrackFile(File *pFile, bool destroy=true)
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...