mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-18 21:27:56 +00:00
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:
parent
cb74956d06
commit
77b0e10e88
@ -0,0 +1 @@
|
||||
Test for tmp-file access after unlink
|
@ -0,0 +1,4 @@
|
||||
_/src/test-libc_deferred_unlink
|
||||
_/src/libc
|
||||
_/src/vfs
|
||||
_/src/posix
|
@ -0,0 +1 @@
|
||||
2023-11-29 6169da302568a61b2d2f8b01b657879e9e51acb2
|
24
repos/libports/recipes/pkg/test-libc_deferred_unlink/runtime
Normal file
24
repos/libports/recipes/pkg/test-libc_deferred_unlink/runtime
Normal 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>
|
@ -0,0 +1,3 @@
|
||||
SRC_DIR = src/test/libc_deferred_unlink
|
||||
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
@ -0,0 +1 @@
|
||||
2023-10-03 fdb0e5bff92af9d951b2bd293fd32b229b8e65b8
|
@ -0,0 +1,2 @@
|
||||
posix
|
||||
libc
|
77
repos/libports/src/test/libc_deferred_unlink/main.c
Normal file
77
repos/libports/src/test/libc_deferred_unlink/main.c
Normal 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;
|
||||
}
|
3
repos/libports/src/test/libc_deferred_unlink/target.mk
Normal file
3
repos/libports/src/test/libc_deferred_unlink/target.mk
Normal file
@ -0,0 +1,3 @@
|
||||
TARGET = test-libc_deferred_unlink
|
||||
SRC_C = main.c
|
||||
LIBS = posix
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user