rocksdb/env/env_chroot.cc
Yu Zhang ba8fa0f546 internal_repo_rocksdb (4372117296613874540) (#12117)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/12117

Reviewed By: ajkr

Differential Revision: D51745846

Pulled By: jowlyzhang

fbshipit-source-id: 51c806a484b3b43d174b06d2cfe9499191d09914
2023-12-04 11:17:32 -08:00

150 lines
5 KiB
C++

// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#if !defined(OS_WIN)
#include "env/env_chroot.h"
#include <unistd.h> // geteuid
#include <cerrno> // errno
#include <cstdlib> // realpath, free
#include "env/composite_env_wrapper.h"
#include "env/fs_remap.h"
#include "rocksdb/utilities/options_type.h"
#include "util/string_util.h" // errnoStr
namespace ROCKSDB_NAMESPACE {
namespace {
static std::unordered_map<std::string, OptionTypeInfo> chroot_fs_type_info = {
{"chroot_dir", {0, OptionType::kString}}};
} // namespace
ChrootFileSystem::ChrootFileSystem(const std::shared_ptr<FileSystem>& base,
const std::string& chroot_dir)
: RemapFileSystem(base), chroot_dir_(chroot_dir) {
RegisterOptions("chroot_dir", &chroot_dir_, &chroot_fs_type_info);
}
Status ChrootFileSystem::PrepareOptions(const ConfigOptions& options) {
Status s = FileSystemWrapper::PrepareOptions(options);
if (!s.ok()) {
return s;
} else if (chroot_dir_.empty()) {
s = Status::InvalidArgument("ChRootFileSystem requires a chroot dir");
} else {
s = target_->FileExists(chroot_dir_, IOOptions(), nullptr);
}
if (s.ok()) {
#if defined(OS_AIX)
char resolvedName[PATH_MAX];
char* real_chroot_dir = realpath(chroot_dir_.c_str(), resolvedName);
#else
char* real_chroot_dir = realpath(chroot_dir_.c_str(), nullptr);
#endif
// chroot_dir must exist so realpath() returns non-nullptr.
assert(real_chroot_dir != nullptr);
chroot_dir_ = real_chroot_dir;
#if !defined(OS_AIX)
free(real_chroot_dir);
#endif
}
return s;
}
IOStatus ChrootFileSystem::GetTestDirectory(const IOOptions& options,
std::string* path,
IODebugContext* dbg) {
// Adapted from PosixEnv's implementation since it doesn't provide a way to
// create directory in the chroot.
char buf[256];
snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid()));
*path = buf;
// Directory may already exist, so ignore return
return CreateDirIfMissing(*path, options, dbg);
}
// Returns status and expanded absolute path including the chroot directory.
// Checks whether the provided path breaks out of the chroot. If it returns
// non-OK status, the returned path should not be used.
std::pair<IOStatus, std::string> ChrootFileSystem::EncodePath(
const std::string& path) {
if (path.empty() || path[0] != '/') {
return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""};
}
std::pair<IOStatus, std::string> res;
res.second = chroot_dir_ + path;
#if defined(OS_AIX)
char resolvedName[PATH_MAX];
char* normalized_path = realpath(res.second.c_str(), resolvedName);
#else
char* normalized_path = realpath(res.second.c_str(), nullptr);
#endif
if (normalized_path == nullptr) {
res.first = IOStatus::NotFound(res.second, errnoStr(errno).c_str());
} else if (strlen(normalized_path) < chroot_dir_.size() ||
strncmp(normalized_path, chroot_dir_.c_str(),
chroot_dir_.size()) != 0) {
res.first = IOStatus::IOError(res.second,
"Attempted to access path outside chroot");
} else {
res.first = IOStatus::OK();
}
#if !defined(OS_AIX)
free(normalized_path);
#endif
return res;
}
// Similar to EncodePath() except assumes the basename in the path hasn't been
// created yet.
std::pair<IOStatus, std::string> ChrootFileSystem::EncodePathWithNewBasename(
const std::string& path) {
if (path.empty() || path[0] != '/') {
return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""};
}
// Basename may be followed by trailing slashes
size_t final_idx = path.find_last_not_of('/');
if (final_idx == std::string::npos) {
// It's only slashes so no basename to extract
return EncodePath(path);
}
// Pull off the basename temporarily since realname(3) (used by
// EncodePath()) requires a path that exists
size_t base_sep = path.rfind('/', final_idx);
auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1));
status_and_enc_path.second.append(path.substr(base_sep + 1));
return status_and_enc_path;
}
std::shared_ptr<FileSystem> NewChrootFileSystem(
const std::shared_ptr<FileSystem>& base, const std::string& chroot_dir) {
auto chroot_fs = std::make_shared<ChrootFileSystem>(base, chroot_dir);
Status s = chroot_fs->PrepareOptions(ConfigOptions());
if (s.ok()) {
return chroot_fs;
} else {
return nullptr;
}
}
Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) {
if (!base_env->FileExists(chroot_dir).ok()) {
return nullptr;
}
auto chroot_fs = NewChrootFileSystem(base_env->GetFileSystem(), chroot_dir);
if (chroot_fs != nullptr) {
return new CompositeEnvWrapper(base_env, chroot_fs);
} else {
return nullptr;
}
}
} // namespace ROCKSDB_NAMESPACE
#endif // !defined(OS_WIN)