// Copyright 2007 Google LLC
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// ms_symbol_server_converter.cc: Obtain symbol files from a Microsoft
// symbol server, and convert them to Breakpad's dumped format.
//
// See ms_symbol_server_converter.h for documentation.
//
// Author: Mark Mentovai

#ifdef HAVE_CONFIG_H
#include <config.h>  // Must come first
#endif

#include <windows.h>
#include <dbghelp.h>
#include <pathcch.h>

#include <cassert>
#include <cstdio>

#include "tools/windows/converter/ms_symbol_server_converter.h"
#include "common/windows/pdb_source_line_writer.h"
#include "common/windows/pe_source_line_writer.h"
#include "common/windows/string_utils-inl.h"

// SYMOPT_NO_PROMPTS is not defined in earlier platform SDKs.  Define it
// in that case, in the event that this code is used with a newer version
// of DbgHelp at runtime that recognizes the option.  The presence of this
// bit in the symbol options should not harm earlier versions of DbgHelp.
#ifndef SYMOPT_NO_PROMPTS
#define SYMOPT_NO_PROMPTS 0x00080000
#endif  // SYMOPT_NO_PROMPTS

namespace {

std::wstring GetExeDirectory() {
  wchar_t directory[MAX_PATH];

  // Get path to this process exe.
  DWORD result = GetModuleFileName(/*hModule=*/nullptr, directory, MAX_PATH);
  if (result <= 0 || result == MAX_PATH) {
    fprintf(stderr,
        "GetExeDirectory: failed to get path to process exe.\n");
    return L"";
  }
  HRESULT hr = PathCchRemoveFileSpec(directory, result + 1);
  if (hr != S_OK) {
    fprintf(stderr,
        "GetExeDirectory: failed to remove basename from path '%ls'.\n",
        directory);
    return L"";
  }

  return std::wstring(directory);
}

}  // namespace

namespace google_breakpad {

// Use sscanf_s if it is available, to quench the warning about scanf being
// deprecated.  Use scanf where sscanf_is not available.  Note that the
// parameters passed to sscanf and sscanf_s are only compatible as long as
// fields of type c, C, s, S, and [ are not used.
#if _MSC_VER >= 1400  // MSVC 2005/8
#define SSCANF sscanf_s
#else  // _MSC_VER >= 1400
#define SSCANF sscanf
#endif  // _MSC_VER >= 1400

bool GUIDOrSignatureIdentifier::InitializeFromString(
    const string& identifier) {
  type_ = TYPE_NONE;

  size_t length = identifier.length();

  if (length > 32 && length <= 40) {
    // GUID
    if (SSCANF(identifier.c_str(),
               "%08X%04hX%04hX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%X",
               &guid_.Data1, &guid_.Data2, &guid_.Data3,
               &guid_.Data4[0], &guid_.Data4[1],
               &guid_.Data4[2], &guid_.Data4[3],
               &guid_.Data4[4], &guid_.Data4[5],
               &guid_.Data4[6], &guid_.Data4[7],
               &age_) != 12) {
      return false;
    }

    type_ = TYPE_GUID;
  } else if (length > 8 && length <= 15) {
    // Signature
    if (SSCANF(identifier.c_str(), "%08X%x", &signature_, &age_) != 2) {
      return false;
    }

    type_ = TYPE_SIGNATURE;
  } else {
    return false;
  }

  return true;
}

#undef SSCANF

MSSymbolServerConverter::MSSymbolServerConverter(
    const string& local_cache,
    const vector<string>& symbol_servers,
    bool trace_symsrv)
    : symbol_path_(),
      fail_dns_(false),
      fail_timeout_(false),
      fail_http_https_redir_(false),
      fail_not_found_(false),
      trace_symsrv_(trace_symsrv) {
  // Setting local_cache can be done without verifying that it exists because
  // SymSrv will create it if it is missing - any creation failures will occur
  // at that time, so there's nothing to check here, making it safe to
  // assign this in the constructor.

  assert(symbol_servers.size() > 0);

#if !defined(NDEBUG)
  // These are characters that are interpreted as having special meanings in
  // symbol_path_.
  const char kInvalidCharacters[] = "*;";
  assert(local_cache.find_first_of(kInvalidCharacters) == string::npos);
#endif  // !defined(NDEBUG)

  for (vector<string>::const_iterator symbol_server = symbol_servers.begin();
       symbol_server != symbol_servers.end();
       ++symbol_server) {
    // The symbol path format is explained by
    // http://msdn.microsoft.com/library/en-us/debug/base/using_symsrv.asp .
    // "srv*" is the same as "symsrv*symsrv.dll*", which means that
    // symsrv.dll is to be responsible for locating symbols.  symsrv.dll
    // interprets the rest of the string as a series of symbol stores separated
    // by '*'.  "srv*local_cache*symbol_server" means to check local_cache
    // first for the symbol file, and if it is not found there, to check
    // symbol_server.  Symbol files found on the symbol server will be placed
    // in the local cache, decompressed.
    //
    // Multiple specifications in this format may be presented, separated by
    // semicolons.

    assert((*symbol_server).find_first_of(kInvalidCharacters) == string::npos);
    symbol_path_ += "srv*" + local_cache + "*" + *symbol_server + ";";
  }

  // Strip the trailing semicolon.
  symbol_path_.erase(symbol_path_.length() - 1);
}

// A stack-based class that manages SymInitialize and SymCleanup calls.
class AutoSymSrv {
 public:
  AutoSymSrv() : initialized_(false) {}

  ~AutoSymSrv() {
    if (!Cleanup()) {
      // Print the error message here, because destructors have no return
      // value.
      fprintf(stderr, "~AutoSymSrv: SymCleanup: error %lu\n", GetLastError());
    }
  }

  bool Initialize(HANDLE process, char* path, bool invade_process) {
    process_ = process;

    // TODO(nbilling): Figure out why dbghelp.dll is being loaded from
    // system32/SysWOW64 before exe folder.

    // Attempt to locate and load dbghelp.dll beside the process exe. This is
    // somewhat of a workaround to loader delay load behavior that is occurring
    // when we call into symsrv APIs. dbghelp.dll must be loaded from beside
    // the process exe so that we are guaranteed to find symsrv.dll alongside
    // dbghelp.dll (a security requirement of dbghelp.dll) and so that the
    // symsrv.dll file that is loaded has a symsrv.yes file alongside it (a
    // requirement of symsrv.dll when accessing Microsoft-owned symbol
    // servers).
    // 'static local' because we don't care about the value but we need the
    // initialization to happen exactly once.
    static HMODULE dbghelp_module = [] () -> HMODULE {
      std::wstring exe_directory = GetExeDirectory();
      if (exe_directory.empty()) {
        return nullptr;
      }
      std::wstring dbghelp_path = exe_directory + L"\\dbghelp.dll";
      return LoadLibrary(dbghelp_path.c_str());
    }();
    if (dbghelp_module == nullptr) {
      fprintf(stderr,
          "AutoSymSrv::Initialize: failed to load dbghelp.dll beside exe.");
      return false;
    }

    initialized_ = SymInitialize(process, path, invade_process) == TRUE;
    return initialized_;
  }

  bool Cleanup() {
    if (initialized_) {
      if (SymCleanup(process_)) {
        initialized_ = false;
        return true;
      }
      return false;
    }

    return true;
  }

 private:
  HANDLE process_;
  bool initialized_;
};

// A stack-based class that "owns" a pathname and deletes it when destroyed,
// unless told not to by having its Release() method called.  Early deletions
// are supported by calling Delete().
class AutoDeleter {
 public:
  explicit AutoDeleter(const string& path) : path_(path) {}

  ~AutoDeleter() {
    int error;
    if ((error = Delete()) != 0) {
      // Print the error message here, because destructors have no return
      // value.
      fprintf(stderr, "~AutoDeleter: Delete: error %d for %s\n",
              error, path_.c_str());
    }
  }

  int Delete() {
    if (path_.empty())
      return 0;

    int error = remove(path_.c_str());
    Release();
    return error;
  }

  void Release() {
    path_.clear();
  }

 private:
  string path_;
};

MSSymbolServerConverter::LocateResult
MSSymbolServerConverter::LocateFile(const string& debug_or_code_file,
                                    const string& debug_or_code_id,
                                    const string& version,
                                    string* file_name) {
  assert(file_name);
  file_name->clear();

  GUIDOrSignatureIdentifier identifier;
  if (!identifier.InitializeFromString(debug_or_code_id)) {
    fprintf(stderr,
            "LocateFile: Unparseable identifier for %s %s %s\n",
            debug_or_code_file.c_str(),
            debug_or_code_id.c_str(),
            version.c_str());
    return LOCATE_FAILURE;
  }

  HANDLE process = GetCurrentProcess();  // CloseHandle is not needed.
  AutoSymSrv symsrv;
  if (!symsrv.Initialize(process,
                         const_cast<char*>(symbol_path_.c_str()),
                         false)) {
    fprintf(stderr, "LocateFile: SymInitialize: error %lu for %s %s %s\n",
            GetLastError(),
            debug_or_code_file.c_str(),
            debug_or_code_id.c_str(),
            version.c_str());
    return LOCATE_FAILURE;
  }

  if (!SymRegisterCallback64(process, SymCallback,
                             reinterpret_cast<ULONG64>(this))) {
    fprintf(stderr,
            "LocateFile: SymRegisterCallback64: error %lu for %s %s %s\n",
            GetLastError(),
            debug_or_code_file.c_str(),
            debug_or_code_id.c_str(),
            version.c_str());
    return LOCATE_FAILURE;
  }

  // SYMOPT_DEBUG arranges for SymCallback to be called with additional
  // debugging information.  This is used to determine the nature of failures.
  DWORD options = SymGetOptions() | SYMOPT_DEBUG | SYMOPT_NO_PROMPTS |
                  SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_SECURE;
  SymSetOptions(options);

  // SymCallback will set these as needed inisde the SymFindFileInPath call.
  fail_dns_ = false;
  fail_timeout_ = false;
  fail_not_found_ = false;

  // Do the lookup.
  char path[MAX_PATH];
  if (!SymFindFileInPath(
          process, NULL,
          const_cast<char*>(debug_or_code_file.c_str()),
          const_cast<void*>(identifier.guid_or_signature_pointer()),
          identifier.age(), 0,
          identifier.type() == GUIDOrSignatureIdentifier::TYPE_GUID ?
              SSRVOPT_GUIDPTR : SSRVOPT_DWORDPTR,
          path, SymFindFileInPathCallback, this)) {
    DWORD error = GetLastError();
    if (error == ERROR_FILE_NOT_FOUND) {
      // This can be returned for a number of reasons.  Use the crumbs
      // collected by SymCallback to determine which one is relevant.

      // These errors are possibly transient.
      if (fail_dns_ || fail_timeout_) {
        return LOCATE_RETRY;
      }

      if (fail_http_https_redir_) {
        return LOCATE_HTTP_HTTPS_REDIR;
      }

      // This is an authoritiative file-not-found message.
      if (fail_not_found_) {
        fprintf(stderr,
                "LocateFile: SymFindFileInPath: LOCATE_NOT_FOUND error "
                "for %s %s %s\n",
                debug_or_code_file.c_str(),
                debug_or_code_id.c_str(),
                version.c_str());
        return LOCATE_NOT_FOUND;
      }

      // If the error is FILE_NOT_FOUND but none of the known error
      // conditions are matched, fall through to LOCATE_FAILURE.
    }

    fprintf(stderr,
            "LocateFile: SymFindFileInPath: error %lu for %s %s %s\n",
            error,
            debug_or_code_file.c_str(),
            debug_or_code_id.c_str(),
            version.c_str());
    return LOCATE_FAILURE;
  }

  // Making sure path is null-terminated.
  path[MAX_PATH - 1] = '\0';

  // The AutoDeleter ensures that the file is only kept when returning
  // LOCATE_SUCCESS.
  AutoDeleter deleter(path);

  // Do the cleanup here even though it will happen when symsrv goes out of
  // scope, to allow it to influence the return value.
  if (!symsrv.Cleanup()) {
    fprintf(stderr, "LocateFile: SymCleanup: error %lu for %s %s %s\n",
            GetLastError(),
            debug_or_code_file.c_str(),
            debug_or_code_id.c_str(),
            version.c_str());
    return LOCATE_FAILURE;
  }

  deleter.Release();

  printf("Downloaded: %s\n", path);
  *file_name = path;
  return LOCATE_SUCCESS;
}


MSSymbolServerConverter::LocateResult
MSSymbolServerConverter::LocatePEFile(const MissingSymbolInfo& missing,
                                      string* pe_file) {
  return LocateFile(missing.code_file, missing.code_identifier,
                    missing.version, pe_file);
}

MSSymbolServerConverter::LocateResult
MSSymbolServerConverter::LocateSymbolFile(const MissingSymbolInfo& missing,
                                          string* symbol_file) {
  return LocateFile(missing.debug_file, missing.debug_identifier,
                    missing.version, symbol_file);
}


// static
BOOL CALLBACK MSSymbolServerConverter::SymCallback(HANDLE process,
                                                   ULONG action,
                                                   ULONG64 data,
                                                   ULONG64 context) {
  MSSymbolServerConverter* self =
      reinterpret_cast<MSSymbolServerConverter*>(context);

  switch (action) {
    case CBA_EVENT: {
      IMAGEHLP_CBA_EVENT* cba_event =
          reinterpret_cast<IMAGEHLP_CBA_EVENT*>(data);

      // Put the string into a string object to be able to use string::find
      // for substring matching.  This is important because the not-found
      // message does not use the entire string but is appended to the URL
      // that SymSrv attempted to retrieve.
      string desc(cba_event->desc);
      if (self->trace_symsrv_) {
        fprintf(stderr, "LocateFile: SymCallback: action desc '%s'\n",
                desc.c_str());
      }

      // desc_action maps strings (in desc) to boolean pointers that are to
      // be set to true if the string matches.
      struct desc_action {
        const char* desc;  // The substring to match.
        bool* action;      // On match, this pointer will be set to true.
      };

      static const desc_action desc_actions[] = {
          // When a DNS error occurs, it could be indiciative of network
          // problems.
          {"SYMSRV:  The server name or address could not be resolved\n",
           &self->fail_dns_},

          // This message is produced if no connection is opened.
          {"SYMSRV:  A connection with the server could not be established\n",
           &self->fail_timeout_},

          // This message is produced if a connection is established but the
          // server fails to respond to the HTTP request.
          {"SYMSRV:  The operation timed out\n", &self->fail_timeout_},

          // This message is produced if the server is redirecting us from http
          // to https. When this happens SymSrv will fail and we need to use
          // the https URL in our call-- we've made a mistake.
          {"ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR\n",
           &self->fail_http_https_redir_},

          // This message is produced when the requested file is not found,
          // even if one or more of the above messages are also produced.
          // It's trapped to distinguish between not-found and unknown-failure
          // conditions.  Note that this message will not be produced if a
          // connection is established and the server begins to respond to the
          // HTTP request but does not finish transmitting the file.
          {" not found\n", &self->fail_not_found_}};

      for (int desc_action_index = 0;
           desc_action_index <
           static_cast<int>(sizeof(desc_actions) / sizeof(desc_action));
           ++desc_action_index) {
        if (desc.find(desc_actions[desc_action_index].desc) != string::npos) {
          *(desc_actions[desc_action_index].action) = true;
          break;
        }
      }

      break;
    }
  }

  // This function is a mere fly on the wall.  Treat everything as unhandled.
  return FALSE;
}

// static
BOOL CALLBACK MSSymbolServerConverter::SymFindFileInPathCallback(
    const char* filename, void* context) {
  // FALSE ends the search, indicating that the located symbol file is
  // satisfactory.
  return FALSE;
}

MSSymbolServerConverter::LocateResult
MSSymbolServerConverter::LocateAndConvertSymbolFile(
    const MissingSymbolInfo& missing,
    bool keep_symbol_file,
    bool keep_pe_file,
    string* converted_symbol_file,
    string* symbol_file,
    string* out_pe_file) {
  assert(converted_symbol_file);
  converted_symbol_file->clear();
  if (symbol_file) {
    symbol_file->clear();
  }

  string pdb_file;
  LocateResult result = LocateSymbolFile(missing, &pdb_file);
  if (result != LOCATE_SUCCESS) {
    fprintf(stderr, "Fallback to PE-only symbol generation for: %s\n",
        missing.debug_file.c_str());
    return LocateAndConvertPEFile(missing, keep_pe_file, converted_symbol_file,
        out_pe_file);
  }

  if (symbol_file && keep_symbol_file) {
    *symbol_file = pdb_file;
  }

  // The conversion of a symbol file for a Windows 64-bit module requires
  // loading of the executable file.  If there is no executable file, convert
  // using only the PDB file.  Without an executable file, the conversion will
  // fail for 64-bit modules but it should succeed for 32-bit modules.
  string pe_file;
  result = LocatePEFile(missing, &pe_file);
  if (result != LOCATE_SUCCESS) {
    fprintf(stderr, "WARNING: Could not download: %s\n", pe_file.c_str());
  }

  if (out_pe_file && keep_pe_file) {
    *out_pe_file = pe_file;
  }

  // Conversion may fail because the file is corrupt.  If a broken file is
  // kept in the local cache, LocateSymbolFile will not hit the network again
  // to attempt to locate it.  To guard against problems like this, the
  // symbol file in the local cache will be removed if conversion fails.
  AutoDeleter pdb_deleter(pdb_file);
  AutoDeleter pe_deleter(pe_file);

  // Be sure that it's a .pdb file, since we'll be replacing .pdb with .sym
  // for the converted file's name.
  string pdb_extension = pdb_file.substr(pdb_file.length() - 4);
  // strcasecmp is called _stricmp here.
  if (_stricmp(pdb_extension.c_str(), ".pdb") != 0) {
    fprintf(stderr, "LocateAndConvertSymbolFile: "
            "no .pdb extension for %s %s %s %s\n",
            missing.debug_file.c_str(),
            missing.debug_identifier.c_str(),
            missing.version.c_str(),
            pdb_file.c_str());
    return LOCATE_FAILURE;
  }

  PDBSourceLineWriter writer;
  wstring pe_file_w;
  if (!WindowsStringUtils::safe_mbstowcs(pe_file, &pe_file_w)) {
    fprintf(stderr,
            "LocateAndConvertSymbolFile: "
                "WindowsStringUtils::safe_mbstowcs failed for %s\n",
            pe_file.c_str());
    return LOCATE_FAILURE;
  }
  wstring pdb_file_w;
  if (!WindowsStringUtils::safe_mbstowcs(pdb_file, &pdb_file_w)) {
    fprintf(stderr,
            "LocateAndConvertSymbolFile: "
                "WindowsStringUtils::safe_mbstowcs failed for %ws\n",
            pdb_file_w.c_str());
    return LOCATE_FAILURE;
  }
  if (!writer.Open(pdb_file_w, PDBSourceLineWriter::PDB_FILE)) {
    fprintf(stderr,
            "ERROR: PDBSourceLineWriter::Open failed for %s %s %s %ws\n",
            missing.debug_file.c_str(), missing.debug_identifier.c_str(),
            missing.version.c_str(), pdb_file_w.c_str());
    return LOCATE_FAILURE;
  }
  if (!writer.SetCodeFile(pe_file_w)) {
    fprintf(stderr,
            "ERROR: PDBSourceLineWriter::SetCodeFile failed for %s %s %s %ws\n",
            missing.debug_file.c_str(), missing.debug_identifier.c_str(),
            missing.version.c_str(), pe_file_w.c_str());
    return LOCATE_FAILURE;
  }

  *converted_symbol_file = pdb_file.substr(0, pdb_file.length() - 4) + ".sym";

  FILE* converted_output = NULL;
#if _MSC_VER >= 1400  // MSVC 2005/8
  errno_t err;
  if ((err = fopen_s(&converted_output, converted_symbol_file->c_str(), "w"))
    != 0) {
#else  // _MSC_VER >= 1400
  // fopen_s and errno_t were introduced in MSVC8.  Use fopen for earlier
  // environments.  Don't use fopen with MSVC8 and later, because it's
  // deprecated.  fopen does not provide reliable error codes, so just use
  // -1 in the event of a failure.
  int err;
  if (!(converted_output = fopen(converted_symbol_file->c_str(), "w"))) {
    err = -1;
#endif  // _MSC_VER >= 1400
    fprintf(stderr, "LocateAndConvertSymbolFile: "
        "fopen_s: error %d for %s %s %s %s\n",
        err,
        missing.debug_file.c_str(),
        missing.debug_identifier.c_str(),
        missing.version.c_str(),
        converted_symbol_file->c_str());
    return LOCATE_FAILURE;
  }

  AutoDeleter sym_deleter(*converted_symbol_file);

  bool success = writer.WriteSymbols(converted_output);
  fclose(converted_output);

  if (!success) {
    fprintf(stderr, "LocateAndConvertSymbolFile: "
            "PDBSourceLineWriter::WriteMap failed for %s %s %s %s\n",
            missing.debug_file.c_str(),
            missing.debug_identifier.c_str(),
            missing.version.c_str(),
            pdb_file.c_str());
    return LOCATE_FAILURE;
  }

  if (keep_symbol_file) {
    pdb_deleter.Release();
  }

  if (keep_pe_file) {
    pe_deleter.Release();
  }

  sym_deleter.Release();

  return LOCATE_SUCCESS;
}

MSSymbolServerConverter::LocateResult
MSSymbolServerConverter::LocateAndConvertPEFile(
    const MissingSymbolInfo& missing,
    bool keep_pe_file,
    string* converted_symbol_file,
    string* out_pe_file) {
  assert(converted_symbol_file);
  converted_symbol_file->clear();

  string pe_file;
  MSSymbolServerConverter::LocateResult result = LocatePEFile(missing,
      &pe_file);
  if (result != LOCATE_SUCCESS) {
    fprintf(stderr, "WARNING: Could not download: %s\n", pe_file.c_str());
    return result;
  }

  if (out_pe_file && keep_pe_file) {
    *out_pe_file = pe_file;
  }

  // Conversion may fail because the file is corrupt.  If a broken file is
  // kept in the local cache, LocatePEFile will not hit the network again
  // to attempt to locate it.  To guard against problems like this, the
  // PE file in the local cache will be removed if conversion fails.
  AutoDeleter pe_deleter(pe_file);

  // Be sure that it's a .exe or .dll file, since we'll be replacing extension
  // with .sym for the converted file's name.
  string pe_extension = pe_file.substr(pe_file.length() - 4);
  // strcasecmp is called _stricmp here.
  if (_stricmp(pe_extension.c_str(), ".exe") != 0 &&
    _stricmp(pe_extension.c_str(), ".dll") != 0) {
    fprintf(stderr, "LocateAndConvertPEFile: "
        "no .dll/.exe extension for %s %s %s %s\n",
        missing.debug_file.c_str(),
        missing.debug_identifier.c_str(),
        missing.version.c_str(),
        pe_file.c_str());
    return LOCATE_FAILURE;
  }

  *converted_symbol_file = pe_file.substr(0, pe_file.length() - 4) + ".sym";

  FILE* converted_output = NULL;
#if _MSC_VER >= 1400  // MSVC 2005/8
  errno_t err;
  if ((err = fopen_s(&converted_output, converted_symbol_file->c_str(), "w"))
      != 0) {
#else  // _MSC_VER >= 1400
  // fopen_s and errno_t were introduced in MSVC8.  Use fopen for earlier
  // environments.  Don't use fopen with MSVC8 and later, because it's
  // deprecated.  fopen does not provide reliable error codes, so just use
  // -1 in the event of a failure.
  int err;
  if (!(converted_output = fopen(converted_symbol_file->c_str(), "w"))) {
    err = -1;
#endif  // _MSC_VER >= 1400
    fprintf(stderr, "LocateAndConvertPEFile: "
        "fopen_s: error %d for %s %s %s %s\n",
        err,
        missing.debug_file.c_str(),
        missing.debug_identifier.c_str(),
        missing.version.c_str(),
        converted_symbol_file->c_str());
    return LOCATE_FAILURE;
  }
  AutoDeleter sym_deleter(*converted_symbol_file);

  wstring pe_file_w;
  if (!WindowsStringUtils::safe_mbstowcs(pe_file, &pe_file_w)) {
    fprintf(stderr,
        "LocateAndConvertPEFile: "
        "WindowsStringUtils::safe_mbstowcs failed for %s\n",
        pe_file.c_str());
    return LOCATE_FAILURE;
  }
  PESourceLineWriter writer(pe_file_w);
  PDBModuleInfo module_info;
  if (!writer.GetModuleInfo(&module_info)) {
    fprintf(stderr, "LocateAndConvertPEFile: "
        "PESourceLineWriter::GetModuleInfo failed for %s %s %s %s\n",
        missing.debug_file.c_str(),
        missing.debug_identifier.c_str(),
        missing.version.c_str(),
        pe_file.c_str());
    return LOCATE_FAILURE;
  }
  if (module_info.cpu.compare(L"x86_64") != 0) {
    // This module is not x64 so we cannot generate Breakpad symbols from the
    // PE alone. Don't delete PE-- no need to retry download.
    pe_deleter.Release();
    return LOCATE_FAILURE;
  }

  bool success = writer.WriteSymbols(converted_output);
  fclose(converted_output);

  if (!success) {
    fprintf(stderr, "LocateAndConvertPEFile: "
        "PESourceLineWriter::WriteMap failed for %s %s %s %s\n",
        missing.debug_file.c_str(),
        missing.debug_identifier.c_str(),
        missing.version.c_str(),
        pe_file.c_str());
    return LOCATE_FAILURE;
  }

  if (keep_pe_file) {
    pe_deleter.Release();
  }

  sym_deleter.Release();

  return LOCATE_SUCCESS;
}

}  // namespace google_breakpad
