vfs/ram_file_system: deferred unlink

This patch changes the unlink operation of the ram fs to defer the
destruction of a file until it is no longer referenced by any VFS handle.
When unlinked, the file no longer appears in the directory. But it can
still be opened and accessed.

With this change, a parent process of a Unix-like subsystem becomes able
to pass the content of an unlinked file to a forked child process. This
mechanism is required when using the 'exec' command in Tcl scripts.

Another use case is the 'tmpfile()' function.

Fixes #3577
This commit is contained in:
Norman Feske 2023-12-11 11:10:41 +01:00
parent cb74956d06
commit 77b0e10e88
10 changed files with 168 additions and 20 deletions

View File

@ -0,0 +1 @@
Test for tmp-file access after unlink

View File

@ -0,0 +1,4 @@
_/src/test-libc_deferred_unlink
_/src/libc
_/src/vfs
_/src/posix

View File

@ -0,0 +1 @@
2023-11-29 6169da302568a61b2d2f8b01b657879e9e51acb2

View File

@ -0,0 +1,24 @@
<runtime ram="4M" caps="200" binary="test-libc_deferred_unlink">
<fail after_seconds="30"/>
<succeed>child * exited with exit value 0</succeed>
<fail>Error: </fail>
<content>
<rom label="ld.lib.so"/>
<rom label="libc.lib.so"/>
<rom label="libm.lib.so"/>
<rom label="posix.lib.so"/>
<rom label="vfs.lib.so"/>
<rom label="test-libc_deferred_unlink"/>
</content>
<config>
<vfs>
<dir name="dev"> <log/> </dir>
<dir name="tmp"> <ram/> </dir>
</vfs>
<libc stdout="/dev/log" stderr="/dev/log"/>
</config>
</runtime>

View File

@ -0,0 +1,3 @@
SRC_DIR = src/test/libc_deferred_unlink
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -0,0 +1 @@
2023-10-03 fdb0e5bff92af9d951b2bd293fd32b229b8e65b8

View File

@ -0,0 +1,2 @@
posix
libc

View File

@ -0,0 +1,77 @@
/*
* \brief Access tmp file after unlink
* \author Norman Feske
* \date 2023-12-08
*/
/*
* Copyright (C) 2023 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <assert.h>
static int dir_entry_exists()
{
DIR *dir = opendir("/tmp");
struct dirent *dirent = NULL;
for (;;) {
dirent = readdir(dir);
if (!dirent || (strcmp(dirent->d_name, "test") == 0))
break;
}
closedir(dir);
return (dirent != NULL);
}
int main(int argc, char **argv)
{
char const * const path = "/tmp/test";
char const * const content = "content of tmp file";
int const write_fd = open(path, O_RDWR|O_CREAT);
assert(write_fd >= 0);
assert(dir_entry_exists());
(void)unlink(path);
assert(!dir_entry_exists());
/* the open 'write_fd' keeps the vfs <ram> from unlinking the file now */
size_t const num_written_bytes = write(write_fd, content, strlen(content));
/* open same file for reading before closing the 'write_fd' */
int const read_fd = open(path, O_RDONLY);
assert(read_fd >= 0);
close(write_fd); /* 'read_fd' still references the file */
char buf[100];
size_t const num_read_bytes = read(read_fd, buf, sizeof(buf));
assert(num_read_bytes == num_written_bytes);
close(read_fd);
/* since no fd refers to the file any longer, it is phyiscally removed now */
int const expect_no_fd = open(path, O_RDONLY);
assert(expect_no_fd == -1);
{
FILE *tmp = tmpfile();
assert(tmp != NULL);
size_t fwrite_len = fwrite("123", 1, 3, tmp);
assert(fwrite_len == 3);
fclose(tmp);
}
return 0;
}

View File

@ -0,0 +1,3 @@
TARGET = test-libc_deferred_unlink
SRC_C = main.c
LIBS = posix

View File

@ -70,11 +70,17 @@ struct Vfs_ram::Io_handle final : public Vfs_handle,
/* Track if this handle has modified its node */
bool modifying = false;
using Path = Genode::String<MAX_PATH_LEN>;
Path const path; /* needed for deferred unlink-on-close to look up the parent */
Io_handle(Vfs::File_system &fs,
Allocator &alloc,
int status_flags,
Vfs_ram::Node &node)
: Vfs_handle(fs, fs, alloc, status_flags), node(node)
Vfs_ram::Node &node,
Path const &path)
:
Vfs_handle(fs, fs, alloc, status_flags), node(node), path(path)
{ }
};
@ -122,6 +128,8 @@ class Vfs_ram::Node : private Genode::Avl_node<Node>
Vfs::Timestamp _modification_time { Vfs::Timestamp::INVALID };
bool _marked_as_unlinked = false;
public:
unsigned long inode;
@ -155,8 +163,9 @@ class Vfs_ram::Node : private Genode::Avl_node<Node>
h->watch_response();
}
void unlink() { inode = 0; }
bool unlinked() const { return inode == 0; }
void mark_as_unlinked() { _marked_as_unlinked = true; }
bool marked_as_unlinked() const { return _marked_as_unlinked; }
bool update_modification_timestamp(Vfs::Timestamp time)
{
@ -211,8 +220,10 @@ class Vfs_ram::Node : private Genode::Avl_node<Node>
*/
Node *index(size_t &i)
{
if (i-- == 0)
return this;
if (!_marked_as_unlinked) {
if (i-- == 0)
return this;
}
Node *n;
@ -537,7 +548,7 @@ class Vfs::Ram_file_system : public Vfs::File_system
if (File * const file = dynamic_cast<File*>(node)) {
if (file->opened()) {
file->unlink();
file->mark_as_unlinked();
return;
}
} else if (Directory *dir = dynamic_cast<Directory*>(node)) {
@ -547,6 +558,18 @@ class Vfs::Ram_file_system : public Vfs::File_system
destroy(_env.alloc(), node);
}
void _try_complete_unlink(Vfs_ram::Directory *parent_ptr, Vfs_ram::Node &node)
{
if (node.marked_as_unlinked() && !node.opened()) {
if (parent_ptr) {
parent_ptr->release(&node);
parent_ptr->notify();
}
node.notify();
remove(&node);
}
}
public:
Ram_file_system(Vfs::Env &env, Genode::Xml_node) : _env(env) { }
@ -616,7 +639,10 @@ class Vfs::Ram_file_system : public Vfs::File_system
}
try {
*handle = new (alloc) Io_handle(*this, alloc, mode, *file);
Io_handle * const io_handle_ptr = new (alloc)
Io_handle(*this, alloc, mode, *file, path);
file->open(*io_handle_ptr);
*handle = io_handle_ptr;
return OPEN_OK;
} catch (Genode::Out_of_ram) {
if (create) {
@ -671,8 +697,10 @@ class Vfs::Ram_file_system : public Vfs::File_system
}
try {
*handle = new (alloc) Io_handle(
*this, alloc, Io_handle::STATUS_RDONLY, *dir);
Io_handle * const io_handle_ptr = new (alloc)
Io_handle(*this, alloc, Io_handle::STATUS_RDONLY, *dir, path);
dir->open(*io_handle_ptr);
*handle = io_handle_ptr;
return OPENDIR_OK;
} catch (Genode::Out_of_ram) {
if (create) {
@ -727,8 +755,10 @@ class Vfs::Ram_file_system : public Vfs::File_system
}
try {
*handle = new (alloc)
Io_handle(*this, alloc, Io_handle::STATUS_RDWR, *link);
Io_handle * const io_handle_ptr = new (alloc)
Io_handle(*this, alloc, Io_handle::STATUS_RDWR, *link, path);
link->open(*io_handle_ptr);
*handle = io_handle_ptr;
return OPENLINK_OK;
} catch (Genode::Out_of_ram) {
if (create) {
@ -753,14 +783,15 @@ class Vfs::Ram_file_system : public Vfs::File_system
Vfs_ram::Node &node = ram_handle->node;
bool node_modified = ram_handle->modifying;
Vfs_ram::Directory * const parent_ptr = lookup_parent(ram_handle->path.string());
node.close(*ram_handle);
destroy(vfs_handle->alloc(), ram_handle);
if (node.unlinked() && !node.opened()) {
destroy(_env.alloc(), &node);
} else if (node_modified) {
if (node_modified)
node.notify();
}
_try_complete_unlink(parent_ptr, node);
}
Stat_result stat(char const *path, Stat &stat) override
@ -855,10 +886,11 @@ class Vfs::Ram_file_system : public Vfs::File_system
if (!node)
return UNLINK_ERR_NO_ENTRY;
parent->release(node);
node->notify();
parent->notify();
remove(node);
/* defer unlink of a node that is still referenced by an Io_handle */
node->mark_as_unlinked();
_try_complete_unlink(parent, *node);
return UNLINK_OK;
}