From 7fb2963ae41010e397838f095b8b7b71332d36ff Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Thu, 28 Oct 2004 22:31:43 +0000 Subject: [PATCH] * Added vfs_normalize_path() service call. * Added resolve_mount_point_to_volume_root() which does what resolve_volume_root_to_mount_point() did. IOW the latter one didn't do what it advertised. * Fixed dir_vnode_to_path(). Basically broken due to the broken resolve_volume_root_to_mount_point(), but also compared potentially unrelated vnode IDs (belonging to different volumes). * Fixed get_dir_path_and_leaf(). It didn't deal correctly with paths ending in '/' (including the root dir). * fs_mount() does now accept a NULL fsName, getting the FS name from the DDM in this case. * fs_mount() now also supports mounting file images; it lets the DDM create a file device for them. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@9629 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/kernel/vfs.h | 4 + src/kernel/core/fs/vfs.cpp | 234 +++++++++++++++++++++++++++++++---- 2 files changed, 213 insertions(+), 25 deletions(-) diff --git a/headers/private/kernel/vfs.h b/headers/private/kernel/vfs.h index 8d34ace786..bbf28597bc 100755 --- a/headers/private/kernel/vfs.h +++ b/headers/private/kernel/vfs.h @@ -81,6 +81,10 @@ status_t vfs_get_fs_node_from_path(mount_id mountID, const char *path, bool kern /* special module convenience call */ status_t vfs_get_module_path(const char *basePath, const char *moduleName, char *pathBuffer, size_t bufferSize); +/* service call for whoever needs a normalized path */ +status_t vfs_normalize_path(const char *path, char *buffer, size_t bufferSize, + bool kernel); + /* calls the syscall dispatcher should use for user file I/O */ status_t _user_mount(const char *path, const char *device, const char *fs_name, uint32 flags, void *args); diff --git a/src/kernel/core/fs/vfs.cpp b/src/kernel/core/fs/vfs.cpp index 690cb5e366..0ce70c42bc 100755 --- a/src/kernel/core/fs/vfs.cpp +++ b/src/kernel/core/fs/vfs.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -96,6 +98,7 @@ struct fs_mount { KPartition *partition; struct list vnodes; bool unmounting; + bool owns_file_device; }; // RecursiveLockLocking @@ -751,6 +754,36 @@ put_vnode(struct vnode *vnode) } +/** \brief Resolves a mount point vnode to the volume root vnode it is covered + by. + + Given an arbitrary vnode, the function checks, whether the node is covered + by the root of a volume. If it is the function obtains a reference to the + volume root node and returns it. + + \param vnode The vnode in question. + \return The volume root vnode the vnode cover is covered by, if it is + indeed a mount point, or \c NULL otherwise. +*/ +static +struct vnode* +resolve_mount_point_to_volume_root(struct vnode *vnode) +{ + if (!vnode) + return NULL; + + struct vnode *volumeRoot = NULL; + + recursive_lock_lock(&sMountOpLock); + if (vnode->covered_by) { + volumeRoot = vnode->covered_by; + inc_vnode_ref_count(volumeRoot); + } + recursive_lock_unlock(&sMountOpLock); + + return volumeRoot; +} + /** \brief Resolves a volume root vnode to the underlying mount point vnode. Given an arbitrary vnode, the function checks, whether the node is the @@ -771,8 +804,9 @@ resolve_volume_root_to_mount_point(struct vnode *vnode) struct vnode *mountPoint = NULL; recursive_lock_lock(&sMountOpLock); - if (vnode->covered_by) { - mountPoint = vnode->covered_by; + struct fs_mount *mount = vnode->mount; + if (vnode == mount->root_vnode && mount->covers_vnode) { + mountPoint = mount->covers_vnode; inc_vnode_ref_count(mountPoint); } recursive_lock_unlock(&sMountOpLock); @@ -812,14 +846,19 @@ get_dir_path_and_leaf(char *path, char *filename) return B_NAME_TOO_LONG; strcpy(path, "."); } else { - // replace the filename portion of the path with a '.' - if (strlcpy(filename, ++p, B_FILE_NAME_LENGTH) >= B_FILE_NAME_LENGTH) - return B_NAME_TOO_LONG; - - if (p[0] != '\0'){ - p[0] = '.'; - p[1] = '\0'; + p++; + if (*p == '\0') { + // special case: the path ends in '/' + strcpy(filename, "."); + } else { + // normal leaf: replace the leaf portion of the path with a '.' + if (strlcpy(filename, p, B_FILE_NAME_LENGTH) + >= B_FILE_NAME_LENGTH) { + return B_NAME_TOO_LONG; + } } + p[0] = '.'; + p[1] = '\0'; } return B_OK; } @@ -983,7 +1022,7 @@ vnode_path_to_vnode(struct vnode *vnode, char *path, bool traverseLeafLink, vnode = nextVnode; // see if we hit a mount point - struct vnode *mountPoint = resolve_volume_root_to_mount_point(vnode); + struct vnode *mountPoint = resolve_mount_point_to_volume_root(vnode); if (mountPoint) { put_vnode(vnode); vnode = mountPoint; @@ -1149,7 +1188,7 @@ get_vnode_name(struct vnode *vnode, struct vnode *parent, static status_t dir_vnode_to_path(struct vnode *vnode, char *buffer, size_t bufferSize) { - FUNCTION(("dir_vnode_to_path(%p, %p, %lu\n)", vnode, buffer, bufferSize)); + FUNCTION(("dir_vnode_to_path(%p, %p, %lu)\n", vnode, buffer, bufferSize)); /* this implementation is currently bound to SYS_MAX_PATH_LEN */ char path[SYS_MAX_PATH_LEN]; @@ -1202,8 +1241,11 @@ dir_vnode_to_path(struct vnode *vnode, char *buffer, size_t bufferSize) if (mountPoint) { put_vnode(parentVnode); parentVnode = mountPoint; + parentID = parentVnode->id; } + bool hitRoot = (parentVnode == vnode); + // Does the file system support getting the name of a vnode? // If so, get it here... if (status == B_OK && FS_CALL(vnode, get_vnode_name)) @@ -1229,7 +1271,7 @@ dir_vnode_to_path(struct vnode *vnode, char *buffer, size_t bufferSize) goto out; } - if (parentID == id) { + if (hitRoot) { // we have reached "/", which means we have constructed the full // path break; @@ -1737,6 +1779,85 @@ err: } +/** \brief Normalizes a given path. + + The path must refer to an existing or non-existing entry in an existing + directory, that is chopping off the leaf component the remaining path must + refer to an existing directory. + + The returned will be canonical in that it will be absolute, will not + contain any "." or ".." components or duplicate occurrences of '/'s, + and none of the directory components will by symbolic links. + + Any two paths referring to the same entry, will result in the same + normalized path (well, that is pretty much the definition of `normalized', + isn't it :-). + + \param path The path to be normalized. + \param buffer The buffer into which the normalized path will be written. + \param bufferSize The size of \a buffer. + \param kernel \c true, if the IO context of the kernel shall be used, + otherwise that of the team this thread belongs to. Only relevant, + if the path is relative (to get the CWD). + \return \c B_OK if everything went fine, another error code otherwise. +*/ +status_t +vfs_normalize_path(const char *path, char *buffer, size_t bufferSize, + bool kernel) +{ + if (!path || !buffer || bufferSize < 1) + return B_BAD_VALUE; +PRINT(("vfs_normalize_path(`%s')\n", path)); + + // copy the supplied path to the stack, so it can be modified + char mutablePath[B_PATH_NAME_LENGTH + 1]; + if (strlcpy(mutablePath, path, B_PATH_NAME_LENGTH) >= B_PATH_NAME_LENGTH) + return B_NAME_TOO_LONG; + + // get the dir vnode and the leaf name + struct vnode *dirNode; + char leaf[B_FILE_NAME_LENGTH]; + status_t error = path_to_dir_vnode(mutablePath, &dirNode, leaf, kernel); + if (error != B_OK) +{ +PRINT(("vfs_normalize_path(): failed to get dir vnode: %s\n", strerror(error))); + return error; +} + // if the leaf is "." or "..", we directly get the correct directory + // vnode and ignore the leaf later + bool isDir = (strcmp(leaf, ".") == 0 || strcmp(leaf, "..") == 0); + if (isDir) + error = vnode_path_to_vnode(dirNode, leaf, false, 0, &dirNode, NULL); + if (error != B_OK) +{ +PRINT(("vfs_normalize_path(): failed to get dir vnode for \".\" or \"..\": %s\n", strerror(error))); + return error; +} + + // get the directory path + error = dir_vnode_to_path(dirNode, buffer, bufferSize); + put_vnode(dirNode); + if (error < B_OK) +{ +PRINT(("vfs_normalize_path(): failed to get dir path: %s\n", strerror(error))); + return error; +} + + // append the leaf name + if (!isDir) { + // insert a directory separator only if this is not the file system root + if ((strcmp(buffer, "/") != 0 + && strlcat(buffer, "/", bufferSize) >= bufferSize) + || strlcat(buffer, leaf, bufferSize) >= bufferSize) { + return B_NAME_TOO_LONG; + } + } + +PRINT(("vfs_normalize_path() -> `%s'\n", buffer)); + return B_OK; +} + + int vfs_put_vnode_ptr(void *_vnode) { @@ -3650,25 +3771,61 @@ fs_mount(char *path, const char *device, const char *fsName, uint32 flags, // The path is always safe, we just have to make sure that fsName is // almost valid - we can't make any assumptions about args, though. -// TODO: fsName == NULL is OK, if device refers to a partition. Then the DDM -// will know, what FS to use. - if (fsName == NULL || fsName[0] == '\0') + // A NULL fsName is OK, if a device was given and the FS is not virtual. + // We'll get it from the DDM later. + if (fsName == NULL) { + if (!device || flags & B_MOUNT_VIRTUAL_DEVICE) + return B_BAD_VALUE; + } else if (fsName[0] == '\0') return B_BAD_VALUE; RecursiveLocker mountOpLocker(sMountOpLock); + // Helper to delete a newly created file device on failure. + // Not exactly beautiful, but helps to keep the code below cleaner. + struct FileDeviceDeleter { + FileDeviceDeleter() : id(-1) {} + ~FileDeviceDeleter() + { + KDiskDeviceManager::Default()->DeleteFileDevice(id); + } + + partition_id id; + } fileDeviceDeleter; + // If the file system is not a "virtual" one, the device argument should // point to a real file/device (if given at all). // get the partition KDiskDeviceManager* ddm = KDiskDeviceManager::Default(); KPartition* partition = NULL; + bool newlyCreatedFileDevice = false; if (!(flags & B_MOUNT_VIRTUAL_DEVICE) && device) { + // normalize the device path + KPath normalizedDevice; + err = normalizedDevice.SetTo(device, true); + if (err != B_OK) + return err; + // get a corresponding partition from the DDM - partition = ddm->RegisterPartition(device, true); - // TODO: Normalize the path! - // TODO: Support for mounting images! + partition = ddm->RegisterPartition(normalizedDevice.Path(), true); + if (!partition) { - PRINT(("fs_mount(): Partition `%s' not found.\n", device)); + // Partition not found: This either means, the user supplied + // an invalid path, or the path refers to an image file. We try + // to let the DDM create a file device for the path. + partition_id deviceID = ddm->CreateFileDevice( + normalizedDevice.Path(), &newlyCreatedFileDevice); + if (deviceID >= 0) { + partition = ddm->RegisterPartition(deviceID, true); + if (newlyCreatedFileDevice) + fileDeviceDeleter.id = deviceID; +// TODO: We must wait here, until the partition scan job is done. + } + } + + if (!partition) { + PRINT(("fs_mount(): Partition `%s' not found.\n", + normalizedDevice.Path())); return B_ENTRY_NOT_FOUND; } } @@ -3688,12 +3845,33 @@ fs_mount(char *path, const char *device, const char *fsName, uint32 flags, } DeviceWriteLocker writeLocker(diskDevice, true); - // make sure, that the partition is not busy if (partition) { + // make sure, that the partition is not busy if (partition->IsBusy() || partition->IsDescendantBusy()) { PRINT(("fs_mount(): Partition is busy.\n")); return B_BUSY; } + + // if no FS name had been supplied, we get it from the partition + if (!fsName) { + KDiskSystem *diskSystem = partition->DiskSystem(); + if (!diskSystem) { + PRINT(("fs_mount(): No FS name was given, and the DDM didn't " + "recognize it.\n")); + return B_BAD_VALUE; + } + + if (!diskSystem->IsFileSystem()) { + PRINT(("fs_mount(): No FS name was given, and the DDM found a " + "partitioning system.\n")); + return B_BAD_VALUE; + } + + // The disk system name will not change, and the KDiskSystem + // object will not go away while the disk device is locked (and + // the partition has a reference to it), so this is safe. + fsName = diskSystem->Name(); + } } mount = (struct fs_mount *)malloc(sizeof(struct fs_mount)); @@ -3722,8 +3900,9 @@ fs_mount(char *path, const char *device, const char *fsName, uint32 flags, goto err4; mount->id = sNextMountID++; - mount->unmounting = false; mount->partition = NULL; + mount->unmounting = false; + mount->owns_file_device = false; mutex_lock(&sMountMutex); @@ -3802,6 +3981,8 @@ fs_mount(char *path, const char *device, const char *fsName, uint32 flags, // keep a partition reference as long as the partition is mounted partitionRegistrar.Detach(); mount->partition = partition; + mount->owns_file_device = newlyCreatedFileDevice; + fileDeviceDeleter.id = -1; } return B_OK; @@ -3933,14 +4114,17 @@ fs_unmount(char *path, bool kernel) // release the file system put_file_system(mount->fs); + // dereference the partition + if (partition) { + if (mount->owns_file_device) + KDiskDeviceManager::Default()->DeleteFileDevice(partition->ID()); + partition->Unregister(); + } + free(mount->device_name); free(mount->fs_name); free(mount); - // dereference the partition - if (partition) - partition->Unregister(); - return 0; }