1//===- PathV3.inc ---------------------------------------------------------===//
2//
3//                     The MCLinker Project
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9#include <mcld/Support/FileSystem.h>
10#include <mcld/Support/Directory.h>
11#include <mcld/Support/Path.h>
12#include <llvm/Support/ErrorHandling.h>
13
14#include <cerrno>
15#include <dirent.h>
16#include <stdio.h>
17#include <sys/stat.h>
18#include <sys/types.h>
19#include <string>
20#include <stack>
21#include <unistd.h>
22
23namespace mcld{
24namespace sys{
25namespace fs{
26namespace detail{
27
28const char          separator = '/';
29const char          preferred_separator = '/';
30
31// return the last charactor being handled.
32size_t canonicalize(std::string& pathname)
33{
34  // Variable Index //
35  // SepTable - stack of result separators
36  // LR(1) Algorithm //
37  // traverse pPathName
38  //   if we meet '//', '///', '////', ...
39  //     -> ignore it
40  //     -> push current into stack
41  //     -> jump to the next not '/'
42  //   if we meet '/./'
43  //     -> ignore
44  //     -> jump to the next not '/'
45  //   if we meet '/../'
46  //     -> pop previous position of '/' P
47  //     -> erase P+1 to now
48  //   if we meet other else
49  //     -> go go go
50  //   if we meet '/.../', '/..../', ... -> illegal
51  if (pathname.empty())
52    return 0;
53
54  size_t handler = 0;
55  std::stack<size_t> slash_stack;
56  slash_stack.push(-1);
57  while (handler < pathname.size()) {
58    if (separator == pathname[handler]) { // handler = 1st '/'
59      size_t next = handler + 1;
60      if (next >= pathname.size())
61        return handler;
62      switch(pathname[next]) { // next = handler + 1;
63        case separator: { // '//'
64          while (next < pathname.size() && separator == pathname[next])
65            ++next;
66          // next is the last not '/'
67          pathname.erase(handler, next - handler - 1);
68          // handler is the first '/'
69          slash_stack.push(handler);
70          break;
71        }
72        case '.': { // '/.'
73          ++next; // next = handler + 2
74          if (next >= pathname.size()) // '/.'
75            return handler;
76          switch (pathname[next]) {
77            case separator: { // '/./'
78              pathname.erase(handler, 2);
79              break;
80            }
81            case '.': { // '/..'
82              ++next; // next = handler + 3;
83              if (next >= pathname.size()) // '/..?'
84                return handler;
85              switch(pathname[next]) {
86                case separator: { // '/../'
87                  handler = slash_stack.top();
88                  slash_stack.pop();
89                  pathname.erase(handler+1, next-handler);
90                  if (static_cast<size_t>(-1) == handler) {
91                    slash_stack.push(-1);
92                    handler = pathname.find_first_of(separator, handler);
93                  }
94                  break;
95                }
96                case '.': { // '/...', illegal
97                  return handler;
98                  break;
99                }
100                default : { // '/..a'
101                  slash_stack.push(handler);
102                  handler = pathname.find_first_of(separator, handler+3);
103                  break;
104                }
105              }
106              break;
107            }
108            default : { // '/.a'
109              slash_stack.push(handler);
110              handler = pathname.find_first_of(separator, handler+2);
111              break;
112            }
113          }
114          break;
115        }
116        default : { // '/a
117          slash_stack.push(handler);
118          handler = pathname.find_first_of(separator, handler+1);
119          break;
120        }
121      }
122    }
123    else {
124      handler = pathname.find_first_of(separator, handler);
125    }
126  }
127  return handler;
128}
129
130bool not_found_error(int perrno)
131{
132  return perrno == ENOENT || perrno == ENOTDIR;
133}
134
135void status(const Path& p, FileStatus& pFileStatus)
136{
137  struct stat path_stat;
138  if(stat(p.c_str(), &path_stat)!= 0)
139  {
140    if(not_found_error(errno))
141    {
142      pFileStatus.setType(FileNotFound);
143    }
144    else
145      pFileStatus.setType(StatusError);
146  }
147  else if(S_ISDIR(path_stat.st_mode))
148    pFileStatus.setType(DirectoryFile);
149  else if(S_ISREG(path_stat.st_mode))
150    pFileStatus.setType(RegularFile);
151  else if(S_ISBLK(path_stat.st_mode))
152    pFileStatus.setType(BlockFile);
153  else if(S_ISCHR(path_stat.st_mode))
154    pFileStatus.setType(CharacterFile);
155  else if(S_ISFIFO(path_stat.st_mode))
156    pFileStatus.setType(FifoFile);
157  else if(S_ISSOCK(path_stat.st_mode))
158    pFileStatus.setType(SocketFile);
159  else
160    pFileStatus.setType(TypeUnknown);
161}
162
163void symlink_status(const Path& p, FileStatus& pFileStatus)
164{
165  struct stat path_stat;
166  if(lstat(p.c_str(), &path_stat)!= 0)
167  {
168    if(errno == ENOENT || errno == ENOTDIR) // these are not errors
169    {
170      pFileStatus.setType(FileNotFound);
171    }
172    else
173      pFileStatus.setType(StatusError);
174  }
175  if(S_ISREG(path_stat.st_mode))
176    pFileStatus.setType(RegularFile);
177  if(S_ISDIR(path_stat.st_mode))
178    pFileStatus.setType(DirectoryFile);
179  if(S_ISLNK(path_stat.st_mode))
180    pFileStatus.setType(SymlinkFile);
181  if(S_ISBLK(path_stat.st_mode))
182    pFileStatus.setType(BlockFile);
183  if(S_ISCHR(path_stat.st_mode))
184    pFileStatus.setType(CharacterFile);
185  if(S_ISFIFO(path_stat.st_mode))
186    pFileStatus.setType(FifoFile);
187  if(S_ISSOCK(path_stat.st_mode))
188    pFileStatus.setType(SocketFile);
189  else
190    pFileStatus.setType(TypeUnknown);
191}
192
193/// read_dir - return true if we read one entry
194//  @return value -1: read error
195//                 0: read the end
196//                 1: success
197static int read_dir(intptr_t& pDir, std::string& pOutFilename)
198{
199  errno = 0;
200  dirent *cur_dir = ::readdir(reinterpret_cast<DIR*>(pDir));
201  if (0 == cur_dir && 0 != errno)
202    return -1;
203
204  // idx does not stay at the end, but all elements had beed put into cache.
205  if (NULL == cur_dir) {
206    return 0;
207  }
208
209  llvm::StringRef name(cur_dir->d_name, strlen(cur_dir->d_name));
210  if ((name.size() == 1 && name[0] == '.') ||
211      (name.size() == 2 && name[0] == '.' && name[1] == '.'))
212    return read_dir(pDir, pOutFilename);
213
214  // find a new directory
215  pOutFilename.append(name.data(), name.size());
216  return 1;
217}
218
219/// directory_iterator_increment - increment function implementation
220//
221//  iterator will call this function in two situations:
222//  1. All elements have been put into cache, and iterator stays at the end
223//     of cache. (a real end)
224//  2. Some but not all elements had beed put into cache, and we stoped.
225//     An iterator now is staying at the end of cache. (a temporal end)
226mcld::sys::fs::PathCache::entry_type* bring_one_into_cache(DirIterator& pIter)
227{
228  mcld::sys::fs::PathCache::entry_type* entry = 0;
229  std::string path(pIter.m_pParent->m_Path.native());
230  switch (read_dir(pIter.m_pParent->m_Handler, path)) {
231  case 1: {
232    // read one
233    bool exist = false;
234    entry = pIter.m_pParent->m_Cache.insert(path, exist);
235    if (!exist)
236      entry->setValue(new Path(path));
237    break;
238  }
239  case 0:// meet real end
240    pIter.m_pParent->m_CacheFull = true;
241    break;
242  default:
243  case -1:
244    llvm::report_fatal_error(std::string("Can't read directory: ")+
245                             pIter.m_pParent->path().native());
246    break;
247  }
248  return entry;
249}
250
251void open_dir(Directory& pDir)
252{
253  pDir.m_Handler = reinterpret_cast<intptr_t>(opendir(pDir.path().c_str()));
254  if (pDir.m_Handler == 0) {
255    errno = 0; // opendir() will set errno if it failed to open directory.
256    pDir.m_CacheFull = true;
257    return;
258  }
259  // read one entry for advance the end element of the cache.
260  std::string path(pDir.path().native());
261  switch (read_dir(pDir.m_Handler, path)) {
262  case 1: {
263    // find a new directory
264    bool exist = false;
265    mcld::sys::fs::PathCache::entry_type* entry = pDir.m_Cache.insert(path, exist);
266    if (!exist)
267      entry->setValue(new Path(path));
268    return;
269  }
270  case 0:
271    // FIXME: a warning function
272    pDir.m_CacheFull = true;
273    return;
274  default:
275  case -1:
276    llvm::report_fatal_error(std::string("Can't read directory: ")+
277                             pDir.path().native());
278  }
279}
280
281void close_dir(Directory& pDir)
282{
283  if (pDir.m_Handler)
284    closedir(reinterpret_cast<DIR *>(pDir.m_Handler));
285  pDir.m_Handler = 0;
286}
287
288void get_pwd(std::string& pPWD)
289{
290  char* pwd = (char*)malloc(PATH_MAX);
291  pPWD.assign(getcwd(pwd, PATH_MAX));
292  free(pwd);
293}
294
295} // namespace of detail
296} // namespace of fs
297} // namespace of sys
298} // namespace of mcld
299
300