summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2020-01-03 03:53:06 +0100
committerAndreas Kling <awesomekling@gmail.com>2020-01-03 03:57:10 +0100
commit3be1c7b5147066812488942f7b982a63fe40885f (patch)
tree56e5fba0aa0d600f4c33874316fe1fd3f5cd2f5c
parent8cc5fa55988b84a57bc2afabda8a5f79dbbdf06f (diff)
downloadserenity-3be1c7b5147066812488942f7b982a63fe40885f.zip
Kernel: Fix awkward bug where "touch /foo/bar/baz" could create "/baz"
To accomodate file creation, path resolution optionally returns the last valid parent directory seen while traversing the path. Clients will then interpret "ENOENT, but I have a parent for you" as meaning that the file doesn't exist, but its immediate parent directory does. The client then goes ahead and creates a new file. In the case of "/foo/bar/baz" where there is no "/foo", it would fail with ENOENT and "/" as the last seen parent directory, causing e.g the open() syscall to create "/baz". Covered by test_io.
-rw-r--r--Kernel/FileSystem/VirtualFileSystem.cpp13
1 files changed, 12 insertions, 1 deletions
diff --git a/Kernel/FileSystem/VirtualFileSystem.cpp b/Kernel/FileSystem/VirtualFileSystem.cpp
index 64317dec64..5539032e69 100644
--- a/Kernel/FileSystem/VirtualFileSystem.cpp
+++ b/Kernel/FileSystem/VirtualFileSystem.cpp
@@ -692,8 +692,19 @@ KResultOr<NonnullRefPtr<Custody>> VFS::resolve_path(StringView path, Custody& ba
auto& current_parent = custody_chain.last();
crumb_id = crumb_inode->lookup(part);
- if (!crumb_id.is_valid())
+ if (!crumb_id.is_valid()) {
+ if (i != parts.size() - 1) {
+ // We didn't find the filename we were looking for,
+ // and we didn't even reach the last path part.
+ // (ENOENT with non-null parent_custody) signals to caller that
+ // we found the immediate parent of the file, but the file itself
+ // does not exist yet.
+ // Since this is not the immediate parent, clear parent_custody.
+ if (parent_custody)
+ *parent_custody = nullptr;
+ }
return KResult(-ENOENT);
+ }
if (auto mount = find_mount_for_host(crumb_id))
crumb_id = mount->guest();
if (inode_was_root_at_head_of_loop && crumb_id.is_root_inode() && !is_vfs_root(crumb_id) && part == "..") {