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.
842 lines
16 KiB
842 lines
16 KiB
/*++
|
|
|
|
Copyright (c) 2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
srcsrv.cpp
|
|
|
|
Abstract:
|
|
|
|
This code obtaining version-controlled source.
|
|
|
|
Author:
|
|
|
|
patst
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
BOOL gInitialized = false;
|
|
LIST_ENTRY gProcessList;
|
|
DWORD gProcessListCount = 0;
|
|
DWORD gOptions = 0;
|
|
|
|
BOOL
|
|
DllMain(
|
|
IN PVOID hdll,
|
|
IN ULONG reason,
|
|
IN PCONTEXT context OPTIONAL
|
|
)
|
|
|
|
{
|
|
switch (reason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL error(DWORD err)
|
|
{
|
|
SetLastError(err);
|
|
return false;
|
|
}
|
|
|
|
PVOID MemAlloc(DWORD size)
|
|
{
|
|
PVOID rc;
|
|
|
|
rc = LocalAlloc(LPTR, size);
|
|
if (!rc)
|
|
return rc;
|
|
|
|
ZeroMemory(rc, size);
|
|
return rc;
|
|
}
|
|
|
|
|
|
void MemFree(PVOID p)
|
|
{
|
|
if (p)
|
|
LocalFree(p);
|
|
}
|
|
|
|
|
|
PPROCESS_ENTRY FindProcessEntry(HANDLE hp)
|
|
{
|
|
PLIST_ENTRY next;
|
|
PPROCESS_ENTRY pe;
|
|
DWORD count;
|
|
|
|
next = gProcessList.Flink;
|
|
if (!next)
|
|
return NULL;
|
|
|
|
for (count = 0; (PVOID)next != (PVOID)&gProcessList; count++)
|
|
{
|
|
assert(count < gProcessListCount);
|
|
if (count >= gProcessListCount)
|
|
return NULL;
|
|
pe = CONTAINING_RECORD(next, PROCESS_ENTRY, ListEntry);
|
|
next = pe->ListEntry.Flink;
|
|
if (pe->hProcess == hp)
|
|
return pe;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
PPROCESS_ENTRY
|
|
FindFirstProcessEntry(
|
|
)
|
|
{
|
|
return CONTAINING_RECORD(gProcessList.Flink, PROCESS_ENTRY, ListEntry);
|
|
}
|
|
|
|
|
|
void
|
|
SendDebugString(
|
|
PPROCESS_ENTRY pe,
|
|
LPSTR sz
|
|
)
|
|
{
|
|
__try
|
|
{
|
|
if (!pe)
|
|
pe = FindFirstProcessEntry();
|
|
|
|
if (!pe || !pe->callback)
|
|
return;
|
|
pe->callback(SRCSRVACTION_TRACE, (DWORD64)sz, pe->context);
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
}
|
|
}
|
|
|
|
|
|
#define dprint ((gOptions & SRCSRVOPT_DEBUG) == SRCSRVOPT_DEBUG)&&_dprint
|
|
#define eprint ((gOptions & SRCSRVOPT_DEBUG) == SRCSRVOPT_DEBUG)&&_eprint
|
|
|
|
#define pdprint ((gOptions & SRCSRVOPT_DEBUG) == SRCSRVOPT_DEBUG)&&_pdprint
|
|
#define pdeprint ((gOptions & SRCSRVOPT_DEBUG) == SRCSRVOPT_DEBUG)&&_pdeprint
|
|
|
|
bool
|
|
_pdprint(
|
|
PPROCESS_ENTRY pe,
|
|
LPSTR format,
|
|
...
|
|
)
|
|
{
|
|
static char buf[1000] = "SRCSRV: ";
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
_vsnprintf(buf+9, sizeof(buf)-9, format, args);
|
|
va_end(args);
|
|
SendDebugString(pe, buf);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
_peprint(
|
|
PPROCESS_ENTRY pe,
|
|
LPSTR format,
|
|
...
|
|
)
|
|
{
|
|
static char buf[1000] = "";
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
_vsnprintf(buf, sizeof(buf), format, args);
|
|
va_end(args);
|
|
SendDebugString(pe, buf);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
_dprint(
|
|
LPSTR format,
|
|
...
|
|
)
|
|
{
|
|
static char buf[1000] = "SRCSRV: ";
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
_vsnprintf(buf+9, sizeof(buf)-9, format, args);
|
|
va_end(args);
|
|
SendDebugString(NULL, buf);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
_eprint(
|
|
LPSTR format,
|
|
...
|
|
)
|
|
{
|
|
static char buf[1000] = "";
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
_vsnprintf(buf, sizeof(buf), format, args);
|
|
va_end(args);
|
|
SendDebugString(NULL, buf);
|
|
return true;
|
|
}
|
|
|
|
|
|
void FreeModuleEntry(PPROCESS_ENTRY pe, PMODULE_ENTRY me)
|
|
{
|
|
MemFree(me->stream);
|
|
MemFree(me);
|
|
}
|
|
|
|
|
|
PMODULE_ENTRY
|
|
FindModuleEntry(
|
|
PPROCESS_ENTRY pe,
|
|
DWORD64 base
|
|
)
|
|
{
|
|
static PLIST_ENTRY next = NULL;
|
|
PMODULE_ENTRY me;
|
|
|
|
if (base == (DWORD64)-1)
|
|
{
|
|
if (!next)
|
|
return NULL;
|
|
if ((PVOID)next == (PVOID)&pe->ModuleList)
|
|
{
|
|
// Reset to NULL so the list can be re-walked
|
|
next = NULL;
|
|
return NULL;
|
|
}
|
|
me = CONTAINING_RECORD( next, MODULE_ENTRY, ListEntry );
|
|
next = me->ListEntry.Flink;
|
|
return me;
|
|
}
|
|
|
|
next = pe->ModuleList.Flink;
|
|
if (!next)
|
|
return NULL;
|
|
|
|
while ((PVOID)next != (PVOID)&pe->ModuleList)
|
|
{
|
|
me = CONTAINING_RECORD(next, MODULE_ENTRY, ListEntry);
|
|
next = me->ListEntry.Flink;
|
|
if (base == me->base)
|
|
return me;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
char *
|
|
dotranslate(
|
|
PMODULE_ENTRY me,
|
|
char *input,
|
|
char *output,
|
|
DWORD outsize
|
|
)
|
|
{
|
|
int i;
|
|
char key[MAX_PATH + 1];
|
|
PVARIABLE var;
|
|
int cbkey;
|
|
char *s;
|
|
char *p;
|
|
|
|
assert(me && input && *input && output);
|
|
|
|
*output = 0;
|
|
|
|
for (i = 0, var = me->vars; i < me->cvars; i++, var++)
|
|
{
|
|
CopyStrArray(key, "${");
|
|
CatStrArray(key, var->key);
|
|
CatStrArray(key, "}");
|
|
cbkey = strlen(var->key) + 3;
|
|
for (s = input, p = strstr(s, key); p; s = p + cbkey, p = strstr(s, key))
|
|
{
|
|
CatNString(output, s, p - s, outsize);
|
|
CatString(output, var->val, outsize);
|
|
}
|
|
}
|
|
|
|
if (!*output)
|
|
CopyString(output, input, outsize);
|
|
|
|
return output;
|
|
}
|
|
|
|
#define translate(me, input, output) dotranslate(me, input, output, DIMA(output))
|
|
|
|
PSDFILE
|
|
find(
|
|
PMODULE_ENTRY me,
|
|
const char *file
|
|
)
|
|
{
|
|
int i;
|
|
PSDFILE sdf;
|
|
|
|
for (i = 0, sdf = me->sdfiles; i < me->cfiles; i++, sdf++)
|
|
{
|
|
if (!_strcmpi(sdf->path, file))
|
|
return sdf;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
DumpModuleEntries(
|
|
PPROCESS_ENTRY pe
|
|
)
|
|
{
|
|
static PLIST_ENTRY next = NULL;
|
|
PMODULE_ENTRY me;
|
|
PVARIABLE vars;
|
|
PSDFILE sdfiles;
|
|
char path[MAX_PATH + 1];
|
|
char depot[MAX_PATH + 1];
|
|
char loc[MAX_PATH + 1];
|
|
|
|
next = pe->ModuleList.Flink;
|
|
if (!next)
|
|
return;
|
|
|
|
while ((PVOID)next != (PVOID)&pe->ModuleList)
|
|
{
|
|
me = CONTAINING_RECORD(next, MODULE_ENTRY, ListEntry);
|
|
dprint("%s 0x%x\n", me->name, me->base);
|
|
dprint("variables:\n");
|
|
for (vars = me->vars; vars->key; vars++)
|
|
dprint("%s=%s\n", vars->key, vars->val);
|
|
dprint("source depot files:\n");
|
|
for (sdfiles = me->sdfiles; sdfiles->path; sdfiles++)
|
|
{
|
|
translate(me, sdfiles->path, path);
|
|
translate(me, sdfiles->depot, depot);
|
|
translate(me, sdfiles->loc, loc);
|
|
dprint("%s %s %s\n", path, depot, loc);
|
|
}
|
|
eprint("\n");
|
|
next = me->ListEntry.Flink;
|
|
}
|
|
}
|
|
|
|
|
|
char *
|
|
GetLine(
|
|
char *sz
|
|
)
|
|
{
|
|
for (;*sz; sz++)
|
|
{
|
|
if (*sz == '\n' || *sz == '\r')
|
|
{
|
|
*sz++ = 0;
|
|
if (*sz == 10)
|
|
sz++;
|
|
return (*sz) ? sz : NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
SetBlock(
|
|
char *sz
|
|
)
|
|
{
|
|
static char *labels[blMax] =
|
|
{
|
|
"SRCSRV: end", // blNone
|
|
"SRCSRV: variables", // blVars
|
|
"SRCSRV: source files" // blSource
|
|
};
|
|
static int lens[blMax] =
|
|
{
|
|
sizeof("SRCSRV: end") / sizeof(char) -1 , // blNone
|
|
sizeof("SRCSRV: variables") / sizeof(char) -1 , // blVars
|
|
sizeof("SRCSRV: source files") / sizeof(char) -1 // blSource
|
|
};
|
|
|
|
int i;
|
|
char *label;
|
|
|
|
for (i = blNone; i < blMax; i++)
|
|
{
|
|
label = labels[i];
|
|
if (!strncmp(sz, label, lens[i]))
|
|
return i;
|
|
}
|
|
|
|
return blMax;
|
|
}
|
|
|
|
|
|
bool
|
|
ParseVars(
|
|
char *sz,
|
|
PVARIABLE var
|
|
)
|
|
{
|
|
char *p;
|
|
|
|
p = (strchr(sz, '='));
|
|
if (!p)
|
|
return false;
|
|
|
|
*p = 0;
|
|
var->key = sz;
|
|
var->val = p + 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
ParseSD(
|
|
char *sz,
|
|
PSDFILE sdfile
|
|
)
|
|
{
|
|
char *p;
|
|
char *g;
|
|
|
|
sz += 4;
|
|
|
|
p = (strchr(sz, '*'));
|
|
if (!p)
|
|
return false;
|
|
*p++ = 0;
|
|
|
|
g = (strchr(p, '*'));
|
|
if (!g)
|
|
return false;
|
|
*g++ = 0;
|
|
|
|
sdfile->path = sz;
|
|
sdfile->depot = p;
|
|
sdfile->loc = g;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL
|
|
IndexStream(
|
|
PMODULE_ENTRY me
|
|
)
|
|
{
|
|
char *sz;
|
|
char *next;
|
|
int block;
|
|
int lines;
|
|
int bl;
|
|
PVARIABLE vars;
|
|
PSDFILE sdfiles;
|
|
|
|
// count the lines
|
|
|
|
for (sz = me->stream, lines = 0; *sz; sz++)
|
|
{
|
|
if (*sz == '\n')
|
|
lines++;
|
|
}
|
|
if (!lines)
|
|
return false;
|
|
|
|
me->vars = (PVARIABLE)MemAlloc(lines * sizeof(VARIABLE));
|
|
if (!me->vars)
|
|
return error(ERROR_NOT_ENOUGH_MEMORY);
|
|
me->sdfiles = (PSDFILE)MemAlloc(lines * sizeof(SDFILE));
|
|
if (!me->sdfiles)
|
|
return error(ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
block = blNone;
|
|
vars = me->vars;
|
|
sdfiles = me->sdfiles;
|
|
me->cfiles = 0;
|
|
me->cvars = 0;
|
|
for (sz = me->stream; sz; sz = next)
|
|
{
|
|
next = GetLine(sz);
|
|
bl = SetBlock(sz);
|
|
// dprint("%s\n", sz);
|
|
if (bl != blMax)
|
|
{
|
|
block = bl;
|
|
continue;
|
|
}
|
|
|
|
switch(block)
|
|
{
|
|
case blVars:
|
|
if (ParseVars(sz, vars))
|
|
{
|
|
vars++;
|
|
me->cvars++;
|
|
}
|
|
break;
|
|
case blSource:
|
|
if (ParseSD(sz, sdfiles))
|
|
{
|
|
sdfiles++;
|
|
me->cfiles++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
DumpIndexes(me);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SrcSrvSetOptions(
|
|
DWORD opts
|
|
)
|
|
{
|
|
DWORD rc = gOptions;
|
|
gOptions = opts;
|
|
return rc;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SrcSrvGetOptions(
|
|
)
|
|
{
|
|
return gOptions;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvInit(
|
|
HANDLE hProcess,
|
|
LPCSTR path
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
|
|
// test the path for copying files to...
|
|
|
|
if (!path || !*path)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
if (!EnsurePathExists(path, NULL, 0, TRUE))
|
|
return false;
|
|
|
|
if (!gInitialized)
|
|
{
|
|
gInitialized = true;
|
|
InitializeListHead(&gProcessList);
|
|
}
|
|
|
|
if (pe = FindProcessEntry(hProcess))
|
|
{
|
|
pe->cRefs++;
|
|
return error(ERROR_INVALID_HANDLE);
|
|
}
|
|
|
|
pe = (PPROCESS_ENTRY)MemAlloc(sizeof(PROCESS_ENTRY));
|
|
if (!pe)
|
|
return error(ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
pe->hProcess = hProcess;
|
|
pe->cRefs = 1;
|
|
CopyStrArray(pe->path, path);
|
|
gProcessListCount++;
|
|
InitializeListHead(&pe->ModuleList);
|
|
InsertTailList(&gProcessList, &pe->ListEntry);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvCleanup(
|
|
HANDLE hProcess
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
PLIST_ENTRY next;
|
|
PMODULE_ENTRY me;
|
|
|
|
pe = FindProcessEntry(hProcess);
|
|
if (!pe)
|
|
return error(ERROR_INVALID_HANDLE);
|
|
|
|
if (--pe->cRefs)
|
|
return true;
|
|
|
|
next = pe->ModuleList.Flink;
|
|
if (next)
|
|
{
|
|
while (next != &pe->ModuleList)
|
|
{
|
|
me = CONTAINING_RECORD(next, MODULE_ENTRY, ListEntry);
|
|
next = me->ListEntry.Flink;
|
|
FreeModuleEntry(pe, me);
|
|
}
|
|
}
|
|
|
|
RemoveEntryList(&pe->ListEntry);
|
|
MemFree(pe);
|
|
gProcessListCount--;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvSetTargetPath(
|
|
HANDLE hProcess,
|
|
LPCSTR path
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
|
|
pe = FindProcessEntry(hProcess);
|
|
if (!pe)
|
|
return error(ERROR_INVALID_HANDLE);
|
|
|
|
// test the path for copying files to...
|
|
|
|
if (!path || !*path)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
if (!EnsurePathExists(path, NULL, 0, TRUE))
|
|
return false;
|
|
|
|
// store the new path
|
|
|
|
CopyStrArray(pe->path, path);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvLoadModule(
|
|
HANDLE hProcess,
|
|
LPCSTR name,
|
|
DWORD64 base,
|
|
PVOID stream,
|
|
DWORD size
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
PMODULE_ENTRY me;
|
|
|
|
if (!base || !name || !*name || !stream || !*(PCHAR)stream || !size)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
pe = FindProcessEntry(hProcess);
|
|
if (!pe)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
me = FindModuleEntry(pe, base);
|
|
if (me)
|
|
SrcSrvUnloadModule(pe, base);
|
|
|
|
me = (PMODULE_ENTRY)MemAlloc(sizeof(MODULE_ENTRY));
|
|
if (!me)
|
|
return error(ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
me->base = base;
|
|
CopyStrArray(me->name, name);
|
|
me->stream = (char *)MemAlloc(size);
|
|
if (!me->stream)
|
|
{
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
goto error;
|
|
}
|
|
memcpy(me->stream, stream, size);
|
|
me->cbStream = size;
|
|
dprint(me->stream);
|
|
IndexStream(me);
|
|
InsertTailList(&pe->ModuleList, &me->ListEntry);
|
|
DumpModuleEntries(pe);
|
|
|
|
return true;
|
|
|
|
error:
|
|
FreeModuleEntry(pe, me);
|
|
return false;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvUnloadModule(
|
|
HANDLE hProcess,
|
|
DWORD64 base
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
PLIST_ENTRY next;
|
|
PMODULE_ENTRY me;
|
|
|
|
pe = FindProcessEntry(hProcess);
|
|
if (!pe)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
next = pe->ModuleList.Flink;
|
|
if (!next)
|
|
return error(ERROR_MOD_NOT_FOUND);
|
|
|
|
while (next != &pe->ModuleList)
|
|
{
|
|
me = CONTAINING_RECORD(next, MODULE_ENTRY, ListEntry);
|
|
if (me->base == base)
|
|
{
|
|
RemoveEntryList(next);
|
|
FreeModuleEntry(pe, me);
|
|
return true;
|
|
}
|
|
next = me->ListEntry.Flink;
|
|
}
|
|
|
|
return error(ERROR_MOD_NOT_FOUND);
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvRegisterCallback(
|
|
HANDLE hProcess,
|
|
PSRCSRVCALLBACKPROC callback,
|
|
DWORD64 context
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
|
|
pe = FindProcessEntry(hProcess);
|
|
if (!pe)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
pe->callback = callback;
|
|
pe->context = context;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BOOL
|
|
WINAPI
|
|
SrcSrvGetFile(
|
|
HANDLE hProcess,
|
|
DWORD64 base,
|
|
LPCSTR filename,
|
|
LPSTR target,
|
|
DWORD trgsize
|
|
)
|
|
{
|
|
PPROCESS_ENTRY pe;
|
|
PMODULE_ENTRY me;
|
|
PSDFILE sdf;
|
|
char name[MAX_PATH + 1];
|
|
char ext[MAX_PATH + 1];
|
|
BOOL rc;
|
|
char depot[MAX_PATH + 1];
|
|
char loc[MAX_PATH + 1];
|
|
char cmd[MAX_PATH * 2];
|
|
STARTUPINFO si;
|
|
PROCESS_INFORMATION pi;
|
|
|
|
if (!base || !filename || !*filename || !target)
|
|
return error(ERROR_INVALID_PARAMETER);
|
|
|
|
*target = 0;
|
|
|
|
pe = FindProcessEntry(hProcess);
|
|
if (!pe)
|
|
return error(ERROR_INVALID_HANDLE);
|
|
|
|
me = FindModuleEntry(pe, base);
|
|
if (!me)
|
|
return error(ERROR_MOD_NOT_FOUND);
|
|
|
|
// get the matching file entry
|
|
|
|
sdf = find(me, filename);
|
|
if (!sdf)
|
|
return error(ERROR_FILE_NOT_FOUND);
|
|
|
|
// build the target path and command line for source depot
|
|
|
|
#if 0
|
|
_splitpath(filename, NULL, NULL, name, ext);
|
|
strcpy(target, pe->path); // SECURITY: Don't know size of target buffer.
|
|
EnsureTrailingBackslash(target);
|
|
strcat(target, name); // SECURITY: Don't know size of target buffer.
|
|
strcat(target, ext); // SECURITY: Don't know size of target buffer.
|
|
|
|
CreateTargetPath(pe, sdf, target);
|
|
#else
|
|
strcpy(target, pe->path); // SECURITY: Don't know size of target buffer.
|
|
EnsureTrailingBackslash(target);
|
|
_splitpath(filename, NULL, NULL, name, ext);
|
|
strcat(target, name); // SECURITY: Don't know size of target buffer.
|
|
if (*ext)
|
|
strcat(target, ext); // SECURITY: Don't know size of target buffer.
|
|
#endif
|
|
|
|
PrintString(cmd,
|
|
DIMA(cmd),
|
|
"sd.exe -p %s print -o %s -q %s",
|
|
translate(me, sdf->depot, depot),
|
|
target,
|
|
translate(me, sdf->loc, loc));
|
|
|
|
// execute the source depot command
|
|
|
|
ZeroMemory((void *)&si, sizeof(si));
|
|
rc = CreateProcess(NULL,
|
|
cmd,
|
|
NULL,
|
|
NULL,
|
|
false,
|
|
0,
|
|
NULL,
|
|
pe->path,
|
|
&si,
|
|
&pi);
|
|
if (!rc)
|
|
*target = 0;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
|