/*++ Copyright (c) 1989 Microsoft Corporation Module Name: fileio.c Abstract: This module implements server performed file io Author: Mark Lucovsky (markl) 27-Nov-1989 Revision History: --*/ #include #include #include #include "psxsrv.h" BOOLEAN FileRead ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd ); BOOLEAN FileWrite ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd ); BOOLEAN FileDup ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd, IN PFILEDESCRIPTOR FdDup ); BOOLEAN FileLseek ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd ); BOOLEAN FileStat ( IN PIONODE IoNode, IN HANDLE FileHandle, OUT struct stat *StatBuf, OUT NTSTATUS *pStatus ); void FindOwnerModeFile( IN HANDLE FileHandle, OUT struct stat *StatBuf ); VOID FileLastClose ( IN PPSX_PROCESS p, IN PSYSTEMOPENFILE SystemOpenFile ) { NTSTATUS st; IO_STATUS_BLOCK Iosb; FILE_DISPOSITION_INFORMATION Disp; wchar_t buf[PATH_MAX]; UNICODE_STRING U; ANSI_STRING A; OBJECT_ATTRIBUTES Obj; HANDLE hDir; if (!SystemOpenFile->IoNode->Junked) { st = NtClose(SystemOpenFile->NtIoHandle); ASSERT(NT_SUCCESS(st)); return; } // // This file has been moved to the junkyard, and should now // be deleted. // Disp.DeleteFile = TRUE; st = NtSetInformationFile(SystemOpenFile->NtIoHandle, &Iosb, &Disp, sizeof(Disp), FileDispositionInformation); if (!NT_SUCCESS(st)) { KdPrint(("PSXSS: FileLastClose: SetInfo: 0x%x\n", st)); } st = NtClose(SystemOpenFile->NtIoHandle); ASSERT(NT_SUCCESS(st)); // // Try to delete the directory that held the junked file. // swprintf(buf, L"\\DosDevices\\%wc:\\%ws", SystemOpenFile->IoNode->DeviceSerialNumber, PSX_JUNK_DIR); U.Buffer = buf; U.Length = wcslen(buf) * sizeof(wchar_t); U.MaximumLength = sizeof(buf); InitializeObjectAttributes(&Obj, &U, 0, NULL, NULL); st = NtOpenFile(&hDir, SYNCHRONIZE | DELETE, &Obj, &Iosb, SHARE_ALL, FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE); if (!NT_SUCCESS(st)) { //null } else { Disp.DeleteFile = TRUE; st = NtSetInformationFile(hDir, &Iosb, &Disp, sizeof(Disp), FileDispositionInformation); NtClose(hDir); } } PSXIO_VECTORS FileVectors = { NULL, NULL, NULL, FileLastClose, NULL, FileRead, FileWrite, FileDup, FileLseek, FileStat }; BOOLEAN FileWrite ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd ) /*++ Routine Description: This procedure implements write when the device being written is a file. Arguments: p - Supplies the address of the process making the call. m - Supplies the address of the message associated with the request. Fd - supplies the address of the file descriptor being written. Return Value: --*/ { PPSX_WRITE_MSG args; NTSTATUS st; IO_STATUS_BLOCK Iosb; LARGE_INTEGER ByteOffset; SIZE_T IoBufferSize; FILE_FS_SIZE_INFORMATION SizeInfo; ULONG Avail; LARGE_INTEGER Time; ULONG PosixTime; PVOID IoBuffer = NULL; args = &m->u.Write; // Allocate buffer in server IoBufferSize = args->Nbytes; st = NtAllocateVirtualMemory(NtCurrentProcess(), &IoBuffer, 0, &IoBufferSize, MEM_COMMIT, PAGE_READWRITE); if (!NT_SUCCESS(st)) { m->Error = ENOMEM; return TRUE; } // Read data from user buffer to server buffer st = NtReadVirtualMemory(p->Process, args->Buf, IoBuffer, args->Nbytes, NULL); if (!NT_SUCCESS(st)) { m->Error = PsxStatusToErrno(st); goto out; } if (Fd->SystemOpenFileDesc->Flags & PSX_FD_APPEND) { ByteOffset = RtlConvertLongToLargeInteger( FILE_WRITE_TO_END_OF_FILE); } else { ByteOffset = RtlConvertLongToLargeInteger( FILE_USE_FILE_POINTER_POSITION); } st = NtWriteFile(Fd->SystemOpenFileDesc->NtIoHandle, NULL, NULL, NULL, &Iosb, IoBuffer, args->Nbytes, &ByteOffset, NULL); if (NT_SUCCESS(st)) { NtQuerySystemTime(&Time); if (!RtlTimeToSecondsSince1970(&Time, &PosixTime)) { PosixTime = 0; } RtlEnterCriticalSection(&Fd->SystemOpenFileDesc->IoNode->IoNodeLock); Fd->SystemOpenFileDesc->IoNode->ModifyDataTime = PosixTime; Fd->SystemOpenFileDesc->IoNode->ModifyIoNodeTime = PosixTime; RtlLeaveCriticalSection(&Fd->SystemOpenFileDesc->IoNode->IoNodeLock); m->ReturnValue = (ULONG)Iosb.Information; goto out; } switch (st) { case STATUS_DISK_FULL: while (0 != --args->Nbytes) { st = NtWriteFile(Fd->SystemOpenFileDesc->NtIoHandle, NULL, NULL, NULL, &Iosb, IoBuffer, args->Nbytes, &ByteOffset, NULL); if (NT_SUCCESS(st)) { m->ReturnValue = (ULONG)Iosb.Information; goto out; } } m->Error = ENOSPC; break; default: m->Error = EIO; break; } out: st = NtFreeVirtualMemory(NtCurrentProcess(), &IoBuffer, &IoBufferSize, MEM_RELEASE); if (!NT_SUCCESS(st) ) { m->Error = ENOMEM; } return TRUE; } BOOLEAN FileRead ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd ) /*++ Routine Description: This procedure implements read when the device being read is a file. Arguments: p - Supplies the address of the process making the call. m - Supplies the address of the message associated with the request. Fd - supplies the address of the file descriptor being read. Return Value: --*/ { PPSX_READ_MSG args; PPSX_READDIR_MSG args2; NTSTATUS st; IO_STATUS_BLOCK Iosb; LARGE_INTEGER ByteOffset; SIZE_T IoBufferSize; LARGE_INTEGER Time; ULONG PosixTime; UCHAR Buf[sizeof(FILE_NAMES_INFORMATION) + NAME_MAX * sizeof(WCHAR)]; PFILE_NAMES_INFORMATION pNamesInfo = (PVOID)Buf; PVOID IoBuffer = NULL; args2 = &m->u.ReadDir; args = &m->u.Read; // // Update the access time on the ionode. // NtQuerySystemTime(&Time); if (!RtlTimeToSecondsSince1970(&Time, &PosixTime)) { PosixTime = 0; } RtlEnterCriticalSection(&Fd->SystemOpenFileDesc->IoNode->IoNodeLock); Fd->SystemOpenFileDesc->IoNode->AccessDataTime = PosixTime; RtlLeaveCriticalSection(&Fd->SystemOpenFileDesc->IoNode->IoNodeLock); if (S_ISDIR(Fd->SystemOpenFileDesc->IoNode->Mode)) { UNICODE_STRING U; ANSI_STRING A; st = NtQueryDirectoryFile( Fd->SystemOpenFileDesc->NtIoHandle, NULL, NULL, NULL, &Iosb, &Buf, sizeof(Buf), FileNamesInformation, TRUE, NULL, args2->RestartScan ); if (STATUS_BUFFER_OVERFLOW == st) { m->Error = ENAMETOOLONG; return TRUE; } if (STATUS_NO_MORE_FILES == st) { m->ReturnValue = 0; return TRUE; } if (!NT_SUCCESS(st)) { m->Error = PsxStatusToErrno(st); return TRUE; } U.Length = U.MaximumLength = (USHORT)pNamesInfo->FileNameLength; U.Buffer = pNamesInfo->FileName; st = RtlUnicodeStringToAnsiString(&A, &U, TRUE); if (!NT_SUCCESS(st)) { m->Error = ENOMEM; return TRUE; } m->ReturnValue = A.Length; st = NtWriteVirtualMemory(p->Process, args2->Buf, A.Buffer, A.Length, NULL); RtlFreeAnsiString(&A); if (!NT_SUCCESS(st)) { m->Error = EIO; return TRUE; } return TRUE; } IoBufferSize = args->Nbytes; st = NtAllocateVirtualMemory(NtCurrentProcess(), &IoBuffer, 0, &IoBufferSize, MEM_COMMIT, PAGE_READWRITE); if (!NT_SUCCESS(st)) { m->Error = ENOMEM; return TRUE; } ByteOffset = RtlConvertLongToLargeInteger( FILE_USE_FILE_POINTER_POSITION); st = NtReadFile(Fd->SystemOpenFileDesc->NtIoHandle, NULL, NULL, NULL, &Iosb, IoBuffer, args->Nbytes, &ByteOffset, NULL); if (STATUS_END_OF_FILE == st) { m->ReturnValue = 0; goto out; } if (!NT_SUCCESS(st)) { m->Error = EIO; goto out; } m->ReturnValue = (ULONG)Iosb.Information; st = NtWriteVirtualMemory(p->Process, args->Buf, IoBuffer, args->Nbytes, NULL); if (!NT_SUCCESS(st)) { m->Error = PsxStatusToErrno(st); } out: st = NtFreeVirtualMemory(NtCurrentProcess(), &IoBuffer, &IoBufferSize, MEM_RELEASE); ASSERT(NT_SUCCESS(st)); return TRUE; } BOOLEAN FileDup ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd, IN PFILEDESCRIPTOR FdDup ) /*++ Routine Description: This procedure implements dup and dup2 Arguments: p - Supplies the address of the process making the call. m - Supplies the address of the message associated with the request. Fd - supplies the address of the file descriptor being duplicated. FdDup - supplies the address of the duplicate file descriptor. Return Value: ??? --*/ { PPSX_DUP_MSG args; args = &m->u.Dup; // // Copy contents of source file descriptor slot into new descriptor // Note that FD_CLOEXEC must be CLEAR on FdDup. // *FdDup = *Fd; FdDup->Flags &= ~PSX_FD_CLOSE_ON_EXEC; // // Increment reference count associated with the SystemOpenFile // descriptor for this file. // // Grab system open file lock RtlEnterCriticalSection(&SystemOpenFileLock); Fd->SystemOpenFileDesc->HandleCount++; RtlLeaveCriticalSection(&SystemOpenFileLock); return TRUE; } BOOLEAN FileLseek ( IN PPSX_PROCESS p, IN OUT PPSX_API_MSG m, IN PFILEDESCRIPTOR Fd ) /*++ Routine Description: This procedure implements lseek when the device being seeked on is a file. Arguments: p - Supplies the address of the process making the call. m - Supplies the address of the message associated with the request. Fd - supplies the address of the file descriptor being seekd Return Value: ??? --*/ { PPSX_LSEEK_MSG args; NTSTATUS st; IO_STATUS_BLOCK Iosb; LARGE_INTEGER Offset, NewByteOffset; FILE_POSITION_INFORMATION FilePosition; FILE_STANDARD_INFORMATION StandardInfo; args = &m->u.Lseek; Offset = RtlConvertLongToLargeInteger(args->Offset); switch (args->Whence) { case SEEK_SET: NewByteOffset = Offset; break; case SEEK_CUR: st = NtQueryInformationFile(Fd->SystemOpenFileDesc->NtIoHandle, &Iosb, &FilePosition, sizeof(FilePosition), FilePositionInformation); if (!NT_SUCCESS(st)) { m->Error = PsxStatusToErrno(st); return TRUE; } NewByteOffset.QuadPart = Offset.QuadPart + FilePosition.CurrentByteOffset.QuadPart; break; case SEEK_END: st = NtQueryInformationFile(Fd->SystemOpenFileDesc->NtIoHandle, &Iosb, &StandardInfo, sizeof(StandardInfo), FileStandardInformation); if (!NT_SUCCESS(st)) { m->Error = PsxStatusToErrno(st); return TRUE; } NewByteOffset.QuadPart = Offset.QuadPart + StandardInfo.EndOfFile.QuadPart; break; default: m->Error = EINVAL; return TRUE; } // Check for overflow. POSIX limited to arithmetic data type for off_t if (NewByteOffset.HighPart != 0 || (off_t)NewByteOffset.LowPart < 0) { m->Error = EINVAL; return TRUE; } FilePosition.CurrentByteOffset = NewByteOffset; args->Offset = NewByteOffset.LowPart; st = NtSetInformationFile(Fd->SystemOpenFileDesc->NtIoHandle, &Iosb, &FilePosition, sizeof(FilePosition), FilePositionInformation); if (!NT_SUCCESS(st)) { m->Error = EINVAL; } return TRUE; } BOOLEAN FileStat ( IN PIONODE IoNode, IN HANDLE FileHandle, OUT struct stat *StatBuf, OUT NTSTATUS *pStatus ) /*++ Routine Description: This procedure implements stat when the device being read is a file. Arguments: IoNode - supplies a pointer to the ionode of the file for which stat is requested. NULL if no active Ionode entry. FileHandle - supplies the Nt file handle of the file . StatBuf - Supplies the address of the statbuf portion of the message associated with the request. Return Value: ??? --*/ { IO_STATUS_BLOCK Iosb; FILE_INTERNAL_INFORMATION SerialNumber; FILE_BASIC_INFORMATION BasicInfo; FILE_STANDARD_INFORMATION StandardInfo; ULONG PosixTime; NTSTATUS st; // // First get the static information on the file from the ionode if // there is one (i.e. if the file currently open. // Open() sets the fields in the ionode. // if (NULL != IoNode) { StatBuf->st_mode = IoNode->Mode; StatBuf->st_ino = (ino_t)IoNode->FileSerialNumber; StatBuf->st_dev = IoNode->DeviceSerialNumber; StatBuf->st_uid = IoNode->OwnerId; StatBuf->st_gid = IoNode->GroupId; StatBuf->st_atime = IoNode->AccessDataTime; StatBuf->st_ctime = IoNode->ModifyIoNodeTime; StatBuf->st_mtime = IoNode->ModifyDataTime; } else { StatBuf->st_uid = 0; StatBuf->st_gid = 0; st = NtQueryInformationFile(FileHandle, &Iosb, &SerialNumber, sizeof(SerialNumber), FileInternalInformation); if (!NT_SUCCESS(st)) { KdPrint(("PSXSS: NtQueryInfoFile failed: 0x%x\n", st)); *pStatus = st; return TRUE; } st = NtQueryInformationFile(FileHandle, &Iosb, &BasicInfo, sizeof(BasicInfo), FileBasicInformation); if (!NT_SUCCESS(st)) { // // can return STATUS_NO_SUCH_FILE if network // file system // *pStatus = st; return TRUE; } StatBuf->st_ino = (ino_t)SerialNumber.IndexNumber.LowPart; StatBuf->st_dev = 0; StatBuf->st_mode = PsxDetermineFileClass(FileHandle); FindOwnerModeFile(FileHandle, StatBuf); // Convert Nt file times to POSIX ones if (!RtlTimeToSecondsSince1970(&BasicInfo.LastAccessTime, &PosixTime)) { PosixTime = 0L; } StatBuf->st_atime = PosixTime; if (!RtlTimeToSecondsSince1970(&BasicInfo.LastWriteTime, &PosixTime)) { PosixTime = 0L; } StatBuf->st_mtime = PosixTime; if (!RtlTimeToSecondsSince1970(&BasicInfo.ChangeTime, &PosixTime)) { PosixTime = 0L; } StatBuf->st_ctime = PosixTime; } st = NtQueryInformationFile(FileHandle, &Iosb, &StandardInfo, sizeof(StandardInfo), FileStandardInformation); if (!NT_SUCCESS(st)) { KdPrint(("PSXSS: NtQueryInfoFile(StdInfo): 0x%x\n", st)); *pStatus = st; return TRUE; } StatBuf->st_size = (off_t)StandardInfo.EndOfFile.LowPart; StatBuf->st_nlink = StandardInfo.NumberOfLinks; return TRUE; } void FindOwnerModeFile( IN HANDLE FileHandle, OUT struct stat *StatBuf ) { SECURITY_INFORMATION SecurityInformation; ULONG LengthNeeded; PSID NtOwner, NtGroup; BOOLEAN OwnerDefaulted, GroupDefaulted; BOOLEAN AclPresent, AclDefaulted; PSECURITY_DESCRIPTOR SecurityDescriptor = NULL; PACL pAcl; NTSTATUS st; ACCESS_MASK UserAccess, GroupAccess, OtherAccess; // // Get the security descriptor for the file. // SecurityInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; // // First try a guess at the necessary descriptor size. // LengthNeeded = 2048; SecurityDescriptor = RtlAllocateHeap(PsxHeap, 0, LengthNeeded); if (NULL == SecurityDescriptor) { return; } st = NtQuerySecurityObject(FileHandle, SecurityInformation, SecurityDescriptor, LengthNeeded, &LengthNeeded); if (STATUS_BUFFER_TOO_SMALL == st) { RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); SecurityDescriptor = RtlAllocateHeap(PsxHeap, 0, LengthNeeded); if (NULL == SecurityDescriptor) { return; } st = NtQuerySecurityObject(FileHandle, SecurityInformation, SecurityDescriptor, LengthNeeded, &LengthNeeded); if (!NT_SUCCESS(st)) { KdPrint(("PSXSS: FindOwnerModeFile: NtQsObj: 0x%x\n", st)); RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); return; } } else if (!NT_SUCCESS(st)) { return; } ASSERT(RtlValidSecurityDescriptor(SecurityDescriptor)); // // Get the owner and group from the security descriptor // st = RtlGetOwnerSecurityDescriptor(SecurityDescriptor, &NtOwner, &OwnerDefaulted); if (!NT_SUCCESS(st)) { RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); return; } st = RtlGetGroupSecurityDescriptor(SecurityDescriptor, &NtGroup, &GroupDefaulted); ASSERT(NT_SUCCESS(st)); if (NULL == NtOwner || NULL == NtGroup) { // // Seems like this file doesn't have an owner or a // group. Would like to say that it's owned by 'world' // or somesuch. // StatBuf->st_uid = 0; StatBuf->st_gid = 0; // // Since we don't know who owns the file, we can't // figure out what permissions we have on it. Say // that all access is granted. We may be lying. // StatBuf->st_mode |= _S_PROT; RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); return; } // // Translate Nt uid and gid to Posix recognizable form // and set StatBuf->st_uid and StatBuf->st_gid. // ASSERT(RtlValidSid(NtOwner)); ASSERT(RtlValidSid(NtGroup)); StatBuf->st_uid = MakePosixId(NtOwner); StatBuf->st_gid = MakePosixId(NtGroup); ASSERT(RtlValidSecurityDescriptor(SecurityDescriptor)); st = RtlGetDaclSecurityDescriptor(SecurityDescriptor, &AclPresent, &pAcl, &AclDefaulted); if (!NT_SUCCESS(st)) { KdPrint(("PSXSS: RtlGetDaclSD: 0x%x\n", st)); } ASSERT(NT_SUCCESS(st)); if (!AclPresent || (AclPresent && NULL == pAcl)) { // All access is granted. StatBuf->st_mode |= _S_PROT; RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); return; } // // We have a Dacl // ASSERT(RtlValidAcl(pAcl)); st = RtlInterpretPosixAcl(ACL_REVISION2, NtOwner, NtGroup, pAcl, &UserAccess, &GroupAccess, &OtherAccess); if (!NT_SUCCESS(st)) { // // XXX.mjb: The Acl is not a Posix acl. It might be nice to // return an error or somesuch. // RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); return; } RtlFreeHeap(PsxHeap, 0, (PVOID)SecurityDescriptor); StatBuf->st_mode |= AccessMaskToMode(UserAccess, GroupAccess, OtherAccess); }