[llvm-commits] [llvm] r161099 - in /llvm/trunk: include/llvm/Support/FileOutputBuffer.h lib/Support/CMakeLists.txt lib/Support/FileOutputBuffer.cpp unittests/Support/CMakeLists.txt unittests/Support/FileOutputBufferTest.cpp

Nick Kledzik kledzik at apple.com
Tue Jul 31 19:29:50 PDT 2012


Author: kledzik
Date: Tue Jul 31 21:29:50 2012
New Revision: 161099

URL: http://llvm.org/viewvc/llvm-project?rev=161099&view=rev
Log:
Initial commit of new FileOutputBuffer support class.  
Since the llvm::sys::fs::map_file_pages() support function it relies on
is not yet implemented on Windows, the unit tests for FileOutputBuffer 
are currently conditionalized to run only on unix.

Added:
    llvm/trunk/include/llvm/Support/FileOutputBuffer.h
    llvm/trunk/lib/Support/FileOutputBuffer.cpp
    llvm/trunk/unittests/Support/FileOutputBufferTest.cpp
Modified:
    llvm/trunk/lib/Support/CMakeLists.txt
    llvm/trunk/unittests/Support/CMakeLists.txt

Added: llvm/trunk/include/llvm/Support/FileOutputBuffer.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Support/FileOutputBuffer.h?rev=161099&view=auto
==============================================================================
--- llvm/trunk/include/llvm/Support/FileOutputBuffer.h (added)
+++ llvm/trunk/include/llvm/Support/FileOutputBuffer.h Tue Jul 31 21:29:50 2012
@@ -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/ADT/SmallString.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/DataTypes.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);
+  
+
+  /// 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.
+  error_code commit(int64_t NewSmallerSize = -1);
+  
+  /// 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.
+  ~FileOutputBuffer();
+
+  
+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;
+};
+
+
+
+} // end namespace llvm
+
+#endif

Modified: llvm/trunk/lib/Support/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Support/CMakeLists.txt?rev=161099&r1=161098&r2=161099&view=diff
==============================================================================
--- llvm/trunk/lib/Support/CMakeLists.txt (original)
+++ llvm/trunk/lib/Support/CMakeLists.txt Tue Jul 31 21:29:50 2012
@@ -23,6 +23,7 @@
   Dwarf.cpp
   ErrorHandling.cpp
   FileUtilities.cpp
+  FileOutputBuffer.cpp
   FoldingSet.cpp
   FormattedStream.cpp
   GraphWriter.cpp

Added: llvm/trunk/lib/Support/FileOutputBuffer.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Support/FileOutputBuffer.cpp?rev=161099&view=auto
==============================================================================
--- llvm/trunk/lib/Support/FileOutputBuffer.cpp (added)
+++ llvm/trunk/lib/Support/FileOutputBuffer.cpp Tue Jul 31 21:29:50 2012
@@ -0,0 +1,148 @@
+//===- 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/ADT/OwningPtr.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/system_error.h"
+
+
+namespace llvm {
+
+
+FileOutputBuffer::FileOutputBuffer(uint8_t *Start, uint8_t *End, 
+                                  StringRef Path, StringRef TmpPath)
+  : 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) {
+  // If file already exists, it must be a regular file (to be mappable).
+  sys::fs::file_status Stat;
+  error_code EC = sys::fs::status(FilePath, Stat);
+  switch (Stat.type()) {
+    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.
+        // FIXME: There is no sys::fs:: api for checking this.
+        // FIXME: In posix, you use the access() call to check this.
+      }
+      break;
+    default:
+      if (EC)
+        return EC;
+      else
+        return make_error_code(errc::operation_not_permitted);
+  }
+
+  // Delete target file.
+  bool Existed;
+  EC = sys::fs::remove(FilePath, Existed);
+  if (EC)
+    return EC;
+  
+  // Create new file in same directory but with random name.
+  SmallString<128> TempFilePath;
+  int FD;
+  EC = sys::fs::unique_file(Twine(FilePath) + ".tmp%%%%%%%",  
+                                                FD, TempFilePath, false, 0644);
+  if (EC)
+    return EC;
+  
+  // The unique_file() interface leaks lower layers and returns a file 
+  // descriptor.  There is no way to directly close it, so use this hack
+  // to hand it off to raw_fd_ostream to close for us.
+  {
+    raw_fd_ostream Dummy(FD, /*shouldClose=*/true);
+  }
+  
+  // Resize file to requested initial size
+  EC = sys::fs::resize_file(Twine(TempFilePath), Size);
+  if (EC)
+    return EC;
+  
+  // If requested, make the output file executable.
+  if ( Flags & F_executable ) {
+    sys::fs::file_status Stat2;
+    EC = sys::fs::status(Twine(TempFilePath), Stat2);
+    if (EC)
+      return EC;
+    
+    sys::fs::perms new_perms = Stat2.permissions();
+    if ( new_perms & sys::fs::owner_read )
+      new_perms |= sys::fs::owner_exe;
+    if ( new_perms & sys::fs::group_read )
+      new_perms |= sys::fs::group_exe;
+    if ( new_perms & sys::fs::others_read )
+      new_perms |= sys::fs::others_exe;
+    new_perms |= sys::fs::add_perms;
+    EC = sys::fs::permissions(Twine(TempFilePath), new_perms);
+    if (EC)
+      return EC;
+  }
+
+  // Memory map new file.
+  void *Base;
+  EC = sys::fs::map_file_pages(Twine(TempFilePath), 0, Size, true, Base);
+  if (EC)
+    return EC;
+  
+  // Create FileOutputBuffer object to own mapped range.
+  uint8_t *Start = reinterpret_cast<uint8_t*>(Base);
+  Result.reset(new FileOutputBuffer(Start, Start+Size, FilePath, TempFilePath));
+                     
+  return error_code::success();
+}                    
+
+
+error_code FileOutputBuffer::commit(int64_t NewSmallerSize) {
+  // Unmap buffer, letting OS flush dirty pages to file on disk.
+  void *Start = reinterpret_cast<void*>(BufferStart);
+  error_code EC = sys::fs::unmap_file_pages(Start, getBufferSize());
+  if (EC)
+    return EC;
+  
+  // If requested, resize file as part of commit.
+  if ( NewSmallerSize != -1 ) {
+    EC = sys::fs::resize_file(Twine(TempPath), NewSmallerSize);
+    if (EC)
+      return EC;
+  }
+  
+  // Rename file to final name.
+  return sys::fs::rename(Twine(TempPath), Twine(FinalPath));
+}
+
+
+} // namespace
+

Modified: llvm/trunk/unittests/Support/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/Support/CMakeLists.txt?rev=161099&r1=161098&r2=161099&view=diff
==============================================================================
--- llvm/trunk/unittests/Support/CMakeLists.txt (original)
+++ llvm/trunk/unittests/Support/CMakeLists.txt Tue Jul 31 21:29:50 2012
@@ -12,6 +12,7 @@
   ConstantRangeTest.cpp
   DataExtractorTest.cpp
   EndianTest.cpp
+  FileOutputBufferTest.cpp
   IntegersSubsetTest.cpp
   LeakDetectorTest.cpp
   ManagedStatic.cpp

Added: llvm/trunk/unittests/Support/FileOutputBufferTest.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/Support/FileOutputBufferTest.cpp?rev=161099&view=auto
==============================================================================
--- llvm/trunk/unittests/Support/FileOutputBufferTest.cpp (added)
+++ llvm/trunk/unittests/Support/FileOutputBufferTest.cpp Tue Jul 31 21:29:50 2012
@@ -0,0 +1,137 @@
+//===- 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/ADT/OwningPtr.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/FileOutputBuffer.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/PathV2.h"
+#include "llvm/Support/raw_ostream.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 {
+
+
+// NOTE: Temporarily run this test on unix only.  Once the file mapping
+// routines are ported to Windows, this conditional can be removed.
+#if LLVM_ON_UNIX
+
+
+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.
+  fs::file_status Status;
+  ASSERT_NO_ERROR(fs::status(Twine(File4), Status));
+  bool IsExecutable = (Status.permissions() & fs::owner_exe);
+  EXPECT_TRUE(IsExecutable);
+
+  // Clean up.
+  uint32_t RemovedCount;
+  ASSERT_NO_ERROR(fs::remove_all(TestDirectory.str(), RemovedCount));
+}
+
+#endif // LLVM_ON_UNIX
+
+} // anonymous namespace





More information about the llvm-commits mailing list