Moved FileSpec into the Host layer since it will vary from host to host.
We have a common unix implementation in lldb/source/Host/common/FileSpec.cpp.



git-svn-id: https://llvm.org/svn/llvm-project/llvdb/trunk@125078 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/source/Host/common/FileSpec.cpp b/source/Host/common/FileSpec.cpp
new file mode 100644
index 0000000..557c704
--- /dev/null
+++ b/source/Host/common/FileSpec.cpp
@@ -0,0 +1,918 @@
+//===-- FileSpec.cpp --------------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <fstream>
+
+#include "lldb/Host/Config.h" // Have to include this before we test the define...
+#ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
+#include <pwd.h>
+#endif
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Program.h"
+
+#include "lldb/Host/FileSpec.h"
+#include "lldb/Core/DataBufferHeap.h"
+#include "lldb/Core/DataBufferMemoryMap.h"
+#include "lldb/Core/Stream.h"
+#include "lldb/Host/Host.h"
+#include "lldb/Utility/CleanUp.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace std;
+
+static bool
+GetFileStats (const FileSpec *file_spec, struct stat *stats_ptr)
+{
+    char resolved_path[PATH_MAX];
+    if (file_spec->GetPath(&resolved_path[0], sizeof(resolved_path)))
+        return ::stat (resolved_path, stats_ptr) == 0;
+    return false;
+}
+
+#ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
+
+static const char*
+GetCachedGlobTildeSlash()
+{
+    static std::string g_tilde;
+    if (g_tilde.empty())
+    {
+        struct passwd *user_entry;
+        user_entry = getpwuid(geteuid());
+        if (user_entry != NULL)
+            g_tilde = user_entry->pw_dir;
+
+        if (g_tilde.empty())
+            return NULL;
+    }
+    return g_tilde.c_str();
+}
+
+// Resolves the username part of a path of the form ~user/other/directories, and
+// writes the result into dst_path.
+// Returns 0 if there WAS a ~ in the path but the username couldn't be resolved.
+// Otherwise returns the number of characters copied into dst_path.  If the return
+// is >= dst_len, then the resolved path is too long...
+size_t
+FileSpec::ResolveUsername (const char *src_path, char *dst_path, size_t dst_len)
+{
+    char user_home[PATH_MAX];
+    const char *user_name;
+    
+    if (src_path == NULL || src_path[0] == '\0')
+        return 0;
+    
+    // If there's no ~, then just copy src_path straight to dst_path (they may be the same string...)
+    if (src_path[0] != '~')
+    {
+        size_t len = strlen (src_path);
+        if (len >= dst_len)
+        {
+            ::bcopy (src_path, dst_path, dst_len - 1);
+            dst_path[dst_len] = '\0';
+        }
+        else
+            ::bcopy (src_path, dst_path, len + 1);
+        
+        return len;
+    }
+    
+    const char *first_slash = ::strchr (src_path, '/');
+    char remainder[PATH_MAX];
+    
+    if (first_slash == NULL)
+    {
+        // The whole name is the username (minus the ~):
+        user_name = src_path + 1;
+        remainder[0] = '\0';
+    }
+    else
+    {
+        int user_name_len = first_slash - src_path - 1;
+        ::memcpy (user_home, src_path + 1, user_name_len);
+        user_home[user_name_len] = '\0';
+        user_name = user_home;
+        
+        ::strcpy (remainder, first_slash);
+    }
+    
+    if (user_name == NULL)
+        return 0;
+    // User name of "" means the current user...
+    
+    struct passwd *user_entry;
+    const char *home_dir = NULL;
+    
+    if (user_name[0] == '\0')
+    {
+        home_dir = GetCachedGlobTildeSlash();
+    }
+    else
+    {
+        user_entry = ::getpwnam (user_name);
+        if (user_entry != NULL)
+            home_dir = user_entry->pw_dir;
+    }
+    
+    if (home_dir == NULL)
+        return 0;
+    else 
+        return ::snprintf (dst_path, dst_len, "%s%s", home_dir, remainder);
+}
+#endif // #ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
+
+size_t
+FileSpec::Resolve (const char *src_path, char *dst_path, size_t dst_len)
+{
+    if (src_path == NULL || src_path[0] == '\0')
+        return 0;
+
+    // Glob if needed for ~/, otherwise copy in case src_path is same as dst_path...
+    char unglobbed_path[PATH_MAX];
+#ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
+    if (src_path[0] == '~')
+    {
+        size_t return_count = ResolveUsername(src_path, unglobbed_path, sizeof(unglobbed_path));
+        
+        // If we couldn't find the user referred to, or the resultant path was too long,
+        // then just copy over the src_path.
+        if (return_count == 0 || return_count >= sizeof(unglobbed_path)) 
+            ::snprintf (unglobbed_path, sizeof(unglobbed_path), "%s", src_path);
+    }
+    else
+#endif // #ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
+    {
+    	::snprintf(unglobbed_path, sizeof(unglobbed_path), "%s", src_path);
+    }
+
+    // Now resolve the path if needed
+    char resolved_path[PATH_MAX];
+    if (::realpath (unglobbed_path, resolved_path))
+    {
+        // Success, copy the resolved path
+        return ::snprintf(dst_path, dst_len, "%s", resolved_path);
+    }
+    else
+    {
+        // Failed, just copy the unglobbed path
+        return ::snprintf(dst_path, dst_len, "%s", unglobbed_path);
+    }
+}
+
+FileSpec::FileSpec() :
+    m_directory(),
+    m_filename()
+{
+}
+
+//------------------------------------------------------------------
+// Default constructor that can take an optional full path to a
+// file on disk.
+//------------------------------------------------------------------
+FileSpec::FileSpec(const char *pathname, bool resolve_path) :
+    m_directory(),
+    m_filename(),
+    m_is_resolved(false)
+{
+    if (pathname && pathname[0])
+        SetFile(pathname, resolve_path);
+}
+
+//------------------------------------------------------------------
+// Copy constructor
+//------------------------------------------------------------------
+FileSpec::FileSpec(const FileSpec& rhs) :
+    m_directory (rhs.m_directory),
+    m_filename (rhs.m_filename),
+    m_is_resolved (rhs.m_is_resolved)
+{
+}
+
+//------------------------------------------------------------------
+// Copy constructor
+//------------------------------------------------------------------
+FileSpec::FileSpec(const FileSpec* rhs) :
+    m_directory(),
+    m_filename()
+{
+    if (rhs)
+        *this = *rhs;
+}
+
+//------------------------------------------------------------------
+// Virtual destrcuctor in case anyone inherits from this class.
+//------------------------------------------------------------------
+FileSpec::~FileSpec()
+{
+}
+
+//------------------------------------------------------------------
+// Assignment operator.
+//------------------------------------------------------------------
+const FileSpec&
+FileSpec::operator= (const FileSpec& rhs)
+{
+    if (this != &rhs)
+    {
+        m_directory = rhs.m_directory;
+        m_filename = rhs.m_filename;
+        m_is_resolved = rhs.m_is_resolved;
+    }
+    return *this;
+}
+
+//------------------------------------------------------------------
+// Update the contents of this object with a new path. The path will
+// be split up into a directory and filename and stored as uniqued
+// string values for quick comparison and efficient memory usage.
+//------------------------------------------------------------------
+void
+FileSpec::SetFile (const char *pathname, bool resolve)
+{
+    m_filename.Clear();
+    m_directory.Clear();
+    m_is_resolved = false;
+    if (pathname == NULL || pathname[0] == '\0')
+        return;
+
+    char resolved_path[PATH_MAX];
+    bool path_fit = true;
+    
+    if (resolve)
+    {
+        path_fit = (FileSpec::Resolve (pathname, resolved_path, sizeof(resolved_path)) < sizeof(resolved_path) - 1);
+        m_is_resolved = path_fit;
+    }
+    else
+    {
+        // Copy the path because "basename" and "dirname" want to muck with the
+        // path buffer
+        if (::strlen (pathname) > sizeof(resolved_path) - 1)
+            path_fit = false;
+        else
+            ::strcpy (resolved_path, pathname);
+    }
+
+    
+    if (path_fit)
+    {
+        char *filename = ::basename (resolved_path);
+        if (filename)
+        {
+            m_filename.SetCString (filename);
+            // Truncate the basename off the end of the resolved path
+
+            // Only attempt to get the dirname if it looks like we have a path
+            if (strchr(resolved_path, '/'))
+            {
+                char *directory = ::dirname (resolved_path);
+
+                // Make sure we didn't get our directory resolved to "." without having
+                // specified
+                if (directory)
+                    m_directory.SetCString(directory);
+                else
+                {
+                    char *last_resolved_path_slash = strrchr(resolved_path, '/');
+                    if (last_resolved_path_slash)
+                    {
+                        *last_resolved_path_slash = '\0';
+                        m_directory.SetCString(resolved_path);
+                    }
+                }
+            }
+        }
+        else
+            m_directory.SetCString(resolved_path);
+    }
+}
+
+//----------------------------------------------------------------------
+// Convert to pointer operator. This allows code to check any FileSpec
+// objects to see if they contain anything valid using code such as:
+//
+//  if (file_spec)
+//  {}
+//----------------------------------------------------------------------
+FileSpec::operator
+void*() const
+{
+    return (m_directory || m_filename) ? const_cast<FileSpec*>(this) : NULL;
+}
+
+//----------------------------------------------------------------------
+// Logical NOT operator. This allows code to check any FileSpec
+// objects to see if they are invalid using code such as:
+//
+//  if (!file_spec)
+//  {}
+//----------------------------------------------------------------------
+bool
+FileSpec::operator!() const
+{
+    return !m_directory && !m_filename;
+}
+
+//------------------------------------------------------------------
+// Equal to operator
+//------------------------------------------------------------------
+bool
+FileSpec::operator== (const FileSpec& rhs) const
+{
+    if (m_filename == rhs.m_filename)
+    {
+        if (m_directory == rhs.m_directory)
+            return true;
+        
+        // TODO: determine if we want to keep this code in here.
+        // The code below was added to handle a case where we were
+        // trying to set a file and line breakpoint and one path
+        // was resolved, and the other not and the directory was
+        // in a mount point that resolved to a more complete path:
+        // "/tmp/a.c" == "/private/tmp/a.c". I might end up pulling
+        // this out...
+        if (IsResolved() && rhs.IsResolved())
+        {
+            // Both paths are resolved, no need to look further...
+            return false;
+        }
+        
+        FileSpec resolved_lhs(*this);
+
+        // If "this" isn't resolved, resolve it
+        if (!IsResolved())
+        {
+            if (resolved_lhs.ResolvePath())
+            {
+                // This path wasn't resolved but now it is. Check if the resolved
+                // directory is the same as our unresolved directory, and if so, 
+                // we can mark this object as resolved to avoid more future resolves
+                m_is_resolved = (m_directory == resolved_lhs.m_directory);
+            }
+            else
+                return false;
+        }
+        
+        FileSpec resolved_rhs(rhs);
+        if (!rhs.IsResolved())
+        {
+            if (resolved_rhs.ResolvePath())
+            {
+                // rhs's path wasn't resolved but now it is. Check if the resolved
+                // directory is the same as rhs's unresolved directory, and if so, 
+                // we can mark this object as resolved to avoid more future resolves
+                rhs.m_is_resolved = (m_directory == resolved_rhs.m_directory);
+            }
+            else
+                return false;
+        }
+
+        // If we reach this point in the code we were able to resolve both paths
+        // and since we only resolve the paths if the basenames are equal, then
+        // we can just check if both directories are equal...
+        return resolved_lhs.GetDirectory() == resolved_rhs.GetDirectory();
+    }
+    return false;
+}
+
+//------------------------------------------------------------------
+// Not equal to operator
+//------------------------------------------------------------------
+bool
+FileSpec::operator!= (const FileSpec& rhs) const
+{
+    return !(*this == rhs);
+}
+
+//------------------------------------------------------------------
+// Less than operator
+//------------------------------------------------------------------
+bool
+FileSpec::operator< (const FileSpec& rhs) const
+{
+    return FileSpec::Compare(*this, rhs, true) < 0;
+}
+
+//------------------------------------------------------------------
+// Dump a FileSpec object to a stream
+//------------------------------------------------------------------
+Stream&
+lldb_private::operator << (Stream &s, const FileSpec& f)
+{
+    f.Dump(&s);
+    return s;
+}
+
+//------------------------------------------------------------------
+// Clear this object by releasing both the directory and filename
+// string values and making them both the empty string.
+//------------------------------------------------------------------
+void
+FileSpec::Clear()
+{
+    m_directory.Clear();
+    m_filename.Clear();
+}
+
+//------------------------------------------------------------------
+// Compare two FileSpec objects. If "full" is true, then both
+// the directory and the filename must match. If "full" is false,
+// then the directory names for "a" and "b" are only compared if
+// they are both non-empty. This allows a FileSpec object to only
+// contain a filename and it can match FileSpec objects that have
+// matching filenames with different paths.
+//
+// Return -1 if the "a" is less than "b", 0 if "a" is equal to "b"
+// and "1" if "a" is greater than "b".
+//------------------------------------------------------------------
+int
+FileSpec::Compare(const FileSpec& a, const FileSpec& b, bool full)
+{
+    int result = 0;
+
+    // If full is true, then we must compare both the directory and filename.
+
+    // If full is false, then if either directory is empty, then we match on
+    // the basename only, and if both directories have valid values, we still
+    // do a full compare. This allows for matching when we just have a filename
+    // in one of the FileSpec objects.
+
+    if (full || (a.m_directory && b.m_directory))
+    {
+        result = ConstString::Compare(a.m_directory, b.m_directory);
+        if (result)
+            return result;
+    }
+    return ConstString::Compare (a.m_filename, b.m_filename);
+}
+
+bool
+FileSpec::Equal (const FileSpec& a, const FileSpec& b, bool full)
+{
+    if (full)
+        return a == b;
+    else
+        return a.m_filename == b.m_filename;
+}
+
+
+
+//------------------------------------------------------------------
+// Dump the object to the supplied stream. If the object contains
+// a valid directory name, it will be displayed followed by a
+// directory delimiter, and the filename.
+//------------------------------------------------------------------
+void
+FileSpec::Dump(Stream *s) const
+{
+    if (m_filename)
+        m_directory.Dump(s, "");    // Provide a default for m_directory when we dump it in case it is invalid
+
+    if (m_directory)
+    {
+        // If dirname was valid, then we need to print a slash between
+        // the directory and the filename
+        s->PutChar('/');
+    }
+    m_filename.Dump(s);
+}
+
+//------------------------------------------------------------------
+// Returns true if the file exists.
+//------------------------------------------------------------------
+bool
+FileSpec::Exists () const
+{
+    struct stat file_stats;
+    return GetFileStats (this, &file_stats);
+}
+
+bool
+FileSpec::ResolveExecutableLocation ()
+{
+    if (!m_directory)
+    {
+        const char *file_cstr = m_filename.GetCString();
+        if (file_cstr)
+        {
+            const std::string file_str (file_cstr);
+            llvm::sys::Path path = llvm::sys::Program::FindProgramByName (file_str);
+            const std::string &path_str = path.str();
+            llvm::StringRef dir_ref = llvm::sys::path::parent_path(path_str);
+            //llvm::StringRef dir_ref = path.getDirname();
+            if (! dir_ref.empty())
+            {
+                // FindProgramByName returns "." if it can't find the file.
+                if (strcmp (".", dir_ref.data()) == 0)
+                    return false;
+
+                m_directory.SetCString (dir_ref.data());
+                if (Exists())
+                    return true;
+                else
+                {
+                    // If FindProgramByName found the file, it returns the directory + filename in its return results.
+                    // We need to separate them.
+                    FileSpec tmp_file (dir_ref.data(), false);
+                    if (tmp_file.Exists())
+                    {
+                        m_directory = tmp_file.m_directory;
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+    
+    return false;
+}
+
+bool
+FileSpec::ResolvePath ()
+{
+    if (m_is_resolved)
+        return true;    // We have already resolved this path
+
+    char path_buf[PATH_MAX];    
+    if (!GetPath (path_buf, PATH_MAX))
+        return false;
+    // SetFile(...) will set m_is_resolved correctly if it can resolve the path
+    SetFile (path_buf, true);
+    return m_is_resolved; 
+}
+
+uint64_t
+FileSpec::GetByteSize() const
+{
+    struct stat file_stats;
+    if (GetFileStats (this, &file_stats))
+        return file_stats.st_size;
+    return 0;
+}
+
+FileSpec::FileType
+FileSpec::GetFileType () const
+{
+    struct stat file_stats;
+    if (GetFileStats (this, &file_stats))
+    {
+        mode_t file_type = file_stats.st_mode & S_IFMT;
+        switch (file_type)
+        {
+        case S_IFDIR:   return eFileTypeDirectory;
+        case S_IFIFO:   return eFileTypePipe;
+        case S_IFREG:   return eFileTypeRegular;
+        case S_IFSOCK:  return eFileTypeSocket;
+        case S_IFLNK:   return eFileTypeSymbolicLink;
+        default:
+            break;
+        }
+        return eFileTypeUnknown;
+    }
+    return eFileTypeInvalid;
+}
+
+TimeValue
+FileSpec::GetModificationTime () const
+{
+    TimeValue mod_time;
+    struct stat file_stats;
+    if (GetFileStats (this, &file_stats))
+        mod_time.OffsetWithSeconds(file_stats.st_mtime);
+    return mod_time;
+}
+
+//------------------------------------------------------------------
+// Directory string get accessor.
+//------------------------------------------------------------------
+ConstString &
+FileSpec::GetDirectory()
+{
+    return m_directory;
+}
+
+//------------------------------------------------------------------
+// Directory string const get accessor.
+//------------------------------------------------------------------
+const ConstString &
+FileSpec::GetDirectory() const
+{
+    return m_directory;
+}
+
+//------------------------------------------------------------------
+// Filename string get accessor.
+//------------------------------------------------------------------
+ConstString &
+FileSpec::GetFilename()
+{
+    return m_filename;
+}
+
+//------------------------------------------------------------------
+// Filename string const get accessor.
+//------------------------------------------------------------------
+const ConstString &
+FileSpec::GetFilename() const
+{
+    return m_filename;
+}
+
+//------------------------------------------------------------------
+// Extract the directory and path into a fixed buffer. This is
+// needed as the directory and path are stored in separate string
+// values.
+//------------------------------------------------------------------
+size_t
+FileSpec::GetPath(char *path, size_t path_max_len) const
+{
+    if (path_max_len)
+    {
+        const char *dirname = m_directory.GetCString();
+        const char *filename = m_filename.GetCString();
+        if (dirname)
+        {
+            if (filename)
+                return ::snprintf (path, path_max_len, "%s/%s", dirname, filename);
+            else
+                return ::snprintf (path, path_max_len, "%s", dirname);
+        }
+        else if (filename)
+        {
+            return ::snprintf (path, path_max_len, "%s", filename);
+        }
+    }
+    path[0] = '\0';
+    return 0;
+}
+
+//------------------------------------------------------------------
+// Returns a shared pointer to a data buffer that contains all or
+// part of the contents of a file. The data is memory mapped and
+// will lazily page in data from the file as memory is accessed.
+// The data that is mappped will start "file_offset" bytes into the
+// file, and "file_size" bytes will be mapped. If "file_size" is
+// greater than the number of bytes available in the file starting
+// at "file_offset", the number of bytes will be appropriately
+// truncated. The final number of bytes that get mapped can be
+// verified using the DataBuffer::GetByteSize() function.
+//------------------------------------------------------------------
+DataBufferSP
+FileSpec::MemoryMapFileContents(off_t file_offset, size_t file_size) const
+{
+    DataBufferSP data_sp;
+    auto_ptr<DataBufferMemoryMap> mmap_data(new DataBufferMemoryMap());
+    if (mmap_data.get())
+    {
+        if (mmap_data->MemoryMapFromFileSpec (this, file_offset, file_size) >= file_size)
+            data_sp.reset(mmap_data.release());
+    }
+    return data_sp;
+}
+
+
+//------------------------------------------------------------------
+// Return the size in bytes that this object takes in memory. This
+// returns the size in bytes of this object, not any shared string
+// values it may refer to.
+//------------------------------------------------------------------
+size_t
+FileSpec::MemorySize() const
+{
+    return m_filename.MemorySize() + m_directory.MemorySize();
+}
+
+
+size_t
+FileSpec::ReadFileContents (off_t file_offset, void *dst, size_t dst_len) const
+{
+    size_t bytes_read = 0;
+    char resolved_path[PATH_MAX];
+    if (GetPath(resolved_path, sizeof(resolved_path)))
+    {
+        int fd = ::open (resolved_path, O_RDONLY, 0);
+        if (fd != -1)
+        {
+            struct stat file_stats;
+            if (::fstat (fd, &file_stats) == 0)
+            {
+                // Read bytes directly into our basic_string buffer
+                if (file_stats.st_size > 0)
+                {
+                    off_t lseek_result = 0;
+                    if (file_offset > 0)
+                        lseek_result = ::lseek (fd, file_offset, SEEK_SET);
+
+                    if (lseek_result == file_offset)
+                    {
+                        ssize_t n = ::read (fd, dst, dst_len);
+                        if (n >= 0)
+                            bytes_read = n;
+                    }
+                }
+            }
+        }
+        close(fd);
+    }
+    return bytes_read;
+}
+
+//------------------------------------------------------------------
+// Returns a shared pointer to a data buffer that contains all or
+// part of the contents of a file. The data copies into a heap based
+// buffer that lives in the DataBuffer shared pointer object returned.
+// The data that is cached will start "file_offset" bytes into the
+// file, and "file_size" bytes will be mapped. If "file_size" is
+// greater than the number of bytes available in the file starting
+// at "file_offset", the number of bytes will be appropriately
+// truncated. The final number of bytes that get mapped can be
+// verified using the DataBuffer::GetByteSize() function.
+//------------------------------------------------------------------
+DataBufferSP
+FileSpec::ReadFileContents (off_t file_offset, size_t file_size) const
+{
+    DataBufferSP data_sp;
+    char resolved_path[PATH_MAX];
+    if (GetPath(resolved_path, sizeof(resolved_path)))
+    {
+        int fd = ::open (resolved_path, O_RDONLY, 0);
+        if (fd != -1)
+        {
+            struct stat file_stats;
+            if (::fstat (fd, &file_stats) == 0)
+            {
+                if (file_stats.st_size > 0)
+                {
+                    off_t lseek_result = 0;
+                    if (file_offset > 0)
+                        lseek_result = ::lseek (fd, file_offset, SEEK_SET);
+
+                    if (lseek_result < 0)
+                    {
+                        // Get error from errno
+                    }
+                    else if (lseek_result == file_offset)
+                    {
+                        const size_t bytes_left = file_stats.st_size - file_offset;
+                        size_t num_bytes_to_read = file_size;
+                        if (num_bytes_to_read > bytes_left)
+                            num_bytes_to_read = bytes_left;
+
+                        std::auto_ptr<DataBufferHeap> data_heap_ap;
+                        data_heap_ap.reset(new DataBufferHeap(num_bytes_to_read, '\0'));
+
+                        if (data_heap_ap.get())
+                        {
+                            ssize_t bytesRead = ::read (fd, (void *)data_heap_ap->GetBytes(), data_heap_ap->GetByteSize());
+                            if (bytesRead >= 0)
+                            {
+                                // Make sure we read exactly what we asked for and if we got
+                                // less, adjust the array
+                                if ((size_t)bytesRead < data_heap_ap->GetByteSize())
+                                    data_heap_ap->SetByteSize(bytesRead);
+                                data_sp.reset(data_heap_ap.release());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        close(fd);
+    }
+    return data_sp;
+}
+
+size_t
+FileSpec::ReadFileLines (STLStringArray &lines)
+{
+    lines.clear();
+    char path[PATH_MAX];
+    if (GetPath(path, sizeof(path)))
+    {
+        ifstream file_stream (path);
+
+        if (file_stream)
+        {
+            std::string line;
+            while (getline (file_stream, line))
+                lines.push_back (line);
+        }
+    }
+    return lines.size();
+}
+
+FileSpec::EnumerateDirectoryResult
+FileSpec::EnumerateDirectory
+(
+    const char *dir_path, 
+    bool find_directories,
+    bool find_files,
+    bool find_other,
+    EnumerateDirectoryCallbackType callback,
+    void *callback_baton
+)
+{
+    if (dir_path && dir_path[0])
+    {
+        lldb_utility::CleanUp <DIR *, int> dir_path_dir (opendir(dir_path), NULL, closedir);
+        if (dir_path_dir.is_valid())
+        {
+            struct dirent* dp;
+            while ((dp = readdir(dir_path_dir.get())) != NULL)
+            {
+                // Only search directories
+                if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN)
+                {
+                    size_t len = strlen(dp->d_name);
+
+                    if (len == 1 && dp->d_name[0] == '.')
+                        continue;
+
+                    if (len == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.')
+                        continue;
+                }
+            
+                bool call_callback = false;
+                FileSpec::FileType file_type = eFileTypeUnknown;
+
+                switch (dp->d_type)
+                {
+                default:
+                case DT_UNKNOWN:    file_type = eFileTypeUnknown;       call_callback = true;               break;
+                case DT_FIFO:       file_type = eFileTypePipe;          call_callback = find_other;         break;
+                case DT_CHR:        file_type = eFileTypeOther;         call_callback = find_other;         break;
+                case DT_DIR:        file_type = eFileTypeDirectory;     call_callback = find_directories;   break;
+                case DT_BLK:        file_type = eFileTypeOther;         call_callback = find_other;         break;
+                case DT_REG:        file_type = eFileTypeRegular;       call_callback = find_files;         break;
+                case DT_LNK:        file_type = eFileTypeSymbolicLink;  call_callback = find_other;         break;
+                case DT_SOCK:       file_type = eFileTypeSocket;        call_callback = find_other;         break;
+                case DT_WHT:        file_type = eFileTypeOther;         call_callback = find_other;         break;
+                }
+
+                if (call_callback)
+                {
+                    char child_path[PATH_MAX];
+                    const int child_path_len = ::snprintf (child_path, sizeof(child_path), "%s/%s", dir_path, dp->d_name);
+                    if (child_path_len < sizeof(child_path) - 1)
+                    {
+                        // Don't resolve the file type or path
+                        FileSpec child_path_spec (child_path, false);
+
+                        EnumerateDirectoryResult result = callback (callback_baton, file_type, child_path_spec);
+                        
+                        switch (result)
+                        {
+                        default:
+                        case eEnumerateDirectoryResultNext:  
+                            // Enumerate next entry in the current directory. We just
+                            // exit this switch and will continue enumerating the
+                            // current directory as we currently are...
+                            break;
+
+                        case eEnumerateDirectoryResultEnter: // Recurse into the current entry if it is a directory or symlink, or next if not
+                            if (FileSpec::EnumerateDirectory (child_path, 
+                                                              find_directories, 
+                                                              find_files, 
+                                                              find_other, 
+                                                              callback, 
+                                                              callback_baton) == eEnumerateDirectoryResultQuit)
+                            {
+                                // The subdirectory returned Quit, which means to 
+                                // stop all directory enumerations at all levels.
+                                return eEnumerateDirectoryResultQuit;
+                            }
+                            break;
+                        
+                        case eEnumerateDirectoryResultExit:  // Exit from the current directory at the current level.
+                            // Exit from this directory level and tell parent to 
+                            // keep enumerating.
+                            return eEnumerateDirectoryResultNext;
+
+                        case eEnumerateDirectoryResultQuit:  // Stop directory enumerations at any level
+                            return eEnumerateDirectoryResultQuit;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    // By default when exiting a directory, we tell the parent enumeration
+    // to continue enumerating.
+    return eEnumerateDirectoryResultNext;    
+}
+