proxygen
FileUtilTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2013-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <folly/FileUtil.h>
20 
21 #include <deque>
22 #if defined(__linux__)
23 #include <dlfcn.h>
24 #endif
25 
26 #include <glog/logging.h>
27 
28 #include <folly/Exception.h>
29 #include <folly/File.h>
30 #include <folly/Range.h>
31 #include <folly/String.h>
33 
34 namespace folly {
35 namespace test {
36 
37 using namespace fileutil_detail;
38 using namespace std;
39 
40 namespace {
41 
42 class Reader {
43  public:
44  Reader(off_t offset, StringPiece data, std::deque<ssize_t> spec);
45 
46  // write-like
47  ssize_t operator()(int fd, void* buf, size_t count);
48 
49  // pwrite-like
50  ssize_t operator()(int fd, void* buf, size_t count, off_t offset);
51 
52  // writev-like
53  ssize_t operator()(int fd, const iovec* iov, int count);
54 
55  // pwritev-like
56  ssize_t operator()(int fd, const iovec* iov, int count, off_t offset);
57 
58  const std::deque<ssize_t> spec() const {
59  return spec_;
60  }
61 
62  private:
63  ssize_t nextSize();
64 
65  off_t offset_;
67  std::deque<ssize_t> spec_;
68 };
69 
70 Reader::Reader(off_t offset, StringPiece data, std::deque<ssize_t> spec)
71  : offset_(offset), data_(data), spec_(std::move(spec)) {}
72 
73 ssize_t Reader::nextSize() {
74  if (spec_.empty()) {
75  throw std::runtime_error("spec empty");
76  }
77  ssize_t n = spec_.front();
78  spec_.pop_front();
79  if (n <= 0) {
80  if (n == -1) {
81  errno = EIO;
82  }
83  spec_.clear(); // so we fail if called again
84  } else {
85  offset_ += n;
86  }
87  return n;
88 }
89 
90 ssize_t Reader::operator()(int /* fd */, void* buf, size_t count) {
91  ssize_t n = nextSize();
92  if (n <= 0) {
93  return n;
94  }
95  if (size_t(n) > count) {
96  throw std::runtime_error("requested count too small");
97  }
98  memcpy(buf, data_.data(), n);
99  data_.advance(n);
100  return n;
101 }
102 
103 ssize_t Reader::operator()(int fd, void* buf, size_t count, off_t offset) {
104  EXPECT_EQ(offset_, offset);
105  return operator()(fd, buf, count);
106 }
107 
108 ssize_t Reader::operator()(int /* fd */, const iovec* iov, int count) {
109  ssize_t n = nextSize();
110  if (n <= 0) {
111  return n;
112  }
113  ssize_t remaining = n;
114  for (; count != 0 && remaining != 0; ++iov, --count) {
115  ssize_t len = std::min(remaining, ssize_t(iov->iov_len));
116  memcpy(iov->iov_base, data_.data(), len);
117  data_.advance(len);
118  remaining -= len;
119  }
120  if (remaining != 0) {
121  throw std::runtime_error("requested total size too small");
122  }
123  return n;
124 }
125 
126 ssize_t Reader::operator()(int fd, const iovec* iov, int count, off_t offset) {
127  EXPECT_EQ(offset_, offset);
128  return operator()(fd, iov, count);
129 }
130 
131 } // namespace
132 
134  protected:
135  FileUtilTest();
136 
137  Reader reader(std::deque<ssize_t> spec);
138 
140  std::vector<std::pair<size_t, Reader>> readers_;
141 };
142 
143 FileUtilTest::FileUtilTest()
144  : in_("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
145  CHECK_EQ(62, in_.size());
146 
147  readers_.emplace_back(0, reader({0}));
148  readers_.emplace_back(62, reader({62}));
149  readers_.emplace_back(62, reader({62, -1})); // error after end (not called)
150  readers_.emplace_back(61, reader({61, 0}));
151  readers_.emplace_back(-1, reader({61, -1})); // error before end
152  readers_.emplace_back(62, reader({31, 31}));
153  readers_.emplace_back(62, reader({1, 10, 20, 10, 1, 20}));
154  readers_.emplace_back(61, reader({1, 10, 20, 10, 20, 0}));
155  readers_.emplace_back(41, reader({1, 10, 20, 10, 0}));
156  readers_.emplace_back(-1, reader({1, 10, 20, 10, 20, -1}));
157 }
158 
159 Reader FileUtilTest::reader(std::deque<ssize_t> spec) {
160  return Reader(42, in_, std::move(spec));
161 }
162 
164  for (auto& p : readers_) {
165  std::string out(in_.size(), '\0');
166  EXPECT_EQ(p.first, wrapFull(p.second, 0, &out[0], out.size()));
167  if (p.first != (decltype(p.first))(-1)) {
168  EXPECT_EQ(in_.substr(0, p.first), out.substr(0, p.first));
169  }
170  }
171 }
172 
174  for (auto& p : readers_) {
175  std::string out(in_.size(), '\0');
176  EXPECT_EQ(p.first, wrapFull(p.second, 0, &out[0], out.size(), off_t(42)));
177  if (p.first != (decltype(p.first))(-1)) {
178  EXPECT_EQ(in_.substr(0, p.first), out.substr(0, p.first));
179  }
180  }
181 }
182 
184  public:
185  explicit IovecBuffers(std::initializer_list<size_t> sizes);
186  explicit IovecBuffers(std::vector<size_t> sizes);
187 
188  std::vector<iovec> iov() const {
189  return iov_;
190  } // yes, make a copy
191  std::string join() const {
192  return folly::join("", buffers_);
193  }
194  size_t size() const;
195 
196  private:
197  std::vector<std::string> buffers_;
198  std::vector<iovec> iov_;
199 };
200 
201 IovecBuffers::IovecBuffers(std::initializer_list<size_t> sizes) {
202  iov_.reserve(sizes.size());
203  for (auto& s : sizes) {
204  buffers_.push_back(std::string(s, '\0'));
205  }
206  for (auto& b : buffers_) {
207  iovec iov;
208  iov.iov_base = &b[0];
209  iov.iov_len = b.size();
210  iov_.push_back(iov);
211  }
212 }
213 
214 IovecBuffers::IovecBuffers(std::vector<size_t> sizes) {
215  iov_.reserve(sizes.size());
216  for (auto s : sizes) {
217  buffers_.push_back(std::string(s, '\0'));
218  }
219  for (auto& b : buffers_) {
220  iovec iov;
221  iov.iov_base = &b[0];
222  iov.iov_len = b.size();
223  iov_.push_back(iov);
224  }
225 }
226 
227 size_t IovecBuffers::size() const {
228  size_t s = 0;
229  for (auto& b : buffers_) {
230  s += b.size();
231  }
232  return s;
233 }
234 
236  for (auto& p : readers_) {
237  IovecBuffers buf({12, 19, 31});
238  ASSERT_EQ(62, buf.size());
239 
240  auto iov = buf.iov();
241  EXPECT_EQ(p.first, wrapvFull(p.second, 0, iov.data(), iov.size()));
242  if (p.first != (decltype(p.first))(-1)) {
243  EXPECT_EQ(in_.substr(0, p.first), buf.join().substr(0, p.first));
244  }
245  }
246 }
247 
248 TEST(FileUtilTest2, wrapv) {
249  TemporaryFile tempFile("file-util-test");
250  std::vector<size_t> sizes;
251  size_t sum = 0;
252  for (int32_t i = 0; i < 1500; ++i) {
253  sizes.push_back(i % 3 + 1);
254  sum += sizes.back();
255  }
256  IovecBuffers buf(sizes);
257  ASSERT_EQ(sum, buf.size());
258  auto iov = buf.iov();
259  EXPECT_EQ(sum, wrapvFull(writev, tempFile.fd(), iov.data(), iov.size()));
260 }
261 
263  for (auto& p : readers_) {
264  IovecBuffers buf({12, 19, 31});
265  ASSERT_EQ(62, buf.size());
266 
267  auto iov = buf.iov();
268  EXPECT_EQ(
269  p.first, wrapvFull(p.second, 0, iov.data(), iov.size(), off_t(42)));
270  if (p.first != (decltype(p.first))(-1)) {
271  EXPECT_EQ(in_.substr(0, p.first), buf.join().substr(0, p.first));
272  }
273  }
274 }
275 
276 TEST(String, readFile) {
277  const TemporaryFile afileTemp, emptyFileTemp;
278  auto afile = afileTemp.path().string();
279  auto emptyFile = emptyFileTemp.path().string();
280 
281  EXPECT_TRUE(writeFile(string(), emptyFile.c_str()));
282  EXPECT_TRUE(writeFile(StringPiece("bar"), afile.c_str()));
283 
284  {
285  string contents;
286  EXPECT_TRUE(readFile(emptyFile.c_str(), contents));
287  EXPECT_EQ(contents, "");
288  EXPECT_TRUE(readFile(afile.c_str(), contents, 0));
289  EXPECT_EQ("", contents);
290  EXPECT_TRUE(readFile(afile.c_str(), contents, 2));
291  EXPECT_EQ("ba", contents);
292  EXPECT_TRUE(readFile(afile.c_str(), contents));
293  EXPECT_EQ("bar", contents);
294  }
295  {
296  vector<unsigned char> contents;
297  EXPECT_TRUE(readFile(emptyFile.c_str(), contents));
298  EXPECT_EQ(vector<unsigned char>(), contents);
299  EXPECT_TRUE(readFile(afile.c_str(), contents, 0));
300  EXPECT_EQ(vector<unsigned char>(), contents);
301  EXPECT_TRUE(readFile(afile.c_str(), contents, 2));
302  EXPECT_EQ(vector<unsigned char>({'b', 'a'}), contents);
303  EXPECT_TRUE(readFile(afile.c_str(), contents));
304  EXPECT_EQ(vector<unsigned char>({'b', 'a', 'r'}), contents);
305  }
306 }
307 
308 class ReadFileFd : public ::testing::Test {
309  protected:
310  void SetUp() override {
311  ASSERT_TRUE(writeFile(StringPiece("bar"), aFile.path().string().c_str()));
312  }
313 
315 };
316 
317 TEST_F(ReadFileFd, ReadZeroBytes) {
318  std::string contents;
319  EXPECT_TRUE(readFile(aFile.fd(), contents, 0));
320  EXPECT_EQ("", contents);
321 }
322 
323 TEST_F(ReadFileFd, ReadPartial) {
324  std::string contents;
325  EXPECT_TRUE(readFile(aFile.fd(), contents, 2));
326  EXPECT_EQ("ba", contents);
327 }
328 
329 TEST_F(ReadFileFd, ReadFull) {
330  std::string contents;
331  EXPECT_TRUE(readFile(aFile.fd(), contents));
332  EXPECT_EQ("bar", contents);
333 }
334 
335 TEST_F(ReadFileFd, WriteOnlyFd) {
336  File f(aFile.path().string(), O_WRONLY);
337  std::string contents;
338  EXPECT_FALSE(readFile(f.fd(), contents));
339  PLOG(INFO);
340 }
341 
342 TEST_F(ReadFileFd, InvalidFd) {
343  File f(aFile.path().string());
344  f.close();
345  std::string contents;
347  [&] { EXPECT_FALSE(readFile(f.fd(), contents)); });
348  PLOG(INFO);
349 }
350 
352  protected:
354 
355  std::set<std::string> listTmpDir() const {
356  std::set<std::string> entries;
357  for (auto& entry : fs::directory_iterator(tmpDir_.path())) {
358  entries.insert(entry.path().filename().string());
359  }
360  return entries;
361  }
362 
363  std::string readData(const string& path) const {
364  string data;
365  if (!readFile(path.c_str(), data)) {
366  throwSystemError("failed to read ", path);
367  }
368  return data;
369  }
370 
371  struct stat statFile(const string& path) const {
372  struct stat s;
373  auto rc = stat(path.c_str(), &s);
374  checkUnixError(rc, "failed to stat() ", path);
375  return s;
376  }
377 
378  mode_t getPerms(const string& path) {
379  return (statFile(path).st_mode & 0777);
380  }
381 
383  return tmpDir_.path().string() + "/" + name.str();
384  }
385 
386  void setDirPerms(mode_t mode) {
387  auto rc = chmod(tmpDir_.path().string().c_str(), mode);
388  checkUnixError(rc, "failed to set permissions on tmp dir");
389  }
390 
391  TemporaryDirectory tmpDir_{"folly_file_test"};
392 };
393 
395  // Call writeFileAtomic() to create a new file
396  auto path = tmpPath("foo");
397  auto contents = StringPiece{"contents\n"};
398  writeFileAtomic(path, contents);
399 
400  // The directory should contain exactly 1 file now, with the correct contents
401  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
402  EXPECT_EQ(contents, readData(path));
403  EXPECT_EQ(0644, getPerms(path));
404 }
405 
406 TEST_F(WriteFileAtomic, overwrite) {
407  // Call writeFileAtomic() to create a new file
408  auto path = tmpPath("foo");
409  auto contents1 = StringPiece{"contents\n"};
410  writeFileAtomic(path, contents1);
411 
412  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
413  EXPECT_EQ(contents1, readData(path));
414  EXPECT_EQ(0644, getPerms(path));
415 
416  // Now overwrite the file with different contents
417  auto contents2 = StringPiece{"testing"};
418  writeFileAtomic(path, contents2);
419  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
420  EXPECT_EQ(contents2, readData(path));
421  EXPECT_EQ(0644, getPerms(path));
422 
423  // Test overwriting with relatively large contents, and different permissions
424  auto contents3 =
425  "asdf" + string(10240, '\n') + "foobar\n" + string(10240, 'b') + "\n";
426  writeFileAtomic(path, contents3, 0444);
427  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
428  EXPECT_EQ(contents3, readData(path));
429  EXPECT_EQ(0444, getPerms(path));
430 
431  // Test overwriting with empty contents
432  //
433  // Note that the file's permissions are 0444 at this point (read-only),
434  // but we writeFileAtomic() should still replace it successfully. Since we
435  // update it with a rename we need write permissions on the parent directory,
436  // but not the destination file.
437  auto contents4 = StringPiece("");
438  writeFileAtomic(path, contents4, 0400);
439  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
440  EXPECT_EQ(contents4, readData(path));
441  EXPECT_EQ(0400, getPerms(path));
442 }
443 
444 TEST_F(WriteFileAtomic, directoryPermissions) {
445  // Test writeFileAtomic() when we do not have write permission in the target
446  // directory.
447  //
448  // Make the test directory read-only
449  setDirPerms(0555);
450  SCOPE_EXIT {
451  // Restore directory permissions before we exit, just to ensure the code
452  // will be able to clean up the directory.
453  try {
454  setDirPerms(0755);
455  } catch (const std::exception&) {
456  // Intentionally ignore errors here, in case an exception is already
457  // being thrown.
458  }
459  };
460 
461  // writeFileAtomic() should fail, and the directory should still be empty
462  auto path1 = tmpPath("foo");
463  auto contents = StringPiece("testing");
464  EXPECT_THROW(writeFileAtomic(path1, contents), std::system_error);
465  EXPECT_EQ(set<string>{}, listTmpDir());
466 
467  // Make the directory writable again, then create the file
468  setDirPerms(0755);
469  writeFileAtomic(path1, contents, 0400);
470  EXPECT_EQ(contents, readData(path1));
471  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
472 
473  // Make the directory read-only again
474  // Creating another file now should fail and we should still have only the
475  // first file.
476  setDirPerms(0555);
477  EXPECT_THROW(
478  writeFileAtomic(tmpPath("another_file.txt"), "x\n"), std::system_error);
479  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
480 }
481 
482 TEST_F(WriteFileAtomic, multipleFiles) {
483  // Test creating multiple files in the same directory
484  writeFileAtomic(tmpPath("foo.txt"), "foo");
485  writeFileAtomic(tmpPath("bar.txt"), "bar", 0400);
486  writeFileAtomic(tmpPath("foo_txt"), "underscore", 0440);
487  writeFileAtomic(tmpPath("foo.txt2"), "foo2", 0444);
488 
489  auto expectedPaths = set<string>{"foo.txt", "bar.txt", "foo_txt", "foo.txt2"};
490  EXPECT_EQ(expectedPaths, listTmpDir());
491  EXPECT_EQ("foo", readData(tmpPath("foo.txt")));
492  EXPECT_EQ("bar", readData(tmpPath("bar.txt")));
493  EXPECT_EQ("underscore", readData(tmpPath("foo_txt")));
494  EXPECT_EQ("foo2", readData(tmpPath("foo.txt2")));
495  EXPECT_EQ(0644, getPerms(tmpPath("foo.txt")));
496  EXPECT_EQ(0400, getPerms(tmpPath("bar.txt")));
497  EXPECT_EQ(0440, getPerms(tmpPath("foo_txt")));
498  EXPECT_EQ(0444, getPerms(tmpPath("foo.txt2")));
499 }
500 } // namespace test
501 } // namespace folly
502 
503 #if defined(__linux__)
504 namespace {
509 class FChmodFailure {
510  public:
511  FChmodFailure() {
512  ++forceFailure_;
513  }
514  ~FChmodFailure() {
515  --forceFailure_;
516  }
517 
518  static bool shouldFail() {
519  return forceFailure_.load() > 0;
520  }
521 
522  private:
523  static std::atomic<int> forceFailure_;
524 };
525 
526 std::atomic<int> FChmodFailure::forceFailure_{0};
527 } // namespace
528 
529 // Replace the system fchmod() function with our own stub, so we can
530 // trigger failures in the writeFileAtomic() tests.
531 int fchmod(int fd, mode_t mode) {
532  static const auto realFunction =
533  reinterpret_cast<int (*)(int, mode_t)>(dlsym(RTLD_NEXT, "fchmod"));
534  // For sanity, make sure we didn't find ourself,
535  // since that would cause infinite recursion.
536  CHECK_NE(realFunction, fchmod);
537 
538  if (FChmodFailure::shouldFail()) {
539  errno = EINVAL;
540  return -1;
541  }
542  return realFunction(fd, mode);
543 }
544 
545 namespace folly {
546 namespace test {
547 TEST_F(WriteFileAtomic, chmodFailure) {
548  auto path = tmpPath("foo");
549 
550  // Use our stubbed out fchmod() function to force a failure when setting up
551  // the temporary file.
552  //
553  // First try when creating the file for the first time.
554  {
555  FChmodFailure fail;
556  EXPECT_THROW(writeFileAtomic(path, "foobar"), std::system_error);
557  }
558  EXPECT_EQ(set<string>{}, listTmpDir());
559 
560  // Now create a file normally so we can overwrite it
561  auto contents = StringPiece("regular perms");
562  writeFileAtomic(path, contents, 0600);
563  EXPECT_EQ(contents, readData(path));
564  EXPECT_EQ(0600, getPerms(path));
565  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
566 
567  // Now try overwriting the file when forcing fchmod to fail
568  {
569  FChmodFailure fail;
570  EXPECT_THROW(writeFileAtomic(path, "overwrite"), std::system_error);
571  }
572  // The file should be unchanged
573  EXPECT_EQ(contents, readData(path));
574  EXPECT_EQ(0600, getPerms(path));
575  EXPECT_EQ(set<string>{"foo"}, listTmpDir());
576 }
577 } // namespace test
578 } // namespace folly
579 #endif
auto f
bool readFile(int fd, Container &out, size_t num_bytes=std::numeric_limits< size_t >::max())
Definition: FileUtil.h:125
std::atomic< int64_t > sum(0)
std::string str() const
Definition: Range.h:591
#define EXPECT_THROW(statement, expected_exception)
Definition: gtest.h:1843
#define ASSERT_EQ(val1, val2)
Definition: gtest.h:1956
std::set< std::string > listTmpDir() const
char b
std::string readData(const string &path) const
std::vector< iovec > iov_
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
#define SCOPE_EXIT
Definition: ScopeGuard.h:274
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
auto msvcSuppressAbortOnInvalidParams(Func func) -> decltype(func())
Definition: TestUtil.h:166
folly::Optional< PskKeyExchangeMode > mode
const char * name
Definition: http_parser.c:437
constexpr auto size(C const &c) -> decltype(c.size())
Definition: Access.h:45
std::vector< std::pair< size_t, Reader > > readers_
std::vector< std::string > buffers_
LogLevel min
Definition: LogLevel.cpp:30
const int sizes[]
std::string join() const
size_t read(T &out, folly::io::Cursor &cursor)
Definition: Types-inl.h:258
ssize_t wrapvFull(F f, int fd, iovec *iov, int count, Offset...offset)
void fail()
Input & in_
Definition: json.cpp:342
constexpr auto data(C &c) -> decltype(c.data())
Definition: Access.h:71
mode_t getPerms(const string &path)
const fs::path & path() const
Definition: TestUtil.cpp:85
ssize_t preadv(int fd, const iovec *iov, int count, off_t offset)
Definition: SysUio.cpp:52
void setDirPerms(mode_t mode)
ssize_t wrapFull(F f, int fd, void *buf, size_t count, Offset...offset)
void writeFileAtomic(StringPiece filename, iovec *iov, int count, mode_t permissions)
Definition: FileUtil.cpp:224
TEST(ProgramOptionsTest, Errors)
string tmpPath(StringPiece name)
int * count
void checkUnixError(ssize_t ret, Args &&...args)
Definition: Exception.h:101
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
IovecBuffers(std::initializer_list< size_t > sizes)
off_t offset_
const char * string
Definition: Conv.cpp:212
static set< string > s
const
Definition: upload.py:398
void join(const Delim &delimiter, Iterator begin, Iterator end, String &output)
Definition: String-inl.h:498
bool writeFile(const Container &data, const char *filename, int flags=O_WRONLY|O_CREAT|O_TRUNC, mode_t mode=0666)
Definition: FileUtil.h:211
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
TEST_F(FileUtilTest, read)
void throwSystemError(Args &&...args)
Definition: Exception.h:76
std::vector< iovec > iov() const
Range< const char * > StringPiece
#define ASSERT_TRUE(condition)
Definition: gtest.h:1865
std::deque< ssize_t > spec_
StringPiece data_
Reader reader(std::deque< ssize_t > spec)