Commit 06827cfc authored by Max Kellermann's avatar Max Kellermann

fs/io/FileOutputStream: move code to new class BaseFileOutputStream

parent 6dc30979
...@@ -37,37 +37,33 @@ FileOutputStream::Create(Path path, Error &error) ...@@ -37,37 +37,33 @@ FileOutputStream::Create(Path path, Error &error)
#ifdef WIN32 #ifdef WIN32
FileOutputStream::FileOutputStream(Path _path, Error &error) FileOutputStream::FileOutputStream(Path _path, Error &error)
:path(_path), :BaseFileOutputStream(_path)
handle(CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, {
SetHandle(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,
nullptr)) nullptr));
{ if (!IsDefined())
if (handle == INVALID_HANDLE_VALUE) {
const auto path_utf8 = path.ToUTF8();
error.FormatLastError("Failed to create %s", error.FormatLastError("Failed to create %s",
path_utf8.c_str()); GetPath().ToUTF8().c_str());
}
} }
bool bool
FileOutputStream::Write(const void *data, size_t size, Error &error) BaseFileOutputStream::Write(const void *data, size_t size, Error &error)
{ {
assert(IsDefined()); assert(IsDefined());
DWORD nbytes; DWORD nbytes;
if (!WriteFile(handle, data, size, &nbytes, nullptr)) { if (!WriteFile(handle, data, size, &nbytes, nullptr)) {
const auto path_utf8 = path.ToUTF8();
error.FormatLastError("Failed to write to %s", error.FormatLastError("Failed to write to %s",
path_utf8.c_str()); path.ToUTF8().c_str());
return false; return false;
} }
if (size_t(nbytes) != size) { if (size_t(nbytes) != size) {
const auto path_utf8 = path.ToUTF8();
error.FormatLastError(ERROR_DISK_FULL, error.FormatLastError(ERROR_DISK_FULL,
"Failed to write to %s", "Failed to write to %s",
path_utf8.c_str()); path.ToUTF8().c_str());
return false; return false;
} }
...@@ -79,8 +75,7 @@ FileOutputStream::Commit(gcc_unused Error &error) ...@@ -79,8 +75,7 @@ FileOutputStream::Commit(gcc_unused Error &error)
{ {
assert(IsDefined()); assert(IsDefined());
CloseHandle(handle); Close();
handle = INVALID_HANDLE_VALUE;
return true; return true;
} }
...@@ -89,9 +84,8 @@ FileOutputStream::Cancel() ...@@ -89,9 +84,8 @@ FileOutputStream::Cancel()
{ {
assert(IsDefined()); assert(IsDefined());
CloseHandle(handle); Close();
handle = INVALID_HANDLE_VALUE; RemoveFile(GetPath());
RemoveFile(path);
} }
#else #else
...@@ -124,35 +118,36 @@ OpenTempFile(FileDescriptor &fd, Path path) ...@@ -124,35 +118,36 @@ OpenTempFile(FileDescriptor &fd, Path path)
#endif /* HAVE_LINKAT */ #endif /* HAVE_LINKAT */
FileOutputStream::FileOutputStream(Path _path, Error &error) FileOutputStream::FileOutputStream(Path _path, Error &error)
:path(_path) :BaseFileOutputStream(_path)
{ {
#ifdef HAVE_LINKAT #ifdef HAVE_LINKAT
/* try Linux's O_TMPFILE first */ /* try Linux's O_TMPFILE first */
is_tmpfile = OpenTempFile(fd, path); is_tmpfile = OpenTempFile(SetFD(), GetPath());
if (!is_tmpfile) { if (!is_tmpfile) {
#endif #endif
/* fall back to plain POSIX */ /* fall back to plain POSIX */
if (!fd.Open(path.c_str(), if (!SetFD().Open(GetPath().c_str(),
O_WRONLY|O_CREAT|O_TRUNC, O_WRONLY|O_CREAT|O_TRUNC,
0666)) 0666))
error.FormatErrno("Failed to create %s", path.c_str()); error.FormatErrno("Failed to create %s",
GetPath().c_str());
#ifdef HAVE_LINKAT #ifdef HAVE_LINKAT
} }
#endif #endif
} }
bool bool
FileOutputStream::Write(const void *data, size_t size, Error &error) BaseFileOutputStream::Write(const void *data, size_t size, Error &error)
{ {
assert(IsDefined()); assert(IsDefined());
ssize_t nbytes = fd.Write(data, size); ssize_t nbytes = fd.Write(data, size);
if (nbytes < 0) { if (nbytes < 0) {
error.FormatErrno("Failed to write to %s", path.c_str()); error.FormatErrno("Failed to write to %s", GetPath().c_str());
return false; return false;
} else if ((size_t)nbytes < size) { } else if ((size_t)nbytes < size) {
error.FormatErrno(ENOSPC, error.FormatErrno(ENOSPC,
"Failed to write to %s", path.c_str()); "Failed to write to %s", GetPath().c_str());
return false; return false;
} }
...@@ -166,24 +161,25 @@ FileOutputStream::Commit(Error &error) ...@@ -166,24 +161,25 @@ FileOutputStream::Commit(Error &error)
#if HAVE_LINKAT #if HAVE_LINKAT
if (is_tmpfile) { if (is_tmpfile) {
RemoveFile(path); RemoveFile(GetPath());
/* hard-link the temporary file to the final path */ /* hard-link the temporary file to the final path */
char fd_path[64]; char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d",
fd.Get()); GetFD().Get());
if (linkat(AT_FDCWD, fd_path, AT_FDCWD, path.c_str(), if (linkat(AT_FDCWD, fd_path, AT_FDCWD, GetPath().c_str(),
AT_SYMLINK_FOLLOW) < 0) { AT_SYMLINK_FOLLOW) < 0) {
error.FormatErrno("Failed to commit %s", path.c_str()); error.FormatErrno("Failed to commit %s",
fd.Close(); GetPath().c_str());
Close();
return false; return false;
} }
} }
#endif #endif
bool success = fd.Close(); bool success = Close();
if (!success) if (!success)
error.FormatErrno("Failed to commit %s", path.c_str()); error.FormatErrno("Failed to commit %s", GetPath().c_str());
return success; return success;
} }
...@@ -193,12 +189,12 @@ FileOutputStream::Cancel() ...@@ -193,12 +189,12 @@ FileOutputStream::Cancel()
{ {
assert(IsDefined()); assert(IsDefined());
fd.Close(); Close();
#ifdef HAVE_LINKAT #ifdef HAVE_LINKAT
if (!is_tmpfile) if (!is_tmpfile)
#endif #endif
RemoveFile(path); RemoveFile(GetPath());
} }
#endif #endif
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
class Path; class Path;
class FileOutputStream final : public OutputStream { class BaseFileOutputStream : public OutputStream {
const AllocatedPath path; const AllocatedPath path;
#ifdef WIN32 #ifdef WIN32
...@@ -46,6 +46,73 @@ class FileOutputStream final : public OutputStream { ...@@ -46,6 +46,73 @@ class FileOutputStream final : public OutputStream {
FileDescriptor fd; FileDescriptor fd;
#endif #endif
protected:
#ifdef WIN32
template<typename P>
BaseFileOutputStream(P &&_path)
:path(std::forward<P>(_path)),
handle(INVALID_HANDLE_VALUE) {}
#else
template<typename P>
BaseFileOutputStream(P &&_path)
:path(std::forward<P>(_path)),
fd(FileDescriptor::Undefined()) {}
#endif
~BaseFileOutputStream() {
assert(!IsDefined());
}
#ifdef WIN32
void SetHandle(HANDLE _handle) {
assert(!IsDefined());
handle = _handle;
assert(IsDefined());
}
#else
FileDescriptor &SetFD() {
assert(!IsDefined());
return fd;
}
const FileDescriptor &GetFD() const {
return fd;
}
#endif
bool Close() {
assert(IsDefined());
#ifdef WIN32
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
return true;
#else
return fd.Close();
#endif
}
public:
bool IsDefined() const {
#ifdef WIN32
return handle != INVALID_HANDLE_VALUE;
#else
return fd.IsDefined();
#endif
}
Path GetPath() const {
return path;
}
/* virtual methods from class OutputStream */
bool Write(const void *data, size_t size, Error &error) override;
};
class FileOutputStream final : public BaseFileOutputStream {
#ifdef HAVE_LINKAT #ifdef HAVE_LINKAT
/** /**
* Was O_TMPFILE used? If yes, then linkat() must be used to * Was O_TMPFILE used? If yes, then linkat() must be used to
...@@ -64,19 +131,8 @@ public: ...@@ -64,19 +131,8 @@ public:
static FileOutputStream *Create(Path path, Error &error); static FileOutputStream *Create(Path path, Error &error);
bool IsDefined() const {
#ifdef WIN32
return handle != INVALID_HANDLE_VALUE;
#else
return fd.IsDefined();
#endif
}
bool Commit(Error &error); bool Commit(Error &error);
void Cancel(); void Cancel();
/* virtual methods from class OutputStream */
bool Write(const void *data, size_t size, Error &error) override;
}; };
#endif #endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment