You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1804 lines
44 KiB
1804 lines
44 KiB
|
|
#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(" ù ");
|
|
|
|
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(" ù ");
|
|
|
|
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;
|
|
}
|
|
|
|
|