Windows NT 4.0 source code leak
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.
 
 
 
 
 
 

916 lines
26 KiB

/********************************************************************/
/** Microsoft LAN Manager **/
/** Copyright(c) Microsoft Corp., 1987-1992 **/
/********************************************************************/
/*
* view.c
* Commands for viewing what resources are available for use.
*
* History:
* 07/02/87, ericpe, initial coding.
* 10/31/88, erichn, uses OS2.H instead of DOSCALLS
* 01/04/89, erichn, filenames now MAXPATHLEN LONG
* 05/02/89, erichn, NLS conversion
* 05/19/89, erichn, NETCMD output sorting
* 06/08/89, erichn, canonicalization sweep
* 02/15/91, danhi, convert to 16/32 mapping layer
* 04/09/91, robdu, LM21 bug fix 1502
* 07/20/92, JohnRo, Use DEFAULT_SERVER equate.
*/
/* Include files */
#define INCL_NOCOMMON
#define INCL_DOSMEMMGR
#define INCL_DOSFILEMGR
#define INCL_ERRORS
#include <os2.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <search.h>
#include <netcons.h>
#include <neterr.h>
#include <apperr.h>
#include <apperr2.h>
#include <service.h>
#include <server.h>
#include <shares.h>
#include <use.h>
#include "netlib0.h"
#include <lui.h>
#include <srvif.h>
#include "port1632.h"
#include "netcmds.h"
#include "nettext.h"
/* Forward declarations */
VOID NEAR display_remarkf ( USHORT, TCHAR FAR *);
int _CRTAPI1 CmpSvrInfo1 ( const VOID FAR *, const VOID FAR * );
int _CRTAPI1 CmpShrInfo1 ( const VOID FAR *, const VOID FAR *);
int _CRTAPI1 CmpShrInfoGen ( const VOID FAR *, const VOID FAR *);
USHORT get_used_as ( TCHAR FAR *, TCHAR FAR * );
void display_other_net(TCHAR *net, TCHAR *node) ;
TCHAR * get_provider_name(TCHAR *net) ;
DWORD enum_net_resource(LPNETRESOURCE, LPBYTE *, LPDWORD, LPDWORD) ;
USHORT list_nets(void) ;
#define VIEW_UNC 0
#define VIEW_MORE (VIEW_UNC + 1)
#define USE_TYPE_DISK (VIEW_MORE + 1)
#define USE_TYPE_COMM (USE_TYPE_DISK + 1)
#define USE_TYPE_PRINT (USE_TYPE_COMM + 1)
#define USE_TYPE_IPC (USE_TYPE_PRINT + 1)
#define USE_TYPE_UNKNOWN (USE_TYPE_IPC + 1)
static MESSAGE ViewMsgList[] = {
{ APE2_VIEW_UNC, NULL },
{ APE2_VIEW_MORE, NULL },
{ APE2_USE_TYPE_DISK, NULL },
{ APE2_USE_TYPE_COMM, NULL },
{ APE2_USE_TYPE_PRINT, NULL },
{ APE2_USE_TYPE_IPC, NULL },
{ APE2_GEN_UNKNOWN, NULL },
};
#define NUM_VIEW_MSGS (sizeof(ViewMsgList)/sizeof(ViewMsgList[0]))
/***
* view_display()
*
* Displays info as reqested through use of the Net View command.
*
* Args:
* name - the name of the server for which info is desired.
* If NULL, the servers on the Net are enumerated.
*
* Returns:
* nothing - success
* exit(1) - command completed with errors
*/
VOID
view_display ( TCHAR * name )
{
USHORT err; /* API return status */
TCHAR FAR * pEnumBuffer;
TCHAR FAR * pGetInfoBuffer;
USHORT2ULONG _read; /* to receive # of entries read */
USHORT msgLen; /* to hold max length of messages */
TCHAR * msgPtr; /* message to print */
struct server_info_1 FAR * server_entry;
struct server_info_0 FAR * server_entry_0;
struct share_info_1 FAR * share_entry;
SHORT errorflag = 0;
USHORT2ULONG i;
TCHAR FAR * comment;
TCHAR tname[MAX_PATH+NNLEN+4]; /* for NetUseGetInfo */
USHORT more_data = FALSE;
TCHAR FAR * DollarPtr;
TCHAR *Domain = NULL;
TCHAR *Network = NULL;
ULONG Type = SV_TYPE_ALL;
GetMessageList(NUM_VIEW_MSGS, ViewMsgList, &msgLen);
for (i = 0; SwitchList[i]; i++)
{
TCHAR *ptr;
//
// only have 2 switches, and they are not compatible
//
if (i > 0)
ErrorExit(APE_ConflictingSwitches) ;
ptr = FindColon(SwitchList[i]);
if (!_tcscmp(SwitchList[i], swtxt_SW_DOMAIN))
{
//
// If no domain specified, then we want to enumerate domains,
// otherwise we want to enumerate the servers on the domain
// specified.
//
if (ptr == NULL)
Type = SV_TYPE_DOMAIN_ENUM;
else
Domain = ptr;
}
else if (!_tcscmp(SwitchList[i], swtxt_SW_NETWORK))
{
//
// enumerate top level of specific network. if none,
// default to LM.
//
if (ptr && *ptr)
Network = ptr ;
}
else
ErrorExit(APE_InvalidSwitch);
}
//
// a specific net was requested. display_other_net does
// not return.
//
if (Network != NULL)
{
(void) display_other_net(Network,name) ;
}
if (name == NULL)
{
ULONG i;
if ((err = MNetServerEnum(DEFAULT_SERVER,
(Type == SV_TYPE_DOMAIN_ENUM ? 0 : 1),
(LPBYTE*)&pEnumBuffer,
&_read,
Type,
Domain)) == ERROR_MORE_DATA)
more_data = TRUE;
else if( err )
ErrorExit (err);
if (_read == 0)
EmptyExit();
NetISort(pEnumBuffer,
_read,
(Type == SV_TYPE_DOMAIN_ENUM ? sizeof(SERVER_INFO_0) : sizeof(SERVER_INFO_1)),
CmpSvrInfo1);
if (Type == SV_TYPE_DOMAIN_ENUM)
InfoPrint(APE2_VIEW_DOMAIN_HDR);
else
InfoPrint(APE2_VIEW_ALL_HDR);
PrintLine();
/* Print the listing */
if (Type == SV_TYPE_DOMAIN_ENUM) {
for (i=0, server_entry_0 =
(SERVER_INFO_0 FAR *) pEnumBuffer; i < _read;
i++, server_entry_0++)
{
WriteToCon(TEXT("%Fws "), PaddedString(20,server_entry_0->sv0_name,NULL));
PrintNL();
}
} else {
for (i=0, server_entry =
(struct server_info_1 FAR *) pEnumBuffer; i < _read;
i++, server_entry++)
{
WriteToCon(TEXT("\\\\%Fws "), PaddedString(20,server_entry->sv1_name,NULL));
display_remarkf (56, server_entry->sv1_comment);
PrintNL();
}
}
NetApiBufferFree(pEnumBuffer);
}
else
{
DWORD avail ;
if ((err = MNetShareEnum(name,
1,
(LPBYTE*)&pEnumBuffer,
&_read
)) == ERROR_MORE_DATA)
more_data = TRUE;
else if (err)
ErrorExit (err);
if (_read == 0)
EmptyExit();
/* Are there any shares that we will display? */
for (i=0, share_entry =(struct share_info_1 FAR *) pEnumBuffer;
i < _read; i++, share_entry++ )
{
DollarPtr = strrchrf(share_entry->shi1_netname, DOLLAR);
/* If no DOLLAR in sharename, or last DOLLAR is nonterminal, break. */
if (!DollarPtr || *(DollarPtr + 1))
break;
}
if (i == _read)
EmptyExit();
NetISort(pEnumBuffer, _read, sizeof(struct share_info_1), CmpShrInfo1);
InfoPrintInsTxt(APE_ViewResourcesAt, name);
if (err = MNetServerGetInfo(name, 1, (LPBYTE*)&pGetInfoBuffer)) {
PrintNL();
}
else
{
server_entry = (struct server_info_1 FAR *)pGetInfoBuffer;
comment = server_entry->sv1_comment;
WriteToCon(TEXT("%Fws\r\n\r\n"), comment);
NetApiBufferFree(pGetInfoBuffer);
}
InfoPrint(APE2_VIEW_SVR_HDR);
PrintLine();
/* Print the listing */
for (i=0, share_entry =(struct share_info_1 FAR *)
pEnumBuffer; i < _read; i++, share_entry++ )
{
/* if the name end in $, do not print it */
DollarPtr = strrchrf(share_entry->shi1_netname, DOLLAR);
if (DollarPtr && *(DollarPtr + 1) == NULLC)
continue;
if (SizeOfHalfWidthString(share_entry->shi1_netname) <= 12)
WriteToCon(TEXT("%Fws "), PaddedString(12,share_entry->shi1_netname,NULL));
else
{
WriteToCon(TEXT("%Fws"), share_entry->shi1_netname);
PrintNL() ;
WriteToCon(TEXT("%-12.12Fws "), TEXT(""));
}
// mask out the non type related bits
switch ( share_entry->shi1_type & ~STYPE_SPECIAL )
{
case STYPE_DISKTREE :
msgPtr = ViewMsgList[USE_TYPE_DISK].msg_text;
break;
case STYPE_PRINTQ :
msgPtr = ViewMsgList[USE_TYPE_PRINT].msg_text;
break;
case STYPE_DEVICE :
msgPtr = ViewMsgList[USE_TYPE_COMM].msg_text;
break;
case STYPE_IPC :
msgPtr = ViewMsgList[USE_TYPE_IPC].msg_text;
break;
default:
msgPtr = ViewMsgList[USE_TYPE_UNKNOWN].msg_text;
break;
}
WriteToCon(TEXT("%Fws "), PaddedString(12,msgPtr,NULL));
_tcscpy(tname, name);
_tcscat(tname, TEXT("\\"));
_tcscat(tname, share_entry->shi1_netname);
if (err = get_used_as ( tname, Buffer ))
{
errorflag = TRUE;
}
else
{
WriteToCon(TEXT("%Fws "), PaddedString(8,Buffer,NULL));
}
/* Print the remark */
display_remarkf (44, share_entry->shi1_remark);
PrintNL();
}
NetApiBufferFree(pEnumBuffer);
}
if ( errorflag )
{
InfoPrint(APE_CmdComplWErrors);
NetcmdExit(1);
}
if ( more_data )
InfoPrint( APE_MoreData);
else
InfoSuccess();
}
/***
* cmpsvinfo1(sva,svb)
*
* Compares two server_info_1 structures and returns a relative
* lexical value, suitable for using in NetISort.
*
*/
int _CRTAPI1 CmpSvrInfo1(const VOID FAR * sva, const VOID FAR * svb)
{
return stricmpf ( ((struct server_info_1 FAR *) sva)->sv1_name,
((struct server_info_1 FAR *) svb)->sv1_name);
}
/***
* CmpShrInfo1(share1,share2)
*
* Compares two share_info_1 structures and returns a relative
* lexical value, suitable for using in NetISort.
*
*/
int _CRTAPI1 CmpShrInfo1(const VOID FAR * share1, const VOID FAR * share2)
{
return stricmpf ( ((struct share_info_1 FAR *) share1)->shi1_netname,
((struct share_info_1 FAR *) share2)->shi1_netname);
}
/***
* CmpShrInfoGen(share1,share2)
*
* Compares two NETRESOURCE structures and returns a relative
* lexical value, suitable for using in NetISort.
*
*/
int _CRTAPI1 CmpShrInfoGen(const VOID FAR * share1, const VOID FAR * share2)
{
return _wcsicmp ( (*((LPNETRESOURCE *) share1))->lpRemoteName,
(*((LPNETRESOURCE *) share2))->lpRemoteName );
}
/***
* display_remarkf(field_width, remark )
*
* Displays the remark left justified in the specifed fieldwidth. If
* the remark is too LONG it is trucated with ellipses (...).
*
* Args:
* field_width - the available field width
* remark - the remark to be displayed
*
*/
VOID NEAR display_remarkf ( USHORT field_width, TCHAR FAR * remark )
{
if( remark )
{
WriteToCon(TEXT("%Fws"), PaddedString(-field_width,remark,NULL));
}
}
/*
* Note- get_used_as assumes the message list has been loaded.
*/
USHORT get_used_as ( TCHAR FAR * unc, TCHAR FAR * outbuf )
{
USHORT err; /* API return status */
TCHAR FAR * pBuffer;
struct use_info_0 FAR * pUseInfo;
USHORT2ULONG i;
USHORT2ULONG eread;
outbuf[0] = 0;
if (err = MNetUseEnum ( DEFAULT_SERVER, 0, (LPBYTE*)&pBuffer,
&eread))
{
return err;
}
pUseInfo = (struct use_info_0 FAR *) pBuffer;
for (i = 0; i < eread; i++)
{
if ( (!stricmpf(unc, pUseInfo[i].ui0_remote)))
{
if (_tcslen(pUseInfo[i].ui0_local) > 0)
_tcscpy(outbuf,pUseInfo[i].ui0_local);
else
_tcscpy(outbuf,ViewMsgList[VIEW_UNC].msg_text);
break;
}
}
NetApiBufferFree(pBuffer);
return 0;
}
/*
* Displays resources for another network (other than Lanman). This
* function does not return.
*
* Args:
* net - the shortname of the network we are interested in
* node - the starting point of the enumeration.
*/
void display_other_net(TCHAR *net, TCHAR *node)
{
LPNETRESOURCE *lplpNetResource ;
NETRESOURCE NetResource ;
HANDLE Handle ;
BYTE TopLevelBuffer[4096] ;
DWORD TopLevelBufferSize = sizeof(TopLevelBuffer) ;
LPBYTE ResultBuffer ;
DWORD ResultBufferSize ;
USHORT err ;
DWORD i, dwErr, TopLevelCount, ResultCount = 0 ;
TCHAR * ProviderName = get_provider_name(net) ;
//
// Check that we can get provider name and alloc the results
// buffer. Netcmd normally does not free memory it allocates,
// as it exits immediately.
//
if (!ProviderName)
{
if ( list_nets() != NERR_Success)
ErrorPrint(APE_InvalidSwitchArg,0) ;
NetcmdExit(1) ;
}
if (err = MAllocMem(ResultBufferSize = 8192, &ResultBuffer))
ErrorExit(err) ;
if (!node)
{
BOOL found = FALSE ;
//
// no node, so must be top level. enum the top and find
// matching provider.
//
dwErr = WNetOpenEnum(RESOURCE_GLOBALNET, 0, 0, NULL, &Handle) ;
if (dwErr != WN_SUCCESS)
{
ErrorExit ((USHORT)dwErr) ;
}
do
{
TopLevelCount = 0xFFFFFFFF ;
dwErr = WNetEnumResource(Handle,
&TopLevelCount,
TopLevelBuffer,
&TopLevelBufferSize) ;
if (dwErr == WN_SUCCESS || dwErr == WN_NO_MORE_ENTRIES)
{
LPNETRESOURCE lpNet ;
DWORD i ;
//
// go thru looking for the right provider
//
lpNet = (LPNETRESOURCE) TopLevelBuffer ;
for ( i = 0; i < TopLevelCount; i++, lpNet++ )
{
DWORD dwEnumErr ;
if (!stricmpf(lpNet->lpProvider, ProviderName))
{
//
// found it!
//
found = TRUE ;
//
// now go enumerate that network.
//
dwEnumErr = enum_net_resource(lpNet,
&ResultBuffer,
&ResultBufferSize,
&ResultCount) ;
if (dwEnumErr)
{
// dont report any errors here
(void) WNetCloseEnum(Handle) ;
ErrorExit((USHORT)dwEnumErr) ;
}
break ;
}
}
}
else
{
//
// error occured.
//
(void) WNetCloseEnum(Handle) ; // dont report any errors here
ErrorExit ((USHORT) dwErr) ;
}
} while ((dwErr == WN_SUCCESS) && !found) ;
(void) WNetCloseEnum(Handle) ; // dont report any errors here
if (!found)
ErrorExit(APE_InvalidSwitchArg) ;
}
else
{
//
// node is provided, lets start there.
//
NETRESOURCE NetRes ;
DWORD dwEnumErr ;
memset(&NetRes, 0, sizeof(NetRes)) ;
NetRes.lpProvider = ProviderName ;
NetRes.lpRemoteName = node ;
dwEnumErr = enum_net_resource(&NetRes,
&ResultBuffer,
&ResultBufferSize,
&ResultCount) ;
if (dwEnumErr)
ErrorExit((USHORT)dwEnumErr) ;
}
if (ResultCount == 0)
EmptyExit() ;
//
// By the time we get here, we have a buffer of pointers that
// point to NETRESOURCE structures. We sort the pointers, and then
// print them out.
//
NetISort(ResultBuffer, ResultCount, sizeof(LPNETRESOURCE), CmpShrInfoGen);
lplpNetResource = (LPNETRESOURCE *)ResultBuffer ;
if (node)
{
TCHAR *TypeString ;
InfoPrintInsTxt(APE_ViewResourcesAt, node);
PrintLine();
for (i = 0; i < ResultCount; i++, lplpNetResource++)
{
// BUGBUG - need real strings
switch ((*lplpNetResource)->dwType)
{
case RESOURCETYPE_DISK:
TypeString = ViewMsgList[USE_TYPE_DISK].msg_text;
break ;
case RESOURCETYPE_PRINT:
TypeString = ViewMsgList[USE_TYPE_PRINT].msg_text;
break ;
default:
TypeString = L"" ;
break ;
}
WriteToCon(TEXT("%Fs %s\r\n"),
PaddedString(12,TypeString,NULL),
(*lplpNetResource)->lpRemoteName) ;
}
}
else
{
InfoPrintInsTxt(APE2_VIEW_OTHER_HDR, ProviderName);
PrintLine();
for (i = 0; i < ResultCount; i++, lplpNetResource++)
{
WriteToCon(TEXT("%s\r\n"), (*lplpNetResource)->lpRemoteName) ;
}
}
InfoSuccess();
NetcmdExit(0);
}
/*
* Enumerates resources for a network starting at a specific point.
*
* Args:
* lpNetResourceStart - Where to start the enumeration
* ResultBuffer - Used to return array of pointers to NETRESOURCEs.
* May be reallocated as need.
* ResultBufferSize - Buffer size, also used to return final size.
* ResultCount - Used to return number of entries in buffer.
*/
DWORD enum_net_resource(LPNETRESOURCE lpNetResourceStart,
LPBYTE *ResultBuffer,
LPDWORD ResultBufferSize,
LPDWORD ResultCount)
{
DWORD dwErr ;
HANDLE EnumHandle ;
DWORD Count ;
USHORT err ;
LPBYTE Buffer ;
DWORD BufferSize ;
BOOL fDisconnect = FALSE ;
LPNETRESOURCE *lpNext = (LPNETRESOURCE *)*ResultBuffer ;
//
// allocate memory and open the enumeration
//
if (err = MAllocMem(BufferSize = 8192, &Buffer))
return (err) ;
dwErr = WNetOpenEnum(RESOURCE_GLOBALNET,
0,
0,
lpNetResourceStart,
&EnumHandle) ;
if (dwErr == ERROR_NOT_AUTHENTICATED)
{
//
// try connecting with default credentials. we need this because
// Win95 changed the behaviour of the API to fail if we are not
// already logged on. below will attempt a logon with default
// credentials, but will fail if that doesnt work.
//
dwErr = WNetAddConnection2(lpNetResourceStart, NULL, NULL, 0) ;
if (dwErr == NERR_Success)
{
dwErr = WNetOpenEnum(RESOURCE_GLOBALNET, // redo the enum
0,
0,
lpNetResourceStart,
&EnumHandle) ;
if (dwErr == NERR_Success)
{
fDisconnect = TRUE ; // remember to disconnect
}
else
{
//
// disconnect now
//
(void) WNetCancelConnection2(lpNetResourceStart->lpRemoteName,
0,
FALSE) ;
}
}
else
{
dwErr = ERROR_NOT_AUTHENTICATED ; // use original error
}
}
if (dwErr != WN_SUCCESS)
return ((USHORT)dwErr) ;
do {
Count = 0xFFFFFFFF ;
dwErr = WNetEnumResource(EnumHandle, &Count, Buffer, &BufferSize) ;
if (((dwErr == WN_SUCCESS) || (dwErr == WN_NO_MORE_ENTRIES)) &&
(Count != 0xFFFFFFFF))
// NOTE - the check for FFFFFFFF is workaround for another bug in API.
{
LPNETRESOURCE lpNetResource ;
DWORD i ;
lpNetResource = (LPNETRESOURCE) Buffer ;
//
// stick the entries into the MyUseInfoBuffer
//
for ( i = 0;
i < Count;
i++,lpNetResource++ )
{
*lpNext++ = lpNetResource ;
++(*ResultCount) ;
if ((LPBYTE)lpNext >= (*ResultBuffer + *ResultBufferSize))
{
USHORT err ;
*ResultBufferSize *= 2 ;
if (err = MReallocMem(*ResultBufferSize,ResultBuffer))
ErrorExit(err) ;
lpNext = (LPNETRESOURCE *) *ResultBuffer ;
lpNext += *ResultCount ;
}
}
//
// allocate a new buffer for next set, since we still need
// data in the old one, we dont free it. Netcmd always lets the
// system clean up when it exits.
//
if (dwErr == WN_SUCCESS)
{
if ( err = MAllocMem(BufferSize, &Buffer) )
{
if (fDisconnect)
{
(void) WNetCancelConnection2(
lpNetResourceStart->lpRemoteName,
0,
FALSE) ;
}
ErrorExit(err) ;
}
}
}
else
{
if (dwErr == WN_NO_MORE_ENTRIES)
dwErr = WN_SUCCESS ;
(void) WNetCloseEnum(EnumHandle) ; // dont report any errors here
if (fDisconnect)
{
(void) WNetCancelConnection2(lpNetResourceStart->lpRemoteName,
0,
FALSE) ;
}
return ((USHORT) dwErr) ;
}
} while (dwErr == WN_SUCCESS) ;
(void) WNetCloseEnum(EnumHandle) ; // we dont report any errors here
if (fDisconnect)
{
(void) WNetCancelConnection2(lpNetResourceStart->lpRemoteName,
0,
FALSE) ;
}
return NERR_Success ;
}
#define SHORT_NAME_KEY L"System\\CurrentControlSet\\Control\\NetworkProvider\\ShortName"
/*
* Given a short name for a network, find the real name (stored in registry).
*
* Args:
* net - the short name
*
* Returns:
* Pointer to static data containing the looked up name if successful,
* NULL otherwise.
*/
TCHAR * get_provider_name(TCHAR *net)
{
DWORD err ;
static TCHAR buffer[256] ;
HKEY hKey ;
DWORD buffersize, datatype ;
buffersize = sizeof(buffer) ;
datatype = REG_SZ ;
err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
SHORT_NAME_KEY,
0L,
KEY_QUERY_VALUE,
&hKey) ;
if (err != ERROR_SUCCESS)
return NULL ;
err = RegQueryValueEx(hKey,
net,
0L,
&datatype,
(LPBYTE) buffer,
&buffersize) ;
(void) RegCloseKey(hKey) ; // ignore any error here. its harmless
// and NET.EXE doesnt hang around anyway.
if (err != ERROR_SUCCESS)
return(NULL) ; // treat as cannot read
return ( buffer ) ;
}
/*
* Print out the installed nets
*
* Args:
* none
*
* Returns:
* NERR_Success if success
* error code otherwise.
*/
USHORT list_nets(void)
{
DWORD err ;
TCHAR value_name[256] ;
TCHAR value_data[512] ;
HKEY hKey ;
DWORD iValue, value_name_size, value_data_size ;
err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
SHORT_NAME_KEY,
0L,
KEY_QUERY_VALUE,
&hKey) ;
if (err != ERROR_SUCCESS)
return ((USHORT) err ) ;
InfoPrint(APE2_VIEW_OTHER_LIST) ;
iValue = 0 ;
do {
value_name_size = sizeof(value_name)/sizeof(value_name[0]) ;
value_data_size = sizeof(value_data) ;
err = RegEnumValue(hKey,
iValue,
value_name,
&value_name_size,
NULL,
NULL,
(LPBYTE) value_data,
&value_data_size) ;
if (err == NO_ERROR)
{
WriteToCon(TEXT("\t%s - %s\r\n"),value_name, value_data) ;
}
iValue++ ;
} while (err == NO_ERROR) ;
(void) RegCloseKey(hKey) ; // ignore any error here. its harmless
// and NET.EXE doesnt hang around anyway.
if (err == ERROR_NO_MORE_ITEMS)
return((USHORT) NO_ERROR) ;
return ((USHORT) err) ;
}