// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "bin/exe_utils.h"

#include "bin/directory.h"
#include "bin/file.h"
#include "bin/platform.h"
#include "platform/utils.h"

namespace dart {
namespace bin {

static bool StartsWithPathSeparator(const char* path,
                                    const char* sep,
                                    intptr_t sep_length) {
  return (strncmp(path, sep, sep_length) == 0
#if defined(DART_HOST_OS_WINDOWS)
          // TODO(aam): GetExecutableName doesn't work reliably on Windows,
          || *path == '/'
#endif
  );  // NOLINT
}

// Returns the directory portion of a given path.
//
// If dir is nullptr, the result must be freed by the caller. Otherwise, the
// result is copied into dir.
static char* GetDirectoryFromPath(const char* path, char* dir) {
  const char* sep = File::PathSeparator();
  const intptr_t sep_length = strlen(sep);
  intptr_t path_len = strlen(path);

  for (intptr_t i = path_len - 1; i >= 0; --i) {
    const char* str = path + i;
    if (StartsWithPathSeparator(str, sep, sep_length)) {
      if (dir != nullptr) {
        strncpy(dir, path, i);
        dir[i] = '\0';
        return dir;
      } else {
        return Utils::StrNDup(path, i + 1);
      }
    }
  }
  return nullptr;
}

// Returns the file portion of a given path. Returned string is either
// `path` if no path separators are found or `path + separator_loc + sep_length`
// if a separator is found.
static const char* GetFileNameFromPath(const char* path) {
  const char* sep = File::PathSeparator();
  const intptr_t sep_length = strlen(sep);
  intptr_t path_len = strlen(path);

  for (intptr_t i = path_len - 1; i >= 0; --i) {
    const char* str = path + i;
    if (StartsWithPathSeparator(str, sep, sep_length)) {
      return str + sep_length;
    }
  }
  // No path separators, assume that path is a file name.
  return path;
}

CStringUniquePtr EXEUtils::GetDirectoryPrefixFromResolvedExeName() {
  const char* name = nullptr;
  const int kTargetSize = PATH_MAX;
  char target[kTargetSize];
  intptr_t target_size =
      Platform::ResolveExecutablePathInto(target, kTargetSize);
  if (target_size > 0 && target_size < kTargetSize - 1) {
    target[target_size] = 0;
    name = target;
  }
  if (name == nullptr) {
    name = Platform::GetExecutableName();
    target_size = strlen(name);
    ASSERT(target_size < kTargetSize);
  }
  Namespace* namespc = Namespace::Create(Namespace::Default());
  char* result;
  if (File::GetType(namespc, name, false) == File::kIsLink) {
    char dir_path[kTargetSize];
    // cwd is currently wherever we launched from, so set the cwd to the
    // directory of the symlink while we try and resolve it. If we don't
    // do this, we won't be able to properly resolve relative paths.
    CStringUniquePtr initial_dir_path(Directory::CurrentNoScope());
    // We might run into symlinks of symlinks, so make sure we follow the
    // links all the way. See https://github.com/dart-lang/sdk/issues/41057 for
    // an example where this happens with brew on MacOS.
    do {
      Directory::SetCurrent(namespc, GetDirectoryFromPath(name, dir_path));
      // Resolve the link without creating Dart scope String.
      name = File::LinkTarget(namespc, GetFileNameFromPath(name), target,
                              kTargetSize);
      if (name == nullptr) {
        return CStringUniquePtr(Utils::StrDup(""));
      }
    } while (File::GetType(namespc, name, false) == File::kIsLink);
    target_size = strlen(name);

    char absolute_path[kTargetSize];

    // Get the absolute path after we've resolved all the symlinks and before
    // we reset the cwd, otherwise path resolution will fail.
    File::GetCanonicalPath(namespc, name, absolute_path, kTargetSize);

    // Reset cwd to the original value.
    Directory::SetCurrent(namespc, initial_dir_path.get());

    result = GetDirectoryFromPath(absolute_path, nullptr);
  } else {
    result = GetDirectoryFromPath(target, nullptr);
  }
  namespc->Release();
  return CStringUniquePtr(result == nullptr ? Utils::StrDup("") : result);
}

CStringUniquePtr EXEUtils::GetDirectoryPrefixFromUnresolvedExeName() {
  const char* exe = Platform::GetExecutableName();
  return CStringUniquePtr(exe == nullptr ? nullptr
                                         : GetDirectoryFromPath(exe, nullptr));
}

#if !defined(DART_HOST_OS_WINDOWS)
void EXEUtils::LoadDartProfilerSymbols(const char* argv0) {
  char* path = reinterpret_cast<char*>(malloc(PATH_MAX + 5));
  if (Platform::ResolveExecutablePathInto(path, PATH_MAX) <= 0) return;

  int len = strlen(path);
  memcpy(path + len, ".sym", 5);  // NOLINT
  File* file = File::Open(nullptr, path, File::kRead);
  free(path);
  if (file == nullptr) return;

  int64_t size = file->Length();
  MappedMemory* mapping = file->Map(File::kReadOnly, 0, size);
  Dart_AddSymbols(argv0, mapping->address(), size);
  mapping->Leak();  // Let us delete the object but keep the mapping.
  delete mapping;
  file->Release();
}
#endif  // !defined(DART_HOST_OS_WINDOWS)

}  // namespace bin
}  // namespace dart
