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
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@ -31,6 +31,10 @@
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <tchar.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -81,8 +85,22 @@ FileOutputStream::Open()
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
inline void
|
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,
|
handle = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr,
|
||||||
CREATE_ALWAYS,
|
CREATE_ALWAYS,
|
||||||
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
|
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,
|
||||||
|
@ -149,10 +167,17 @@ FileOutputStream::Sync()
|
||||||
|
|
||||||
void
|
void
|
||||||
FileOutputStream::Commit()
|
FileOutputStream::Commit()
|
||||||
{
|
try {
|
||||||
assert(IsDefined());
|
assert(IsDefined());
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
|
if (tmp_path != nullptr)
|
||||||
|
RenameOrThrow(tmp_path, path);
|
||||||
|
} catch (...) {
|
||||||
|
if (tmp_path != nullptr)
|
||||||
|
Delete(tmp_path);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
@ -200,6 +225,22 @@ FileOutputStream::OpenCreate([[maybe_unused]] bool visible)
|
||||||
}
|
}
|
||||||
#endif
|
#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 */
|
/* fall back to plain POSIX */
|
||||||
if (!fd.Open(
|
if (!fd.Open(
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
@ -263,7 +304,7 @@ FileOutputStream::Sync()
|
||||||
|
|
||||||
void
|
void
|
||||||
FileOutputStream::Commit()
|
FileOutputStream::Commit()
|
||||||
{
|
try {
|
||||||
assert(IsDefined());
|
assert(IsDefined());
|
||||||
|
|
||||||
#ifdef HAVE_O_TMPFILE
|
#ifdef HAVE_O_TMPFILE
|
||||||
|
@ -283,6 +324,13 @@ FileOutputStream::Commit()
|
||||||
if (!Close()) {
|
if (!Close()) {
|
||||||
throw FormatErrno("Failed to commit %s", path.c_str());
|
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
|
#endif
|
||||||
|
@ -294,6 +342,11 @@ FileOutputStream::Cancel() noexcept
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
|
if (tmp_path != nullptr) {
|
||||||
|
Delete(tmp_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case Mode::CREATE:
|
case Mode::CREATE:
|
||||||
#ifdef HAVE_O_TMPFILE
|
#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
|
inline void
|
||||||
FileOutputStream::Delete(Path delete_path) const noexcept
|
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
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@ -68,6 +68,13 @@ class Path;
|
||||||
class FileOutputStream final : public OutputStream {
|
class FileOutputStream final : public OutputStream {
|
||||||
const AllocatedPath path;
|
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__
|
#ifdef __linux__
|
||||||
const FileDescriptor directory_fd;
|
const FileDescriptor directory_fd;
|
||||||
#endif
|
#endif
|
||||||
|
@ -206,6 +213,7 @@ private:
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenameOrThrow(Path old_path, Path new_path) const;
|
||||||
void Delete(Path delete_path) const noexcept;
|
void Delete(Path delete_path) const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ test(
|
||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
net_dep,
|
net_dep,
|
||||||
|
fs_dep,
|
||||||
gtest_dep,
|
gtest_dep,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue