readFile reads an entire file into a string, vector<char>, or similar
authorAndrei Alexandrescu <aalexandre@fb.com>
Wed, 15 Jan 2014 18:38:22 +0000 (10:38 -0800)
committerJordan DeLong <jdelong@fb.com>
Sun, 19 Jan 2014 01:39:37 +0000 (17:39 -0800)
Test Plan: unittest

Reviewed By: lucian@fb.com

FB internal diff: D1129497

13 files changed:
folly/File.h
folly/FileUtil.h
folly/String-inl.h
folly/String.h
folly/experimental/FileGen-inl.h
folly/experimental/io/HugePages.cpp
folly/experimental/test/GenBenchmark.cpp
folly/experimental/test/GenTest.cpp
folly/io/test/RecordIOTest.cpp
folly/test/FileUtilTest.cpp
folly/test/MemoryMappingTest.cpp
folly/test/StringTest.cpp
folly/test/SubprocessTest.cpp

index ec4a208579b7dfda778fcb620c9b94855ead1bcc..1c6793fa72346dda229e739c40d34de914a5a88d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
index 6b9325aef06c32d34b24e70d976994b8d981a572..4939803341215fa78816a593ff56e15d3deffb97 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #define FOLLY_FILEUTIL_H_
 
 #include "folly/Portability.h"
+#include "folly/ScopeGuard.h"
 
+#include <cassert>
+#include <limits>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/uio.h>
@@ -102,7 +105,66 @@ ssize_t writevFull(int fd, iovec* iov, int count);
 ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset);
 #endif
 
+/**
+ * Read entire file (if num_bytes is defaulted) or no more than
+ * num_bytes (otherwise) into container *out. The container is assumed
+ * to be contiguous, with element size equal to 1, and offer size(),
+ * reserve(), and random access (e.g. std::vector<char>, std::string,
+ * fbstring).
+ *
+ * Returns: true on success or false on failure. In the latter case
+ * errno will be set appropriately by the failing system primitive.
+ */
+template <class Container>
+bool readFile(const char* file_name, Container& out,
+              size_t num_bytes = std::numeric_limits<size_t>::max()) {
+  static_assert(sizeof(out[0]) == 1,
+                "readFile: only containers with byte-sized elements accepted");
+  assert(file_name);
+
+  const auto fd = open(file_name, O_RDONLY);
+  if (fd == -1) return false;
+
+  size_t soFar = 0; // amount of bytes successfully read
+  SCOPE_EXIT {
+    assert(out.size() >= soFar); // resize better doesn't throw
+    out.resize(soFar);
+    // Ignore errors when closing the file
+    close(fd);
+  };
+
+  // Obtain file size:
+  struct stat buf;
+  if (fstat(fd, &buf) == -1) return false;
+  // Some files (notably under /proc and /sys on Linux) lie about
+  // their size, so treat the size advertised by fstat under advise
+  // but don't rely on it. In particular, if the size is zero, we
+  // should attempt to read stuff. If not zero, we'll attempt to read
+  // one extra byte.
+  constexpr size_t initialAlloc = 1024 * 4;
+  out.resize(
+    std::min(
+      buf.st_size ? buf.st_size + 1 : initialAlloc,
+      num_bytes));
+
+  while (soFar < out.size()) {
+    const auto actual = readFull(fd, &out[soFar], out.size() - soFar);
+    if (actual == -1) {
+      return false;
+    }
+    soFar += actual;
+    if (soFar < out.size()) {
+      // File exhausted
+      break;
+    }
+    // Ew, allocate more memory. Use exponential growth to avoid
+    // quadratic behavior. Cap size to num_bytes.
+    out.resize(std::min(out.size() * 3 / 2, num_bytes));
+  }
+
+  return true;
+}
+
 }  // namespaces
 
 #endif /* FOLLY_FILEUTIL_H_ */
-
index e61ef33217c5655b87fcdff8ae894c23495af197..a2179209e4e240e6d83bf81f6da3550b6a3d342f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -648,4 +648,3 @@ void hexDump(const void* ptr, size_t size, OutIt out) {
 }  // namespace folly
 
 #endif /* FOLLY_STRING_INL_H_ */
-
index da93cd265d106a296e60a651aad59cd1cfb16bfd..31b21b1e8ab837d5e9f6ce5e8506fc215212b539 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
index d28966b76d50f785bb8010e1565360953c0884e1..2825d68eaff5cf7f80610e48627587a84481007b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -135,4 +135,12 @@ inline auto byLine(File file, char delim = '\n')
        | resplit(delim);
 }
 
+/**
+ * Ditto, take the filename and opens it
+ */
+inline auto byLine(const char* fileName, char delim = '\n')
+  -> decltype(byLine(File(fileName))) {
+  return byLine(File(fileName), delim);
+}
+
 }}  // !folly::gen
index 041dfa2d15d7b31141c73301752805c1de8c9237..21426fdc16cafe4258473dfab238f7d703930250 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -360,4 +360,3 @@ HugePages::File HugePages::create(ByteRange data,
 }
 
 }  // namespace folly
-
index 2b03ca0f600dc661e53220c7c142b053478c799d..52f3e0a22c8b09cbb333fa2dd6dfcf5f6dbd0284 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -598,7 +598,7 @@ BENCHMARK(ByLine_Pipes, iters) {
     PCHECK(::read(rfd, &buf, 1) == 1);  // wait for startup
   }
 
-  auto s = byLine(rfd) | eachTo<int64_t>() | sum;
+  auto s = byLine(File(rfd)) | eachTo<int64_t>() | sum;
   folly::doNotOptimizeAway(s);
 
   BENCHMARK_SUSPEND {
index 499ab0526ed92ced78b024a95ce671fb858e2d36..35c1414b37e134149df23e5a85775d1ed92e1da7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1349,7 +1349,7 @@ TEST_P(FileGenBufferedTest, FileWriter) {
   auto collect = eachTo<std::string>() | as<vector>();
   auto expected = src | resplit('\n') | collect;
 
-  src | eachAs<StringPiece>() | toFile(file.fd(), bufferSize);
+  src | eachAs<StringPiece>() | toFile(File(file.fd()), bufferSize);
   auto found = byLine(file.path().c_str()) | collect;
 
   EXPECT_TRUE(expected == found);
index 4c12f8176213ee37e224e8cefe886e3b2f39d5c5..2ffbb976510e64324340d7ad66f4da68d92d322c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -54,12 +54,12 @@ std::unique_ptr<IOBuf> iobufs(std::initializer_list<T> ranges) {
 TEST(RecordIOTest, Simple) {
   TemporaryFile file;
   {
-    RecordIOWriter writer(file.fd());
+    RecordIOWriter writer(File(file.fd()));
     writer.write(iobufs({"hello ", "world"}));
     writer.write(iobufs({"goodbye"}));
   }
   {
-    RecordIOReader reader(file.fd());
+    RecordIOReader reader(File(file.fd()));
     auto it = reader.begin();
     ASSERT_FALSE(it == reader.end());
     EXPECT_EQ("hello world", sp((it++)->first));
@@ -68,12 +68,12 @@ TEST(RecordIOTest, Simple) {
     EXPECT_TRUE(it == reader.end());
   }
   {
-    RecordIOWriter writer(file.fd());
+    RecordIOWriter writer(File(file.fd()));
     writer.write(iobufs({"meow"}));
     writer.write(iobufs({"woof"}));
   }
   {
-    RecordIOReader reader(file.fd());
+    RecordIOReader reader(File(file.fd()));
     auto it = reader.begin();
     ASSERT_FALSE(it == reader.end());
     EXPECT_EQ("hello world", sp((it++)->first));
@@ -93,13 +93,13 @@ TEST(RecordIOTest, SmallRecords) {
   memset(tmp, 'x', kSize);
   TemporaryFile file;
   {
-    RecordIOWriter writer(file.fd());
+    RecordIOWriter writer(File(file.fd()));
     for (int i = 0; i < kSize; ++i) {  // record of size 0 should be ignored
       writer.write(IOBuf::wrapBuffer(tmp, i));
     }
   }
   {
-    RecordIOReader reader(file.fd());
+    RecordIOReader reader(File(file.fd()));
     auto it = reader.begin();
     for (int i = 1; i < kSize; ++i) {
       ASSERT_FALSE(it == reader.end());
@@ -112,19 +112,19 @@ TEST(RecordIOTest, SmallRecords) {
 TEST(RecordIOTest, MultipleFileIds) {
   TemporaryFile file;
   {
-    RecordIOWriter writer(file.fd(), 1);
+    RecordIOWriter writer(File(file.fd()), 1);
     writer.write(iobufs({"hello"}));
   }
   {
-    RecordIOWriter writer(file.fd(), 2);
+    RecordIOWriter writer(File(file.fd()), 2);
     writer.write(iobufs({"world"}));
   }
   {
-    RecordIOWriter writer(file.fd(), 1);
+    RecordIOWriter writer(File(file.fd()), 1);
     writer.write(iobufs({"goodbye"}));
   }
   {
-    RecordIOReader reader(file.fd(), 0);  // return all
+    RecordIOReader reader(File(file.fd()), 0);  // return all
     auto it = reader.begin();
     ASSERT_FALSE(it == reader.end());
     EXPECT_EQ("hello", sp((it++)->first));
@@ -135,7 +135,7 @@ TEST(RecordIOTest, MultipleFileIds) {
     EXPECT_TRUE(it == reader.end());
   }
   {
-    RecordIOReader reader(file.fd(), 1);
+    RecordIOReader reader(File(file.fd()), 1);
     auto it = reader.begin();
     ASSERT_FALSE(it == reader.end());
     EXPECT_EQ("hello", sp((it++)->first));
@@ -144,14 +144,14 @@ TEST(RecordIOTest, MultipleFileIds) {
     EXPECT_TRUE(it == reader.end());
   }
   {
-    RecordIOReader reader(file.fd(), 2);
+    RecordIOReader reader(File(file.fd()), 2);
     auto it = reader.begin();
     ASSERT_FALSE(it == reader.end());
     EXPECT_EQ("world", sp((it++)->first));
     EXPECT_TRUE(it == reader.end());
   }
   {
-    RecordIOReader reader(file.fd(), 3);
+    RecordIOReader reader(File(file.fd()), 3);
     auto it = reader.begin();
     EXPECT_TRUE(it == reader.end());
   }
@@ -160,7 +160,7 @@ TEST(RecordIOTest, MultipleFileIds) {
 TEST(RecordIOTest, ExtraMagic) {
   TemporaryFile file;
   {
-    RecordIOWriter writer(file.fd());
+    RecordIOWriter writer(File(file.fd()));
     writer.write(iobufs({"hello"}));
   }
   uint8_t buf[recordio_helpers::headerSize() + 5];
@@ -172,7 +172,7 @@ TEST(RecordIOTest, ExtraMagic) {
   // and an extra record
   EXPECT_EQ(sizeof(buf), write(file.fd(), buf, sizeof(buf)));
   {
-    RecordIOReader reader(file.fd());
+    RecordIOReader reader(File(file.fd()));
     auto it = reader.begin();
     ASSERT_FALSE(it == reader.end());
     EXPECT_EQ("hello", sp((it++)->first));
@@ -213,7 +213,7 @@ TEST(RecordIOTest, Randomized) {
   // Recreate the writer multiple times so we test that we create a
   // continuous stream
   for (size_t i = 0; i < 3; ++i) {
-    RecordIOWriter writer(file.fd());
+    RecordIOWriter writer(File(file.fd()));
     for (size_t j = 0; j < recordCount; ++j) {
       off_t beginPos = writer.filePos();
       record.clear();
@@ -251,7 +251,7 @@ TEST(RecordIOTest, Randomized) {
 
   {
     size_t i = 0;
-    RecordIOReader reader(file.fd());
+    RecordIOReader reader(File(file.fd()));
     for (auto& r : reader) {
       SCOPED_TRACE(i);
       ASSERT_LT(i, records.size());
@@ -270,4 +270,3 @@ int main(int argc, char *argv[]) {
   google::ParseCommandLineFlags(&argc, &argv, true);
   return RUN_ALL_TESTS();
 }
-
index f466f11fbba0b1e78aee6d175b47bb77b905ae92..d40be3d5b43002093026d4021f161d4fcd91d5e8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
 namespace folly { namespace test {
 
 using namespace fileutil_detail;
+using namespace std;
 
 namespace {
 
@@ -238,6 +239,49 @@ TEST_F(FileUtilTest, preadv) {
 }
 #endif
 
+TEST(String, readFile) {
+  srand(time(nullptr));
+  const string tmpPrefix = to<string>("/tmp/folly-file-util-test-",
+                                      getpid(), "-", rand(), "-");
+  const string afile = tmpPrefix + "myfile";
+  const string emptyFile = tmpPrefix + "myfile2";
+
+  SCOPE_EXIT {
+    unlink(afile.c_str());
+    unlink(emptyFile.c_str());
+  };
+
+  auto f = fopen(emptyFile.c_str(), "wb");
+  EXPECT_NE(nullptr, f);
+  EXPECT_EQ(0, fclose(f));
+  f = fopen(afile.c_str(), "wb");
+  EXPECT_NE(nullptr, f);
+  EXPECT_EQ(3, fwrite("bar", 1, 3, f));
+  EXPECT_EQ(0, fclose(f));
+
+  {
+    string contents;
+    EXPECT_TRUE(readFile(emptyFile.c_str(), contents));
+    EXPECT_EQ(contents, "");
+    EXPECT_TRUE(readFile(afile.c_str(), contents, 0));
+    EXPECT_EQ("", contents);
+    EXPECT_TRUE(readFile(afile.c_str(), contents, 2));
+    EXPECT_EQ("ba", contents);
+    EXPECT_TRUE(readFile(afile.c_str(), contents));
+    EXPECT_EQ("bar", contents);
+  }
+  {
+    vector<unsigned char> contents;
+    EXPECT_TRUE(readFile(emptyFile.c_str(), contents));
+    EXPECT_EQ(vector<unsigned char>(), contents);
+    EXPECT_TRUE(readFile(afile.c_str(), contents, 0));
+    EXPECT_EQ(vector<unsigned char>(), contents);
+    EXPECT_TRUE(readFile(afile.c_str(), contents, 2));
+    EXPECT_EQ(vector<unsigned char>({'b', 'a'}), contents);
+    EXPECT_TRUE(readFile(afile.c_str(), contents));
+    EXPECT_EQ(vector<unsigned char>({'b', 'a', 'r'}), contents);
+  }
+}
 
 }}  // namespaces
 
@@ -246,4 +290,3 @@ int main(int argc, char *argv[]) {
   google::ParseCommandLineFlags(&argc, &argv, true);
   return RUN_ALL_TESTS();
 }
-
index 61dceb2af1b1cda4b12a409f1a1ce6c1ff227da3..c111073a716ec3ff6040da349f94f150a4edd674 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,16 +23,16 @@ namespace folly {
 TEST(MemoryMapping, Basic) {
   File f = File::temporary();
   {
-    WritableMemoryMapping m(f.fd(), 0, sizeof(double));
+    WritableMemoryMapping m(File(f.fd()), 0, sizeof(double));
     double volatile* d = m.asWritableRange<double>().data();
     *d = 37 * M_PI;
   }
   {
-    MemoryMapping m(f.fd(), 0, 3);
+    MemoryMapping m(File(f.fd()), 0, 3);
     EXPECT_EQ(0, m.asRange<int>().size()); //not big enough
   }
   {
-    MemoryMapping m(f.fd(), 0, sizeof(double));
+    MemoryMapping m(File(f.fd()), 0, sizeof(double));
     const double volatile* d = m.asRange<double>().data();
     EXPECT_EQ(*d, 37 * M_PI);
   }
@@ -41,8 +41,8 @@ TEST(MemoryMapping, Basic) {
 TEST(MemoryMapping, DoublyMapped) {
   File f = File::temporary();
   // two mappings of the same memory, different addresses.
-  WritableMemoryMapping mw(f.fd(), 0, sizeof(double));
-  MemoryMapping mr(f.fd(), 0, sizeof(double));
+  WritableMemoryMapping mw(File(f.fd()), 0, sizeof(double));
+  MemoryMapping mr(File(f.fd()), 0, sizeof(double));
 
   double volatile* dw = mw.asWritableRange<double>().data();
   const double volatile* dr = mr.asRange<double>().data();
@@ -84,11 +84,11 @@ TEST(MemoryMapping, Simple) {
   writeStringToFileOrDie("hello", f.fd());
 
   {
-    MemoryMapping m(f.fd());
+    MemoryMapping m(File(f.fd()));
     EXPECT_EQ("hello", m.data());
   }
   {
-    MemoryMapping m(f.fd(), 1, 2);
+    MemoryMapping m(File(f.fd()), 1, 2);
     EXPECT_EQ("el", m.data());
   }
 }
@@ -105,20 +105,20 @@ TEST(MemoryMapping, LargeFile) {
   writeStringToFileOrDie(fileData, f.fd());
 
   {
-    MemoryMapping m(f.fd());
+    MemoryMapping m(File(f.fd()));
     EXPECT_EQ(fileData, m.data());
   }
   {
     size_t size = sysconf(_SC_PAGESIZE) * 2;
     StringPiece s(fileData.data() + 9, size - 9);
-    MemoryMapping m(f.fd(), 9, size - 9);
+    MemoryMapping m(File(f.fd()), 9, size - 9);
     EXPECT_EQ(s.toString(), m.data());
   }
 }
 
 TEST(MemoryMapping, ZeroLength) {
   File f = File::temporary();
-  MemoryMapping m(f.fd());
+  MemoryMapping m(File(f.fd()));
   EXPECT_TRUE(m.mlock(MemoryMapping::LockMode::MUST_LOCK));
   EXPECT_TRUE(m.mlocked());
   EXPECT_EQ(0, m.data().size());
index 92f6ba97d76844e60850ec1f6e7240ce78b2a00d..925af1fe1de7ff6e68da533cbc06f9092cbe1939 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1028,4 +1028,3 @@ int main(int argc, char *argv[]) {
   }
   return ret;
 }
-
index 769eec572c1ae48c275f59efbc8dfefbefc3ed84..ab4c4b24f7afed5227ed18a38275ec4d498e1e9b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2014 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -181,7 +181,7 @@ TEST(ParentDeathSubprocessTest, ParentDeathSignal) {
 TEST(PopenSubprocessTest, PopenRead) {
   Subprocess proc("ls /", Subprocess::pipeStdout());
   int found = 0;
-  gen::byLine(proc.stdout()) |
+  gen::byLine(File(proc.stdout())) |
     [&] (StringPiece line) {
       if (line == "etc" || line == "bin" || line == "usr") {
         ++found;