Skip to content

Commit

Permalink
Add APIs to get group details and to change ownership of file.
Browse files Browse the repository at this point in the history
There are a few calls in scylladb that bypass Seastar APIs when making libc
and syscall (getgrnam, chown), see issue scylladb/scylladb#17443.

This commit implements those APIs, which can be used to address the
issue scylladb/scylladb#17443
  • Loading branch information
muthu90tech committed Sep 17, 2024
1 parent e42e382 commit 6daa5f3
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 1 deletion.
8 changes: 8 additions & 0 deletions include/seastar/core/file.hh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ struct directory_entry {
std::optional<directory_entry_type> type;
};

/// Group details from the system group database
struct group_details {
sstring group_name;
sstring group_passwd;
__gid_t group_id;
std::vector<sstring> group_members;
};

/// Filesystem object stat information
struct stat_data {
uint64_t device_id; // ID of device containing file
Expand Down
2 changes: 2 additions & 0 deletions include/seastar/core/reactor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ public:
future<> touch_directory(std::string_view name, file_permissions permissions = file_permissions::default_dir_permissions) noexcept;
future<std::optional<directory_entry_type>> file_type(std::string_view name, follow_symlink = follow_symlink::yes) noexcept;
future<stat_data> file_stat(std::string_view pathname, follow_symlink) noexcept;
future<> chown(std::string_view filepath, uid_t owner, gid_t group);
future<std::optional<struct group_details>> getgrnam(std::string_view name);
future<uint64_t> file_size(std::string_view pathname) noexcept;
future<bool> file_accessible(std::string_view pathname, access_flags flags) noexcept;
future<bool> file_exists(std::string_view pathname) noexcept {
Expand Down
15 changes: 15 additions & 0 deletions include/seastar/core/seastar.hh
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,21 @@ using follow_symlink = bool_class<follow_symlink_tag>;
/// with follow_symlink::yes, or for the link itself, with follow_symlink::no.
future<stat_data> file_stat(std::string_view name, follow_symlink fs = follow_symlink::yes) noexcept;

/// Wrapper around getgrnam_r.
/// If the provided group name does not exist in the group database, this call will return an empty optional.
/// If the provided group name exists in the group database, the optional returned will contain the struct group_details information.
/// When an unexpected error is thrown by the getgrnam_r libc call, this function throws std::system_error with std::error_code.
/// \param groupname name of the group
///
/// \return optional struct group_details of the group identified by name. struct group_details has details of the group from the group database.
future<std::optional<struct group_details>> getgrnam(std::string_view name);

/// Change the owner and group of file. This is a wrapper around chown syscall.
/// The function throws std::system_error, when the chown syscall fails.
/// \param filepath
/// \param owner
/// \param group
future<> chown(std::string_view filepath, uid_t owner, gid_t group);
/// Return the size of a file.
///
/// \param name name of the file to return the size
Expand Down
43 changes: 43 additions & 0 deletions src/core/reactor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ module;
#include <cassert>
#include <chrono>
#include <cmath>
#include <coroutine>
#include <exception>
#include <filesystem>
#include <fstream>
#include <regex>
#include <thread>

#include <grp.h>
#include <spawn.h>
#include <sys/syscall.h>
#include <sys/vfs.h>
Expand Down Expand Up @@ -2200,6 +2202,47 @@ void reactor::kill(pid_t pid, int sig) {
ret.throw_if_error();
}

future<std::optional<struct group_details>> reactor::getgrnam(std::string_view name) {
syscall_result_extra<std::optional<struct group_details>> sr = co_await _thread_pool->submit<syscall_result_extra<std::optional<struct group_details>>>(
[name = sstring(name)] {
struct group grp;
struct group *result;
memset(&grp, 0, sizeof(struct group));
char buf[1024];
errno = 0;
int ret = ::getgrnam_r(name.c_str(), &grp, buf, sizeof(buf), &result);
if (!result) {
return wrap_syscall(ret, std::optional<struct group_details>(std::nullopt));
}

group_details gd;
gd.group_name = sstring(grp.gr_name);
gd.group_passwd = sstring(grp.gr_passwd);
gd.group_id = grp.gr_gid;
for (char **members = grp.gr_mem; *members != nullptr; ++members) {
gd.group_members.emplace_back(sstring(*members));
}
return wrap_syscall(ret, std::optional<struct group_details>(gd));
});

if (sr.result != 0) {
throw std::system_error(sr.ec());
}

co_return sr.extra;
}

future<> reactor::chown(std::string_view filepath, uid_t owner, gid_t group) {
syscall_result<int> sr = co_await _thread_pool->submit<syscall_result<int>>(
[filepath = sstring(filepath), owner, group] {
int ret = ::chown(filepath.c_str(), owner, group);
return wrap_syscall(ret);
});

sr.throw_if_error();
co_return;
}

future<stat_data>
reactor::file_stat(std::string_view pathname, follow_symlink follow) noexcept {
// Allocating memory for a sstring can throw, hence the futurize_invoke
Expand Down
2 changes: 1 addition & 1 deletion src/core/syscall_result.hh
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct syscall_result {
throw_fs_exception(reason, fs::path(path1), fs::path(path2));
}
}
protected:

std::error_code ec() const {
return std::error_code(error, std::system_category());
}
Expand Down
8 changes: 8 additions & 0 deletions src/util/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ future<stat_data> file_stat(std::string_view name, follow_symlink follow) noexce
return engine().file_stat(name, follow);
}

future<std::optional<struct group_details>> getgrnam(std::string_view name) {
return engine().getgrnam(name);
}

future<> chown(std::string_view filepath, uid_t owner, gid_t group) {
return engine().chown(filepath, owner, group);
}

future<uint64_t> file_size(std::string_view name) noexcept {
return engine().file_size(name);
}
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ seastar_add_test (network_interface
seastar_add_test (json_formatter
SOURCES json_formatter_test.cc)

seastar_add_test (libc_wrapper
SOURCES libc_wrapper_test.cc)

seastar_add_test (locking
SOURCES locking_test.cc)

Expand Down
44 changes: 44 additions & 0 deletions tests/unit/libc_wrapper_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/*
* Copyright (C) 2024 ScyllaDB.
*/
#include <coroutine>
#include <iostream>

#include <seastar/core/coroutine.hh>
#include <seastar/core/reactor.hh>
#include <seastar/core/seastar.hh>

#include <seastar/testing/test_case.hh>

using namespace seastar;

SEASTAR_TEST_CASE(getgrnam_group_name_does_not_exist_test) {
// A better approach would be to use a test setup and teardown to create a fake group
// with members in it and in the teardown delete the fake group.
std::optional<struct group_details> grp = co_await getgrnam("roo");
BOOST_REQUIRE(!grp.has_value());
}

SEASTAR_TEST_CASE(getgrnam_group_name_exists_test) {
std::optional<struct group_details> grp = co_await getgrnam("root");
BOOST_REQUIRE(grp.has_value());
BOOST_REQUIRE_EQUAL(grp.value().group_name.c_str(), "root");
}

0 comments on commit 6daa5f3

Please sign in to comment.