io/FileOutputStream: write to temporary file if O_TMPFILE is not available
This commit is contained in:
parent
c344403bed
commit
0c98d93e9a
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2014-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2014-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@ -31,6 +31,10 @@
|
|||
#include "system/Error.hxx"
|
||||
#include "util/StringFormat.hxx"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <tchar.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
@ -81,8 +85,22 @@ FileOutputStream::Open()
|
|||
#ifdef _WIN32
|
||||
|
||||
inline void
|
||||
FileOutputStream::OpenCreate([[maybe_unused]] bool visible)
|
||||
FileOutputStream::OpenCreate(bool visible)
|
||||
{
|
||||
if (!visible) {
|
||||
/* attempt to create a temporary file */
|
||||
tmp_path = path.WithSuffix(_T(".tmp"));
|
||||
Delete(tmp_path);
|
||||
|
||||
handle = CreateFile(tmp_path.c_str(), GENERIC_WRITE, 0, nullptr,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
|
||||
nullptr);
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
handle = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
|
||||
|
@ -149,10 +167,17 @@ FileOutputStream::Sync()
|
|||
|
||||
void
|
||||
FileOutputStream::Commit()
|
||||
{
|
||||
try {
|
||||
assert(IsDefined());
|
||||
|
||||
Close();
|
||||
|
||||
if (tmp_path != nullptr)
|
||||
RenameOrThrow(tmp_path, path);
|
||||
} catch (...) {
|
||||
if (tmp_path != nullptr)
|
||||
Delete(tmp_path);
|
||||
throw;
|
||||
}
|
||||
|
||||
#else
|
||||
|
@ -200,6 +225,22 @@ FileOutputStream::OpenCreate([[maybe_unused]] bool visible)
|
|||
}
|
||||
#endif
|
||||
|
||||
if (!visible) {
|
||||
/* attempt to create a temporary file */
|
||||
tmp_path = path + ".tmp";
|
||||
Delete(tmp_path);
|
||||
|
||||
if (fd.Open(
|
||||
#ifdef __linux__
|
||||
directory_fd,
|
||||
#endif
|
||||
tmp_path.c_str(),
|
||||
O_WRONLY|O_CREAT|O_EXCL,
|
||||
0666))
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/* fall back to plain POSIX */
|
||||
if (!fd.Open(
|
||||
#ifdef __linux__
|
||||
|
@ -263,7 +304,7 @@ FileOutputStream::Sync()
|
|||
|
||||
void
|
||||
FileOutputStream::Commit()
|
||||
{
|
||||
try {
|
||||
assert(IsDefined());
|
||||
|
||||
#ifdef HAVE_O_TMPFILE
|
||||
|
@ -283,6 +324,13 @@ FileOutputStream::Commit()
|
|||
if (!Close()) {
|
||||
throw FormatErrno("Failed to commit %s", path.c_str());
|
||||
}
|
||||
|
||||
if (tmp_path != nullptr)
|
||||
RenameOrThrow(tmp_path, path);
|
||||
} catch (...) {
|
||||
if (tmp_path != nullptr)
|
||||
Delete(tmp_path);
|
||||
throw;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -294,6 +342,11 @@ FileOutputStream::Cancel() noexcept
|
|||
|
||||
Close();
|
||||
|
||||
if (tmp_path != nullptr) {
|
||||
Delete(tmp_path);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case Mode::CREATE:
|
||||
#ifdef HAVE_O_TMPFILE
|
||||
|
@ -310,6 +363,27 @@ FileOutputStream::Cancel() noexcept
|
|||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
FileOutputStream::RenameOrThrow([[maybe_unused]] Path old_path,
|
||||
[[maybe_unused]] Path new_path) const
|
||||
{
|
||||
assert(old_path != nullptr);
|
||||
assert(new_path != nullptr);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!MoveFileEx(old_path.c_str(), new_path.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING))
|
||||
throw MakeLastError("Failed to rename file");
|
||||
#elif defined(__linux__)
|
||||
if (renameat(directory_fd.Get(), tmp_path.c_str(),
|
||||
directory_fd.Get(), path.c_str()) < 0)
|
||||
throw MakeErrno("Failed to rename file");
|
||||
#else
|
||||
if (rename(tmp_path.c_str(), path.c_str()))
|
||||
throw MakeErrno("Failed to rename file");
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
FileOutputStream::Delete(Path delete_path) const noexcept
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2014-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2014-2022 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
@ -68,6 +68,13 @@ class Path;
|
|||
class FileOutputStream final : public OutputStream {
|
||||
const AllocatedPath path;
|
||||
|
||||
/**
|
||||
* If a temporary file is being written to, then this is its
|
||||
* path. Commit() will rename it to the path specified in the
|
||||
* constructor.
|
||||
*/
|
||||
AllocatedPath tmp_path{nullptr};
|
||||
|
||||
#ifdef __linux__
|
||||
const FileDescriptor directory_fd;
|
||||
#endif
|
||||
|
@ -206,6 +213,7 @@ private:
|
|||
#endif
|
||||
}
|
||||
|
||||
void RenameOrThrow(Path old_path, Path new_path) const;
|
||||
void Delete(Path delete_path) const noexcept;
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ test(
|
|||
include_directories: inc,
|
||||
dependencies: [
|
||||
net_dep,
|
||||
fs_dep,
|
||||
gtest_dep,
|
||||
],
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue