The Pedigree Project 0.1
modules/system/mountroot/main.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 "pedigree/kernel/BootstrapInfo.h"
21#include "pedigree/kernel/LockGuard.h"
22#include "pedigree/kernel/Log.h"
23#include "pedigree/kernel/Service.h"
24#include "pedigree/kernel/ServiceFeatures.h"
25#include "pedigree/kernel/ServiceManager.h"
26#include "pedigree/kernel/core/BootIO.h"
27#include "pedigree/kernel/machine/Device.h"
28#include "pedigree/kernel/machine/Disk.h"
29#include "pedigree/kernel/machine/Machine.h"
30#include "pedigree/kernel/panic.h"
31#include "pedigree/kernel/process/Mutex.h"
32#include "pedigree/kernel/utilities/Iterator.h"
33#include "pedigree/kernel/utilities/List.h"
34#include "pedigree/kernel/utilities/Pointers.h"
35#include "pedigree/kernel/utilities/StaticString.h"
36#include "pedigree/kernel/utilities/String.h"
37#include "pedigree/kernel/utilities/StringView.h"
38#include "pedigree/kernel/utilities/Tree.h"
39#include "pedigree/kernel/utilities/Vector.h"
40#include "pedigree/kernel/utilities/utility.h"
41
42#include "modules/Module.h"
43#include "modules/system/lodisk/LoDisk.h"
45#include "modules/system/vfs/Filesystem.h"
46#include "modules/system/vfs/VFS.h"
47
48#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
49#include "pedigree/kernel/processor/hosted/smoke.h"
50#endif
51
52class File;
53
54static bool bRootMounted = false;
55
56enum class RootSelectorKind { None, Uuid, Label };
57static RootSelectorKind g_RootSelectorKind = RootSelectorKind::None;
58static String g_RootSelectorValue;
59
60static List<Filesystem*> g_MountedFilesystems;
61static Mutex g_MountLock;
62static ServiceFeatures g_MountFeatures;
63static FileDisk* g_pLiveDisk = nullptr;
64
65static void error(const char* s) {
66 extern BootIO bootIO;
67 static HugeStaticString str;
68 str += s;
69 str += "\n";
70 bootIO.write(str, BootIO::Red, BootIO::Black);
71 str.clear();
72}
73
74static bool parseRootSelector() {
75 char* commandLine = g_pBootstrapInfo->getCommandLine();
76 if (!commandLine) {
77 return false;
78 }
79
80 RootSelectorKind kind = RootSelectorKind::None;
81 String value;
82 Vector<String> arguments = String(commandLine).tokenise(' ');
83 for (auto argument : arguments) {
84 StringView view = argument.view();
85 if (view.length() > 10 && view.substring(0, 10) == "root=UUID=") {
86 kind = RootSelectorKind::Uuid;
87 value = view.substring(10, view.length()).toString();
88 } else if (view.length() > 11 && view.substring(0, 11) == "root=LABEL=") {
89 if (kind != RootSelectorKind::Uuid) {
90 kind = RootSelectorKind::Label;
91 value = view.substring(11, view.length()).toString();
92 }
93 }
94 }
95
96 if (kind == RootSelectorKind::None || !value.length()) {
97 return false;
98 }
99
100 value.strip();
101 if (!value.length()) {
102 return false;
103 }
104
105 g_RootSelectorKind = kind;
106 g_RootSelectorValue = value;
107 return true;
108}
109
110static bool isRootFilesystem(Filesystem* filesystem) {
111 if (g_RootSelectorKind == RootSelectorKind::Uuid) {
112 String uuid;
113 return filesystem->getUuid(uuid) && uuid == g_RootSelectorValue;
114 }
115 if (g_RootSelectorKind == RootSelectorKind::Label) {
116 return filesystem->getVolumeLabel() == g_RootSelectorValue;
117 }
118 return false;
119}
120
121static Device* probeDisk(Device* diskDevice) {
122 if (diskDevice->getType() != Device::Disk) {
123 return diskDevice;
124 }
125
126 Disk* pDisk = static_cast<Disk*>(diskDevice);
127 for (auto* filesystem : g_MountedFilesystems) {
128 if (filesystem->getDisk() == pDisk) {
129 return diskDevice;
130 }
131 }
132 String stableName;
133 Filesystem* pFs = nullptr;
134 if (VFS::instance().mount(pDisk, stableName, &pFs)) {
135 // For mount message
136 bool didMountAsRoot = false;
137
138 if (!bRootMounted && isRootFilesystem(pFs)) {
139 NOTICE("Mounted " << stableName << " successfully as root.");
141 bRootMounted = didMountAsRoot = true;
142 }
143
144 if (!didMountAsRoot) {
145 NOTICE("Mounted " << stableName << " at /media/" << stableName << ".");
146 }
147
148 g_MountedFilesystems.pushBack(pFs);
149 }
150
151 return diskDevice;
152}
153
154class MountService final : public Service {
155 public:
156 bool serve(ServiceFeatures::Type type, void* data, size_t length) override {
157 if (!data || length != sizeof(Device*)) {
158 return false;
159 }
160 Device* device = static_cast<Device*>(data);
161 LockGuard<Mutex> guard(g_MountLock);
162 if (type == ServiceFeatures::touch) {
163 Device::foreach (probeDisk, device);
164 return true;
165 }
166 if (type != ServiceFeatures::withdraw && type != ServiceFeatures::write) {
167 return false;
168 }
169 for (auto it = g_MountedFilesystems.begin(); it != g_MountedFilesystems.end();) {
170 Filesystem* filesystem = *it;
171 Device* ancestor = filesystem->getDisk();
172 while (ancestor && ancestor != device) {
173 ancestor = ancestor->getParent();
174 }
175 if (!ancestor) {
176 ++it;
177 continue;
178 }
179 const bool root = filesystem == VFS::instance().getRootFilesystem();
180 const String label = filesystem->getVolumeLabel();
181 if (!VFS::instance().removeDiskFilesystem(filesystem, type == ServiceFeatures::write)) {
182 return false;
183 }
184 NOTICE("Removed filesystem " << label);
185 if (root) {
186 ++it;
187 } else {
188 it = g_MountedFilesystems.erase(it);
189 }
190 }
191 return true;
192 }
193};
194static MountService g_MountService;
195
196#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
197static bool installHostedProfileRoot() {
199 if (!root || !root.get()->initialise(nullptr))
200 return false;
201
202 const char* directories[] = {"/etc", "/dev", "/dev/shm", "/run", "/run/lock", "/run/sockets",
203 "/sys", "/var", "/var/run", "/proc", "/tmp"};
204 for (const char* path : directories) {
205 if (!root.get()->createDirectory(StringView(path), 0755))
206 return false;
207 }
208
209 const struct {
210 const char* path;
211 const char* contents;
212 } accounts[] = {{"/etc/passwd", "root:x:0:0:root:/:/bin/sh\n"},
213 {"/etc/group", "root:x:0:root\n"}};
214 for (const auto& account : accounts) {
215 if (!root.get()->createFile(StringView(account.path), 0644))
216 return false;
218 File* file = root.get()->findRetained(StringView(account.path), lease);
219 const size_t length = StringLength(account.contents);
220 if (!file || file->write(0, length, reinterpret_cast<uintptr_t>(account.contents)) != length)
221 return false;
222 }
223
224 // Publish only the complete fixture so POSIX and users see the same boot namespace.
225 if (!VFS::instance().setRootFilesystem(root.get()))
226 return false;
227 g_MountedFilesystems.pushBack(root.releaseOwnership());
228 bRootMounted = true;
229 return true;
230}
231#endif
232
233static bool init() {
234 LockGuard<Mutex> guard(g_MountLock);
235#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
236 const bool hostedProfile = hostedSyscallProfileRequested();
237#else
238 const bool hostedProfile = false;
239#endif
240 if (!hostedProfile && !parseRootSelector()) {
241 error("No valid root=UUID= or root=LABEL= selector was supplied.");
242 if (!HOSTED) {
243 return false;
244 }
245 }
246
247 // Mount scratch filesystem (ie, pure ram filesystem, for POSIX /tmp etc)
248 RamFs* pRamFs = new RamFs;
249 pRamFs->initialise(0);
250 VFS::instance().registerFilesystem(pRamFs, String("scratch"));
251 g_MountedFilesystems.pushBack(pRamFs);
252
253 // Mount runtime filesystem.
254 // The runtime filesystem assigns a Process ownership to each file, only
255 // that process can modify/remove it. If the Process terminates without
256 // removing the file, the file is not removed.
257 RamFs* pRuntimeFs = new RamFs;
258 pRuntimeFs->initialise(0);
259 pRuntimeFs->setProcessOwnership(true);
260 VFS::instance().registerFilesystem(pRuntimeFs, String("runtime"));
261 g_MountedFilesystems.pushBack(pRuntimeFs);
262
263#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
264 if (hostedProfile) {
265 if (!installHostedProfileRoot()) {
266 error("Unable to prepare the hosted syscall profiling root.");
267 return false;
268 }
269 return true;
270 }
271#endif
272
273 // Root selection must not hide later partitions, such as the UEFI ESP.
274 // The first matching root wins; other filesystems remain available in /media.
275 g_MountFeatures.add(ServiceFeatures::touch);
276 g_MountFeatures.add(ServiceFeatures::withdraw);
277 g_MountFeatures.add(ServiceFeatures::write);
278 ServiceManager::instance().addService(String("mountroot"), &g_MountService, &g_MountFeatures);
279 Device::foreach (probeDisk);
280
281 if (VFS::instance().getFilesystemAt(String("/media/raw")) == 0) {
282 error("/media/raw does not exist - cannot continue startup.");
283 return false;
284 }
285
286 // Are we running a live CD?
290 if (VFS::instance().find(String("/livedisk.img"))) {
291 NOTICE("trying to find live disk");
292 FileDisk* pRamDisk = new FileDisk(String("/livedisk.img"), FileDisk::RamOnly);
293 if (pRamDisk && pRamDisk->initialise()) {
294 NOTICE("have a live disk");
295 Device::addToRoot(pRamDisk);
296 g_pLiveDisk = pRamDisk;
297
298 // Mount it in the VFS
300 bRootMounted = false;
301 NOTICE("probing ram disk for partitions");
302 Device::foreach (probeDisk, pRamDisk);
303 } else
304 delete pRamDisk;
305 }
306
307 // Is there a root disk mounted?
308 if (!bRootMounted) {
309 error("No root disk matched the supplied root filesystem selector.");
310 if (!HOSTED) // hosted builds don't mount disks
311 {
312 return false;
313 }
314 }
315
316 // All done, nothing more to do here.
317 return true;
318}
319
320static Device* removeLiveDisk(Device* device) {
321 if (device == g_pLiveDisk) {
322 g_pLiveDisk = nullptr;
323 return nullptr;
324 }
325 return device;
326}
327
328static bool isLiveDiskFilesystem(Filesystem* filesystem) {
329 Device* device = filesystem->getDisk();
330 while (device) {
331 if (device == g_pLiveDisk) {
332 return true;
333 }
334 device = device->getParent();
335 }
336 return false;
337}
338
339static void showUnmount(Filesystem* filesystem) {
340 LargeStaticString detail;
341 detail += "Unmounting ";
342 detail += filesystem->getVolumeLabel();
343 Machine::setShutdownPhase(Machine::ShutdownPhase::Filesystems, detail);
344}
345
346static void destroy() {
347 Machine::setShutdownPhase(Machine::ShutdownPhase::Filesystems, "Draining mount service");
348 ServiceManager::instance().removeService(String("mountroot"));
349 NOTICE("Unmounting all filesystems...");
350
351 Vector<Filesystem*> ownedBackings;
352 Machine::setShutdownPhase(Machine::ShutdownPhase::Filesystems, "Releasing mount namespace");
353 if (!VFS::instance().shutdownMountView(ownedBackings)) {
354 panic("mountroot could not drain the filesystem namespace");
355 }
356 for (auto* filesystem : ownedBackings)
357 g_MountedFilesystems.pushBack(filesystem);
358
359 List<Filesystem*> liveDiskFilesystems;
360 List<Filesystem*> backingFilesystems;
361
362 for (auto filesystem : g_MountedFilesystems) {
363 if (g_pLiveDisk && isLiveDiskFilesystem(filesystem)) {
364 liveDiskFilesystems.pushBack(filesystem);
365 } else {
366 backingFilesystems.pushBack(filesystem);
367 }
368 }
369
370 // Filesystems on the live disk retain its partitions, while FileDisk
371 // retains a File in the original backing filesystem.
372 while (liveDiskFilesystems.count()) {
373 Filesystem* filesystem = liveDiskFilesystems.popFront();
374 showUnmount(filesystem);
375 NOTICE("Unmounting " << filesystem->getVolumeLabel() << " [" << Hex << filesystem << "]...");
376 if (!VFS::instance().unregisterFilesystem(filesystem, true, true)) {
377 panic("mountroot could not cleanly unmount a live-disk filesystem");
378 }
379 NOTICE("unmount done");
380 }
381
382 if (g_pLiveDisk) {
383 Machine::setShutdownPhase(Machine::ShutdownPhase::Filesystems, "Retiring live disk");
384 Device::foreach (removeLiveDisk);
385 if (g_pLiveDisk) {
386 panic("mountroot could not retire its live-disk device");
387 }
388 }
389
390 while (backingFilesystems.count()) {
391 Filesystem* filesystem = backingFilesystems.popFront();
392 showUnmount(filesystem);
393 NOTICE("Unmounting " << filesystem->getVolumeLabel() << " [" << Hex << filesystem << "]...");
394 if (!VFS::instance().unregisterFilesystem(filesystem, true, true)) {
395 panic("mountroot could not cleanly unmount a backing filesystem");
396 }
397 NOTICE("unmount done");
398 }
399
400 g_MountedFilesystems.clear();
401 bRootMounted = false;
402
403 NOTICE("Unmounting all filesystems has completed.");
404}
405
406MODULE_INFO_RUNTIME_PINNED("mountroot", &init, &destroy, "vfs", "partition", "rawfs", "ramfs");
407
408// We expect the filesystems metamodule to fail, but by the time it does and
409// we are allowed to continue, all the filesystems are loaded.
410MODULE_OPTIONAL_DEPENDS("filesystems", "fat", "ext2", "iso9660", "lodisk", "diskimage");
An in-RAM filesystem.
EXPORTED_PUBLIC void write(T &str, Colour foreColour, Colour backColour)
static void addToRoot(Device *device)
Definition Device.cc:114
static void foreach(Callback callback, Device *root=0)
Definition Device.cc:107
@ Disk
A disk device - a block device in UNIX terms.
Definition Device.h:75
Device * getParent() const
Definition Device.h:166
virtual Type getType()
Definition Device.h:178
Definition Disk.h:35
@ RamOnly
Definition LoDisk.h:49
Definition File.h:75
virtual uint64_t write(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true) final
Definition File.cc:342
virtual const String & getVolumeLabel() const =0
virtual bool getUuid(String &uuid) const
Definition Filesystem.h:98
Disk * getDisk()
Definition Filesystem.h:145
Definition List.h:61
static void setShutdownPhase(ShutdownPhase phase, const char *detail=nullptr)
Definition Machine.cc:33
bool serve(ServiceFeatures::Type type, void *data, size_t length) override
Definition Mutex.h:56
Definition RamFs.h:118
virtual bool initialise(Disk *pDisk) override
Definition RamFs.cc:441
virtual void add(Type s)
void addService(const String &serviceName, Service *s, ServiceFeatures *feats)
void removeService(const String &serviceName)
StringView substring(size_t start, size_t end, bool hashed=HASH_STRINGVIEWS_BY_DEFAULT) const
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
bool setRootFilesystem(Filesystem *pFs)
Definition VFS.cc:716
String registerFilesystem(Filesystem *pFs, const String &preferredStableName)
Definition VFS.cc:534
Filesystem * getRootFilesystem() const
Definition VFS.cc:742
static VFS & instance()
Definition VFS.cc:311
A vector / dynamic array.
Definition Vector.h:33
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Hex
Definition Log.h:124
T popFront()
Definition List.h:330
size_t count() const
Definition List.h:212
void pushBack(const T &value)
Definition List.h:216