1 // Copyright (c) 2013 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
17 namespace {
18
19 using crazy::String;
20 using crazy::Vector;
21
Panic(const char * msg,...)22 void 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.
34 template <class T>
35 class List {
36 public:
List()37 List() : entries_() {}
38
~List()39 ~List() { Reset(); }
40
Reset()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.
PushBack(T * item)51 void PushBack(T* item) { entries_.PushBack(item); }
52
GetCount() const53 size_t GetCount() const { return entries_.GetCount(); }
54
operator [](size_t index)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.
62 class MockFileEntry {
63 public:
MockFileEntry()64 MockFileEntry() : path_(), data_() {}
65
~MockFileEntry()66 ~MockFileEntry() {}
67
GetPath() const68 const char* GetPath() const { return path_.c_str(); }
GetData() const69 const char* GetData() const { return data_.c_str(); }
GetDataSize() const70 size_t GetDataSize() const { return data_.size(); }
71
SetPath(const char * path)72 void SetPath(const char* path) { path_.Assign(path); }
73
SetData(const char * data,size_t data_size)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.
84 class MockEnvEntry {
85 public:
MockEnvEntry(const char * var_name,const char * var_value)86 MockEnvEntry(const char* var_name, const char* var_value)
87 : var_name_(var_name), var_value_(var_value) {}
88
GetName() const89 const String& GetName() const { return var_name_; }
GetValue() const90 const String& GetValue() const { return var_value_; }
91
92 private:
93 crazy::String var_name_;
94 crazy::String var_value_;
95 };
96
97 class MockSystem {
98 public:
MockSystem()99 MockSystem() : files_(), environment_() {}
100
~MockSystem()101 ~MockSystem() { Reset(); }
102
SetCurrentDir(const char * path)103 void SetCurrentDir(const char* path) { current_dir_ = path; }
104
GetCurrentDir() const105 String GetCurrentDir() const { return current_dir_; }
106
AddFileEntry(MockFileEntry * entry)107 void AddFileEntry(MockFileEntry* entry) { files_.PushBack(entry); }
108
AddEnvEntry(MockEnvEntry * entry)109 void AddEnvEntry(MockEnvEntry* entry) { environment_.PushBack(entry); }
110
FindFileEntry(const char * path)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
FindEnvEntry(const char * var_name)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
Reset()129 void Reset() {
130 files_.Reset();
131 environment_.Reset();
132 current_dir_ = "/";
133 }
134
Check()135 void Check() {
136 if (!active_)
137 Panic("No mock file system setup!");
138 }
139
Activate()140 void Activate() {
141 if (active_)
142 Panic("Double mock file system activation!");
143
144 active_ = true;
145 }
146
Deactivate()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
161 static MockSystem s_mock_fs;
162
163 class MockFileHandle {
164 public:
MockFileHandle(MockFileEntry * entry)165 MockFileHandle(MockFileEntry* entry) : entry_(entry), offset_(0) {}
~MockFileHandle()166 ~MockFileHandle() {}
167
IsEof() const168 bool IsEof() const { return offset_ >= entry_->GetDataSize(); }
169
GetString(char * buffer,size_t buffer_size)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
Read(void * buffer,size_t buffer_size)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
SeekTo(off_t offset)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
Map(void * address,size_t length,int prot,int flags,off_t offset)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
NewMockFileHandle(const char * path,crazy::FileOpenMode open_mode)259 MockFileHandle* 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
277 namespace crazy {
278
279 #ifdef UNIT_TESTS
280
PathExists(const char * path)281 bool PathExists(const char* path) {
282 s_mock_fs.Check();
283 return s_mock_fs.FindFileEntry(path) != NULL;
284 }
285
PathIsFile(const char * path)286 bool PathIsFile(const char* path) {
287 // TODO(digit): Change this when support for mock directories is added.
288 return PathExists(path);
289 }
290
GetCurrentDirectory()291 String GetCurrentDirectory() {
292 s_mock_fs.Check();
293 return s_mock_fs.GetCurrentDir();
294 }
295
GetEnv(const char * var_name)296 const 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
OpenReadOnly(const char * path)305 bool FileDescriptor::OpenReadOnly(const char* path) {
306 fd_ = NewMockFileHandle(path, FILE_OPEN_READ_ONLY);
307 return fd_ != NULL;
308 }
309
OpenReadWrite(const char * path)310 bool FileDescriptor::OpenReadWrite(const char* path) {
311 // NOT IMPLEMENTED ON PURPOSE.
312 return false;
313 }
314
Close()315 void FileDescriptor::Close() {
316 if (fd_) {
317 MockFileHandle* handle = reinterpret_cast<MockFileHandle*>(fd_);
318 delete handle;
319 fd_ = NULL;
320 }
321 }
322
Read(void * buffer,size_t buffer_size)323 int 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
SeekTo(off_t offset)332 int 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
Map(void * address,size_t length,int prot,int flags,off_t offset)341 void* 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
SystemMock()354 SystemMock::SystemMock() { s_mock_fs.Activate(); }
355
~SystemMock()356 SystemMock::~SystemMock() {
357 s_mock_fs.Deactivate();
358 s_mock_fs.Reset();
359 }
360
AddRegularFile(const char * path,const char * data,size_t data_size)361 void 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
AddEnvVariable(const char * var_name,const char * var_value)373 void 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
SetCurrentDir(const char * path)380 void 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