#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/File.h"
#include "lldb/Host/FileSpec.h"
#include "lldb/Core/DataBufferHeap.h"
#include "lldb/Core/DataBufferMemoryMap.h"
#include "lldb/Core/RegularExpression.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, 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();
}
#endif // #ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
size_t
FileSpec::ResolveUsername (const char *src_path, char *dst_path, size_t dst_len)
{
if (src_path == NULL || src_path[0] == '\0')
return 0;
#ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
char user_home[PATH_MAX];
const char *user_name;
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)
{
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;
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);
#else
return ::snprintf (dst_path, dst_len, "%s", src_path);
#endif // #ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
}
size_t
FileSpec::ResolvePartialUsername (const char *partial_name, StringList &matches)
{
#ifdef LLDB_CONFIG_TILDE_RESOLVES_TO_USER
size_t extant_entries = matches.GetSize();
setpwent();
struct passwd *user_entry;
const char *name_start = partial_name + 1;
std::set<std::string> name_list;
while ((user_entry = getpwent()) != NULL)
{
if (strstr(user_entry->pw_name, name_start) == user_entry->pw_name)
{
std::string tmp_buf("~");
tmp_buf.append(user_entry->pw_name);
tmp_buf.push_back('/');
name_list.insert(tmp_buf);
}
}
std::set<std::string>::iterator pos, end = name_list.end();
for (pos = name_list.begin(); pos != end; pos++)
{
matches.AppendString((*pos).c_str());
}
return matches.GetSize() - extant_entries;
#else
return 0;
#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;
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 (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);
}
char resolved_path[PATH_MAX];
if (::realpath (unglobbed_path, resolved_path))
{
return ::snprintf(dst_path, dst_len, "%s", resolved_path);
}
else
{
return ::snprintf(dst_path, dst_len, "%s", unglobbed_path);
}
}
FileSpec::FileSpec() :
m_directory(),
m_filename()
{
}
FileSpec::FileSpec(const char *pathname, bool resolve_path) :
m_directory(),
m_filename(),
m_is_resolved(false)
{
if (pathname && pathname[0])
SetFile(pathname, resolve_path);
}
FileSpec::FileSpec(const FileSpec& rhs) :
m_directory (rhs.m_directory),
m_filename (rhs.m_filename),
m_is_resolved (rhs.m_is_resolved)
{
}
FileSpec::FileSpec(const FileSpec* rhs) :
m_directory(),
m_filename()
{
if (rhs)
*this = *rhs;
}
FileSpec::~FileSpec()
{
}
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;
}
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
{
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);
if (strchr(resolved_path, '/'))
{
char *directory = ::dirname (resolved_path);
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);
}
}
FileSpec::operator bool() const
{
return m_filename || m_directory;
}
bool
FileSpec::operator!() const
{
return !m_directory && !m_filename;
}
bool
FileSpec::operator== (const FileSpec& rhs) const
{
if (m_filename == rhs.m_filename)
{
if (m_directory == rhs.m_directory)
return true;
if (IsResolved() && rhs.IsResolved())
{
return false;
}
FileSpec resolved_lhs(*this);
if (!IsResolved())
{
if (resolved_lhs.ResolvePath())
{
m_is_resolved = (m_directory == resolved_lhs.m_directory);
}
else
return false;
}
FileSpec resolved_rhs(rhs);
if (!rhs.IsResolved())
{
if (resolved_rhs.ResolvePath())
{
rhs.m_is_resolved = (rhs.m_directory == resolved_rhs.m_directory);
}
else
return false;
}
return resolved_lhs.GetDirectory() == resolved_rhs.GetDirectory();
}
return false;
}
bool
FileSpec::operator!= (const FileSpec& rhs) const
{
return !(*this == rhs);
}
bool
FileSpec::operator< (const FileSpec& rhs) const
{
return FileSpec::Compare(*this, rhs, true) < 0;
}
Stream&
lldb_private::operator << (Stream &s, const FileSpec& f)
{
f.Dump(&s);
return s;
}
void
FileSpec::Clear()
{
m_directory.Clear();
m_filename.Clear();
}
int
FileSpec::Compare(const FileSpec& a, const FileSpec& b, bool full)
{
int result = 0;
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 && (a.GetDirectory().IsEmpty() || b.GetDirectory().IsEmpty()))
return a.m_filename == b.m_filename;
else
return a == b;
}
void
FileSpec::Dump(Stream *s) const
{
if (s)
{
m_directory.Dump(s);
if (m_directory)
s->PutChar('/');
m_filename.Dump(s);
}
}
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);
if (! dir_ref.empty())
{
if (strcmp (".", dir_ref.data()) == 0)
return false;
m_directory.SetCString (dir_ref.data());
if (Exists())
return true;
else
{
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;
char path_buf[PATH_MAX];
if (!GetPath (path_buf, PATH_MAX))
return false;
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;
}
ConstString &
FileSpec::GetDirectory()
{
return m_directory;
}
const ConstString &
FileSpec::GetDirectory() const
{
return m_directory;
}
ConstString &
FileSpec::GetFilename()
{
return m_filename;
}
const ConstString &
FileSpec::GetFilename() const
{
return m_filename;
}
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);
}
}
if (path)
path[0] = '\0';
return 0;
}
ConstString
FileSpec::GetFileNameExtension () const
{
if (m_filename)
{
const char *filename = m_filename.GetCString();
const char* dot_pos = strrchr(filename, '.');
if (dot_pos && dot_pos[1] != '\0')
return ConstString(dot_pos+1);
}
return ConstString();
}
ConstString
FileSpec::GetFileNameStrippingExtension () const
{
const char *filename = m_filename.GetCString();
if (filename == NULL)
return ConstString();
const char* dot_pos = strrchr(filename, '.');
if (dot_pos == NULL)
return m_filename;
return ConstString(filename, dot_pos-filename);
}
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;
}
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, Error *error_ptr) const
{
Error error;
size_t bytes_read = 0;
char resolved_path[PATH_MAX];
if (GetPath(resolved_path, sizeof(resolved_path)))
{
File file;
error = file.Open(resolved_path, File::eOpenOptionRead);
if (error.Success())
{
off_t file_offset_after_seek = file_offset;
bytes_read = dst_len;
error = file.Read(dst, bytes_read, file_offset_after_seek);
}
}
else
{
error.SetErrorString("invalid file specification");
}
if (error_ptr)
*error_ptr = error;
return bytes_read;
}
DataBufferSP
FileSpec::ReadFileContents (off_t file_offset, size_t file_size, Error *error_ptr) const
{
Error error;
DataBufferSP data_sp;
char resolved_path[PATH_MAX];
if (GetPath(resolved_path, sizeof(resolved_path)))
{
File file;
error = file.Open(resolved_path, File::eOpenOptionRead);
if (error.Success())
{
const bool null_terminate = false;
error = file.Read (file_size, file_offset, null_terminate, data_sp);
}
}
else
{
error.SetErrorString("invalid file specification");
}
if (error_ptr)
*error_ptr = error;
return data_sp;
}
DataBufferSP
FileSpec::ReadFileContentsAsCString(Error *error_ptr)
{
Error error;
DataBufferSP data_sp;
char resolved_path[PATH_MAX];
if (GetPath(resolved_path, sizeof(resolved_path)))
{
File file;
error = file.Open(resolved_path, File::eOpenOptionRead);
if (error.Success())
{
off_t offset = 0;
size_t length = SIZE_MAX;
const bool null_terminate = true;
error = file.Read (length, offset, null_terminate, data_sp);
}
}
else
{
error.SetErrorString("invalid file specification");
}
if (error_ptr)
*error_ptr = error;
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)
{
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;
#if !defined(__OpenBSD__)
case DT_WHT: file_type = eFileTypeOther; call_callback = find_other; break;
#endif
}
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 < (int)(sizeof(child_path) - 1))
{
FileSpec child_path_spec (child_path, false);
EnumerateDirectoryResult result = callback (callback_baton, file_type, child_path_spec);
switch (result)
{
default:
case eEnumerateDirectoryResultNext:
break;
case eEnumerateDirectoryResultEnter: if (FileSpec::EnumerateDirectory (child_path,
find_directories,
find_files,
find_other,
callback,
callback_baton) == eEnumerateDirectoryResultQuit)
{
return eEnumerateDirectoryResultQuit;
}
break;
case eEnumerateDirectoryResultExit: return eEnumerateDirectoryResultNext;
case eEnumerateDirectoryResultQuit: return eEnumerateDirectoryResultQuit;
}
}
}
}
}
}
return eEnumerateDirectoryResultNext;
}
bool
FileSpec::IsSourceImplementationFile () const
{
ConstString extension (GetFileNameExtension());
if (extension)
{
static RegularExpression g_source_file_regex ("^(c|m|mm|cpp|c\\+\\+|cxx|cc|cp|s|asm|f|f77|f90|f95|f03|for|ftn|fpp|ada|adb|ads)$",
REG_EXTENDED | REG_ICASE);
return g_source_file_regex.Execute (extension.GetCString());
}
return false;
}
bool
FileSpec::IsRelativeToCurrentWorkingDirectory () const
{
const char *directory = m_directory.GetCString();
if (directory && directory[0])
{
switch (directory[0])
{
case '/':
case '~':
return false;
default:
return true;
}
}
else if (m_filename)
{
return true;
}
return false;
}