ClickHouse/src/Common/renameat2.cpp

122 lines
3.5 KiB
C++
Raw Normal View History

2020-04-23 18:00:43 +00:00
#include <Common/renameat2.h>
2020-03-23 00:12:13 +00:00
#include <Common/Exception.h>
2020-04-09 22:22:43 +00:00
#include <Poco/File.h>
2020-03-23 00:12:13 +00:00
2020-04-09 22:22:43 +00:00
#if defined(linux) || defined(__linux) || defined(__linux__)
2020-03-23 00:12:13 +00:00
#include <unistd.h>
#include <fcntl.h>
2020-03-23 00:12:13 +00:00
#include <sys/syscall.h>
#include <linux/fs.h>
2020-04-09 22:22:43 +00:00
#include <sys/utsname.h>
2020-03-23 00:12:13 +00:00
#endif
namespace DB
{
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
extern const int ATOMIC_RENAME_FAIL;
extern const int SYSTEM_ERROR;
2020-04-09 22:22:43 +00:00
extern const int UNSUPPORTED_METHOD;
extern const int FILE_ALREADY_EXISTS;
2020-03-23 00:12:13 +00:00
}
2020-04-09 22:22:43 +00:00
static bool supportsRenameat2Impl()
{
#if defined(__NR_renameat2)
/// renameat2 is available in linux since 3.15
struct utsname sysinfo;
if (uname(&sysinfo))
return false;
char * point = nullptr;
2020-04-10 23:02:15 +00:00
auto v_major = strtol(sysinfo.release, &point, 10);
2020-04-09 22:22:43 +00:00
errno = 0;
if (errno || *point != '.' || v_major < 3)
return false;
if (3 < v_major)
return true;
errno = 0;
2020-04-10 23:02:15 +00:00
auto v_minor = strtol(point + 1, nullptr, 10);
2020-04-09 22:22:43 +00:00
return !errno && 15 <= v_minor;
#else
return false;
#endif
}
#if defined(__NR_renameat2)
2020-03-23 00:12:13 +00:00
static void renameat2(const std::string & old_path, const std::string & new_path, int flags)
{
if (old_path.empty() || new_path.empty())
2020-04-23 18:00:43 +00:00
throw Exception("Cannot rename " + old_path + " to " + new_path + ": path is empty", ErrorCodes::LOGICAL_ERROR);
2020-03-23 00:12:13 +00:00
/// int olddirfd (ignored for absolute oldpath), const char *oldpath,
/// int newdirfd (ignored for absolute newpath), const char *newpath,
/// unsigned int flags
if (0 == syscall(__NR_renameat2, AT_FDCWD, old_path.c_str(), AT_FDCWD, new_path.c_str(), flags))
2020-03-23 00:12:13 +00:00
return;
if (errno == EEXIST)
2020-04-23 18:00:43 +00:00
throwFromErrno("Cannot rename " + old_path + " to " + new_path + " because the second path already exists", ErrorCodes::ATOMIC_RENAME_FAIL);
2020-03-23 00:12:13 +00:00
if (errno == ENOENT)
throwFromErrno("Paths cannot be exchanged because " + old_path + " or " + new_path + " does not exist", ErrorCodes::ATOMIC_RENAME_FAIL);
2020-04-23 18:00:43 +00:00
throwFromErrnoWithPath("Cannot rename " + old_path + " to " + new_path, new_path, ErrorCodes::SYSTEM_ERROR);
2020-03-23 00:12:13 +00:00
}
#else
2020-04-09 22:22:43 +00:00
#define RENAME_NOREPLACE -1
#define RENAME_EXCHANGE -1
2020-03-23 00:12:13 +00:00
2020-04-09 22:22:43 +00:00
[[noreturn]]
static void renameat2(const std::string &, const std::string &, int)
2020-03-23 00:12:13 +00:00
{
2020-04-09 22:22:43 +00:00
throw Exception("Compiled without renameat2() support", ErrorCodes::UNSUPPORTED_METHOD);
2020-03-23 00:12:13 +00:00
}
2020-04-09 22:22:43 +00:00
#endif
static void renameNoReplaceFallback(const std::string & old_path, const std::string & new_path)
2020-03-23 00:12:13 +00:00
{
2020-04-09 22:22:43 +00:00
/// NOTE it's unsafe
if (Poco::File{new_path}.exists())
throw Exception("File " + new_path + " exists", ErrorCodes::FILE_ALREADY_EXISTS);
Poco::File{old_path}.renameTo(new_path);
2020-03-23 00:12:13 +00:00
}
2020-04-09 22:22:43 +00:00
/// Do not use [[noreturn]] to avoid warnings like "code will never be executed" in other places
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-noreturn"
static void renameExchangeFallback(const std::string &, const std::string &)
{
throw Exception("System call renameat2() is not supported", ErrorCodes::UNSUPPORTED_METHOD);
}
#pragma GCC diagnostic pop
bool supportsRenameat2()
{
static bool supports = supportsRenameat2Impl();
return supports;
}
2020-03-23 00:12:13 +00:00
void renameNoReplace(const std::string & old_path, const std::string & new_path)
{
2020-04-09 22:22:43 +00:00
if (supportsRenameat2())
renameat2(old_path, new_path, RENAME_NOREPLACE);
else
renameNoReplaceFallback(old_path, new_path);
2020-03-23 00:12:13 +00:00
}
void renameExchange(const std::string & old_path, const std::string & new_path)
{
2020-04-09 22:22:43 +00:00
if (supportsRenameat2())
renameat2(old_path, new_path, RENAME_EXCHANGE);
else
renameExchangeFallback(old_path, new_path);
2020-03-23 00:12:13 +00:00
}
}