/*++ Copyright (c) 1997 Microsoft Corporation Module Name: netboot.c Abstract: This module implements the net boot file system used by the operating system loader. Author: Chuck Lenzmeier (chuckl) 09-Jan-1997 Revision History: --*/ #include "bootlib.h" #include "stdio.h" #ifdef UINT16 #undef UINT16 #endif #ifdef INT16 #undef INT16 #endif #include #include #include #include #include #include #if defined(_IA64_) #include "bootia64.h" #else #include "bootx86.h" #endif #ifndef BOOL typedef int BOOL; #endif #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif #ifndef BYTE typedef unsigned char BYTE; #endif #ifndef LPBYTE typedef BYTE *LPBYTE; #endif #define MAX_PATH 260 // // Define global data. // BOOLEAN BlBootingFromNet = FALSE; BOOLEAN NetBootInitialized = FALSE; PARC_OPEN_ROUTINE NetRealArcOpenRoutine; PARC_CLOSE_ROUTINE NetRealArcCloseRoutine; BL_DEVICE_ENTRY_TABLE NetDeviceEntryTable; BOOTFS_INFO NetBootFsInfo={L"net"}; #if defined(REMOTE_BOOT_SECURITY) ULONG TftpSecurityHandle = 0; #endif // defined(REMOTE_BOOT_SECURITY) BOOLEAN NetBootTftpUsedPassword2; // // We cache the last file opened, in case we get a request to open it again. // We don't save a copy of the data, just a pointer to the data read by that // open. So if the original open is closed before the next open for the // same file comes in, we won't get a cache hit. But this system works for // reading compressed files, which is the situation we care about. In that // case a file is opened once and then re-opened twice more before the // original open is closed. // ULONG CachedFileDeviceId = 0; UCHAR CachedFilePath[MAX_PATH]; ULONG CachedFileSize = 0; PUCHAR CachedFileData = NULL; extern ARC_STATUS GetParametersFromRom ( VOID ); PBL_DEVICE_ENTRY_TABLE IsNetFileStructure ( IN ULONG DeviceId, IN PVOID StructureContext ) /*++ Routine Description: This routine determines if the partition on the specified channel contains a net file system volume. Arguments: DeviceId - Supplies the file table index for the device on which read operations are to be performed. StructureContext - Supplies a pointer to a net file structure context. Return Value: A pointer to the net entry table is returned if the partition is recognized as containing a net volume. Otherwise, NULL is returned. --*/ { PNET_STRUCTURE_CONTEXT NetStructureContext; DPRINT( TRACE, ("IsNetFileStructure\n") ); if ( !BlBootingFromNet || (DeviceId != NET_DEVICE_ID) ) { return NULL; } // // Clear the file system context block for the specified channel and // establish a pointer to the context structure that can be used by other // routines // NetStructureContext = (PNET_STRUCTURE_CONTEXT)StructureContext; RtlZeroMemory(NetStructureContext, sizeof(NET_STRUCTURE_CONTEXT)); // // Return the address of the table. // return &NetDeviceEntryTable; } // IsNetFileStructure ARC_STATUS NetInitialize ( VOID ) /*++ Routine Description: This routine initializes the net boot filesystem. Arguments: None. Return Value: ESUCCESS. --*/ { NTSTATUS status; DPRINT( TRACE, ("NetInitialize\n") ); //DbgBreakPoint( ); if( NetBootInitialized ) { return ESUCCESS; } // // Initialize the file entry table. Note that we need to do // this even if we aren't booting from the net because we may // use the 'Net' I/O functions to lay on top of any files that // we download through the debugger port. So for that case, // we need access to all these functions here (see bd\file.c) // NetDeviceEntryTable.Close = NetClose; NetDeviceEntryTable.Mount = NetMount; NetDeviceEntryTable.Open = NetOpen; NetDeviceEntryTable.Read = NetRead; NetDeviceEntryTable.GetReadStatus = NetGetReadStatus; NetDeviceEntryTable.Seek = NetSeek; NetDeviceEntryTable.Write = NetWrite; NetDeviceEntryTable.GetFileInformation = NetGetFileInformation; NetDeviceEntryTable.SetFileInformation = NetSetFileInformation; NetDeviceEntryTable.Rename = NetRename; NetDeviceEntryTable.GetDirectoryEntry = NetGetDirectoryEntry; NetDeviceEntryTable.BootFsInfo = &NetBootFsInfo; if( !BlBootingFromNet ) { return ESUCCESS; } NetBootInitialized = TRUE; DPRINT( LOUD, ("NetInitialize: booting from net\n") ); //DPRINT( LOUD, (" NetInitialize at %08x\n", NetInitialize) ); //DPRINT( LOUD, (" NetOpen at %08x\n", NetOpen) ); //DbgBreakPoint( ); // // Hook the ArcOpen and ArcClose routines. // NetRealArcOpenRoutine = SYSTEM_BLOCK->FirmwareVector[OpenRoutine]; SYSTEM_BLOCK->FirmwareVector[OpenRoutine] = NetArcOpen; NetRealArcCloseRoutine = SYSTEM_BLOCK->FirmwareVector[CloseRoutine]; SYSTEM_BLOCK->FirmwareVector[CloseRoutine] = NetArcClose; // // Get boot parameters from the boot ROM. // status = GetParametersFromRom( ); if ( status != ESUCCESS ) { return status; } return ESUCCESS; } VOID NetTerminate ( VOID ) /*++ Routine Description: This routine shuts down the net boot filesystem. Arguments: None. Return Value: ESUCCESS. --*/ { #if defined(_X86_) #if defined(REMOTE_BOOT_SECURITY) if ( TftpSecurityHandle != 0 ) { TftpLogoff(NetServerIpAddress, TftpSecurityHandle); TftpSecurityHandle = 0; } #endif // defined(REMOTE_BOOT_SECURITY) // // let's not set the receive status if the card isn't active. // RomSetReceiveStatus( 0 ); #endif // defined(_X86_) #ifdef EFI extern VOID EfiNetTerminate(); EfiNetTerminate(); #endif return; } // NetTerminate ARC_STATUS NetArcClose ( IN ULONG FileId ) { DPRINT( TRACE, ("NetArcClose\n") ); if ( FileId != NET_DEVICE_ID ) { return NetRealArcCloseRoutine( FileId ); } return ESUCCESS; } // NetArcClose ARC_STATUS NetArcOpen ( IN CHAR * FIRMWARE_PTR OpenPath, IN OPEN_MODE OpenMode, OUT ULONG * FIRMWARE_PTR FileId ) { DPRINT( TRACE, ("NetArcOpen\n") ); if ( _strnicmp(OpenPath, "net(", 4) != 0 ) { return NetRealArcOpenRoutine( OpenPath, OpenMode, FileId ); } *FileId = NET_DEVICE_ID; return ESUCCESS; } // NetArcOpen ARC_STATUS NetClose ( IN ULONG FileId ) { PBL_FILE_TABLE fileTableEntry; DPRINT( TRACE, ("NetClose FileId = %d\n", FileId) ); fileTableEntry = &BlFileTable[FileId]; { DPRINT( REAL_LOUD, ("NetClose: id %d, freeing memory at 0x%08x, %d bytes\n", FileId, fileTableEntry->u.NetFileContext.InMemoryCopy, fileTableEntry->u.NetFileContext.FileSize) ); BlFreeDescriptor( (ULONG)((ULONG_PTR)fileTableEntry->u.NetFileContext.InMemoryCopy >> PAGE_SHIFT )); // // If the data read for this specific open was what was cached, // then mark the cache empty. // if (fileTableEntry->u.NetFileContext.InMemoryCopy == CachedFileData) { CachedFileData = NULL; CachedFilePath[0] = '\0'; } } fileTableEntry->Flags.Open = 0; return EROFS; } // NetClose ARC_STATUS NetMount ( IN CHAR * FIRMWARE_PTR MountPath, IN MOUNT_OPERATION Operation ) { DPRINT( TRACE, ("NetMount\n") ); return EROFS; } // NetMount ARC_STATUS NetOpen ( IN CHAR * FIRMWARE_PTR OpenPath, IN OPEN_MODE OpenMode, OUT ULONG * FIRMWARE_PTR FileId ) { NTSTATUS ntStatus; ARC_STATUS arcStatus; // holds temp values, not the function return value PBL_FILE_TABLE fileTableEntry; TFTP_REQUEST request; ULONG oldBase; ULONG oldLimit; PCHAR p; #if defined(REMOTE_BOOT_SECURITY) static BOOLEAN NetBootTryTftpSecurity = FALSE; #endif // defined(REMOTE_BOOT_SECURITY) DPRINT( TRACE, ("NetOpen FileId = %d\n", *FileId) ); DPRINT( LOUD, ("NetOpen: opening %s, id %d, mode %d\n", OpenPath, *FileId, OpenMode) ); fileTableEntry = &BlFileTable[*FileId]; if ( OpenMode != ArcOpenReadOnly ) { DPRINT( LOUD, ("NetOpen: invalid OpenMode\n") ); return EROFS; } fileTableEntry->Flags.Open = 1; // Prevent GetCSCFileNameFromUNCPath using our entry #if defined(REMOTE_BOOT_SECURITY) // // Login if we don't have a valid handle, using the on-disk secret. // if ((TftpSecurityHandle == 0) && NetBootTryTftpSecurity) { ULONG FileId; RI_SECRET Secret; UCHAR Domain[RI_SECRET_DOMAIN_SIZE + 1]; UCHAR User[RI_SECRET_USER_SIZE + 1]; struct { UCHAR Owf[LM_OWF_PASSWORD_SIZE+NT_OWF_PASSWORD_SIZE]; } Passwords[2]; UCHAR Sid[RI_SECRET_SID_SIZE]; arcStatus = BlOpenRawDisk(&FileId); if (arcStatus == ESUCCESS) { arcStatus = BlReadSecret(FileId, &Secret); if (arcStatus == ESUCCESS) { BlParseSecret( Domain, User, Passwords[0].Owf, Passwords[0].Owf + LM_OWF_PASSWORD_SIZE, Passwords[1].Owf, Passwords[1].Owf + LM_OWF_PASSWORD_SIZE, Sid, &Secret); DPRINT(LOUD, ("Logging on to <%s><%s>\n", Domain, User)); // // Try logging on with the first password, if that fails // then try the second. // ntStatus = TftpLogin( Domain, User, Passwords[0].Owf, NetServerIpAddress, &TftpSecurityHandle); if (!NT_SUCCESS(ntStatus)) { DPRINT(LOUD, ("TftpLogin using password 2\n")); ntStatus = TftpLogin( Domain, User, Passwords[1].Owf, NetServerIpAddress, &TftpSecurityHandle); if (NT_SUCCESS(ntStatus)) { NetBootTftpUsedPassword2 = TRUE; } } } else { ntStatus = STATUS_OBJECT_PATH_NOT_FOUND; } arcStatus = BlCloseRawDisk(FileId); // // We are inside the if() for successfully opening the raw // disk, so we are not diskless. On these machines we must // fail the open at this point. // if (!NT_SUCCESS(ntStatus)) { DPRINT( ERROR, ("TftpLogin failed %lx\n", ntStatus) ); return EACCES; } } else { NetBootTryTftpSecurity = FALSE; // so we don't try to open it again } } #endif // defined(REMOTE_BOOT_SECURITY) DPRINT( LOUD, ("NetOpen: opening %s\n", OpenPath) ); oldBase = BlUsableBase; oldLimit = BlUsableLimit; BlUsableBase = BL_DRIVER_RANGE_LOW; BlUsableLimit = BL_DRIVER_RANGE_HIGH; // // If this request matches the cached file, then just copy that data. // if ((fileTableEntry->DeviceId == CachedFileDeviceId) && (strcmp(OpenPath, CachedFilePath) == 0) && (CachedFileData != NULL)) { ULONG basePage; arcStatus = BlAllocateAlignedDescriptor( LoaderFirmwareTemporary, 0, (CachedFileSize + PAGE_SIZE - 1) >> PAGE_SHIFT, 0, &basePage ); BlUsableBase = oldBase; BlUsableLimit = oldLimit; if ( arcStatus != ESUCCESS ) { fileTableEntry->Flags.Open = 0; // Free entry we didn't use return EROFS; } DPRINT( REAL_LOUD, ("NetOpen: Using cache for file %s\n", CachedFilePath) ); fileTableEntry->u.NetFileContext.InMemoryCopy = (PUCHAR)ULongToPtr( (basePage << PAGE_SHIFT) ); memcpy(fileTableEntry->u.NetFileContext.InMemoryCopy, CachedFileData, CachedFileSize); fileTableEntry->u.NetFileContext.FileSize = CachedFileSize; } else { request.RemoteFileName = OpenPath; request.ServerIpAddress = NetServerIpAddress; request.MemoryAddress = NULL; request.MaximumLength = 0; request.BytesTransferred = 0xbadf00d; request.Operation = TFTP_RRQ; request.MemoryType = LoaderFirmwareTemporary; #if defined(REMOTE_BOOT_SECURITY) request.SecurityHandle = TftpSecurityHandle; #endif // defined(REMOTE_BOOT_SECURITY) request.ShowProgress = FALSE; ntStatus = TftpGetPut( &request ); DPRINT( REAL_LOUD, ("NetOpen: TftpGetPut(get) status: %x, bytes: %x\n", ntStatus, request.BytesTransferred) ); BlUsableBase = oldBase; BlUsableLimit = oldLimit; if ( !NT_SUCCESS(ntStatus) ) { if ( request.MemoryAddress != NULL ) { DPRINT( REAL_LOUD, ("NetOpen: freeing memory at 0x%08x, %d bytes\n", request.MemoryAddress, request.MaximumLength) ); BlFreeDescriptor( (ULONG)((ULONG_PTR)request.MemoryAddress >> PAGE_SHIFT )); } fileTableEntry->Flags.Open = 0; // Free entry we didn't use if ( ntStatus == STATUS_INSUFFICIENT_RESOURCES ) { return ENOMEM; } return EROFS; } fileTableEntry->u.NetFileContext.FileSize = request.BytesTransferred; fileTableEntry->u.NetFileContext.InMemoryCopy = request.MemoryAddress; // // We always cache the last file that was actually read from // the network. // strcpy(CachedFilePath, OpenPath); CachedFileDeviceId = fileTableEntry->DeviceId; CachedFileSize = request.BytesTransferred; CachedFileData = request.MemoryAddress; } fileTableEntry->Position.QuadPart = 0; fileTableEntry->Flags.Read = 1; return ESUCCESS; } // NetOpen ARC_STATUS NetRead ( IN ULONG FileId, OUT VOID * FIRMWARE_PTR Buffer, IN ULONG Length, OUT ULONG * FIRMWARE_PTR Count ) { PBL_FILE_TABLE fileTableEntry; PNET_FILE_CONTEXT context; PUCHAR source; fileTableEntry = &BlFileTable[FileId]; context = &fileTableEntry->u.NetFileContext; { source = context->InMemoryCopy + fileTableEntry->Position.LowPart; if ( (fileTableEntry->Position.LowPart + Length) > context->FileSize ) { Length = context->FileSize - fileTableEntry->Position.LowPart; } RtlCopyMemory( Buffer, source, Length ); *Count = Length; fileTableEntry->Position.LowPart += Length; } DPRINT( REAL_LOUD, ("NetRead: id %d, length %d, count %d, new pos %x\n", FileId, Length, *Count, fileTableEntry->Position.LowPart) ); return ESUCCESS; } // NetRead ARC_STATUS NetGetReadStatus ( IN ULONG FileId ) { DPRINT( TRACE, ("NetGetReadStatus\n") ); return ESUCCESS; } // NetGetReadStatus ARC_STATUS NetSeek ( IN ULONG FileId, IN LARGE_INTEGER * FIRMWARE_PTR Offset, IN SEEK_MODE SeekMode ) { PBL_FILE_TABLE fileTableEntry; LARGE_INTEGER newPosition; //DPRINT( TRACE, ("NetSeek\n") ); fileTableEntry = &BlFileTable[FileId]; { if ( SeekMode == SeekAbsolute ) { newPosition = *Offset; } else if ( SeekMode == SeekRelative ) { newPosition.QuadPart = fileTableEntry->Position.QuadPart + Offset->QuadPart; } else { return EROFS; } DPRINT( REAL_LOUD, ("NetSeek: id %d, mode %d, offset %x, new pos %x\n", FileId, SeekMode, Offset->LowPart, newPosition.LowPart) ); if ( newPosition.QuadPart > fileTableEntry->u.NetFileContext.FileSize ) { return EROFS; } fileTableEntry->Position = newPosition; } return ESUCCESS; } // NetSeek ARC_STATUS NetWrite ( IN ULONG FileId, IN VOID * FIRMWARE_PTR Buffer, IN ULONG Length, OUT ULONG * FIRMWARE_PTR Count ) { DPRINT( TRACE, ("NetWrite\n") ); return EROFS; } // NetWrite ARC_STATUS NetGetFileInformation ( IN ULONG FileId, OUT FILE_INFORMATION * FIRMWARE_PTR Buffer ) { PBL_FILE_TABLE fileTableEntry; //DPRINT( TRACE, ("NetGetFileInformation\n") ); fileTableEntry = &BlFileTable[FileId]; { Buffer->EndingAddress.QuadPart = fileTableEntry->u.NetFileContext.FileSize; Buffer->CurrentPosition.QuadPart = fileTableEntry->Position.QuadPart; DPRINT( REAL_LOUD, ("NetGetFileInformation returning size %x, position %x\n", Buffer->EndingAddress.LowPart, Buffer->CurrentPosition.LowPart) ); return ESUCCESS; } } // NetGetFileInformation ARC_STATUS NetSetFileInformation ( IN ULONG FileId, IN ULONG AttributeFlags, IN ULONG AttributeMask ) { DPRINT( TRACE, ("NetSetFileInformation\n") ); return EROFS; } // NetSetFileInformation ARC_STATUS NetRename ( IN ULONG FileId, IN CHAR * FIRMWARE_PTR NewFileName ) { DPRINT( TRACE, ("NetRename\n") ); return EROFS; } // NetRename ARC_STATUS NetGetDirectoryEntry ( IN ULONG FileId, IN DIRECTORY_ENTRY * FIRMWARE_PTR DirEntry, IN ULONG NumberDir, OUT ULONG * FIRMWARE_PTR CountDir ) { DPRINT( TRACE, ("NetGetDirectoryEntry\n") ); return EROFS; } // NetGetDirectoryEntry