libc: fix nested monitor call in 'symlink'

The symlink implementation wrongly constructed a 'Sync' object within
the context of a monitor call. The 'Sync' constructor indirectly
depended on libc I/O for obtaining the current time, ultimately
resulting in a nested attempt of a monitor call. This could be
reproduced via the base.run script:

  $ cd /home
  $ ln -s a b

The 'ln' command resulted in the following log message:

  [init -> /bin/bash -> 7] Error: deadlock ahead, mutex=0x10ff8c70, return ip=0x500583a7

The patch fixes the problem by splitting the single monitor call into
two monitor calls and moving the construction of the 'Sync' object
in-between both monitor calls, thereby executing the constructor at the
libc application level.

Fixes #4219
This commit is contained in:
Norman Feske 2021-07-12 14:34:16 +02:00 committed by Christian Helmuth
parent f3908b8283
commit 5138aeba80

View File

@ -1675,76 +1675,88 @@ int Libc::Vfs_plugin::fsync(File_descriptor *fd)
int Libc::Vfs_plugin::symlink(const char *target_path, const char *link_path)
{
enum class Stage { OPEN, WRITE, SYNC };
Stage stage { Stage::OPEN };
Vfs::Vfs_handle *handle { nullptr };
Constructible<Sync> sync;
Vfs::file_size const count { ::strlen(target_path) + 1 };
Vfs::file_size out_count { 0 };
{
bool succeeded { false };
int result_errno { 0 };
monitor().monitor([&] {
bool succeeded { false };
int result_errno { 0 };
monitor().monitor([&] {
typedef Vfs::Directory_service::Openlink_result Openlink_result;
switch (stage) {
case Stage::OPEN:
{
typedef Vfs::Directory_service::Openlink_result Openlink_result;
Openlink_result openlink_result =
_root_fs.openlink(link_path, true, &handle, _alloc);
Openlink_result openlink_result =
_root_fs.openlink(link_path, true, &handle, _alloc);
switch (openlink_result) {
case Openlink_result::OPENLINK_ERR_LOOKUP_FAILED:
result_errno = ENOENT; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_NAME_TOO_LONG:
result_errno = ENAMETOOLONG; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_NODE_ALREADY_EXISTS:
result_errno = EEXIST; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_NO_SPACE:
result_errno = ENOSPC; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_OUT_OF_RAM:
result_errno = ENOSPC; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_OUT_OF_CAPS:
result_errno = ENOSPC; return Fn::COMPLETE;
case Vfs::Directory_service::OPENLINK_ERR_PERMISSION_DENIED:
result_errno = EPERM; return Fn::COMPLETE;
case Openlink_result::OPENLINK_OK:
break;
}
switch (openlink_result) {
case Openlink_result::OPENLINK_ERR_LOOKUP_FAILED:
result_errno = ENOENT; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_NAME_TOO_LONG:
result_errno = ENAMETOOLONG; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_NODE_ALREADY_EXISTS:
result_errno = EEXIST; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_NO_SPACE:
result_errno = ENOSPC; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_OUT_OF_RAM:
result_errno = ENOSPC; return Fn::COMPLETE;
case Openlink_result::OPENLINK_ERR_OUT_OF_CAPS:
result_errno = ENOSPC; return Fn::COMPLETE;
case Vfs::Directory_service::OPENLINK_ERR_PERMISSION_DENIED:
result_errno = EPERM; return Fn::COMPLETE;
case Openlink_result::OPENLINK_OK:
break;
}
handle->handler(&_response_handler);
sync.construct(*handle, _update_mtime, _current_real_time);
} stage = Stage::WRITE; [[fallthrough]];
case Stage::WRITE:
{
try {
handle->fs().write(handle, target_path, count, out_count);
} catch (Vfs::File_io_service::Insufficient_buffer) {
return Fn::INCOMPLETE;
}
} stage = Stage::SYNC; [[fallthrough]];
case Stage::SYNC:
{
if (!sync->complete())
return Fn::INCOMPLETE;
handle->close();
} break;
}
if (out_count != count)
result_errno = ENAMETOOLONG;
else
handle->handler(&_response_handler);
succeeded = true;
return Fn::COMPLETE;
});
return Fn::COMPLETE;
});
if (!succeeded)
if (!succeeded)
return Errno(result_errno);
}
/* must be done outside the monitor because constructor needs libc I/O */
sync.construct(*handle, _update_mtime, _current_real_time);
{
bool succeeded { false };
int result_errno { 0 };
enum class Stage { WRITE, SYNC } stage = Stage::WRITE;
monitor().monitor([&] {
switch (stage) {
case Stage::WRITE:
{
try {
handle->fs().write(handle, target_path, count, out_count);
} catch (Vfs::File_io_service::Insufficient_buffer) {
return Fn::INCOMPLETE;
}
} stage = Stage::SYNC; [[fallthrough]];
case Stage::SYNC:
{
if (!sync->complete())
return Fn::INCOMPLETE;
handle->close();
} break;
}
if (out_count != count)
result_errno = ENAMETOOLONG;
else
succeeded = true;
return Fn::COMPLETE;
});
if (!succeeded)
return Errno(result_errno);
}
return 0;
}