Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

853 lines
20 KiB

/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
psxsup.c
Abstract:
PSX Support Routines
Author:
Mark Lucovsky (markl) 27-Nov-1989
Revision History:
--*/
#include "psxsrv.h"
#include <ntlsa.h>
#include <ntsam.h>
#include <ntseapi.h>
#include "sesport.h"
#include "seposix.h"
#define UNICODE
#include <windows.h>
#include <lm.h>
#include <lmaccess.h>
#include <sys/stat.h>
//
// This number will never be returned in the PosixOffset field of
// a trusted domain query.
//
#define INVALID_POSIX_OFFSET 1
ULONG
PsxStatusToErrno(
IN NTSTATUS Status
)
/*++
Routine Description:
This procedure converts an NT status code to an
equivalent errno value.
The conversion is a function of the status code class.
Arguments:
Class - Supplies the status code class to use.
Status - Supplies the status code to convert.
Return Value:
Returns an equivalent error code to the supplied status code.
--*/
{
ULONG Error;
switch (Status) {
case STATUS_INVALID_PARAMETER:
Error = EINVAL;
break;
case STATUS_DIRECTORY_NOT_EMPTY:
// Error = ENOTEMPTY;
Error = EEXIST;
break;
case STATUS_OBJECT_PATH_INVALID:
case STATUS_OBJECT_PATH_SYNTAX_BAD:
case STATUS_NOT_A_DIRECTORY:
Error = ENOTDIR;
break;
case STATUS_OBJECT_NAME_COLLISION:
Error = EEXIST;
break;
case STATUS_OBJECT_PATH_NOT_FOUND:
case STATUS_OBJECT_NAME_NOT_FOUND:
case STATUS_DELETE_PENDING:
case STATUS_NO_SUCH_FILE:
Error = ENOENT;
break;
case STATUS_NO_MEMORY:
case STATUS_INSUFFICIENT_RESOURCES:
Error = ENOMEM;
break;
case STATUS_CANNOT_DELETE:
Error = ETXTBUSY;
break;
case STATUS_DISK_FULL:
Error = ENOSPC;
break;
case STATUS_MEDIA_WRITE_PROTECTED:
Error = EROFS;
break;
case STATUS_OBJECT_NAME_INVALID:
Error = ENAMETOOLONG;
break;
case STATUS_FILE_IS_A_DIRECTORY:
Error = EISDIR;
break;
case STATUS_NOT_SAME_DEVICE:
Error = EXDEV;
break;
case STATUS_INVALID_OWNER:
Error = EPERM;
break;
case STATUS_INVALID_IMAGE_FORMAT:
case STATUS_INVALID_IMAGE_LE_FORMAT:
case STATUS_INVALID_IMAGE_NOT_MZ:
case STATUS_INVALID_IMAGE_PROTECT:
case STATUS_INVALID_IMAGE_WIN_16:
Error = ENOEXEC;
break;
case STATUS_NOT_IMPLEMENTED:
Error = ENOSYS;
break;
case STATUS_TOO_MANY_LINKS:
Error = EMLINK;
break;
default:
Error = EACCES;
}
return Error;
}
ULONG
PsxStatusToErrnoPath(
IN PUNICODE_STRING Path
)
/*++
Routine Description:
This procedure is called when an NtOpenFile returns
STATUS_OBJECT_PATH_NOT_FOUND; this routine is to
distinguish the following too cases:
/file.c/foo, where file.c exists (ENOTDIR)
/noent/foo, where noent doesn't exist (ENOENT)
(NtOpenFile returns OBJECT_PATH_NOT_FOUND for both cases).
Arguments:
Path - Supplies the path that was given to NtOpenFile. The
path string is destroyed by this function.
Return Value:
Returns an equivalent error code to the supplied status code.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Obj;
HANDLE FileHandle;
ULONG DesiredAccess;
IO_STATUS_BLOCK Iosb;
ULONG Options;
PWCHAR pwc, pwcSav;
ULONG MinLen;
PSX_GET_SIZEOF(DOSDEVICE_X_W,MinLen);
DesiredAccess = SYNCHRONIZE;
Options = FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE;
pwcSav = NULL;
for (;;) {
//
// Remove a trailing component.
//
pwc = wcsrchr(Path->Buffer, L'\\');
if (pwcSav)
*pwcSav = L'\\';
if (NULL == pwc) {
break;
}
*pwc = UNICODE_NULL;
pwcSav = pwc;
Path->Length = wcslen(Path->Buffer) * sizeof(WCHAR);
if (Path->Length <= MinLen) {
*pwcSav = L'\\';
break;
}
InitializeObjectAttributes(&Obj, Path, 0, NULL, NULL);
Status = NtOpenFile(&FileHandle, DesiredAccess, &Obj,
&Iosb, SHARE_ALL, Options);
if (NT_SUCCESS(Status)) {
NtClose(FileHandle);
}
if (STATUS_NOT_A_DIRECTORY == Status) {
*pwcSav = L'\\';
Path->Length = wcslen(Path->Buffer) * sizeof(WCHAR);
return ENOTDIR;
}
}
Path->Length = wcslen(Path->Buffer) * sizeof(WCHAR);
return ENOENT;
}
ULONG
PsxDetermineFileClass(
IN HANDLE FileHandle
)
/*++
Routine Description:
This function examines a file handle and returns its FileClass
Arguments:
FileHandle - Supplies a handle to an open file whose class is to be
determined.
Return Value:
The file class of the specified file. Defined in <sys/stat.h>.
--*/
{
NTSTATUS st;
IO_STATUS_BLOCK Iosb;
FILE_BASIC_INFORMATION BasicInfo;
FILE_FS_DEVICE_INFORMATION DeviceInfo;
//
// Call NtQueryFile to get device type and attributes
//
st = NtQueryInformationFile(
FileHandle,
&Iosb,
&BasicInfo,
sizeof(BasicInfo),
FileBasicInformation
);
if (!NT_SUCCESS(st)) {
// XXX.mjb: Sometimes fails on HPFS
KdPrint(("PSXS: PsxDetermineFileClass: NtQueryInfoFile: 0x%x\n", st));
return S_IFREG;
}
st = NtQueryVolumeInformationFile(
FileHandle,
&Iosb,
&DeviceInfo,
sizeof(DeviceInfo),
FileFsDeviceInformation
);
ASSERT(NT_SUCCESS(st));
switch (DeviceInfo.DeviceType) {
case FILE_DEVICE_DATALINK:
case FILE_DEVICE_KEYBOARD:
case FILE_DEVICE_MOUSE:
case FILE_DEVICE_NETWORK:
case FILE_DEVICE_NULL:
case FILE_DEVICE_PHYSICAL_NETCARD:
case FILE_DEVICE_PARALLEL_PORT:
case FILE_DEVICE_PRINTER:
case FILE_DEVICE_SOUND:
case FILE_DEVICE_SCREEN:
case FILE_DEVICE_SERIAL_PORT:
case FILE_DEVICE_TRANSPORT:
return S_IFCHR;
case FILE_DEVICE_DFS:
case FILE_DEVICE_DISK_FILE_SYSTEM:
case FILE_DEVICE_NETWORK_FILE_SYSTEM:
return S_IFBLK;
case FILE_DEVICE_DISK:
case FILE_DEVICE_VIRTUAL_DISK:
case FILE_DEVICE_TAPE:
break;
default:
break;
// return 0;
}
//
// The only thing left is RegularFile class. Now
// determine if this is a directory, named pipe,
// or regular file.
//
if (BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
return S_IFDIR;
}
//
// For now, anything marked as a system file is a named pipe.
// In the future, this will involve checking to see if file
// has the Object Bit and has the appropriate EA (named pipe
// class id).
//
if (BasicInfo.FileAttributes & FILE_ATTRIBUTE_SYSTEM) {
return S_IFIFO;
}
return S_IFREG;
}
VOID
EndImpersonation(
VOID
)
{
HANDLE Handle;
NTSTATUS Status;
Handle = NULL;
Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken,
(PVOID)&Handle, sizeof(HANDLE));
ASSERT(NT_SUCCESS(Status));
}
//
// MakePosixId -- convert the given SID into a Posix Id. This basically
// means find the Posix Offset and add it to the last sub-authority
// in the SID. The Posix Id is returned.
//
uid_t
MakePosixId(PSID Sid)
{
NTSTATUS Status;
LSA_HANDLE
PolicyHandle,
TrustedDomainHandle;
PTRUSTED_POSIX_OFFSET_INFO
pPosixOff;
OBJECT_ATTRIBUTES
Obj;
SECURITY_QUALITY_OF_SERVICE
SecurityQoS;
CHAR buf[SECURITY_DESCRIPTOR_MIN_LENGTH];
PSECURITY_DESCRIPTOR
SecurityDescriptor = (PVOID)buf;
PSID DomainSid;
ULONG RelativeId, offset;
UNICODE_STRING
DCName,
Domain_U;
PPOLICY_ACCOUNT_DOMAIN_INFO
AccountDomainInfo;
PPOLICY_PRIMARY_DOMAIN_INFO
PrimaryDomainInfo;
UCHAR SubAuthCount;
LPBYTE netbuf;
ULONG i;
SubAuthCount = *RtlSubAuthorityCountSid(Sid);
RelativeId = *RtlSubAuthoritySid(Sid, SubAuthCount - 1);
//
// Map S-1-5-5-X-Y to Id 0xFFF
//
if (3 == SubAuthCount &&
5 == RtlIdentifierAuthoritySid(Sid)->Value[5] &&
5 == *RtlSubAuthoritySid(Sid, 0)) {
return 0xFFF;
}
//
// First copy the given Sid to a Sid for that domain.
//
DomainSid = RtlAllocateHeap(PsxHeap, 0, RtlLengthSid(Sid));
if (NULL == DomainSid) {
KdPrint(("PSXSS: MakePosixId: no memory\n"));
return 0;
}
Status = RtlCopySid(RtlLengthSid(Sid), DomainSid, Sid);
ASSERT(NT_SUCCESS(Status));
--*RtlSubAuthorityCountSid(DomainSid);
//
// See if the offset for the domain is already known.
//
if (INVALID_POSIX_OFFSET != (offset = GetOffsetBySid(DomainSid))) {
// XXX.mjb: close handles, free memory.
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
return (offset | RelativeId);
}
//
// If the Domain part of the passed-in Sid is our account domain,
// then the offset is known.
//
SecurityQoS.ImpersonationLevel = SecurityIdentification;
SecurityQoS.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
SecurityQoS.EffectiveOnly = TRUE;
InitializeObjectAttributes(&Obj, NULL, 0, NULL, NULL);
Obj.SecurityQualityOfService = &SecurityQoS;
Status = LsaOpenPolicy(NULL, &Obj, GENERIC_EXECUTE, &PolicyHandle);
if (!NT_SUCCESS(Status)) {
KdPrint(("PSXSS: Can't open policy: 0x%x\n", Status));
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
return 0;
}
Status = LsaQueryInformationPolicy(PolicyHandle,
PolicyAccountDomainInformation, (PVOID *)&AccountDomainInfo);
if (!NT_SUCCESS(Status)) {
KdPrint(("PSXSS: Can't query info policy: 0x%x\n", Status));
return 0;
}
ASSERT(NULL != AccountDomainInfo->DomainSid);
if (RtlEqualSid(AccountDomainInfo->DomainSid, DomainSid)) {
MapSidToOffset(DomainSid, SE_ACCOUNT_DOMAIN_POSIX_OFFSET);
LsaFreeMemory(AccountDomainInfo);
LsaClose(PolicyHandle);
return RelativeId | SE_ACCOUNT_DOMAIN_POSIX_OFFSET;
}
LsaFreeMemory(AccountDomainInfo);
Status = LsaQueryInformationPolicy(PolicyHandle,
PolicyPrimaryDomainInformation, (PVOID *)&PrimaryDomainInfo);
ASSERT(NT_SUCCESS(Status));
if (NULL == PrimaryDomainInfo->Sid) {
//
// This machine does not have a primary domain, and the
// sid we're mapping does not belong to the account domain
// and is not a well-known sid.
//
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
LsaFreeMemory(PrimaryDomainInfo);
LsaClose(PolicyHandle);
return RelativeId;
}
if (NULL != PrimaryDomainInfo->Sid &&
RtlEqualSid(PrimaryDomainInfo->Sid, DomainSid)) {
MapSidToOffset(DomainSid, SE_PRIMARY_DOMAIN_POSIX_OFFSET);
LsaFreeMemory(PrimaryDomainInfo);
LsaClose(PolicyHandle);
return RelativeId | SE_PRIMARY_DOMAIN_POSIX_OFFSET;
}
Status = NetGetAnyDCName(NULL,
PrimaryDomainInfo->Name.Buffer,
&netbuf);
if (Status != ERROR_SUCCESS) {
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
LsaFreeMemory(PrimaryDomainInfo);
LsaClose(PolicyHandle);
return RelativeId;
}
DCName.Buffer = (PVOID)netbuf;
NetApiBufferSize(netbuf, (LPDWORD)&DCName.MaximumLength);
DCName.Length = wcslen((PWCHAR)netbuf) * sizeof(WCHAR);
LsaClose(PolicyHandle);
Status = LsaOpenPolicy(&DCName, &Obj, GENERIC_EXECUTE, &PolicyHandle);
if (!NT_SUCCESS(Status)) {
KdPrint(("PSXSS: Can't open policy on DC %wZ: 0x%x\n", &DCName,
Status));
LsaFreeMemory(PrimaryDomainInfo);
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
return RelativeId;
}
NetApiBufferFree(netbuf);
LsaFreeMemory(PrimaryDomainInfo);
Status = LsaOpenTrustedDomain(PolicyHandle, DomainSid, GENERIC_EXECUTE,
&TrustedDomainHandle);
if (!NT_SUCCESS(Status)) {
KdPrint(("PSXSS: Can't open trusted domain\n"));
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
return RelativeId;
}
Status = LsaQueryInfoTrustedDomain(TrustedDomainHandle,
TrustedPosixOffsetInformation,
(PVOID)&pPosixOff);
if (!NT_SUCCESS(Status)) {
KdPrint(("PSXSS: Can't query Posix offset info: 0x%x\n",
Status));
LsaClose(PolicyHandle);
LsaClose(TrustedDomainHandle);
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
return RelativeId;
}
offset = pPosixOff->Offset;
LsaFreeMemory(pPosixOff);
if (offset & 0xFFFF) {
KdPrint(("PSXSS: bad PsxOffset 0x%x\n", offset));
RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid);
LsaClose(TrustedDomainHandle);
LsaClose(PolicyHandle);
offset = 0;
}
ASSERT(INVALID_POSIX_OFFSET != offset);
MapSidToOffset(DomainSid, offset);
//
// Do not free DomainSid -- there is still a reference to it in
// the Sid-Offset cache (put there by MapSidToOffset).
//
LsaClose(PolicyHandle);
LsaClose(TrustedDomainHandle);
return offset | RelativeId;
}
typedef struct _SID_AND_OFFSET {
LIST_ENTRY Links;
PSID Sid;
ULONG Offset;
} SID_AND_OFFSET, *PSID_AND_OFFSET;
LIST_ENTRY SidList;
RTL_CRITICAL_SECTION SidListMutex;
//
// GetOffsetBySid -- search the SidList for the given Sid. If we've
// encountered this domain before, we'll know the Posix offset,
// which is returned. If not, INVALID_POSIX_OFFSET is returned.
//
ULONG
GetOffsetBySid(PSID Sid)
{
PSID_AND_OFFSET pSO;
ULONG Offset = INVALID_POSIX_OFFSET;
RtlEnterCriticalSection(&SidListMutex);
for (pSO = (PSID_AND_OFFSET)SidList.Flink;
pSO != (PSID_AND_OFFSET)&SidList;
pSO = (PSID_AND_OFFSET)pSO->Links.Flink) {
if (RtlEqualSid(Sid, pSO->Sid)) {
Offset = pSO->Offset;
break;
}
}
RtlLeaveCriticalSection(&SidListMutex);
return Offset;
}
//
// GetSidByOffset -- search the SidList for the given offset. Called in
// the process of converting a uid or gid to a Sid, as in getpwuid().
//
PSID
GetSidByOffset(ULONG Offset)
{
PSID_AND_OFFSET pSO;
PSID Sid = NULL;
RtlEnterCriticalSection(&SidListMutex);
for (pSO = (PSID_AND_OFFSET)SidList.Flink;
pSO != (PSID_AND_OFFSET)&SidList;
pSO = (PSID_AND_OFFSET)pSO->Links.Flink) {
if (Offset == pSO->Offset) {
Sid = pSO->Sid;
break;
}
}
RtlLeaveCriticalSection(&SidListMutex);
return Sid;
}
//
// MapSidToOffset -- add the given sid and offset to the cache. If there's
// an error, like no memory, it's simply dropped on the floor.
//
VOID
MapSidToOffset(PSID Sid, ULONG Offset)
{
PSID_AND_OFFSET pSO;
pSO = RtlAllocateHeap(PsxHeap, 0, sizeof(*pSO));
if (NULL == pSO) {
return;
}
pSO->Sid = Sid;
pSO->Offset = Offset;
RtlEnterCriticalSection(&SidListMutex);
InsertHeadList(&SidList, &pSO->Links);
RtlLeaveCriticalSection(&SidListMutex);
}
//
// InitSidList -- do initialization, including mapping special Sids to
// their appropriate offsets. No locking is done, assumed to be
// called in a single-threaded way.
//
VOID
InitSidList(VOID)
{
NTSTATUS Status;
PSID Sid;
SID_IDENTIFIER_AUTHORITY
AuthSec = SECURITY_NT_AUTHORITY,
Auth0 = SECURITY_NULL_SID_AUTHORITY,
Auth1 = SECURITY_WORLD_SID_AUTHORITY,
Auth2 = SECURITY_LOCAL_SID_AUTHORITY,
Auth3 = SECURITY_CREATOR_SID_AUTHORITY,
Auth4 = SECURITY_NON_UNIQUE_AUTHORITY,
Auth5 = SECURITY_NT_AUTHORITY;
RtlInitializeCriticalSection(&SidListMutex);
InitializeListHead(&SidList);
Status = RtlAllocateAndInitializeSid(&Auth0, 0,
0, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_NULL_POSIX_ID);
Status = RtlAllocateAndInitializeSid(&Auth1, 0,
0, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_WORLD_POSIX_ID);
Status = RtlAllocateAndInitializeSid(&Auth2, 0,
0, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_LOCAL_POSIX_ID);
Status = RtlAllocateAndInitializeSid(&Auth3, 0,
0, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_CREATOR_OWNER_POSIX_ID);
Status = RtlAllocateAndInitializeSid(&Auth4, 0,
0, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_NON_UNIQUE_POSIX_ID);
Status = RtlAllocateAndInitializeSid(&Auth5, 0,
0, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_AUTHORITY_POSIX_ID);
//
// "Builtin" domain has known offset.
//
Status = RtlAllocateAndInitializeSid(&AuthSec, 1,
SECURITY_BUILTIN_DOMAIN_RID, 0, 0, 0, 0, 0, 0, 0, &Sid);
ASSERT(NT_SUCCESS(Status));
MapSidToOffset(Sid, SE_BUILT_IN_DOMAIN_POSIX_OFFSET);
}
//
// AccessMaskToMode -- convert a set of NT ACCESS_MASKS to the POSIX
// mode_t.
//
mode_t
AccessMaskToMode(
ACCESS_MASK UserAccess,
ACCESS_MASK GroupAccess,
ACCESS_MASK OtherAccess
)
{
mode_t Mode = 0;
int i;
PACCESS_MASK pAM;
//
// Make sure that if a GENERIC_ACCESS is set, we notice that
// the mask implies FILE_GENERIC_ACCESS.
//
for (i = 0; i < 3; ++i) {
switch (i) {
case 0:
pAM = &UserAccess;
break;
case 1:
pAM = &GroupAccess;
break;
case 2:
pAM = &OtherAccess;
break;
}
if (*pAM & GENERIC_READ) {
*pAM |= FILE_GENERIC_READ;
}
if (*pAM & GENERIC_WRITE) {
*pAM |= FILE_GENERIC_WRITE;
}
if (*pAM & GENERIC_EXECUTE) {
*pAM |= FILE_GENERIC_EXECUTE;
}
if (*pAM & GENERIC_ALL) {
*pAM |= FILE_ALL_ACCESS;
}
}
if (UserAccess & FILE_READ_DATA) {
Mode |= S_IRUSR;
}
if ((UserAccess & FILE_WRITE_DATA) &&
(UserAccess & FILE_APPEND_DATA)) {
Mode |= S_IWUSR;
}
if (UserAccess & FILE_EXECUTE) {
Mode |= S_IXUSR;
}
if (GroupAccess & FILE_READ_DATA) {
Mode |= S_IRGRP;
}
if ((GroupAccess & FILE_WRITE_DATA) &&
(GroupAccess & FILE_APPEND_DATA)) {
Mode |= S_IWGRP;
}
if (GroupAccess & FILE_EXECUTE) {
Mode |= S_IXGRP;
}
if (OtherAccess & FILE_READ_DATA) {
Mode |= S_IROTH;
}
if ((OtherAccess & FILE_WRITE_DATA) &&
(OtherAccess & FILE_APPEND_DATA)) {
Mode |= S_IWOTH;
}
if (OtherAccess & FILE_EXECUTE) {
Mode |= S_IXOTH;
}
return Mode;
}
void
ModeToAccessMask(
mode_t Mode,
PACCESS_MASK pUserAccess,
PACCESS_MASK pGroupAccess,
PACCESS_MASK pOtherAccess
)
{
//
// All ACL's have these standard permissions:
// READ_ATTR and READ_EA so anybody can access() any file.
//
*pUserAccess = SYNCHRONIZE |
READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA;
*pGroupAccess = *pOtherAccess = *pUserAccess;
//
// The owner always gets WRITE_DAC (for chmod), FILE_WRITE_ATTR
// (for utimes), and WRITE_OWNER (for chgrp).
//
*pUserAccess |= (WRITE_DAC | WRITE_OWNER | FILE_WRITE_ATTRIBUTES);
if (Mode & S_IRUSR) {
*pUserAccess |= FILE_GENERIC_READ | FILE_LIST_DIRECTORY;
}
if (Mode & S_IWUSR) {
*pUserAccess |= FILE_GENERIC_WRITE | FILE_DELETE_CHILD;
}
if (Mode & S_IXUSR) {
*pUserAccess |= FILE_GENERIC_EXECUTE;
}
if (Mode & S_IRGRP) {
*pGroupAccess |= FILE_GENERIC_READ | FILE_LIST_DIRECTORY;
}
if (Mode & S_IWGRP) {
*pGroupAccess |= FILE_GENERIC_WRITE | FILE_DELETE_CHILD;
}
if (Mode & S_IXGRP) {
*pGroupAccess |= FILE_GENERIC_EXECUTE;
}
if (Mode & S_IROTH) {
*pOtherAccess |= FILE_GENERIC_READ | FILE_LIST_DIRECTORY;
}
if (Mode & S_IWOTH) {
*pOtherAccess |= FILE_GENERIC_WRITE | FILE_DELETE_CHILD;
}
if (Mode & S_IXOTH) {
*pOtherAccess |= FILE_GENERIC_EXECUTE;
}
}