1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "crazy_linker_system_mock.h"
6
7#include <stdarg.h>
8#include <stdio.h>
9#include <stdlib.h>
10
11#include "crazy_linker_util.h"
12#include "crazy_linker_system.h"
13
14// Unit-testing support code. This should never be compiled into
15// the production code.
16
17namespace {
18
19using crazy::String;
20using crazy::Vector;
21
22void Panic(const char* msg, ...) {
23  va_list args;
24  fprintf(stderr, "PANIC: ");
25  va_start(args, msg);
26  vfprintf(stderr, msg, args);
27  va_end(args);
28  fprintf(stderr, "\n");
29  exit(1);
30}
31
32// Models a simple list of pointers to objects, which are owned by the
33// list itself.
34template <class T>
35class List {
36 public:
37  List() : entries_() {}
38
39  ~List() { Reset(); }
40
41  void Reset() {
42    for (size_t n = 0; n < entries_.GetCount(); ++n) {
43      T* entry = entries_[n];
44      delete entry;
45      entries_[n] = NULL;
46    }
47    entries_.Resize(0);
48  }
49
50  // Add an item to the list, transfer ownership to it.
51  void PushBack(T* item) { entries_.PushBack(item); }
52
53  size_t GetCount() const { return entries_.GetCount(); }
54
55  T* operator[](size_t index) { return entries_[index]; }
56
57 private:
58  crazy::Vector<T*> entries_;
59};
60
61// Models a single file entry in a mock file system.
62class MockFileEntry {
63 public:
64  MockFileEntry() : path_(), data_() {}
65
66  ~MockFileEntry() {}
67
68  const char* GetPath() const { return path_.c_str(); }
69  const char* GetData() const { return data_.c_str(); }
70  size_t GetDataSize() const { return data_.size(); }
71
72  void SetPath(const char* path) { path_.Assign(path); }
73
74  void SetData(const char* data, size_t data_size) {
75    data_.Assign(data, data_size);
76  }
77
78 private:
79  crazy::String path_;
80  crazy::String data_;
81};
82
83// Models a single mock environment variable value.
84class MockEnvEntry {
85 public:
86  MockEnvEntry(const char* var_name, const char* var_value)
87      : var_name_(var_name), var_value_(var_value) {}
88
89  const String& GetName() const { return var_name_; }
90  const String& GetValue() const { return var_value_; }
91
92 private:
93  crazy::String var_name_;
94  crazy::String var_value_;
95};
96
97class MockSystem {
98 public:
99  MockSystem() : files_(), environment_() {}
100
101  ~MockSystem() { Reset(); }
102
103  void SetCurrentDir(const char* path) { current_dir_ = path; }
104
105  String GetCurrentDir() const { return current_dir_; }
106
107  void AddFileEntry(MockFileEntry* entry) { files_.PushBack(entry); }
108
109  void AddEnvEntry(MockEnvEntry* entry) { environment_.PushBack(entry); }
110
111  MockFileEntry* FindFileEntry(const char* path) {
112    for (size_t n = 0; n < files_.GetCount(); ++n) {
113      MockFileEntry* entry = files_[n];
114      if (entry->GetPath() && !strcmp(path, entry->GetPath()))
115        return entry;
116    }
117    return NULL;
118  }
119
120  MockEnvEntry* FindEnvEntry(const char* var_name) {
121    for (size_t n = 0; n < environment_.GetCount(); ++n) {
122      MockEnvEntry* entry = environment_[n];
123      if (!strcmp(entry->GetName().c_str(), var_name))
124        return entry;
125    }
126    return NULL;
127  }
128
129  void Reset() {
130    files_.Reset();
131    environment_.Reset();
132    current_dir_ = "/";
133  }
134
135  void Check() {
136    if (!active_)
137      Panic("No mock file system setup!");
138  }
139
140  void Activate() {
141    if (active_)
142      Panic("Double mock file system activation!");
143
144    active_ = true;
145  }
146
147  void Deactivate() {
148    if (!active_)
149      Panic("Double mock file system deactivation!");
150
151    active_ = false;
152  }
153
154 private:
155  List<MockFileEntry> files_;
156  List<MockEnvEntry> environment_;
157  String current_dir_;
158  bool active_;
159};
160
161static MockSystem s_mock_fs;
162
163class MockFileHandle {
164 public:
165  MockFileHandle(MockFileEntry* entry) : entry_(entry), offset_(0) {}
166  ~MockFileHandle() {}
167
168  bool IsEof() const { return offset_ >= entry_->GetDataSize(); }
169
170  bool GetString(char* buffer, size_t buffer_size) {
171    const char* data = entry_->GetData();
172    size_t data_size = entry_->GetDataSize();
173
174    if (offset_ >= data_size || buffer_size == 0)
175      return false;
176
177    while (buffer_size > 1) {
178      char ch = data[offset_++];
179      *buffer++ = ch;
180      buffer_size--;
181      if (ch == '\n')
182        break;
183    }
184    *buffer = '\0';
185    return true;
186  }
187
188  int Read(void* buffer, size_t buffer_size) {
189    if (buffer_size == 0)
190      return 0;
191
192    const char* data = entry_->GetData();
193    size_t data_size = entry_->GetDataSize();
194
195    size_t avail = data_size - offset_;
196    if (avail == 0)
197      return 0;
198
199    if (buffer_size > avail)
200      buffer_size = avail;
201
202    ::memcpy(buffer, data + offset_, buffer_size);
203    offset_ += buffer_size;
204
205    return static_cast<int>(buffer_size);
206  }
207
208  int SeekTo(off_t offset) {
209    if (offset < 0) {
210      errno = EINVAL;
211      return -1;
212    }
213
214    const char* data = entry_->GetData();
215    size_t data_size = entry_->GetDataSize();
216
217    if (offset > static_cast<off_t>(data_size)) {
218      errno = EINVAL;
219      return -1;
220    }
221
222    offset_ = static_cast<size_t>(offset);
223    return 0;
224  }
225
226  void* Map(void* address, size_t length, int prot, int flags, off_t offset) {
227    const char* data = entry_->GetData();
228    size_t data_size = entry_->GetDataSize();
229    if (offset_ >= data_size) {
230      errno = EINVAL;
231      return MAP_FAILED;
232    }
233
234    // Allocate an anonymous memory mapping, then copy the file contents
235    // into it.
236    void* map = mmap(address, length, PROT_WRITE, MAP_ANONYMOUS, -1, 0);
237    if (map == MAP_FAILED) {
238      return map;
239    }
240
241    size_t avail = data_size - offset_;
242    if (avail > length)
243      avail = length;
244
245    ::memcpy(map, data + offset_, avail);
246
247    // Restore desired protection after the write.
248    mprotect(map, length, prot);
249
250    // Done.
251    return map;
252  }
253
254 private:
255  MockFileEntry* entry_;
256  size_t offset_;
257};
258
259MockFileHandle* NewMockFileHandle(const char* path,
260                                  crazy::FileOpenMode open_mode) {
261  // Check that a mock file system instance is active.
262  s_mock_fs.Check();
263
264  // TODO(digit): Add write support.
265  if (open_mode != crazy::FILE_OPEN_READ_ONLY)
266    Panic("Unsupported open mode (%d): %s", open_mode, path);
267
268  MockFileEntry* entry = s_mock_fs.FindFileEntry(path);
269  if (!entry)
270    Panic("Missing mock file entry: %s", path);
271
272  return new MockFileHandle(entry);
273}
274
275}  // namespace
276
277namespace crazy {
278
279#ifdef UNIT_TESTS
280
281bool PathExists(const char* path) {
282  s_mock_fs.Check();
283  return s_mock_fs.FindFileEntry(path) != NULL;
284}
285
286bool PathIsFile(const char* path) {
287  // TODO(digit): Change this when support for mock directories is added.
288  return PathExists(path);
289}
290
291String GetCurrentDirectory() {
292  s_mock_fs.Check();
293  return s_mock_fs.GetCurrentDir();
294}
295
296const char* GetEnv(const char* var_name) {
297  s_mock_fs.Check();
298  MockEnvEntry* entry = s_mock_fs.FindEnvEntry(var_name);
299  if (!entry)
300    return NULL;
301  else
302    return entry->GetValue().c_str();
303}
304
305bool FileDescriptor::OpenReadOnly(const char* path) {
306  fd_ = NewMockFileHandle(path, FILE_OPEN_READ_ONLY);
307  return fd_ != NULL;
308}
309
310bool FileDescriptor::OpenReadWrite(const char* path) {
311  // NOT IMPLEMENTED ON PURPOSE.
312  return false;
313}
314
315void FileDescriptor::Close() {
316  if (fd_) {
317    MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_);
318    delete handle;
319    fd_ = NULL;
320  }
321}
322
323int FileDescriptor::Read(void* buffer, size_t buffer_size) {
324  if (!fd_) {
325    errno = EBADF;
326    return -1;
327  }
328  MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_);
329  return handle->Read(buffer, buffer_size);
330}
331
332int FileDescriptor::SeekTo(off_t offset) {
333  if (!fd_) {
334    errno = EBADF;
335    return -1;
336  }
337  MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_);
338  return handle->SeekTo(offset);
339}
340
341void* FileDescriptor::Map(void* address,
342                          size_t length,
343                          int prot,
344                          int flags,
345                          off_t offset) {
346  if (!fd_ || (offset & 4095) != 0) {
347    errno = EINVAL;
348    return MAP_FAILED;
349  }
350  MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_);
351  return handle->Map(address, length, prot, flags, offset);
352}
353
354SystemMock::SystemMock() { s_mock_fs.Activate(); }
355
356SystemMock::~SystemMock() {
357  s_mock_fs.Deactivate();
358  s_mock_fs.Reset();
359}
360
361void SystemMock::AddRegularFile(const char* path,
362                                const char* data,
363                                size_t data_size) {
364  s_mock_fs.Check();
365
366  MockFileEntry* entry = new MockFileEntry();
367  entry->SetPath(path);
368  entry->SetData(data, data_size);
369
370  s_mock_fs.AddFileEntry(entry);
371}
372
373void SystemMock::AddEnvVariable(const char* var_name, const char* var_value) {
374  s_mock_fs.Check();
375
376  MockEnvEntry* env = new MockEnvEntry(var_name, var_value);
377  s_mock_fs.AddEnvEntry(env);
378}
379
380void SystemMock::SetCurrentDir(const char* path) {
381  s_mock_fs.Check();
382  s_mock_fs.SetCurrentDir(path);
383}
384
385#endif  // UNIT_TESTS
386
387}  // namespace crazy
388