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