Windows-Server-2003/sdktools/debuggers/sdsrv/sdsrv.cpp

1805 lines
42 KiB
C++
Raw Permalink Blame History

#ifndef _WINDOWS_H
#include "windows.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <fcntl.h>
#include <assert.h>
#include <typeinfo.h>
#include <time.h>
#include <limits.h>
#include <strsafe.h>
#include "initguid.h"
#include "sdapi.h"
#ifndef DIMA
#define DIMAT(Array, EltType) (sizeof(Array) / sizeof(EltType))
#define DIMA(Array) DIMAT(Array, (Array)[0])
#endif
static const char usage[] = "sdapitest [-?] command [args]";
static const char long_usage[] =
"Usage:\n"
"\n"
" sdapitest [options] command [args]\n"
"\n"
" Roughly emulates the SD.EXE client, using the SDAPI.\n"
"\n"
" Options:\n"
" -? Print this message.\n"
" -! Break into debugger.\n"
"\n"
" -d Debug/diagnostic/informational output mode.\n"
" -v Verbose mode (show type of output).\n"
"\n"
" -c client Set client name.\n"
" -H host Set host name.\n"
" -p port Set server port.\n"
" -P password Set user's password.\n"
" -u user Set username.\n"
"\n"
" -i file Read settings from file (same format as SD.INI).\n"
" If file is a directory name, walk up the directory\n"
" parent chain searching for an SD.INI file to read.\n"
" -I file Same as -i, but clears the current settings first.\n"
"\n"
" -x file Read commands from 'file'. To read commands from\n"
" stdin, use - as the file name. This can even be used\n"
" as a simplistic interactive SD shell. Each command\n"
" can be optionally preceded by an integer and a comma.\n"
" The integer indicates the number of seconds to pause\n"
" before running the command.\n"
"\n"
" -T Use the SDAPI structured mode.\n"
#if 0
" -C Use CreateSDAPIObject() instead of CoCreateInstance();\n"
" (note, must come before any other options).\n"
#endif
"\n"
" Special Commands:\n"
" demo Uses structured mode to run 'sd changes' and\n"
" format the output specially.\n"
" detect [-s] file Uses ISDClientUtilities::DetectType to detect\n"
" file's type the same way 'sd add file' does.\n"
" set [-S service] var=[value]\n"
" Uses ISDClientUtilities::Set to set variables\n"
" similar to how 'sd set' does.\n"
" ** DOES NOT UPDATE THE SDAPI OBJECT, therefore\n"
" the 'query' command (below) cannot report the\n"
" new value.\n"
" query [-S service] [var]\n"
" Uses ISDClientUtilities::QuerySettings to\n"
" report the current settings similar to how\n"
" 'sd set' does.\n"
" ** QUERIES THE SDAPI OBJECT, therefore cannot\n"
" report a new value from 'set' (above).\n"
;
static BOOL s_fVerbose = FALSE;
BOOL
wcs2ansi(
const WCHAR *pwsz,
char *psz,
DWORD pszlen
)
{
BOOL rc;
int len;
assert(psz && pwsz);
len = wcslen(pwsz);
if (!len) {
*psz = 0;
return TRUE;
}
rc = WideCharToMultiByte(CP_ACP,
WC_SEPCHARS | WC_COMPOSITECHECK,
pwsz,
len,
psz,
pszlen,
NULL,
NULL);
if (!rc)
return FALSE;
psz[len] = 0;
return TRUE;
}
BOOL
ansi2wcs(
const char *psz,
WCHAR *pwsz,
DWORD pwszlen
)
{
BOOL rc;
int len;
assert(psz && pwsz);
len = strlen(psz);
if (!len) {
*pwsz = 0L;
return TRUE;
}
rc = MultiByteToWideChar(CP_ACP,
MB_COMPOSITE,
psz,
len,
pwsz,
pwszlen);
if (!rc)
return FALSE;
pwsz[len] = 0;
return TRUE;
}
///////////////////////////////////////////////////////////////////////////
// Debugging Aids
// compile-time assert
#define CASSERT(expr) extern int cassert##__LINE__[(expr) ? 1 : 0]
// run-time assert
#ifdef DEBUG
#define AssertHelper \
do { \
switch (MessageBox(NULL, "Assertion failed.", "SDAPITEST", MB_ABORTRETRYIGNORE)) { \
case IDABORT: exit(2); break; \
case IDRETRY: DebugBreak(); break; \
} \
} while (0)
#define Assert(expr) \
do { \
if (!(expr)) { \
printf("%s\n", #expr); \
AssertHelper; \
} \
} while (0)
#define Assert1(expr, fmt, arg1) \
do { \
if (!(expr)) { \
printf(#fmt "\n", arg1); \
AssertHelper; \
} \
} while (0)
#define IfDebug(x) x
#else
#define Assert(expr) do {} while (0)
#define Assert1(expr, fmt, arg1) do {} while (0)
#define IfDebug(x)
#endif
#define Panic0(s) Assert1(FALSE, "%s", s)
#define PanicSz(s) Panic0(s)
///////////////////////////////////////////////////////////////////////////
// Embedded Interface Macros
#define OffsetOf(s,m) (size_t)( (char *)&(((s *)0)->m) - (char *)0 )
#define EmbeddorOf(C,m,p) ((C *)(((char *)p) - OffsetOf(C,m)))
#define DeclareEmbeddedInterface(interface) \
class E##interface : public interface \
{ \
public: \
STDMETHOD_(ULONG, AddRef)(); \
STDMETHOD_(ULONG, Release)(); \
STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj); \
Declare##interface##Members(IMPL) \
} m_##interface; \
friend class E##interface;
#define ImplementEmbeddedUnknown(embeddor, interface) \
STDMETHODIMP embeddor::E##interface::QueryInterface(REFIID iid,void **ppv)\
{ \
return EmbeddorOf(embeddor,m_##interface,this)->QueryInterface(iid,ppv);\
} \
STDMETHODIMP_(ULONG) embeddor::E##interface::AddRef() \
{ \
return EmbeddorOf(embeddor, m_##interface, this)->AddRef(); \
} \
STDMETHODIMP_(ULONG) embeddor::E##interface::Release() \
{ \
return EmbeddorOf(embeddor, m_##interface, this)->Release(); \
}
#define EMBEDDEDTHIS(embeddor, interface) \
embeddor *pThis = EmbeddorOf(embeddor,m_##interface,this)
///////////////////////////////////////////////////////////////////////////
// dbgPrintF
static BOOL s_fDbg = FALSE;
static int s_cIndent = 0;
void dbgPrintF(const char *pszFmt, ...)
{
if (s_fDbg)
{
va_list args;
va_start(args, pszFmt);
for (int c = s_cIndent; c--;)
printf("... ");
vprintf(pszFmt, args);
//printf("\n");
va_end(args);
}
}
class DbgIndent
{
public:
DbgIndent() { s_cIndent++; }
~DbgIndent() { s_cIndent--; }
};
#define DBGINDENT DbgIndent dbgindent;
class Ender
{
public:
~Ender() { dbgPrintF(""); dbgPrintF("---- end ----"); }
};
///////////////////////////////////////////////////////////////////////////
// VARIANT Helpers
inline int SzToWz(UINT CodePage, const char* pszFrom, int cchFrom, WCHAR* pwzTo, int cchMax)
{
return MultiByteToWideChar(CodePage, 0, pszFrom, cchFrom, pwzTo, cchMax);
}
BSTR BstrFromSz(const char *psz, int cch = 0)
{
BSTR bstr;
int cchActual;
if (!cch)
cch = strlen(psz);
bstr = (BSTR)malloc((cch + 1) * sizeof(WCHAR));
if (bstr)
{
ansi2wcs(psz, bstr, cch + 1);
cchActual = SzToWz(CP_OEMCP, psz, cch, bstr, cch);
bstr[cchActual] = 0;
}
return bstr;
}
HRESULT VariantSet(VARIANT *pvar, const char *psz, int cch = 0)
{
if (pvar->vt != VT_EMPTY || !psz)
return E_INVALIDARG;
V_BSTR(pvar) = BstrFromSz(psz, cch);
V_VT(pvar) = VT_BSTR;
if (!V_VT(pvar))
return E_OUTOFMEMORY;
return S_OK;
}
///////////////////////////////////////////////////////////////////////////
// Smart Interface Pointer
void SetI(IUnknown * volatile *ppunkL, IUnknown *punkR)
{
// addref right side first, in case punkR and *ppunkL are on the same
// object (weak refs) or are the same variable.
if (punkR)
punkR->AddRef();
if (*ppunkL)
{
IUnknown *punkRel = *ppunkL;
*ppunkL = 0;
punkRel->Release();
}
*ppunkL = punkR;
}
#ifdef DEBUG
void ReleaseI(IUnknown *punk)
{
if (punk)
{
if (IsBadReadPtr(punk,sizeof(void *)))
{
Panic0("Bad Punk");
return;
}
if (IsBadReadPtr(*((void**) punk),sizeof(void *) * 3))
{
Panic0("Bad Vtable");
return;
}
punk->Release();
}
}
#else
inline void ReleaseI(IUnknown *punk)
{
if (punk)
punk->Release();
}
#endif
template <class IFace> class PrivateRelease : public IFace
{
private:
// force Release to be private to prevent "spfoo->Release()"!!!
STDMETHODIMP_(ULONG) Release();
};
template <class IFace, const GUID *piid>
class SPI
{
public:
SPI() { m_p = 0; }
//SPI(IFace *p) { m_p = p; if (m_p) m_p->AddRef(); }
~SPI() { ReleaseI(m_p); }
operator IFace*() const { return m_p; }
PrivateRelease<IFace> *operator->() const
{ return (PrivateRelease<IFace>*)m_p; }
IFace **operator &() { Assert1(!m_p, "Non-empty %s as out param.", typeid(SPI<IFace, piid>).name()); return &m_p; }
IFace *operator=(IFace *p) { Assert1(!m_p, "Non-empty %s in assignment.", typeid(SPI<IFace, piid>).name()); return m_p = p; }
IFace *Transfer() { IFace *p = m_p; m_p = 0; return p; }
IFace *Copy() { if (m_p) m_p->AddRef(); return m_p; }
void Release() { SetI((IUnknown **)&m_p, 0); }
void Set(IFace *p) { SetI((IUnknown **)&m_p, p); }
bool operator!() { return (m_p == NULL); }
BOOL FQuery(IUnknown *punk) { return FHrSucceeded(HrQuery(punk)); }
HRESULT HrQuery(IUnknown *punk) { Assert1(!m_p, "Non-empty %s in HrQuery().", typeid(SPI<IFace, piid>).name()); return HrQueryInterface(punk, *piid, (void**)&m_p); }
protected:
IFace *m_p;
private:
// disallow these methods from being called
SPI<IFace, piid> &operator=(const SPI<IFace, piid>& sp)
{ SetI((IUnknown **)&m_p, sp.m_p); return *this; }
};
#define DeclareSPI(TAG, IFace)\
EXTERN_C const GUID CDECL IID_##IFace;\
typedef SPI<IFace, &IID_##IFace> SP##TAG;
DeclareSPI(API, ISDClientApi)
///////////////////////////////////////////////////////////////////////////
// ClientUser
#define DeclareIUnknownMembers(IPURE) \
STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID* ppvObj) IPURE; \
STDMETHOD_(ULONG,AddRef) (THIS) IPURE; \
STDMETHOD_(ULONG,Release) (THIS) IPURE; \
class ClientUser : public ISDClientUser
{
public:
ClientUser() : m_cRef(1), m_fFresh(TRUE), m_fDemo(FALSE) {}
virtual ~ClientUser() {}
DeclareIUnknownMembers(IMPL);
DeclareISDClientUserMembers(IMPL);
DeclareEmbeddedInterface(ISDActionUser);
DeclareEmbeddedInterface(ISDInputUser);
void SetDemo(BOOL fDemo) { m_fDemo = fDemo; m_fFresh = TRUE; }
private:
ULONG m_cRef;
BOOL m_fFresh;
BOOL m_fDemo;
};
STDMETHODIMP_(ULONG) ClientUser::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) ClientUser::Release()
{
if (--m_cRef > 0)
return m_cRef;
delete this;
return 0;
}
STDMETHODIMP ClientUser::QueryInterface(REFIID iid, void** ppvObj)
{
HRESULT hr = S_OK;
if (iid == IID_IUnknown || iid == IID_ISDClientUser)
*ppvObj = (ISDClientUser*)this;
else if (iid == IID_ISDActionUser)
*ppvObj = &m_ISDActionUser;
else if (iid == IID_ISDInputUser)
*ppvObj = &m_ISDInputUser;
else
{
*ppvObj = 0;
return E_NOINTERFACE;
}
((IUnknown*)*ppvObj)->AddRef();
return hr;
}
// ---- ISDClientUser -----------------------------------------------------
/*----------------------------------------------------------------------------
ISDClientUser::OutputText
Called for text data, generally the result of 'print textfile' or
'spec-command -o' (where spec-command is branch, change, client,
label, protect, user, etc).
IMPORTANT NOTE:
The implementation of this method must translate '\n' in the pszText
string to '\r\n' on Windows platforms to ensure correct line
termination. This is particularly important when using 'print' to
download the contents of a file.
Args:
pszText - [in] text string (not null terminated, and may
contain embedded null characters that are part of
the data itself).
cchText - [in] number of bytes in pszText.
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::OutputText( const char *pszText,
int cchText )
{
fwrite(pszText, cchText, 1, stdout);
return S_OK;
}
/*----------------------------------------------------------------------------
ISDClientUser::OutputBinary
Called for binary data, generally the result of 'print nontextfile' or
'print unicodefile'.
Args:
pbData - [in] stream of bytes.
cbData - [in] number of bytes in pbData.
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::OutputBinary( const unsigned char *pbData,
int cbData )
{
static BOOL s_fBinary = FALSE;
// we rely on a trailing zero length buffer to
// tell us to turn off binary output for stdout.
if (s_fBinary == !cbData)
{
// toggle
s_fBinary = !!cbData;
fflush(stdout);
_setmode(_fileno(stdout), s_fBinary ? O_BINARY : O_TEXT);
}
fwrite(pbData, cbData, 1, stdout);
return S_OK;
}
/*----------------------------------------------------------------------------
ISDClientUser::OutputInfo
Called for tabular data, usually the results of commands that affect
sets of files.
Some commands also support structured output; see ISDClientApi::Init
and ISDClientUser::OutputStructured for more information.
Args:
cIndent - [in] indentation levels 0 - 2 (loosely implies
hierarchical relationship). The SD.EXE client
program normally handles 1 by prepending "... " to
the string, and handles 2 by prepending "... ... ".
pszInfo - [in] informational message string.
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::OutputInfo( int cIndent,
const char *pszInfo )
{
if (s_fVerbose)
printf(cIndent ? "info%d:\t" : "info:\t", cIndent);
while (cIndent--)
printf(" <20> ");
printf("%s\n", pszInfo);
return S_OK;
}
/*----------------------------------------------------------------------------
ISDClientUser::OutputWarning
Called for warning messages (any text normally displayed in yellow by
the SD.EXE client program).
As of this writing, there is no list of the possible warning messages.
Args:
cIndent - [in] indentation levels 0 - 2 (loosely implies
hierarchical relationship). The SD.EXE client
program normally handles 1 by prepending "... " to
the string, and handles 2 by prepending "... ... ".
pszWarning - [in] warning message string.
fEmptyReason - [in] the message is an "empty reason" message.
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::OutputWarning( int cIndent,
const char *pszWarning,
BOOL fEmptyReason )
{
if (s_fVerbose)
printf(cIndent ? "%s%d:\t" : "%s:\t",
fEmptyReason ? "empty" : "warn", cIndent);
while (cIndent--)
printf(" <20> ");
printf("%s\n", pszWarning);
return S_OK;
}
/*----------------------------------------------------------------------------
ISDClientUser::OutputError
Called for error messages, failed commands (any text normally
displayed in red by the SD.EXE client program).
As of this writing, there is no list of the possible error messages.
Args:
pszError - [in] error message string.
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::OutputError( const char *pszError )
{
if (s_fVerbose)
fprintf(stderr, "error:\t");
fprintf(stderr, "%s", pszError);
return S_OK;
}
/*----------------------------------------------------------------------------
ISDClientUser::OutputStructured
Called for tabular data if the ISDClientApi::Init call requested
structured output and the command being run supports structured
output.
See the ISDVars interface in SDAPI.H for more information.
Args:
pVars - [in] pointer to object containing the data; use the
provided accessor methods to retrieve the data.
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::OutputStructured( ISDVars *pVars )
{
// your code here
if (m_fDemo)
{
// sample implementation -- illustrates how to use structured mode.
const char *pszChange;
const char *pszTime;
const char *pszUser;
const char *pszDesc;
//const char *pszClient;
//const char *pszStatus;
int nChange;
time_t ttTime;
tm tmTime;
char szDesc[32];
if (m_fFresh)
{
printf("CHANGE DATE---- TIME---- "
"USER---------------- DESC------------------------\n");
m_fFresh = FALSE;
}
pVars->GetVar("change", &pszChange, 0, 0);
pVars->GetVar("time", &pszTime, 0, 0);
pVars->GetVar("user", &pszUser, 0, 0);
pVars->GetVar("desc", &pszDesc, 0, 0);
//pVars->GetVar("client", &pszClient, 0, 0);
//pVars->GetVar("status", &pszStatus, 0, 0);
nChange = atoi(pszChange);
ttTime = atoi(pszTime);
tmTime = *gmtime(&ttTime);
StringCchCopy(szDesc, DIMA(szDesc), pszDesc);
szDesc[sizeof(szDesc) - 1] = 0;
for (char *psz = szDesc; *psz; ++psz)
if (*psz == '\r' || *psz == '\n')
*psz = ' ';
printf("%6d %2d/%02d/%02d %2d:%02d:%02d %-20s %.28s\n",
nChange,
tmTime.tm_mon, tmTime.tm_mday, tmTime.tm_year % 100,
tmTime.tm_hour, tmTime.tm_min, tmTime.tm_sec,
pszUser,
szDesc);
}
else
{
// sample implementation -- merely dumps the variables; useful only
// for inspecting the output and learning the possible variables.
HRESULT hr;
const char *pszVar;
const char *pszValue;
BOOL fUnicode;
int ii;
for (ii = 0; 1; ii++)
{
hr = pVars->GetVarByIndex(ii, &pszVar, &pszValue, 0, &fUnicode);
if (hr != S_OK)
break;
// output the variable name and value
printf(fUnicode ? "%s[unicode]=%S\n" : "%s=%s\n", pszVar, pszValue);
}
}
return S_OK;
}
/*----------------------------------------------------------------------------
ISDClientUser::Finished
Called by ISDClientUser::Run when a command has finished. The command
may or may not have completed successfully.
For example, this is where SD.EXE displays the auto-summary (see the
-Y option in 'sd -?' for more information).
Rets:
The return value is ignored. For future compatibility, the method
should return E_NOTIMPL if it is not implemented, or S_OK for success.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::Finished()
{
// your code here
return S_OK;
}
// ---- ISDInputUser ------------------------------------------------------
ImplementEmbeddedUnknown(ClientUser, ISDInputUser)
/*----------------------------------------------------------------------------
ISDClientUser::InputData
Called to provide data to 'spec-command -i', where spec-command is
branch, change, client, label, protect, user, etc.
Args:
pvarInput - [in] pointer to VARIANT to contain input data.
NOTE: SD will convert the BSTR from codepage 1200
(Unicode) to CP_OEMCP (the OEM codepage).
Rets:
HRESULT - return S_OK to indicate strInput contains the data.
return an error HRESULT code to indicate an error
has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDInputUser::InputData( VARIANT* pvarInput )
{
return E_NOTIMPL;
}
/*----------------------------------------------------------------------------
ISDInputUser::Prompt
Called to prompt the user for a response. Called by 'resolve', and
also when prompting the user to enter a password.
Args:
pszPrompt - [in] prompt string.
pvarResponse - [in] pointer to VARIANT to contain user's response.
NOTE: SD will convert the BSTR from codepage 1200
(Unicode) to CP_OEMCP (the OEM codepage).
fPassword - [in] prompting for a password (hide the input text).
Rets:
HRESULT - return S_OK to indicate pvarResponse contains the
user's response. return an error HRESULT code to
indicate an error has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDInputUser::Prompt( const char* pszPrompt, VARIANT* pvarResponse, BOOL fPassword )
{
char sz[1024];
if (fPassword)
return E_NOTIMPL;
if (s_fVerbose)
printf("prompt:\t");
printf("%s", pszPrompt);
fflush(stdout);
fflush(stdin);
fgets(sz, sizeof(sz), stdin);
return VariantSet(pvarResponse, sz);
}
/*----------------------------------------------------------------------------
ISDInputUser::PromptYesNo
Called to prompt the user for a yes/no response.
Currently only called by 'resolve'.
Args:
pszPrompt - [in] prompt string.
Rets:
HRESULT - return S_OK for Yes. return S_FALSE for No. return
E_NOTIMPL to allow the SDAPI to perform the default
behavior, which is to call ISDClientUser::Prompt and
loop until the user responds y/Y/n/N or an error
occurs. return other error HRESULT codes to
indicate an error has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDInputUser::PromptYesNo( const char* pszPrompt )
{
return E_NOTIMPL;
}
/*----------------------------------------------------------------------------
ISDInputUser::ErrorPause
Called to display an error message and wait for the user before
continuing.
Args:
pszError - [in] message string.
Rets:
HRESULT - return S_OK to continue. return an error HRESULT
code to indicate an error has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDInputUser::ErrorPause( const char* pszError )
{
EMBEDDEDTHIS(ClientUser, ISDInputUser);
char sz[1024];
pThis->OutputError(pszError);
printf("prompt:\tHit return to continue...");
fgets(sz, sizeof(sz), stdin);
return S_OK;
}
// ---- ISDActionUser -----------------------------------------------------
ImplementEmbeddedUnknown(ClientUser, ISDActionUser)
/*----------------------------------------------------------------------------
ISDActionUser::Diff
Called by 'resolve' when the user selects any of the 'd' (diff)
actions. Also called by 'diff'.
In particular, this is not called by 'diff2' because the server
computes the diff and sends the computed diff to the client.
Args:
pszDiffCmd - [in] may be NULL. user-defined command to launch
external diff engine, as defined by the SDDIFF or
SDUDIFF variables; see 'sd help variables' for more
information.
pszLeft - [in] name of Left file for the diff.
pszRight - [in] name of Right file for the diff.
eTextual - [in] indicates the lowest common denominator file
type for the 2 input files (non-textual, text, or
Unicode).
pszFlags - [in] flags for the diff engine (per the -d<flags>
option).
pszPaginateCmd - [in] may be NULL. user-defined command to pipe the
diff output through, as defined by the SDPAGER
variable; see 'sd help variables' for more info.
For example, "more.exe".
Rets:
HRESULT - return S_OK to indicate the diff has been performed
successfully. return E_NOTIMPL to allow the SDAPI
to perform the default behavior, which is to launch
an external diff engine (if defined) or use use the
internal SD diff engine. return other error HRESULT
codes to indicate an error has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDActionUser::Diff( const char *pszDiffCmd,
const char *pszLeft,
const char *pszRight,
DWORD eTextual,
const char *pszFlags,
const char *pszPaginateCmd )
{
return E_NOTIMPL;
}
/*----------------------------------------------------------------------------
ISDActionUser::EditForm
Called by all commands that launch a user form (e.g. 'branch',
'change', 'client', etc).
IMPORTANT NOTE:
This command is synchronous in nature; if your implementation launches
an editor, your code must not return until the user has finished
editing the file.
Args:
pszEditCmd - [in] may by NULL. user-defined command to launch
external editor, as defined by the SDFORMEDITOR
variable; see 'sd help variables' for more
information.
pszFile - [in] name of file to edit.
Rets:
HRESULT - return S_OK to indicate the user has finished
editing the file. return E_NOTIMPL to allow the
SDAPI to perform the default behavior, which is to
launch an external editor engine (if defined) or to
launch notepad.exe. return other error HRESULT
codes to indicate an error has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDActionUser::EditForm( const char *pszEditCmd,
const char *pszFile )
{
return E_NOTIMPL;
}
/*----------------------------------------------------------------------------
ISDActionUser::EditFile
Called by 'resolve' when the user selects any of the 'e' actions.
IMPORTANT NOTE:
This command is synchronous in nature; if your implementation launches
an editor, your code must not return until the user has finished
editing the file.
Args:
pszEditCmd - [in] may by NULL. user-defined command to launch
external editor, as defined by the SDEDITOR, or
SDUEDITOR variables; see 'sd help variables' for
more information.
pszFile - [in] name of file to edit.
eTextual - [in] indicates the file type (non-textual, text, or
Unicode).
Rets:
HRESULT - return S_OK to indicate the user has finished
editing the file. return E_NOTIMPL to allow the
SDAPI to perform the default behavior, which is to
launch an external editor engine (if defined) or to
launch notepad.exe. return other error HRESULT
codes to indicate an error has occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDActionUser::EditFile( const char *pszEditCmd,
const char *pszFile,
DWORD eTextual )
{
return E_NOTIMPL;
}
/*----------------------------------------------------------------------------
ISDActionUser::Merge
Called by the 'resolve' command when the user selects the 'm' action
to invoke an external merge engine.
Args:
pszMergeCmd - [in] may be NULL. user-defined command to launch
external merge engine, as defined by the SDMERGE
variable; see 'sd help variables' for more info.
pszBase - [in] name of Base file for the 3-way merge.
pszTheirs - [in] name of Theirs file for the 3-way merge.
pszYours - [in] name of Yours file for the 3-way merge.
pszResult - [in] name of file where the resulting merged file
must be written.
eTextual - [in] indicates the lowest common denominator file
type for the 3 input files (non-textual, text, or
Unicode).
Rets:
HRESULT - return S_OK to indicate the merge has been performed
successfully. return E_NOTIMPL to allow the SDAPI
to perform the default behavior, which is to launch
the external merge engine (if defined). return
other error HRESULT codes to indicate an error has
occurred.
----------------------------------------------------------------------------*/
STDMETHODIMP ClientUser::EISDActionUser::Merge( const char *pszMergeCmd,
const char *pszBase,
const char *pszTheirs,
const char *pszYours,
const char *pszResult,
DWORD eTextual )
{
return E_NOTIMPL;
}
///////////////////////////////////////////////////////////////////////////
// Console Mode
HANDLE g_hRestoreConsole = INVALID_HANDLE_VALUE;
DWORD g_dwResetConsoleMode;
void RestoreConsole_SetMode(DWORD dw)
{
g_hRestoreConsole = GetStdHandle(STD_OUTPUT_HANDLE);
g_dwResetConsoleMode = SetConsoleMode(g_hRestoreConsole, dw);
}
BOOL WINAPI RestoreConsole_BreakHandler(DWORD dwCtrlType)
{
if (g_hRestoreConsole != INVALID_HANDLE_VALUE)
SetConsoleMode(g_hRestoreConsole, g_dwResetConsoleMode);
#if 0
if (g_hRestoreConsole != INVALID_HANDLE_VALUE)
SetConsoleTextAttribute(g_hRestoreConsole, g_wRestoreAttr);
#endif
return FALSE;
}
///////////////////////////////////////////////////////////////////////////
// Options (a dumbed-down option parsing class)
enum { c_cMaxOptions = 20 };
enum OptFlag
{
// bitwise selectors
OPT_ONE = 0x01, // exactly one
OPT_TWO = 0x02, // exactly two
OPT_THREE = 0x04, // exactly three
OPT_MORE = 0x10, // more than three
OPT_NONE = 0x20, // require none
// combos of the above
OPT_OPT = OPT_NONE|OPT_ONE,
OPT_ANY = OPT_NONE|OPT_ONE|OPT_TWO|OPT_THREE|OPT_MORE,
OPT_SOME = OPT_ONE|OPT_TWO|OPT_THREE|OPT_MORE,
};
class Options
{
public:
Options() { m_cOpts = 0; m_pszError = 0; }
~Options() { delete m_pszError; }
BOOL Parse(int &argc, const char **&argv, const char *pszOpts,
int flag, const char *pszUsage);
const char* GetErrorString() const { Assert(m_pszError); return m_pszError; }
const char* GetValue(char chOpt, int iSubOpt) const;
const char* operator[](char chOpt) const { return GetValue(chOpt, 0); }
protected:
void ClearError() { delete m_pszError; m_pszError = 0; }
void SetError(const char *pszUsage, const char *pszFormat, ...);
private:
int m_cOpts;
char m_rgchFlags[c_cMaxOptions];
const char* m_rgpszOpts[c_cMaxOptions];
char* m_pszError;
};
static const char *GetArg(const char *psz, int &argc, const char **&argv)
{
psz++;
if (*psz)
return psz;
if (!argc)
return 0;
argc--;
argv++;
return argv[0];
}
BOOL Options::Parse(int &argc, const char **&argv, const char *pszOpts,
int flag, const char *pszUsage)
{
BOOL fSlash; // allow both - and /
const char *psz;
const char *pszArg;
Assert(pszOpts);
Assert(pszUsage);
ClearError();
fSlash = (*pszOpts == '/');
if (fSlash)
pszOpts++;
// parse flags
while (argc)
{
if (argv[0][0] != '-' && (!fSlash || argv[0][0] != '/'))
break; // not a flag, so done parsing
if (argv[0][1] == '-')
{
// '--' is special and means that subsequent arguments should
// not be treated as flags even if they being with '-'.
argc--;
argv++;
break;
}
pszArg = argv[0];
while (TRUE)
{
pszArg++; // skip the '-' or option character
if (!*pszArg)
break;
#ifdef DEBUG
if (*pszArg == '!')
{
DebugBreak();
continue;
}
#endif
psz = pszOpts;
while (*psz && *psz != *pszArg)
psz++;
if (!*psz)
{
SetError(pszUsage, "Invalid option: '%c'.", *pszArg);
return FALSE;
}
if (m_cOpts >= c_cMaxOptions)
{
SetError(pszUsage, "Too many options.");
return FALSE;
}
m_rgchFlags[m_cOpts] = *pszArg;
m_rgpszOpts[m_cOpts] = "true";
if (psz[1] == '.')
{
m_rgpszOpts[m_cOpts++] = pszArg + 1;
break;
}
else if (psz[1] == ':')
{
psz = GetArg(pszArg, argc, argv);
if (!psz)
{
SetError(pszUsage, "Option '%c' missing required argument.", *pszArg);
return FALSE;
}
m_rgpszOpts[m_cOpts++] = psz;
break;
}
m_cOpts++;
}
argc--;
argv++;
}
// check number of arguments
if (!((argc == 0 && (flag & OPT_NONE)) ||
(argc == 1 && (flag & OPT_ONE)) ||
(argc == 2 && (flag & OPT_TWO)) ||
(argc == 3 && (flag & OPT_THREE)) ||
(argc > 3 && (flag & OPT_MORE))))
{
SetError(pszUsage, "Missing/wrong number of arguments.");
return FALSE;
}
return TRUE;
}
void Options::SetError(const char *pszUsage, const char *pszFormat, ...)
{
int cch;
va_list args;
va_start(args, pszFormat);
ClearError();
m_pszError = new char[1024]; //$ todo: (chrisant) BUFFER OVERRUN
StringCchPrintf(m_pszError, 1024, "Usage: %s\n", pszUsage);
cch = strlen(m_pszError);
StringCchVPrintfEx(m_pszError + cch,
1024 - cch,
NULL,
NULL,
0,
pszFormat,
args);
va_end(args);
}
const char *Options::GetValue(char chOpt, int iSubOpt) const
{
for (int ii = m_cOpts; ii--;)
if (chOpt == m_rgchFlags[ii])
if (iSubOpt-- == 0)
return m_rgpszOpts[ii];
return 0;
}
///////////////////////////////////////////////////////////////////////////
// RunCmd
static void PrintError(HRESULT hr)
{
char sz[1024];
int cch;
cch = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
0, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
sz, sizeof(sz), NULL);
sz[cch] = 0;
fprintf(stderr, "error: (0x%08.8x)\n%s", hr, sz);
}
static BOOL FStrPrefixCut(const char *pszPrefix, const char **ppsz)
{
int cch = strlen(pszPrefix);
BOOL fPrefix = (strncmp(*ppsz, pszPrefix, cch) == 0 && (!(*ppsz)[cch] || isspace((*ppsz)[cch])));
if (fPrefix)
{
*ppsz += cch;
while (isspace(**ppsz))
(*ppsz)++;
}
return fPrefix;
}
HRESULT Cmd_Detect(ISDClientApi *papi, const char *psz)
{
HRESULT hr = S_OK;
ISDClientUtilities *putil;
BOOL fServer = FALSE;
// check for -s flag, to detect based on the server's capabilities
if (FStrPrefixCut("-s", &psz))
fServer = TRUE;
// check for file argument
if (*psz && *psz != '-')
{
hr = papi->QueryInterface(IID_ISDClientUtilities, (void**)&putil);
if (SUCCEEDED(hr))
{
DWORD tt;
const char *pszType;
// detect file type
hr = putil->DetectType(psz, &tt, &pszType, fServer);
if (SUCCEEDED(hr))
{
if (pszType)
{
const char *pszTT;
switch (tt)
{
default:
case SDTT_NONTEXT: pszTT = "SDT_NONTEXT"; break;
case SDTT_TEXT: pszTT = "SDT_TEXT"; break;
case SDTT_UNICODE: pszTT = "SDT_UNICODE"; break;
}
printf("%s - %s (%s)\n", psz, pszType, pszTT);
}
else
{
printf("%s - unable to determine file type.\n", psz);
}
}
else
{
PrintError(hr);
}
putil->Release();
}
}
else
{
fprintf(stderr, "Usage: detect [-s] file\n\n"
"The -s flag set the fServer parameter to TRUE in the DetectType call.\n"
"Please refer to the SDAPI documentation for more information.\n");
}
return hr;
}
HRESULT Cmd_Set(ISDClientApi *papi, const char *psz)
{
HRESULT hr = S_OK;
ISDClientUtilities *putil;
char szVar[64];
char szService[64];
const char *pszValue;
szVar[0] = 0;
szService[0] = 0;
// check for the "-S servicename" optional flag
if (FStrPrefixCut("-S", &psz))
{
pszValue = psz;
while (*pszValue && !isspace(*pszValue))
pszValue++;
lstrcpyn(szService, psz, min(pszValue - psz + 1, sizeof(szService)));
psz = pszValue;
while (isspace(*psz))
psz++;
}
// find the end of the variable name
pszValue = strpbrk(psz, "= \t");
if (*psz && *psz != '-' && pszValue && *pszValue == '=')
{
// copy the variable name
lstrcpyn(szVar, psz, min(pszValue - psz + 1, sizeof(szVar)));
pszValue++;
hr = papi->QueryInterface(IID_ISDClientUtilities, (void**)&putil);
if (SUCCEEDED(hr))
{
// set the variable and value
hr = putil->Set(szVar, pszValue, FALSE, szService);
if (FAILED(hr))
{
PrintError(hr);
}
putil->Release();
}
}
else
{
fprintf(stderr, "Usage: set [-S service] var=[value]\n");
}
return hr;
}
HRESULT Cmd_Query(ISDClientApi *papi, const char *psz)
{
HRESULT hr = S_OK;
ISDClientUtilities *putil;
char szService[64];
const char *pszValue;
szService[0] = 0;
// check for the "-S servicename" optional flag
if (FStrPrefixCut("-S", &psz))
{
pszValue = psz;
while (*pszValue && !isspace(*pszValue))
pszValue++;
lstrcpyn(szService, psz, min(pszValue - psz + 1, sizeof(szService)));
psz = pszValue;
while (isspace(*psz))
psz++;
}
// find the end of the (optional) variable name
pszValue = strpbrk(psz, "= \t");
if (*psz == '-' || pszValue)
{
fprintf(stderr, "Usage: query [-S service] [var]\n");
return S_OK;
}
hr = papi->QueryInterface(IID_ISDClientUtilities, (void**)&putil);
if (SUCCEEDED(hr))
{
ISDVars *pVars;
hr = putil->QuerySettings(psz, szService, &pVars);
if (SUCCEEDED(hr))
{
int ii;
for (ii = 0; 1; ii++)
{
const char *pszVar;
const char *pszValue;
const char *pszHow;
const char *pszType;
if (pVars->GetVarX("var", ii, &pszVar, 0, 0) != S_OK)
break;
pVars->GetVarX("value", ii, &pszValue, 0, 0);
pVars->GetVarX("how", ii, &pszHow, 0, 0);
pVars->GetVarX("type", ii, &pszType, 0, 0);
printf("%s=%s (%s)", pszVar, pszValue, pszHow);
if (strcmp(pszType, "env") != 0)
printf(" (%s)", pszType);
printf("\n");
}
pVars->Release();
}
else
{
PrintError(hr);
}
putil->Release();
}
return hr;
}
HRESULT RunCmd(ISDClientApi *papi, const char *psz, int argc, const char **argv, ClientUser *pui, BOOL fStructured)
{
BOOL fDemo = FALSE;
DWORD dwTicks;
HRESULT hr = S_OK;
char *pszFree = 0;
dbgPrintF("\nRUN:\t[%s]\n", psz);
dwTicks = GetTickCount();
if (FStrPrefixCut("detect", &psz))
{
hr = Cmd_Detect(papi, psz);
}
else if (FStrPrefixCut("set", &psz))
{
hr = Cmd_Set(papi, psz);
}
else if (FStrPrefixCut("query", &psz))
{
hr = Cmd_Query(papi, psz);
}
else
{
if (FStrPrefixCut("demo", &psz))
{
// demo mode
fDemo = TRUE;
fStructured = TRUE;
// alloc string (length of command string, plus "changes ")
pszFree = (char*)malloc(lstrlen(psz) + 8 + 1);
// format string
StringCchPrintf(pszFree, lstrlen(psz) + 8 + 1, "changes %s", psz);
// use the formatted string
psz = pszFree;
}
pui->SetDemo(fDemo);
if (argc && argv)
papi->SetArgv(argc, argv);
hr = papi->Run(psz, pui, fStructured);
}
dwTicks = GetTickCount() - dwTicks;
dbgPrintF("[took %dms to run command]\n", dwTicks);
free(pszFree);
return hr;
}
///////////////////////////////////////////////////////////////////////////
// main
int __cdecl main(int argc, const char **argv)
{
ClientUser *pui = 0;
SPAPI spapi;
const char *pszFile = 0;
#if 0
BOOL fCreate = FALSE;
#endif
BOOL fDemo = FALSE;
BOOL fStructured = FALSE;
BOOL fStdin = FALSE;
int nRet = 0;
DWORD dwTicks;
HRESULT hr;
SetConsoleCtrlHandler(RestoreConsole_BreakHandler, TRUE);
if (argc)
{
// skip app name
argc--;
argv++;
if (argc && !strcmp(argv[0], "-!"))
{
argc--;
argv++;
DebugBreak();
}
#if 0
if (argc && !strcmp(argv[0], "-C"))
{
argc--;
argv++;
fCreate = TRUE;
}
#endif
}
#ifdef DEBUG
{
int n = argc;
const char **pp = argv;
printf("argc = %d\n", n);
for (int i = 0; i < n; i++)
{
printf("%d:\t[%s]\n", i, pp[0]);
pp++;
}
}
#endif
// parse options
Options opts;
const char *s;
if (!opts.Parse(argc, argv, "?p:u:P:c:H:i:I:x:dvT", OPT_OPT, usage))
{
fprintf(stderr, "%s", opts.GetErrorString());
return 1;
}
if (opts['?'])
{
// full usage text
printf("%s", long_usage);
return 0;
}
if (opts['d']) s_fDbg = TRUE;
if (opts['v']) s_fVerbose = TRUE;
if (opts['T']) fStructured = TRUE;
if (pszFile = opts['x'])
{
fStdin = FALSE;
if (strcmp(pszFile, "-") == 0)
{
pszFile = 0;
fStdin = TRUE;
}
}
// create SDAPI object
#if 1
hr = CreateSDAPIObject(CLSID_SDAPI, (void**)&spapi);
#else
hr = CoInitialize(0);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_SDAPI, NULL, CLSCTX_INPROC_SERVER,
IID_ISDClientApi, (void**)&spapi);
}
#endif
if (FAILED(hr))
{
fprintf(stderr, "ERROR:\tunable to create SDAPI object (0x%08x).\n", hr);
return 1;
}
// initialize the SDAPI object based on the options
if (s = opts['I']) spapi->LoadIniFile(s, TRUE);
if (s = opts['i']) spapi->LoadIniFile(s, FALSE);
if (s = opts['p']) spapi->SetPort(s);
if (s = opts['u']) spapi->SetUser(s);
if (s = opts['P']) spapi->SetPassword(s);
if (s = opts['c']) spapi->SetClient(s);
if (s = opts['H']) spapi->SetHost(s);
pui = new ClientUser;
if (!pui)
{
fprintf(stderr, "ERROR:\tunable to allocate ClientUser.\n");
return 1;
}
// connect to server
dbgPrintF("\nINIT:\tconnect to server\n");
dwTicks = GetTickCount();
hr = spapi->Init(pui);
dwTicks = GetTickCount() - dwTicks;
dbgPrintF("[took %dms to connect and authenticate]\n\n", dwTicks);
if (FAILED(hr))
goto LFatal;
// detect server version
SDVERINFO ver;
ver.dwSize = sizeof(ver);
if (spapi->GetVersion(&ver) == S_OK)
{
dbgPrintF("SDAPI:\t[%d.%d.%d.%d]\n",
ver.nApiMajor, ver.nApiMinor, ver.nApiBuild, ver.nApiDot);
if (ver.nSrvMajor || ver.nSrvMinor || ver.nSrvBuild || ver.nSrvDot)
{
dbgPrintF("SERVER:\t[%d.%d.%d.%d]\n",
ver.nSrvMajor, ver.nSrvMinor, ver.nSrvBuild, ver.nSrvDot);
}
}
else
{
dbgPrintF("SDAPI:\t[unknown build]\n");
dbgPrintF("SERVER:\t[unknown build]\n");
}
// run commands from file
if (pszFile || fStdin)
{
FILE *pfile = 0;
FILE *pfileClose = 0;
char sz[4096];
if (pszFile)
{
pfileClose = fopen(pszFile, "rt");
pfile = pfileClose;
}
else
{
pfile = stdin;
RestoreConsole_SetMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT);
}
if (pfile)
{
while (fgets(sz, sizeof(sz), pfile))
{
int cch = strlen(sz);
if (!cch)
continue;
// trim linefeeds
cch--;
while (sz[cch] == '\r' || sz[cch] == '\n')
{
sz[cch] = 0;
cch--;
}
cch++;
if (!cch)
continue;
// sleep
int cSleep = atoi(sz);
if (cSleep >= 0)
Sleep(cSleep * 1000);
// get command line
const char *psz = strchr(sz, ',');
if (psz)
psz++;
else
psz = sz;
// run command
hr = RunCmd(spapi, psz, 0, 0, pui, fStructured);
if (FAILED(hr))
{
const char *pszError = 0;
if (SUCCEEDED(spapi->GetErrorString(&pszError)) && pszError)
fprintf(stderr, "error:\n%s\n", pszError);
}
}
}
if (pfileClose)
fclose(pfileClose);
}
// run command from command line
if (argc)
{
hr = RunCmd(spapi, argv[0], argc - 1, argv + 1, pui, fStructured);
if (FAILED(hr))
goto LFatal;
}
// final
LOut:
pui->Release();
if (spapi)
nRet = FAILED(spapi->Final()) || nRet;
return nRet;
LFatal:
if (spapi)
{
const char *pszError = 0;
if (SUCCEEDED(spapi->GetErrorString(&pszError)) && pszError)
fprintf(stderr, "error:\n%s\n", pszError);
}
nRet = 1;
goto LOut;
}