/* **********************************************************
 * Copyright (c) 2007-2008 VMware, Inc.  All rights reserved.
 * **********************************************************/

/*
 * 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 VMware, Inc. 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 VMWARE, INC. 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.
 */

/* Copyright (c) 2003-2007 Determina Corp. */
/* Copyright (c) 2002-2003 Massachusetts Institute of Technology */
/* Copyright (c) 2002 Hewlett-Packard Company */

// DynamoRIODoc.cpp : implementation of the CDynamoRIODoc class
//

#include "stdafx.h"
#include <assert.h>
#include <commdlg.h>
#include <imagehlp.h>
#include <direct.h>
#include "DynamoRIO.h"
#include "DynamoRIODoc.h"
#include "ShellInterface.h"
#include "CmdlineDlg.h"

#ifdef _DEBUG
#    define new DEBUG_NEW
#    undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define INJECTOR_SUBPATH _T("\\bin\\drinject.exe")

/////////////////////////////////////////////////////////////////////////////
// CDynamoRIODoc

IMPLEMENT_DYNCREATE(CDynamoRIODoc, CDocument)

BEGIN_MESSAGE_MAP(CDynamoRIODoc, CDocument)
//{{AFX_MSG_MAP(CDynamoRIODoc)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDynamoRIODoc construction/destruction

CDynamoRIODoc::CDynamoRIODoc()
{
#ifndef DRSTATS_DEMO
    InitPaths();

    CShellInterface::Initialize();
#endif

    // make sure framework prompts to "save unsaved work"
    SetModifiedFlag(TRUE);
}

#ifndef DRSTATS_DEMO
void
CDynamoRIODoc::InitPaths()
{
    // limit to 200 to give room for rest of injector path
    int len = GetEnvironmentVariable(_T("DYNAMORIO_HOME"), m_dynamorio_home, _MAX_DIR);
    assert(len <= _MAX_DIR && len + _tcslen(INJECTOR_SUBPATH) < MAX_PATH);
    if (len == 0 || len > _MAX_DIR)
        m_dynamorio_home[0] = _T('\0');
    else
        NULL_TERMINATE_BUFFER(m_dynamorio_home);
    _stprintf(m_injector_path, _T("%s%s"), m_dynamorio_home, INJECTOR_SUBPATH);

#    if 0
    // there are issues with privileges to write to Program Files, so we no longer do this:
    _stprintf(m_logs_dir, _T("%s\\logs"), m_dynamorio_home);
#    else
    // instead we do $USERPROFILE\\Application Data\\DynamoRIO
    TCHAR userprof[MAX_PATH];
    CString profile_dir;
    len = GetEnvironmentVariable(_T("USERPROFILE"), userprof, MAX_PATH);
    assert(len <= MAX_PATH);
    if (len == 0 || len > MAX_PATH) {
        // on NT, use $SYSTEMROOT\\Profiles
        len = GetEnvironmentVariable(_T("SYSTEMROOT"), userprof, MAX_PATH);
        assert(len > 0 && len < MAX_PATH);
        profile_dir.Format(_T("%s\\Profiles"), userprof);
    } else {
        profile_dir.Format(_T("%s"), userprof);
    }
    _stprintf(m_logs_dir, _T("%s\\Application Data\\DynamoRIO"), profile_dir);
    if (!SetCurrentDirectory(m_logs_dir)) {
        if (!CreateDirectory(m_logs_dir, NULL)) {
            _stprintf(userprof, _T("Cannot create default working directory %s"),
                      m_logs_dir);
            MessageBox(NULL, userprof, _T("Error"), MB_OK | MYMBFLAGS);
            _stprintf(m_logs_dir, _T("c:\\")); // any better ideas?
        }
    }

#    endif
}
#endif /* !DRSTATS_DEMO */

CDynamoRIODoc::~CDynamoRIODoc()
{
#ifndef DRSTATS_DEMO
    CShellInterface::Uninitialize();
#endif
}

BOOL
CDynamoRIODoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;

    // TODO: add reinitialization code here
    // (SDI documents will reuse this document)

    // blank title initially
    SetTitle(_T(""));

    return TRUE;
}

BOOL
CDynamoRIODoc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if (!CDocument::OnOpenDocument(lpszPathName))
        return FALSE;

    // initialization that doesn't happen in Serialize
    // for us, that's all of it

    return TRUE;
}

#ifndef DRSTATS_DEMO
BOOL
CDynamoRIODoc::RunApplication(LPCTSTR lpszPathName)
{
    // application name (may be different than lpszPathName, for shortcuts)
    TCHAR app_name[MAX_PATH];
    // working directory
    TCHAR rundir[_MAX_DIR];
    // entire command line = app_name + arguments
    TCHAR app_cmdline[MAX_PATH * 2];
    // buffer for error messages
    TCHAR msg[MAX_PATH * 3];

    // default working directory
    TCHAR default_rundir[_MAX_DIR];
    _stprintf(default_rundir, _T("%s"), m_logs_dir);

    CString app_args; // will be used to build app_cmdline

    // if app is a shortcut, resolve it now
    int len = _tcslen(lpszPathName);
    TCHAR *last4 = (TCHAR *)lpszPathName + len - 4;
    if (_tcscmp(last4, _T(".lnk")) == 0) {
        TCHAR params[MAX_PATH];
        if (CShellInterface::ResolveLinkFile((TCHAR *)lpszPathName, app_name, params,
                                             rundir,
                                             CDynamoRIOApp::GetActiveView()->m_hWnd)) {
            _stprintf(msg, _T("Resolved link file to %s %s\nin directory %s\n"), app_name,
                      params, rundir);
            MessageBox(NULL, msg, _T("Link File"), MB_OK | MYMBFLAGS);
            app_args.Format(_T("%s"), params);
        } else {
            _stprintf(msg, _T("Failed to resolve link file"));
            MessageBox(NULL, msg, _T("Error"), MB_OK | MYMBFLAGS);
            return FALSE;
        }
    } else {
        _stprintf(app_name, _T("%s"), lpszPathName);

        // ask for arguments and working dir
        // pass in default working dir
        CString defwdir(default_rundir);
        CCmdlineDlg dlg(defwdir);
        int res = dlg.DoModal();
        if (res == IDCANCEL)
            return FALSE;
        CString wdir = dlg.GetWorkingDir();
        _tcscpy(rundir, wdir.GetBuffer(0));
        app_args = dlg.GetArguments();
    }

    // construct app_cmdline
    // if passing app name to injector as argument, must quote it
    // if running natively, do not quote it
    if (CDynamoRIOApp::SystemwideSet()) {
        _stprintf(app_cmdline, _T("%s %s"), app_name, app_args);
    } else {
        _stprintf(app_cmdline, _T("\"%s\" %s"), app_name, app_args);
    }

    // if rundir has environment vars, resolve them
    CString dir(rundir);
    CString realdir(_T(""));
    int start = dir.Find(_T('%'));
    int old_end = 0;
    while (start != -1) {
        realdir += dir.Mid(old_end, start - old_end);
        int end = dir.Find(_T('%'), start + 1);
        if (end == -1) {
            realdir += dir.Right(dir.GetLength() - start);
            break;
        }
#    if 0 // debugging stuff
        _stprintf(msg, _T("from %d to %d"), start, end);
        MessageBox(NULL, msg, _T("env var value"), MB_OK);
#    endif
        CString name = dir.Mid(start + 1, end - start - 1);
#    if 0 // debugging stuff
        MessageBox(NULL, name.GetBuffer(0), _T("env var name"), MB_OK);
#    endif
        int len = GetEnvironmentVariable(name.GetBuffer(0), msg, MAX_PATH);
        assert(len <= MAX_PATH);
        if (len == 0 || len > MAX_PATH)
            msg[0] = _T('\0');
#    if 0 // debugging stuff
        MessageBox(NULL, msg, _T("env var value"), MB_OK);
#    endif
        realdir += msg;
        old_end = end + 1;

        start = dir.Find(_T('%'), end + 1);
    }
    if (!realdir.IsEmpty())
        _stprintf(rundir, _T("%s"), realdir.GetBuffer(0));

    // if rundir is empty, use default
    // FIXME: have option to set default?
    if (rundir[0] == _T('\0'))
        _tcscpy(rundir, default_rundir);

    // be robust -- make sure app file exists
    CFile check;
    if (!check.Open(app_name, CFile::modeRead | CFile::shareDenyNone)) {
        _stprintf(msg, _T("Application %s does not exist"), app_name);
        MessageBox(NULL, msg, _T("Error"), MB_OK | MYMBFLAGS);
        return FALSE;
    }
    check.Close();

    // now go to run directory
    if (!SetCurrentDirectory(rundir)) {
        _stprintf(msg, _T("Error changing to working directory %s"), rundir);
        MessageBox(NULL, msg, _T("Error"), MB_OK | MYMBFLAGS);
        return FALSE;
    }

    // now launch application
    TCHAR *launch_name;
    TCHAR launch_cmdline[MAX_PATH * 2];

    if (CDynamoRIOApp::SystemwideSet()) {
        // just launch app natively, systemwide injector will catch it
        launch_name = app_name;
        // note that we want target app name as part of cmd line
        _tcscpy(launch_cmdline, app_cmdline);
    } else {
        // explicitly launch app under injector
        launch_name = m_injector_path;
        // note that we want target app name as part of cmd line
        TCHAR *dll_path = CDynamoRIOApp::GetDllPath();
        _stprintf(launch_cmdline, _T("\"%s\" \"%s\" %s"), m_injector_path, dll_path,
                  app_cmdline);

        // be robust
        if (!check.Open(m_injector_path, CFile::modeRead | CFile::shareDenyNone)) {
            _stprintf(msg, _T("DynamoRIO injector %s does not exist"), m_injector_path);
            MessageBox(NULL, msg, _T("DynamoRIO Configuration Error"), MB_OK | MYMBFLAGS);
            return FALSE;
        }
        check.Close();
        if (!check.Open(dll_path, CFile::modeRead | CFile::shareDenyNone)) {
            _stprintf(msg, _T("DynamoRIO library %s does not exist"), dll_path);
            MessageBox(NULL, msg, _T("DynamoRIO Configuration Error"), MB_OK | MYMBFLAGS);
            return FALSE;
        }
        check.Close();
    }

    assert(_tcslen(rundir) + _tcslen(launch_cmdline) + _tcslen(_T("\nin directory\n")) <
           MAX_PATH * 2);

#    if 0  // I'm disabling this dialog, usually just annoying
    _stprintf(msg, _T("%s\nin directory\n%s"), launch_cmdline, rundir);
    MessageBox(NULL, msg, _T("About to run"), MB_OK | MB_TOPMOST);
#    endif // 0

#    if 0 // I'm taking this out, it requires an extra library and doesn't add much
    // FIXME: do this up above, instead of checking app_name exists?
    PLOADED_IMAGE       li;
#        ifdef UNICODE
    // ImageLoad does not take unicode strings
    char ansi_launch_name[MAX_PATH];
    WideCharToMultiByte(CP_ACP, 0, launch_name, _tcslen(launch_name),
                        ansi_launch_name, MAX_PATH, NULL, NULL);
    if (li = ImageLoad(ansi_launch_name, NULL))
#        else
    if (li = ImageLoad(launch_name, NULL))
#        endif
    {
        ImageUnload(li);
    } else {
        _stprintf(msg, _T("Failed to load %s"), launch_name);
        MessageBox(NULL, msg, _T("Error"), MB_OK | MYMBFLAGS);
        return FALSE;
    }
#    endif // 0

    // Launch the application process
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    STARTUPINFO mysi;
    GetStartupInfo(&mysi);
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = mysi.hStdInput;
    si.hStdOutput = mysi.hStdOutput;
    si.hStdError = mysi.hStdError;

    /* Must specify TRUE for bInheritHandles so child inherits stdin! */
    if (!CreateProcess(launch_name, launch_cmdline, NULL, NULL, TRUE,
                       DETACHED_PROCESS, // to avoid console creation!
                       NULL, NULL, &si, &pi)) {
        _stprintf(msg, _T("Failed to load %s"), app_name);
        MessageBox(NULL, msg, _T("Error"), MB_OK | MYMBFLAGS);
        return FALSE;
    }

#    if 0
        // FIXME: We have the injector's pid, not the child's pid!
    if (!CDynamoRIOApp::GetActiveView()->SelectProcess(pi.dwProcessId))
        MessageBox(NULL, _T("Failed to select"), _T("Error"),  MB_OK | MYMBFLAGS);
#    endif
    // it takes some time for the new process to start up and its shared memory
    // to become visible, so wait for it, but don't wait forever -- there could be
    // a usage error or the new process may crash
    int i = 0;
    // wait 10ms*200 = 2 seconds
    while (i < 200) {
        // change the <no instances found> entry
        if (CDynamoRIOApp::GetActiveView()->UpdateProcessList())
            break;
        Sleep(10);
        i++;
    }

    // don't call SetTitle -- View sets it to what's being viewed
    // SetTitle(lpszPathName);

    return TRUE;
}
#endif /* !DRSTATS_DEMO */

/////////////////////////////////////////////////////////////////////////////
// CDynamoRIODoc serialization

void
CDynamoRIODoc::Serialize(CArchive &ar)
{
    if (ar.IsStoring()) {
        // TODO: add storing code here
    } else {
        // TODO: add loading code here
    }
}

/////////////////////////////////////////////////////////////////////////////
// CDynamoRIODoc diagnostics

#ifdef _DEBUG
void
CDynamoRIODoc::AssertValid() const
{
    CDocument::AssertValid();
}

void
CDynamoRIODoc::Dump(CDumpContext &dc) const
{
    CDocument::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CDynamoRIODoc commands

BOOL
CDynamoRIODoc::SaveModified()
{
#ifndef DRSTATS_DEMO
    CDynamoRIOApp::AboutToExit();
#endif
    return CDocument::SaveModified();
}