//===-- PlatformPOSIX.cpp ---------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "PlatformPOSIX.h"

#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/FunctionCaller.h"
#include "lldb/Expression/UserExpression.h"
#include "lldb/Expression/UtilityFunction.h"
#include "lldb/Host/File.h"
#include "lldb/Host/FileCache.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/ADT/ScopeExit.h"

using namespace lldb;
using namespace lldb_private;

/// Default Constructor
PlatformPOSIX::PlatformPOSIX(bool is_host)
    : RemoteAwarePlatform(is_host), // This is the local host platform
      m_option_group_platform_rsync(new OptionGroupPlatformRSync()),
      m_option_group_platform_ssh(new OptionGroupPlatformSSH()),
      m_option_group_platform_caching(new OptionGroupPlatformCaching()) {}

/// Destructor.
///
/// The destructor is virtual since this class is designed to be
/// inherited from by the plug-in instance.
PlatformPOSIX::~PlatformPOSIX() {}

lldb_private::OptionGroupOptions *PlatformPOSIX::GetConnectionOptions(
    lldb_private::CommandInterpreter &interpreter) {
  auto iter = m_options.find(&interpreter), end = m_options.end();
  if (iter == end) {
    std::unique_ptr<lldb_private::OptionGroupOptions> options(
        new OptionGroupOptions());
    options->Append(m_option_group_platform_rsync.get());
    options->Append(m_option_group_platform_ssh.get());
    options->Append(m_option_group_platform_caching.get());
    m_options[&interpreter] = std::move(options);
  }

  return m_options.at(&interpreter).get();
}

Status
PlatformPOSIX::ResolveExecutable(const ModuleSpec &module_spec,
                                 lldb::ModuleSP &exe_module_sp,
                                 const FileSpecList *module_search_paths_ptr) {
  Status error;
  // Nothing special to do here, just use the actual file and architecture

  char exe_path[PATH_MAX];
  ModuleSpec resolved_module_spec(module_spec);

  if (IsHost()) {
    // If we have "ls" as the exe_file, resolve the executable location based
    // on the current path variables
    if (!FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec())) {
      resolved_module_spec.GetFileSpec().GetPath(exe_path, sizeof(exe_path));
      resolved_module_spec.GetFileSpec().SetFile(exe_path,
                                                 FileSpec::Style::native);
      FileSystem::Instance().Resolve(resolved_module_spec.GetFileSpec());
    }

    if (!FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec()))
      FileSystem::Instance().ResolveExecutableLocation(
          resolved_module_spec.GetFileSpec());

    // Resolve any executable within a bundle on MacOSX
    Host::ResolveExecutableInBundle(resolved_module_spec.GetFileSpec());

    if (FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec()))
      error.Clear();
    else {
      const uint32_t permissions = FileSystem::Instance().GetPermissions(
          resolved_module_spec.GetFileSpec());
      if (permissions && (permissions & eFilePermissionsEveryoneR) == 0)
        error.SetErrorStringWithFormat(
            "executable '%s' is not readable",
            resolved_module_spec.GetFileSpec().GetPath().c_str());
      else
        error.SetErrorStringWithFormat(
            "unable to find executable for '%s'",
            resolved_module_spec.GetFileSpec().GetPath().c_str());
    }
  } else {
    if (m_remote_platform_sp) {
      error =
          GetCachedExecutable(resolved_module_spec, exe_module_sp,
                              module_search_paths_ptr, *m_remote_platform_sp);
    } else {
      // We may connect to a process and use the provided executable (Don't use
      // local $PATH).

      // Resolve any executable within a bundle on MacOSX
      Host::ResolveExecutableInBundle(resolved_module_spec.GetFileSpec());

      if (FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec()))
        error.Clear();
      else
        error.SetErrorStringWithFormat("the platform is not currently "
                                       "connected, and '%s' doesn't exist in "
                                       "the system root.",
                                       exe_path);
    }
  }

  if (error.Success()) {
    if (resolved_module_spec.GetArchitecture().IsValid()) {
      error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp,
                                          module_search_paths_ptr, nullptr, nullptr);
      if (error.Fail()) {
        // If we failed, it may be because the vendor and os aren't known. If
	// that is the case, try setting them to the host architecture and give
	// it another try.
        llvm::Triple &module_triple =
            resolved_module_spec.GetArchitecture().GetTriple();
        bool is_vendor_specified =
            (module_triple.getVendor() != llvm::Triple::UnknownVendor);
        bool is_os_specified =
            (module_triple.getOS() != llvm::Triple::UnknownOS);
        if (!is_vendor_specified || !is_os_specified) {
          const llvm::Triple &host_triple =
              HostInfo::GetArchitecture(HostInfo::eArchKindDefault).GetTriple();

          if (!is_vendor_specified)
            module_triple.setVendorName(host_triple.getVendorName());
          if (!is_os_specified)
            module_triple.setOSName(host_triple.getOSName());

          error = ModuleList::GetSharedModule(resolved_module_spec,
                                              exe_module_sp, module_search_paths_ptr, nullptr, nullptr);
        }
      }

      // TODO find out why exe_module_sp might be NULL
      if (error.Fail() || !exe_module_sp || !exe_module_sp->GetObjectFile()) {
        exe_module_sp.reset();
        error.SetErrorStringWithFormat(
            "'%s' doesn't contain the architecture %s",
            resolved_module_spec.GetFileSpec().GetPath().c_str(),
            resolved_module_spec.GetArchitecture().GetArchitectureName());
      }
    } else {
      // No valid architecture was specified, ask the platform for the
      // architectures that we should be using (in the correct order) and see
      // if we can find a match that way
      StreamString arch_names;
      for (uint32_t idx = 0; GetSupportedArchitectureAtIndex(
               idx, resolved_module_spec.GetArchitecture());
           ++idx) {
        error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp,
                                            module_search_paths_ptr, nullptr, nullptr);
        // Did we find an executable using one of the
        if (error.Success()) {
          if (exe_module_sp && exe_module_sp->GetObjectFile())
            break;
          else
            error.SetErrorToGenericError();
        }

        if (idx > 0)
          arch_names.PutCString(", ");
        arch_names.PutCString(
            resolved_module_spec.GetArchitecture().GetArchitectureName());
      }

      if (error.Fail() || !exe_module_sp) {
        if (FileSystem::Instance().Readable(
                resolved_module_spec.GetFileSpec())) {
          error.SetErrorStringWithFormat(
              "'%s' doesn't contain any '%s' platform architectures: %s",
              resolved_module_spec.GetFileSpec().GetPath().c_str(),
              GetPluginName().GetCString(), arch_names.GetData());
        } else {
          error.SetErrorStringWithFormat(
              "'%s' is not readable",
              resolved_module_spec.GetFileSpec().GetPath().c_str());
        }
      }
    }
  }

  return error;
}

static uint32_t chown_file(Platform *platform, const char *path,
                           uint32_t uid = UINT32_MAX,
                           uint32_t gid = UINT32_MAX) {
  if (!platform || !path || *path == 0)
    return UINT32_MAX;

  if (uid == UINT32_MAX && gid == UINT32_MAX)
    return 0; // pretend I did chown correctly - actually I just didn't care

  StreamString command;
  command.PutCString("chown ");
  if (uid != UINT32_MAX)
    command.Printf("%d", uid);
  if (gid != UINT32_MAX)
    command.Printf(":%d", gid);
  command.Printf("%s", path);
  int status;
  platform->RunShellCommand(command.GetData(), nullptr, &status, nullptr,
                            nullptr, std::chrono::seconds(10));
  return status;
}

lldb_private::Status
PlatformPOSIX::PutFile(const lldb_private::FileSpec &source,
                       const lldb_private::FileSpec &destination, uint32_t uid,
                       uint32_t gid) {
  Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));

  if (IsHost()) {
    if (FileSpec::Equal(source, destination, true))
      return Status();
    // cp src dst
    // chown uid:gid dst
    std::string src_path(source.GetPath());
    if (src_path.empty())
      return Status("unable to get file path for source");
    std::string dst_path(destination.GetPath());
    if (dst_path.empty())
      return Status("unable to get file path for destination");
    StreamString command;
    command.Printf("cp %s %s", src_path.c_str(), dst_path.c_str());
    int status;
    RunShellCommand(command.GetData(), nullptr, &status, nullptr, nullptr,
                    std::chrono::seconds(10));
    if (status != 0)
      return Status("unable to perform copy");
    if (uid == UINT32_MAX && gid == UINT32_MAX)
      return Status();
    if (chown_file(this, dst_path.c_str(), uid, gid) != 0)
      return Status("unable to perform chown");
    return Status();
  } else if (m_remote_platform_sp) {
    if (GetSupportsRSync()) {
      std::string src_path(source.GetPath());
      if (src_path.empty())
        return Status("unable to get file path for source");
      std::string dst_path(destination.GetPath());
      if (dst_path.empty())
        return Status("unable to get file path for destination");
      StreamString command;
      if (GetIgnoresRemoteHostname()) {
        if (!GetRSyncPrefix())
          command.Printf("rsync %s %s %s", GetRSyncOpts(), src_path.c_str(),
                         dst_path.c_str());
        else
          command.Printf("rsync %s %s %s%s", GetRSyncOpts(), src_path.c_str(),
                         GetRSyncPrefix(), dst_path.c_str());
      } else
        command.Printf("rsync %s %s %s:%s", GetRSyncOpts(), src_path.c_str(),
                       GetHostname(), dst_path.c_str());
      LLDB_LOGF(log, "[PutFile] Running command: %s\n", command.GetData());
      int retcode;
      Host::RunShellCommand(command.GetData(), nullptr, &retcode, nullptr,
                            nullptr, std::chrono::minutes(1));
      if (retcode == 0) {
        // Don't chown a local file for a remote system
        //                if (chown_file(this,dst_path.c_str(),uid,gid) != 0)
        //                    return Status("unable to perform chown");
        return Status();
      }
      // if we are still here rsync has failed - let's try the slow way before
      // giving up
    }
  }
  return Platform::PutFile(source, destination, uid, gid);
}

lldb_private::Status PlatformPOSIX::GetFile(
    const lldb_private::FileSpec &source,      // remote file path
    const lldb_private::FileSpec &destination) // local file path
{
  Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));

  // Check the args, first.
  std::string src_path(source.GetPath());
  if (src_path.empty())
    return Status("unable to get file path for source");
  std::string dst_path(destination.GetPath());
  if (dst_path.empty())
    return Status("unable to get file path for destination");
  if (IsHost()) {
    if (FileSpec::Equal(source, destination, true))
      return Status("local scenario->source and destination are the same file "
                    "path: no operation performed");
    // cp src dst
    StreamString cp_command;
    cp_command.Printf("cp %s %s", src_path.c_str(), dst_path.c_str());
    int status;
    RunShellCommand(cp_command.GetData(), nullptr, &status, nullptr, nullptr,
                    std::chrono::seconds(10));
    if (status != 0)
      return Status("unable to perform copy");
    return Status();
  } else if (m_remote_platform_sp) {
    if (GetSupportsRSync()) {
      StreamString command;
      if (GetIgnoresRemoteHostname()) {
        if (!GetRSyncPrefix())
          command.Printf("rsync %s %s %s", GetRSyncOpts(), src_path.c_str(),
                         dst_path.c_str());
        else
          command.Printf("rsync %s %s%s %s", GetRSyncOpts(), GetRSyncPrefix(),
                         src_path.c_str(), dst_path.c_str());
      } else
        command.Printf("rsync %s %s:%s %s", GetRSyncOpts(),
                       m_remote_platform_sp->GetHostname(), src_path.c_str(),
                       dst_path.c_str());
      LLDB_LOGF(log, "[GetFile] Running command: %s\n", command.GetData());
      int retcode;
      Host::RunShellCommand(command.GetData(), nullptr, &retcode, nullptr,
                            nullptr, std::chrono::minutes(1));
      if (retcode == 0)
        return Status();
      // If we are here, rsync has failed - let's try the slow way before
      // giving up
    }
    // open src and dst
    // read/write, read/write, read/write, ...
    // close src
    // close dst
    LLDB_LOGF(log, "[GetFile] Using block by block transfer....\n");
    Status error;
    user_id_t fd_src = OpenFile(source, File::eOpenOptionRead,
                                lldb::eFilePermissionsFileDefault, error);

    if (fd_src == UINT64_MAX)
      return Status("unable to open source file");

    uint32_t permissions = 0;
    error = GetFilePermissions(source, permissions);

    if (permissions == 0)
      permissions = lldb::eFilePermissionsFileDefault;

    user_id_t fd_dst = FileCache::GetInstance().OpenFile(
        destination, File::eOpenOptionCanCreate | File::eOpenOptionWrite |
                         File::eOpenOptionTruncate,
        permissions, error);

    if (fd_dst == UINT64_MAX) {
      if (error.Success())
        error.SetErrorString("unable to open destination file");
    }

    if (error.Success()) {
      lldb::DataBufferSP buffer_sp(new DataBufferHeap(1024, 0));
      uint64_t offset = 0;
      error.Clear();
      while (error.Success()) {
        const uint64_t n_read = ReadFile(fd_src, offset, buffer_sp->GetBytes(),
                                         buffer_sp->GetByteSize(), error);
        if (error.Fail())
          break;
        if (n_read == 0)
          break;
        if (FileCache::GetInstance().WriteFile(fd_dst, offset,
                                               buffer_sp->GetBytes(), n_read,
                                               error) != n_read) {
          if (!error.Fail())
            error.SetErrorString("unable to write to destination file");
          break;
        }
        offset += n_read;
      }
    }
    // Ignore the close error of src.
    if (fd_src != UINT64_MAX)
      CloseFile(fd_src, error);
    // And close the dst file descriptot.
    if (fd_dst != UINT64_MAX &&
        !FileCache::GetInstance().CloseFile(fd_dst, error)) {
      if (!error.Fail())
        error.SetErrorString("unable to close destination file");
    }
    return error;
  }
  return Platform::GetFile(source, destination);
}

std::string PlatformPOSIX::GetPlatformSpecificConnectionInformation() {
  StreamString stream;
  if (GetSupportsRSync()) {
    stream.PutCString("rsync");
    if ((GetRSyncOpts() && *GetRSyncOpts()) ||
        (GetRSyncPrefix() && *GetRSyncPrefix()) || GetIgnoresRemoteHostname()) {
      stream.Printf(", options: ");
      if (GetRSyncOpts() && *GetRSyncOpts())
        stream.Printf("'%s' ", GetRSyncOpts());
      stream.Printf(", prefix: ");
      if (GetRSyncPrefix() && *GetRSyncPrefix())
        stream.Printf("'%s' ", GetRSyncPrefix());
      if (GetIgnoresRemoteHostname())
        stream.Printf("ignore remote-hostname ");
    }
  }
  if (GetSupportsSSH()) {
    stream.PutCString("ssh");
    if (GetSSHOpts() && *GetSSHOpts())
      stream.Printf(", options: '%s' ", GetSSHOpts());
  }
  if (GetLocalCacheDirectory() && *GetLocalCacheDirectory())
    stream.Printf("cache dir: %s", GetLocalCacheDirectory());
  if (stream.GetSize())
    return stream.GetString();
  else
    return "";
}

const lldb::UnixSignalsSP &PlatformPOSIX::GetRemoteUnixSignals() {
  if (IsRemote() && m_remote_platform_sp)
    return m_remote_platform_sp->GetRemoteUnixSignals();
  return Platform::GetRemoteUnixSignals();
}

Status PlatformPOSIX::ConnectRemote(Args &args) {
  Status error;
  if (IsHost()) {
    error.SetErrorStringWithFormat(
        "can't connect to the host platform '%s', always connected",
        GetPluginName().GetCString());
  } else {
    if (!m_remote_platform_sp)
      m_remote_platform_sp =
          Platform::Create(ConstString("remote-gdb-server"), error);

    if (m_remote_platform_sp && error.Success())
      error = m_remote_platform_sp->ConnectRemote(args);
    else
      error.SetErrorString("failed to create a 'remote-gdb-server' platform");

    if (error.Fail())
      m_remote_platform_sp.reset();
  }

  if (error.Success() && m_remote_platform_sp) {
    if (m_option_group_platform_rsync.get() &&
        m_option_group_platform_ssh.get() &&
        m_option_group_platform_caching.get()) {
      if (m_option_group_platform_rsync->m_rsync) {
        SetSupportsRSync(true);
        SetRSyncOpts(m_option_group_platform_rsync->m_rsync_opts.c_str());
        SetRSyncPrefix(m_option_group_platform_rsync->m_rsync_prefix.c_str());
        SetIgnoresRemoteHostname(
            m_option_group_platform_rsync->m_ignores_remote_hostname);
      }
      if (m_option_group_platform_ssh->m_ssh) {
        SetSupportsSSH(true);
        SetSSHOpts(m_option_group_platform_ssh->m_ssh_opts.c_str());
      }
      SetLocalCacheDirectory(
          m_option_group_platform_caching->m_cache_dir.c_str());
    }
  }

  return error;
}

Status PlatformPOSIX::DisconnectRemote() {
  Status error;

  if (IsHost()) {
    error.SetErrorStringWithFormat(
        "can't disconnect from the host platform '%s', always connected",
        GetPluginName().GetCString());
  } else {
    if (m_remote_platform_sp)
      error = m_remote_platform_sp->DisconnectRemote();
    else
      error.SetErrorString("the platform is not currently connected");
  }
  return error;
}

lldb::ProcessSP PlatformPOSIX::Attach(ProcessAttachInfo &attach_info,
                                      Debugger &debugger, Target *target,
                                      Status &error) {
  lldb::ProcessSP process_sp;
  Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));

  if (IsHost()) {
    if (target == nullptr) {
      TargetSP new_target_sp;

      error = debugger.GetTargetList().CreateTarget(
          debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
      target = new_target_sp.get();
      LLDB_LOGF(log, "PlatformPOSIX::%s created new target", __FUNCTION__);
    } else {
      error.Clear();
      LLDB_LOGF(log, "PlatformPOSIX::%s target already existed, setting target",
                __FUNCTION__);
    }

    if (target && error.Success()) {
      debugger.GetTargetList().SetSelectedTarget(target);
      if (log) {
        ModuleSP exe_module_sp = target->GetExecutableModule();
        LLDB_LOGF(log, "PlatformPOSIX::%s set selected target to %p %s",
                  __FUNCTION__, (void *)target,
                  exe_module_sp ? exe_module_sp->GetFileSpec().GetPath().c_str()
                                : "<null>");
      }

      process_sp =
          target->CreateProcess(attach_info.GetListenerForProcess(debugger),
                                attach_info.GetProcessPluginName(), nullptr);

      if (process_sp) {
        ListenerSP listener_sp = attach_info.GetHijackListener();
        if (listener_sp == nullptr) {
          listener_sp =
              Listener::MakeListener("lldb.PlatformPOSIX.attach.hijack");
          attach_info.SetHijackListener(listener_sp);
        }
        process_sp->HijackProcessEvents(listener_sp);
        error = process_sp->Attach(attach_info);
      }
    }
  } else {
    if (m_remote_platform_sp)
      process_sp =
          m_remote_platform_sp->Attach(attach_info, debugger, target, error);
    else
      error.SetErrorString("the platform is not currently connected");
  }
  return process_sp;
}

lldb::ProcessSP
PlatformPOSIX::DebugProcess(ProcessLaunchInfo &launch_info, Debugger &debugger,
                            Target *target, // Can be NULL, if NULL create a new
                                            // target, else use existing one
                            Status &error) {
  ProcessSP process_sp;

  if (IsHost()) {
    // We are going to hand this process off to debugserver which will be in
    // charge of setting the exit status.  However, we still need to reap it
    // from lldb. So, make sure we use a exit callback which does not set exit
    // status.
    const bool monitor_signals = false;
    launch_info.SetMonitorProcessCallback(
        &ProcessLaunchInfo::NoOpMonitorCallback, monitor_signals);
    process_sp = Platform::DebugProcess(launch_info, debugger, target, error);
  } else {
    if (m_remote_platform_sp)
      process_sp = m_remote_platform_sp->DebugProcess(launch_info, debugger,
                                                      target, error);
    else
      error.SetErrorString("the platform is not currently connected");
  }
  return process_sp;
}

void PlatformPOSIX::CalculateTrapHandlerSymbolNames() {
  m_trap_handlers.push_back(ConstString("_sigtramp"));
}

Status PlatformPOSIX::EvaluateLibdlExpression(
    lldb_private::Process *process, const char *expr_cstr,
    llvm::StringRef expr_prefix, lldb::ValueObjectSP &result_valobj_sp) {
  DynamicLoader *loader = process->GetDynamicLoader();
  if (loader) {
    Status error = loader->CanLoadImage();
    if (error.Fail())
      return error;
  }

  ThreadSP thread_sp(process->GetThreadList().GetExpressionExecutionThread());
  if (!thread_sp)
    return Status("Selected thread isn't valid");

  StackFrameSP frame_sp(thread_sp->GetStackFrameAtIndex(0));
  if (!frame_sp)
    return Status("Frame 0 isn't valid");

  ExecutionContext exe_ctx;
  frame_sp->CalculateExecutionContext(exe_ctx);
  EvaluateExpressionOptions expr_options;
  expr_options.SetUnwindOnError(true);
  expr_options.SetIgnoreBreakpoints(true);
  expr_options.SetExecutionPolicy(eExecutionPolicyAlways);
  expr_options.SetLanguage(eLanguageTypeC_plus_plus);
  expr_options.SetTrapExceptions(false); // dlopen can't throw exceptions, so
                                         // don't do the work to trap them.
  expr_options.SetTimeout(process->GetUtilityExpressionTimeout());

  Status expr_error;
  ExpressionResults result =
      UserExpression::Evaluate(exe_ctx, expr_options, expr_cstr, expr_prefix,
                               result_valobj_sp, expr_error);
  if (result != eExpressionCompleted)
    return expr_error;

  if (result_valobj_sp->GetError().Fail())
    return result_valobj_sp->GetError();
  return Status();
}

std::unique_ptr<UtilityFunction>
PlatformPOSIX::MakeLoadImageUtilityFunction(ExecutionContext &exe_ctx,
                                            Status &error) {
  // Remember to prepend this with the prefix from
  // GetLibdlFunctionDeclarations. The returned values are all in
  // __lldb_dlopen_result for consistency. The wrapper returns a void * but
  // doesn't use it because UtilityFunctions don't work with void returns at
  // present.
  static const char *dlopen_wrapper_code = R"(
  struct __lldb_dlopen_result {
    void *image_ptr;
    const char *error_str;
  };
  
  extern void *memcpy(void *, const void *, size_t size);
  extern size_t strlen(const char *);
  

  void * __lldb_dlopen_wrapper (const char *name, 
                                const char *path_strings,
                                char *buffer,
                                __lldb_dlopen_result *result_ptr)
  {
    // This is the case where the name is the full path:
    if (!path_strings) {
      result_ptr->image_ptr = dlopen(name, 2);
      if (result_ptr->image_ptr)
        result_ptr->error_str = nullptr;
      return nullptr;
    }
    
    // This is the case where we have a list of paths:
    size_t name_len = strlen(name);
    while (path_strings && path_strings[0] != '\0') {
      size_t path_len = strlen(path_strings);
      memcpy((void *) buffer, (void *) path_strings, path_len);
      buffer[path_len] = '/';
      char *target_ptr = buffer+path_len+1; 
      memcpy((void *) target_ptr, (void *) name, name_len + 1);
      result_ptr->image_ptr = dlopen(buffer, 2);
      if (result_ptr->image_ptr) {
        result_ptr->error_str = nullptr;
        break;
      }
      result_ptr->error_str = dlerror();
      path_strings = path_strings + path_len + 1;
    }
    return nullptr;
  }
  )";

  static const char *dlopen_wrapper_name = "__lldb_dlopen_wrapper";
  Process *process = exe_ctx.GetProcessSP().get();
  // Insert the dlopen shim defines into our generic expression:
  std::string expr(GetLibdlFunctionDeclarations(process));
  expr.append(dlopen_wrapper_code);
  Status utility_error;
  DiagnosticManager diagnostics;
  
  std::unique_ptr<UtilityFunction> dlopen_utility_func_up(process
      ->GetTarget().GetUtilityFunctionForLanguage(expr.c_str(),
                                                  eLanguageTypeObjC,
                                                  dlopen_wrapper_name,
                                                  utility_error));
  if (utility_error.Fail()) {
    error.SetErrorStringWithFormat("dlopen error: could not make utility"
                                   "function: %s", utility_error.AsCString());
    return nullptr;
  }
  if (!dlopen_utility_func_up->Install(diagnostics, exe_ctx)) {
    error.SetErrorStringWithFormat("dlopen error: could not install utility"
                                   "function: %s", 
                                   diagnostics.GetString().c_str());
    return nullptr;
  }

  Value value;
  ValueList arguments;
  FunctionCaller *do_dlopen_function = nullptr;

  // Fetch the clang types we will need:
  ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext();

  CompilerType clang_void_pointer_type
      = ast->GetBasicType(eBasicTypeVoid).GetPointerType();
  CompilerType clang_char_pointer_type
        = ast->GetBasicType(eBasicTypeChar).GetPointerType();

  // We are passing four arguments, the basename, the list of places to look,
  // a buffer big enough for all the path + name combos, and
  // a pointer to the storage we've made for the result:
  value.SetValueType(Value::eValueTypeScalar);
  value.SetCompilerType(clang_void_pointer_type);
  arguments.PushValue(value);
  value.SetCompilerType(clang_char_pointer_type);
  arguments.PushValue(value);
  arguments.PushValue(value);
  arguments.PushValue(value);
  
  do_dlopen_function = dlopen_utility_func_up->MakeFunctionCaller(
      clang_void_pointer_type, arguments, exe_ctx.GetThreadSP(), utility_error);
  if (utility_error.Fail()) {
    error.SetErrorStringWithFormat("dlopen error: could not make function"
                                   "caller: %s", utility_error.AsCString());
    return nullptr;
  }
  
  do_dlopen_function = dlopen_utility_func_up->GetFunctionCaller();
  if (!do_dlopen_function) {
    error.SetErrorString("dlopen error: could not get function caller.");
    return nullptr;
  }
  
  // We made a good utility function, so cache it in the process:
  return dlopen_utility_func_up;
}

uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
                                    const lldb_private::FileSpec &remote_file,
                                    const std::vector<std::string> *paths,
                                    lldb_private::Status &error,
                                    lldb_private::FileSpec *loaded_image) {
  if (loaded_image)
    loaded_image->Clear();

  std::string path;
  path = remote_file.GetPath();
  
  ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread();
  if (!thread_sp) {
    error.SetErrorString("dlopen error: no thread available to call dlopen.");
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  DiagnosticManager diagnostics;
  
  ExecutionContext exe_ctx;
  thread_sp->CalculateExecutionContext(exe_ctx);

  Status utility_error;
  UtilityFunction *dlopen_utility_func;
  ValueList arguments;
  FunctionCaller *do_dlopen_function = nullptr;

  // The UtilityFunction is held in the Process.  Platforms don't track the
  // lifespan of the Targets that use them, we can't put this in the Platform.
  dlopen_utility_func = process->GetLoadImageUtilityFunction(
      this, [&]() -> std::unique_ptr<UtilityFunction> {
        return MakeLoadImageUtilityFunction(exe_ctx, error);
      });
  // If we couldn't make it, the error will be in error, so we can exit here.
  if (!dlopen_utility_func)
    return LLDB_INVALID_IMAGE_TOKEN;
    
  do_dlopen_function = dlopen_utility_func->GetFunctionCaller();
  if (!do_dlopen_function) {
    error.SetErrorString("dlopen error: could not get function caller.");
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  arguments = do_dlopen_function->GetArgumentValues();
  
  // Now insert the path we are searching for and the result structure into the
  // target.
  uint32_t permissions = ePermissionsReadable|ePermissionsWritable;
  size_t path_len = path.size() + 1;
  lldb::addr_t path_addr = process->AllocateMemory(path_len, 
                                                   permissions,
                                                   utility_error);
  if (path_addr == LLDB_INVALID_ADDRESS) {
    error.SetErrorStringWithFormat("dlopen error: could not allocate memory"
                                    "for path: %s", utility_error.AsCString());
    return LLDB_INVALID_IMAGE_TOKEN;
  }

  // Make sure we deallocate the input string memory:
  auto path_cleanup = llvm::make_scope_exit([process, path_addr] {
    // Deallocate the buffer.
    process->DeallocateMemory(path_addr);
  });

  process->WriteMemory(path_addr, path.c_str(), path_len, utility_error);
  if (utility_error.Fail()) {
    error.SetErrorStringWithFormat("dlopen error: could not write path string:"
                                    " %s", utility_error.AsCString());
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  // Make space for our return structure.  It is two pointers big: the token
  // and the error string.
  const uint32_t addr_size = process->GetAddressByteSize();
  lldb::addr_t return_addr = process->CallocateMemory(2*addr_size,
                                                      permissions,
                                                      utility_error);
  if (utility_error.Fail()) {
    error.SetErrorStringWithFormat("dlopen error: could not allocate memory"
                                    "for path: %s", utility_error.AsCString());
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  // Make sure we deallocate the result structure memory
  auto return_cleanup = llvm::make_scope_exit([process, return_addr] {
    // Deallocate the buffer
    process->DeallocateMemory(return_addr);
  });

  // This will be the address of the storage for paths, if we are using them,
  // or nullptr to signal we aren't.
  lldb::addr_t path_array_addr = 0x0;
  llvm::Optional<llvm::detail::scope_exit<std::function<void()>>>
      path_array_cleanup;

  // This is the address to a buffer large enough to hold the largest path
  // conjoined with the library name we're passing in.  This is a convenience 
  // to avoid having to call malloc in the dlopen function.
  lldb::addr_t buffer_addr = 0x0;
  llvm::Optional<llvm::detail::scope_exit<std::function<void()>>>
      buffer_cleanup;

  // Set the values into our args and write them to the target:
  if (paths != nullptr) {
    // First insert the paths into the target.  This is expected to be a 
    // continuous buffer with the strings laid out null terminated and
    // end to end with an empty string terminating the buffer.
    // We also compute the buffer's required size as we go.
    size_t buffer_size = 0;
    std::string path_array;
    for (auto path : *paths) {
      // Don't insert empty paths, they will make us abort the path
      // search prematurely.
      if (path.empty())
        continue;
      size_t path_size = path.size();
      path_array.append(path);
      path_array.push_back('\0');
      if (path_size > buffer_size)
        buffer_size = path_size;
    }
    path_array.push_back('\0');
    
    path_array_addr = process->AllocateMemory(path_array.size(), 
                                              permissions,
                                              utility_error);
    if (path_array_addr == LLDB_INVALID_ADDRESS) {
      error.SetErrorStringWithFormat("dlopen error: could not allocate memory"
                                      "for path array: %s", 
                                      utility_error.AsCString());
      return LLDB_INVALID_IMAGE_TOKEN;
    }
    
    // Make sure we deallocate the paths array.
    path_array_cleanup.emplace([process, path_array_addr]() {
      // Deallocate the path array.
      process->DeallocateMemory(path_array_addr);
    });

    process->WriteMemory(path_array_addr, path_array.data(), 
                         path_array.size(), utility_error);

    if (utility_error.Fail()) {
      error.SetErrorStringWithFormat("dlopen error: could not write path array:"
                                     " %s", utility_error.AsCString());
      return LLDB_INVALID_IMAGE_TOKEN;
    }
    // Now make spaces in the target for the buffer.  We need to add one for
    // the '/' that the utility function will insert and one for the '\0':
    buffer_size += path.size() + 2;
    
    buffer_addr = process->AllocateMemory(buffer_size, 
                                          permissions,
                                          utility_error);
    if (buffer_addr == LLDB_INVALID_ADDRESS) {
      error.SetErrorStringWithFormat("dlopen error: could not allocate memory"
                                      "for buffer: %s", 
                                      utility_error.AsCString());
      return LLDB_INVALID_IMAGE_TOKEN;
    }
  
    // Make sure we deallocate the buffer memory:
    buffer_cleanup.emplace([process, buffer_addr]() {
      // Deallocate the buffer.
      process->DeallocateMemory(buffer_addr);
    });
  }
    
  arguments.GetValueAtIndex(0)->GetScalar() = path_addr;
  arguments.GetValueAtIndex(1)->GetScalar() = path_array_addr;
  arguments.GetValueAtIndex(2)->GetScalar() = buffer_addr;
  arguments.GetValueAtIndex(3)->GetScalar() = return_addr;

  lldb::addr_t func_args_addr = LLDB_INVALID_ADDRESS;
  
  diagnostics.Clear();
  if (!do_dlopen_function->WriteFunctionArguments(exe_ctx, 
                                                 func_args_addr,
                                                 arguments,
                                                 diagnostics)) {
    error.SetErrorStringWithFormat("dlopen error: could not write function "
                                   "arguments: %s", 
                                   diagnostics.GetString().c_str());
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  // Make sure we clean up the args structure.  We can't reuse it because the
  // Platform lives longer than the process and the Platforms don't get a
  // signal to clean up cached data when a process goes away.
  auto args_cleanup =
      llvm::make_scope_exit([do_dlopen_function, &exe_ctx, func_args_addr] {
        do_dlopen_function->DeallocateFunctionResults(exe_ctx, func_args_addr);
      });

  // Now run the caller:
  EvaluateExpressionOptions options;
  options.SetExecutionPolicy(eExecutionPolicyAlways);
  options.SetLanguage(eLanguageTypeC_plus_plus);
  options.SetIgnoreBreakpoints(true);
  options.SetUnwindOnError(true);
  options.SetTrapExceptions(false); // dlopen can't throw exceptions, so
                                    // don't do the work to trap them.
  options.SetTimeout(process->GetUtilityExpressionTimeout());
  options.SetIsForUtilityExpr(true);

  Value return_value;
  // Fetch the clang types we will need:
  ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext();

  CompilerType clang_void_pointer_type
      = ast->GetBasicType(eBasicTypeVoid).GetPointerType();

  return_value.SetCompilerType(clang_void_pointer_type);
  
  ExpressionResults results = do_dlopen_function->ExecuteFunction(
      exe_ctx, &func_args_addr, options, diagnostics, return_value);
  if (results != eExpressionCompleted) {
    error.SetErrorStringWithFormat("dlopen error: failed executing "
                                   "dlopen wrapper function: %s", 
                                   diagnostics.GetString().c_str());
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  // Read the dlopen token from the return area:
  lldb::addr_t token = process->ReadPointerFromMemory(return_addr, 
                                                      utility_error);
  if (utility_error.Fail()) {
    error.SetErrorStringWithFormat("dlopen error: could not read the return "
                                    "struct: %s", utility_error.AsCString());
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  // The dlopen succeeded!
  if (token != 0x0) {
    if (loaded_image && buffer_addr != 0x0)
    {
      // Capture the image which was loaded.  We leave it in the buffer on
      // exit from the dlopen function, so we can just read it from there:
      std::string name_string;
      process->ReadCStringFromMemory(buffer_addr, name_string, utility_error);
      if (utility_error.Success())
        loaded_image->SetFile(name_string, llvm::sys::path::Style::posix);
    }
    return process->AddImageToken(token);
  }
    
  // We got an error, lets read in the error string:
  std::string dlopen_error_str;
  lldb::addr_t error_addr 
    = process->ReadPointerFromMemory(return_addr + addr_size, utility_error);
  if (utility_error.Fail()) {
    error.SetErrorStringWithFormat("dlopen error: could not read error string: "
                                    "%s", utility_error.AsCString());
    return LLDB_INVALID_IMAGE_TOKEN;
  }
  
  size_t num_chars = process->ReadCStringFromMemory(error_addr + addr_size, 
                                                    dlopen_error_str, 
                                                    utility_error);
  if (utility_error.Success() && num_chars > 0)
    error.SetErrorStringWithFormat("dlopen error: %s",
                                   dlopen_error_str.c_str());
  else
    error.SetErrorStringWithFormat("dlopen failed for unknown reasons.");

  return LLDB_INVALID_IMAGE_TOKEN;
}

Status PlatformPOSIX::UnloadImage(lldb_private::Process *process,
                                  uint32_t image_token) {
  const addr_t image_addr = process->GetImagePtrFromToken(image_token);
  if (image_addr == LLDB_INVALID_ADDRESS)
    return Status("Invalid image token");

  StreamString expr;
  expr.Printf("dlclose((void *)0x%" PRIx64 ")", image_addr);
  llvm::StringRef prefix = GetLibdlFunctionDeclarations(process);
  lldb::ValueObjectSP result_valobj_sp;
  Status error = EvaluateLibdlExpression(process, expr.GetData(), prefix,
                                         result_valobj_sp);
  if (error.Fail())
    return error;

  if (result_valobj_sp->GetError().Fail())
    return result_valobj_sp->GetError();

  Scalar scalar;
  if (result_valobj_sp->ResolveValue(scalar)) {
    if (scalar.UInt(1))
      return Status("expression failed: \"%s\"", expr.GetData());
    process->ResetImageToken(image_token);
  }
  return Status();
}

llvm::StringRef
PlatformPOSIX::GetLibdlFunctionDeclarations(lldb_private::Process *process) {
  return R"(
              extern "C" void* dlopen(const char*, int);
              extern "C" void* dlsym(void*, const char*);
              extern "C" int   dlclose(void*);
              extern "C" char* dlerror(void);
             )";
}

size_t PlatformPOSIX::ConnectToWaitingProcesses(Debugger &debugger,
                                                Status &error) {
  if (m_remote_platform_sp)
    return m_remote_platform_sp->ConnectToWaitingProcesses(debugger, error);
  return Platform::ConnectToWaitingProcesses(debugger, error);
}

ConstString PlatformPOSIX::GetFullNameForDylib(ConstString basename) {
  if (basename.IsEmpty())
    return basename;

  StreamString stream;
  stream.Printf("lib%s.so", basename.GetCString());
  return ConstString(stream.GetString());
}
