I now have an implementation of FileOutputBuffer (OutputBuffer was already taken). The patch supports the functionality listed below and I've tested that it works for lld.
To implement the FileOutputBuffer, I needed to add some more functions to llvm/Support/FileSystem.h, including:
is_writable_file()
is_executable_file()
set_file_executable()
For these parts I would like to follow
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3365.html#TOC
which is what the rest of the fs library is modeled after. So we would
use:
error_code permissions(const Twine &path, perms p);
If the name is confusing (it says nothing about modification) I'm fine
with modify_permissions or something similar.
unique_file_sized()
Instead of adding this I would much rather add resize_file and call it
after unique_file.
map_file_pages()
unmap_file_pages()
These are good.
I've implemented the unix side for all these, but have the windows side just stubbed out.
I've also added a test case (unittests/Support/FileOutputBufferTest.cpp) which exercises FileOutputBuffer.
-Nick
As a generally comment there is lots of trailing white space and tabs
and white space only changes.
Inline comments:
Index: include/llvm/Support/FileSystem.h
--- include/llvm/Support/FileSystem.h (revision 157010)
+++ include/llvm/Support/FileSystem.h (working copy)
@@ -426,12 +454,35 @@
/// @param result_path Set to the opened file's absolute path.
/// @param makeAbsolute If true and @model is not an absolute path, a temp
/// directory will be prepended.
+/// @param private_file If true file will have permissions set to only
+/// be accessible by the current user. If false, then the file
+/// permissions are set to be globally readable.
/// @results errc::success if result_{fd,path} have been successfully set,
/// otherwise a platform specific error_code.
error_code unique_file(const Twine &model, int &result_fd,
- SmallVectorImpl<char> &result_path,
- bool makeAbsolute = true, unsigned mode = 0600);
+ SmallVectorImpl<char> &result_path,
+ bool makeAbsolute = true,
+ bool private_file = true);
Incorrect whitespace only change and tabs.
Index: include/llvm/Support/FileOutputBuffer.h
--- include/llvm/Support/FileOutputBuffer.h (revision 0)
+++ include/llvm/Support/FileOutputBuffer.h (revision 0)
@@ -0,0 +1,97 @@
+//=== FileOutputBuffer.h - File Output Buffer -------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Utility for creating a in-memory buffer that will be written to a file.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_FILEOUTPUTBUFFER_H
+#define LLVM_SUPPORT_FILEOUTPUTBUFFER_H
+
+#include "llvm/Support/DataTypes.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/SmallString.h"
+
+namespace llvm {
+
+class error_code;
+template<class T> class OwningPtr;
+
+/// FileOutputBuffer - This interface provides simple way to create an in-memory
+/// buffer which will be written to a file. During the lifetime of these
+/// objects, the content or existence of the specified file is undefined. That
+/// is, creating an OutputBuffer for a file may immediately remove the file.
+/// If the FileOutputBuffer is committed, the target file's content will become
+/// the buffer content at the time of the commit. If the FileOutputBuffer is
+/// not committed, the file will be deleted in the FileOutputBuffer destructor.
+class FileOutputBuffer {
+public:
+
+ enum {
+ F_executable = 1 /// set the 'x' bit on the resulting file
+ };
+
+ /// Factory method to create an OutputBuffer object which manages a read/write
+ /// buffer of the specified size. When committed, the buffer will be written
+ /// to the file at the specified path.
+ static error_code create(StringRef filePath, size_t size,
+ OwningPtr<FileOutputBuffer> &result,
+ unsigned flags=0);
Arguments should start with an upper case letter. (same for all of these)
+ /// Returns a pointer to the start of the buffer.
+ uint8_t *getBufferStart() const {
+ return bufferStart;
+ }
+
+ /// Returns a pointer to the end of the buffer.
+ uint8_t *getBufferEnd() const {
+ return bufferEnd;
+ }
+
+ /// Returns size of the buffer.
+ size_t getBufferSize() const {
+ return bufferEnd - bufferStart;
+ }
+
+ /// Returns path where file will show up if buffer is committed.
+ StringRef getPath() const {
+ return finalPath;
+ }
+
+ /// Flushes the content of the buffer to its file and deallocates the
+ /// buffer. If commit() is not called before this object's destructor
+ /// is called, the file is deleted in the destructor. The optional parameter
+ /// is used if it turns out you want the file size to be smaller than
+ /// initially requested.
+ virtual error_code commit(int64_t newSmallerSize = -1);
Why is this virtual? I don't ever see a need to subclass FileOutputBuffer.
+ /// If this object was previously committed, the destructor just deletes
+ /// this object. If this object was not committed, the destructor
+ /// deallocates the buffer and the target file is never written.
+ virtual ~FileOutputBuffer();
Same.
+
+
+protected:
+ FileOutputBuffer(const FileOutputBuffer &); // DO NOT IMPLEMENT
+ FileOutputBuffer &operator=(const FileOutputBuffer &); // DO NOT IMPLEMENT
+ FileOutputBuffer(uint8_t *start, uint8_t *end,
+ StringRef path, StringRef tempPath);
+
+ uint8_t *bufferStart;
+ uint8_t *bufferEnd;
+ SmallString<128> finalPath;
+ SmallString<128> tempPath;
These should all start with an uppercase letter.
+};
+
+
+
+} // end namespace llvm
+
+#endif
Index: unittests/Support/FileOutputBufferTest.cpp
--- unittests/Support/FileOutputBufferTest.cpp (revision 0)
+++ unittests/Support/FileOutputBufferTest.cpp (revision 0)
@@ -0,0 +1,131 @@
+//===- llvm/unittest/Support/FileOutputBuffer.cpp - unit tests ------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/PathV2.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/FileOutputBuffer.h"
+#include "llvm/ADT/OwningPtr.h"
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::sys;
+
+#define ASSERT_NO_ERROR(x) \
+ if (error_code ASSERT_NO_ERROR_ec = x) { \
+ errs() << #x ": did not return errc::success.\n" \
+ << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \
+ << "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \
+ } else {}
+
+namespace {
+
+
+TEST(FileOutputBuffer, Test) {
+ // Create unique temporary directory for these tests
+ SmallString<128> TestDirectory;
+ {
+ int fd;
+ ASSERT_NO_ERROR(
+ fs::unique_file("FileOutputBuffer-test-%%-%%-%%-%%/dir", fd,
+ TestDirectory));
+ ::close(fd);
+ TestDirectory = path::parent_path(TestDirectory);
+ }
+
+ // TEST 1: Verify commit case.
+ SmallString<128> File1(TestDirectory);
+ File1.append("/file1");
+ {
+ OwningPtr<FileOutputBuffer> Buffer;
+ ASSERT_NO_ERROR(FileOutputBuffer::create(File1, 8192, Buffer));
+ // Start buffer with special header.
+ memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
+ // Write to end of buffer to verify it is writable.
+ memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
+ // Commit buffer.
+ ASSERT_NO_ERROR(Buffer->commit());
+ }
+ // Verify file exists and starts with special header.
+ bool MagicMatches = false;
+ ASSERT_NO_ERROR(fs::has_magic(Twine(File1), Twine("AABBCCDDEEFFGGHHIIJJ"),
+ MagicMatches));
+ EXPECT_TRUE(MagicMatches);
+ // Verify file is correct size.
+ uint64_t File1Size;
+ ASSERT_NO_ERROR(fs::file_size(Twine(File1), File1Size));
+ ASSERT_EQ(File1Size, 8192ULL);
+
+ // TEST 2: Verify abort case.
+ SmallString<128> File2(TestDirectory);
+ File2.append("/file2");
+ {
+ OwningPtr<FileOutputBuffer> Buffer2;
+ ASSERT_NO_ERROR(FileOutputBuffer::create(File2, 8192, Buffer2));
+ // Fill buffer with special header.
+ memcpy(Buffer2->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
+ // Do *not* commit buffer.
+ }
+ // Verify file does not exist (because buffer not commited).
+ bool Exists = false;
+ ASSERT_NO_ERROR(fs::exists(Twine(File2), Exists));
+ EXPECT_FALSE(Exists);
+
+
+ // TEST 3: Verify sizing down case.
+ SmallString<128> File3(TestDirectory);
+ File3.append("/file3");
+ {
+ OwningPtr<FileOutputBuffer> Buffer;
+ ASSERT_NO_ERROR(FileOutputBuffer::create(File3, 8192000, Buffer));
+ // Start buffer with special header.
+ memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
+ // Write to end of buffer to verify it is writable.
+ memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
+ // Commit buffer, but size down to smaller size
+ ASSERT_NO_ERROR(Buffer->commit(5000));
+ }
+ // Verify file exists and starts with special header.
+ bool MagicMatches3 = false;
+ ASSERT_NO_ERROR(fs::has_magic(Twine(File3), Twine("AABBCCDDEEFFGGHHIIJJ"),
+ MagicMatches3));
+ EXPECT_TRUE(MagicMatches3);
+ // Verify file is correct size.
+ uint64_t File3Size;
+ ASSERT_NO_ERROR(fs::file_size(Twine(File3), File3Size));
+ ASSERT_EQ(File3Size, 5000ULL);
+
+
+ // TEST 4: Verify file can be made executable.
+ SmallString<128> File4(TestDirectory);
+ File4.append("/file4");
+ {
+ OwningPtr<FileOutputBuffer> Buffer;
+ ASSERT_NO_ERROR(FileOutputBuffer::create(File4, 8192, Buffer,
+ FileOutputBuffer::F_executable));
+ // Start buffer with special header.
+ memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
+ // Commit buffer.
+ ASSERT_NO_ERROR(Buffer->commit());
+ }
+ // Verify file exists and is executable.
+ bool IsExecutable;
+ ASSERT_NO_ERROR(fs::is_executable_file(Twine(File4), IsExecutable));
+ EXPECT_TRUE(IsExecutable);
+
+
+ // Clean up.
+ uint32_t RemovedCount;
+ ASSERT_NO_ERROR(fs::remove_all(TestDirectory.str(), RemovedCount));
+}
+
+
+} // anonymous namespace
Index: unittests/CMakeLists.txt
--- unittests/CMakeLists.txt (revision 157010)
+++ unittests/CMakeLists.txt (working copy)
@@ -165,6 +165,7 @@
Support/CommandLineTest.cpp
Support/ConstantRangeTest.cpp
Support/EndianTest.cpp
+ Support/FileOutputBufferTest.cpp
Support/LeakDetectorTest.cpp
Support/MathExtrasTest.cpp
Support/Path.cpp
Index: lib/Support/Unix/PathV2.inc
--- lib/Support/Unix/PathV2.inc (revision 157010)
+++ lib/Support/Unix/PathV2.inc (working copy)
@@ -24,6 +24,9 @@
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
@@ -52,6 +55,7 @@
# define PATH_MAX 4096
#endif
+
using namespace llvm;
namespace {
@@ -347,10 +351,19 @@
return error_code::success();
}
-// Since this is most often used for temporary files, mode defaults to 0600.
+static mode_t current_umask() {
+ // There is no posix way to just get current umask. You
+ // have to set it to something else, and get the "old" value.
+ // So, get and reset to get the current umask value;
+ mode_t mask = ::umask(0);
+ ::umask(mask);
+ return mask;
+}
+
+
error_code unique_file(const Twine &model, int &result_fd,
- SmallVectorImpl<char> &result_path,
- bool makeAbsolute, unsigned mode) {
+ SmallVectorImpl<char> &result_path,
+ bool makeAbsolute, bool private_file) {
Incorrect whitespace change.
SmallString<128> Model;
model.toVector(Model);
// Null terminate.
@@ -380,7 +393,8 @@
// Try to open + create the file.
rety_open_create:
- int RandomFD = ::open(RandomPath.c_str(), O_RDWR | O_CREAT | O_EXCL, mode);
+ int perms = (private_file ? 0600 : 0666) & ~(current_umask());
+ int RandomFD = ::open(RandomPath.c_str(), O_RDWR | O_CREAT | O_EXCL, perms);
if (RandomFD == -1) {
// If the file existed, try again, otherwise, error.
if (errno == errc::file_exists)
@@ -426,6 +440,28 @@
return error_code::success();
}
+
+error_code unique_file_sized(const Twine &model, size_t size,
+ SmallVectorImpl<char> &result_path,
+ bool makeAbsolute) {
+ int fd;
+ if ( error_code ec = unique_file(model, fd, result_path, makeAbsolute, false) )
+ return ec;
+
+ if ( ::ftruncate(fd, size) == -1 ) {
+ int error = errno;
+ result_path.push_back('\0');
This is incorrect because result_path now actually includes a \0. I added a
function c_str(container) specifically for this case that adds a 0 then removes
it.
+ ::unlink(result_path.begin());
+ ::close(fd);
+ return error_code(error, system_category());
+ }
+
+ ::close(fd);
+
+ return error_code::success();
+}
+
+
error_code detail::directory_iterator_construct(detail::DirIterState &it,
StringRef path){
SmallString<128> path_null(path);
@@ -496,6 +532,97 @@
return error_code::success();
}
+error_code map_file_pages(const Twine &path, off_t file_offset, size_t size,
+ bool map_writable, void *&result) {
+ SmallString<128> path_storage;
+ StringRef name = path.toNullTerminatedStringRef(path_storage);
+ int oflags = map_writable ? O_RDWR : O_RDONLY;
+ int fd = ::open(name.begin(), oflags);
+ if ( fd == -1 )
+ return error_code(errno, system_category());
+ int flags = map_writable ? MAP_SHARED : MAP_PRIVATE;
+ int prot = map_writable ? (PROT_READ|PROT_WRITE) : PROT_READ;
+#ifdef MAP_FILE
+ flags |= MAP_FILE;
+#endif
+ result = ::mmap(0, size, prot, flags, fd, file_offset);
+ if (result == MAP_FAILED) {
+ ::close(fd);
+ return error_code(errno, system_category());
+ }
+
+ ::close(fd);
Could AutoFD work here?
+ return error_code::success();
+}
+
+error_code unmap_file_pages(void *base, size_t size) {
+ if ( ::munmap(base, size) == -1 )
+ return error_code(errno, system_category());
+
+ return error_code::success();
+}
+
+
+error_code is_writable_file(const Twine &path, bool &result) {
+ result = false;
+ SmallString<128> path_storage;
+ StringRef name = path.toNullTerminatedStringRef(path_storage);
+ if ( ::access(name.begin(), W_OK) == -1 ) {
+ int error = errno;
+ if ( error == EACCES )
+ result = false;
+ else
+ return error_code(error, system_category());
+ } else {
+ result = true;
+ }
+ return error_code::success();
+}
+
+
+error_code is_executable_file(const Twine &path, bool &result) {
+ result = false;
+ SmallString<128> path_storage;
+ StringRef name = path.toNullTerminatedStringRef(path_storage);
+ if ( ::access(name.begin(), X_OK) == -1 ) {
+ int error = errno;
+ if ( error == EACCES )
+ result = false;
+ else
+ return error_code(error, system_category());
+ } else {
+ result = true;
+ }
+ return error_code::success();
+}
+
+
+
+error_code set_file_executable(const Twine &path) {
+ SmallString<128> path_storage;
+ StringRef name = path.toNullTerminatedStringRef(path_storage);
+
+ struct stat buf;
+ if ( stat(name.begin(), &buf) == -1 )
+ return error_code(errno, system_category());
+
+ // Only add 'x' bit where 'r' bit is already set
+ mode_t perms = buf.st_mode;
+ if ( perms & S_IRUSR )
+ perms |= S_IXUSR;
+ if ( perms & S_IRGRP )
+ perms |= S_IXGRP;
+ if ( perms & S_IROTH )
+ perms |= S_IXOTH;
+
+ if ( ::chmod(name.begin(), perms) == -1)
+ return error_code(errno, system_category());
+
+ return error_code::success();
+}
+
+
+
} // end namespace fs
} // end namespace sys
} // end namespace llvm
Index: lib/Support/FileOutputBuffer.cpp
--- lib/Support/FileOutputBuffer.cpp (revision 0)
+++ lib/Support/FileOutputBuffer.cpp (revision 0)
@@ -0,0 +1,127 @@
+//===- FileOutputBuffer.cpp - File Output Buffer ----------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Utility for creating a in-memory buffer that will be written to a file.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/FileOutputBuffer.h"
+#include "llvm/Support/system_error.h"
+#include "llvm/Support/FileSystem.h"
+
+#include "llvm/Support/raw_ostream.h"
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/OwningPtr.h"
These should be sorted alphabetically except for the main header.
+
+namespace llvm {
+
+
+FileOutputBuffer::FileOutputBuffer(uint8_t *start, uint8_t *end,
+ StringRef path, StringRef tmpPath)
Missing an alignment space.
+ : bufferStart(start), bufferEnd(end) {
+ finalPath.assign(path);
+ tempPath.assign(tmpPath);
+}
+
+
+FileOutputBuffer::~FileOutputBuffer() {
+ // If not already commited, delete buffer and remove temp file.
+ if ( bufferStart != NULL ) {
+ sys::fs::unmap_file_pages((void*)bufferStart, getBufferSize());
+ bool existed;
+ sys::fs::remove(Twine(tempPath), existed);
+ }
+}
+
+
+
+error_code FileOutputBuffer::create(StringRef filePath,
+ size_t size,
+ OwningPtr<FileOutputBuffer> &result,
+ unsigned flags) {
Alignment whitespace and capitalized first character.
+ // If file already exists, it must be a regular file (to be mappable).
+ Twine filePathTwine(filePath);
Twine should not be used like this. It stores references to temporaries. It will
work in this case because filePath is not a temporary and you haven't invoked
any operators. It's also uneeded. StringRef is convertable to Twine.
+ sys::fs::file_status stat;
+ bool writable;
This is only used in the case below, so it should be folded in there.
+ error_code ec = sys::fs::status(filePathTwine, stat);
stat is undefined if ec isn't success. ec will be success even in the case of
file_not_found.
+ switch ( stat.type() ) {
No space after ( and before ). Same lots of other places.
+ case sys::fs::file_type::file_not_found:
+ // If file does not exist, we'll create one.
+ break;
+ case sys::fs::file_type::regular_file:
+ // If file is not currently writable, error out.
+ ec = sys::fs::is_writable_file(filePathTwine, writable);
+ if ( !ec && !writable )
+ return make_error_code(errc::permission_denied);
+ break;
+ default:
+ if ( ec )
+ return ec;
+ else
+ return make_error_code(errc::operation_not_permitted);
+ }
+
+ // Delete target file.
+ bool existed;
+ if ( error_code ec = sys::fs::remove(filePath, existed) )
+ return ec;
+
+ // Create new file of specified size in same directory but with random name.
+ SmallString<128> modelStr;
+ modelStr.assign(filePath);
+ modelStr.append(".tmp%%%%%%%");
+ SmallString<128> tempFilePath;
+ if ( error_code ec = sys::fs::unique_file_sized(Twine(modelStr), size,
+ tempFilePath, false) ) {
This should be: Twine(filePath) + ".tmp%%%%%%%"
+ return ec;
+ }
Unneeded block.
+
+ // If requested, make the output file executable.
+ if ( flags & F_executable )
Tab.
+ sys::fs::set_file_executable(Twine(tempFilePath));
+
+ // Memory map new file.
+ void *base;
+ if ( error_code ec = sys::fs::map_file_pages(Twine(tempFilePath),
+ 0, size, true, base) ) {
+ return ec;
+ }
+
+ // Create FileOutputBuffer object to own mapped range.
+ uint8_t* start = (uint8_t*)base;
* should be right aligned and the case should be a c++ cast.
+ result.reset(new FileOutputBuffer(start, start+size, filePath, tempFilePath));
Spaces around binary operator +.
+
+ return error_code::success();
+}
+
+
+error_code FileOutputBuffer::commit(int64_t newSmallerSize) {
+ // Unmap buffer, letting OS flush dirty pages to file on disk.
+ if ( error_code ec = sys::fs::unmap_file_pages((void*)bufferStart,
c++ cast.
+ getBufferSize()) ) {
+ return ec;
+ }
+
+ // If requested, resize file as part of commit.
+ if ( newSmallerSize != -1 ) {
+ if ( error_code ec = sys::fs::resize_file(Twine(tempPath), newSmallerSize) )
+ return ec;
+ }
+
+ // Rename file to final name.
+ if ( error_code ec = sys::fs::rename(Twine(tempPath), Twine(finalPath)) )
+ return ec;
+
+ return error_code::success();
+}
+
+
+} // namespace
+
Index: lib/Support/CMakeLists.txt
--- lib/Support/CMakeLists.txt (revision 157010)
+++ lib/Support/CMakeLists.txt (working copy)
@@ -23,6 +23,7 @@
Dwarf.cpp
ErrorHandling.cpp
FileUtilities.cpp
+ FileOutputBuffer.cpp
FoldingSet.cpp
FormattedStream.cpp
GraphWriter.cpp
Index: lib/Support/Windows/PathV2.inc
--- lib/Support/Windows/PathV2.inc (revision 157010)
+++ lib/Support/Windows/PathV2.inc (working copy)
@@ -501,7 +501,7 @@
// it currently comes in as a UNIX mode.
error_code unique_file(const Twine &model, int &result_fd,
SmallVectorImpl<char> &result_path,
- bool makeAbsolute, unsigned mode) {
+ bool makeAbsolute, bool private_file) {
// Use result_path as temp storage.
result_path.set_size(0);
StringRef m = model.toStringRef(result_path);
@@ -755,6 +755,43 @@
return error_code::success();
}
+error_code map_file_pages(const Twine &path, off_t file_offset, size_t size,
+ bool map_writable, void *&result) {
+ assert(0 && "NOT IMPLEMENTED");
+}
+
+error_code unmap_file_pages(void *base, size_t size) {
+ assert(0 && "NOT IMPLEMENTED");
+}
+
+error_code is_writable_file(const Twine &path, bool &result) {
+#if 0 // verify code below before enabling:
+ SmallString<128> path_storage;
+ StringRef name = path.toNullTerminatedStringRef(path_storage);
+
+ DWORD attr = GetFileAttributes(name.begin());
+
+ if (attr == INVALID_FILE_ATTRIBUTES)
+ return windows_error(::GetLastError());
+
+ // Files are writable by default, unless marked READONLY
+ result = !(attr & FILE_ATTRIBUTE_READONLY);
+ #endif
This looks right, the only thing it's missing is symlink handling.