1 /* Copyright (c) Mark Harmstone 2019 2 * 3 * This file is part of WinBtrfs. 4 * 5 * WinBtrfs is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Lesser General Public Licence as published by 7 * the Free Software Foundation, either version 3 of the Licence, or 8 * (at your option) any later version. 9 * 10 * WinBtrfs is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Lesser General Public Licence for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public Licence 16 * along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */ 17 18 #include "btrfs_drv.h" 19 20 #ifndef __REACTOS__ 21 #ifdef _MSC_VER 22 #include <ntstrsafe.h> 23 #endif 24 #else 25 #include <ntstrsafe.h> 26 #endif 27 28 extern ERESOURCE pdo_list_lock; 29 extern LIST_ENTRY pdo_list; 30 extern ERESOURCE boot_lock; 31 extern PDRIVER_OBJECT drvobj; 32 33 BTRFS_UUID boot_uuid; // initialized to 0 34 uint64_t boot_subvol = 0; 35 36 #ifndef _MSC_VER 37 NTSTATUS RtlUnicodeStringPrintf(PUNICODE_STRING DestinationString, const WCHAR* pszFormat, ...); // not in mingw 38 #endif 39 40 // Not in any headers? Windbg knows about it though. 41 #define DOE_START_PENDING 0x10 42 43 // Just as much as we need - the version in mingw is truncated still further 44 typedef struct { 45 CSHORT Type; 46 USHORT Size; 47 PDEVICE_OBJECT DeviceObject; 48 ULONG PowerFlags; 49 void* Dope; 50 ULONG ExtensionFlags; 51 } DEVOBJ_EXTENSION2; 52 53 typedef enum { 54 system_root_unknown, 55 system_root_partition, 56 system_root_btrfs 57 } system_root_type; 58 59 typedef struct { 60 uint32_t disk_num; 61 uint32_t partition_num; 62 BTRFS_UUID uuid; 63 system_root_type type; 64 } system_root; 65 66 static void get_system_root(system_root* sr) { 67 NTSTATUS Status; 68 HANDLE h; 69 UNICODE_STRING us, target; 70 OBJECT_ATTRIBUTES objatt; 71 ULONG retlen = 0; 72 bool second_time = false; 73 74 static const WCHAR system_root[] = L"\\SystemRoot"; 75 static const WCHAR boot_device[] = L"\\Device\\BootDevice"; 76 static const WCHAR arc_prefix[] = L"\\ArcName\\multi(0)disk(0)rdisk("; 77 static const WCHAR arc_middle[] = L")partition("; 78 static const WCHAR arc_btrfs_prefix[] = L"\\ArcName\\btrfs("; 79 80 us.Buffer = (WCHAR*)system_root; 81 us.Length = us.MaximumLength = sizeof(system_root) - sizeof(WCHAR); 82 83 InitializeObjectAttributes(&objatt, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); 84 85 while (true) { 86 Status = ZwOpenSymbolicLinkObject(&h, GENERIC_READ, &objatt); 87 if (!NT_SUCCESS(Status)) { 88 ERR("ZwOpenSymbolicLinkObject returned %08lx\n", Status); 89 return; 90 } 91 92 target.Length = target.MaximumLength = 0; 93 94 Status = ZwQuerySymbolicLinkObject(h, &target, &retlen); 95 if (Status != STATUS_BUFFER_TOO_SMALL) { 96 ERR("ZwQuerySymbolicLinkObject returned %08lx\n", Status); 97 NtClose(h); 98 return; 99 } 100 101 if (retlen == 0) { 102 NtClose(h); 103 return; 104 } 105 106 target.Buffer = ExAllocatePoolWithTag(NonPagedPool, retlen, ALLOC_TAG); 107 if (!target.Buffer) { 108 ERR("out of memory\n"); 109 NtClose(h); 110 return; 111 } 112 113 target.Length = target.MaximumLength = (USHORT)retlen; 114 115 Status = ZwQuerySymbolicLinkObject(h, &target, NULL); 116 if (!NT_SUCCESS(Status)) { 117 ERR("ZwQuerySymbolicLinkObject returned %08lx\n", Status); 118 NtClose(h); 119 ExFreePool(target.Buffer); 120 return; 121 } 122 123 NtClose(h); 124 125 if (second_time) { 126 TRACE("boot device is %.*S\n", (int)(target.Length / sizeof(WCHAR)), target.Buffer); 127 } else { 128 TRACE("system root is %.*S\n", (int)(target.Length / sizeof(WCHAR)), target.Buffer); 129 } 130 131 if (!second_time && target.Length >= sizeof(boot_device) - sizeof(WCHAR) && 132 RtlCompareMemory(target.Buffer, boot_device, sizeof(boot_device) - sizeof(WCHAR)) == sizeof(boot_device) - sizeof(WCHAR)) { 133 ExFreePool(target.Buffer); 134 135 us.Buffer = (WCHAR*)boot_device; 136 us.Length = us.MaximumLength = sizeof(boot_device) - sizeof(WCHAR); 137 138 second_time = true; 139 } else 140 break; 141 } 142 143 sr->type = system_root_unknown; 144 145 if (target.Length >= sizeof(arc_prefix) - sizeof(WCHAR) && 146 RtlCompareMemory(target.Buffer, arc_prefix, sizeof(arc_prefix) - sizeof(WCHAR)) == sizeof(arc_prefix) - sizeof(WCHAR)) { 147 WCHAR* s = &target.Buffer[(sizeof(arc_prefix) / sizeof(WCHAR)) - 1]; 148 ULONG left = ((target.Length - sizeof(arc_prefix)) / sizeof(WCHAR)) + 1; 149 150 if (left == 0 || s[0] < '0' || s[0] > '9') { 151 ExFreePool(target.Buffer); 152 return; 153 } 154 155 sr->disk_num = 0; 156 157 while (left > 0 && s[0] >= '0' && s[0] <= '9') { 158 sr->disk_num *= 10; 159 sr->disk_num += s[0] - '0'; 160 s++; 161 left--; 162 } 163 164 if (left <= (sizeof(arc_middle) / sizeof(WCHAR)) - 1 || 165 RtlCompareMemory(s, arc_middle, sizeof(arc_middle) - sizeof(WCHAR)) != sizeof(arc_middle) - sizeof(WCHAR)) { 166 ExFreePool(target.Buffer); 167 return; 168 } 169 170 s = &s[(sizeof(arc_middle) / sizeof(WCHAR)) - 1]; 171 left -= (sizeof(arc_middle) / sizeof(WCHAR)) - 1; 172 173 if (left == 0 || s[0] < '0' || s[0] > '9') { 174 ExFreePool(target.Buffer); 175 return; 176 } 177 178 sr->partition_num = 0; 179 180 while (left > 0 && s[0] >= '0' && s[0] <= '9') { 181 sr->partition_num *= 10; 182 sr->partition_num += s[0] - '0'; 183 s++; 184 left--; 185 } 186 187 sr->type = system_root_partition; 188 } else if (target.Length >= sizeof(arc_btrfs_prefix) - sizeof(WCHAR) && 189 RtlCompareMemory(target.Buffer, arc_btrfs_prefix, sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) == sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) { 190 WCHAR* s = &target.Buffer[(sizeof(arc_btrfs_prefix) / sizeof(WCHAR)) - 1]; 191 #ifdef __REACTOS__ 192 unsigned int i; 193 #endif // __REACTOS__ 194 195 #ifndef __REACTOS__ 196 for (unsigned int i = 0; i < 16; i++) { 197 #else 198 for (i = 0; i < 16; i++) { 199 #endif // __REACTOS__ 200 if (*s >= '0' && *s <= '9') 201 sr->uuid.uuid[i] = (*s - '0') << 4; 202 else if (*s >= 'a' && *s <= 'f') 203 sr->uuid.uuid[i] = (*s - 'a' + 0xa) << 4; 204 else if (*s >= 'A' && *s <= 'F') 205 sr->uuid.uuid[i] = (*s - 'A' + 0xa) << 4; 206 else { 207 ExFreePool(target.Buffer); 208 return; 209 } 210 211 s++; 212 213 if (*s >= '0' && *s <= '9') 214 sr->uuid.uuid[i] |= *s - '0'; 215 else if (*s >= 'a' && *s <= 'f') 216 sr->uuid.uuid[i] |= *s - 'a' + 0xa; 217 else if (*s >= 'A' && *s <= 'F') 218 sr->uuid.uuid[i] |= *s - 'A' + 0xa; 219 else { 220 ExFreePool(target.Buffer); 221 return; 222 } 223 224 s++; 225 226 if (i == 3 || i == 5 || i == 7 || i == 9) { 227 if (*s != '-') { 228 ExFreePool(target.Buffer); 229 return; 230 } 231 232 s++; 233 } 234 } 235 236 if (*s != ')') { 237 ExFreePool(target.Buffer); 238 return; 239 } 240 241 sr->type = system_root_btrfs; 242 } 243 244 ExFreePool(target.Buffer); 245 } 246 247 static void change_symlink(uint32_t disk_num, uint32_t partition_num, BTRFS_UUID* uuid) { 248 NTSTATUS Status; 249 UNICODE_STRING us, us2; 250 WCHAR symlink[60], target[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) + 36], *w; 251 #ifdef __REACTOS__ 252 unsigned int i; 253 #endif 254 255 us.Buffer = symlink; 256 us.Length = 0; 257 us.MaximumLength = sizeof(symlink); 258 259 Status = RtlUnicodeStringPrintf(&us, L"\\Device\\Harddisk%u\\Partition%u", disk_num, partition_num); 260 if (!NT_SUCCESS(Status)) { 261 ERR("RtlUnicodeStringPrintf returned %08lx\n", Status); 262 return; 263 } 264 265 Status = IoDeleteSymbolicLink(&us); 266 if (!NT_SUCCESS(Status)) 267 ERR("IoDeleteSymbolicLink returned %08lx\n", Status); 268 269 RtlCopyMemory(target, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)); 270 271 w = &target[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1]; 272 273 #ifndef __REACTOS__ 274 for (unsigned int i = 0; i < 16; i++) { 275 #else 276 for (i = 0; i < 16; i++) { 277 #endif 278 *w = hex_digit(uuid->uuid[i] >> 4); w++; 279 *w = hex_digit(uuid->uuid[i] & 0xf); w++; 280 281 if (i == 3 || i == 5 || i == 7 || i == 9) { 282 *w = L'-'; 283 w++; 284 } 285 } 286 287 *w = L'}'; 288 289 us2.Buffer = target; 290 us2.Length = us2.MaximumLength = sizeof(target); 291 292 Status = IoCreateSymbolicLink(&us, &us2); 293 if (!NT_SUCCESS(Status)) 294 ERR("IoCreateSymbolicLink returned %08lx\n", Status); 295 } 296 297 static void mountmgr_notification(BTRFS_UUID* uuid) { 298 UNICODE_STRING mmdevpath; 299 NTSTATUS Status; 300 PFILE_OBJECT FileObject; 301 PDEVICE_OBJECT mountmgr; 302 ULONG mmtnlen; 303 MOUNTMGR_TARGET_NAME* mmtn; 304 WCHAR* w; 305 #ifdef __REACTOS__ 306 unsigned int i; 307 #endif 308 309 RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME); 310 Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr); 311 if (!NT_SUCCESS(Status)) { 312 ERR("IoGetDeviceObjectPointer returned %08lx\n", Status); 313 return; 314 } 315 316 mmtnlen = offsetof(MOUNTMGR_TARGET_NAME, DeviceName[0]) + sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR)); 317 318 mmtn = ExAllocatePoolWithTag(NonPagedPool, mmtnlen, ALLOC_TAG); 319 if (!mmtn) { 320 ERR("out of memory\n"); 321 return; 322 } 323 324 mmtn->DeviceNameLength = sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR)); 325 326 RtlCopyMemory(mmtn->DeviceName, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)); 327 328 w = &mmtn->DeviceName[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1]; 329 330 #ifndef __REACTOS__ 331 for (unsigned int i = 0; i < 16; i++) { 332 #else 333 for (i = 0; i < 16; i++) { 334 #endif 335 *w = hex_digit(uuid->uuid[i] >> 4); w++; 336 *w = hex_digit(uuid->uuid[i] & 0xf); w++; 337 338 if (i == 3 || i == 5 || i == 7 || i == 9) { 339 *w = L'-'; 340 w++; 341 } 342 } 343 344 *w = L'}'; 345 346 Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION, mmtn, mmtnlen, NULL, 0, false, NULL); 347 if (!NT_SUCCESS(Status)) { 348 ERR("IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION returned %08lx\n", Status); 349 ExFreePool(mmtn); 350 return; 351 } 352 353 ExFreePool(mmtn); 354 } 355 356 static void check_boot_options() { 357 NTSTATUS Status; 358 WCHAR* s; 359 360 static const WCHAR pathw[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control"; 361 static const WCHAR namew[] = L"SystemStartOptions"; 362 static const WCHAR subvol[] = L"SUBVOL="; 363 364 _SEH2_TRY { 365 HANDLE control; 366 OBJECT_ATTRIBUTES oa; 367 UNICODE_STRING path; 368 ULONG kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)); 369 KEY_VALUE_FULL_INFORMATION* kvfi; 370 UNICODE_STRING name; 371 WCHAR* options; 372 373 path.Buffer = (WCHAR*)pathw; 374 path.Length = path.MaximumLength = sizeof(pathw) - sizeof(WCHAR); 375 376 InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); 377 378 Status = ZwOpenKey(&control, KEY_QUERY_VALUE, &oa); 379 if (!NT_SUCCESS(Status)) { 380 ERR("ZwOpenKey returned %08lx\n", Status); 381 return; 382 } 383 384 // FIXME - don't fail if value too long (can we query for the length?) 385 386 kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG); 387 if (!kvfi) { 388 ERR("out of memory\n"); 389 NtClose(control); 390 return; 391 } 392 393 name.Buffer = (WCHAR*)namew; 394 name.Length = name.MaximumLength = sizeof(namew) - sizeof(WCHAR); 395 396 Status = ZwQueryValueKey(control, &name, KeyValueFullInformation, kvfi, 397 kvfilen, &kvfilen); 398 if (!NT_SUCCESS(Status)) { 399 ERR("ZwQueryValueKey returned %08lx\n", Status); 400 NtClose(control); 401 return; 402 } 403 404 NtClose(control); 405 406 options = (WCHAR*)((uint8_t*)kvfi + kvfi->DataOffset); 407 options[kvfi->DataLength / sizeof(WCHAR)] = 0; // FIXME - make sure buffer long enough to allow this 408 409 s = wcsstr(options, subvol); 410 411 if (!s) 412 return; 413 414 s += (sizeof(subvol) / sizeof(WCHAR)) - 1; 415 416 boot_subvol = 0; 417 418 while (true) { 419 if (*s >= '0' && *s <= '9') { 420 boot_subvol <<= 4; 421 boot_subvol |= *s - '0'; 422 } else if (*s >= 'a' && *s <= 'f') { 423 boot_subvol <<= 4; 424 boot_subvol |= *s - 'a' + 0xa; 425 } else if (*s >= 'A' && *s <= 'F') { 426 boot_subvol <<= 4; 427 boot_subvol |= *s - 'A' + 0xa; 428 } else 429 break; 430 431 s++; 432 } 433 } _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) { 434 return; 435 } _SEH2_END; 436 437 if (boot_subvol != 0) { 438 TRACE("passed subvol %I64x in boot options\n", boot_subvol); 439 } 440 } 441 442 void boot_add_device(DEVICE_OBJECT* pdo) { 443 pdo_device_extension* pdode = pdo->DeviceExtension; 444 445 AddDevice(drvobj, pdo); 446 447 // To stop Windows sneakily setting DOE_START_PENDING 448 pdode->dont_report = true; 449 450 if (pdo->DeviceObjectExtension) { 451 ((DEVOBJ_EXTENSION2*)pdo->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING; 452 453 if (pdode && pdode->vde && pdode->vde->device) 454 ((DEVOBJ_EXTENSION2*)pdode->vde->device->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING; 455 } 456 457 mountmgr_notification(&pdode->uuid); 458 } 459 460 /* If booting from Btrfs, Windows will pass the device object for the raw partition to 461 * mount_vol - which is no good to us, as we only use the \Device\Btrfs{} devices we 462 * create so that RAID works correctly. 463 * At the time check_system_root gets called, \SystemRoot is a symlink to the ARC device, 464 * e.g. \ArcName\multi(0)disk(0)rdisk(0)partition(1)\Windows. We can't change the symlink, 465 * as it gets clobbered by IopReassignSystemRoot shortly afterwards, and we can't touch 466 * the \ArcName symlinks as they haven't been created yet. Instead, we need to change the 467 * symlink \Device\HarddiskX\PartitionY, which is what the ArcName symlink will shortly 468 * point to. 469 */ 470 void __stdcall check_system_root(PDRIVER_OBJECT DriverObject, PVOID Context, ULONG Count) { 471 system_root sr; 472 LIST_ENTRY* le; 473 bool done = false; 474 PDEVICE_OBJECT pdo_to_add = NULL; 475 volume_child* boot_vc = NULL; 476 477 TRACE("(%p, %p, %lu)\n", DriverObject, Context, Count); 478 479 // wait for any PNP notifications in progress to finish 480 ExAcquireResourceExclusiveLite(&boot_lock, TRUE); 481 ExReleaseResourceLite(&boot_lock); 482 483 get_system_root(&sr); 484 485 if (sr.type == system_root_partition) { 486 TRACE("system boot partition is disk %u, partition %u\n", sr.disk_num, sr.partition_num); 487 488 ExAcquireResourceSharedLite(&pdo_list_lock, true); 489 490 le = pdo_list.Flink; 491 while (le != &pdo_list) { 492 LIST_ENTRY* le2; 493 pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); 494 495 ExAcquireResourceSharedLite(&pdode->child_lock, true); 496 497 le2 = pdode->children.Flink; 498 499 while (le2 != &pdode->children) { 500 volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry); 501 502 if (vc->disk_num == sr.disk_num && vc->part_num == sr.partition_num) { 503 change_symlink(sr.disk_num, sr.partition_num, &pdode->uuid); 504 done = true; 505 506 vc->boot_volume = true; 507 boot_uuid = pdode->uuid; 508 509 if (!pdode->vde) 510 pdo_to_add = pdode->pdo; 511 512 boot_vc = vc; 513 514 break; 515 } 516 517 le2 = le2->Flink; 518 } 519 520 if (done) { 521 le2 = pdode->children.Flink; 522 523 while (le2 != &pdode->children) { 524 volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry); 525 526 /* On Windows 7 we need to clear the DO_SYSTEM_BOOT_PARTITION flag of 527 * all of our underlying partition objects - otherwise IopMountVolume 528 * will bugcheck with UNMOUNTABLE_BOOT_VOLUME when it tries and fails 529 * to mount one. */ 530 if (vc->devobj) { 531 PDEVICE_OBJECT dev = vc->devobj; 532 533 ObReferenceObject(dev); 534 535 while (dev) { 536 PDEVICE_OBJECT dev2 = IoGetLowerDeviceObject(dev); 537 538 dev->Flags &= ~DO_SYSTEM_BOOT_PARTITION; 539 540 ObDereferenceObject(dev); 541 542 dev = dev2; 543 } 544 } 545 546 le2 = le2->Flink; 547 } 548 549 ExReleaseResourceLite(&pdode->child_lock); 550 551 break; 552 } 553 554 ExReleaseResourceLite(&pdode->child_lock); 555 556 le = le->Flink; 557 } 558 559 ExReleaseResourceLite(&pdo_list_lock); 560 } else if (sr.type == system_root_btrfs) { 561 boot_uuid = sr.uuid; 562 563 ExAcquireResourceSharedLite(&pdo_list_lock, true); 564 565 le = pdo_list.Flink; 566 while (le != &pdo_list) { 567 pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry); 568 569 if (RtlCompareMemory(&pdode->uuid, &sr.uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) { 570 if (!pdode->vde) 571 pdo_to_add = pdode->pdo; 572 573 break; 574 } 575 576 le = le->Flink; 577 } 578 579 ExReleaseResourceLite(&pdo_list_lock); 580 } 581 582 if (boot_vc) { 583 NTSTATUS Status; 584 UNICODE_STRING name; 585 586 /* On Windows 8, mountmgr!MountMgrFindBootVolume returns the first volume in its database 587 * with the DO_SYSTEM_BOOT_PARTITION flag set. We've cleared the bit on the underlying devices, 588 * but as it caches it we need to disable and re-enable the volume so mountmgr receives a PNP 589 * notification to refresh its list. */ 590 591 static const WCHAR prefix[] = L"\\??"; 592 593 name.Length = name.MaximumLength = boot_vc->pnp_name.Length + sizeof(prefix) - sizeof(WCHAR); 594 595 name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG); 596 if (!name.Buffer) 597 ERR("out of memory\n"); 598 else { 599 RtlCopyMemory(name.Buffer, prefix, sizeof(prefix) - sizeof(WCHAR)); 600 RtlCopyMemory(&name.Buffer[(sizeof(prefix) / sizeof(WCHAR)) - 1], boot_vc->pnp_name.Buffer, boot_vc->pnp_name.Length); 601 602 Status = IoSetDeviceInterfaceState(&name, false); 603 if (!NT_SUCCESS(Status)) 604 ERR("IoSetDeviceInterfaceState returned %08lx\n", Status); 605 606 Status = IoSetDeviceInterfaceState(&name, true); 607 if (!NT_SUCCESS(Status)) 608 ERR("IoSetDeviceInterfaceState returned %08lx\n", Status); 609 610 ExFreePool(name.Buffer); 611 } 612 } 613 614 if (sr.type == system_root_btrfs || boot_vc) 615 check_boot_options(); 616 617 // If our FS depends on volumes that aren't there when we do our IoRegisterPlugPlayNotification calls 618 // in DriverEntry, bus_query_device_relations won't get called until it's too late. We need to do our 619 // own call to AddDevice here as a result. We need to clear the DOE_START_PENDING bits, or NtOpenFile 620 // will return STATUS_NO_SUCH_DEVICE. 621 if (pdo_to_add) 622 boot_add_device(pdo_to_add); 623 } 624