/*++ Copyright (c) 2000 Microsoft Corporation Module Name: asrbkup.c Abstract: This module contains the following ASR routines: AsrCreateStateFile{A|W} AsrAddSifEntry{A|W} AsrFreeContext Author: Guhan Suriyanarayanan (guhans) 27-May-2000 Environment: User-mode only. Notes: Naming conventions: _AsrpXXX private ASR Macros AsrpXXX private ASR routines AsrXXX Publically defined and documented routines Revision History: 27-May-2000 guhans Moved ASR-backup related routines from asr.c to asrbkup.c 01-Jan-2000 guhans Initial implementation for Asr routines in asr.c --*/ #include "setupp.h" #pragma hdrstop #include // DiskClassGuid #include // GPT partition type guids #include // ioctl_volume_query_failover_set #include // SetupDi routines #include // mountmgr ioctls #include // UuidToStringW, RpcStringFreeW #include // ASR public routines #define THIS_MODULE 'B' #include "asrpriv.h" // Private ASR definitions and routines // // -------- // constants local to this module. These are not accessed outside this file. // -------- // // // The Setup Key to find the system partition from // const WCHAR ASR_REGKEY_SETUP[] = L"SYSTEM\\SETUP"; const WCHAR ASR_REGVALUE_SYSTEM_PARTITION[] = L"SystemPartition"; // // The ASR registry entries. Currently, this is used to look // up the commands to run during an Asr backup, but we could // have other settings here later. // const WCHAR ASR_REGKEY_ASR_COMMANDS[] = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Asr\\Commands"; const WCHAR ASR_REGKEY_ASR[] = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Asr\\"; const WCHAR ASR_REGVALUE_TIMEOUT[] = L"ProcessTimeOut"; // // File to save the PnP information in. // const WCHAR ASR_DEFAULT_SIF_PATH[] = L"\\\\?\\%systemroot%\\repair\\asr.sif"; const WCHAR ASRPNP_DEFAULT_SIF_NAME[] = L"asrpnp.sif"; // // We only support x86, AMD64, and IA64 architectures. // const WCHAR ASR_PLATFORM_X86[] = L"x86"; const WCHAR ASR_PLATFORM_AMD64[] = L"AMD64"; const WCHAR ASR_PLATFORM_IA64[] = L"IA64"; // // This is the suffix that we add when launching the apps registered for ASR. // Remember to change the length if you're changing this. The length should // include space for 20 digits (max 64-bit int) + null + space at the beginning. // #define ASR_COMMANDLINE_SUFFIX_LEN 35 const WCHAR ASR_COMMANDLINE_SUFFIX[] = L" /context=%I64u"; // // Miscellaneous constants // const WCHAR ASR_DOS_DEVICES_PREFIX[] = L"\\DosDevices\\"; const WCHAR ASR_DEVICE_PATH_PREFIX[] = L"\\Device\\Harddisk"; // // Sections in asr.sif // const WCHAR ASR_SIF_VERSION_SECTION_NAME[] = L"[VERSION]"; const WCHAR ASR_SIF_SYSTEM_SECTION_NAME[] = L"[SYSTEMS]"; const WCHAR ASR_SIF_BUSES_SECTION_NAME[] = L"[BUSES]"; const WCHAR ASR_SIF_MBR_DISKS_SECTION_NAME[] = L"[DISKS.MBR]"; const WCHAR ASR_SIF_GPT_DISKS_SECTION_NAME[] = L"[DISKS.GPT]"; const WCHAR ASR_SIF_MBR_PARTITIONS_SECTION_NAME[] = L"[PARTITIONS.MBR]"; const WCHAR ASR_SIF_GPT_PARTITIONS_SECTION_NAME[] = L"[PARTITIONS.GPT]"; const WCHAR ASR_SIF_PROVIDER_PREFIX[] = L"Provider="; // wcslen("Provider=""\r\n\0") #define ASR_SIF_CCH_PROVIDER_STRING 14 // // While launching registered applications during an ASR backup, we // add two environment variables to the environment block for the // process being launched: the AsrContext and the critical volume // list. // #define ASR_CCH_ENVBLOCK_ASR_ENTRIES (32 + 1 + 28 + 2) const WCHAR ASR_ENVBLOCK_CONTEXT_ENTRY[] = L"_AsrContext=%I64u"; const WCHAR ASR_ENVBLOCK_CRITICAL_VOLUME_ENTRY[] = L"_AsrCriticalVolumeList="; // // Pre-defined flags designating the boot and system partitions // in the partitions section of asr.sif. Remember to change the // counter-parts in setupdd.sys if you change these! // const BYTE ASR_FLAGS_BOOT_PTN = 1; const BYTE ASR_FLAGS_SYSTEM_PTN = 2; // // For now, we only allow one system per sif file. If a sif // already exists at the location AsrCreateStateFile is called, // the existing sif is deleted. The asr.sif architecture does // allow for multiple systems per sif file, but // - I don't see any compelling reason to support this, and // - It would be a test nightmare // const BYTE ASR_SYSTEM_KEY = 1; // // _AsrpCheckTrue: primarily used with WriteFile calls. // #define _AsrpCheckTrue( Expression ) \ if (!Expression) { \ return FALSE; \ } // // -------- // constants used across asr modules. // -------- // const WCHAR ASR_SIF_SYSTEM_SECTION[] = L"SYSTEMS"; const WCHAR ASR_SIF_BUSES_SECTION[] = L"BUSES"; const WCHAR ASR_SIF_MBR_DISKS_SECTION[] = L"DISKS.MBR"; const WCHAR ASR_SIF_GPT_DISKS_SECTION[] = L"DISKS.GPT"; const WCHAR ASR_SIF_MBR_PARTITIONS_SECTION[] = L"PARTITIONS.MBR"; const WCHAR ASR_SIF_GPT_PARTITIONS_SECTION[] = L"PARTITIONS.GPT"; const WCHAR ASR_WSZ_VOLUME_PREFIX[] = L"\\??\\Volume"; const WCHAR ASR_WSZ_DEVICE_PATH_FORMAT[] = L"\\Device\\Harddisk%d\\Partition%d"; // // -------- // function prototypes // -------- // // // Function prototype for AsrCreatePnpStateFileW. // (linked into syssetup.dll from pnpsif.lib) // BOOL AsrCreatePnpStateFileW( IN PCWSTR lpFilePath ); // // -------- // private functions // -------- // BOOL AsrpGetMountPoints( IN PCWSTR DeviceName, IN CONST DWORD SizeDeviceName, OUT PMOUNTMGR_MOUNT_POINTS *pMountPointsOut ) /*++ Routine Description: Returns the current list of mount-points for DeviceName, by querying the mount manager. Arguments: DeviceName - The device name that the mount-point list is requested for. Typically, this is something of the form \Device\HarddiskX\PartitionY or \DosDevices\X: SizeDeviceName - The size, in bytes, of DeviceName. This includes the terminating null character. pMountPointsOut - Receives the output list of mount-points. The caller must free this memory by calling HeapFree for the current process heap. Return Values: TRUE, if everything went well. MountPointsOut contains the promised data. FALSE, if the mount manager returned an error. MountPoints is NULL. Call GetLastError() for more information. --*/ { PMOUNTMGR_MOUNT_POINT mountPointIn = NULL; PMOUNTMGR_MOUNT_POINTS mountPointsOut = NULL; MOUNTMGR_MOUNT_POINTS mountPointsTemp; DWORD mountPointsSize = 0; HANDLE mpHandle = NULL; HANDLE heapHandle = NULL; ULONG index = 0; LONG status = ERROR_SUCCESS; BOOL result = FALSE; memset(&mountPointsTemp, 0L, sizeof(MOUNTMGR_MOUNT_POINTS)); MYASSERT(pMountPointsOut); *pMountPointsOut = NULL; heapHandle = GetProcessHeap(); MYASSERT(heapHandle); mountPointIn = (PMOUNTMGR_MOUNT_POINT) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof (MOUNTMGR_MOUNT_POINT) + (SizeDeviceName - sizeof(WCHAR)) ); _AsrpErrExitCode((!mountPointIn), status, ERROR_NOT_ENOUGH_MEMORY); // // Try with a decently sized mountPoints out: if it isn't big // enough, we'll realloc as needed // mountPointsOut = (PMOUNTMGR_MOUNT_POINTS) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, (MAX_PATH + 1) * (sizeof(WCHAR)) ); _AsrpErrExitCode(!mountPointsOut, status, ERROR_NOT_ENOUGH_MEMORY); // // Get a handle to the mount manager // mpHandle = CreateFileW( MOUNTMGR_DOS_DEVICE_NAME, // lpFileName 0, // dwDesiredAccess FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode NULL, // lpSecurityAttributes OPEN_EXISTING, // dwCreationFlags FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes NULL // hTemplateFile ); _AsrpErrExitCode((!mpHandle || INVALID_HANDLE_VALUE == mpHandle), status, GetLastError() ); // // put the DeviceName right after struct mountPointIn // wcsncpy((PWSTR) (mountPointIn + 1), DeviceName, (SizeDeviceName / sizeof(WCHAR)) - 1 ); mountPointIn->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT); mountPointIn->DeviceNameLength = (USHORT)(SizeDeviceName - sizeof(WCHAR)); result = DeviceIoControl( mpHandle, IOCTL_MOUNTMGR_QUERY_POINTS, mountPointIn, sizeof(MOUNTMGR_MOUNT_POINT) + mountPointIn->DeviceNameLength, &mountPointsTemp, sizeof(MOUNTMGR_MOUNT_POINTS), &mountPointsSize, NULL ); while (!result) { status = GetLastError(); if (ERROR_MORE_DATA == status) { // // The buffer is not big enough, re-size and try again // status = ERROR_SUCCESS; _AsrpHeapFree(mountPointsOut); mountPointsOut = (PMOUNTMGR_MOUNT_POINTS) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, mountPointsTemp.Size ); _AsrpErrExitCode((!mountPointsOut), status, ERROR_NOT_ENOUGH_MEMORY); result = DeviceIoControl( mpHandle, IOCTL_MOUNTMGR_QUERY_POINTS, mountPointIn, sizeof(MOUNTMGR_MOUNT_POINT) + mountPointIn->DeviceNameLength, mountPointsOut, mountPointsTemp.Size, &mountPointsSize, NULL ); _AsrpErrExitCode((!mountPointsSize), status, GetLastError()); } else { // // If some other error occurred, EXIT. // result = TRUE; status = GetLastError(); // _AsrpErrExitCode(status, status, GetLastError()); } } EXIT: // // Free up locally allocated memory // _AsrpHeapFree(mountPointIn); if (ERROR_SUCCESS != status) { // // On failure, free up mountPointsOut as well // _AsrpHeapFree(mountPointsOut); } _AsrpCloseHandle(mpHandle); *pMountPointsOut = mountPointsOut; return (BOOL) (ERROR_SUCCESS == status); } BOOL AsrpGetMorePartitionInfo( IN PCWSTR DeviceName, IN CONST DWORD SizeDeviceName, IN CONST PASR_SYSTEM_INFO pSystemInfo OPTIONAL, OUT PWSTR pVolumeGuid, OUT USHORT* pPartitionFlags OPTIONAL, OUT UCHAR* pFileSystemType OPTIONAL, OUT LPDWORD pClusterSize OPTIONAL ) /*++ Routine Description: Gets additional information about the partition specified by DeviceName, including the volume guid (if any) for the volume that maps to the partition specified by DeviceName. If the partition is the current system or boot drive, pPartitionFlags and pFileSystemType are set appropriately. Arguments: DeviceName - A null terminated string containing the device path to the partition, typically of the form \Device\HarddiskX\PartitionY SizeDeviceName - Size, in bytes, of DeviceName, including \0 at the end pSystemInfo - The SYSTEM_INFO structure for the current system; used for finding out the current system partition. This is an optional parameter. If absent, pPartitionFlags will not have the SYSTEM_FLAG set, even if DeviceName is in fact the system partition. pVolumeGuid - Receives a null-terminated string containing the GUID for the volume on this partition. This is only relevant for basic disks, where volumes and partitions have a one-on-one relationship. This will be set to a blank null-terminated string if there is no volume (or multiple volumes) on this partition. *** Note that if ANY of three of the OPTIONAL parameters are not present, NONE of them will be filled with valid data. pPartitionFlags - If the current partition is a partition of interest, this receives the appropriate flags, IN ADDITION TO THE FLAGS ALREADY SET when the routine is called (i.e., caller should usually zero this out). Currently, the two flags of interest are: ASR_FLAGS_BOOT_PTN for the boot partition ASR_FLAGS_SYSTEM_PTN for (you guessed it) the system partition pFileSystemType - If (and ONLY if) the current partition is a partition of interest, this will contain a UCHAR to the file-system type of the partition. Currently, the three file-systems this recognises are: PARTITION_HUGE (FAT) PARTITION_FAT32 (FAT32) PARTITION_IFS (NTFS) pClusterSize - The file-system cluster size. Set to 0 if the information could not be obtained. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { PMOUNTMGR_MOUNT_POINTS mountPointsOut = NULL; HANDLE heapHandle = NULL; ULONG index = 0; LONG status = ERROR_SUCCESS; BOOL result = FALSE; BOOL volumeGuidSet = FALSE; // // set OUT variables to known values. // MYASSERT(pVolumeGuid); wcscpy(pVolumeGuid, L""); /* if (ARGUMENT_PRESENT(pPartitionFlags)) { *pPartitionFlags = 0; } */ if (ARGUMENT_PRESENT(pClusterSize)) { *pClusterSize = 0; } heapHandle = GetProcessHeap(); MYASSERT(heapHandle); // // Open the mount manager, and get a list of all the symbolic links // this partition // result = AsrpGetMountPoints(DeviceName, SizeDeviceName, &mountPointsOut); _AsrpErrExitCode((!result), status, GetLastError()); _AsrpErrExitCode((!mountPointsOut), status, ERROR_SUCCESS); // // Check if this is the system partition, by comparing the // device path with the one stored in the Setup key. // if (ARGUMENT_PRESENT(pSystemInfo) && ARGUMENT_PRESENT(pPartitionFlags)) { PWSTR deviceName = (PWSTR) ( ((LPBYTE) mountPointsOut) + mountPointsOut->MountPoints[index].DeviceNameOffset ); UINT sizeDeviceName = (UINT)(mountPointsOut->MountPoints[index].DeviceNameLength); if ((pSystemInfo->SystemPath) && (wcslen(pSystemInfo->SystemPath)==sizeDeviceName/sizeof(WCHAR)) && (!wcsncmp(pSystemInfo->SystemPath, deviceName, sizeDeviceName/sizeof(WCHAR))) ) { *pPartitionFlags |= ASR_FLAGS_SYSTEM_PTN; } } for (index = 0; index < mountPointsOut->NumberOfMountPoints; index++) { // // Go through the list of mount points returned, and find the one // that looks like an nt volume guid // PWSTR linkName = (PWSTR) (((LPBYTE) mountPointsOut) + mountPointsOut->MountPoints[index].SymbolicLinkNameOffset ); UINT sizeLinkName = (UINT)(mountPointsOut->MountPoints[index].SymbolicLinkNameLength); if ((!volumeGuidSet) && !wcsncmp(ASR_WSZ_VOLUME_PREFIX, linkName, wcslen(ASR_WSZ_VOLUME_PREFIX)) ) { wcsncpy(pVolumeGuid, linkName, sizeLinkName / sizeof(WCHAR)); volumeGuidSet = TRUE; // we got a GUID, no need to check again } else if ( ARGUMENT_PRESENT(pSystemInfo) && ARGUMENT_PRESENT(pPartitionFlags) ) { // // Also, if this link isn't a GUID, it might be a drive letter. // use the boot directory's drive letter to check if this // is the boot volume, and mark it if so. // if (!wcsncmp(ASR_DOS_DEVICES_PREFIX, linkName, wcslen(ASR_DOS_DEVICES_PREFIX)) ) { if ((pSystemInfo->BootDirectory) && (pSystemInfo->BootDirectory[0] == linkName[wcslen(ASR_DOS_DEVICES_PREFIX)]) ) { *pPartitionFlags |= ASR_FLAGS_BOOT_PTN; } } } } EXIT: // // If this is a partition of interest, we need to get the file-system // type as well // if (ARGUMENT_PRESENT(pFileSystemType) && ARGUMENT_PRESENT(pPartitionFlags) && ARGUMENT_PRESENT(pClusterSize) ) { if (*pPartitionFlags) { WCHAR fsName[20]; DWORD dwSectorsPerCluster = 0, dwBytesPerSector = 0, dwNumFreeClusters = 0, dwTotalNumClusters = 0; // // Convert the NT Volume-GUID (starts with \??\) to a DOS // Volume (begins with a \\?\, and ends with a back-slash), // since GetVolumeInformation needs it in this format. // pVolumeGuid[1] = L'\\'; wcscat(pVolumeGuid, L"\\"); memset(fsName, 0L, 20*sizeof(WCHAR)); result = GetVolumeInformationW(pVolumeGuid, NULL, 0L, NULL, NULL, NULL, fsName, 20); if (result) { if (!wcscmp(fsName, L"NTFS")) { *pFileSystemType = PARTITION_IFS; } else if (!wcscmp(fsName, L"FAT32")) { *pFileSystemType = PARTITION_FAT32; } else if (!wcscmp(fsName, L"FAT")) { *pFileSystemType = PARTITION_HUGE; } else { *pFileSystemType = 0; } } else { GetLastError(); // debug } result = GetDiskFreeSpace(pVolumeGuid, &dwSectorsPerCluster, &dwBytesPerSector, &dwNumFreeClusters, &dwTotalNumClusters ); if (result) { *pClusterSize = dwSectorsPerCluster * dwBytesPerSector; } else { GetLastError(); // debug } // // Convert the guid back to NT namespace, by changing \\?\ // to \??\ and removing the trailing slash. // pVolumeGuid[1] = L'?'; pVolumeGuid[wcslen(pVolumeGuid)-1] = L'\0'; } } // // Free up locally allocated data // _AsrpHeapFree(mountPointsOut); // // If we hit errors, make sure the VolumeGuid is a blank string. // if (status != ERROR_SUCCESS) { wcscpy(pVolumeGuid, L""); } return (BOOL) (status == ERROR_SUCCESS); } BOOL AsrpDetermineBuses( IN PASR_DISK_INFO pDiskList ) /*++ Routine Description: This attempts to group the disks based on which bus they are on. For SCSI disks, this is relatively easy, since it can be based on the location info (port). For other disks, we attempt to get the PnP parent node of the disks, and group all disks having the same parent. The groups are identified by the SifBusKey field of each disk structure-- i.e., all disks that have SifBusKey == 1 are on one bus, SifBusKey == 2 are on another bus, and so on. The SifBusKey values are guaranteed to be sequential, and not have any holes (i.e., For a system with "n" buses, the SifBusKey values will be 1,2,3,...,n). At the end SifBusKey is zero for disks which couldn't be grouped. Arguments: pDiskList - The ASR_DISK_INFO list of disks present on the current system. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { BOOL done = FALSE, newPass = TRUE; ULONG port = 0, sifBusKey = 0; DEVINST parent; STORAGE_BUS_TYPE busType = BusTypeUnknown; PASR_DISK_INFO pCurrentDisk = pDiskList; // // The first pass goes through and groups all the scsi disks together. // Note that this works for IDE too, since IDE disks respond to the // IOCTL_SCSI_GET_ADDRESS and appear to us to have valid location info. // do { sifBusKey++; pCurrentDisk = pDiskList; done = TRUE; newPass = TRUE; while (pCurrentDisk) { if ((BusTypeUnknown == pCurrentDisk->BusType) || (!pCurrentDisk->pScsiAddress)) { pCurrentDisk = pCurrentDisk->pNext; continue; } if (0 == pCurrentDisk->SifBusKey) { done = FALSE; if (newPass) { pCurrentDisk->SifBusKey = sifBusKey; port = pCurrentDisk->pScsiAddress->PortNumber; busType = pCurrentDisk->BusType; newPass = FALSE; } else { if ((pCurrentDisk->pScsiAddress->PortNumber == port) && (pCurrentDisk->BusType == busType)) { pCurrentDisk->SifBusKey = sifBusKey; } } } pCurrentDisk = pCurrentDisk->pNext; } } while (!done); // // By now, the only disks with SifBusKey is 0 are disks for which // pScsiAddress is NULL, ie (most-likely) non SCSI/IDE disks. Attempt // to group them on the basis of their parent dev node (which is usually // the bus). We may have to loop through multiple times again. // --sifBusKey; // compensate for the last pass above do { sifBusKey++; pCurrentDisk = pDiskList; done = TRUE; newPass = TRUE; while (pCurrentDisk) { if ((BusTypeUnknown == pCurrentDisk->BusType) || (!pCurrentDisk->pScsiAddress)) { if ((0 == pCurrentDisk->SifBusKey) && (pCurrentDisk->ParentDevInst)) { done = FALSE; if (newPass) { pCurrentDisk->SifBusKey = sifBusKey; parent = pCurrentDisk->ParentDevInst; newPass = FALSE; } else { if (pCurrentDisk->ParentDevInst == parent) { pCurrentDisk->SifBusKey = sifBusKey; } } } } pCurrentDisk = pCurrentDisk->pNext; } } while (!done); // // Disks that still have SifBusKey = 0 couldn't be grouped. Either the // BusType is unknown, or the parent node couldn't be found. // return TRUE; } BOOL AsrpGetDiskLayout( IN CONST HANDLE hDisk, IN CONST PASR_SYSTEM_INFO pSystemInfo, OUT PASR_DISK_INFO pCurrentDisk, IN BOOL AllDetails ) /*++ Routine Description: Fills in the fields of the pCurrentDisk structure with the relevant information about the disk represented by hDisk, by querying the system with the appropriate IOCTL's. Arguments: hDisk - handle to the disk of interest. pSystemInfo - The SYSTEM_INFO structure for the current system. pCurrentDisk - Receives the information about the disk represented by hDisk AllDetails - If FALSE, only the pDriveLayout information of pCurrentDisk is filled in. This is an optimisation that comes in handy when we're dealing with disks on a shared cluster bus. If TRUE, all the fields of pCurrentDisk are filled in. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { DWORD index = 0, status = ERROR_SUCCESS; DWORD dwBytesReturned = 0L, bufferLength = 0L; BOOL result = FALSE; PDISK_GEOMETRY diskGeometry = NULL; DWORD sizeDiskGeometry = 0L; PDRIVE_LAYOUT_INFORMATION_EX driveLayoutEx = NULL; DWORD sizeDriveLayoutEx = 0L; STORAGE_DEVICE_NUMBER deviceNumber; DWORD sizeDeviceNumber = 0L; PPARTITION_INFORMATION_EX partition0Ex = NULL; DWORD sizePartition0Ex = 0L; PASR_PTN_INFO pPartitionTable = NULL; DWORD sizePartitionTable = 0L; STORAGE_PROPERTY_QUERY propertyQuery; STORAGE_DEVICE_DESCRIPTOR *deviceDesc = NULL; STORAGE_BUS_TYPE busType = BusTypeUnknown; PSCSI_ADDRESS scsiAddress = NULL; HANDLE heapHandle = GetProcessHeap(); // For memory allocations MYASSERT(heapHandle); // It better not be NULL MYASSERT(pCurrentDisk); MYASSERT((hDisk) && (INVALID_HANDLE_VALUE != hDisk)); // // Initialize OUT variables to known values // pCurrentDisk->Style = PARTITION_STYLE_RAW; pCurrentDisk->pDriveLayoutEx = NULL; pCurrentDisk->sizeDriveLayoutEx = 0L; pCurrentDisk->pDiskGeometry = NULL; pCurrentDisk->sizeDiskGeometry = 0L; pCurrentDisk->pPartition0Ex = NULL; pCurrentDisk->sizePartition0Ex = 0L; pCurrentDisk->pScsiAddress = NULL; pCurrentDisk->BusType = BusTypeUnknown; pCurrentDisk->SifBusKey = 0L; SetLastError(ERROR_SUCCESS); // // Get the device number for this device. This should succeed even if // this is a clustered disk that this node doesn't own. // result = DeviceIoControl( hDisk, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &deviceNumber, sizeof(STORAGE_DEVICE_NUMBER), &sizeDeviceNumber, NULL ); _AsrpErrExitCode(!result, status, GetLastError()); pCurrentDisk->DeviceNumber = deviceNumber.DeviceNumber; // // The output buffer for IOCTL_DISK_GET_DRIVE_LAYOUT_EX consists of a // DRIVE_LAYOUT_INFORMATION_EX structure as a header, followed by an // array of PARTITION_INFORMATION_EX structures. // // We initially allocate enough space for the DRIVE_LAYOUT_INFORMATION_EX // struct, which contains a single PARTITION_INFORMATION_EX struct, and // 3 more PARTITION_INFORMATION_EX structs, since each (MBR) disk will // have a minimum of four partitions, even if they are not all in use. // If the disk contains more than four partitions, we'll increase the // buffer size as needed // bufferLength = sizeof(DRIVE_LAYOUT_INFORMATION_EX) + (sizeof(PARTITION_INFORMATION_EX) * 3); driveLayoutEx = (PDRIVE_LAYOUT_INFORMATION_EX) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, bufferLength ); _AsrpErrExitCode(!driveLayoutEx, status, ERROR_NOT_ENOUGH_MEMORY); result = FALSE; while (!result) { result = DeviceIoControl( hDisk, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0L, driveLayoutEx, bufferLength, &sizeDriveLayoutEx, NULL ); if (!result) { status = GetLastError(); _AsrpHeapFree(driveLayoutEx); // // If the buffer is of insufficient size, resize the buffer. // Note that get-drive-layout-ex could return error-insufficient- // buffer (instead of? in addition to? error-more-data) // if ((ERROR_MORE_DATA == status) || (ERROR_INSUFFICIENT_BUFFER == status) ) { status = ERROR_SUCCESS; bufferLength += sizeof(PARTITION_INFORMATION_EX) * 4; driveLayoutEx = (PDRIVE_LAYOUT_INFORMATION_EX) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, bufferLength ); _AsrpErrExitCode(!driveLayoutEx, status, ERROR_NOT_ENOUGH_MEMORY ); } else { // // some other error occurred, EXIT, and go to the next drive. // result = TRUE; status = ERROR_SUCCESS; } } else { if (!AllDetails) { // // If we don't want all the details for this disk, just exit // now. This is used in the case of clusters, where we don't // want to get all the details twice even if the current node // owns the disk // pCurrentDisk->pDriveLayoutEx = driveLayoutEx; pCurrentDisk->sizeDriveLayoutEx = sizeDriveLayoutEx; // // Jump to EXIT // _AsrpErrExitCode(TRUE, status, ERROR_SUCCESS); } // // The disk geometry: so that we can match the bytes-per-sector // value during restore. // diskGeometry = (PDISK_GEOMETRY) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof(DISK_GEOMETRY) ); _AsrpErrExitCode(!diskGeometry, status, ERROR_NOT_ENOUGH_MEMORY); result = DeviceIoControl( hDisk, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, diskGeometry, sizeof(DISK_GEOMETRY), &sizeDiskGeometry, NULL ); _AsrpErrExitCode(!result, status, ERROR_READ_FAULT); partition0Ex = (PPARTITION_INFORMATION_EX) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof(PARTITION_INFORMATION_EX) ); _AsrpErrExitCode(!partition0Ex, status, ERROR_NOT_ENOUGH_MEMORY); // // Information about partition 0 (the whole disk), to get the true // sector count of the disk // result = DeviceIoControl( hDisk, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0, partition0Ex, sizeof(PARTITION_INFORMATION_EX), &sizePartition0Ex, NULL ); _AsrpErrExitCode(!result, status, ERROR_READ_FAULT); // // Figure out the bus that this disk is on. This will only be // used to group the disks--all the disks on a bus will be // restored to the same bus if possible // propertyQuery.QueryType = PropertyStandardQuery; propertyQuery.PropertyId = StorageDeviceProperty; deviceDesc = (STORAGE_DEVICE_DESCRIPTOR *) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, ASR_BUFFER_SIZE ); _AsrpErrExitCode(!deviceDesc, status, ERROR_NOT_ENOUGH_MEMORY); result = DeviceIoControl( hDisk, IOCTL_STORAGE_QUERY_PROPERTY, &propertyQuery, sizeof(STORAGE_PROPERTY_QUERY), deviceDesc, ASR_BUFFER_SIZE, &dwBytesReturned, NULL ); if (result) { busType = deviceDesc->BusType; } _AsrpHeapFree(deviceDesc); scsiAddress = (PSCSI_ADDRESS) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof(SCSI_ADDRESS) ); _AsrpErrExitCode(!scsiAddress, status, ERROR_NOT_ENOUGH_MEMORY); result = DeviceIoControl( hDisk, IOCTL_SCSI_GET_ADDRESS, NULL, 0, scsiAddress, sizeof(SCSI_ADDRESS), &dwBytesReturned, NULL ); if (!result) { // Not fatal--expected for non SCSI/IDE disks _AsrpHeapFree(scsiAddress); result = TRUE; } } } if (driveLayoutEx) { PPARTITION_INFORMATION_EX currentPartitionEx = NULL; WCHAR devicePath[MAX_PATH + 1]; pCurrentDisk->Style = driveLayoutEx->PartitionStyle; sizePartitionTable = sizeof(ASR_PTN_INFO) * (driveLayoutEx->PartitionCount); pPartitionTable = (PASR_PTN_INFO) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizePartitionTable ); _AsrpErrExitCode(!pPartitionTable, status, ERROR_NOT_ENOUGH_MEMORY); for (index = 0; index < driveLayoutEx->PartitionCount; index++) { currentPartitionEx = &driveLayoutEx->PartitionEntry[index]; pPartitionTable[index].SlotIndex = index; if (currentPartitionEx->PartitionNumber) { swprintf(devicePath, ASR_WSZ_DEVICE_PATH_FORMAT, deviceNumber.DeviceNumber, currentPartitionEx->PartitionNumber ); pPartitionTable[index].PartitionFlags = 0; // // Check specially for the EFI system partition // if ((PARTITION_STYLE_GPT == driveLayoutEx->PartitionStyle) && IsEqualGUID(&(currentPartitionEx->Gpt.PartitionType), &(PARTITION_SYSTEM_GUID)) ) { pPartitionTable[index].PartitionFlags |= ASR_FLAGS_SYSTEM_PTN; } AsrpGetMorePartitionInfo( devicePath, (wcslen(devicePath)+1) * sizeof(WCHAR), // cb including \0 pSystemInfo, pPartitionTable[index].szVolumeGuid, &(pPartitionTable[index].PartitionFlags), &(pPartitionTable[index].FileSystemType), &(pPartitionTable[index].ClusterSize) ); // // Make sure that the file-system type for the EFI system // partition is set to FAT // if ((PARTITION_STYLE_GPT == driveLayoutEx->PartitionStyle) && IsEqualGUID(&(currentPartitionEx->Gpt.PartitionType), &(PARTITION_SYSTEM_GUID)) ) { pPartitionTable[index].FileSystemType = PARTITION_HUGE; } if (pPartitionTable[index].PartitionFlags) { pCurrentDisk->IsCritical = TRUE; } } } } pCurrentDisk->pDriveLayoutEx = driveLayoutEx; pCurrentDisk->sizeDriveLayoutEx = sizeDriveLayoutEx; pCurrentDisk->pDiskGeometry = diskGeometry; pCurrentDisk->sizeDiskGeometry = sizeDiskGeometry; pCurrentDisk->DeviceNumber = deviceNumber.DeviceNumber; pCurrentDisk->pPartition0Ex = partition0Ex; pCurrentDisk->sizePartition0Ex = sizePartition0Ex; pCurrentDisk->pScsiAddress = scsiAddress; pCurrentDisk->BusType = busType; pCurrentDisk->PartitionInfoTable = pPartitionTable; pCurrentDisk->sizePartitionInfoTable = sizePartitionTable; EXIT: // // Free up locally allocated memory on failure // if (status != ERROR_SUCCESS) { _AsrpHeapFree(driveLayoutEx); _AsrpHeapFree(diskGeometry); _AsrpHeapFree(partition0Ex); _AsrpHeapFree(scsiAddress); _AsrpHeapFree(pPartitionTable); } // // Make sure the last error is set if we are going to return FALSE // if ((ERROR_SUCCESS != status) && (ERROR_SUCCESS == GetLastError())) { SetLastError(status); } return (BOOL) (status == ERROR_SUCCESS); } BOOL AsrpGetSystemPath( IN PASR_SYSTEM_INFO pSystemInfo ) /*++ Routine Description: Gets the system partition DevicePath, and fills in the SystemPath field of the ASR_SYSTEM_INFO struct, by looking up the HKLM\Setup registry key. This key is updated at every boot with the path to the current system device. The path is of the form \Device\Harddisk0\Partition1 (basic disks) \Device\HarddiskDmVolumes\DgName\Volume1 (dynamic disks) Arguments: pSystemInfo - The SystemPath field of this struct will be filled with a pointer to the path to the current system device Return Value: If the function succeeds, the return value is a nonzero value. pSystemInfo->SystemPath is a pointer to a null-terminated string containing the path to the current system device. The caller is reponsible for freeing this memory with a call to HeapFree(GetProcessHeap(),...). If the function fails, the return value is zero. To get extended error information, call GetLastError(). pSystemInfo->SystemPath is set to NULL. --*/ { HKEY regKey = NULL; DWORD type = 0L; HANDLE heapHandle = NULL; DWORD status = ERROR_SUCCESS; PWSTR systemPartition = NULL; DWORD cbSystemPartition = 0L; heapHandle = GetProcessHeap(); MYASSERT(heapHandle); MYASSERT(pSystemInfo); if (!pSystemInfo) { SetLastError(ERROR_BAD_ENVIRONMENT); return FALSE; } pSystemInfo->SystemPath = NULL; // // Open the reg key to find the system partition // status = RegOpenKeyExW( HKEY_LOCAL_MACHINE, // hKey ASR_REGKEY_SETUP, // lpSubKey 0, // ulOptions--Reserved, must be 0 MAXIMUM_ALLOWED, // samDesired ®Key // phkResult ); _AsrpErrExitCode(status, status, ERROR_REGISTRY_IO_FAILED); // // Allocate a reasonably sized buffer for the system partition, to // start off with. If this isn't big enough, we'll re-allocate as // needed. // cbSystemPartition = (MAX_PATH + 1) * sizeof(WCHAR); systemPartition = HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, cbSystemPartition ); _AsrpErrExitCode((!systemPartition), status, ERROR_NOT_ENOUGH_MEMORY); // // Get the system partition device Name. This is of the form // \Device\Harddisk0\Partition1 (basic disks) // \Device\HarddiskDmVolumes\DgName\Volume1 (dynamic disks) // status = RegQueryValueExW( regKey, ASR_REGVALUE_SYSTEM_PARTITION, NULL, &type, (LPBYTE)systemPartition, &cbSystemPartition // \0 is included ); _AsrpErrExitCode((type != REG_SZ), status, ERROR_REGISTRY_IO_FAILED); while (ERROR_MORE_DATA == status) { // // Our buffer wasn't big enough, cbSystemPartition contains // the required size. // _AsrpHeapFree(systemPartition); systemPartition = HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, cbSystemPartition ); _AsrpErrExitCode((!systemPartition), status, ERROR_NOT_ENOUGH_MEMORY); status = RegQueryValueExW( regKey, ASR_REGVALUE_SYSTEM_PARTITION, NULL, &type, (LPBYTE)systemPartition, &cbSystemPartition // \0 is included ); } EXIT: if (regKey) { RegCloseKey(regKey); regKey = NULL; } if (ERROR_SUCCESS != status) { _AsrpHeapFree(systemPartition); return FALSE; } else { pSystemInfo->SystemPath = systemPartition; return TRUE; } } BOOL AsrpInitSystemInformation( IN OUT PASR_SYSTEM_INFO pSystemInfo, IN CONST BOOL bEnableAutoExtend ) /*++ Routine Description: Initialisation routine to allocate memory for various fields in the ASR_SYSTEM_INFO structure, and fill them in with the relevant information. Arguments: pSystemInfo - The struct to be filled in with info about the current system. Return Value: If the function succeeds, the return value is a nonzero value. The caller is responsible for freeing memory pointed to by the various pointers in the struct, using HeapFree(GetProcessHeap(),...). If the function fails, the return value is zero. To get extended error information, call GetLastError(). The caller is still responsible for checking the fields and releasing any non-NULL pointers using HeapFree(GetProcessHeap(),...). --*/ { DWORD cchBootDirectory = 0L, reqdSize = 0L; BOOL result = FALSE; HANDLE heapHandle = GetProcessHeap(); // // Initialise the structure to zeroes // memset(pSystemInfo, 0L, sizeof (ASR_SYSTEM_INFO)); // // The auto-extension feature // pSystemInfo->AutoExtendEnabled = bEnableAutoExtend; // // Get the machine name // pSystemInfo->sizeComputerName = MAX_COMPUTERNAME_LENGTH + 1; if (!GetComputerNameW(pSystemInfo->ComputerName, &(pSystemInfo->sizeComputerName) )) { // // GetComputerName sets LastError // return FALSE; } // // Get the Processor Architecture. We expect the process architecture // to be a either x86, amd64, or ia64, so if it doesn't fit in our buffer of // six characters, we don't support it anyway. // pSystemInfo->Platform = HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, 6*sizeof(WCHAR) ); if (!pSystemInfo->Platform) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } reqdSize = GetEnvironmentVariableW(L"PROCESSOR_ARCHITECTURE", pSystemInfo->Platform, 6 ); if (0 == reqdSize) { // // We couldn't find the PROCESSOR_ARCHITECTURE variable // SetLastError(ERROR_BAD_ENVIRONMENT); return FALSE; } if (reqdSize > 6) { // // The architecture didn't fit in our buffer // SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } // // Get the OS version // pSystemInfo->OsVersionEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); result = GetVersionEx((LPOSVERSIONINFO) (&(pSystemInfo->OsVersionEx))); if (!result) { // // GetVersionEx sets the LastError // return FALSE; } // // Get the boot directory // pSystemInfo->BootDirectory = HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, (MAX_PATH+1)*sizeof(WCHAR) ); if (!(pSystemInfo->BootDirectory)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } cchBootDirectory = GetWindowsDirectoryW(pSystemInfo->BootDirectory, MAX_PATH + 1 ); if (0 == cchBootDirectory) { // // GetWindowsDirectory sets LastError // return FALSE; } if (cchBootDirectory > ASR_SIF_ENTRY_MAX_CHARS - MAX_COMPUTERNAME_LENGTH - 26) { // // We can't write out sif lines that are more than // ASR_SIF_ENTRY_MAX_CHARS chars long // SetLastError(ERROR_BAD_ENVIRONMENT); return FALSE; } if (cchBootDirectory > MAX_PATH) { UINT cchNewSize = cchBootDirectory + 1; // // Our buffer wasn't big enough, free and re-alloc as needed // _AsrpHeapFree(pSystemInfo->BootDirectory); pSystemInfo->BootDirectory = HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, (cchNewSize + 1) * sizeof(WCHAR) ); if (!(pSystemInfo->BootDirectory)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } cchBootDirectory = GetWindowsDirectoryW(pSystemInfo->BootDirectory, MAX_PATH + 1 ); if (!cchBootDirectory) { // // GetWindowsDirectory sets LastError // return FALSE; } if (cchBootDirectory > cchNewSize) { SetLastError(ERROR_BAD_ENVIRONMENT); return FALSE; } } // // Get the system directory // if (!AsrpGetSystemPath(pSystemInfo)) { // // AsrpGetSystemPath sets LastError // return FALSE; } // // Get the time-zone information. We need to save and restore this since // GUI-mode Setup (ASR) will otherwise default to GMT, and the file-time // stamps on all the restored files will be off, since most back-up apps // assume that they're restoring in the same time-zone that they backed // up in and do nothing special to restore the time-zone first. // GetTimeZoneInformation(&(pSystemInfo->TimeZoneInformation)); return TRUE; } BOOL AsrpInitLayoutInformation( IN CONST PASR_SYSTEM_INFO pSystemInfo, IN OUT PASR_DISK_INFO pDiskList, OUT PULONG MaxDeviceNumber OPTIONAL, IN BOOL AllDetails ) /*++ Routine Description: Initialisation routine to fill in layout and other interesting information about the disks on the system. Arguments: pSystemInfo - the ASR_SYSTEM_INFO for the current system pDiskList - ASR_DISK_INFO list of disks on the current system, with the DevicePath field for each disk pointing to a null terminated path to the disk, that can be used to open a handle to the disk. The other fields of the structure are filled in by this routine, if the disk could be accessed and the appropriate info could be obtained. MaxDeviceNumber - Receives the max device number of all the disks on the system. This can be used as an optimisation to allocate memory for a table of disks based on the device number. This is an optional argument. AllDetails - If FALSE, only the pDriveLayout information is filled in for each disk. This is an optimisation that comes in handy when we're dealing with disks on a shared cluster bus. If TRUE, all the fields are filled in for each disk. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { BOOL result = FALSE; HANDLE hDisk = NULL; PASR_DISK_INFO currentDisk = pDiskList; if (ARGUMENT_PRESENT(MaxDeviceNumber)) { *MaxDeviceNumber = 0; } while (currentDisk) { // // Open the disk. If an error occurs, get the next // disk from the disk list and continue. // hDisk = CreateFileW( currentDisk->DevicePath, // lpFileName 0, // dwDesiredAccess FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode NULL, // lpSecurityAttributes OPEN_EXISTING, // dwCreationFlags FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes NULL // hTemplateFile ); if ((!hDisk) || (INVALID_HANDLE_VALUE == hDisk)) { // // We couldn't open the disk. If this is a critical disk, we'll // fail later in AsrpMarkCriticalDisks, so it's okay to ignore // this error for now. // currentDisk = currentDisk->pNext; continue; } // // Get the layout and other interesting info for this disk. // If this fails, we must abort. // result = AsrpGetDiskLayout(hDisk, pSystemInfo, currentDisk, AllDetails ); if (!result) { DWORD status = GetLastError(); _AsrpCloseHandle(hDisk); // this may change LastError. SetLastError(status); return FALSE; } _AsrpCloseHandle(hDisk); // // Set the max device number if needed // if (ARGUMENT_PRESENT(MaxDeviceNumber) && (currentDisk->DeviceNumber > *MaxDeviceNumber) ) { *MaxDeviceNumber = currentDisk->DeviceNumber; } // // Get the next drive from the drive list. // currentDisk = currentDisk->pNext; } return TRUE; } BOOL AsrpInitDiskInformation( OUT PASR_DISK_INFO *ppDiskList ) /*++ Routine Description: Initialisation routine to get a list of disks present on the system. This routine allocates a ASR_DISK_INFO struct for each disk on the machine, and fills in the DevicePath and ParentDevInst fields of each with a path to the disk. It is expected that the other fields will be filled in with a subsequent call to AsrpInitLayoutInformation(). Arguments: ppDiskList - Receives the location of the first ASR_DISK_INFO struct. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). ppDiskList may point to an incomplete list of disks on the system, and it is the caller's responsibility to free the memory allocated, if any, using HeapFree(GetProcessHeap(),...). --*/ { DWORD count = 0, status = ERROR_SUCCESS; HDEVINFO hdevInfo = NULL; BOOL result = FALSE; PASR_DISK_INFO pNewDisk = NULL; HANDLE heapHandle = NULL; PSP_DEVICE_INTERFACE_DETAIL_DATA_W pDiDetail = NULL; SP_DEVICE_INTERFACE_DATA devInterfaceData; DWORD sizeDiDetail = 0; SP_DEVINFO_DATA devInfoData; // // Initialise stuff to zeros // memset(&devInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA)); *ppDiskList = NULL; heapHandle = GetProcessHeap(); // used for HeapAlloc functions MYASSERT(heapHandle); // // Get a device interface set which includes all Disk devices // present on the machine. DiskClassGuid is a predefined GUID that // will return all disk-type device interfaces // hdevInfo = SetupDiGetClassDevsW( &DiskClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ); _AsrpErrExitCode( ((NULL == hdevInfo) || (INVALID_HANDLE_VALUE == hdevInfo)), status, ERROR_IO_DEVICE ); // // Iterate over all devices interfaces in the set // for (count = 0; ; count++) { // must set size first devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); // // Retrieve the device interface data for each device interface // result = SetupDiEnumDeviceInterfaces( hdevInfo, NULL, &DiskClassGuid, count, &devInterfaceData ); if (!result) { // // If we retrieved the last item, break // status = GetLastError(); if (ERROR_NO_MORE_ITEMS == status) { status = ERROR_SUCCESS; break; } else { // // Some other error occured, goto EXIT. We overwrite the // last error. // _AsrpErrExitCode(status, status, ERROR_IO_DEVICE); } } // // Get the required buffer-size for the device path // result = SetupDiGetDeviceInterfaceDetailW( hdevInfo, &devInterfaceData, NULL, 0, &sizeDiDetail, NULL ); if (!result) { status = GetLastError(); // // If a value other than "insufficient buffer" is returned, // an error occured // _AsrpErrExitCode((ERROR_INSUFFICIENT_BUFFER != status), status, ERROR_IO_DEVICE ); } else { // // The call should have failed since we're getting the // required buffer size. If it doesn't, and error occurred. // _AsrpErrExitCode(status, status, ERROR_IO_DEVICE); } // // Allocate memory for the buffer // pDiDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA_W) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeDiDetail ); _AsrpErrExitCode(!pDiDetail, status, ERROR_NOT_ENOUGH_MEMORY); // must set the struct's size member pDiDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); // // Finally, retrieve the device interface detail info // result = SetupDiGetDeviceInterfaceDetailW( hdevInfo, &devInterfaceData, pDiDetail, sizeDiDetail, NULL, &devInfoData ); _AsrpErrExitCode(!result, status, GetLastError()); // // Okay, now alloc a struct for this disk, and fill in the DevicePath // field with the Path from the interface detail. // pNewDisk = (PASR_DISK_INFO) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof(ASR_DISK_INFO) ); _AsrpErrExitCode(!pNewDisk, status, ERROR_NOT_ENOUGH_MEMORY); // // Insert at the head so this is O(1) and not O(n!) // pNewDisk->pNext = *ppDiskList; *ppDiskList = pNewDisk; pNewDisk->DevicePath = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof(WCHAR) * (wcslen(pDiDetail->DevicePath) + 1) ); _AsrpErrExitCode(!(pNewDisk->DevicePath), status, ERROR_NOT_ENOUGH_MEMORY ); wcscpy(pNewDisk->DevicePath, pDiDetail->DevicePath); // // Get the PnP parent of this disk, so we can use it for grouping // disks later based on the bus they are on. // CM_Get_Parent(&(pNewDisk->ParentDevInst), devInfoData.DevInst, 0 ); _AsrpHeapFree(pDiDetail); } EXIT: // // Free local mem allocs // _AsrpHeapFree(pDiDetail); if ((hdevInfo) && (INVALID_HANDLE_VALUE != hdevInfo)) { SetupDiDestroyDeviceInfoList(hdevInfo); hdevInfo = NULL; } return (BOOL) (status == ERROR_SUCCESS); } BOOL AsrpMarkCriticalDisks( IN PASR_DISK_INFO pDiskList, IN PCWSTR CriticalVolumeList, IN ULONG MaxDeviceNumber ) /*++ Routine Description: Sets the IsCritical flag of each of the critical disks on the system. A disk is deemed "critical" if it is part of part of the failover set for any of the critical volumes present on the system. Arguments: pDiskList - The list of disks on the current system. CriticalVolumeList - A multi-string containing a list of the volume GUID's of each of the critical volumes present on the system. The GUID's must be in the NT name-space, i.e., must be of the form: \??\Volume{GUID} MaxDeviceNumber - The highest storage device number of the disks present in the disk list, as determined by calling IOCTL_STORAGE_GET_DEVICE_NUMBER for each of them. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { PCWSTR volGuid = NULL; PASR_DISK_INFO currentDisk = NULL; PVOLUME_FAILOVER_SET failoverSet = NULL; DWORD index = 0, reqdSize=0, sizeFailoverSet = 0, status = ERROR_SUCCESS; BOOL result = TRUE, *criticalDiskTable = NULL; WCHAR devicePath[ASR_CCH_DEVICE_PATH_FORMAT + 1]; HANDLE heapHandle = NULL, hDevice = NULL; memset(devicePath, 0L, (ASR_CCH_DEVICE_PATH_FORMAT+1)*sizeof(WCHAR)); if (!CriticalVolumeList) { // // No critical volumes: // #ifdef PRERELEASE return TRUE; #else return FALSE; #endif } if (!pDiskList) { // // No disks on machine?! // MYASSERT(0 && L"DiskList is NULL"); return FALSE; } heapHandle = GetProcessHeap(); MYASSERT(heapHandle); // // criticalDiskTable is our table of BOOL values. // criticalDiskTable = (BOOL *) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeof (BOOL) * (MaxDeviceNumber + 1) ); _AsrpErrExitCode(!criticalDiskTable, status, ERROR_NOT_ENOUGH_MEMORY); // // Try with a reasonable sized buffer first--say 10 disks. We'll // realloc as needed if this isn't enough. // sizeFailoverSet = sizeof(VOLUME_FAILOVER_SET) + (10 * sizeof(ULONG)); failoverSet = (PVOLUME_FAILOVER_SET) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeFailoverSet ); _AsrpErrExitCode(!failoverSet, status, ERROR_NOT_ENOUGH_MEMORY); volGuid = CriticalVolumeList; while (*volGuid) { // // Convert the \??\ to \\?\ so that CreateFile can use it // wcsncpy(devicePath, volGuid, ASR_CCH_DEVICE_PATH_FORMAT); devicePath[1] = L'\\'; // // Get a handle so we can send the ioctl // hDevice = CreateFileW( devicePath, // lpFileName 0, // dwDesiredAccess FILE_SHARE_READ | FILE_SHARE_WRITE, // dwShareMode NULL, // lpSecurityAttributes OPEN_EXISTING, // dwCreationFlags 0, // dwFlagsAndAttributes NULL // hTemplateFile ); _AsrpErrExitCode(((!hDevice) || (INVALID_HANDLE_VALUE == hDevice)), status, GetLastError()); result = DeviceIoControl( hDevice, IOCTL_VOLUME_QUERY_FAILOVER_SET, NULL, 0, failoverSet, sizeFailoverSet, &reqdSize, NULL ); // // We're doing this in a while loop because if the disk configuration // changes in the small interval between when we get the reqd buffer // size and when we send the ioctl again with a buffer of the "reqd" // size, we may still end up with a buffer that isn't big enough. // while (!result) { status = GetLastError(); if (ERROR_MORE_DATA == status) { // // The buffer was too small, reallocate the reqd size. // status = ERROR_SUCCESS; sizeFailoverSet = (sizeof(VOLUME_FAILOVER_SET) + ((failoverSet->NumberOfDisks) * sizeof(ULONG))); _AsrpHeapFree(failoverSet); failoverSet = (PVOLUME_FAILOVER_SET) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, sizeFailoverSet ); _AsrpErrExitCode(!failoverSet, status, ERROR_NOT_ENOUGH_MEMORY ); result = DeviceIoControl( hDevice, IOCTL_VOLUME_QUERY_FAILOVER_SET, NULL, 0, failoverSet, sizeFailoverSet, &reqdSize, NULL ); } else { // // The IOCTL failed because of something else, this is // fatal since we can't find the critical disk list now. // _AsrpErrExitCode((TRUE), status, status); } } // // Mark the appropriate entries in our table // for (index = 0; index < failoverSet->NumberOfDisks; index++) { criticalDiskTable[failoverSet->DiskNumbers[index]] = 1; } _AsrpCloseHandle(hDevice); // // Repeat for next volumeguid in list // volGuid += (wcslen(CriticalVolumeList) + 1); } // // Now go through the list of disks, and mark the critical flags. // currentDisk = pDiskList; while (currentDisk) { if (currentDisk->IsClusterShared) { // // By definition, cluster shared disks cannot be critical. // currentDisk = currentDisk->pNext; continue; } currentDisk->IsCritical = (criticalDiskTable[currentDisk->DeviceNumber] ? TRUE : FALSE); // // Increment the entry, so that we can track how many critical volumes // reside on this disk, and--more importantly--ensure that all the // critical disks exist on the system (next loop below) // if (currentDisk->IsCritical) { ++(criticalDiskTable[currentDisk->DeviceNumber]); } currentDisk = currentDisk->pNext; } // // Finally, we want to make sure that we don't have any critical disks // in our table that we didn't find physical disks for. (I.e., make // sure that the system has no "missing" critical disks) // for (index = 0; index < MaxDeviceNumber; index++) { if (1 == criticalDiskTable[index]) { // // If the table still has "1" for the value, it was never // incremented in the while loop above, ie our diskList doesn't // have a disk corresponding to this. // _AsrpErrExitCode(TRUE, status, ERROR_DEV_NOT_EXIST); } } EXIT: _AsrpHeapFree(failoverSet); _AsrpHeapFree(criticalDiskTable); _AsrpCloseHandle(hDevice); return (BOOL)(ERROR_SUCCESS == status); } PASR_DISK_INFO AsrpFreeDiskInfo( PASR_DISK_INFO pCurrentDisk ) /*++ Routine Description: Helper function to free memory pointed to by various pointers in the ASR_DISK_INFO struct, and then free the struct itself. Arguments: pCurrentDisk - the struct to be freed Return Value: pCurrentDisk->Next, which is a pointer to the next disk in the list --*/ { HANDLE heapHandle = NULL; PASR_DISK_INFO pNext = NULL; heapHandle = GetProcessHeap(); MYASSERT(heapHandle); if (pCurrentDisk) { pNext = pCurrentDisk->pNext; // // If it's a packed struct, then we only need to free the struct // itself. If not, we need to free the memory the pointers point // to as well. // if (!pCurrentDisk->IsPacked) { _AsrpHeapFree(pCurrentDisk->DevicePath); _AsrpHeapFree(pCurrentDisk->pDriveLayoutEx); _AsrpHeapFree(pCurrentDisk->pDiskGeometry); _AsrpHeapFree(pCurrentDisk->pPartition0Ex); _AsrpHeapFree(pCurrentDisk->pScsiAddress); _AsrpHeapFree(pCurrentDisk->PartitionInfoTable); } _AsrpHeapFree(pCurrentDisk); } return pNext; } BOOL AsrpIsRemovableOrInaccesibleMedia( IN PASR_DISK_INFO pDisk ) /*++ Routine Description: Checks if the disk represented by pDisk should be removed from our list of disks that we'll store information on in the state file. Disks that should be removed include disks that are removable, or disks that we couldn't access. Arguments: pDisk - the disk structure to be checked Return Value: TRUE if the device is removable, or some key information about the disk is missing. Since code depends on the driveLayout being non-NULL, for instance, we shall just remove the disk from the list if we couldn't get it's drive layout. We shall therefore not backup information about any disk whose drive geo or layout we couldn't get, and not restore to any such disk. FALSE if the structure contains all the required information, and is not a removable device. --*/ { if ((NULL == pDisk->pDiskGeometry) || (NULL == pDisk->pDriveLayoutEx) || (NULL == pDisk->pPartition0Ex) || (FixedMedia != pDisk->pDiskGeometry->MediaType) ) { return TRUE; } return FALSE; } BOOL AsrpFreeNonFixedMedia( IN OUT PASR_DISK_INFO *ppDiskList ) /*++ Routine Description: Removes removable media, and disks that are inaccessible, from the list of disks passed in. Arguments: ppDiskList - a pointer to the address of the first disk in the list of all the disks present on the current system. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). Currently, this function always succeeds. --*/ { PASR_DISK_INFO prevDisk = NULL, currentDisk = *ppDiskList; while (currentDisk) { if (AsrpIsRemovableOrInaccesibleMedia(currentDisk)) { // // Disk is not Fixed, we should remove it from out list // if (NULL == prevDisk) { // this is the first disk in the list *ppDiskList = currentDisk->pNext; } else { prevDisk->pNext = currentDisk->pNext; } // // Free it and get a pointer to the next disk // currentDisk = AsrpFreeDiskInfo(currentDisk); } else { // // Disk is okay, move on to the next disk // prevDisk = currentDisk; currentDisk = currentDisk->pNext; } } return TRUE; } VOID AsrpFreeStateInformation( IN OUT PASR_DISK_INFO *ppDiskList OPTIONAL, IN OUT PASR_SYSTEM_INFO pSystemInfo OPTIONAL ) /*++ Routine Description: Frees the memory addressed by pointers in the ASR_DISK_INFO and ASR_SYSTEM_INFO structs. Frees the list of disks pointed to by the ASR_DISK_INFO struct. Arguments: ppDiskList - Pointer to the address of the first Disk in the DiskList being freed. The address is set to NULL after the list is freed, to prevent further unintended accesses to the freed object. pSystemInfo - A pointer to the ASR_SYSTEM_INFO struct, containing the pointers to be freed. Return Value: If the function succeeds, the return value is a nonzero value. *ppDiskList is set to NULL. If the function fails, the return value is zero. To get extended error information, call GetLastError(). Currently, this function always succeeds. --*/ { PASR_DISK_INFO pTempDisk = NULL; HANDLE heapHandle = GetProcessHeap(); MYASSERT(heapHandle); if (ARGUMENT_PRESENT(ppDiskList)) { pTempDisk = *ppDiskList; while (pTempDisk) { pTempDisk = AsrpFreeDiskInfo(pTempDisk); } *ppDiskList = NULL; } if (ARGUMENT_PRESENT(pSystemInfo)) { _AsrpHeapFree(pSystemInfo->SystemPath); _AsrpHeapFree(pSystemInfo->BootDirectory); } } VOID AsrpFreePartitionList( IN OUT PASR_PTN_INFO_LIST *ppPtnList OPTIONAL ) /*++ Routine Description: Frees the list of partitions, along with memory addressed by all the pointers in the list. Arguments: ppPtnList - Pointer to the address of the first partition in the list being freed. The address is set to NULL after the list is freed, to prevent further unintended accesses to the freed object. Return Value: If the function succeeds, the return value is a nonzero value. *ppPtnList is set to NULL. If the function fails, the return value is zero. To get extended error information, call GetLastError(). Currently, this function always succeeds. --*/ { DWORD index = 0, numberOfPartitions = 0; PASR_PTN_INFO_LIST pList = NULL; PASR_PTN_INFO pCurrent = NULL, pNext = NULL; HANDLE heapHandle = GetProcessHeap(); if (!ARGUMENT_PRESENT(ppPtnList) || !(*ppPtnList)) { return; } pList = *ppPtnList; numberOfPartitions = pList[0].numTotalPtns; for (index = 0; index < numberOfPartitions; index++) { pCurrent = pList[index].pOffsetHead; while (pCurrent) { // // Save a pointer to the next // pNext = pCurrent->pOffsetNext; // // No pointers in PASR_PTN_INFO, okay to free as-is. // _AsrpHeapFree(pCurrent); pCurrent = pNext; } } _AsrpHeapFree(pList); *ppPtnList = NULL; } BOOL AsrpWriteVersionSection( IN CONST HANDLE SifHandle, IN PCWSTR Provider OPTIONAL ) /*++ Routine Description: Creates the VERSION section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. Provider - Pointer to a null-terminated string containing the name of the application creating the asr.sif. The length of this string must not exceed (ASR_SIF_ENTRY_MAX_CHARS - ASR_SIF_CCH_PROVIDER_STRING) characters. This is an optional argument. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; DWORD size; // // Write out the section name // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_VERSION_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // Section Entries // wcscpy(infstring, L"Signature=\"$Windows NT$\"\r\n"); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); wcscpy(infstring, L"ASR-Version=\"1.0\"\r\n"); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); if (ARGUMENT_PRESENT(Provider)) { if (wcslen(Provider) > (ASR_SIF_ENTRY_MAX_CHARS - ASR_SIF_CCH_PROVIDER_STRING) ) { // // This string is too long to fit into one line in asr.sif // SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } swprintf(infstring, L"%ws\"%.*ws\"\r\n", ASR_SIF_PROVIDER_PREFIX, (ASR_SIF_ENTRY_MAX_CHARS - ASR_SIF_CCH_PROVIDER_STRING), Provider ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); } return TRUE; } BOOL AsrpWriteSystemsSection( IN CONST HANDLE SifHandle, IN CONST PASR_SYSTEM_INFO pSystemInfo ) /*++ Routine Description: Creates the SYSTEMS section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. pSystemInfo - Pointer to information about the current system. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; DWORD size = 0, SKU = 0; if ((!pSystemInfo) || (!pSystemInfo->BootDirectory)) { // // We need a boot directory // SetLastError(ERROR_BAD_ENVIRONMENT); return FALSE; } // // Write out the section name // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_SYSTEM_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); SKU = (DWORD) (pSystemInfo->OsVersionEx.wProductType); SKU = SKU << 16; // shift the ProductType left 2 bytes SKU = SKU | (DWORD) (pSystemInfo->OsVersionEx.wSuiteMask); // // Create the section entry, and write it out to file. // swprintf(infstring, L"1=\"%ws\",\"%ws\",\"%d.%d\",\"%ws\",%d,0x%08x,\"%ld %ld %ld %hd-%hd-%hd-%hd %hd:%02hd:%02hd.%hd %hd-%hd-%hd-%hd %hd:%02hd:%02hd.%hd\",\"%ws\",\"%ws\"\r\n", pSystemInfo->ComputerName, pSystemInfo->Platform, pSystemInfo->OsVersionEx.dwMajorVersion, pSystemInfo->OsVersionEx.dwMinorVersion, pSystemInfo->BootDirectory, ((pSystemInfo->AutoExtendEnabled) ? 1 : 0), // Product SKU SKU, // Time-zone stuff pSystemInfo->TimeZoneInformation.Bias, pSystemInfo->TimeZoneInformation.StandardBias, pSystemInfo->TimeZoneInformation.DaylightBias, pSystemInfo->TimeZoneInformation.StandardDate.wYear, pSystemInfo->TimeZoneInformation.StandardDate.wMonth, pSystemInfo->TimeZoneInformation.StandardDate.wDayOfWeek, pSystemInfo->TimeZoneInformation.StandardDate.wDay, pSystemInfo->TimeZoneInformation.StandardDate.wHour, pSystemInfo->TimeZoneInformation.StandardDate.wMinute, pSystemInfo->TimeZoneInformation.StandardDate.wSecond, pSystemInfo->TimeZoneInformation.StandardDate.wMilliseconds, pSystemInfo->TimeZoneInformation.DaylightDate.wYear, pSystemInfo->TimeZoneInformation.DaylightDate.wMonth, pSystemInfo->TimeZoneInformation.DaylightDate.wDayOfWeek, pSystemInfo->TimeZoneInformation.DaylightDate.wDay, pSystemInfo->TimeZoneInformation.DaylightDate.wHour, pSystemInfo->TimeZoneInformation.DaylightDate.wMinute, pSystemInfo->TimeZoneInformation.DaylightDate.wSecond, pSystemInfo->TimeZoneInformation.DaylightDate.wMilliseconds, pSystemInfo->TimeZoneInformation.StandardName, pSystemInfo->TimeZoneInformation.DaylightName ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); return TRUE; } BOOL AsrpWriteBusesSection( IN CONST HANDLE SifHandle, IN CONST PASR_DISK_INFO pDiskList ) /*++ Routine Description: Creates the BUSES section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. pDiskList - List of disks present on the current system. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { DWORD size = 0, busKey = 1; BOOL done = FALSE; WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; PASR_DISK_INFO pCurrentDisk = NULL; // // Write out the section name // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_BUSES_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // Create the list of buses. This routine fills in the SifBusKey field // for each disk. // AsrpDetermineBuses(pDiskList); // // Go through the list of disks now, and add one entry in asr.sif for each // bus present on the system (i.e., each unique SifBusKey value). Note // that we won't care about disks for which we couldn't get any bus info-- // SifBusKey is 0 for such disks, and we start here from SifBusKey == 1. // // Also, we assume that SifBusKey values have no holes. // while (!done) { done = TRUE; // assume that we've been through all the buses. // // Start from the beginning of the list // pCurrentDisk = pDiskList; while (pCurrentDisk) { if (pCurrentDisk->SifBusKey > busKey) { // // There are SifBusKeys we haven't covered yet. // done = FALSE; } if (pCurrentDisk->SifBusKey == busKey) { // // This is the SifBusKey we're looking for, so lets write // out the bus type to file. // swprintf(infstring, L"%lu=%d,%lu\r\n", busKey, ASR_SYSTEM_KEY, pCurrentDisk->BusType ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // We've already covered this SifBusKey, lets move on to the // next. // ++busKey; } pCurrentDisk = pCurrentDisk->pNext; } } return TRUE; } BOOL AsrpWriteMbrDisksSection( IN CONST HANDLE SifHandle, // handle to the state file IN CONST PASR_DISK_INFO pDiskList ) /*++ Routine Description: Creates the DISKS.MBR section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. pDiskList - List of disks present on the current system. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { DWORD size = 0, diskKey = 1; WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; PASR_DISK_INFO pCurrentDisk = pDiskList; // // Write out the section name: [DISKS.MBR] // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_MBR_DISKS_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // Go through the list of disks, and write one entry for each MBR disk // on the list. // while (pCurrentDisk) { if (PARTITION_STYLE_MBR != pCurrentDisk->pDriveLayoutEx->PartitionStyle ) { // // Skip non-MBR (i.e., GPT) disks. // pCurrentDisk = pCurrentDisk->pNext; continue; } pCurrentDisk->SifDiskKey = diskKey; swprintf(infstring, L"%lu=%d,%lu,%lu,0x%08x,%lu,%lu,%lu,%I64u\r\n", diskKey, ASR_SYSTEM_KEY, pCurrentDisk->SifBusKey, pCurrentDisk->IsCritical, pCurrentDisk->pDriveLayoutEx->Mbr.Signature, pCurrentDisk->pDiskGeometry->BytesPerSector, pCurrentDisk->pDiskGeometry->SectorsPerTrack, pCurrentDisk->pDiskGeometry->TracksPerCylinder, (ULONG64)(pCurrentDisk->pPartition0Ex->PartitionLength.QuadPart / pCurrentDisk->pDiskGeometry->BytesPerSector) ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); ++diskKey; pCurrentDisk = pCurrentDisk->pNext; } return TRUE; } BOOL AsrpWriteGptDisksSection( IN CONST HANDLE SifHandle, // handle to the state file IN CONST PASR_DISK_INFO pDiskList ) /*++ Routine Description: Creates the DISKS.GPT section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. pDiskList - List of disks present on the current system. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { DWORD size = 0, diskKey = 1; PWSTR lpGuidString = NULL; RPC_STATUS rpcStatus = RPC_S_OK; PASR_DISK_INFO pCurrentDisk = pDiskList; WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; // // Write out the section name: [DISKS.GPT] // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_GPT_DISKS_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // Go through the list of disks, and write one entry for each GPT disk // on the list. // while (pCurrentDisk) { if (PARTITION_STYLE_GPT != pCurrentDisk->pDriveLayoutEx->PartitionStyle ) { // // Skip non-GPT (i.e., MBR) disks. // pCurrentDisk = pCurrentDisk->pNext; continue; } // // Convert the DiskId to a printable string // rpcStatus = UuidToStringW( &pCurrentDisk->pDriveLayoutEx->Gpt.DiskId, &lpGuidString ); if (rpcStatus != RPC_S_OK) { if (lpGuidString) { RpcStringFreeW(&lpGuidString); } // // The only error from UuidToStringW is RPC_S_OUT_OF_MEMORY // SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } pCurrentDisk->SifDiskKey = diskKey; swprintf(infstring, L"%lu=%d,%lu,%lu,%ws%ws%ws,%lu,%lu,%lu,%lu,%I64u\r\n", diskKey, ASR_SYSTEM_KEY, pCurrentDisk->SifBusKey, pCurrentDisk->IsCritical, (lpGuidString ? L"\"" : L""), (lpGuidString ? lpGuidString : L""), (lpGuidString ? L"\"" : L""), pCurrentDisk->pDriveLayoutEx->Gpt.MaxPartitionCount, pCurrentDisk->pDiskGeometry->BytesPerSector, pCurrentDisk->pDiskGeometry->SectorsPerTrack, pCurrentDisk->pDiskGeometry->TracksPerCylinder, (ULONG64) (pCurrentDisk->pPartition0Ex->PartitionLength.QuadPart / pCurrentDisk->pDiskGeometry->BytesPerSector) ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); if (lpGuidString) { RpcStringFreeW(&lpGuidString); lpGuidString = NULL; } ++diskKey; pCurrentDisk = pCurrentDisk->pNext; } return TRUE; } BOOL AsrpWriteMbrPartitionsSection( IN CONST HANDLE SifHandle, // handle to the state file IN CONST PASR_DISK_INFO pDiskList, IN CONST PASR_SYSTEM_INFO pSystemInfo ) /*++ Routine Description: Creates the PARTITIONS.MBR section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. pDiskList - List of disks present on the current system. pSystemInfo - Info about the current system, used to determine the current boot and system partitions (and mark them appropriately in asr.sif) Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { DWORD size = 0, index = 0, partitionKey = 1; UCHAR fsType = 0; PWSTR volumeGuid = NULL; BOOL writeVolumeGuid = FALSE; PASR_DISK_INFO pCurrentDisk = pDiskList; WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; PPARTITION_INFORMATION_EX currentPartitionEx = NULL; // // Write out the section name: [PARTITIONS.MBR] // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_MBR_PARTITIONS_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // Go through the list of disks, and write one entry for each partition on // each of the MBR disks on the list. // while (pCurrentDisk) { if (pCurrentDisk->pDriveLayoutEx) { if (PARTITION_STYLE_MBR != pCurrentDisk->pDriveLayoutEx->PartitionStyle ) { // // Skip non-MBR (i.e., GPT) disks // pCurrentDisk = pCurrentDisk->pNext; continue; } // // Enumerate partitions on the disk. We expect to find only // MBR partitions. // for (index =0; index < pCurrentDisk->pDriveLayoutEx->PartitionCount; index++ ) { currentPartitionEx = &pCurrentDisk->pDriveLayoutEx->PartitionEntry[index]; MYASSERT(currentPartitionEx->PartitionStyle == PARTITION_STYLE_MBR); if (currentPartitionEx->Mbr.PartitionType == 0) { // // Empty partition table entry. // continue; } fsType = pCurrentDisk->PartitionInfoTable[index].FileSystemType; volumeGuid = pCurrentDisk->PartitionInfoTable[index].szVolumeGuid; // // We only want to write out the Volume GUID for basic // (recognized) partitions/volumes, since it does not make // sense in the context of LDM or other unknown partition // types which would need special handling from their // respective recovery agents such as asr_ldm in GUI-mode // Setup. // writeVolumeGuid = (wcslen(volumeGuid) > 0) && IsRecognizedPartition(currentPartitionEx->Mbr.PartitionType); // // Create the entry and write it to file. // swprintf( infstring, L"%d=%d,%d,%lu,%ws%ws%ws,0x%02x,0x%02x,0x%02x,%I64u,%I64u,0x%x\r\n", partitionKey, pCurrentDisk->SifDiskKey, index, pCurrentDisk->PartitionInfoTable[index].PartitionFlags, (writeVolumeGuid ? L"\"" : L""), (writeVolumeGuid ? volumeGuid : L""), (writeVolumeGuid ? L"\"" : L""), (currentPartitionEx->Mbr.BootIndicator)?0x80:0, currentPartitionEx->Mbr.PartitionType, ((fsType) ? fsType : currentPartitionEx->Mbr.PartitionType), (ULONG64) ((currentPartitionEx->StartingOffset.QuadPart)/ (pCurrentDisk->pDiskGeometry->BytesPerSector)), (ULONG64) ((currentPartitionEx->PartitionLength.QuadPart)/ (pCurrentDisk->pDiskGeometry->BytesPerSector)), pCurrentDisk->PartitionInfoTable[index].ClusterSize ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); ++partitionKey; } } pCurrentDisk = pCurrentDisk->pNext; } return TRUE; } BOOL AsrpWriteGptPartitionsSection( IN CONST HANDLE SifHandle, IN CONST PASR_DISK_INFO pDiskList, IN CONST PASR_SYSTEM_INFO pSystemInfo ) /*++ Routine Description: Creates the PARTITIONS.GPT section of the ASR state file, and writes out the entries in that section to file. Arguments: SifHandle - Handle to asr.sif, the ASR state file. pDiskList - List of disks present on the current system. pSystemInfo - Info about the current system, used to determine the current boot and system partitions (and mark them appropriately in asr.sif) Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { DWORD size = 0, index = 0, partitionKey = 1; UCHAR fsType = 0; PWSTR volumeGuid = NULL, partitionId = NULL, partitionType = NULL; BOOL writeVolumeGuid = FALSE; RPC_STATUS rpcStatus = RPC_S_OK; PASR_DISK_INFO pCurrentDisk = pDiskList; WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; PPARTITION_INFORMATION_EX currentPartitionEx = NULL; // // Write out the section name: [PARTITIONS.GPT] // swprintf(infstring, L"\r\n%ws\r\n", ASR_SIF_GPT_PARTITIONS_SECTION_NAME); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); // // Go through the list of disks, and write one entry for each partition on // each of the GPT disks on the list. // while (pCurrentDisk) { if (pCurrentDisk->pDriveLayoutEx) { if (PARTITION_STYLE_GPT != pCurrentDisk->pDriveLayoutEx->PartitionStyle ) { // // Skip non-GPT (i.e., MBR) disks. // pCurrentDisk = pCurrentDisk->pNext; continue; } // // Enumerate partitions on the disk. We expect to find only // GPT partitions. // for (index =0; index < pCurrentDisk->pDriveLayoutEx->PartitionCount; index++) { currentPartitionEx = &pCurrentDisk->pDriveLayoutEx->PartitionEntry[index]; MYASSERT(currentPartitionEx->PartitionStyle == PARTITION_STYLE_GPT); // // Convert the Guids to printable strings // rpcStatus = UuidToStringW( ¤tPartitionEx->Gpt.PartitionType, &partitionType ); if (rpcStatus != RPC_S_OK) { if (partitionType) { RpcStringFreeW(&partitionType); partitionType = NULL; } // // The only error from UuidToString is RPC_S_OUT_OF_MEMORY // SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } rpcStatus = UuidToStringW( ¤tPartitionEx->Gpt.PartitionId, &partitionId ); if (rpcStatus != RPC_S_OK) { if (partitionType) { RpcStringFreeW(&partitionType); partitionType = NULL; } if (partitionId) { RpcStringFreeW(&partitionId); partitionId = NULL; } // // The only error from UuidToString is RPC_S_OUT_OF_MEMORY // SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } fsType = pCurrentDisk->PartitionInfoTable[index].FileSystemType; volumeGuid = pCurrentDisk->PartitionInfoTable[index].szVolumeGuid; // // We only want to write out the Volume GUID for basic // (recognized) partitions/volumes, since it does not make // sense in the context of LDM or other unknown partition // types which would need special handling from their // respective recovery agents such as asr_ldm in GUI-mode // Setup. // writeVolumeGuid = (wcslen(volumeGuid) > 0) && IsEqualGUID(&(partitionType), &(PARTITION_BASIC_DATA_GUID)); // // Create the entry and write it to file. // swprintf( infstring, L"%d=%d,%d,%d,%ws%ws%ws,%ws%ws%ws,%ws%ws%ws,0x%I64x,%ws%ws%ws,0x%02x,%I64u,%I64u,0x%x\r\n", partitionKey, pCurrentDisk->SifDiskKey, index, //slot-index pCurrentDisk->PartitionInfoTable[index].PartitionFlags, (writeVolumeGuid ? L"\"" : L""), (writeVolumeGuid ? volumeGuid : L""), (writeVolumeGuid ? L"\"" : L""), (partitionType ? L"\"" : L""), (partitionType ? partitionType : L""), (partitionType ? L"\"" : L""), (partitionId ? L"\"" : L""), (partitionId ? partitionId : L""), (partitionId ? L"\"" : L""), currentPartitionEx->Gpt.Attributes, (currentPartitionEx->Gpt.Name ? L"\"" : L""), (currentPartitionEx->Gpt.Name ? currentPartitionEx->Gpt.Name : L""), (currentPartitionEx->Gpt.Name ? L"\"" : L""), // // ISSUE-2000/04/12-guhans: GetVolumeInformation does not // work on GPT and fstype is always zero // fsType, (ULONG64) ((currentPartitionEx->StartingOffset.QuadPart)/ (pCurrentDisk->pDiskGeometry->BytesPerSector)), (ULONG64) ((currentPartitionEx->PartitionLength.QuadPart)/ (pCurrentDisk->pDiskGeometry->BytesPerSector)), pCurrentDisk->PartitionInfoTable[index].ClusterSize ); _AsrpCheckTrue(WriteFile(SifHandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL)); if (partitionType) { RpcStringFreeW(&partitionType); partitionType = NULL; } if (partitionId) { RpcStringFreeW(&partitionId); partitionId = NULL; } ++partitionKey; } } pCurrentDisk = pCurrentDisk->pNext; } return TRUE; } BOOL AsrpCreateEnvironmentBlock( IN PCWSTR CriticalVolumeList, IN HANDLE SifHandle, OUT PWSTR *NewBlock ) /*++ Routine Description: Creates a new environment block that is passed in to apps launched as part of an ASR backup. This routine retrieves the current process's environment block, adds the ASR environment variables to it, and creates a multi-sz suitable for being passed in as the lpEnvironment parameter of CreateProcess. Arguments: CriticalVolumeList - A multi-string containing a list of the volume GUID's of each of the critical volumes present on the system. The GUID's must be in the NT name-space, i.e., must be of the form: \??\Volume{GUID} This multi-sz is used to create the semi-colon separated list of volumes in the "_AsrCriticalVolumeList" variable in NewBlock. SifHandle - A (duplicate) handle to asr.sif, the ASR state file. This is used in creating the "_AsrContext" variable in NewBlock. NewBlock - Receives the new environment block. In addition to all the environment variables in the current process environment block, this block contains two additional "ASR" variables: _AsrContext= _AsrCriticalVolumeList=;;...; The caller is responsible for freeing this block when it is no longer needed, using HeapFree(GetProcessHeap(),...). Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). (*NewBlock) is set to NULL. --*/ { PCWSTR lpTemp = CriticalVolumeList; PWSTR lpCurrentEnvStrings = NULL; DWORD cchContextEntry = 0, cchEnvBlock = 0, cbEnvBlock = 0, cbCurrentProcessEnvBlock = 0, status = ERROR_SUCCESS; HANDLE heapHandle = GetProcessHeap(); MYASSERT(NewBlock); // // Find out how much space the environment block will need // // // For _AsrContext=1234 and _AsrCriticalVolumes="..." entries // lpTemp = CriticalVolumeList; if (CriticalVolumeList) { while (*lpTemp) { lpTemp += (wcslen(lpTemp) + 1); } } cbEnvBlock = (DWORD) ((lpTemp - CriticalVolumeList + 1) * sizeof(WCHAR)); cbEnvBlock += ASR_CCH_ENVBLOCK_ASR_ENTRIES * sizeof(WCHAR); // // For all the current environment strings // lpCurrentEnvStrings = GetEnvironmentStringsW(); lpTemp = lpCurrentEnvStrings; if (lpCurrentEnvStrings ) { while (*lpTemp) { lpTemp += (wcslen(lpTemp) + 1); } } cbCurrentProcessEnvBlock = (DWORD) ((lpTemp - lpCurrentEnvStrings + 1) * sizeof(WCHAR)); cbEnvBlock += cbCurrentProcessEnvBlock; // // And allocate the space // *NewBlock = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, cbEnvBlock ); _AsrpErrExitCode(!(*NewBlock), status, ERROR_NOT_ENOUGH_MEMORY); // // First, add the AsrContext=1234 entry in the environment block // swprintf( (*NewBlock), ASR_ENVBLOCK_CONTEXT_ENTRY, (ULONG64) (SifHandle) ); // // Keep track of where this entry ends, so we can add a NULL at this // index later. // cchContextEntry = wcslen((*NewBlock)); wcscat((*NewBlock), L" "); // this character will be replaced by a NULL later // // Append each critical volume GUID, separated by a semi-colon. // wcscat((*NewBlock), ASR_ENVBLOCK_CRITICAL_VOLUME_ENTRY); if (CriticalVolumeList) { lpTemp = CriticalVolumeList; while (*lpTemp) { wcscat((*NewBlock), lpTemp); wcscat((*NewBlock), L";"); lpTemp += (wcslen(lpTemp) + 1); } } else { wcscat((*NewBlock), L";"); } // // Mark the end with two NULL's // cchEnvBlock = wcslen(*NewBlock) - 1; // (*NewBlock)[cchEnvBlock - 1] = L'"'; (*NewBlock)[cchEnvBlock] = L'\0'; // // Separate the two entries with a NULL // (*NewBlock)[cchContextEntry] = L'\0'; // // Copy over the current environment strings // RtlCopyMemory(&(*NewBlock)[cchEnvBlock + 1], lpCurrentEnvStrings, cbCurrentProcessEnvBlock ); EXIT: if (lpCurrentEnvStrings) { FreeEnvironmentStringsW(lpCurrentEnvStrings); lpCurrentEnvStrings = NULL; } if (ERROR_SUCCESS != status) { _AsrpHeapFree((*NewBlock)); } return (BOOL) (ERROR_SUCCESS == status); } BOOL AsrpLaunchRegisteredCommands( IN HANDLE SifHandle, IN PCWSTR CriticalVolumeList ) /*++ Routine Description: This launches apps that have registered to be part of an ASR-backup. The commands are read from the following ASR-Commands key: "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Asr\\Commands" This key contains a REG_EXPAND_SZ entry for each application to be launched, with the data containing the full command-line to be invoked: ApplicationName::REG_EXPAND_SZ:: Such as: ASR utility::REG_EXPAND_SZ::"%systemroot%\\system32\\asr_fmt.exe /backup" When invoking the app, we expand out all the environment variables in the command-line. In addition, we append a "context" parameter to the command line, which the app is expected to use in calls to AsrAddSifEntry. The above entry would thus translate something like: c:\windows\system32\asr_fmt.exe /backup /context=2000 The environment block of the process is a duplicate of the current process environment block, with one exception--it contains two additional "Asr" variables: _AsrContext= _AsrCriticalVolumeList=;;...; Each application invoked must complete in the allowed time-out value. The time-out is configurable in the registry, by changing the value of the "ProcessTimeOut" value under the ASR key. We ship with a default of 3600 seconds, but the sys-admin can change it if needed. (0=infinite). Arguments: SifHandle - A handle to asr.sif, the ASR state file. A duplicate of this handle is passed in to applications as the "context" parameter, and as the "_AsrContext" variable in the environment block. CriticalVolumeList - A multi-string containing a list of the volume GUID's of each of the critical volumes present on the system. The GUID's must be in the NT name-space, i.e., must be of the form: \??\Volume{GUID} This multi-sz is used to create the semi-colon separated list of volumes in the "_AsrCriticalVolumeList" variable in the env block of the new processes. Applications (such as volume-managers) can use this list to determine if they manage any critical volumes, and make a note of it in the asr.sif. This way, they can intelligently decide to abort the ASR restore process if needed. Return Value: If the function succeeds, the return value is a nonzero value. This implies that all the applications invoked were successful (i.e., returned an exit code of 0). If the function fails, the return value is zero. To get extended error information, call GetLastError(). Note that if any of the applications returned an exit code other than 0, we interpret that as a fatal error, and will return an error. --*/ { HKEY regKey = NULL; DWORD status = ERROR_SUCCESS, waitResult = WAIT_ABANDONED, lpcValues = 0L, index = 0L, cbData = 0L, cbMaxDataLen = 0L, cchValueName = 0L, cchMaxValueLen = 0L, cbCommand = 0L, cchReqd = 0L, timeLeft = 0L, maxTimeOutValue = 0L; HANDLE heapHandle = NULL, processHandle = NULL, dupSifHandle = NULL; PWSTR valueName = NULL, data = NULL, command = NULL, lpEnvBlock = NULL; WCHAR cmdLineSuffix[ASR_COMMANDLINE_SUFFIX_LEN + 1]; BOOL result = FALSE; STARTUPINFOW startUpInfo; PROCESS_INFORMATION processInfo; heapHandle = GetProcessHeap(); processHandle = GetCurrentProcess(); MYASSERT(heapHandle && processHandle); ZeroMemory(cmdLineSuffix, (ASR_COMMANDLINE_SUFFIX_LEN + 1) * sizeof(WCHAR)); ZeroMemory(&startUpInfo, sizeof(STARTUPINFOW)); ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); // // Get the time out value for processes, if set in the registry // If the key is missing, or is set to "0", the timeout is set // to INFINITE. // status = RegOpenKeyExW( HKEY_LOCAL_MACHINE, // hKey ASR_REGKEY_ASR, // lpSubKey 0, // ulOptions--Reserved, must be 0 MAXIMUM_ALLOWED, // samDesired ®Key // phkResult ); if ((regKey) && (ERROR_SUCCESS == status)) { DWORD type = 0L, timeOut = 0L, cbTimeOut = (sizeof(DWORD)); status = RegQueryValueExW( regKey, // hKey ASR_REGVALUE_TIMEOUT, // lpValueName NULL, // lpReserved &type, // lpType (LPBYTE) &timeOut, // lpData &cbTimeOut // lpcbData ); if ((ERROR_SUCCESS == status) && (REG_DWORD == type)) { maxTimeOutValue = timeOut; } } if (regKey) { RegCloseKey(regKey); regKey = NULL; } // // Open and enumerate the entries in the ASR command key. If // the key doesn't exist, we don't have to execute anything // status = RegOpenKeyExW( HKEY_LOCAL_MACHINE, // hKey ASR_REGKEY_ASR_COMMANDS, // lpSubKey 0, // ulOptions--Reserved, must be 0 MAXIMUM_ALLOWED, // samDesired ®Key // phkResult ); if ((!regKey) || (ERROR_SUCCESS != status)) { return TRUE; } // // Get the max ValueName and Data entries, and // allocate memory for them // status = RegQueryInfoKey( regKey, NULL, // class NULL, // lpcClass NULL, // lpReserved NULL, // lpcSubKeys NULL, // lpcMaxSubKeyLen NULL, // lpcMaxClassLen, &lpcValues, // number of values &cchMaxValueLen, // max value length, in cch &cbMaxDataLen, // max data length, in cb NULL, // lpcbSecurityDescriptor NULL // lpftLastWriteTime ); _AsrpErrExitCode((ERROR_SUCCESS != status), status, status); _AsrpErrExitCode((0 == lpcValues), status, ERROR_SUCCESS); // Key is empty, we're done valueName = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, (cchMaxValueLen + 1) * sizeof (WCHAR) // cch not cb ); _AsrpErrExitCode(!valueName, status, ERROR_NOT_ENOUGH_MEMORY); data = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, cbMaxDataLen + ((ASR_COMMANDLINE_SUFFIX_LEN + 2) * sizeof(WCHAR)) ); _AsrpErrExitCode(!data, status, ERROR_NOT_ENOUGH_MEMORY); // // "command" will contain the full command string, after any environment // variables (eg %systemroot%) in "data" have been expanded. We'll start // off with "command" being MAX_PATH characters longer than "data", and // we'll re-allocate a bigger buffer if/when needed // cbCommand = cbMaxDataLen + ((ASR_COMMANDLINE_SUFFIX_LEN + MAX_PATH + 2) * sizeof(WCHAR)); command = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, cbCommand ); _AsrpErrExitCode(!command, status, ERROR_NOT_ENOUGH_MEMORY); do { cchValueName = cchMaxValueLen + 1; cbData = cbMaxDataLen + sizeof(WCHAR); // // Enumerate the commands, and execute them one after the other // status = RegEnumValueW( regKey, // hKey index++, // dwIndex valueName, // lpValueName &cchValueName, // lpcValueName NULL, // lpReserved NULL, // lpType (LPBYTE)data, // lpData &cbData // lpcbData ); _AsrpErrExitCode((ERROR_NO_MORE_ITEMS == status), status, ERROR_SUCCESS ); // done with enum _AsrpErrExitCode((ERROR_SUCCESS != status), status, status); // // Create a copy of the sif handle to pass to the app launched. // We clean-up close the handle after the app is done. // result = DuplicateHandle( processHandle, SifHandle, processHandle, &dupSifHandle, 0L, TRUE, DUPLICATE_SAME_ACCESS ); _AsrpErrExitCode((!result), status, GetLastError()); // // Append the "/context=" to // the command line // swprintf(cmdLineSuffix, ASR_COMMANDLINE_SUFFIX, (ULONG64)(dupSifHandle) ); wcscat(data, cmdLineSuffix); // // Expand any environment strings in the command line // cchReqd = ExpandEnvironmentStringsW(data, command, (cbCommand / sizeof(WCHAR)) ); _AsrpErrExitCode((!cchReqd), status, GetLastError()); if ((cchReqd * sizeof(WCHAR)) > cbCommand) { // // Our "command" buffer wasn't big enough, re-allocate as needed // _AsrpHeapFree(command); cbCommand = ((cchReqd + 1) * sizeof(WCHAR)); command = HeapAlloc(heapHandle, HEAP_ZERO_MEMORY, cbCommand); _AsrpErrExitCode(!command, status, ERROR_NOT_ENOUGH_MEMORY); // // Try expanding the env strings again ... // cchReqd = ExpandEnvironmentStringsW(data, command, (cbCommand / sizeof(WCHAR)) ); _AsrpErrExitCode( ((!cchReqd) || (cchReqd * sizeof(WCHAR)) > cbCommand), status, GetLastError() ); } // // Create the environment block to be passed to the // process being launched. The environment block // contains the entries: // _AsrCriticalVolumes=\??\Volume{Guid1};\??\Volume{Guid2} // _AsrContext= // // in addition to all the environment strings in the current process. // result = AsrpCreateEnvironmentBlock(CriticalVolumeList, dupSifHandle, &lpEnvBlock ); _AsrpErrExitCode((!result), status, GetLastError()); // // Execute the command as a separate process // memset(&startUpInfo, 0L, sizeof (startUpInfo)); result = CreateProcessW( NULL, // lpApplicationName command, // lpCommandLine NULL, // lpProcessAttributes NULL, // lpThreadAttributes TRUE, // bInheritHandles CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags lpEnvBlock, // new environment block NULL, // current directory name (null=current dir) &startUpInfo, // statup information &processInfo // process information ); _AsrpErrExitCode((!result), status, GetLastError() ); // process couldn't be launched // // Process was launched: start the timer countdown if a maximum // timeout was specified in the registry. Loop till either the // process completes, or the timer expires // timeLeft = maxTimeOutValue; if (timeLeft) { do { waitResult = WaitForSingleObject(processInfo.hProcess, 1000); // 1000 ms = 1 sec --timeLeft; } while ((WAIT_TIMEOUT == waitResult) && (timeLeft)); if (!timeLeft) { // // The process did not terminate in the allowed time. We treat // this as a fatal error--terminate the process, and set its // error code to ERROR_TIMEOUT // TerminateProcess(processInfo.hProcess, ERROR_TIMEOUT); } } else { // // No timeout was specified in the registry, wait for process to // complete. // waitResult = WaitForSingleObject(processInfo.hProcess, INFINITE); } // // Check if the wait failed above. If last error is something useful, // we don't want to destroy it--if it's ERROR_SUCCESS, we'll set it to // ERROR_TIMEOUT // status = GetLastError(); _AsrpErrExitCode((WAIT_OBJECT_0!=waitResult), status, (ERROR_SUCCESS == status ? ERROR_TIMEOUT : status)); // wait failed above // // Get the process's exit code: if it doesn't return ERROR_SUCCESS, // we exit the loop, set the last error to the error returned, // and return FALSE // GetExitCodeProcess(processInfo.hProcess, &status); _AsrpErrExitCode((ERROR_SUCCESS != status), status, status); _AsrpCloseHandle(dupSifHandle); _AsrpHeapFree(lpEnvBlock); } while (ERROR_SUCCESS == status); EXIT: // // Clean-up // if (regKey) { RegCloseKey(regKey); regKey = NULL; } _AsrpCloseHandle(dupSifHandle); _AsrpHeapFree(valueName); _AsrpHeapFree(data); _AsrpHeapFree(command); _AsrpHeapFree(lpEnvBlock); if (ERROR_SUCCESS != status) { SetLastError(status); return FALSE; } else { return TRUE; } } BOOL AsrpIsSupportedConfiguration( IN CONST PASR_DISK_INFO pDiskList, IN CONST PASR_SYSTEM_INFO pSystemInfo ) /*++ Routine Description: Checks if ASR backup can be performed on the system. We do not support systems that have: - PROCESSOR_ARCHITECTURE other than "x86", "amd64", or "ia64" - any FT volumes present anywhere on the system Arguments: pDiskList - The list of disks on the system. pSystemInfo - System information for this system. Return Value: If we support this ASR configuration, the return value is non-zero. If this configuration is not supported, the return value is zero. GetLastError() will return ERROR_NOT_SUPPORTED. --*/ { PASR_DISK_INFO pCurrentDisk = pDiskList; ULONG index; // // 1. platform must be x86, amd64, or ia64 // if (wcscmp(pSystemInfo->Platform, ASR_PLATFORM_X86) && wcscmp(pSystemInfo->Platform, ASR_PLATFORM_AMD64) && wcscmp(pSystemInfo->Platform, ASR_PLATFORM_IA64)) { SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } // // 2. System cannot any FT volumes. All mirrors, stripes and so on are // expected to be LDM volumes on dynamic disks. // while (pCurrentDisk) { if (!(pCurrentDisk->pDriveLayoutEx) || !(pCurrentDisk->pDiskGeometry)) { MYASSERT(0); pCurrentDisk = pCurrentDisk->pNext; continue; } if (pCurrentDisk->pDriveLayoutEx->PartitionStyle == PARTITION_STYLE_MBR) { for (index =0; index < pCurrentDisk->pDriveLayoutEx->PartitionCount; index++) { MYASSERT(pCurrentDisk->pDriveLayoutEx->PartitionEntry[index].PartitionStyle == PARTITION_STYLE_MBR); if (IsFTPartition(pCurrentDisk->pDriveLayoutEx->PartitionEntry[index].Mbr.PartitionType)) { SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } } } else if (pCurrentDisk->pDriveLayoutEx->PartitionStyle == PARTITION_STYLE_GPT) { // // GPT disks can't have FT Mirrors. // } pCurrentDisk = pCurrentDisk->pNext; } return TRUE; } // // ----- // The following routines are helpers for AsrAddSifEntry // ----- // BOOL AsrpSifCheckSectionNameSyntax( IN PCWSTR lpSectionName ) /*++ Routine Description: Performs some basic validation of lpSectionName to make sure that it conforms to the expected format for a section header Arguments: lpSectionName - The null-terminated string to be checked. Return Value: If lpSectionName appears to be a valid section name, the return value is a nonzero value. If lpSectionName does not pass our basic validation, the return value is zero. Note that GetLastError will NOT return additional error information in this case. --*/ { UINT i = 0; WCHAR wch = 0; // // Must be non-null // if (!lpSectionName) { return FALSE; } // // Must have atleast 3 chars, ([.]) and at most ASR_SIF_ENTRY_MAX_CHARS // chars // if ((ASR_SIF_ENTRY_MAX_CHARS < wcslen(lpSectionName)) || 3 > wcslen(lpSectionName)) { return FALSE; } // // First char must be [, and last char must be ]. // if (L'[' != lpSectionName[0] || L']' != lpSectionName[wcslen(lpSectionName)-1]) { return FALSE; } // // Check for illegal characters. Legal set of chars: A-Z a-z . _ // for (i = 1; i < wcslen(lpSectionName)-1; i++) { wch = lpSectionName[i]; if ((wch < L'A' || wch > 'Z') && (wch < L'a' || wch > 'z') && (wch < L'0' || wch > '9') && (wch != L'.') && (wch != '_')) { return FALSE; } } return TRUE; } BOOL AsrpSifCheckCommandsEntrySyntax( PCWSTR pwszEntry ) /*++ Routine Description: Performs some basic validation of pwszEntry to make sure that it conforms to the expected entry format for the Commands section Arguments: pwszEntry - The null-terminated string to be checked. Return Value: If pwszEntry appears to be a valid section name, the return value is a nonzero value. If pwszEntry does not pass our basic validation, the return value is zero. Note that GetLastError will NOT return additional error information in this case. --*/ { BOOL fValid = FALSE; if (!pwszEntry) { return TRUE; // NULL is okay } // // COMMANDS section entry format: // system-key,sequence-number,action-on-completion,"command","parameters" // system-key must be 1 // 1000 <= sequence-number <= 4999 // 0 <= action-on-completion <= 1 // command: no syntax check // parameters: no syntax check // fValid = ( // must be atleast 10 chars (1,0000,0,c) 10 <= wcslen(pwszEntry) && // system-key must be 1 L'1' == pwszEntry[0] && L',' == pwszEntry[1] && // 1000 <= sequence-number <= 4999 L'1' <= pwszEntry[2] && L'4' >= pwszEntry[2] && L'0' <= pwszEntry[3] && L'9' >= pwszEntry[3] && L'0' <= pwszEntry[4] && L'9' >= pwszEntry[4] && L'0' <= pwszEntry[5] && L'9' >= pwszEntry[5] && L',' == pwszEntry[6] && // action-on-completion = [0|1] L'0' <= pwszEntry[7] && L'1' >= pwszEntry[7] ); return fValid; } INT AsrpSkipMatchingQuotes( IN PCWSTR pwszEntry, IN const INT StartingOffset ) /*++ Routine Description: Checks if this entry starts with a quote. If it does, it finds the ending quote, and returns the index of the char after the ending quote (usually is a comma). Arguments: pwszEntry - The null-terminated string to check. StartingOffset - The index of the starting-quote in pwszEntry. Return Value: If the character at StartingOffset is a quote, this returns the index of the character after the next quote (the matching end-quote) in the string. If a matching end-quote is not found, it returns -1. If the character at StartingOffset is not a quote, this returns StartingOffset. Essentially, this returns the position where we expect the next comma in the sif entry to be. --*/ { INT offset = StartingOffset; if (pwszEntry[offset] == L'"') { // // Find the ending quote and make sure we don't go out of bounds. // while ( (pwszEntry[++offset]) && (pwszEntry[offset] != L'\"')) { ; } if (!pwszEntry[offset]) { // // We didn't find the closing quotes--we went out of bounds // offset = -1; } else { // // Found closing quote // offset++; } } return offset; } BOOL AsrpSifCheckInstallFilesEntrySyntax( IN PCWSTR pwszEntry, OUT PINT DestinationFilePathIndex OPTIONAL ) /*++ Routine Description: Performs some basic validation of pwszEntry to make sure that it conforms to the expected entry format for the InstallFiles section Arguments: pwszEntry - The null-terminated string to be checked. DestinationFilePathIndex - This receives the index at which the destination-file-path field in the sif entry (pwszEntry) begins. This is an optional parameter. Return Value: If pwszEntry appears to be a valid section name, the return value is a nonzero value. If pwszEntry does not pass our basic validation, the return value is zero. Note that GetLastError will NOT return additional error information in this case. --*/ { INT offset = 0; if (ARGUMENT_PRESENT(DestinationFilePathIndex)) { *DestinationFilePathIndex = 0; } // // NULL is okay // if (!pwszEntry) { return TRUE; } // // INSTALLFILES section entry format: // system-key,source-media-label,source-device, // source-file-path,destination-file-path,vendor-name,flags // // system-key must be 1 // // must be atleast 10 chars (1,m,d,p,,v) // if (wcslen(pwszEntry) < 10) { return FALSE; } // // system-key must be 1 // if (L'1' != pwszEntry[0] || L',' != pwszEntry[1] || L'"' != pwszEntry[2]) { return FALSE; } offset = 2; // // source-media-label // offset = AsrpSkipMatchingQuotes(pwszEntry, offset); if ((offset < 0) || L',' != pwszEntry[offset]) { return FALSE; } // // source-device // if (L'"' != pwszEntry[++offset]) { return FALSE; } offset = AsrpSkipMatchingQuotes(pwszEntry, offset); if ((offset < 0) || L',' != pwszEntry[offset]) { return FALSE; } // // source-file-path, must be enclosed in quotes. // if (L'"' != pwszEntry[++offset]) { return FALSE; } offset = AsrpSkipMatchingQuotes(pwszEntry, offset); if ((offset < 0) || L',' != pwszEntry[offset]) { return FALSE; } // // destination-file-path, must be enclosed in quotes. // if (L'"' != pwszEntry[++offset]) { return FALSE; } if (ARGUMENT_PRESENT(DestinationFilePathIndex)) { *DestinationFilePathIndex = offset; } offset = AsrpSkipMatchingQuotes(pwszEntry, offset); if ((offset < 0) || L',' != pwszEntry[offset]) { return FALSE; } // // vendor-name, must be enclosed in quotes. // if (L'"' != pwszEntry[++offset]) { return FALSE; } offset = AsrpSkipMatchingQuotes(pwszEntry, offset); if (offset < 0) { return FALSE; } return TRUE; } BOOL AsrpIsRunningOnPersonalSKU( VOID ) /*++ Routine Description: This function checks the system to see if we are running on the personal version of the operating system. The personal version is denoted by the product id equal to WINNT, which is really workstation, and the product suite containing the personal suite string. This is lifted from "IsRunningOnPersonal" by WesW. Arguments: None. Return Value: TRUE if we are running on personal, FALSE otherwise. --*/ { OSVERSIONINFOEXW OsVer = {0}; ULONGLONG ConditionMask = 0; OsVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); OsVer.wSuiteMask = VER_SUITE_PERSONAL; OsVer.wProductType = VER_NT_WORKSTATION; VER_SET_CONDITION(ConditionMask, VER_PRODUCT_TYPE, VER_EQUAL); VER_SET_CONDITION(ConditionMask, VER_SUITENAME, VER_AND); return VerifyVersionInfo(&OsVer, VER_PRODUCT_TYPE | VER_SUITENAME, ConditionMask ); } BOOL AsrpIsInGroup( IN CONST DWORD dwGroup ) /*++ Routine Description: This function checks to see if the specified SID is enabled in the primary access token for the current thread. This is based on a similar function in dmadmin.exe. Arguments: dwGroup - The SID to be checked for Return Value: TRUE if the specified SID is enabled, FALSE otherwise. --*/ { SID_IDENTIFIER_AUTHORITY sidAuth = SECURITY_NT_AUTHORITY; PSID sidGroup = NULL; BOOL bResult = FALSE, bIsInGroup = TRUE; // // Build the SID for the Administrators group // bResult = AllocateAndInitializeSid(&sidAuth, 2, SECURITY_BUILTIN_DOMAIN_RID, dwGroup, 0, 0, 0, 0, 0, 0, &sidGroup ); if (!bResult) { return FALSE; } // // Check the current thread token membership // bResult = CheckTokenMembership(NULL, sidGroup, &bIsInGroup); FreeSid(sidGroup); return (bResult && bIsInGroup); } BOOL AsrpHasPrivilege( CONST PCWSTR szPrivilege ) /*++ Routine Description: This function checks to see if the specified privilege is enabled in the primary access token for the current thread. This is based on a similar function in dmadmin.exe. Arguments: szPrivilege - The privilege to be checked for Return Value: TRUE if the specified privilege is enabled, FALSE otherwise. --*/ { LUID luidValue; // LUID (locally unique ID) for the privilege BOOL bResult = FALSE, bHasPrivilege = FALSE; HANDLE hToken = NULL; PRIVILEGE_SET privilegeSet; // // Get the LUID for the privilege from the privilege name // bResult = LookupPrivilegeValue( NULL, szPrivilege, &luidValue ); if (!bResult) { return FALSE; } // // We want to use the token for the current process // bResult = OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hToken ); if (!bResult) { return FALSE; } // // And check for the privilege // privilegeSet.PrivilegeCount = 1; privilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY; privilegeSet.Privilege[0].Luid = luidValue; privilegeSet.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; bResult = PrivilegeCheck(hToken, &privilegeSet, &bHasPrivilege); CloseHandle(hToken); return (bResult && bHasPrivilege); } BOOL AsrpCheckBackupPrivilege( VOID ) /*++ Routine Description: This function checks to see if the current process has the SE_BACKUP_NAME privilege enabled. This is based on a similar function in dmadmin.exe. Arguments: None. Return Value: TRUE if the SE_BACKUP_NAME privilege is enabled, FALSE otherwise. --*/ { BOOL bHasPrivilege = FALSE; bHasPrivilege = AsrpHasPrivilege(SE_BACKUP_NAME); /* // // Don't give up yet--check for the local administrator rights // if (!bHasPrivilege) { bHasPrivilege = AsrpIsInGroup(DOMAIN_ALIAS_RID_ADMINS); } */ if (!bHasPrivilege) { SetLastError(ERROR_PRIVILEGE_NOT_HELD); } return bHasPrivilege; } // // ------------------- // Public functions // ------------------- // // The functions below are for use by external backup and // restore applications supporting ASR. // // // ---- AsrCreateStateFile // BOOL AsrCreateStateFileW( IN PCWSTR lpFilePath OPTIONAL, IN PCWSTR lpProviderName OPTIONAL, IN CONST BOOL bEnableAutoExtend, IN PCWSTR mszCriticalVolumes, OUT DWORD_PTR *lpAsrContext ) /*-- Routine Description: AsrCreateStateFile creates an ASR state file with basic information about the system, and launches third-party applications that have been registered to be run as part of an ASR backup. Arguments: lpFileName - Pointer to a null-terminated string that specifies the full path where the ASR state-file is to be created. If a file already exists at the location pointed to by this parameter, it is over-written. This parameter can be NULL. If it is NULL, the ASR state-file is created at the default location (%systemroot%\repair\asr.sif). lpProviderName - Pointer to a null-terminated string that specifies the full name and version of the backup-and-restore application calling AsrCreateStateFile. There is a string size limit of (ASR_SIF_ENTRY_MAX_CHARS - ASR_SIF_CCH_PROVIDER_STRING) characters for this parameter. This parameter can be NULL. If it is NULL, a "Provider=" entry is not created in the Version section of the ASR state file. bEnableAutoExtend - Indicates whether partitions are to be auto-extended during an ASR restore. If this parameter is TRUE, partitions will be auto-extended during the ASR restore. If this is FALSE, partitions will not be extended. lpCriticalVolumes - Pointer to a multi-string containing volume-GUID’s for the critical volumes. This list is used to obtain the list of critical disks in the system that must be restored for a successful ASR. The volume-GUID's must be in the NT-namespace, of the form \??\Volume{Guid} This parameter cannot be NULL. lpAsrContext - Pointer to a variable receiving an ASR context. The context returned should be used in calls to the other ASR API, including AsrAddSifEntry. The calling application must call AsrFreeContext to free this context when it is no longer needed. This parameter cannot be NULL. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { BOOL result = FALSE; DWORD status = ERROR_SUCCESS, size = 0; ULONG maxDeviceNumber = 0; HANDLE sifhandle = NULL, heapHandle = NULL; PWSTR asrSifPath = NULL, pnpSifPath = NULL, tempPointer = NULL; UINT cchAsrSifPath = 0; char UnicodeFlag[3]; WCHAR infstring[ASR_SIF_ENTRY_MAX_CHARS + 1]; SECURITY_ATTRIBUTES securityAttributes; ASR_SYSTEM_INFO SystemInfo; PASR_DISK_INFO OriginalDiskList = NULL; if (AsrpIsRunningOnPersonalSKU()) { // // ASR is not supported on the Personal SKU // SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } if (!AsrpCheckBackupPrivilege()) { // // The caller needs to first acquire SE_BACKUP_NAME // SetLastError(ERROR_PRIVILEGE_NOT_HELD); return FALSE; } // // Check the IN parameters: // #ifdef PRERELEASE // // Don't enforce "CriticalVolumes must be non-NULL" for test // if (!(lpAsrContext)) #else if (!(lpAsrContext && mszCriticalVolumes)) #endif { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // // Set the OUT paramaters to known error values // *lpAsrContext = 0; // // Guard ourselves against returning ERROR_SUCCESS if we encountered // an unexpected error. We should never actually return this, since // we always SetLastError whereever we return FALSE from. // SetLastError(ERROR_CAN_NOT_COMPLETE); // // Zero out structs // memset(&SystemInfo, 0L, sizeof (SYSTEM_INFO)); heapHandle = GetProcessHeap(); // // Determine the file-path. If lpFilePath is provided, copy it over to // locally allocated memory and use it, else use the default path. // if (ARGUMENT_PRESENT(lpFilePath)) { cchAsrSifPath = wcslen(lpFilePath); // // Do a sanity check: we don't want to allow a file path // more than 4096 characters long. // if (cchAsrSifPath > ASR_SIF_ENTRY_MAX_CHARS) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } asrSifPath = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, ((cchAsrSifPath + 1) * sizeof(WCHAR)) ); _AsrpErrExitCode(!asrSifPath, status, ERROR_NOT_ENOUGH_MEMORY); wcsncpy(asrSifPath, lpFilePath, cchAsrSifPath); } else { // // lpFilePath is NULL, form the default path (of the form // \\?\c:\windows\repair\asr.sif) // // // Try with a reasonably sized buffer to begin with. // asrSifPath = AsrpExpandEnvStrings(ASR_DEFAULT_SIF_PATH); _AsrpErrExitCode(!asrSifPath, status, ERROR_BAD_ENVIRONMENT); // // Set cchAsrSifPath to the size of the asrSif buffer, since we // use this in determining the size of the pnpSif buffer below. // cchAsrSifPath = wcslen(asrSifPath); } // // Determine the file-path of the asrpnp.sif file, based on the location // of the asr.sif file. // pnpSifPath = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, ((cchAsrSifPath + 1 + wcslen(ASRPNP_DEFAULT_SIF_NAME))* sizeof(WCHAR)) ); _AsrpErrExitCode(!pnpSifPath, status, ERROR_NOT_ENOUGH_MEMORY); wcscpy(pnpSifPath, asrSifPath); tempPointer = pnpSifPath; while (*tempPointer) { tempPointer++; } while ((*tempPointer != L'\\') && (*tempPointer != L':') && (tempPointer >= pnpSifPath) ) { tempPointer--; } tempPointer++; wcscpy(tempPointer, ASRPNP_DEFAULT_SIF_NAME); // // We need to make the handle to asr.sif inheritable, since it will // be passed (in the guise of the "AsrContext") to apps that have // registered to be run as part of ASR. // securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.lpSecurityDescriptor = NULL; securityAttributes.bInheritHandle = TRUE; // // Create the file. The handle will be closed by the calling backup-app. // sifhandle = CreateFileW( asrSifPath, // lpFileName GENERIC_WRITE | GENERIC_READ, // dwDesiredAccess FILE_SHARE_READ, // dwShareMode &securityAttributes, // lpSecurityAttributes CREATE_ALWAYS, // dwCreationFlags FILE_FLAG_BACKUP_SEMANTICS, // dwFlagsAndAttributes NULL // hTemplateFile ); if (!sifhandle || INVALID_HANDLE_VALUE == sifhandle) { // // LastError is set by CreateFile // _AsrpErrExitCode(TRUE, status, GetLastError()); } // // File was successfully created. Add the unicode flag at the beginning // of the file, followed by comments. // sprintf(UnicodeFlag, "%c%c", 0xFF, 0xFE); result = WriteFile(sifhandle, UnicodeFlag, strlen(UnicodeFlag)*sizeof(char), &size, NULL); _AsrpErrExitCode(!result, status, GetLastError()); wcscpy(infstring, L";\r\n; Microsoft Windows Automated System Recovery State Information File\r\n;\r\n"); result = WriteFile(sifhandle, infstring, wcslen(infstring)*sizeof(WCHAR), &size, NULL); _AsrpErrExitCode(!result, status, GetLastError()); // // Beyond this point, we must zero out asr.sif on any failure. Also, if // there's any failure, we must be careful not to make further system // calls that could change the error returned by GetLastError(). // // // Since the function return values below are and-ed, if any of the calls // fails, we won't execute the ones following it. // result = ( // // Initialise the global structures // AsrpInitSystemInformation(&SystemInfo, bEnableAutoExtend) && AsrpInitDiskInformation(&OriginalDiskList) && AsrpInitLayoutInformation(&SystemInfo, OriginalDiskList, &maxDeviceNumber, TRUE ) && AsrpInitClusterSharedDisks(OriginalDiskList) && AsrpFreeNonFixedMedia(&OriginalDiskList) && AsrpMarkCriticalDisks(OriginalDiskList, mszCriticalVolumes, maxDeviceNumber ) // // Check if the system configuration is supported // && AsrpIsSupportedConfiguration(OriginalDiskList, &SystemInfo) // // Write the required sections to asr.sif // && AsrpWriteVersionSection(sifhandle, lpProviderName) && AsrpWriteSystemsSection(sifhandle, &SystemInfo) && AsrpWriteBusesSection(sifhandle, OriginalDiskList) && AsrpWriteMbrDisksSection(sifhandle, OriginalDiskList) && AsrpWriteGptDisksSection(sifhandle, OriginalDiskList) && AsrpWriteMbrPartitionsSection(sifhandle, OriginalDiskList, &SystemInfo ) && AsrpWriteGptPartitionsSection(sifhandle, OriginalDiskList, &SystemInfo ) && FlushFileBuffers(sifhandle) // // Create asrpnp.sif, containing entries needed to recover the PnP // entries in the registry // && AsrCreatePnpStateFileW(pnpSifPath) ); if (result) { // everything above succeeded // // Launch the apps registered to be run as part of ASR-backup. If any // of these apps don't complete successfully, we'll fail the ASR- // backup. // result = ( AsrpLaunchRegisteredCommands(sifhandle, mszCriticalVolumes) && FlushFileBuffers(sifhandle) ); } if (!result) { // // One of the functions above failed--we'll make asr.sif zero-length // and return the error. CreateFileW or CloseHandle might over-write // the LastError, so we save our error now and set it at the end. // status = GetLastError(); #ifndef PRERELEASE // // On release versions, we wipe out the asr.sif if we hit an error, // so that the user doesn't unknowingly end up with an incomplete // asr.sif // // We don't want to delete the incomplete asr.sif during test cycles, // though, since the sif may be useful for debugging. // _AsrpCloseHandle(sifhandle); // // Delete asr.sif and create it again, so that we have a zero-length // asr.sif // DeleteFileW(asrSifPath); /* sifhandle = CreateFileW( asrSifPath, // lpFileName GENERIC_WRITE, // dwDesiredAccess 0, // dwShareMode &securityAttributes, // lpSecurityAttributes CREATE_ALWAYS, // dwCreationFlags FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes NULL // hTemplateFile ); _AsrpCloseHandle(sifhandle); */ #endif SetLastError(status); } EXIT: // // Clean up // _AsrpHeapFree(asrSifPath); _AsrpHeapFree(pnpSifPath); AsrpFreeStateInformation(&OriginalDiskList, &SystemInfo); // // Set the OUT parameters // *lpAsrContext = (DWORD_PTR)sifhandle; if (ERROR_SUCCESS != status) { SetLastError(status); } if (!result) { if (ERROR_SUCCESS == GetLastError()) { // // We're going to return failure, but we haven't set the LastError to // a failure code. This is bad, since we have no clue what went wrong. // // We shouldn't ever get here, because the function returning FALSE above // should set the LastError as it sees fit. // // But I've added this in just to be safe. Let's set it to a generic // error. // MYASSERT(0 && L"Returning failure, but LastError is not set"); SetLastError(ERROR_CAN_NOT_COMPLETE); } } return ((result) && (ERROR_SUCCESS == status)); } BOOL AsrCreateStateFileA( IN LPCSTR lpFilePath, IN LPCSTR lpProviderName, IN CONST BOOL bEnableAutoExtend, IN LPCSTR mszCriticalVolumes, OUT DWORD_PTR *lpAsrContext ) /*++ Routine Description: This is the ANSI wrapper for AsrCreateStateFile. Please see AsrCreateStateFileW for a detailed description. Arguments: Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { PWSTR asrSifPath = NULL, providerName = NULL, lpwszCriticalVolumes = NULL; DWORD cchString = 0, status = ERROR_SUCCESS; BOOL result = FALSE; HANDLE heapHandle = GetProcessHeap(); if (AsrpIsRunningOnPersonalSKU()) { // // ASR is not supported on the Personal SKU // SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } if (!AsrpCheckBackupPrivilege()) { // // The caller needs to first acquire SE_BACKUP_NAME // SetLastError(ERROR_PRIVILEGE_NOT_HELD); return FALSE; } // // Check the IN parameters // #ifdef PRERELEASE // // Don't enforce "CriticalVolumes must be non-NULL" for test // if (!(lpAsrContext)) { #else if (!(lpAsrContext && mszCriticalVolumes)) { #endif SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // // if lpFilePath is not NULL, allocate a big enough buffer to hold // it, and convert it to wide char // if (lpFilePath) { cchString = strlen(lpFilePath); // // Do a sanity check: we don't want to allow a file path // more than 4096 characters long. // _AsrpErrExitCode( (cchString > ASR_SIF_ENTRY_MAX_CHARS), status, ERROR_INVALID_PARAMETER ); // // Allocate a big enough buffer, and copy it over // asrSifPath = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, ((cchString + 1) * sizeof(WCHAR)) ); _AsrpErrExitCode(!asrSifPath, status, ERROR_NOT_ENOUGH_MEMORY); result = MultiByteToWideChar(CP_ACP, // CodePage 0, // dwFlags lpFilePath, // lpMultiByteStr -1, // cbMultiByte: -1 since lpMultiByteStr is null terminated asrSifPath, // lpWideCharStr (cchString + 1) // cchWideChar ); _AsrpErrExitCode(!result, status, ERROR_INVALID_PARAMETER); } // // if lpProviderName is not NULL, make sure it isn't insanely long, // and convert it to wide char // if (lpProviderName) { cchString = strlen(lpProviderName); // // Do a sanity check: we don't want to allow an entry // more than 4096 characters long. // _AsrpErrExitCode( (cchString > (ASR_SIF_ENTRY_MAX_CHARS - ASR_SIF_CCH_PROVIDER_STRING)), status, ERROR_INVALID_PARAMETER ); // // Allocate a big enough buffer, and copy it over // providerName = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, ((cchString + 1) * sizeof(WCHAR)) ); _AsrpErrExitCode(!providerName, status, ERROR_NOT_ENOUGH_MEMORY); // // Convert to wide string // result = MultiByteToWideChar(CP_ACP, 0, lpProviderName, -1, providerName, cchString + 1 ); _AsrpErrExitCode(!result, status, ERROR_INVALID_PARAMETER); } if (mszCriticalVolumes) { // // Find the total length of mszCriticalVolumes // LPCSTR lpVolume = mszCriticalVolumes; while (*lpVolume) { lpVolume += (strlen(lpVolume) + 1); } // // Convert the string to wide-chars // cchString = (DWORD) (lpVolume - mszCriticalVolumes + 1); lpwszCriticalVolumes = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, cchString * sizeof(WCHAR) ); _AsrpErrExitCode(!lpwszCriticalVolumes, status, ERROR_NOT_ENOUGH_MEMORY); result = MultiByteToWideChar(CP_ACP, 0, mszCriticalVolumes, cchString, lpwszCriticalVolumes, cchString * sizeof(WCHAR) ); _AsrpErrExitCode(!result, status, ERROR_INVALID_PARAMETER); } result = AsrCreateStateFileW( asrSifPath, providerName, bEnableAutoExtend, lpwszCriticalVolumes, lpAsrContext ); EXIT: _AsrpHeapFree(asrSifPath); _AsrpHeapFree(providerName); _AsrpHeapFree(lpwszCriticalVolumes); return ((result) && (ERROR_SUCCESS == status)); } // // ---- AsrAddSifEntry // BOOL AsrAddSifEntryW( IN DWORD_PTR AsrContext, IN PCWSTR lpSectionName, IN PCWSTR lpSifEntry OPTIONAL ) /*++ Routine Description: The AsrSifEntry function adds entries to the ASR state file. It can be used by applications that need to save application-specific information in the ASR state file. Arguments: AsrContext - A valid ASR context. See the notes for more information about this parameter. lpSectionName - Pointer to a null-terminated string that specifies the section name. This parameter cannot be NULL. The section name has a string size limit of ASR_MAX_SIF_LINE characters. This limit is related to how the AsrAddSifEntry function parses entries in the ASR state file. The section name is case-insensitive. It is converted to all-caps before being added to the state file. The section name must not contain spaces or non-printable characters. The valid character set for section name is limited to letters (A-Z, a-z), numbers (0-9), and the following special characters: underscore ("_") and period ("."). If the state file does not contain a section with the section name pointed to by lpSectionName, a new section is created with this section name. lpSifEntry - Pointer to a null-terminated string that is to be added to the state file in the specified section. If *lpSifEntry is a valid entry, there is a string size limit of ASR_SIF_ENTRY_MAX_CHARS characters. This limit is related to how the AsrAddSifEntry function parses entries in the ASR state file. If lpSifEntry parameter is NULL, an empty section with the section name pointed to by lpSectionName is created if it doesn't already exist. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). Notes: The application calling AsrAddSifEntry obtains the ASR context by one of two methods: - If the application is the backup-and-restore application that creates the ASR state file, it receives the context as a parameter returned by AsrCreateStateFile. - If the application is launched by AsrCreateStateFile as part of an ASR backup, it receives the context to the state file as the /context command-line parameter. The application is responsible for reading this parameter to get the value of the context. AsrAddSifEntry will fail if the section name is that of a reserved section that applications are not allowed to add entries to. The following sections in the ASR state file are reserved: - Version, System, Disks.Mbr, Disk.Gpt, Partitions.Mbr and Partitions.Gpt If the section name is recognised (Commands or InstallFiles), AsrAddSifEntry will check the syntax of *lpSifEntry to ensure that it is in the proper format. In addition, AsrAddSifEntry will check to ensure that there are no filename collisions for the InstallFiles section. If a collision is detected, the API returns ERROR_ALREADY_EXISTS. Applications must use the following pre-defined values to access the recognised sections: - ASR_COMMANDS_SECTION_NAME_W for the Commands section, and - ASR_INSTALLFILES_SECTION_NAME for the InstallFiles section. --*/ { DWORD status = ERROR_SUCCESS, nextKey = 0, fileOffset = 0, size = 0, fileSize = 0, bufferSize = 0, destFilePos = 0; HANDLE sifhandle = NULL; WCHAR sifstring[ASR_SIF_ENTRY_MAX_CHARS *2 + 1], ucaseSectionName[ASR_SIF_ENTRY_MAX_CHARS + 1]; // lpSectionName converted to upper case PWSTR buffer = NULL, sectionStart = NULL, lastEqual = NULL, nextSection = NULL, nextChar = NULL, sectionName = NULL; BOOL commandsSection = FALSE, installFilesSection = FALSE, result = FALSE; HANDLE heapHandle = NULL; if (AsrpIsRunningOnPersonalSKU()) { // // ASR is not supported on the Personal SKU // SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } if (!AsrpCheckBackupPrivilege()) { // // The caller needs to first acquire SE_BACKUP_NAME // SetLastError(ERROR_PRIVILEGE_NOT_HELD); return FALSE; } heapHandle = GetProcessHeap(); MYASSERT(heapHandle); // // Zero out local structs // memset(sifstring, 0, (ASR_SIF_ENTRY_MAX_CHARS *2 + 1) * sizeof(WCHAR)); memset(ucaseSectionName, 0, (ASR_SIF_ENTRY_MAX_CHARS + 1) * (sizeof (WCHAR))); // // No OUT parameters // // // Check the IN parameters: The SectionName should meet // syntax requirements, SifEntry shouldn't be too long, // and the sifhandle should be valid. // if ((!AsrpSifCheckSectionNameSyntax(lpSectionName)) || (ARGUMENT_PRESENT(lpSifEntry) && (wcslen(lpSifEntry) > ASR_SIF_ENTRY_MAX_CHARS)) || ((!AsrContext) || (INVALID_HANDLE_VALUE == (HANDLE)AsrContext)) ) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } while (lpSectionName[size]) { if ((lpSectionName[size] >= L'a') && (lpSectionName[size] <= L'z')) { ucaseSectionName[size] = lpSectionName[size] - L'a' + L'A'; } else { ucaseSectionName[size] = lpSectionName[size]; } size++; } // // If the section is a recognised section (COMMANDS or INSTALLFILES), // we check the format of the sif entry. // if (!wcscmp(ucaseSectionName, ASR_SIF_SECTION_COMMANDS_W)) { // COMMANDS section if (!AsrpSifCheckCommandsEntrySyntax(lpSifEntry)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } commandsSection = TRUE; } else if(!wcscmp(ucaseSectionName, ASR_SIF_SECTION_INSTALLFILES_W)) { // INSTALLFILES section if (!AsrpSifCheckInstallFilesEntrySyntax(lpSifEntry, &destFilePos)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } installFilesSection = TRUE; } // // We do not allow anyone to write to reserved sections: // VERSION, SYSTEMS, DISKS.[MBR|GPT], PARTITIONS.[MBR|GPT] // else if ( !wcscmp(ucaseSectionName, ASR_SIF_VERSION_SECTION_NAME) || !wcscmp(ucaseSectionName, ASR_SIF_SYSTEM_SECTION_NAME) || !wcscmp(ucaseSectionName, ASR_SIF_MBR_DISKS_SECTION_NAME) || !wcscmp(ucaseSectionName, ASR_SIF_GPT_DISKS_SECTION_NAME) || !wcscmp(ucaseSectionName, ASR_SIF_MBR_PARTITIONS_SECTION_NAME) || !wcscmp(ucaseSectionName, ASR_SIF_GPT_PARTITIONS_SECTION_NAME) ) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } sectionName = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, (wcslen(ucaseSectionName) + 5) * sizeof (WCHAR) ); _AsrpErrExitCode(!sectionName, status, ERROR_NOT_ENOUGH_MEMORY); swprintf(sectionName, L"\r\n%ws\r\n", ucaseSectionName); sifhandle = (HANDLE) AsrContext; // // The algorithm to add to the middle of asr.sif is rather ugly // at the moment: we read the entire file into memory, make our // necessary changes, and write back the changed portion of the // file to disk. This is inefficient, but it's okay for now since // we expect asr.sif to be about 5 or 6 KB at the most. // // We should revisit this if the performance is unacceptably poor. // // // Allocate memory for the file // fileSize = GetFileSize(sifhandle, NULL); GetLastError(); _AsrpErrExitCode((fileSize == 0xFFFFFFFF), status, ERROR_INVALID_DATA); SetFilePointer(sifhandle, 0, NULL, FILE_BEGIN); buffer = (PWSTR) HeapAlloc( heapHandle, HEAP_ZERO_MEMORY, fileSize + 2 ); _AsrpErrExitCode(!buffer, status, ERROR_NOT_ENOUGH_MEMORY); // // And read file into memory. // result = ReadFile(sifhandle, buffer, fileSize, &size, NULL); _AsrpErrExitCode(!result, status, GetLastError()); // // Try to locate ucaseSectionName in the file // sectionStart = wcsstr(buffer, sectionName); if (!sectionStart) { // // sectionName was not found, ie the section does not exist // Add it at the end, and add the SifEntry right after it. // swprintf(sifstring, L"\r\n%ws\r\n%ws%ws\r\n", ucaseSectionName, ((commandsSection || installFilesSection) ? L"1=" : L""), (ARGUMENT_PRESENT(lpSifEntry) ? lpSifEntry : L"") ); // // File pointer already points to the end (because of ReadFile above) // if (!WriteFile(sifhandle, sifstring, wcslen(sifstring)*sizeof (WCHAR), &size, NULL)) { status = GetLastError(); } // We're done } else { // // The section exists, if lpSifEntry is NULL, we're done // if (ARGUMENT_PRESENT(lpSifEntry)) { // // SifEntry is not NULL, we'll add it at the end of the section // nextChar = sectionStart + 4; // Move pointer from \r to . in \r\n[. nextKey = 1; // // Find where this section ends--look either for the start // of the next section, or for the end of the file // while(*nextChar && *nextChar != L'[') { // // If this is a recognised section, we need to generate // the to add the entry in a = format. // We go through each line, and find the last key that // already exists. The new key will be last key + 1. // if (commandsSection || installFilesSection) { UINT commaCount = 0; BOOL tracking = FALSE; UINT count = 0; WCHAR c1, c2; while (*nextChar && (*nextChar != L'[') && (*nextChar != L'\n')) { if (installFilesSection) { if (*nextChar == L',') { commaCount++; } if ((commaCount > 2) && (L'"' == *nextChar)) { if (tracking) { // duplicate file name _AsrpErrExitCode((L'"'== lpSifEntry[destFilePos + count]), status, ERROR_ALREADY_EXISTS); } else { tracking = TRUE; count = 0; } } if (tracking) { c1 = *nextChar; if (c1 >= L'a' && c1 <= L'z') { c1 = c1 - L'a' + L'A'; } c2 = lpSifEntry[destFilePos + count]; if (c2 >= L'a' && c2 <= L'z') { c2 = c2 - L'a' + L'A'; } if (c1 == c2) { count++; } else { tracking = FALSE; } } } nextChar++; } if (*nextChar == L'\n') { ++nextChar; if (*nextChar >= L'0' && *nextChar <= L'9') { nextKey = 0; while (*nextChar >= L'0' && *nextChar <= L'9') { nextKey = nextKey*10 + (*nextChar - L'0'); nextChar++; } nextKey++; } } } else { nextChar++; } } // // We save a pointer to the next section in the sif, since we // need to write it out to disk. // if (*nextChar) { nextSection = nextChar; } else { nextSection = NULL; } if (commandsSection || installFilesSection) { // // Form the = string // swprintf( sifstring, L"%lu=%ws\r\n", nextKey, lpSifEntry ); } else { // // Not a recognised section: don't add the = // format, keep the string exactly as passed in // wcscpy(sifstring, lpSifEntry); wcscat(sifstring, L"\r\n"); } if (nextSection) { // // There are sections following the section we're adding to // We need to mark the point where the new entry is added. // While writing out to disk, we'll start from this point. // fileOffset = (DWORD) (((LPBYTE)nextSection) - ((LPBYTE)buffer) - sizeof(WCHAR)*2); // section start - file start - "\r\n" SetFilePointer(sifhandle, fileOffset, NULL, FILE_BEGIN); } // // file pointer points to where the entry must be added // if (!WriteFile(sifhandle, sifstring, wcslen(sifstring)*sizeof(WCHAR), &size, NULL)) { status = GetLastError(); } else if (nextSection) { // // write out all sections following this entry // if (!WriteFile( sifhandle, ((LPBYTE)nextSection) - (sizeof(WCHAR)*2), fileSize - fileOffset, &size, NULL )) { status = GetLastError(); } } } } EXIT: _AsrpHeapFree(sectionName); _AsrpHeapFree(buffer); return (BOOL) (ERROR_SUCCESS == status); } BOOL AsrAddSifEntryA( IN DWORD_PTR AsrContext, IN LPCSTR lpSectionName, IN LPCSTR lpSifEntry OPTIONAL ) /*++ This is the ANSI wrapper for AsrAddSifEntry. See AsrAddSifEntryW for a full description. --*/ { WCHAR wszSectionName[ASR_SIF_ENTRY_MAX_CHARS + 1]; WCHAR wszSifEntry[ASR_SIF_ENTRY_MAX_CHARS + 1]; if (AsrpIsRunningOnPersonalSKU()) { // // ASR is not supported on the Personal SKU // SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } if (!AsrpCheckBackupPrivilege()) { // // The caller needs to first acquire SE_BACKUP_NAME // SetLastError(ERROR_PRIVILEGE_NOT_HELD); return FALSE; } memset(wszSectionName, 0L, ASR_SIF_ENTRY_MAX_CHARS + 1); memset(wszSifEntry, 0L, ASR_SIF_ENTRY_MAX_CHARS + 1); // // lpSectionName must be non-NULL // if ((!lpSectionName) || !(MultiByteToWideChar( CP_ACP, 0, lpSectionName, -1, wszSectionName, ASR_SIF_ENTRY_MAX_CHARS + 1 ))) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } // // lpSifEntry is allowed to be NULL // if (ARGUMENT_PRESENT(lpSifEntry) && !(MultiByteToWideChar( CP_ACP, 0, lpSifEntry, -1, wszSifEntry, ASR_SIF_ENTRY_MAX_CHARS + 1 ))) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } return AsrAddSifEntryW( AsrContext, wszSectionName, wszSifEntry ); } // // ---- AsrFreeContext // BOOL AsrFreeContext( IN OUT DWORD_PTR *lpAsrContext ) /*++ Routine Description: AsrFreeContext frees the Asr Context, and sets lpAsrContext to NULL. Arguments: lpAsrContext This is the Asr context to be freed. This argument must not be NULL. AsrFreeContext will set this value to NULL after freeing it, to prevent further unintended accesses to the freed object. Return Value: If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero. To get extended error information, call GetLastError(). --*/ { BOOL result = FALSE; if (AsrpIsRunningOnPersonalSKU()) { // // ASR is not supported on the Personal SKU // SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } // // Essentially, the lpAsrContext is just a file handle, and all we need // to do to free it is call CloseHandle. // if ((lpAsrContext) && (*lpAsrContext) && (INVALID_HANDLE_VALUE != (HANDLE)(*lpAsrContext)) ) { result = CloseHandle((HANDLE)*lpAsrContext); *lpAsrContext = 0; } else { SetLastError(ERROR_INVALID_PARAMETER); } return result; }