mpd/src/io/FileOutputStream.hxx
2023-09-12 09:43:14 +02:00

192 lines
4.0 KiB
C++

// SPDX-License-Identifier: BSD-2-Clause
// author: Max Kellermann <max.kellermann@gmail.com>
#pragma once
#include "OutputStream.hxx"
#include "fs/AllocatedPath.hxx"
#ifndef _WIN32
#include "FileDescriptor.hxx"
#endif
#include <cassert>
#include <cstdint>
#ifdef _WIN32
#include <fileapi.h>
#include <windef.h> // for HWND (needed by winbase.h)
#include <handleapi.h> // for INVALID_HANDLE_VALUE
#include <winbase.h> // for FILE_END
#endif
#if defined(__linux__) && !defined(ANDROID)
/* we don't use O_TMPFILE on Android because Android's braindead
SELinux policy disallows hardlinks
(https://android.googlesource.com/platform/external/sepolicy/+/85ce2c7),
even hardlinks from /proc/self/fd/N, which however is required to
use O_TMPFILE */
#define HAVE_O_TMPFILE
#endif
class Path;
/**
* An #OutputStream implementation which writes to a file.
*
* The destructor will attempt to roll back the changes by calling
* Cancel(). To confirm that data shall be written and the existing
* file shall be replaced, call Commit().
*/
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
#ifdef _WIN32
HANDLE handle = INVALID_HANDLE_VALUE;
#else
FileDescriptor fd = FileDescriptor::Undefined();
#endif
#ifdef HAVE_O_TMPFILE
/**
* Was O_TMPFILE used? If yes, then linkat() must be used to
* create a link to this file.
*/
bool is_tmpfile = false;
#endif
public:
enum class Mode : uint8_t {
/**
* Create a new file, or replace an existing file.
* File contents may not be visible until Commit() has
* been called.
*/
CREATE,
/**
* Like #CREATE, but no attempt is made to hide file
* contents during the transaction (e.g. via O_TMPFILE
* or a hidden temporary file).
*/
CREATE_VISIBLE,
/**
* Append to a file that already exists. If it does
* not, an exception is thrown.
*/
APPEND_EXISTING,
/**
* Like #APPEND_EXISTING, but create the file if it
* does not exist.
*/
APPEND_OR_CREATE,
};
private:
Mode mode;
public:
explicit FileOutputStream(Path _path, Mode _mode=Mode::CREATE);
#ifdef __linux__
FileOutputStream(FileDescriptor _directory_fd, Path _path,
Mode _mode=Mode::CREATE);
#endif
~FileOutputStream() noexcept {
if (IsDefined())
Cancel();
}
FileOutputStream(const FileOutputStream &) = delete;
FileOutputStream &operator=(const FileOutputStream &) = delete;
public:
Path GetPath() const noexcept {
return path;
}
/**
* Returns the current offset.
*/
[[gnu::pure]]
uint64_t Tell() const noexcept;
/* virtual methods from class OutputStream */
void Write(std::span<const std::byte> src) override;
/**
* Flush all data written to this object to disk (but does not
* commit to the final path). This method blocks until this
* flush is complete. It can be called repeatedly.
*
* Throws on error.
*/
void Sync();
/**
* Commit all data written to the file and make the file
* visible on the specified path.
*
* After returning, this object must not be used again.
*
* Throws on error.
*/
void Commit();
/**
* Attempt to roll back all changes.
*
* After returning, this object must not be used again.
*/
void Cancel() noexcept;
private:
void OpenCreate(bool visible);
void OpenAppend(bool create);
void Open();
bool Close() noexcept {
assert(IsDefined());
#ifdef _WIN32
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
return true;
#else
return fd.Close();
#endif
}
#ifdef _WIN32
bool SeekEOF() noexcept {
return SetFilePointer(handle, 0, nullptr,
FILE_END) != 0xffffffff;
}
#endif
bool IsDefined() const noexcept {
#ifdef _WIN32
return handle != INVALID_HANDLE_VALUE;
#else
return fd.IsDefined();
#endif
}
void RenameOrThrow(Path old_path, Path new_path) const;
void Delete(Path delete_path) const noexcept;
};