/****************************************************************************** COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011. This file is part of Sortix. Sortix is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Sortix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Sortix. If not, see . mount.cpp Handles system wide mount points and initialization of new file systems. ******************************************************************************/ #include "platform.h" #include #include "device.h" #include "filesystem.h" #include "thread.h" #include "process.h" #include "memorymanagement.h" namespace Sortix { namespace Syscall { void SysOpen(Thread* thread) { struct Parameters { struct { const char* USER userPath SYSPARAM; nat openFlags SYSPARAM; nat mode SYSPARAM; }; struct { int handle; bool pending; FileSystem::SysCallback callbackInfo; const char* safePath; }; }; // We store our parameters and data inside the thread object, and we // are passed parameters in a special way, so make sure it works. STATIC_ASSERT(0 * sizeof(size_t) == offsetof(Parameters, userPath)); STATIC_ASSERT(1 * sizeof(size_t) == offsetof(Parameters, openflags)); STATIC_ASSERT(2 * sizeof(size_t) == offsetof(Parameters, mode)); STATIC_ASSERT(sizeof(Parameters) <= sizeof(thread->sysParams)); Parameters* params = (Parameters*) thread->sysParams; int handle; const char* path; // Initialize our variables and validate parameters. if ( !thread->sysParamsInited ) { thread->sysParamsInited = true; params->handle = handle = -1; params->pending = false; // Make sure the path is a valid string in userspace. params->safePath = path = ValidateUserString(params->userPath) if ( path == NULL ) { thread->SysReturnError(-1); return; } params->openFlags &= O_USERSPACEABLE; } else { path = params->safePath; handle = param->handle; } // Get a descriptor for our device. if ( handle == -1 ) { param->handle = handle = thread->GetProcess()->_descs.Reserve(); if ( handle < 0 ) { thread->SysReturnError(handle); return; } } // Attempt to open the path. If the operation is blocking, this // function will re-called when it completes, successful or not. if ( !params->pending ) { params->pending = true; MountPoint* mount = Mount::GetMountPoint(path); if ( mount == NULL ) { Error::Set(Error::BADINPUT); thread->SysReturnError(-1); return; } if ( !mount->fs->Open(path, params->openFlags, params->mode, ¶ms->callbackInfo, thread) ) { return; } } // Return an error if the open failed. if ( unlikely(params->callbackInfo.device == NULL) ) { handle = thread->GetProcess()->_descs.Free(handle); Error::Set(params->callbackInfo.deviceError); thread->SysReturnError(-1); return; } // Bind the device to the descriptor and return it. handle = thread->GetProcess()->_descs.UseReservation(handle, params->callbackInfo.device); thread->SysReturn(handle); return; } void SysMount(Thread* thread) { struct Parameters { struct { const char* USER userDevicePath SYSPARAM; const char* USER userTargetPath SYSPARAM; const char* USER userDriverName SYSPARAM; nat mode SYSPARAM; }; struct { int stage; DevBuffer* storage; DevFileSystem* fsDriver; DevDirectory* directory; FileSystem::SysCallback callbackInfo; }; }; // We store our parameters and data inside the thread object, and we // are passed parameters in a special way, so make sure it works. STATIC_ASSERT(0 * sizeof(size_t) == offsetof(Parameters, userDevicePath)); STATIC_ASSERT(1 * sizeof(size_t) == offsetof(Parameters, userTargetPath)); STATIC_ASSERT(2 * sizeof(size_t) == offsetof(Parameters, userDriverName)); STATIC_ASSERT(3 * sizeof(size_t) == offsetof(Parameters, mode)); STATIC_ASSERT(sizeof(Parameters) <= sizeof(thread->sysParams)); Parameters* params = (Parameters*) thread->sysParams; // Initialize our variables and validate parameters. if ( !thread->sysParamsInited ) { thread->sysParamsInited = true; if ( ValidateUserString(params->userDevicePath) == NULL || ValidateUserString(params->userTargetPath) == NULL || ( params->userDriverName != NULL && ValidateUserString(params->userDriverName) == NULL ) ) { thread->SysReturnError(-1); return; } params->mode &= O_USERSPACEABLE; nat baseMode = params->mode & O_LOWERFLAGS; nat extMode = params->mode & (~O_LOWERFLAGS); // Deny unsupported open modes. if ( (baseMode != O_RDWR && baseMode != O_RDONLY) || (extMode != 0 && extMode != O_EXCLUSIVELY && extMode != O_SYNC) ) { Error::Set(Error::BADINPUT); thread->SysReturnError(-1); return; } params->stage = 0; params->storage = NULL; params->fsDriver = NULL; params->directory = NULL; } MountPoint* mount; Device* device = NULL; switch ( params->stage ) { // Get a handle to the storage device. case 0: params->stage++; mount = Mount::GetMountPoint(¶ms->userDevicePath); if ( mount == NULL ) { Error::Set(Error::BADINPUT); break; } ASSERT(params->mode & O_USERSPACEABLE == 0); if ( !mount->fs->Open(params->userDevicePath, params->mode | O_EXCLUSIVELY, 0, ¶ms->callbackInfo, thread) ) { return; } // Validate the properness of the storage device. case 1: params->stage++; device = params->callbackInfo.device; // Did it exist/could we open it/did we have permission? if ( unlikely(device == NULL) ) { Error::Set(params->callbackInfo.deviceError); break; } // Is it a buffer? if ( unlikely(!device->IsType(Device::BUFFER)) ) { Error::Set(Error::ENOTBLK); break; } params->storage = (DevBuffer*) device; device = NULL; // Get a handle to the destination directory. case 2: params->stage++; mount = Mount::GetMountPoint(¶ms->userTargetPath); if ( mount == NULL ) { Error::Set(Error::BADINPUT); break; } // TODO: Figure out some good mode flags here! // TODO: Make sure the FS driver forces the dest dir to be empty! if ( !mount->fs->Open(params->userTargetPath, O_RDWR | O_DIRECTORY | O_MOUNT, 0, ¶ms->callbackInfo, thread) ) { return; } // Validate the properness of the destination directory. case 2: params->stage++; device = params->callbackInfo.device; // Did it exist/could we open it? if ( unlikely(device == NULL) ) { Error::Set(params->callbackInfo.deviceError); break; } // Is it a directory? if ( unlikely(!device->IsType(Device::DIRECTORY)) ) { Error::Set(Error::ENOTDIR); break; } params->directory = (DevDirectory*) device; device = NULL; // Validate that the user owns the directory. if ( !params->directory->IsOwner(thread->GetProcess()->GetUser()) ) { Error::Set(Error::EPERM); break; } // TODO: Add a run-time assertion that the dir is empty! // Load the proper driver for this file system type. case 3: params->stage++; // TODO: Make an async interface that can scan a device! params->fsDriver = FileSystem::CreateDriver(params->userDriverName); if ( params->fsDriver == NULL ) { Error::Set(Error::ENODEV); break; } // Now bind the file system driver to the device and directory. case 4: params->stage++; MountPoint* newMount = new MountPoint(); newMount->device = params->storage; newMount->fs = params->fsDriver; // TODO: Actually do the mounting! // Check if everything went well. case 4: // TODO: Check if everything went well! thread->SysReturn(0); return; } if ( device != NULL ) { device->close(); } if ( params->storage != NULL ) { params->storage->close(); } if ( params->directory != NULL ) { params->directory->close(); } if ( params->fsDriver != NULL ) { params->fsDriver->close(); } thread->SysReturnError(-1); return; } } namespace Mount { MountPoint* GetMountPoint(const char** path, Thread* thread) { ASSERT(path != NULL); ASSERT(*path != NULL); ASSERT(thread != NULL); if ( unlikely(**path != '/') ) { return NULL; } (*path)++; MountPoint* currentFS = thread->GetProcess->GetRootFS(); ASSERT(currentFS != NULL); // Simply check if path belongs to a child mount point. for ( MountPoint* considering = currentFS->child; considering != NULL; considering = considering->nextSibling ) { ASSERT(considering->IsSane()); size_t consideringPathLen = String::Length(considering->path); if ( String::CompareN(*path, considering->path, consideringPathLen) == 0 ) { // Check if this is the right dir! char lastChar = (*path)[consideringPathLen]; if ( lastChar == '/' ) { (*path) += consideringPathLen + 1; } else if ( lastChar == '\0' ) { (*path) += consideringPathLen + 0; } else { continue; } // Check if the mountpoint isn't ready. if ( considering->waiting != NULL ) { Error::Set(Error::EBUSY); return NULL; } currentFS = considering; considering = currentFS->child; } } ASSERT(currentFS->fs != NULL); return currentFS; } } }