From e7238c8a529af289fcdcc4d6368b3f703e2c470f Mon Sep 17 00:00:00 2001
From: nakst <>
Date: Fri, 4 Feb 2022 22:15:46 +0000
Subject: [PATCH] add APIs for applications to add and remove custom mount
 points

---
 apps/file_manager/main.cpp |  37 ++++---
 apps/installer.cpp         |   2 +
 apps/installer.ini         |   1 +
 desktop/api.cpp            | 208 +++++++++++++++++++------------------
 desktop/desktop.cpp        |  73 ++++++-------
 desktop/os.header          |  37 +++----
 desktop/posix.cpp          |  19 +++-
 kernel/drivers.cpp         |  13 ++-
 kernel/files.cpp           |  28 ++---
 kernel/module.h            |  17 +--
 kernel/objects.cpp         |   4 +-
 kernel/syscall.cpp         |  35 ++++++-
 util/api_table.ini         |   3 +-
 13 files changed, 251 insertions(+), 226 deletions(-)

diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp
index d3b099e..516abd8 100644
--- a/apps/file_manager/main.cpp
+++ b/apps/file_manager/main.cpp
@@ -99,6 +99,7 @@ struct HistoryEntry {
 struct Drive {
 	char *prefix;
 	size_t prefixBytes;
+	EsObjectID deviceID;
 	EsVolumeInformation information;
 };
 
@@ -424,14 +425,16 @@ void DriveRefreshFolders(bool removed, const char *prefix, size_t prefixBytes) {
 	foldersToRefresh.Free();
 }
 
-void DriveRemove(const char *prefix, size_t prefixBytes) {
-	if (!prefixBytes || prefix[0] == '|') return;
+void DriveRemove(EsObjectID deviceID) {
+	char *prefix = nullptr;
+	size_t prefixBytes = 0;
 	EsMutexAcquire(&drivesMutex);
 	bool found = false;
 
 	for (uintptr_t index = 0; index < drives.Length(); index++) {
-		if (0 == EsStringCompareRaw(prefix, prefixBytes, drives[index].prefix, drives[index].prefixBytes)) {
-			EsHeapFree(drives[index].prefix);
+		if (drives[index].deviceID == deviceID) {
+			prefix = drives[index].prefix;
+			prefixBytes = drives[index].prefixBytes;
 			drives.Delete(index);
 			found = true;
 
@@ -447,17 +450,25 @@ void DriveRemove(const char *prefix, size_t prefixBytes) {
 	EsAssert(found);
 	EsMutexRelease(&drivesMutex);
 	DriveRefreshFolders(true, prefix, prefixBytes);
+	EsHeapFree(prefix);
 }
 
-void DriveAdd(const char *prefix, size_t prefixBytes) {
-	if (!prefixBytes || prefix[0] == '|') return;
-
+void DriveAdd(EsHandle deviceHandle, EsObjectID deviceID) {
+	// TODO Proper prefix allocation algorithm for drives.
+	static int nextPrefix = 1;
+	char prefix[16];
+	bool isBootFileSystem = EsDeviceControl(deviceHandle, ES_DEVICE_CONTROL_FS_IS_BOOT, 0, nullptr) == 1;
+	size_t prefixBytes = EsStringFormat(prefix, sizeof(prefix), "%fd:", ES_STRING_FORMAT_SIMPLE, isBootFileSystem ? 0 : nextPrefix);
+	if (!isBootFileSystem) nextPrefix++;
+	
 	EsMutexAcquire(&drivesMutex);
 
 	Drive drive = {};
 	drive.prefix = (char *) EsHeapAllocate(prefixBytes, false);
 	EsMemoryCopy(drive.prefix, prefix, prefixBytes);
 	drive.prefixBytes = prefixBytes;
+	drive.deviceID = deviceID;
+	EsMountPointAdd(prefix, prefixBytes, deviceHandle);
 	EsMountPointGetVolumeInformation(prefix, prefixBytes, &drive.information);
 	drives.Add(drive);
 
@@ -528,8 +539,8 @@ void _start() {
 
 	// Enumerate drives.
 
-	EsMountPointEnumerate([] (const char *prefix, size_t prefixBytes, EsGeneric) {
-		DriveAdd(prefix, prefixBytes);
+	EsDeviceEnumerate([] (EsMessageDevice device, EsGeneric) {
+		if (device.type == ES_DEVICE_FILE_SYSTEM) DriveAdd(device.handle, device.id);
 	}, 0);
 
 	// Process messages.
@@ -585,10 +596,10 @@ void _start() {
 			loadedFolders.Free();
 			thumbnailCache.Free();
 #endif
-		} else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) {
-			DriveAdd(message->registerFileSystem.mountPoint->prefix, message->registerFileSystem.mountPoint->prefixBytes);
-		} else if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM) {
-			DriveRemove(message->unregisterFileSystem.mountPoint->prefix, message->unregisterFileSystem.mountPoint->prefixBytes);
+		} else if (message->type == ES_MSG_DEVICE_CONNECTED && message->device.type == ES_DEVICE_FILE_SYSTEM) {
+			DriveAdd(message->device.handle, message->device.id);
+		} else if (message->type == ES_MSG_DEVICE_DISCONNECTED && message->device.type == ES_DEVICE_FILE_SYSTEM) {
+			DriveRemove(message->device.id);
 		} else if (message->type == ES_MSG_FILE_MANAGER_FILE_MODIFIED) {
 			char *_path = (char *) EsHeapAllocate(message->user.context2.u, false);
 			EsConstantBufferRead(message->user.context1.u, _path); 
diff --git a/apps/installer.cpp b/apps/installer.cpp
index 1a5a91b..50465e6 100644
--- a/apps/installer.cpp
+++ b/apps/installer.cpp
@@ -2,6 +2,8 @@
 // It is released under the terms of the MIT license -- see LICENSE.md.
 // Written by: nakst.
 
+// TODO Update to use the new mount point APIs.
+
 #define ES_PRIVATE_APIS
 #define INSTALLER
 
diff --git a/apps/installer.ini b/apps/installer.ini
index 52f4300..a1a9ef1 100644
--- a/apps/installer.ini
+++ b/apps/installer.ini
@@ -5,6 +5,7 @@ permission_all_devices=1
 permission_shutdown=1
 hidden=1
 is_installer=1
+disabled=1
 
 [build]
 source=apps/installer.cpp
diff --git a/desktop/api.cpp b/desktop/api.cpp
index ae3979a..8a7c97a 100644
--- a/desktop/api.cpp
+++ b/desktop/api.cpp
@@ -87,11 +87,6 @@ struct ThreadLocalStorage {
 	uint64_t timerAdjustTicks;
 };
 
-struct MountPoint : EsMountPoint {
-	EsVolumeInformation information;
-	bool removing;
-};
-
 struct Timer {
 	EsTimer id;
 	double afterMs;
@@ -193,7 +188,8 @@ struct {
 
 	EsHandle desktopRequestPipe, desktopResponsePipe;
 
-	Array<MountPoint> mountPoints;
+	EsMutex mountPointsMutex;
+	Array<EsMountPoint> mountPoints2;
 	Array<EsMessageDevice> connectedDevices;
 	bool foundBootFileSystem;
 	EsProcessStartupInformation *startupInformation;
@@ -245,7 +241,8 @@ EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptr
 uint8_t *ApplicationStartupInformationToBuffer(const _EsApplicationStartupInformation *information, size_t *dataBytes = nullptr);
 char *SystemConfigurationGroupReadString(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, size_t *valueBytes = nullptr);
 int64_t SystemConfigurationGroupReadInteger(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, int64_t defaultValue = 0);
-MountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes);
+bool NodeFindMountPoint(const char *prefix, size_t prefixBytes, EsMountPoint *result, bool mutexTaken);
+EsError MountPointAdd(const char *prefix, size_t prefixBytes, EsHandle base, bool addedByApplication);
 EsWindow *WindowFromWindowID(EsObjectID id);
 void POSIXCleanup();
 extern "C" void _init();
@@ -276,49 +273,101 @@ uintptr_t APISyscallCheckForCrash(uintptr_t argument0, uintptr_t argument1, uint
 }
 #endif
 
-MountPoint *NodeAddMountPoint(const char *prefix, size_t prefixBytes, EsHandle base, bool queryInformation) {
-	MountPoint mountPoint = {};
-	EsAssert(prefixBytes < sizeof(mountPoint.prefix));
-	EsMemoryCopy(mountPoint.prefix, prefix, prefixBytes);
-	mountPoint.base = base;
-	mountPoint.prefixBytes = prefixBytes;
+EsError MountPointAdd(const char *prefix, size_t prefixBytes, EsHandle base, bool addedByApplication) {
+	EsMutexAcquire(&api.mountPointsMutex);
+	bool duplicate = NodeFindMountPoint(prefix, prefixBytes, nullptr, true);
+	EsError error = ES_SUCCESS;
 
-	if (queryInformation) {
-		EsSyscall(ES_SYSCALL_VOLUME_GET_INFORMATION, base, (uintptr_t) &mountPoint.information, 0, 0);
-	}
+	if (duplicate) {
+		error = ES_ERROR_MOUNT_POINT_ALREADY_EXISTS;
+	} else {
+		EsMountPoint mountPoint = {};
+		EsAssert(prefixBytes < sizeof(mountPoint.prefix));
+		EsMemoryCopy(mountPoint.prefix, prefix, prefixBytes);
+		mountPoint.base = EsSyscall(ES_SYSCALL_HANDLE_SHARE, base, ES_CURRENT_PROCESS, 0, 0);
+		mountPoint.prefixBytes = prefixBytes;
+		mountPoint.addedByApplication = addedByApplication;
 
-	return api.mountPoints.Add(mountPoint);
-}
-
-MountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes) {
-	for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
-		MountPoint *mountPoint = &api.mountPoints[i];
-
-		if (prefixBytes >= mountPoint->prefixBytes 
-				&& 0 == EsMemoryCompare(prefix, mountPoint->prefix, mountPoint->prefixBytes)
-				&& !mountPoint->removing) {
-			return mountPoint;
+		if (ES_CHECK_ERROR(mountPoint.base)) {
+			error = ES_ERROR_INSUFFICIENT_RESOURCES;
+		} else {
+			if (!api.mountPoints2.Add(mountPoint)) {
+				EsHandleClose(mountPoint.base);
+				error = ES_ERROR_INSUFFICIENT_RESOURCES;
+			}
 		}
 	}
 
-	return nullptr;
+	EsMutexRelease(&api.mountPointsMutex);
+	return error;
+}
+
+EsError EsMountPointAdd(const char *prefix, size_t prefixBytes, EsHandle base) {
+	return MountPointAdd(prefix, prefixBytes, base, true);
+}
+
+bool NodeFindMountPoint(const char *prefix, size_t prefixBytes, EsMountPoint *result, bool mutexTaken) {
+	if (!mutexTaken) EsMutexAcquire(&api.mountPointsMutex);
+	bool found = false;
+
+	for (uintptr_t i = 0; i < api.mountPoints2.Length(); i++) {
+		EsMountPoint *mountPoint = &api.mountPoints2[i];
+
+		if (prefixBytes >= mountPoint->prefixBytes && 0 == EsMemoryCompare(prefix, mountPoint->prefix, mountPoint->prefixBytes)) {
+			// Only permanent mount points can be used retrieved with NodeFindMountPoint when mutexTaken = false,
+			// because mount points added by the application can be removed as soon as we release the mutex,
+			// and the base handle would be closed.
+			EsAssert(mutexTaken || !mountPoint->addedByApplication);
+			if (result) EsMemoryCopy(result, mountPoint, sizeof(EsMountPoint));
+			found = true;
+			break;
+		}
+	}
+
+	if (!mutexTaken) EsMutexRelease(&api.mountPointsMutex);
+	return found;
+}
+
+bool EsMountPointRemove(const char *prefix, size_t prefixBytes) {
+	EsMutexAcquire(&api.mountPointsMutex);
+	bool found = false;
+
+	for (uintptr_t i = 0; i < api.mountPoints2.Length(); i++) {
+		EsMountPoint *mountPoint = &api.mountPoints2[i];
+
+		if (prefixBytes >= mountPoint->prefixBytes && 0 == EsMemoryCompare(prefix, mountPoint->prefix, mountPoint->prefixBytes)) {
+			EsAssert(mountPoint->addedByApplication);
+			EsHandleClose(mountPoint->base);
+			api.mountPoints2.Delete(i);
+			found = true;
+			break;
+		}
+	}
+
+	EsMutexRelease(&api.mountPointsMutex);
+	return found;
 }
 
 bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information) {
-	MountPoint *mountPoint = NodeFindMountPoint(prefix, prefixBytes);
-	if (!mountPoint) return false;
-	EsSyscall(ES_SYSCALL_VOLUME_GET_INFORMATION, mountPoint->base, (uintptr_t) &mountPoint->information, 0, 0);
-	EsMemoryCopy(information, &mountPoint->information, sizeof(EsVolumeInformation));
-	return true;
-}
+	EsMutexAcquire(&api.mountPointsMutex);
+	EsMountPoint mountPoint;
+	bool found = NodeFindMountPoint(prefix, prefixBytes, &mountPoint, true);
 
-void EsMountPointEnumerate(EsMountPointEnumerationCallback callback, EsGeneric context) {
-	EsMessageMutexCheck();
-	
-	for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
-		MountPoint *mountPoint = &api.mountPoints[i];
-		callback(mountPoint->prefix, mountPoint->prefixBytes, context);
+	if (found) {
+		_EsNodeInformation node;
+		node.handle = mountPoint.base;
+		EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) "/", 1, ES_NODE_DIRECTORY, (uintptr_t) &node);
+
+		if (error == ES_SUCCESS) {
+			EsSyscall(ES_SYSCALL_VOLUME_GET_INFORMATION, node.handle, (uintptr_t) information, 0, 0);
+			EsHandleClose(node.handle);
+		} else {
+			EsMemoryZero(information, sizeof(EsVolumeInformation));
+		}
 	}
+
+	EsMutexRelease(&api.mountPointsMutex);
+	return found;
 }
 
 void EsDeviceEnumerate(EsDeviceEnumerationCallback callback, EsGeneric context) {
@@ -330,17 +379,24 @@ void EsDeviceEnumerate(EsDeviceEnumerationCallback callback, EsGeneric context)
 }
 
 EsError NodeOpen(const char *path, size_t pathBytes, uint32_t flags, _EsNodeInformation *node) {
-	EsMountPoint *mountPoint = NodeFindMountPoint(path, pathBytes);
+	// TODO I really don't like having to acquire a mutex to open a node.
+	// 	This could be replaced with a writer lock!
+	// 	(...but we don't have writer locks in userland yet.)
+	EsMutexAcquire(&api.mountPointsMutex);
 
-	if (!mountPoint) {
-		return ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME;
+	EsMountPoint mountPoint;
+	bool found = NodeFindMountPoint(path, pathBytes, &mountPoint, true);
+	EsError error = ES_ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME;
+
+	if (found) {
+		node->handle = mountPoint.base;
+		path += mountPoint.prefixBytes;
+		pathBytes -= mountPoint.prefixBytes;
+		error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, flags, (uintptr_t) node);
 	}
 
-	node->handle = mountPoint->base;
-	path += mountPoint->prefixBytes;
-	pathBytes -= mountPoint->prefixBytes;
-
-	return EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, flags, (uintptr_t) node);
+	EsMutexRelease(&api.mountPointsMutex);
+	return error;
 }
 
 EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, bool createIfNeeded) {
@@ -1069,7 +1125,7 @@ EsMessage *EsMessageReceive() {
 				FreeUnusedStyles(true /* include permanent styles */);
 				theming.loadedStyles.Free();
 				SystemConfigurationUnload();
-				api.mountPoints.Free();
+				api.mountPoints2.Free();
 				api.postBox.Free();
 				api.timers.Free();
 				gui.animatingElements.Free();
@@ -1089,13 +1145,6 @@ EsMessage *EsMessageReceive() {
 #endif
 				EsProcessTerminateCurrent();
 			}
-		} else if (message.message.type == ES_MSG_UNREGISTER_FILE_SYSTEM) {
-			for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
-				if (api.mountPoints[i].information.id == message.message.unregisterFileSystem.id) {
-					EsHandleClose(api.mountPoints[i].base);
-					api.mountPoints.Delete(i);
-				}
-			}
 		}
 
 		EsMessageMutexRelease();
@@ -1291,49 +1340,6 @@ EsMessage *EsMessageReceive() {
 
 				return &message.message;
 			}
-		} else if (type == ES_MSG_REGISTER_FILE_SYSTEM) {
-			EsMessageRegisterFileSystem *m = &message.message.registerFileSystem;
-
-			int64_t index = 1; // TODO This is incorrect!
-
-			while (true) {
-				bool seen = false;
-
-				for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
-					if (index == EsIntegerParse(api.mountPoints[i].prefix, api.mountPoints[i].prefixBytes)) {
-						seen = true;
-						break;
-					}
-				}
-
-				if (seen) {
-					index++;
-				} else {
-					break;
-				}
-			}
-
-			bool isBootFileSystem = m->isBootFileSystem;
-			char prefix[16];
-			size_t prefixBytes = EsStringFormat(prefix, sizeof(prefix), "%fd:", ES_STRING_FORMAT_SIMPLE, isBootFileSystem ? 0 : index);
-
-			m->mountPoint = NodeAddMountPoint(prefix, prefixBytes, m->rootDirectory, true);
-
-			if (isBootFileSystem) {
-				api.foundBootFileSystem = true;
-			}
-
-			if (m->mountPoint) {
-				return &message.message;
-			}
-		} else if (type == ES_MSG_UNREGISTER_FILE_SYSTEM) {
-			for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
-				if (api.mountPoints[i].information.id == message.message.unregisterFileSystem.id) {
-					message.message.unregisterFileSystem.mountPoint = &api.mountPoints[i];
-					api.mountPoints[i].removing = true;
-					return &message.message;
-				}
-			}
 		} else if (type == ES_MSG_DEVICE_CONNECTED) {
 			api.connectedDevices.Add(message.message.device);
 			return &message.message;
@@ -1561,7 +1567,7 @@ extern "C" void _start(EsProcessStartupInformation *_startupInformation) {
 
 		path = EsSystemConfigurationReadString(EsLiteral("paths"), EsLiteral("fonts"));
 		NodeOpen(path, EsCStringLength(path), ES_NODE_DIRECTORY, &node);
-		NodeAddMountPoint(EsLiteral("|Fonts:"), node.handle, false);
+		MountPointAdd(EsLiteral("|Fonts:"), node.handle, false);
 		EsHeapFree(path);
 
 		SettingsLoadDefaults();
@@ -1586,7 +1592,7 @@ extern "C" void _start(EsProcessStartupInformation *_startupInformation) {
 
 		for (uintptr_t i = 0; i < header->initialMountPointCount; i++) {
 			const EsMountPoint *mountPoint = (const EsMountPoint *) EsBufferRead(&buffer, sizeof(EsMountPoint));
-			NodeAddMountPoint(mountPoint->prefix, mountPoint->prefixBytes, mountPoint->base, true);
+			MountPointAdd(mountPoint->prefix, mountPoint->prefixBytes, mountPoint->base, false);
 		}
 
 		for (uintptr_t i = 0; i < header->initialDeviceCount; i++) {
diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp
index bd1b12c..8288e71 100644
--- a/desktop/desktop.cpp
+++ b/desktop/desktop.cpp
@@ -655,6 +655,10 @@ int CursorLocatorMessage(EsElement *element, EsMessage *message) {
 }
 
 int ProcessGlobalKeyboardShortcuts(EsElement *, EsMessage *message) {
+	if (!desktop.setupDesktopUIComplete) {
+		return 0;
+	}
+
 	if (message->type == ES_MSG_KEY_DOWN) {
 		bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL;
 		uint32_t scancode = ScancodeMapToLabel(message->keyboard.scancode);
@@ -1866,7 +1870,8 @@ bool ApplicationInstanceStart(_EsApplicationStartupInformation *startupInformati
 			arguments.permissions |= ES_PERMISSION_PROCESS_OPEN;
 			arguments.permissions |= ES_PERMISSION_POSIX_SUBSYSTEM;
 
-			MountPoint root = *NodeFindMountPoint(EsLiteral("0:"));
+			EsMountPoint root;
+			EsAssert(NodeFindMountPoint(EsLiteral("0:"), &root, false));
 			root.prefixBytes = EsStringFormat(root.prefix, sizeof(root.prefix), "|POSIX:");
 			initialMountPoints.Add(root);
 
@@ -1879,26 +1884,20 @@ bool ApplicationInstanceStart(_EsApplicationStartupInformation *startupInformati
 		}
 
 		if (application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
-			// We will inform the process of new and removed mount points on the message thread,
-			// in response to ES_MSG_REGISTER_FILE_SYSTEM and ES_MSG_UNREGISTER_FILE_SYSTEM.
-			EsMessageMutexCheck();
-
-			for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
-				initialMountPoints.Add(api.mountPoints[i]);
-				handleDuplicateList.Add(api.mountPoints[i].base);
-				handleModeDuplicateList.Add(0);
-			}
-
 			arguments.permissions |= ES_PERMISSION_GET_VOLUME_INFORMATION;
-		} else {
-			MountPoint fonts = *NodeFindMountPoint(EsLiteral("|Fonts:"));
+		} 
+
+		{
+			EsMountPoint fonts;
+			EsAssert(NodeFindMountPoint(EsLiteral("|Fonts:"), &fonts, false));
 			initialMountPoints.Add(fonts);
 			handleDuplicateList.Add(fonts.base);
 			handleModeDuplicateList.Add(2 /* prevent write */);
 		}
 
-		if (application->permissions & APPLICATION_PERMISSION_ALL_DEVICES) {
-			for (uintptr_t i = 0; i < api.connectedDevices.Length(); i++) {
+		for (uintptr_t i = 0; i < api.connectedDevices.Length(); i++) {
+			if ((application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)
+					|| ((application->permissions & APPLICATION_PERMISSION_ALL_FILES) && api.connectedDevices[i].type == ES_DEVICE_FILE_SYSTEM)) {
 				initialDevices.Add(api.connectedDevices[i]);
 				handleDuplicateList.Add(api.connectedDevices[i].handle);
 				handleModeDuplicateList.Add(0);
@@ -3153,24 +3152,18 @@ void DesktopSendMessage(EsMessage *message) {
 		EmbeddedWindowDestroyed(message->embeddedWindowDestroyedID);
 	} else if (message->type == ES_MSG_APPLICATION_CRASH) {
 		ApplicationInstanceCrashed(message);
-	} else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) {
-		EsHandle rootDirectory = message->registerFileSystem.rootDirectory;
-
-		for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
-			ApplicationProcess *process = desktop.allApplicationProcesses[i];
-
-			if (process->application && (process->application->permissions & APPLICATION_PERMISSION_ALL_FILES)) {
-				message->registerFileSystem.rootDirectory = EsSyscall(ES_SYSCALL_HANDLE_SHARE, rootDirectory, process->handle, 0, 0);
-				EsMessagePostRemote(process->handle, message);
-			}
-		}
 	} else if (message->type == ES_MSG_DEVICE_CONNECTED) {
 		EsHandle handle = message->device.handle;
 
+		EsPrint("Desktop received ES_MSG_DEVICE_CONNECTED with ID %d and type %z.\n", 
+				message->device.id, EnumLookupNameFromValue(enumStrings_EsDeviceType, message->device.type));
+
 		for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
 			ApplicationProcess *process = desktop.allApplicationProcesses[i];
+			if (!process->application) continue;
 
-			if (process->application && (process->application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)) {
+			if ((process->application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)
+					|| ((process->application->permissions & APPLICATION_PERMISSION_ALL_FILES) && message->device.type == ES_DEVICE_FILE_SYSTEM)) {
 				message->device.handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, handle, process->handle, 0, 0);
 				EsMessagePostRemote(process->handle, message);
 			}
@@ -3193,26 +3186,22 @@ void DesktopSendMessage(EsMessage *message) {
 			} else {
 				// The screen resolution will be correctly queried in DesktopSetup.
 			}
+		} else if (message->device.type == ES_DEVICE_FILE_SYSTEM) {
+			if (EsDeviceControl(message->device.handle, ES_DEVICE_CONTROL_FS_IS_BOOT, 0, 0) == 1) {
+				// Mount the boot file system at "0:".
+				MountPointAdd("0:", 2, message->device.handle, false);
+				api.foundBootFileSystem = true;
+			}
 		}
-	} else if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM || message->type == ES_MSG_DEVICE_DISCONNECTED) {
+	} else if (message->type == ES_MSG_DEVICE_DISCONNECTED) {
 		for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
 			ApplicationProcess *process = desktop.allApplicationProcesses[i];
+			if (!process->application) continue;
 
-			if (!process->application) {
-				continue;
+			if ((process->application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)
+					|| ((process->application->permissions & APPLICATION_PERMISSION_ALL_FILES) && message->device.type == ES_DEVICE_FILE_SYSTEM)) {
+				EsMessagePostRemote(process->handle, message);
 			}
-
-			if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM) {
-				if (~process->application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
-					continue;
-				}
-			} else if (message->type == ES_MSG_DEVICE_DISCONNECTED) {
-				if (~process->application->permissions & APPLICATION_PERMISSION_ALL_DEVICES) {
-					continue;
-				}
-			}
-
-			EsMessagePostRemote(process->handle, message);
 		}
 	} else if (message->type == ES_MSG_KEY_DOWN) {
 		ProcessGlobalKeyboardShortcuts(nullptr, message);
diff --git a/desktop/os.header b/desktop/os.header
index 306a2b5..9ce931b 100644
--- a/desktop/os.header
+++ b/desktop/os.header
@@ -337,6 +337,7 @@ define ES_ERROR_CANCELLED			(-73)
 define ES_ERROR_BLOCK_ACCESS_INVALID		(-74)
 define ES_ERROR_DEVICE_REMOVED			(-75)
 define ES_ERROR_TOO_MANY_FILES_WITH_NAME	(-76)
+define ES_ERROR_MOUNT_POINT_ALREADY_EXISTS      (-77)
 
 define ES_INVALID_HANDLE 		((EsHandle) (0))
 define ES_CURRENT_THREAD	 	((EsHandle) (0x10))
@@ -1047,8 +1048,6 @@ enum EsMessageType {
 	ES_MSG_INSTANCE_CREATE			= 0x6002
 	ES_MSG_DEVICE_CONNECTED			= 0x6003
 	ES_MSG_DEVICE_DISCONNECTED		= 0x6004
-	ES_MSG_REGISTER_FILE_SYSTEM		= 0x6005
-	ES_MSG_UNREGISTER_FILE_SYSTEM		= 0x6006
 
 	// Instance messages:
 	ES_MSG_INSTANCE_OPEN			= 0x6800
@@ -1176,6 +1175,8 @@ enum EsDeviceControlType {
 	ES_DEVICE_CONTROL_BLOCK_DETECT_FS       = 0x1004 // Detect file systems. All existing file systems must have been unmounted.
 
 	ES_DEVICE_CONTROL_CLOCK_READ            = 0x2001
+
+	ES_DEVICE_CONTROL_FS_IS_BOOT            = 0x3001
 }
 
 function_pointer int EsElementCallback(struct EsElement *element, struct EsMessage *message);
@@ -1297,12 +1298,6 @@ struct EsSnapshotProcesses {
 	EsSnapshotProcessesItem processes[];
 }
 
-struct EsMountPoint {
-	char prefix[16];
-	size_t prefixBytes;
-	EsHandle base;
-};
-
 struct EsProcessCreateData {
 	EsHandle systemData;
 	EsHandle subsystemData;
@@ -1799,20 +1794,9 @@ struct EsMessageTabOperation {
 	EsError error;
 };
 
-struct EsMessageRegisterFileSystem {
-	EsHandle rootDirectory;
-	bool isBootFileSystem;
-	EsMountPoint *mountPoint;
-};
-
-struct EsMessageUnregisterFileSystem {
-	EsObjectID id;
-	EsMountPoint *mountPoint;
-};
-
 struct EsMessageDevice {
 	EsObjectID id;
-	EsHandle handle;
+	EsHandle handle; // Not set in ES_MSG_DEVICE_DISCONNECTED. This handle is owned by the system, and is closed on receiving the disconnected message within EsMessageReceive.
 	EsDeviceType type;
 };
 
@@ -1888,8 +1872,6 @@ struct EsMessage {
 		EsMessageEyedrop eyedrop;
 		EsMessageCreateInstance createInstance;
 		EsMessageTabOperation tabOperation;
-		EsMessageRegisterFileSystem registerFileSystem;
-		EsMessageUnregisterFileSystem unregisterFileSystem;
 		EsMessageDevice device;
 		EsObjectID embeddedWindowDestroyedID;
 	};
@@ -1972,6 +1954,13 @@ struct EsListViewEnumString {
 	STRING string;
 };
 
+private struct EsMountPoint {
+	char prefix[16];
+	size_t prefixBytes;
+	uintptr_t base;
+	bool addedByApplication;
+};
+
 // Function pointer types.
 
 function_pointer void EsThreadEntryCallback(EsGeneric argument);
@@ -1980,7 +1969,6 @@ function_pointer int EsCRTComparisonCallback(const void *left, const void *right
 function_pointer void EsTimerCallback(EsGeneric argument);
 function_pointer void EsMenuCallback(EsMenu *menu, EsGeneric context); 
 function_pointer void EsUndoCallback(const void *item, EsUndoManager *manager, EsMessage *message);
-function_pointer void EsMountPointEnumerationCallback(const char *prefix, size_t prefixBytes, EsGeneric context);
 function_pointer void EsDeviceEnumerationCallback(EsMessageDevice device, EsGeneric context);
 function_pointer void EsListViewEnumerateVisibleItemsCallback(EsListView *view, EsElement *item, uint32_t group, EsListViewIndex index);
 function_pointer void EsFontEnumerationCallback(const EsFontInformation *information, EsGeneric context);
@@ -2058,7 +2046,8 @@ function void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flag
 
 // These calls require permission_all_files.
 function bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information); // Returns false if the mount point does not exist.
-function void EsMountPointEnumerate(EsMountPointEnumerationCallback callback, EsGeneric context);
+function EsError EsMountPointAdd(const char *prefix, size_t prefixBytes, EsHandle base); // The system maintains a duplicate of the base handle.
+function bool EsMountPointRemove(const char *prefix, size_t prefixBytes); // Returns false if the mount point does not exist. You can only remove mount points you added with EsMountPointAdd.
 function void EsOpenDocumentQueryInformation(STRING filePath, EsOpenDocumentInformation *information);
 function void _EsPathAnnouncePathMoved(STRING oldPath, STRING newPath); // Set oldPathBytes = 0 to make the file manager refresh the path.
 function void _EsOpenDocumentEnumerate(EsBuffer *outputBuffer);
diff --git a/desktop/posix.cpp b/desktop/posix.cpp
index c4a101f..6eb32c6 100644
--- a/desktop/posix.cpp
+++ b/desktop/posix.cpp
@@ -14,7 +14,7 @@
 extern "C" void *ProcessorTLSRead(uintptr_t offset);
 extern "C" void ProcessorTLSWrite(uintptr_t offset, void *value);
 extern ptrdiff_t tlsStorageOffset;
-EsMountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes);
+bool NodeFindMountPoint(const char *prefix, size_t prefixBytes, EsMountPoint *result, bool mutexTaken);
 EsProcessStartupInformation *ProcessGetStartupInformation();
 
 #define _POSIX_SOURCE
@@ -50,6 +50,7 @@ struct ChildProcess {
 char *workingDirectory;
 Array<ChildProcess> childProcesses;
 Array<void *> _argv;
+EsHandle posixMountPointBase;
 
 #ifdef ES_ARCH_X86_64
 Elf64_Phdr *tlsHeader;
@@ -165,6 +166,14 @@ long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long
 		// EsPrint(":: %z %x %x %x\n", syscallNames[n], a1, a2, a3);
 	}
 
+	if (posixMountPointBase) {
+		// It doesn't matter if multiple threads try to do this at the same time,
+		// they'll all get the same result.
+		EsMountPoint mountPoint;
+		EsAssert(NodeFindMountPoint(EsLiteral("|POSIX:"), &mountPoint, false));
+		posixMountPointBase = mountPoint.base;
+	}
+
 	long returnValue = 0;
 	_EsPOSIXSyscall syscall = { n, a1, a2, a3, a4, a5, a6 };
 
@@ -212,7 +221,7 @@ long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long
 			size_t pathBytes;
 			char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
 			syscall.arguments[0] = (long) path;
-			syscall.arguments[4] = (long) NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
+			syscall.arguments[4] = (long) posixMountPointBase;
 			syscall.arguments[6] = (long) pathBytes;
 			returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
 			// EsPrint("SYS_open '%s' with handle %d\n", pathBytes, path, returnValue);
@@ -306,7 +315,7 @@ long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long
 
 		case SYS_unlink: {
 			_EsNodeInformation node;
-			node.handle = NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
+			node.handle = posixMountPointBase;
 			size_t pathBytes;
 			char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
 			EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE, (uintptr_t) &node);
@@ -326,7 +335,7 @@ long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long
 
 		case SYS_truncate: {
 			_EsNodeInformation node;
-			node.handle = NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
+			node.handle = posixMountPointBase;
 			size_t pathBytes;
 			char *path = EsPOSIXConvertPath((const char *) a1, &pathBytes, false);
 			EsError error = EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, ES_NODE_FAIL_IF_NOT_FOUND | ES_FILE_WRITE, (uintptr_t) &node);
@@ -404,7 +413,7 @@ long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long
 			syscall.arguments[1] = (long) pathBytes;
 			syscall.arguments[2] = (long) newEnvironment;
 			syscall.arguments[3] = (long) environmentSize;
-			syscall.arguments[4] = (long) NodeFindMountPoint(EsLiteral("|POSIX:"))->base;
+			syscall.arguments[4] = (long) posixMountPointBase;
 
 			returnValue = EsSyscall(ES_SYSCALL_POSIX, (uintptr_t) &syscall, 0, 0, 0);
 		} break;
diff --git a/kernel/drivers.cpp b/kernel/drivers.cpp
index a808b9e..76654f3 100644
--- a/kernel/drivers.cpp
+++ b/kernel/drivers.cpp
@@ -80,14 +80,17 @@ void KDeviceDestroy(KDevice *device) {
 	KMutexRelease(&deviceTreeMutex);
 }
 
-void KDeviceOpenHandle(KDevice *device) {
+void KDeviceOpenHandle(KDevice *device, uint32_t handleFlags) {
+	if (device->trackHandle && (handleFlags & K_DEVICE_HANDLE_TRACKED)) device->trackHandle(device, true);
 	KMutexAcquire(&deviceTreeMutex);
 	if (!device->handles) KernelPanic("KDeviceOpenHandle - Device %s has no handles.\n", device);
 	device->handles++;
 	KMutexRelease(&deviceTreeMutex);
 }
 
-void KDeviceCloseHandle(KDevice *device) {
+void KDeviceCloseHandle(KDevice *device, uint32_t handleFlags) {
+	if (device->trackHandle && (handleFlags & K_DEVICE_HANDLE_TRACKED)) device->trackHandle(device, false);
+		
 	KMutexAcquire(&deviceTreeMutex);
 
 	if (!device->handles) KernelPanic("KDeviceCloseHandle - Device %s has no handles.\n", device);
@@ -131,7 +134,7 @@ void DeviceRemovedRecurse(KDevice *device) {
 	}
 }
 
-void KDeviceSendConnectedMessage(KDevice *device, EsDeviceType type) {
+void KDeviceSendConnectedMessage(KDevice *device, EsDeviceType type, uint32_t handleFlags) {
 	KMutexAcquire(&deviceTreeMutex);
 
 	if (device->flags & K_DEVICE_VISIBLE_TO_USER) {
@@ -143,14 +146,14 @@ void KDeviceSendConnectedMessage(KDevice *device, EsDeviceType type) {
 
 	KMutexRelease(&deviceTreeMutex);
 
-	KDeviceOpenHandle(device);
+	KDeviceOpenHandle(device, handleFlags);
 
 	_EsMessageWithObject m;
 	EsMemoryZero(&m, sizeof(m));
 	m.message.type = ES_MSG_DEVICE_CONNECTED;
 	m.message.device.id = device->objectID;
 	m.message.device.type = type;
-	m.message.device.handle = DesktopOpenHandle(device, 0, KERNEL_OBJECT_DEVICE);
+	m.message.device.handle = DesktopOpenHandle(device, handleFlags, KERNEL_OBJECT_DEVICE);
 
 	if (m.message.device.handle) {
 		if (!DesktopSendMessage(&m)) {
diff --git a/kernel/files.cpp b/kernel/files.cpp
index ab9d31c..c22b636 100644
--- a/kernel/files.cpp
+++ b/kernel/files.cpp
@@ -1881,38 +1881,22 @@ void FSRegisterBootFileSystem(KFileSystem *fileSystem, EsUniqueIdentifier identi
 	FSRegisterFileSystem(fileSystem); 
 }
 
-void FSFileSystemDeviceRemoved(KDevice *device) {
+void FSTrackUserFileSystemHandle(KDevice *device, bool opened) {
 	KFileSystem *fileSystem = (KFileSystem *) device;
-	_EsMessageWithObject m;
-	EsMemoryZero(&m, sizeof(m));
-	m.message.type = ES_MSG_UNREGISTER_FILE_SYSTEM;
-	m.message.unregisterFileSystem.id = fileSystem->objectID;
-	DesktopSendMessage(&m);
+	if (opened) FSNodeOpenHandle(fileSystem->rootDirectory, ES_FLAGS_DEFAULT, FS_NODE_OPEN_HANDLE_STANDARD);
+	else FSNodeCloseHandle(fileSystem->rootDirectory, ES_FLAGS_DEFAULT);
 }
 
 void FSRegisterFileSystem(KFileSystem *fileSystem) {
-	fileSystem->removed = FSFileSystemDeviceRemoved;
-		
+	fileSystem->trackHandle = FSTrackUserFileSystemHandle;
 	MMObjectCacheRegister(&fileSystem->cachedDirectoryEntries, FSTrimCachedDirectoryEntry, 
 			sizeof(FSDirectoryEntry) + 16 /* approximate average name bytes */ + fileSystem->directoryEntryDataBytes);
 	MMObjectCacheRegister(&fileSystem->cachedNodes, FSTrimCachedNode,
 			sizeof(FSFile) + fileSystem->nodeDataBytes);
 	fileSystem->rootDirectory->directoryEntry->directoryChildren = fileSystem->rootDirectoryInitialChildren;
 	FSNodeOpenHandle(fileSystem->rootDirectory, ES_FLAGS_DEFAULT, fileSystem->isBootFileSystem ? FS_NODE_OPEN_HANDLE_STANDARD : FS_NODE_OPEN_HANDLE_FIRST);
-
-	_EsMessageWithObject m;
-	EsMemoryZero(&m, sizeof(m));
-	m.message.type = ES_MSG_REGISTER_FILE_SYSTEM;
-	m.message.registerFileSystem.isBootFileSystem = fileSystem->isBootFileSystem;
-	m.message.registerFileSystem.rootDirectory = DesktopOpenHandle(fileSystem->rootDirectory, _ES_NODE_DIRECTORY_WRITE, KERNEL_OBJECT_NODE);
-
-	if (m.message.registerFileSystem.rootDirectory) {
-		if (!DesktopSendMessage(&m)) {
-			DesktopCloseHandle(m.message.registerFileSystem.rootDirectory); // This will check that the handle is still valid.
-		}
-	}
-
-	KDeviceSendConnectedMessage(fileSystem, ES_DEVICE_FILE_SYSTEM);
+	KDeviceSendConnectedMessage(fileSystem, ES_DEVICE_FILE_SYSTEM, K_DEVICE_HANDLE_TRACKED);
+	FSNodeCloseHandle(fileSystem->rootDirectory, ES_FLAGS_DEFAULT);
 }
 
 void FSRegisterBlockDevice(KBlockDevice *device) {
diff --git a/kernel/module.h b/kernel/module.h
index f13851a..d48bf7b 100644
--- a/kernel/module.h
+++ b/kernel/module.h
@@ -495,10 +495,13 @@ struct KDevice {
 	EsObjectID objectID;
 
 	// These callbacks are called with the deviceTreeMutex locked, and are all optional.
-	void (*shutdown)(KDevice *device);  // Called when the computer is about to shutdown.
-	void (*dumpState)(KDevice *device); // Dump the entire state of the device for debugging.
-	void (*removed)(KDevice *device);   // Called when the device is removed. Called after the children are informed.
-	void (*destroy)(KDevice *device);   // Called just before the device is destroyed.
+	void (*shutdown)(KDevice *device);                 // Called when the computer is about to shutdown.
+	void (*dumpState)(KDevice *device);                // Dump the entire state of the device for debugging.
+	void (*removed)(KDevice *device);                  // Called when the device is removed. Called after the children are informed.
+	void (*destroy)(KDevice *device);                  // Called just before the device is destroyed.
+	void (*trackHandle)(KDevice *device, bool opened); // Called when a handle to the device with K_DEVICE_HANDLE_TRACKED is opened/closed.
+							     
+#define K_DEVICE_HANDLE_TRACKED (1 << 0)
 };
 
 struct KDriver {
@@ -524,11 +527,11 @@ void KDeviceAttachAll(KDevice *parentDevice, const char *cParentDriver);
 bool KDeviceAttachByName(KDevice *parentDevice, const char *cName);
 
 KDevice *KDeviceCreate(const char *cDebugName, KDevice *parent, size_t bytes /* must be at least the size of a KDevice */);
-void KDeviceOpenHandle(KDevice *device);
+void KDeviceOpenHandle(KDevice *device, uint32_t handleFlags = ES_FLAGS_DEFAULT);
 void KDeviceDestroy(KDevice *device); // Call if initialisation of the device failed. Otherwise use KDeviceCloseHandle.
-void KDeviceCloseHandle(KDevice *device); // The device creator is responsible for one handle after the creating it. The device is destroyed once all handles are closed.
+void KDeviceCloseHandle(KDevice *device, uint32_t handleFlags = ES_FLAGS_DEFAULT); // The device creator is responsible for one handle after the creating it. The device is destroyed once all handles are closed.
 void KDeviceRemoved(KDevice *device); // Call when a child device is removed. Must be called only once!
-void KDeviceSendConnectedMessage(KDevice *device, EsDeviceType type); // Send a message to Desktop to inform it the device was connected.
+void KDeviceSendConnectedMessage(KDevice *device, EsDeviceType type, uint32_t handleFlags = ES_FLAGS_DEFAULT); // Send a message to Desktop to inform it the device was connected.
 
 #include <bin/generated_code/kernel_config.h>
 
diff --git a/kernel/objects.cpp b/kernel/objects.cpp
index 9f95047..dbaae4e 100644
--- a/kernel/objects.cpp
+++ b/kernel/objects.cpp
@@ -190,7 +190,7 @@ bool OpenHandleToObject(void *object, KernelObjectType type, uint32_t flags) {
 		} break;
 
 		case KERNEL_OBJECT_DEVICE: {
-			KDeviceOpenHandle((KDevice *) object);
+			KDeviceOpenHandle((KDevice *) object, flags);
 		} break;
 
 		default: {
@@ -351,7 +351,7 @@ void CloseHandleToObject(void *object, KernelObjectType type, uint32_t flags) {
 		} break;
 
 		case KERNEL_OBJECT_DEVICE: {
-			KDeviceCloseHandle((KDevice *) object);
+			KDeviceCloseHandle((KDevice *) object, flags);
 		} break;
 
 		default: {
diff --git a/kernel/syscall.cpp b/kernel/syscall.cpp
index 554b1f5..4bf9f6c 100644
--- a/kernel/syscall.cpp
+++ b/kernel/syscall.cpp
@@ -651,18 +651,37 @@ SYSCALL_IMPLEMENT(ES_SYSCALL_NODE_OPEN) {
 	_EsNodeInformation information;
 	SYSCALL_READ(&information, argument3, sizeof(_EsNodeInformation));
 
-	SYSCALL_HANDLE_2(information.handle, KERNEL_OBJECT_NODE, _directory);
-	KNode *directory = (KNode *) _directory.object; 
+	SYSCALL_HANDLE_2(information.handle, (KernelObjectType) (KERNEL_OBJECT_NODE | KERNEL_OBJECT_DEVICE), _directory);
+
+	KNode *directory = nullptr;
+	uint64_t directoryFlags = 0;
+
+	if (_directory.type == KERNEL_OBJECT_DEVICE) {
+		KDevice *device = (KDevice *) _directory.object;
+
+		if (device->type == ES_DEVICE_FILE_SYSTEM) {
+			KFileSystem *fileSystem = (KFileSystem *) device;
+			directory = fileSystem->rootDirectory;
+			directoryFlags = _ES_NODE_DIRECTORY_WRITE;
+		} else {
+			SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true);
+		}
+	} else if (_directory.type == KERNEL_OBJECT_NODE) {
+		directory = (KNode *) _directory.object; 
+		directoryFlags = _directory.flags;
+	} else {
+		EsAssert(false);
+	}
 
 	if (directory->directoryEntry->type != ES_NODE_DIRECTORY) {
 		SYSCALL_RETURN(ES_FATAL_ERROR_INCORRECT_NODE_TYPE, true);
 	}
 
-	if ((~_directory.flags & _ES_NODE_DIRECTORY_WRITE) && needWritePermission) {
+	if ((~directoryFlags & _ES_NODE_DIRECTORY_WRITE) && needWritePermission) {
 		SYSCALL_RETURN(ES_ERROR_PERMISSION_NOT_GRANTED, false);
 	}
 
-	if (~_directory.flags & _ES_NODE_DIRECTORY_WRITE) {
+	if (~directoryFlags & _ES_NODE_DIRECTORY_WRITE) {
 		flags |= _ES_NODE_NO_WRITE_BASE;
 	}
 
@@ -1604,6 +1623,14 @@ SYSCALL_IMPLEMENT(ES_SYSCALL_DEVICE_CONTROL) {
 		} else {
 			SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true);
 		}
+	} else if (device->type == ES_DEVICE_FILE_SYSTEM) {
+		KFileSystem *fileSystem = (KFileSystem *) device;
+
+		if (type == ES_DEVICE_CONTROL_FS_IS_BOOT) {
+			SYSCALL_RETURN(fileSystem->isBootFileSystem ? 1 : 0, false);
+		} else {
+			SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true);
+		}
 	} else {
 		SYSCALL_RETURN(ES_FATAL_ERROR_UNKNOWN_SYSCALL, true);
 	}
diff --git a/util/api_table.ini b/util/api_table.ini
index 5124f5a..744844c 100644
--- a/util/api_table.ini
+++ b/util/api_table.ini
@@ -161,8 +161,10 @@ EsStringFormatTemporary=159
 EsStringFormatV=160
 EsStringFormatAppend=161
 EsStringFormatAppendV=162
+EsMountPointAdd=163
 EsSystemConfigurationReadFileTypes=164
 EsImageLoad=165
+EsMountPointRemove=166
 EsCRTabs=168
 EsCRTacosf=169
 EsCRTasinf=170
@@ -369,7 +371,6 @@ EsElementRepaintForScroll=372
 EsMemoryFaultRange=373
 EsDrawBitmapScaled=374
 EsRectangleCenter=375
-EsMountPointEnumerate=376
 EsMountPointGetVolumeInformation=377
 EsListViewInvalidateAll=378
 EsListViewGetFocusedItem=379