The Pedigree Project 0.1
ProcFs.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "ProcFs.h"
21#include "pedigree/kernel/BootstrapInfo.h"
22#include "pedigree/kernel/LockGuard.h"
23#include "pedigree/kernel/Version.h"
24#include "pedigree/kernel/machine/Device.h"
25#include "pedigree/kernel/process/Scheduler.h"
26#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
27#include "pedigree/kernel/time/Time.h"
28#include "pedigree/kernel/utilities/StaticString.h"
29
30#include "PosixProcess.h"
31#include "file-syscalls.h"
32#include "modules/system/users/Group.h"
33#include "modules/system/users/User.h"
34
35namespace {
36Mutex procRegistryLock;
37ProcFs* procRegistry = nullptr;
38} // namespace
39
41extern size_t g_FreePages;
42extern size_t g_AllocedPages;
43
44MeminfoFile::MeminfoFile(size_t inode, Filesystem* pParentFS, File* pParent)
45 : File(String("meminfo"), 0, 0, 0, inode, pParentFS, 0, pParent), m_Contents(), m_Lock() {
46 setPermissionsOnly(FILE_UR | FILE_GR | FILE_OR);
47 setUidOnly(0);
48 setGidOnly(0);
49
50 updateContents();
51}
52
53MeminfoFile::~MeminfoFile() = default;
54
55size_t MeminfoFile::getSize() {
56 updateContents();
57 LockGuard<Mutex> guard(m_Lock);
58 return m_Contents.length();
59}
60
61void MeminfoFile::updateContents() {
62 const auto memory = PhysicalMemoryManager::instance().memorySnapshot();
63 const uint64_t totalPages = memory.available ? memory.totalPages : g_FreePages + g_AllocedPages;
64 const uint64_t freePages = memory.available ? memory.freePages : g_FreePages;
65 const uint64_t totalKb = (totalPages * PhysicalMemoryManager::getPageSize()) / 1024;
66 const uint64_t freeKb = (freePages * PhysicalMemoryManager::getPageSize()) / 1024;
67 String contents;
68 contents.Format(
69 "MemTotal: %lu kB\n"
70 "MemFree: %lu kB\n"
71 "MemAvailable: %lu kB\n"
72 "Buffers: 0 kB\n"
73 "Cached: 0 kB\n"
74 "SwapCached: 0 kB\n"
75 "SwapTotal: 0 kB\n"
76 "SwapFree: 0 kB\n",
77 totalKb, freeKb, freeKb);
78 LockGuard<Mutex> guard(m_Lock);
79 m_Contents = contents;
80}
81
82uint64_t MeminfoFile::readBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
83 bool bCanBlock) {
84 updateContents();
85 LockGuard<Mutex> guard(m_Lock);
86
87 if (location >= m_Contents.length()) {
88 return 0; // EOF
89 } else if (size > m_Contents.length() - location) {
90 size = m_Contents.length() - location;
91 }
92
93 char* destination = reinterpret_cast<char*>(buffer);
94 const char* source = static_cast<const char*>(m_Contents);
95
96 MemoryCopy(destination, source + location, size);
97
98 return size;
99}
100
101uint64_t MeminfoFile::writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
102 bool bCanBlock) {
103 return 0;
104}
105
106PciDevicesFile::PciDevicesFile(size_t inode, Filesystem* pParentFS, File* pParent)
107 : File(String("devices"), 0, 0, 0, inode, pParentFS, 0, pParent), m_Contents() {
108 setPermissionsOnly(FILE_UR | FILE_UW | FILE_GR | FILE_GW | FILE_OR);
109 setUidOnly(0);
110 setGidOnly(0);
111
112 resync();
113}
114
115PciDevicesFile::~PciDevicesFile() {}
116
117size_t PciDevicesFile::getSize() {
118 return m_Contents.length();
119}
120
121uint64_t PciDevicesFile::readBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
122 bool bCanBlock) {
123 resync();
124
125 if (location >= m_Contents.length()) {
126 return 0; // EOF
127 } else if ((location + size) > m_Contents.length()) {
128 size = m_Contents.length() - location;
129 }
130
131 char* destination = reinterpret_cast<char*>(buffer);
132 const char* source = static_cast<const char*>(m_Contents);
133
134 StringCopy(destination, source);
135
136 return size;
137}
138
139uint64_t PciDevicesFile::writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
140 bool bCanBlock) {
141 return 0;
142}
143
145 m_Contents = String();
146
147 auto printer = [this](Device* p) -> Device* {
148 String initial;
149 initial.Format("%02x%02x\t%04x%04x\t%x", p->getPciBusPosition(),
150 (p->getPciDevicePosition() << 4) | p->getPciFunctionNumber(),
151 p->getPciVendorId(), p->getPciDeviceId(), p->getInterruptNumber());
152
153 String resStart;
154 String resLength;
155 for (size_t i = 0; i < 7; ++i) {
156 String thisResStart;
157 String thisResLength;
158
159 if (i < p->addresses().count()) {
160 size_t length = p->addresses()[i]->m_Size;
161
162 uintptr_t address = p->addresses()[i]->m_Address;
163 uintptr_t flags = 0;
164 if (p->addresses()[i]->m_IsIoSpace) {
165 flags = 1;
166 }
167
169 thisResStart.Format("\t%16lx", address | flags);
170 thisResLength.Format("\t%16lx", length ? length + 1 : 0);
171 } else {
172 thisResStart.Format("\t%16lx", 0);
173 thisResLength.Format("\t%16lx", 0);
174 }
175
176 resStart += thisResStart;
177 resLength += thisResLength;
178 }
179
180 m_Contents += initial;
181 m_Contents += resStart;
182 m_Contents += resLength;
183 m_Contents += "\t";
184 m_Contents += "\n";
185
186 return p;
187 };
188
189 auto callback = pedigree_std::make_callable(printer);
190 Device::foreach (callback, nullptr);
191}
192
193MountFile::MountFile(size_t inode, Filesystem* pParentFS, File* pParent)
194 : File(String("mounts"), 0, 0, 0, inode, pParentFS, 0, pParent) {
195 setPermissionsOnly(FILE_UR | FILE_GR | FILE_OR);
196 setUidOnly(0);
197 setGidOnly(0);
198}
199
200MountFile::~MountFile() = default;
201
202uint64_t MountFile::readBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
203 bool bCanBlock) {
204 String mounts;
205 generate_mtab(mounts);
206
207 if (location >= mounts.length()) {
208 // "EOF"
209 return 0;
210 }
211
212 if ((location + size) >= mounts.length()) {
213 size = mounts.length() - location;
214 }
215
216 char* destination = reinterpret_cast<char*>(buffer);
217 StringCopyN(destination, static_cast<const char*>(mounts) + location, size);
218
219 return size;
220}
221
222uint64_t MountFile::writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
223 bool bCanBlock) {
224 return 0;
225}
226
227size_t MountFile::getSize() {
228 String mounts;
229 generate_mtab(mounts);
230 return mounts.length();
231}
232
233UptimeFile::UptimeFile(size_t inode, Filesystem* pParentFS, File* pParent)
234 : File(String("uptime"), 0, 0, 0, inode, pParentFS, 0, pParent) {
235 setPermissionsOnly(FILE_UR | FILE_GR | FILE_OR);
236 setUidOnly(0);
237 setGidOnly(0);
238}
239
240UptimeFile::~UptimeFile() = default;
241
242uint64_t UptimeFile::readBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
243 bool bCanBlock) {
244 String f = generateString();
245
246 if (location >= f.length()) {
247 // "EOF"
248 return 0;
249 }
250
251 if ((location + size) >= f.length()) {
252 size = f.length() - location;
253 }
254
255 char* destination = reinterpret_cast<char*>(buffer);
256 StringCopyN(destination, static_cast<const char*>(f) + location, size);
257
258 return size;
259}
260
261uint64_t UptimeFile::writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
262 bool bCanBlock) {
263 return 0;
264}
265
266size_t UptimeFile::getSize() {
267 String f = generateString();
268 return f.length();
269}
270
271String UptimeFile::generateString() {
272 const uint64_t uptime = Time::getTicks();
273 const uint64_t idle = Scheduler::instance().systemActivity().idleNanoseconds;
274
275 String f;
276 f.Format("%lu.%02lu %lu.%02lu\n", uptime / Time::Multiplier::Second,
277 (uptime % Time::Multiplier::Second) / (Time::Multiplier::Second / 100),
278 idle / Time::Multiplier::Second,
279 (idle % Time::Multiplier::Second) / (Time::Multiplier::Second / 100));
280
281 return f;
282}
283
284ConstantFile::ConstantFile(String name, const char* value, size_t size, size_t inode,
285 Filesystem* pParentFS, File* pParent)
286 : File(name, 0, 0, 0, inode, pParentFS, 0, pParent), m_Contents() {
287 setPermissionsOnly(FILE_UR | FILE_UW | FILE_GR | FILE_GW | FILE_OR);
288 setUidOnly(0);
289 setGidOnly(0);
290
291 m_Contents = new char[size];
292 m_Size = size;
293 MemoryCopy(m_Contents, value, size);
294}
295
296ConstantFile::~ConstantFile() {
297 delete[] m_Contents;
298}
299
300uint64_t ConstantFile::readBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
301 bool bCanBlock) {
302 if (location >= m_Size) {
303 return 0; // EOF
304 } else if ((location + size) > m_Size) {
305 size = m_Size - location;
306 }
307
308 char* destination = reinterpret_cast<char*>(buffer);
309 const char* source = m_Contents + location;
310
311 MemoryCopy(destination, source, size);
312
313 return size;
314}
315
316uint64_t ConstantFile::writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
317 bool bCanBlock) {
318 return 0;
319}
320
321size_t ConstantFile::getSize() {
322 return m_Size;
323}
324
325ProcFsDirectory::~ProcFsDirectory() = default;
326
327ProcFs::~ProcFs() {
328 {
329 LockGuard<Mutex> guard(procRegistryLock);
330 ProcFs** link = &procRegistry;
331 while (*link && *link != this) {
332 link = &(*link)->m_RegistryNext;
333 }
334 if (*link) {
335 *link = m_RegistryNext;
336 }
337 }
338 delete m_pRoot;
339}
340
345
346 // Deterministic inode assignment to each ProcFs node
347 m_NextInode = 0;
348
349 if (m_pRoot) {
350 delete m_pRoot;
351 }
352
353 m_pRoot = new ProcFsDirectory(String(""), 0, 0, 0, getNextInode(), this, 0, 0);
354 // Allow user/group to read and write, but disallow all others anything
355 // other than the ability to list and access files.
356 m_pRoot->setPermissions(FILE_UR | FILE_UW | FILE_UX | FILE_GR | FILE_GW | FILE_GX | FILE_OR |
357 FILE_OX);
358
359 // dot entry
361 Directory* dot = new ProcFsDirectory(String("."), 0, 0, 0, m_pRoot->getInode(), this, 0, 0);
362 dot->setPermissions(m_pRoot->getPermissions());
363 m_pRoot->addEntry(dot->getName(), dot);
364
365 MeminfoFile* meminfo = new MeminfoFile(getNextInode(), this, m_pRoot);
366 m_pRoot->addEntry(meminfo->getName(), meminfo);
367
369 MountFile* mounts = new MountFile(getNextInode(), this, m_pRoot);
370 m_pRoot->addEntry(mounts->getName(), mounts);
371
372 initialiseResolverFile();
373 initialiseNetworkFile();
374
375 auto* sys = new ProcFsDirectory(String("sys"), 0, 0, 0, getNextInode(), this, 0, m_pRoot);
376 auto* kernel = new ProcFsDirectory(String("kernel"), 0, 0, 0, getNextInode(), this, 0, sys);
377 constexpr uint32_t directoryMode = FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX;
378 sys->setPermissions(directoryMode);
379 kernel->setPermissions(directoryMode);
380 m_pRoot->addEntry(sys->getName(), sys);
381 sys->addEntry(kernel->getName(), kernel);
382 const char* overflowNames[] = {"overflowuid", "overflowgid"};
383 for (const char* name : overflowNames) {
384 auto* value = new ConstantFile(String(name), "65534\n", 6, getNextInode(), this, kernel);
385 value->setPermissions(FILE_UR | FILE_GR | FILE_OR);
386 kernel->addEntry(value->getName(), value);
387 }
388
389 UptimeFile* uptime = new UptimeFile(getNextInode(), this, m_pRoot);
390 m_pRoot->addEntry(uptime->getName(), uptime);
391
392 if (!procfsAddSystemStatusFiles(*this, *m_pRoot))
393 return false;
394
395 static String fs("\text2\nnodev\tproc\nnodev\tsysfs\nnodev\ttmpfs\n");
396 ConstantFile* pFilesystems = new ConstantFile(String("filesystems"), fs.cstr(), fs.length(),
397 getNextInode(), this, m_pRoot);
398 m_pRoot->addEntry(pFilesystems->getName(), pFilesystems);
399
400 // Kernel command line
401 static String cmdline("noswap quiet boot=live\n", 25); //(g_pBootstrapInfo->getCommandLine());
402 NOTICE("cmdline is '" << cmdline << "'");
403 ConstantFile* pCmdline = new ConstantFile(String("cmdline"), cmdline.cstr(), cmdline.length(),
404 getNextInode(), this, m_pRoot);
405 m_pRoot->addEntry(pCmdline->getName(), pCmdline);
406
407 // /proc/version contains some extra version info (not same as uname)
408 static String version;
409 version.Format("Pedigree version %s (%s@%s) %s", g_pBuildRevision, g_pBuildUser, g_pBuildMachine,
410 g_pBuildTime);
411 ConstantFile* pVersion = new ConstantFile(String("version"), version.cstr(), version.length(),
412 getNextInode(), this, m_pRoot);
413 m_pRoot->addEntry(pVersion->getName(), pVersion);
414
415 ProcFsDirectory* pBusDir =
416 new ProcFsDirectory(String("bus"), 0, 0, 0, getNextInode(), this, 0, m_pRoot);
417 ProcFsDirectory* pPciDir =
418 new ProcFsDirectory(String("pci"), 0, 0, 0, getNextInode(), this, 0, pBusDir);
419
420 pBusDir->setPermissions(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
421 pPciDir->setPermissions(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
422
423 m_pRoot->addEntry(pBusDir->getName(), pBusDir);
424 pBusDir->addEntry(pPciDir->getName(), pPciDir);
425
426 // Load PCI devices into bus/pci/devices file
427 auto printer = [pPciDir, this](Device* p) -> Device* {
428 String bus;
429 bus.Format("%02x", p->getPciBusPosition());
430
431 // create config space file for this
432 File* d = VFS::instance().find(bus, pPciDir);
433 if (!d) {
434 ProcFsDirectory* dir = new ProcFsDirectory(bus, 0, 0, 0, getNextInode(), this, 0, pPciDir);
435 pPciDir->addEntry(dir->getName(), dir);
436
437 d = dir;
438 }
439
440 ProcFsDirectory* dir = static_cast<ProcFsDirectory*>(Directory::fromFile(d));
441
442 String fn;
443 fn.Format("%02x.%01x", p->getPciDevicePosition(), p->getPciFunctionNumber());
444
445 // Sometimes the device file already exists, avoid creating duplicate
446 // files
447 File* e = VFS::instance().find(fn, dir);
448 if (!e) {
449 PciBus::ConfigSpace space = p->getPciConfigHeader();
450
451 ConstantFile* cf = new ConstantFile(fn, reinterpret_cast<const char*>(&space), sizeof(space),
452 getNextInode(), this, dir);
453 dir->addEntry(cf->getName(), cf);
454 }
455
456 String initial;
457 initial.Format("%02x%02x\t%04x%04x\t%x", p->getPciBusPosition(),
458 (p->getPciDevicePosition() << 4) | p->getPciFunctionNumber(),
459 p->getPciVendorId(), p->getPciDeviceId(), p->getInterruptNumber());
460
461 String resStart;
462 String resLength;
463 for (size_t i = 0; i < 7; ++i) {
464 String thisResStart;
465 String thisResLength;
466
467 if (i < p->addresses().count()) {
468 size_t length = p->addresses()[i]->m_Size;
469
470 uintptr_t address = p->addresses()[i]->m_Address;
471 uintptr_t flags = 0;
472 if (p->addresses()[i]->m_IsIoSpace) {
473 flags = 1;
474 }
475
477 thisResStart.Format("\t%16lx", address | flags);
478 thisResLength.Format("\t%16lx", length ? length + 1 : 0);
479 } else {
480 thisResStart.Format("\t%16lx", 0);
481 thisResLength.Format("\t%16lx", 0);
482 }
483
484 resStart += thisResStart;
485 resLength += thisResLength;
486 }
487
488 m_PciDevices += initial;
489 m_PciDevices += resStart;
490 m_PciDevices += resLength;
491 m_PciDevices += "\t";
492 m_PciDevices += "\n";
493
494 return p;
495 };
496
497 auto callback = pedigree_std::make_callable(printer);
498 Device::foreach (callback, nullptr);
499
500 ConstantFile* pPciDevices = new ConstantFile(
501 String("devices"), m_PciDevices.cstr(), m_PciDevices.length(), getNextInode(), this, pPciDir);
502 pPciDir->addEntry(pPciDevices->getName(), pPciDevices);
503
504 if (!initialiseNamespaceLinks()) {
505 return false;
506 }
507 LockGuard<Mutex> guard(procRegistryLock);
508 if (!m_Registered) {
509 m_RegistryNext = procRegistry;
510 procRegistry = this;
511 m_Registered = true;
512 }
513 size_t after = 0;
515 while (Scheduler::instance().acquireNextProcess(process, after)) {
516 after = process->getId();
517 if (process->getType() == Process::Posix) {
518 addProcess(static_cast<PosixProcess*>(process.get()));
519 }
520 }
521 return true;
522}
523
524size_t ProcFs::getNextInode() {
525 return (m_NextInode += 1) - 1;
526}
527
528void ProcFs::revertInode() {
529 // Concurrent allocations may already have published later inode numbers.
530 // A failed creation leaves a gap rather than reusing a live inode.
531}
532
534 size_t pid = proc->getUserspaceId(m_PidNamespace.get());
535 if (!pid) {
536 return;
537 }
538
540 name.append(pid);
541 Directory::ChildLease existing;
542 if (m_pRoot->lookupChild(HashedStringView(name), existing) == Directory::LookupStatus::Found) {
543 return;
544 }
545 auto procDir = createProcessDirectory(proc);
546 if (!procDir) {
547 WARNING("ProcFs: could not prepare process directory for " << Dec << pid);
548 return;
549 }
550 procDir->setPermissions(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
551
553 if (proc->getUser()) {
554 procDir->setUid(proc->getUser()->getId());
555 }
556 if (proc->getGroup()) {
557 procDir->setGid(proc->getGroup()->getId());
558 }
559
560 // Directory owns publication and lifetime, including concurrent exit and lookup.
561 m_pRoot->addEntry(procDir->getName(), procDir);
562
564}
565
566void ProcFs::removeProcess(PosixProcess* proc) {
567 size_t pid = proc->getUserspaceId(m_PidNamespace.get());
568 if (!pid) {
569 return;
570 }
571
572 String s;
573 s.Format("%d", pid);
574
575 m_pRoot->remove(s);
576}
577
578void ProcFs::publishProcess(PosixProcess* process) {
579 LockGuard<Mutex> guard(procRegistryLock);
580 for (auto* filesystem = procRegistry; filesystem; filesystem = filesystem->m_RegistryNext) {
581 filesystem->addProcess(process);
582 }
583}
584
585void ProcFs::unpublishProcess(PosixProcess* process) {
586 LockGuard<Mutex> guard(procRegistryLock);
587 for (auto* filesystem = procRegistry; filesystem; filesystem = filesystem->m_RegistryNext) {
588 filesystem->removeProcess(process);
589 }
590}
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:316
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:300
static void foreach(Callback callback, Device *root=0)
Definition Device.cc:107
static Directory * fromFile(File *pF)
Definition Directory.h:151
void remove(const HashedStringView &s)
Definition Directory.cc:597
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:355
Definition Disk.h:35
Definition File.h:75
String getName() const
Definition File.cc:782
void setGidOnly(size_t gid)
Definition File.cc:1448
void setPermissionsOnly(uint32_t perms)
Definition File.cc:1440
void setUidOnly(size_t uid)
Definition File.cc:1444
size_t getId() const
Definition Group.h:50
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:82
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:101
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:222
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:202
Definition Mutex.h:56
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:121
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:139
void resync()
Definition ProcFs.cc:144
static PhysicalMemoryManager & instance()
void addProcess(PosixProcess *proc)
Definition ProcFs.cc:533
virtual bool initialise(Disk *pDisk)
Definition ProcFs.cc:341
size_t getUserspaceId() const
Definition Process.h:504
size_t getId()
Definition Process.h:499
Group * getGroup() const
User * getUser() const
static Scheduler & instance()
Definition Scheduler.h:96
T * get() const
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:261
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition ProcFs.cc:242
size_t getId() const
Definition User.h:60
File * find(const String &path, File *pStartNode=0)
Definition VFS.cc:1023
static VFS & instance()
Definition VFS.cc:311
@ Dec
Definition Log.h:126