Leaked source code of windows server 2003
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.
 
 
 
 
 
 

971 lines
27 KiB

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
fileenum.c
Abstract:
The code in this source file traverses a drive tree and calls
an external callback function for each file. An INF can be
provided to exclude files and/or directories from enumeration.
Author:
Jim Schmidt (jimschm) 16-Aug-1996
Revision History:
Marc R. Whitten (marcw) 11-Sep-1997 Tweaked exclusion handling code, removed
obsolete code.
Mike Condra (mikeco) 02-Jun-1996 Add fns to tap into file/path exclusion
Jim Schmidt (jimschm) 20-Dec-1996 Added callback levels and made single
source file for both A and W versions
Jim Schmidt (jimschm) 27-Nov-1996 Added level and filter to EnumTree
--*/
#include "pch.h"
#include "migshared.h"
typedef struct {
FILEENUMPROCA fnEnumCallback;
FILEENUMFAILPROCA fnFailCallback;
DWORD EnumID;
LPVOID pParam;
DWORD Levels;
DWORD CurrentLevel;
DWORD AttributeFilter;
} ENUMSTRUCTA, *PENUMSTRUCTA;
BOOL EnumTreeEngineA (LPCSTR CurrentPath, PENUMSTRUCTA pes);
BOOL IsPathExcludedA (DWORD EnumID, LPCSTR Path);
BOOL IsFileExcludedA (DWORD EnumID, LPCSTR File, BYTE byBitmask[]);
BOOL BuildExclusionsFromInfA (DWORD EnumID, PEXCLUDEINFA ExcludeInfStruct);
void CreateBitmaskA (DWORD EnumID, LPCSTR FindPattern, BYTE byBitmask[]);
typedef struct {
FILEENUMPROCW fnEnumCallback;
FILEENUMFAILPROCW fnFailCallback;
DWORD EnumID;
LPVOID pParam;
DWORD Levels;
DWORD CurrentLevel;
DWORD AttributeFilter;
} ENUMSTRUCTW, *PENUMSTRUCTW;
BOOL EnumTreeEngineW (LPCWSTR CurrentPath, PENUMSTRUCTW pes);
BOOL IsPathExcludedW (DWORD EnumID, LPCWSTR Path);
BOOL IsFileExcludedW (DWORD EnumID, LPCWSTR File, BYTE byBitmask[]);
BOOL BuildExclusionsFromInfW (DWORD EnumID, PEXCLUDEINFW ExcludeInfStruct);
void CreateBitmaskW (DWORD EnumID, LPCWSTR FindPattern, BYTE byBitmask[]);
#ifdef UNICODE
BOOL
WINAPI
FileEnum_Entry (
IN HINSTANCE hinstDLL,
IN DWORD dwReason,
IN LPVOID lpv)
/*++
Routine Description:
FileEnum_Entry is called after the C runtime is initialized, and its purpose
is to initialize the globals for this process. For this LIB, it
does nothing.
Arguments:
hinstDLL - (OS-supplied) Instance handle for the DLL
dwReason - (OS-supplied) Type of initialization or termination
lpv - (OS-supplied) Unused
Return Value:
TRUE because DLL always initializes properly.
--*/
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
/*++
Routine Description:
GenerateEnumID maintains a static that is used to generate unique
enumeration handles for callers. The enumeration handle is guaranteed to
be unique for the first 2^32 calls.
Arguments:
none
Return Value:
A DWORD enumeration handle that may be used to identify an exclusion
list.
--*/
DWORD
GenerateEnumID (
void
)
{
static DWORD s_EnumID = 0;
return ++s_EnumID;
}
#endif
#ifdef UNICODE
#define FILEENUMPROCT FILEENUMPROCW
#define FILEENUMFAILPROCT FILEENUMFAILPROCW
#define PEXCLUDEINFT PEXCLUDEINFW
#define ENUMSTRUCTT ENUMSTRUCTW
#define PENUMSTRUCTT PENUMSTRUCTW
#define EnumerateAllDrivesT EnumerateAllDrivesW
#define EnumerateTreeT EnumerateTreeW
#define EnumTreeEngineT EnumTreeEngineW
#define IsPathExcludedT IsPathExcludedW
#define CreateBitmaskT CreateBitmaskW
#define IsFileExcludedT IsFileExcludedW
#define BuildExclusionsFromInfT BuildExclusionsFromInfW
#define ClearExclusionsT ClearExclusionsW
#define ExcludeFileT ExcludeFileW
#define ExcludePathT ExcludePathW
#else
#define FILEENUMPROCT FILEENUMPROCA
#define FILEENUMFAILPROCT FILEENUMFAILPROCA
#define PEXCLUDEINFT PEXCLUDEINFA
#define ENUMSTRUCTT ENUMSTRUCTA
#define PENUMSTRUCTT PENUMSTRUCTA
#define EnumerateAllDrivesT EnumerateAllDrivesA
#define EnumerateTreeT EnumerateTreeA
#define EnumTreeEngineT EnumTreeEngineA
#define IsPathExcludedT IsPathExcludedA
#define CreateBitmaskT CreateBitmaskA
#define IsFileExcludedT IsFileExcludedA
#define BuildExclusionsFromInfT BuildExclusionsFromInfA
#define ClearExclusionsT ClearExclusionsA
#define ExcludeFileT ExcludeFileA
#define ExcludePathT ExcludePathA
#endif
#define MAX_DRIVES 64
BOOL
EnumerateAllDrivesT (
IN FILEENUMPROCT fnEnumCallback,
IN FILEENUMFAILPROCT fnFailCallback,
IN DWORD EnumID,
IN LPVOID pParam,
IN PEXCLUDEINFT ExcludeInfStruct,
IN DWORD AttributeFilter
)
/*++
Routine Description:
EnumerateAllDrives first builds an exclusion list if an exclusion INF path
is provided, and then enumerates every file on every drive that is not
excluded. The callback function is called once per file. The pParam
parameter is passed to the callback.
Arguments:
fnEnumCallback - A pointer to your callback function
EnumID - A caller-defined value used to identify the exclusion list
pParam - LPVOID passed to callback function
ExcludeInfStruct - Struct containing INF file information for excluding dirs or files
AttributeFilter - FILTER_xxx constants
Return Value:
TRUE if function succeeds. Call GetLastError for error code if return
value is FALSE.
--*/
{
TCHAR LogicalDrives[MAX_DRIVES];
DWORD rc;
PCTSTR p;
UINT driveType;
rc = GetLogicalDriveStrings (
MAX_DRIVES,
LogicalDrives
);
if (!rc || rc > MAX_DRIVES) {
if (rc)
SetLastError (ERROR_OUTOFMEMORY);
return FALSE;
}
for (p = LogicalDrives ; *p ; p = GetEndOfString (p) + 1) {
driveType = GetDriveType(p);
if (driveType == DRIVE_REMOTE || driveType == DRIVE_CDROM) {
continue;
}
if (!EnumerateTreeT (p,
fnEnumCallback,
fnFailCallback,
EnumID,
pParam,
ENUM_ALL_LEVELS,
ExcludeInfStruct,
AttributeFilter
))
break;
}
return (*p == 0);
}
BOOL
EnumerateTreeT (
IN PCTSTR EnumRoot,
IN FILEENUMPROCT fnEnumCallback,
IN FILEENUMFAILPROCT fnFailCallback, OPTIONAL
IN DWORD EnumID,
IN LPVOID pParam,
IN DWORD Levels,
IN PEXCLUDEINFT ExcludeInfStruct, OPTIONAL
IN DWORD AttributeFilter
)
/*++
Routine Description:
EnumerateTree is similar to EnumerateAllDrives, except it allows you to
enumerate a specific drive, or a specific subdir on a drive. Supply the
drive letter and optional subdirectory in EnumRoot. Before enumerating,
EnumerateTree will first build an exclusion list if an exclusion INF path
is provided. Then every file below EnumRoot is enumerated, and the
callback is called once per file, passing pParam unchanged.
Arguments:
EnumRoot - Drive and optional path to enumerate
fnEnumCallback - A pointer to your callback function
fnFailCallback - A pointer to optional fn that logs enumeration errors
EnumID - A caller-defined value used to identify the exclusion list
pParam - LPVOID passed to callback function
ExcludeInfStruct - Struct containing INF file information for excluding dirs or files
AttributeFilter - FILTER_xxx constants
Return Value:
TRUE if function succeeds. Call GetLastError for error code if return
value is FALSE.
--*/
{
ENUMSTRUCTT es;
BOOL b;
if (ExcludeInfStruct)
if (!BuildExclusionsFromInfT (
EnumID,
ExcludeInfStruct
)) {
DEBUGMSG ((DBG_ERROR, "Error in exclusion file"));
return FALSE;
}
es.fnEnumCallback = fnEnumCallback;
es.fnFailCallback = fnFailCallback;
es.EnumID = EnumID;
es.pParam = pParam;
es.Levels = Levels;
es.CurrentLevel = 1;
es.AttributeFilter = AttributeFilter;
if (!IsPathLengthOk(EnumRoot))
{
if (NULL != fnFailCallback)
{
fnFailCallback(EnumRoot);
return TRUE;
}
}
if (IsPathExcludedT (EnumID, EnumRoot))
return TRUE;
b = EnumTreeEngineT (EnumRoot, &es);
return b;
}
BOOL
EnumTreeEngineT (
PCTSTR CurrentPath,
PENUMSTRUCTT pes
)
{
WIN32_FIND_DATA fd; // A find struct for this subdir
HANDLE hFind; // A find handle for this subdir
PTSTR FullFilePath; // Buffer used to build file path
static TCHAR FindPattern[MAX_TCHAR_PATH * 2]; // Temp buffer used to build pattern
BYTE byBitmask[MAX_PATH]; // Bitmask is used to speed exclusion lookup
static DWORD Attrib; // Temp attribute storage for filter processing
static INT rc; // Callback return value
DWORD PrevLevelCt; // Storage for parent's max depth setting
BOOL RecurseStatus;
DWORD CurrentDirData = 0;
//
// Do nothing when CurrentPath is at the size limit.
//
if (!IsPathLengthOk(CurrentPath))
{
if (NULL != pes->fnFailCallback)
{
pes->fnFailCallback(CurrentPath);
}
return TRUE;
}
PrevLevelCt = pes->Levels;
StringCopy (FindPattern, CurrentPath);
//
// Create a bitmask that tells us when subdirectories match partial
// file patterns.
//
ZeroMemory (byBitmask, sizeof (byBitmask));
CreateBitmaskT (pes->EnumID, FindPattern, byBitmask);
AppendPathWack (FindPattern);
StringCat (FindPattern, TEXT("*"));
hFind = FindFirstFile (FindPattern, &fd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
FullFilePath = JoinPaths (CurrentPath, fd.cFileName);
__try {
//
// Ignore this path if FullFilePath is too long
// this way fd.cFileName will surely be within limits (since it's shorter)
//
if (!IsPathLengthOk (FullFilePath)) {
if (NULL != pes->fnFailCallback) {
pes->fnFailCallback(FullFilePath);
}
__leave;
}
// Filter directories named ".", "..". Set Attrib symbol.
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (!StringCompare (fd.cFileName, TEXT(".")) ||
!StringCompare (fd.cFileName, TEXT("..")))
__leave;
Attrib = FILTER_DIRECTORIES;
} else {
Attrib = FILTER_FILES;
}
// Call the callback
if (Attrib & pes->AttributeFilter) {
rc = CALLBACK_CONTINUE;
switch (Attrib) {
case FILTER_DIRECTORIES:
// Ignore excluded paths
if (IsPathExcludedT (pes->EnumID, FullFilePath)) {
break;
}
// Callback for 'directory first'
if (!(pes->AttributeFilter & FILTER_DIRS_LAST)) {
rc = pes->fnEnumCallback (
FullFilePath,
NULL,
&fd,
pes->EnumID,
pes->pParam,
&CurrentDirData
);
}
if (rc >= CALLBACK_CONTINUE && pes->CurrentLevel != pes -> Levels) {
// Recurse on directory
pes->CurrentLevel++;
RecurseStatus = EnumTreeEngineT (FullFilePath, pes);
pes->CurrentLevel--;
if (!RecurseStatus) {
PushError();
FindClose(hFind);
PopError();
#pragma prefast(suppress:242, "try/finally perf not an issue here")
return FALSE;
}
}
// Callback for 'directory last'
if (pes->AttributeFilter & FILTER_DIRS_LAST) {
rc = pes->fnEnumCallback (
FullFilePath,
NULL,
&fd,
pes->EnumID,
pes->pParam,
&CurrentDirData
);
}
break;
case FILTER_FILES:
if (!IsFileExcludedT (pes->EnumID, FullFilePath, byBitmask)) {
rc = pes->fnEnumCallback (FullFilePath,
NULL,
&fd,
pes->EnumID,
pes->pParam,
&CurrentDirData
);
}
break;
}
if (rc == CALLBACK_FAILED) {
PushError();
FindClose (hFind);
PopError();
#pragma prefast(suppress:242, "try/finally perf not an issue here")
return FALSE;
}
else if (rc == CALLBACK_SUBDIR_DONE) {
#pragma prefast(suppress:242, "try/finally perf not an issue here")
break;
}
else if (rc > 0) {
pes->Levels = pes->CurrentLevel + rc;
}
}
else if (Attrib == FILTER_DIRECTORIES && !IsPathExcludedT (pes->EnumID, FullFilePath)) {
// Recurse on directory.
if (pes->CurrentLevel != pes -> Levels) {
pes->CurrentLevel++;
RecurseStatus = EnumTreeEngineT (FullFilePath, pes);
pes->CurrentLevel--;
if (!RecurseStatus) {
PushError();
FindClose(hFind);
PopError();
#pragma prefast(suppress:242, "try/finally perf not an issue here")
return FALSE;
}
}
}
}
__finally {
FreePathString (FullFilePath);
}
} while (FindNextFile (hFind, &fd));
FindClose (hFind);
//
// Test error code returned from FindNextFile
//
if (GetLastError() != ERROR_NO_MORE_FILES && GetLastError() != ERROR_SUCCESS)
{
//
// Caller to handle not-ready message
//
if (GetLastError() != ERROR_NOT_READY)
{
DEBUGMSG((DBG_ERROR,
"EnumTreeEngineT: Error from FindNextFile.\n"
" FindPattern: %s\n"
" Error: %u (%x)",
FindPattern,
GetLastError(),GetLastError()));
}
return FALSE;
}
SetLastError(ERROR_SUCCESS);
}
else {
//
// Test return codes from FindFirstFile
//
if (GetLastError () != ERROR_NO_MORE_FILES)
{
//
// Caller to handle not-ready message
//
if (GetLastError() != ERROR_NOT_READY)
{
DEBUGMSG((DBG_WARNING,
"EnumTreeEngineT: Warning from FindFirstFile.\n"
" FindPattern: %s\n",
FindPattern));
}
// return FALSE;
}
SetLastError (ERROR_SUCCESS);
}
// If a callback returned a positive, non-zero number, the depth
// of the subdirectory search was limited for this level. Now that
// this level is done, we must restore the depth value of our parent.
pes->Levels = PrevLevelCt;
return TRUE;
}
/*++
A bitmask is used in IsFileExcluded for faster relative directory searches.
Instead of looking in the MemDb for each part of the path, IsFileExcluded
skips segments that are known not to match. We create the bitmask here
by looking up each portion of FindPattern. Bit 1 is set if the last
subdirectory exists in the file exclusion list, Bit 2 is set if the last
two subdirectories exist in the file exclusion list, and so on.
For example, assume FindPattern is set to C:\DEV\FOO\BAR. CreateBitmask
first looks in the memory database for BAR\*, and if it is found bit 1 is set.
Then CreateBitmask looks in the memory database for FOO\BAR\*, and sets bit
2. Again the function looks up DEV\FOO\BAR\* for bit 3 and finally
C:\DEV\FOO\BAR\* for bit 4.
Bit 0 is always set (empty paths always match).
Once this bitmask is set up, IsFileExcluded can test only the patterns that
are known to exist.
--*/
void
CreateBitmaskT (
DWORD EnumID,
PCTSTR FindPattern,
BYTE byBitmask[]
)
{
TCHAR EnumPath[MAX_TCHAR_PATH * 2];
TCHAR ShortPath[MAX_TCHAR_PATH * 2];
PCTSTR p;
PTSTR End;
int nByte;
int nBitVal;
// Always set bit 0
byBitmask[0] |= 1;
// Build full file spec
wsprintf (
EnumPath,
TEXT("%s\\%X\\%s\\"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_FILES
);
End = GetEndOfString (EnumPath);
StringCopy (End, FindPattern);
AppendPathWack (End);
StringCat (End, TEXT("*"));
// Start with last subdirectory, and build mask in reverse
p = _tcsrchr (EnumPath, TEXT('\\'));
nByte = 0;
nBitVal = 2;
do {
// Move back to previous backslash
for (p = _tcsdec (EnumPath, p) ;
p >= End && *p != TEXT('\\') ;
p = _tcsdec (EnumPath, p))
{
}
// Check if partial file is in the tree
wsprintf (
ShortPath,
TEXT("%s\\%X\\%s%s"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_FILES,
p
);
if (MemDbGetPatternValueWithPattern (ShortPath, NULL))
byBitmask[nByte] |= nBitVal;
// Inc bit pos
nBitVal *= 2;
if (nBitVal == 256) {
nBitVal = 1;
nByte++;
}
} while (p > End);
}
BOOL
IsPathExcludedT (DWORD EnumID, PCTSTR Path)
{
TCHAR EnumPath[MAX_TCHAR_PATH * 2];
TCHAR ShortPath[MAX_TCHAR_PATH * 2];
PCTSTR p;
PTSTR End;
// Try full paths
wsprintf (
EnumPath,
TEXT("%s\\%X\\%s\\"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_PATHS
);
End = GetEndOfString (EnumPath);
p = _tcsappend (End, Path);
if (MemDbGetPatternValue (EnumPath, NULL)) {
return TRUE;
}
// Try partial paths
do {
// Move back to previous backslash
for (p = _tcsdec (EnumPath, p) ;
p > End && (*p != TEXT('\\')) ;
p = _tcsdec (EnumPath, p))
{
}
// Check if partial path is in the tree
if (p > End && p[1]) {
wsprintf (
ShortPath,
TEXT("%s\\%X\\%s%s"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_PATHS,
p
);
if (MemDbGetPatternValue (ShortPath, NULL)) {
return TRUE;
}
}
} while (p > End);
return FALSE;
}
BOOL
IsFileExcludedT (DWORD EnumID, PCTSTR File, BYTE byBitmask[])
{
TCHAR EnumPath[MAX_TCHAR_PATH * 2];
TCHAR ShortPath[MAX_TCHAR_PATH * 2];
PCTSTR p;
PTSTR End;
int nByte;
int nBit;
// Build full file spec
wsprintf (
EnumPath,
TEXT("%s\\%X\\%s\\"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_FILES
);
End = GetEndOfString (EnumPath);
p = _tcsappend (End, File);
//
// Try partial file specs until full spec is reached
//
nByte = 0;
nBit = 1;
do {
//
// Move back to previous backslash in path
// (p starts at NULL of EnumPath, End is in the middle of EnumPath)
//
for (p = _tcsdec (EnumPath, p) ;
p >= End && (*p != TEXT('\\')) ;
p = _tcsdec (EnumPath, p))
{
}
// Bitmask is used to make sure slightly expensive query is necessary
if (byBitmask[nByte] & nBit) {
//
// Check if partial file is in the tree
//
wsprintf (
ShortPath,
TEXT("%s\\%X\\%s%s"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_FILES,
p
);
if (MemDbGetPatternValue (ShortPath, NULL)) {
return TRUE;
}
}
nBit *= 2;
if (nBit == 256) {
nBit = 1;
nByte++;
}
} while (p > End);
return FALSE;
}
//
// ClearExclusions removes all enumaration exclusions. It is called
// automatically at the end of enumeration when an exclusion INF file is
// used. Use it when you need to programmatically build an exclusion list
// with ExcludeDrive, ExcludePath, and ExcludeFile.
//
// You can combine programmatic exclusions with an exclusion INF file, but
// beware that the programmatic exclusions will be cleared after
// EnumarteAllDrives or EnumerateTree completes.
//
// If you do not use an INF, the programmatic exclusions will not
// automatically be cleared.
//
// EnumID - Caller-defined value to identify enumeration exclusion list
//
VOID
ClearExclusionsT (
DWORD EnumID
)
{
TCHAR EnumPath[MAX_TCHAR_PATH * 2];
wsprintf (EnumPath, TEXT("%s\\%X"), MEMDB_CATEGORY_FILEENUM, EnumID);
MemDbDeleteTree (EnumPath);
}
/*++
Routine Description:
ExcludePath adds a path name to the exclusion list. There are two
cases:
1. A full path spec is supplied, including the drive letter or
UNC double-backslash.
2. The path does not start with a drive letter and is a portion of
a full path.
The dot and double-dot directories are not supported. Any part of
the path may contain wildcard characters, but a wildcard can not
be used in place of a backslash.
Arguments:
EnumID - A caller-defined value that identifies the exclusion list
Path - The path specification as described above
Return Value:
none
--*/
VOID
ExcludePathT (
IN DWORD EnumID,
IN PCTSTR Path
)
{
TCHAR EnumPath[MAX_TCHAR_PATH * 2];
wsprintf (
EnumPath,
TEXT("%s\\%X\\%s\\%s"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_PATHS,
Path
);
MemDbSetValue (EnumPath, 0);
}
/*++
Routine Description:
ExcludeFile adds a file spec to the exclusion list. There are two
cases:
1. A full path spec is supplied, including the drive letter or
UNC double-backslash.
2. The path does not start with a drive letter and is a portion of
a full path.
The dot and double-dot directories are not supported. Any part of
the path may contain wildcard characters, but a wildcard can not
be used in place of a backslash.
Arguments:
EnumID - A caller-defined value that identifies the exclusion list
File - The file specification as described above
Return Value:
none
--*/
VOID
ExcludeFileT (
IN DWORD EnumID,
IN PCTSTR File
)
{
TCHAR EnumPath[MAX_TCHAR_PATH * 2];
wsprintf (
EnumPath,
TEXT("%s\\%X\\%s\\%s"),
MEMDB_CATEGORY_FILEENUM,
EnumID,
MEMDB_FIELD_FE_FILES,
File
);
MemDbSetValue (EnumPath, 0);
}
BOOL
BuildExclusionsFromInfT (DWORD EnumID,
PEXCLUDEINFT ExcludeInfStruct)
{
HINF hInf;
INFCONTEXT ic;
TCHAR Exclude[MAX_TCHAR_PATH * 2];
// Attempt to open
hInf = SetupOpenInfFile (ExcludeInfStruct->ExclusionInfPath, NULL, INF_STYLE_WIN4, NULL);
if (hInf == INVALID_HANDLE_VALUE)
return FALSE;
// Read in path exclusions
if (ExcludeInfStruct->PathSection) {
if (SetupFindFirstLine (hInf, ExcludeInfStruct->PathSection, NULL, &ic)) {
do {
if (SetupGetStringField (&ic, 1, Exclude, MAX_TCHAR_PATH, NULL)) {
ExcludePathT (EnumID, Exclude);
}
} while (SetupFindNextLine (&ic, &ic));
}
}
// Read in file exclusions
if (ExcludeInfStruct->FileSection) {
if (SetupFindFirstLine (hInf, ExcludeInfStruct->FileSection, NULL, &ic)) {
do {
if (SetupGetStringField (&ic, 1, Exclude, MAX_TCHAR_PATH, NULL)) {
ExcludeFileT (EnumID, Exclude);
}
} while (SetupFindNextLine (&ic, &ic));
}
}
// Clean up
SetupCloseInfFile (hInf);
return TRUE;
}