mirror of https://github.com/lianthony/NT4.0
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.
1423 lines
37 KiB
1423 lines
37 KiB
/*++
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
fileq2.c
|
|
|
|
Abstract:
|
|
|
|
Setup file queue routines for enqueing copy operations.
|
|
|
|
Author:
|
|
|
|
Ted Miller (tedm) 15-Feb-1995
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "setupntp.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
PSOURCE_MEDIA_INFO
|
|
pSetupQueueSourceMedia(
|
|
IN OUT PSP_FILE_QUEUE Queue,
|
|
IN OUT PSP_FILE_QUEUE_NODE QueueNode,
|
|
IN LONG SourceRootStringId,
|
|
IN PCTSTR SourceDescription, OPTIONAL
|
|
IN PCTSTR SourceTagfile OPTIONAL
|
|
);
|
|
|
|
BOOL
|
|
pSetupQueueSingleCopy(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN HINF InfHandle,
|
|
IN HINF ListInfHandle, OPTIONAL
|
|
IN PCTSTR SectionName, OPTIONAL
|
|
IN PCTSTR SourceRootPath,
|
|
IN PCTSTR SourceFilename,
|
|
IN PCTSTR TargetFilename,
|
|
IN DWORD CopyStyle
|
|
);
|
|
|
|
BOOL
|
|
pSetupGetSourceAllInfo(
|
|
IN HINF InfHandle,
|
|
IN UINT SourceId,
|
|
OUT PCTSTR *Description,
|
|
OUT PCTSTR *Tagfile,
|
|
OUT PCTSTR *RelativePath
|
|
);
|
|
|
|
|
|
//
|
|
// HACK ALERT!!! HACK HACK HACK!!!!
|
|
//
|
|
// There might be an override platform specified. If this is so,
|
|
// we will look for \i386, \mips, etc as the final component of the
|
|
// specified path when queuing files, and replace it with the
|
|
// override path. This is a TOTAL HACK.
|
|
//
|
|
PCTSTR PlatformPathOverride;
|
|
CRITICAL_SECTION PlatformPathOverrideCritSect;
|
|
|
|
VOID
|
|
pSetupInitPlatformPathOverrideSupport(
|
|
IN BOOL Init
|
|
)
|
|
{
|
|
if(Init) {
|
|
InitializeCriticalSection(&PlatformPathOverrideCritSect);
|
|
} else {
|
|
DeleteCriticalSection(&PlatformPathOverrideCritSect);
|
|
}
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// ANSI version
|
|
//
|
|
BOOL
|
|
SetupSetPlatformPathOverrideA(
|
|
IN PCSTR Override OPTIONAL
|
|
)
|
|
{
|
|
BOOL b;
|
|
DWORD rc;
|
|
PCWSTR p;
|
|
|
|
if(Override) {
|
|
rc = CaptureAndConvertAnsiArg(Override,&p);
|
|
} else {
|
|
p = NULL;
|
|
rc = NO_ERROR;
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
b = SetupSetPlatformPathOverrideW(p);
|
|
rc = GetLastError();
|
|
} else {
|
|
b = FALSE;
|
|
}
|
|
|
|
if(p) {
|
|
MyFree(p);
|
|
}
|
|
|
|
SetLastError(rc);
|
|
return(b);
|
|
}
|
|
#else
|
|
//
|
|
// Unicode stub
|
|
//
|
|
BOOL
|
|
SetupSetPlatformPathOverrideW(
|
|
IN PCWSTR Override OPTIONAL
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Override);
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return(FALSE);
|
|
}
|
|
#endif
|
|
|
|
BOOL
|
|
SetupSetPlatformPathOverride(
|
|
IN PCTSTR Override OPTIONAL
|
|
)
|
|
{
|
|
BOOL b;
|
|
DWORD rc;
|
|
|
|
EnterCriticalSection(&PlatformPathOverrideCritSect);
|
|
|
|
if(Override) {
|
|
if(PlatformPathOverride) {
|
|
MyFree(PlatformPathOverride);
|
|
PlatformPathOverride = NULL;
|
|
}
|
|
|
|
try {
|
|
b = ((PlatformPathOverride = DuplicateString(Override)) != NULL);
|
|
if(!b) {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
b = FALSE;
|
|
rc = ERROR_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
if(PlatformPathOverride) {
|
|
MyFree(PlatformPathOverride);
|
|
PlatformPathOverride = NULL;
|
|
}
|
|
b = TRUE;
|
|
}
|
|
|
|
LeaveCriticalSection(&PlatformPathOverrideCritSect);
|
|
|
|
if(!b) {
|
|
SetLastError(rc);
|
|
}
|
|
return(b);
|
|
}
|
|
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// ANSI version
|
|
//
|
|
BOOL
|
|
SetupQueueCopyA(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN PCSTR SourceRootPath,
|
|
IN PCSTR SourcePath, OPTIONAL
|
|
IN PCSTR SourceFilename,
|
|
IN PCSTR SourceDescription, OPTIONAL
|
|
IN PCSTR SourceTagfile, OPTIONAL
|
|
IN PCSTR TargetDirectory,
|
|
IN PCSTR TargetFilename, OPTIONAL
|
|
IN DWORD CopyStyle
|
|
)
|
|
{
|
|
PCWSTR sourceRootPath;
|
|
PCWSTR sourcePath;
|
|
PCWSTR sourceFilename;
|
|
PCWSTR sourceDescription;
|
|
PCWSTR sourceTagfile;
|
|
PCWSTR targetDirectory;
|
|
PCWSTR targetFilename;
|
|
BOOL b;
|
|
DWORD rc;
|
|
|
|
sourceRootPath = NULL;
|
|
sourcePath = NULL;
|
|
sourceFilename = NULL;
|
|
sourceDescription = NULL;
|
|
sourceTagfile = NULL;
|
|
targetDirectory = NULL;
|
|
targetFilename = NULL;
|
|
rc = NO_ERROR;
|
|
|
|
if(SourceRootPath) {
|
|
rc = CaptureAndConvertAnsiArg(SourceRootPath,&sourceRootPath);
|
|
}
|
|
if((rc == NO_ERROR) && SourcePath) {
|
|
rc = CaptureAndConvertAnsiArg(SourcePath,&sourcePath);
|
|
}
|
|
if((rc == NO_ERROR) && SourceFilename) {
|
|
rc = CaptureAndConvertAnsiArg(SourceFilename,&sourceFilename);
|
|
}
|
|
if((rc == NO_ERROR) && SourceDescription) {
|
|
rc = CaptureAndConvertAnsiArg(SourceDescription,&sourceDescription);
|
|
}
|
|
if((rc == NO_ERROR) && SourceTagfile) {
|
|
rc = CaptureAndConvertAnsiArg(SourceTagfile,&sourceTagfile);
|
|
}
|
|
if((rc == NO_ERROR) && TargetDirectory) {
|
|
rc = CaptureAndConvertAnsiArg(TargetDirectory,&targetDirectory);
|
|
}
|
|
if((rc == NO_ERROR) && TargetFilename) {
|
|
rc = CaptureAndConvertAnsiArg(TargetFilename,&targetFilename);
|
|
}
|
|
|
|
if(rc == NO_ERROR) {
|
|
b = SetupQueueCopyW(
|
|
QueueHandle,
|
|
sourceRootPath,
|
|
sourcePath,
|
|
sourceFilename,
|
|
sourceDescription,
|
|
sourceTagfile,
|
|
targetDirectory,
|
|
targetFilename,
|
|
CopyStyle
|
|
);
|
|
|
|
rc = GetLastError();
|
|
}
|
|
|
|
if(sourceRootPath) {
|
|
MyFree(sourceRootPath);
|
|
}
|
|
if(sourcePath) {
|
|
MyFree(sourcePath);
|
|
}
|
|
if(sourceFilename) {
|
|
MyFree(sourceFilename);
|
|
}
|
|
if(sourceDescription) {
|
|
MyFree(sourceDescription);
|
|
}
|
|
if(sourceTagfile) {
|
|
MyFree(sourceTagfile);
|
|
}
|
|
if(targetDirectory) {
|
|
MyFree(targetDirectory);
|
|
}
|
|
if(targetFilename) {
|
|
MyFree(targetFilename);
|
|
}
|
|
|
|
SetLastError(rc);
|
|
return(b);
|
|
}
|
|
#else
|
|
//
|
|
// Unicode stub
|
|
//
|
|
BOOL
|
|
SetupQueueCopyW(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN PCWSTR SourceRootPath,
|
|
IN PCWSTR SourcePath, OPTIONAL
|
|
IN PCWSTR SourceFilename,
|
|
IN PCWSTR SourceDescription, OPTIONAL
|
|
IN PCWSTR SourceTagfile, OPTIONAL
|
|
IN PCWSTR TargetDirectory,
|
|
IN PCWSTR TargetFilename, OPTIONAL
|
|
IN DWORD CopyStyle
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(QueueHandle);
|
|
UNREFERENCED_PARAMETER(SourceRootPath);
|
|
UNREFERENCED_PARAMETER(SourcePath);
|
|
UNREFERENCED_PARAMETER(SourceFilename);
|
|
UNREFERENCED_PARAMETER(SourceDescription);
|
|
UNREFERENCED_PARAMETER(SourceTagfile);
|
|
UNREFERENCED_PARAMETER(TargetDirectory);
|
|
UNREFERENCED_PARAMETER(TargetFilename);
|
|
UNREFERENCED_PARAMETER(CopyStyle);
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return(FALSE);
|
|
}
|
|
#endif
|
|
|
|
BOOL
|
|
SetupQueueCopy(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN PCTSTR SourceRootPath,
|
|
IN PCTSTR SourcePath, OPTIONAL
|
|
IN PCTSTR SourceFilename,
|
|
IN PCTSTR SourceDescription, OPTIONAL
|
|
IN PCTSTR SourceTagfile, OPTIONAL
|
|
IN PCTSTR TargetDirectory,
|
|
IN PCTSTR TargetFilename, OPTIONAL
|
|
IN DWORD CopyStyle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Place a copy operation on a setup file queue.
|
|
|
|
Arguments:
|
|
|
|
QueueHandle - supplies a handle to a setup file queue, as returned
|
|
by SetupOpenFileQueue.
|
|
|
|
SourceRootPath - Supplies the root of the source for this copy,
|
|
such as A:\ or \\FOO\BAR\BAZ.
|
|
|
|
SourcePath - if specified, supplies the path relative to SourceRootPath
|
|
where the file can be found.
|
|
|
|
SourceFilename - supplies the filename part of the file to be copied.
|
|
|
|
SourceDescription - if specified, supplies a description of the source
|
|
media, to be used during disk prompts.
|
|
|
|
SourceTagfile - if specified, supplies a tag file whose presence at
|
|
SourceRootPath indicates the presence of the source media.
|
|
If not specified, the file itself will be used as the tag file
|
|
if required (tagfiles are used only for removable media).
|
|
|
|
TargetDirectory - supplies the directory where the file is to be copied.
|
|
|
|
TargetFilename - if specified, supplies the name of the target file.
|
|
If not specified, the target file will have the same name as the source.
|
|
|
|
CopyStyle - supplies flags that control the behavior of the copy operation
|
|
for this file.
|
|
|
|
Return Value:
|
|
|
|
Boolean value indicating outcome. If FALSE, GetLastError() returns
|
|
extended error information.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSP_FILE_QUEUE Queue;
|
|
PSP_FILE_QUEUE_NODE QueueNode,TempNode;
|
|
PSOURCE_MEDIA_INFO Source;
|
|
TCHAR TempBuffer[MAX_PATH];
|
|
PCTSTR LastPathPart;
|
|
PCTSTR p;
|
|
int Size;
|
|
DWORD d;
|
|
|
|
Queue = (PSP_FILE_QUEUE)QueueHandle;
|
|
d = NO_ERROR;
|
|
|
|
//
|
|
// Allocate a queue structure.
|
|
//
|
|
QueueNode = MyMalloc(sizeof(SP_FILE_QUEUE_NODE));
|
|
if(!QueueNode) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean0;
|
|
}
|
|
|
|
//
|
|
// Operation is copy.
|
|
//
|
|
QueueNode->Operation = FILEOP_COPY;
|
|
QueueNode->InternalFlags = 0;
|
|
|
|
//
|
|
// HACK ALERT!!! HACK HACK HACK!!!!
|
|
//
|
|
// There might be an override platform specified. If this is so,
|
|
// we will look for \i386, \mips, etc as the final component of the
|
|
// specified path, and replace it with the override path.
|
|
// This is a TOTAL HACK.
|
|
//
|
|
EnterCriticalSection(&PlatformPathOverrideCritSect);
|
|
try {
|
|
if(PlatformPathOverride) {
|
|
p = SourcePath ? SourcePath : SourceRootPath;
|
|
if(LastPathPart = _tcsrchr(p,L'\\')) {
|
|
LastPathPart++;
|
|
} else {
|
|
LastPathPart = p;
|
|
}
|
|
#if defined(_ALPHA_)
|
|
if(!lstrcmpi(LastPathPart,TEXT("alpha"))) {
|
|
#elif defined(_MIPS_)
|
|
if(!lstrcmpi(LastPathPart,TEXT("mips"))) {
|
|
#elif defined(_PPC_)
|
|
if(!lstrcmpi(LastPathPart,TEXT("ppc"))) {
|
|
#elif defined(_X86_)
|
|
if(!lstrcmpi(LastPathPart,TEXT("x86")) || !lstrcmpi(LastPathPart,TEXT("i386"))) {
|
|
#endif
|
|
Size = LastPathPart - p;
|
|
Size = min(Size,MAX_PATH);
|
|
Size *= sizeof(TCHAR);
|
|
|
|
CopyMemory(TempBuffer,p,Size);
|
|
TempBuffer[Size/sizeof(TCHAR)] = 0;
|
|
|
|
//
|
|
// If the path was something like "mips" then TempBuffer
|
|
// will be empty and we don't want to introduce any extra
|
|
// backslashes.
|
|
//
|
|
if(*TempBuffer) {
|
|
ConcatenatePaths(TempBuffer,PlatformPathOverride,MAX_PATH,NULL);
|
|
} else {
|
|
lstrcpyn(TempBuffer,PlatformPathOverride,MAX_PATH);
|
|
}
|
|
|
|
if(SourcePath) {
|
|
SourcePath = TempBuffer;
|
|
} else {
|
|
SourceRootPath = TempBuffer;
|
|
}
|
|
}
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
d = ERROR_INVALID_PARAMETER;
|
|
}
|
|
LeaveCriticalSection(&PlatformPathOverrideCritSect);
|
|
if(d != NO_ERROR) {
|
|
goto clean1;
|
|
}
|
|
|
|
//
|
|
// NOTE: When adding the following strings to the string table, we cast away
|
|
// their CONST-ness to avoid a compiler warning. Since we are adding them
|
|
// case-sensitively, we are guaranteed they will not be modified.
|
|
//
|
|
try {
|
|
//
|
|
// Set up the source root path.
|
|
//
|
|
QueueNode->SourceRootPath = StringTableAddString(
|
|
Queue->StringTable,
|
|
(PTSTR)SourceRootPath,
|
|
STRTAB_CASE_SENSITIVE
|
|
);
|
|
|
|
if(QueueNode->SourceRootPath == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Set up the source path.
|
|
//
|
|
if(d == NO_ERROR) {
|
|
if(SourcePath) {
|
|
QueueNode->SourcePath = StringTableAddString(
|
|
Queue->StringTable,
|
|
(PTSTR)SourcePath,
|
|
STRTAB_CASE_SENSITIVE
|
|
);
|
|
|
|
if(QueueNode->SourcePath == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
} else {
|
|
QueueNode->SourcePath = -1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set up the source filename.
|
|
//
|
|
if(d == NO_ERROR) {
|
|
QueueNode->SourceFilename = StringTableAddString(
|
|
Queue->StringTable,
|
|
(PTSTR)SourceFilename,
|
|
STRTAB_CASE_SENSITIVE
|
|
);
|
|
|
|
if(QueueNode->SourceFilename == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set up the target directory.
|
|
//
|
|
if(d == NO_ERROR) {
|
|
QueueNode->TargetDirectory = StringTableAddString(
|
|
Queue->StringTable,
|
|
(PTSTR)TargetDirectory,
|
|
STRTAB_CASE_SENSITIVE
|
|
);
|
|
|
|
if(QueueNode->TargetDirectory == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set up the target filename.
|
|
//
|
|
if(d == NO_ERROR) {
|
|
QueueNode->TargetFilename = StringTableAddString(
|
|
Queue->StringTable,
|
|
(PTSTR)(TargetFilename ? TargetFilename
|
|
: SourceFilename),
|
|
STRTAB_CASE_SENSITIVE
|
|
);
|
|
|
|
if(QueueNode->TargetFilename == -1) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
d = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if(d != NO_ERROR) {
|
|
goto clean1;
|
|
}
|
|
|
|
//
|
|
// Set up the copy style flags
|
|
//
|
|
QueueNode->StyleFlags = CopyStyle;
|
|
QueueNode->Next = NULL;
|
|
|
|
//
|
|
// Set up the source media.
|
|
//
|
|
try {
|
|
Source = pSetupQueueSourceMedia(
|
|
Queue,
|
|
QueueNode,
|
|
QueueNode->SourceRootPath,
|
|
SourceDescription,
|
|
SourceTagfile
|
|
);
|
|
if(!Source) {
|
|
d = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
d = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
if(d != NO_ERROR) {
|
|
goto clean1;
|
|
}
|
|
|
|
//
|
|
// Link the node onto the end of the copy queue for this source media.
|
|
//
|
|
if(Source->CopyQueue) {
|
|
for(TempNode=Source->CopyQueue; TempNode->Next; TempNode=TempNode->Next) ;
|
|
TempNode->Next = QueueNode;
|
|
} else {
|
|
Source->CopyQueue = QueueNode;
|
|
}
|
|
|
|
Queue->CopyNodeCount++;
|
|
Source->CopyNodeCount++;
|
|
|
|
return(TRUE);
|
|
|
|
clean1:
|
|
MyFree(QueueNode);
|
|
clean0:
|
|
SetLastError(d);
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// ANSI version
|
|
//
|
|
BOOL
|
|
SetupQueueCopySectionA(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN PCSTR SourceRootPath,
|
|
IN HINF InfHandle,
|
|
IN HINF ListInfHandle, OPTIONAL
|
|
IN PCSTR Section,
|
|
IN DWORD CopyStyle
|
|
)
|
|
{
|
|
PWSTR sourcerootpath;
|
|
PWSTR section;
|
|
DWORD rc;
|
|
BOOL b;
|
|
|
|
rc = CaptureAndConvertAnsiArg(SourceRootPath,&sourcerootpath);
|
|
if(rc != NO_ERROR) {
|
|
SetLastError(rc);
|
|
return(FALSE);
|
|
}
|
|
rc = CaptureAndConvertAnsiArg(Section,§ion);
|
|
if(rc != NO_ERROR) {
|
|
MyFree(sourcerootpath);
|
|
SetLastError(rc);
|
|
return(FALSE);
|
|
}
|
|
|
|
b = SetupQueueCopySectionW(
|
|
QueueHandle,
|
|
sourcerootpath,
|
|
InfHandle,
|
|
ListInfHandle,
|
|
section,
|
|
CopyStyle
|
|
);
|
|
|
|
rc = GetLastError();
|
|
|
|
MyFree(sourcerootpath);
|
|
MyFree(section);
|
|
|
|
SetLastError(rc);
|
|
return(b);
|
|
}
|
|
#else
|
|
//
|
|
// Unicode stub
|
|
//
|
|
BOOL
|
|
SetupQueueCopySectionW(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN PCWSTR SourceRootPath,
|
|
IN HINF InfHandle,
|
|
IN HINF ListInfHandle, OPTIONAL
|
|
IN PCWSTR Section,
|
|
IN DWORD CopyStyle
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(QueueHandle);
|
|
UNREFERENCED_PARAMETER(SourceRootPath);
|
|
UNREFERENCED_PARAMETER(InfHandle);
|
|
UNREFERENCED_PARAMETER(ListInfHandle);
|
|
UNREFERENCED_PARAMETER(Section);
|
|
UNREFERENCED_PARAMETER(CopyStyle);
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return(FALSE);
|
|
}
|
|
#endif
|
|
|
|
BOOL
|
|
SetupQueueCopySection(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN PCTSTR SourceRootPath,
|
|
IN HINF InfHandle,
|
|
IN HINF ListInfHandle, OPTIONAL
|
|
IN PCTSTR Section,
|
|
IN DWORD CopyStyle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Queue an entire section in an inf file for copy. The section must be
|
|
in copy-section format and the inf file must contain [SourceDisksFiles]
|
|
and [SourceDisksNames] sections.
|
|
|
|
Arguments:
|
|
|
|
QueueHandle - supplies a handle to a setup file queue, as returned
|
|
by SetupOpenFileQueue.
|
|
|
|
SourceRootPath - supplies the root directory for the intended source.
|
|
This should be a sharepoint or a device root such as a:\ or g:\.
|
|
|
|
InfHandle - supplies a handle to an open inf file, that contains the
|
|
[SourceDisksFiles] and [SourceDisksNames] sections, and, if
|
|
ListInfHandle is not specified, contains the section names by Section.
|
|
This handle must be for a win95-style inf.
|
|
|
|
ListInfHandle - if specified, supplies a handle to an open inf file
|
|
containing the section to be queued for copy. Otherwise InfHandle
|
|
is assumed to contain the section.
|
|
|
|
Section - supplies the name of the section to be queued for copy.
|
|
|
|
CopyStyle - supplies flags that control the behavior of the copy operation
|
|
for this file.
|
|
|
|
Return Value:
|
|
|
|
Boolean value indicating outcome. If FALSE, GetLastError() returns
|
|
extended error information. Some of the files may have been queued.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
INFCONTEXT LineContext;
|
|
PCTSTR SourceFilename;
|
|
PCTSTR TargetFilename;
|
|
UINT Flags;
|
|
LONG LineCount;
|
|
|
|
//
|
|
// Note that there are no potential faults here so no try/excepts
|
|
// are necessary. pSetupQueueSingleCopy does all validation.
|
|
//
|
|
|
|
if(!ListInfHandle) {
|
|
ListInfHandle = InfHandle;
|
|
}
|
|
|
|
//
|
|
// Check for missing section
|
|
//
|
|
LineCount = SetupGetLineCount (ListInfHandle, Section);
|
|
if(LineCount == -1) {
|
|
SetLastError(ERROR_SECTION_NOT_FOUND);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// if section is empty, do nothing.
|
|
//
|
|
if(LineCount == 0) {
|
|
return(TRUE);
|
|
}
|
|
|
|
//
|
|
// The section has to exist and there has to be at least one line in it.
|
|
//
|
|
b = SetupFindFirstLine(ListInfHandle,Section,NULL,&LineContext);
|
|
if(!b) {
|
|
SetLastError(ERROR_SECTION_NOT_FOUND);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Iterate every line in the section.
|
|
//
|
|
do {
|
|
//
|
|
// Get the target filename out of the line.
|
|
// Field 1 is the target so there must be one for the line to be valid.
|
|
//
|
|
TargetFilename = pSetupFilenameFromLine(&LineContext,FALSE);
|
|
if(!TargetFilename) {
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Get source filename out of the line. If there is none, use
|
|
// the target name as the source name.
|
|
//
|
|
SourceFilename = pSetupFilenameFromLine(&LineContext,TRUE);
|
|
if(!SourceFilename || (*SourceFilename == 0)) {
|
|
SourceFilename = TargetFilename;
|
|
}
|
|
|
|
//
|
|
// If present, flags are field 3.
|
|
//
|
|
if(SetupGetIntField(&LineContext,4,(PINT)&Flags)) {
|
|
|
|
if(Flags & COPYFLG_WARN_IF_SKIP) {
|
|
CopyStyle |= SP_COPY_WARNIFSKIP;
|
|
}
|
|
|
|
if(Flags & COPYFLG_NOSKIP) {
|
|
CopyStyle |= SP_COPY_NOSKIP;
|
|
}
|
|
|
|
if(Flags & COPYFLG_NOVERSIONCHECK) {
|
|
CopyStyle &= ~SP_COPY_NEWER;
|
|
}
|
|
|
|
if(Flags & COPYFLG_FORCE_FILE_IN_USE) {
|
|
CopyStyle |= SP_COPY_FORCE_IN_USE;
|
|
CopyStyle |= SP_COPY_IN_USE_NEEDS_REBOOT;
|
|
}
|
|
|
|
if(Flags & COPYFLG_NO_OVERWRITE) {
|
|
CopyStyle |= SP_COPY_FORCE_NOOVERWRITE;
|
|
}
|
|
|
|
if(Flags & COPYFLG_NO_VERSION_DIALOG) {
|
|
CopyStyle |= SP_COPY_FORCE_NEWER;
|
|
}
|
|
|
|
if(Flags & COPYFLG_REPLACEONLY) {
|
|
CopyStyle |= SP_COPY_REPLACEONLY;
|
|
}
|
|
}
|
|
|
|
b = pSetupQueueSingleCopy(
|
|
QueueHandle,
|
|
InfHandle,
|
|
ListInfHandle,
|
|
Section,
|
|
SourceRootPath,
|
|
SourceFilename,
|
|
TargetFilename,
|
|
CopyStyle
|
|
);
|
|
|
|
if(!b) {
|
|
return(FALSE);
|
|
}
|
|
} while(SetupFindNextLine(&LineContext,&LineContext));
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pSetupQueueSingleCopy(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN HINF InfHandle,
|
|
IN HINF ListInfHandle, OPTIONAL
|
|
IN PCTSTR SectionName, OPTIONAL
|
|
IN PCTSTR SourceRootPath,
|
|
IN PCTSTR SourceFilename,
|
|
IN PCTSTR TargetFilename,
|
|
IN DWORD CopyStyle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add a single file to the copy queue, using the default source media
|
|
and destination as specified in an inf file.
|
|
|
|
Arguments:
|
|
|
|
QueueHandle - supplies a handle to a setup file queue, as returned
|
|
by SetupOpenFileQueue.
|
|
|
|
InfHandle - supplies a handle to an open inf file, that contains the
|
|
[SourceDisksFiles] and [SourceDisksNames] sections.
|
|
This handle must be for a win95-style inf.
|
|
|
|
ListInfHandle - if specified, supplies handle to the inf in which
|
|
the file being copied appears (such as in a file copy list section).
|
|
If not specified, this is assumed to be the same inf as InfHandle.
|
|
|
|
SourceRootPath - supplies the root directory for the intended source.
|
|
This should be a sharepoint or a device root such as a:\ or g:\.
|
|
|
|
SourceFilename - supplies the filename of the source file. Filename part
|
|
only.
|
|
|
|
TargetFilename - supplies the filename of the target file. Filename part
|
|
only.
|
|
|
|
CopyStyle - supplies flags that control the behavior of the copy operation
|
|
for this file.
|
|
|
|
Return Value:
|
|
|
|
Boolean value indicating outcome. If FALSE, GetLastError() returns
|
|
extended error information.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
UINT SourceId;
|
|
DWORD SizeRequired;
|
|
PTSTR TargetDirectory;
|
|
PCTSTR SourceDescription,SourceTagfile,SourceRelativePath;
|
|
DWORD rc;
|
|
TCHAR FileSubdir[MAX_PATH];
|
|
TCHAR RelativePath[MAX_PATH];
|
|
|
|
if(!ListInfHandle) {
|
|
ListInfHandle = InfHandle;
|
|
}
|
|
|
|
//
|
|
// Determine the source disk id and subdir where the file is located.
|
|
//
|
|
b = SetupGetSourceFileLocation(
|
|
InfHandle,
|
|
NULL,
|
|
SourceFilename,
|
|
&SourceId,
|
|
FileSubdir,
|
|
MAX_PATH,
|
|
&rc
|
|
);
|
|
|
|
if(!b) {
|
|
//
|
|
// Assume file subdir is too big and is invalid.
|
|
// Try to fetch just the id and assume there is no subdir.
|
|
//
|
|
if(b = SetupGetSourceFileLocation(InfHandle,NULL,SourceFilename,&SourceId,NULL,0,NULL)) {
|
|
FileSubdir[0] = 0;
|
|
}
|
|
}
|
|
|
|
if(b) {
|
|
//
|
|
// Get information about the source. Need the tag file,
|
|
// description, and relative source path.
|
|
//
|
|
b = pSetupGetSourceAllInfo(
|
|
InfHandle,
|
|
SourceId,
|
|
&SourceDescription,
|
|
&SourceTagfile,
|
|
&SourceRelativePath
|
|
);
|
|
|
|
if(!b) {
|
|
//
|
|
// Last error already set.
|
|
//
|
|
return(FALSE);
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// Assume there is no SourceDisksFiles section and fake it as best we can.
|
|
// Assume the media has a description of "Unknown," set the source path to
|
|
// the source root if there is one, and assume no tag file.
|
|
//
|
|
FileSubdir[0] = 0;
|
|
SourceDescription = NULL;
|
|
SourceTagfile = NULL;
|
|
SourceRelativePath = NULL;
|
|
}
|
|
|
|
//
|
|
// Determine the target path for the file.
|
|
//
|
|
if(b = SetupGetTargetPath(ListInfHandle,NULL,SectionName,NULL,0,&SizeRequired)) {
|
|
|
|
if(TargetDirectory = MyMalloc(SizeRequired*sizeof(TCHAR))) {
|
|
|
|
if(b = SetupGetTargetPath(ListInfHandle,NULL,SectionName,TargetDirectory,SizeRequired,NULL)) {
|
|
|
|
//
|
|
// Append the source relative path and the file subdir.
|
|
//
|
|
if(SourceRelativePath) {
|
|
lstrcpyn(RelativePath,SourceRelativePath,MAX_PATH);
|
|
if(FileSubdir[0]) {
|
|
ConcatenatePaths(RelativePath,FileSubdir,MAX_PATH,NULL);
|
|
}
|
|
} else {
|
|
RelativePath[0] = 0;
|
|
}
|
|
|
|
//
|
|
// Add to queue.
|
|
//
|
|
b = SetupQueueCopy(
|
|
QueueHandle,
|
|
SourceRootPath,
|
|
RelativePath,
|
|
SourceFilename,
|
|
SourceDescription,
|
|
SourceTagfile,
|
|
TargetDirectory,
|
|
TargetFilename,
|
|
CopyStyle
|
|
);
|
|
|
|
rc = GetLastError();
|
|
}
|
|
|
|
MyFree(TargetDirectory);
|
|
|
|
} else {
|
|
rc = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
} else {
|
|
rc = GetLastError();
|
|
}
|
|
|
|
if(SourceDescription) {
|
|
MyFree(SourceDescription);
|
|
}
|
|
if(SourceTagfile) {
|
|
MyFree(SourceTagfile);
|
|
}
|
|
if(SourceRelativePath) {
|
|
MyFree(SourceRelativePath);
|
|
}
|
|
|
|
if(!b) {
|
|
SetLastError(rc);
|
|
}
|
|
|
|
return(b);
|
|
}
|
|
|
|
|
|
#ifdef UNICODE
|
|
//
|
|
// ANSI version
|
|
//
|
|
BOOL
|
|
SetupQueueDefaultCopyA(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN HINF InfHandle,
|
|
IN PCSTR SourceRootPath,
|
|
IN PCSTR SourceFilename,
|
|
IN PCSTR TargetFilename,
|
|
IN DWORD CopyStyle
|
|
)
|
|
{
|
|
PWSTR sourcerootpath;
|
|
PWSTR sourcefilename;
|
|
PWSTR targetfilename;
|
|
DWORD rc;
|
|
BOOL b;
|
|
|
|
b = FALSE;
|
|
rc = CaptureAndConvertAnsiArg(SourceRootPath,&sourcerootpath);
|
|
if(rc == NO_ERROR) {
|
|
|
|
rc = CaptureAndConvertAnsiArg(SourceFilename,&sourcefilename);
|
|
if(rc == NO_ERROR) {
|
|
|
|
rc = CaptureAndConvertAnsiArg(TargetFilename,&targetfilename);
|
|
if(rc == NO_ERROR) {
|
|
|
|
b = SetupQueueDefaultCopyW(
|
|
QueueHandle,
|
|
InfHandle,
|
|
sourcerootpath,
|
|
sourcefilename,
|
|
targetfilename,
|
|
CopyStyle
|
|
);
|
|
|
|
rc = GetLastError();
|
|
|
|
MyFree(targetfilename);
|
|
}
|
|
|
|
MyFree(sourcefilename);
|
|
}
|
|
|
|
MyFree(sourcerootpath);
|
|
}
|
|
|
|
SetLastError(rc);
|
|
return(b);
|
|
}
|
|
#else
|
|
//
|
|
// Unicode stub
|
|
//
|
|
BOOL
|
|
SetupQueueDefaultCopyW(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN HINF InfHandle,
|
|
IN PCWSTR SourceRootPath,
|
|
IN PCWSTR SourceFilename,
|
|
IN PCWSTR TargetFilename,
|
|
IN DWORD CopyStyle
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(QueueHandle);
|
|
UNREFERENCED_PARAMETER(InfHandle);
|
|
UNREFERENCED_PARAMETER(SourceRootPath);
|
|
UNREFERENCED_PARAMETER(SourceFilename);
|
|
UNREFERENCED_PARAMETER(TargetFilename);
|
|
UNREFERENCED_PARAMETER(CopyStyle);
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
return(FALSE);
|
|
}
|
|
#endif
|
|
|
|
|
|
BOOL
|
|
SetupQueueDefaultCopy(
|
|
IN HSPFILEQ QueueHandle,
|
|
IN HINF InfHandle,
|
|
IN PCTSTR SourceRootPath,
|
|
IN PCTSTR SourceFilename,
|
|
IN PCTSTR TargetFilename,
|
|
IN DWORD CopyStyle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add a single file to the copy queue, using the default source media
|
|
and destination as specified in an inf file.
|
|
|
|
Arguments:
|
|
|
|
QueueHandle - supplies a handle to a setup file queue, as returned
|
|
by SetupOpenFileQueue.
|
|
|
|
InfHandle - supplies a handle to an open inf file, that contains the
|
|
[SourceDisksFiles] and [SourceDisksNames] sections.
|
|
This handle must be for a win95-style inf.
|
|
|
|
SourceRootPath - supplies the root directory for the intended source.
|
|
This should be a sharepoint or a device root such as a:\ or g:\.
|
|
|
|
SourceFilename - supplies the filename of the source file. Filename part
|
|
only.
|
|
|
|
TargetFilename - supplies the filename of the target file. Filename part
|
|
only.
|
|
|
|
CopyStyle - supplies flags that control the behavior of the copy operation
|
|
for this file.
|
|
|
|
Return Value:
|
|
|
|
Boolean value indicating outcome. If FALSE, GetLastError() returns
|
|
extended error information.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
|
|
b = pSetupQueueSingleCopy(
|
|
QueueHandle,
|
|
InfHandle,
|
|
NULL,
|
|
NULL,
|
|
SourceRootPath,
|
|
SourceFilename,
|
|
TargetFilename,
|
|
CopyStyle
|
|
);
|
|
|
|
return(b);
|
|
}
|
|
|
|
|
|
PSOURCE_MEDIA_INFO
|
|
pSetupQueueSourceMedia(
|
|
IN OUT PSP_FILE_QUEUE Queue,
|
|
IN OUT PSP_FILE_QUEUE_NODE QueueNode,
|
|
IN LONG SourceRootStringId,
|
|
IN PCTSTR SourceDescription, OPTIONAL
|
|
IN PCTSTR SourceTagfile OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set up a file queue node's source media descriptor pointer, creating a new
|
|
source media descriptor if necessary.
|
|
|
|
Arguments:
|
|
|
|
Queue - supplies pointer to file queue with which the queue node
|
|
is associated.
|
|
|
|
QueueNode - supplies file queue node whose source media descriptor pointer
|
|
is to be set.
|
|
|
|
SourceRootStringId - supplies string id of root to source (something like a:\).
|
|
|
|
SourceDescription - if specified, supplies a description for the media.
|
|
|
|
SourceTagfile - if specified, supplies a tag file for the media.
|
|
Ignored if SourceDescription is not specified.
|
|
|
|
Return Value:
|
|
|
|
Pointer to source media info structure, or NULL if out of memory.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG DescriptionStringId;
|
|
LONG TagfileStringId;
|
|
PSOURCE_MEDIA_INFO Source,LastSource;
|
|
BOOL b1,b2;
|
|
TCHAR TempTagfileString[MAX_PATH];
|
|
TCHAR TempSrcDescString[LINE_LEN];
|
|
|
|
//
|
|
// If no description is specified, force the tagfile to none.
|
|
//
|
|
if(!SourceDescription) {
|
|
SourceTagfile = NULL;
|
|
}
|
|
|
|
if(SourceDescription) {
|
|
//
|
|
// Description specified. See if it's in the table. If not,
|
|
// no need to search the list of media descriptors because we know
|
|
// we can't find a match.
|
|
//
|
|
// (We must first copy this string to a writeable buffer, to speed up the
|
|
// case-insensitive lookup.
|
|
//
|
|
lstrcpyn(TempSrcDescString, SourceDescription, SIZECHARS(TempSrcDescString));
|
|
DescriptionStringId = StringTableLookUpString(Queue->StringTable,
|
|
TempSrcDescString,
|
|
STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE
|
|
);
|
|
b1 = (DescriptionStringId != -1);
|
|
} else {
|
|
//
|
|
// No description specified, look for a source media with -1 as the
|
|
// description string id
|
|
//
|
|
DescriptionStringId = -1;
|
|
b1 = TRUE;
|
|
}
|
|
|
|
if(SourceTagfile) {
|
|
//
|
|
// Tagfile specified. See if it's in the table. If not,
|
|
// no need to search the list of media descriptors because we know
|
|
// we can't find a match.
|
|
//
|
|
// (Again, we must first copy the string to a writeable buffer.
|
|
//
|
|
lstrcpyn(TempTagfileString, SourceTagfile, SIZECHARS(TempTagfileString));
|
|
TagfileStringId = StringTableLookUpString(Queue->StringTable,
|
|
TempTagfileString,
|
|
STRTAB_CASE_INSENSITIVE | STRTAB_BUFFER_WRITEABLE
|
|
);
|
|
b2 = (TagfileStringId != -1);
|
|
} else {
|
|
//
|
|
// No tagfile specified, look for a source media with -1 as the
|
|
// tagfile string id
|
|
//
|
|
TagfileStringId = -1;
|
|
b2 = TRUE;
|
|
}
|
|
|
|
//
|
|
// If we think there's a possibility of finding an existing source that
|
|
// matches the caller's parameters, scan the source media list looking
|
|
// for a match.
|
|
//
|
|
if(b1 && b2) {
|
|
|
|
for(Source=Queue->SourceMediaList; Source; Source=Source->Next) {
|
|
|
|
if((Source->Description == DescriptionStringId)
|
|
&& (Source->Tagfile == TagfileStringId)
|
|
&& (Source->SourceRootPath == SourceRootStringId)) {
|
|
|
|
//
|
|
// Got a match. Point the queue node at this source and return.
|
|
//
|
|
QueueNode->SourceMediaInfo = Source;
|
|
return(Source);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Need to add a new source media descriptor.
|
|
// Allocate the structure and fill it in.
|
|
//
|
|
Source = MyMalloc(sizeof(SOURCE_MEDIA_INFO));
|
|
if(!Source) {
|
|
return(NULL);
|
|
}
|
|
|
|
Source->Next = NULL;
|
|
Source->CopyQueue = NULL;
|
|
Source->CopyNodeCount = 0;
|
|
|
|
if(SourceDescription) {
|
|
//
|
|
// Since we already passed this in for a case-insensitive lookup with a writeable
|
|
// buffer, we can add it case-sensitively, because it's already lower-cased.
|
|
//
|
|
Source->Description = StringTableAddString(Queue->StringTable,
|
|
TempSrcDescString,
|
|
STRTAB_CASE_SENSITIVE | STRTAB_ALREADY_LOWERCASE
|
|
);
|
|
//
|
|
// We also must add the description in its original case, since this is a displayable string.
|
|
// (We're safe in casting away the CONST-ness of this string, since it won't be modified.)
|
|
//
|
|
Source->DescriptionDisplayName = StringTableAddString(Queue->StringTable,
|
|
(PTSTR)SourceDescription,
|
|
STRTAB_CASE_SENSITIVE
|
|
);
|
|
|
|
if((Source->Description == -1) || (Source->DescriptionDisplayName == -1)) {
|
|
MyFree(Source);
|
|
return(NULL);
|
|
}
|
|
} else {
|
|
Source->Description = Source->DescriptionDisplayName = -1;
|
|
}
|
|
|
|
if(SourceTagfile) {
|
|
//
|
|
// Again, we already lower-cased this in a writeable buffer above.
|
|
//
|
|
Source->Tagfile = StringTableAddString(Queue->StringTable,
|
|
TempTagfileString,
|
|
STRTAB_CASE_SENSITIVE | STRTAB_ALREADY_LOWERCASE
|
|
);
|
|
if(Source->Tagfile == -1) {
|
|
MyFree(Source);
|
|
return(NULL);
|
|
}
|
|
} else {
|
|
Source->Tagfile = -1;
|
|
}
|
|
|
|
Source->SourceRootPath = SourceRootStringId;
|
|
|
|
if(Queue->SourceMediaList) {
|
|
for(LastSource=Queue->SourceMediaList; LastSource->Next; LastSource=LastSource->Next) ;
|
|
LastSource->Next = Source;
|
|
} else {
|
|
Queue->SourceMediaList = Source;
|
|
}
|
|
|
|
Queue->SourceMediaCount++;
|
|
|
|
QueueNode->SourceMediaInfo = Source;
|
|
return(Source);
|
|
}
|
|
|
|
|
|
BOOL
|
|
pSetupGetSourceAllInfo(
|
|
IN HINF InfHandle,
|
|
IN UINT SourceId,
|
|
OUT PCTSTR *Description,
|
|
OUT PCTSTR *Tagfile,
|
|
OUT PCTSTR *RelativePath
|
|
)
|
|
{
|
|
BOOL b;
|
|
DWORD RequiredSize;
|
|
PTSTR p;
|
|
DWORD ec;
|
|
|
|
//
|
|
// Get path relative to the source.
|
|
//
|
|
b = SetupGetSourceInfo(InfHandle,SourceId,SRCINFO_PATH,NULL,0,&RequiredSize);
|
|
if(!b) {
|
|
ec = GetLastError();
|
|
goto clean0;
|
|
}
|
|
|
|
p = MyMalloc(RequiredSize*sizeof(TCHAR));
|
|
if(!p) {
|
|
ec = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean0;
|
|
}
|
|
SetupGetSourceInfo(InfHandle,SourceId,SRCINFO_PATH,p,RequiredSize,NULL);
|
|
*RelativePath = p;
|
|
|
|
//
|
|
// Get description.
|
|
//
|
|
b = SetupGetSourceInfo(InfHandle,SourceId,SRCINFO_DESCRIPTION,NULL,0,&RequiredSize);
|
|
if(!b) {
|
|
ec = GetLastError();
|
|
goto clean1;
|
|
}
|
|
|
|
p = MyMalloc(RequiredSize*sizeof(TCHAR));
|
|
if(!p) {
|
|
ec = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean1;
|
|
}
|
|
SetupGetSourceInfo(InfHandle,SourceId,SRCINFO_DESCRIPTION,p,RequiredSize,NULL);
|
|
*Description = p;
|
|
|
|
//
|
|
// Get tagfile, if any.
|
|
//
|
|
b = SetupGetSourceInfo(InfHandle,SourceId,SRCINFO_TAGFILE,NULL,0,&RequiredSize);
|
|
if(!b) {
|
|
ec = GetLastError();
|
|
goto clean2;
|
|
}
|
|
|
|
p = MyMalloc(RequiredSize*sizeof(TCHAR));
|
|
if(!p) {
|
|
ec = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto clean2;
|
|
}
|
|
SetupGetSourceInfo(InfHandle,SourceId,SRCINFO_TAGFILE,p,RequiredSize,NULL);
|
|
if(*p) {
|
|
*Tagfile = p;
|
|
} else {
|
|
MyFree(p);
|
|
*Tagfile = NULL;
|
|
}
|
|
|
|
return(TRUE);
|
|
|
|
clean2:
|
|
MyFree(*Description);
|
|
clean1:
|
|
MyFree(*RelativePath);
|
|
clean0:
|
|
SetLastError(ec);
|
|
return(FALSE);
|
|
}
|