[WINSPOOL] Implement DocumentPropertiesA including DEVMODE conversions (#2339)
[reactos.git] / base / setup / lib / fsutil.c
1 /*
2 * PROJECT: ReactOS Setup Library
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Filesystem support functions
5 * COPYRIGHT: Copyright 2003-2019 Casper S. Hornstrup (chorns@users.sourceforge.net)
6 * Copyright 2017-2019 Hermes Belusca-Maito
7 */
8
9 //
10 // See also: https://git.reactos.org/?p=reactos.git;a=blob;f=reactos/dll/win32/fmifs/init.c;h=e895f5ef9cae4806123f6bbdd3dfed37ec1c8d33;hb=b9db9a4e377a2055f635b2fb69fef4e1750d219c
11 // for how to get FS providers in a dynamic way. In the (near) future we may
12 // consider merging some of this code with us into a fmifs / fsutil / fslib library...
13 //
14
15 /* INCLUDES *****************************************************************/
16
17 #include "precomp.h"
18
19 #include "fsutil.h"
20 #include "partlist.h"
21
22 #include <fslib/vfatlib.h>
23 #include <fslib/btrfslib.h>
24 // #include <fslib/ext2lib.h>
25 // #include <fslib/ntfslib.h>
26
27 #define NDEBUG
28 #include <debug.h>
29
30
31 /* LOCALS *******************************************************************/
32
33 /** IFS_PROVIDER **/
34 typedef struct _FILE_SYSTEM
35 {
36 PCWSTR FileSystemName;
37 FORMATEX FormatFunc;
38 CHKDSKEX ChkdskFunc;
39 } FILE_SYSTEM, *PFILE_SYSTEM;
40
41 /* The list of file systems on which we can install ReactOS */
42 static FILE_SYSTEM RegisteredFileSystems[] =
43 {
44 /* NOTE: The FAT formatter automatically determines
45 * whether it will use FAT-16 or FAT-32. */
46 { L"FAT" , VfatFormat, VfatChkdsk },
47 #if 0
48 { L"FAT32", VfatFormat, VfatChkdsk }, // Do we support specific FAT sub-formats specifications?
49 { L"FATX" , VfatxFormat, VfatxChkdsk },
50 { L"NTFS" , NtfsFormat, NtfsChkdsk },
51 #endif
52 { L"BTRFS", BtrfsFormatEx, BtrfsChkdskEx },
53 #if 0
54 { L"EXT2" , Ext2Format, Ext2Chkdsk },
55 { L"EXT3" , Ext2Format, Ext2Chkdsk },
56 { L"EXT4" , Ext2Format, Ext2Chkdsk },
57 { L"FFS" , FfsFormat , FfsChkdsk },
58 { L"REISERFS", ReiserfsFormat, ReiserfsChkdsk },
59 #endif
60 };
61
62
63 /* FUNCTIONS ****************************************************************/
64
65 /** QueryAvailableFileSystemFormat() **/
66 BOOLEAN
67 GetRegisteredFileSystems(
68 IN ULONG Index,
69 OUT PCWSTR* FileSystemName)
70 {
71 if (Index >= ARRAYSIZE(RegisteredFileSystems))
72 return FALSE;
73
74 *FileSystemName = RegisteredFileSystems[Index].FileSystemName;
75
76 return TRUE;
77 }
78
79
80 /** GetProvider() **/
81 static PFILE_SYSTEM
82 GetFileSystemByName(
83 IN PCWSTR FileSystemName)
84 {
85 #if 0 // Reenable when the list of registered FSes will again be dynamic
86
87 PLIST_ENTRY ListEntry;
88 PFILE_SYSTEM_ITEM Item;
89
90 ListEntry = List->ListHead.Flink;
91 while (ListEntry != &List->ListHead)
92 {
93 Item = CONTAINING_RECORD(ListEntry, FILE_SYSTEM_ITEM, ListEntry);
94 if (Item->FileSystemName &&
95 (wcsicmp(FileSystemName, Item->FileSystemName) == 0 ||
96 /* Map FAT32 back to FAT */
97 (wcsicmp(FileSystemName, L"FAT32") == 0 && wcsicmp(Item->FileSystemName, L"FAT") == 0)))
98 {
99 return Item;
100 }
101
102 ListEntry = ListEntry->Flink;
103 }
104
105 #else
106
107 ULONG Count = ARRAYSIZE(RegisteredFileSystems);
108 PFILE_SYSTEM FileSystems = RegisteredFileSystems;
109
110 ASSERT(FileSystems && Count != 0);
111
112 while (Count--)
113 {
114 if (FileSystems->FileSystemName &&
115 (wcsicmp(FileSystemName, FileSystems->FileSystemName) == 0 ||
116 /* Map FAT32 back to FAT */
117 (wcsicmp(FileSystemName, L"FAT32") == 0 && wcsicmp(FileSystems->FileSystemName, L"FAT") == 0)))
118 {
119 return FileSystems;
120 }
121
122 ++FileSystems;
123 }
124
125 #endif
126
127 return NULL;
128 }
129
130
131 //
132 // FileSystem recognition, using NT OS functionality
133 //
134
135 /* NOTE: Ripped & adapted from base/system/autochk/autochk.c */
136 NTSTATUS
137 GetFileSystemNameByHandle(
138 IN HANDLE PartitionHandle,
139 IN OUT PWSTR FileSystemName,
140 IN SIZE_T FileSystemNameSize)
141 {
142 NTSTATUS Status;
143 IO_STATUS_BLOCK IoStatusBlock;
144 UCHAR Buffer[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
145 PFILE_FS_ATTRIBUTE_INFORMATION FileFsAttribute = (PFILE_FS_ATTRIBUTE_INFORMATION)Buffer;
146
147 /* Retrieve the FS attributes */
148 Status = NtQueryVolumeInformationFile(PartitionHandle,
149 &IoStatusBlock,
150 FileFsAttribute,
151 sizeof(Buffer),
152 FileFsAttributeInformation);
153 if (!NT_SUCCESS(Status))
154 {
155 DPRINT1("NtQueryVolumeInformationFile failed, Status 0x%08lx\n", Status);
156 return Status;
157 }
158
159 if (FileSystemNameSize < FileFsAttribute->FileSystemNameLength + sizeof(WCHAR))
160 return STATUS_BUFFER_TOO_SMALL;
161
162 return RtlStringCbCopyNW(FileSystemName, FileSystemNameSize,
163 FileFsAttribute->FileSystemName,
164 FileFsAttribute->FileSystemNameLength);
165 }
166
167 NTSTATUS
168 GetFileSystemName_UStr(
169 IN PUNICODE_STRING PartitionPath,
170 IN OUT PWSTR FileSystemName,
171 IN SIZE_T FileSystemNameSize)
172 {
173 NTSTATUS Status;
174 OBJECT_ATTRIBUTES ObjectAttributes;
175 HANDLE PartitionHandle;
176 IO_STATUS_BLOCK IoStatusBlock;
177
178 /* Open the partition */
179 InitializeObjectAttributes(&ObjectAttributes,
180 PartitionPath,
181 OBJ_CASE_INSENSITIVE,
182 NULL,
183 NULL);
184 Status = NtOpenFile(&PartitionHandle,
185 FILE_GENERIC_READ /* | SYNCHRONIZE */,
186 &ObjectAttributes,
187 &IoStatusBlock,
188 FILE_SHARE_READ | FILE_SHARE_WRITE,
189 0 /* FILE_SYNCHRONOUS_IO_NONALERT */);
190 if (!NT_SUCCESS(Status))
191 {
192 DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", PartitionPath, Status);
193 return Status;
194 }
195
196 /* Retrieve the FS attributes */
197 Status = GetFileSystemNameByHandle(PartitionHandle, FileSystemName, FileSystemNameSize);
198 if (!NT_SUCCESS(Status))
199 {
200 DPRINT1("GetFileSystemNameByHandle() failed for partition '%wZ', Status 0x%08lx\n",
201 PartitionPath, Status);
202 }
203
204 /* Close the partition */
205 NtClose(PartitionHandle);
206
207 return Status;
208 }
209
210 NTSTATUS
211 GetFileSystemName(
212 IN PCWSTR Partition,
213 IN OUT PWSTR FileSystemName,
214 IN SIZE_T FileSystemNameSize)
215 {
216 UNICODE_STRING PartitionPath;
217
218 RtlInitUnicodeString(&PartitionPath, Partition);
219 return GetFileSystemName_UStr(&PartitionPath,
220 FileSystemName,
221 FileSystemNameSize);
222 }
223
224 NTSTATUS
225 InferFileSystemByHandle(
226 IN HANDLE PartitionHandle,
227 IN UCHAR PartitionType,
228 IN OUT PWSTR FileSystemName,
229 IN SIZE_T FileSystemNameSize)
230 {
231 NTSTATUS Status;
232
233 if (FileSystemNameSize < sizeof(WCHAR))
234 return STATUS_BUFFER_TOO_SMALL;
235
236 *FileSystemName = L'\0';
237
238 /* Try to infer a file system using NT file system recognition */
239 Status = GetFileSystemNameByHandle(PartitionHandle,
240 FileSystemName,
241 FileSystemNameSize);
242 if (NT_SUCCESS(Status) && *FileSystemName)
243 {
244 goto Quit;
245 }
246
247 /*
248 * Try to infer a preferred file system for this partition, given its ID.
249 *
250 * WARNING: This is partly a hack, since partitions with the same ID can
251 * be formatted with different file systems: for example, usual Linux
252 * partitions that are formatted in EXT2/3/4, ReiserFS, etc... have the
253 * same partition ID 0x83.
254 *
255 * The proper fix is to make a function that detects the existing FS
256 * from a given partition (not based on the partition ID).
257 * On the contrary, for unformatted partitions with a given ID, the
258 * following code is OK.
259 */
260 if ((PartitionType == PARTITION_FAT_12) ||
261 (PartitionType == PARTITION_FAT_16) ||
262 (PartitionType == PARTITION_HUGE ) ||
263 (PartitionType == PARTITION_XINT13))
264 {
265 /* FAT12 or FAT16 */
266 Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"FAT");
267 }
268 else if ((PartitionType == PARTITION_FAT32) ||
269 (PartitionType == PARTITION_FAT32_XINT13))
270 {
271 Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"FAT32");
272 }
273 else if (PartitionType == PARTITION_LINUX)
274 {
275 // WARNING: See the warning above.
276 /* Could also be EXT2/3/4, ReiserFS, ... */
277 Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"BTRFS");
278 }
279 else if (PartitionType == PARTITION_IFS)
280 {
281 // WARNING: See the warning above.
282 /* Could also be HPFS */
283 Status = RtlStringCbCopyW(FileSystemName, FileSystemNameSize, L"NTFS");
284 }
285
286 Quit:
287 if (*FileSystemName)
288 {
289 // WARNING: We cannot write on this FS yet!
290 if (PartitionType == PARTITION_IFS)
291 {
292 DPRINT1("Recognized file system '%S' that doesn't have write support yet!\n",
293 FileSystemName);
294 }
295 }
296
297 DPRINT1("InferFileSystem -- PartitionType: 0x%02X ; FileSystem (guessed): %S\n",
298 PartitionType, *FileSystemName ? FileSystemName : L"None");
299
300 return Status;
301 }
302
303 NTSTATUS
304 InferFileSystem(
305 IN PCWSTR Partition,
306 IN UCHAR PartitionType,
307 IN OUT PWSTR FileSystemName,
308 IN SIZE_T FileSystemNameSize)
309 {
310 NTSTATUS Status;
311 UNICODE_STRING PartitionPath;
312 OBJECT_ATTRIBUTES ObjectAttributes;
313 HANDLE PartitionHandle;
314 IO_STATUS_BLOCK IoStatusBlock;
315
316 /* Open the partition */
317 RtlInitUnicodeString(&PartitionPath, Partition);
318 InitializeObjectAttributes(&ObjectAttributes,
319 &PartitionPath,
320 OBJ_CASE_INSENSITIVE,
321 NULL,
322 NULL);
323 Status = NtOpenFile(&PartitionHandle,
324 FILE_GENERIC_READ /* | SYNCHRONIZE */,
325 &ObjectAttributes,
326 &IoStatusBlock,
327 FILE_SHARE_READ | FILE_SHARE_WRITE,
328 0 /* FILE_SYNCHRONOUS_IO_NONALERT */);
329 if (!NT_SUCCESS(Status))
330 {
331 DPRINT1("Failed to open partition '%wZ', Status 0x%08lx\n", &PartitionPath, Status);
332 return Status;
333 }
334
335 /* Retrieve the FS */
336 Status = InferFileSystemByHandle(PartitionHandle,
337 PartitionType,
338 FileSystemName,
339 FileSystemNameSize);
340
341 /* Close the partition */
342 NtClose(PartitionHandle);
343
344 return Status;
345 }
346
347 /** ChkdskEx() **/
348 NTSTATUS
349 ChkdskFileSystem_UStr(
350 IN PUNICODE_STRING DriveRoot,
351 IN PCWSTR FileSystemName,
352 IN BOOLEAN FixErrors,
353 IN BOOLEAN Verbose,
354 IN BOOLEAN CheckOnlyIfDirty,
355 IN BOOLEAN ScanDrive,
356 IN PFMIFSCALLBACK Callback)
357 {
358 PFILE_SYSTEM FileSystem;
359
360 FileSystem = GetFileSystemByName(FileSystemName);
361
362 if (!FileSystem || !FileSystem->ChkdskFunc)
363 {
364 // BOOLEAN Argument = FALSE;
365 // Callback(DONE, 0, &Argument);
366 return STATUS_NOT_SUPPORTED;
367 }
368
369 return FileSystem->ChkdskFunc(DriveRoot,
370 FixErrors,
371 Verbose,
372 CheckOnlyIfDirty,
373 ScanDrive,
374 Callback);
375 }
376
377 NTSTATUS
378 ChkdskFileSystem(
379 IN PCWSTR DriveRoot,
380 IN PCWSTR FileSystemName,
381 IN BOOLEAN FixErrors,
382 IN BOOLEAN Verbose,
383 IN BOOLEAN CheckOnlyIfDirty,
384 IN BOOLEAN ScanDrive,
385 IN PFMIFSCALLBACK Callback)
386 {
387 UNICODE_STRING DriveRootU;
388
389 RtlInitUnicodeString(&DriveRootU, DriveRoot);
390 return ChkdskFileSystem_UStr(&DriveRootU,
391 FileSystemName,
392 FixErrors,
393 Verbose,
394 CheckOnlyIfDirty,
395 ScanDrive,
396 Callback);
397 }
398
399
400 /** FormatEx() **/
401 NTSTATUS
402 FormatFileSystem_UStr(
403 IN PUNICODE_STRING DriveRoot,
404 IN PCWSTR FileSystemName,
405 IN FMIFS_MEDIA_FLAG MediaFlag,
406 IN PUNICODE_STRING Label,
407 IN BOOLEAN QuickFormat,
408 IN ULONG ClusterSize,
409 IN PFMIFSCALLBACK Callback)
410 {
411 PFILE_SYSTEM FileSystem;
412
413 FileSystem = GetFileSystemByName(FileSystemName);
414
415 if (!FileSystem || !FileSystem->FormatFunc)
416 {
417 // BOOLEAN Argument = FALSE;
418 // Callback(DONE, 0, &Argument);
419 return STATUS_NOT_SUPPORTED;
420 }
421
422 return FileSystem->FormatFunc(DriveRoot,
423 MediaFlag,
424 Label,
425 QuickFormat,
426 ClusterSize,
427 Callback);
428 }
429
430 NTSTATUS
431 FormatFileSystem(
432 IN PCWSTR DriveRoot,
433 IN PCWSTR FileSystemName,
434 IN FMIFS_MEDIA_FLAG MediaFlag,
435 IN PCWSTR Label,
436 IN BOOLEAN QuickFormat,
437 IN ULONG ClusterSize,
438 IN PFMIFSCALLBACK Callback)
439 {
440 UNICODE_STRING DriveRootU;
441 UNICODE_STRING LabelU;
442
443 RtlInitUnicodeString(&DriveRootU, DriveRoot);
444 RtlInitUnicodeString(&LabelU, Label);
445
446 return FormatFileSystem_UStr(&DriveRootU,
447 FileSystemName,
448 MediaFlag,
449 &LabelU,
450 QuickFormat,
451 ClusterSize,
452 Callback);
453 }
454
455
456 UCHAR
457 FileSystemToPartitionType(
458 IN PCWSTR FileSystem,
459 IN PULARGE_INTEGER StartSector,
460 IN PULARGE_INTEGER SectorCount)
461 {
462 ASSERT(FileSystem && StartSector && SectorCount);
463
464 if (wcsicmp(FileSystem, L"FAT") == 0 ||
465 wcsicmp(FileSystem, L"FAT32") == 0 ||
466 wcsicmp(FileSystem, L"RAW") == 0)
467 {
468 if (SectorCount->QuadPart < 8192)
469 {
470 /* FAT12 CHS partition (disk is smaller than 4.1MB) */
471 return PARTITION_FAT_12;
472 }
473 else if (StartSector->QuadPart < 1450560)
474 {
475 /* Partition starts below the 8.4GB boundary ==> CHS partition */
476
477 if (SectorCount->QuadPart < 65536)
478 {
479 /* FAT16 CHS partition (partition size < 32MB) */
480 return PARTITION_FAT_16;
481 }
482 else if (SectorCount->QuadPart < 1048576)
483 {
484 /* FAT16 CHS partition (partition size < 512MB) */
485 return PARTITION_HUGE;
486 }
487 else
488 {
489 /* FAT32 CHS partition (partition size >= 512MB) */
490 return PARTITION_FAT32;
491 }
492 }
493 else
494 {
495 /* Partition starts above the 8.4GB boundary ==> LBA partition */
496
497 if (SectorCount->QuadPart < 1048576)
498 {
499 /* FAT16 LBA partition (partition size < 512MB) */
500 return PARTITION_XINT13;
501 }
502 else
503 {
504 /* FAT32 LBA partition (partition size >= 512MB) */
505 return PARTITION_FAT32_XINT13;
506 }
507 }
508 }
509 else if (wcsicmp(FileSystem, L"NTFS") == 0)
510 {
511 return PARTITION_IFS;
512 }
513 else if (wcsicmp(FileSystem, L"BTRFS") == 0 ||
514 wcsicmp(FileSystem, L"EXT2") == 0 ||
515 wcsicmp(FileSystem, L"EXT3") == 0 ||
516 wcsicmp(FileSystem, L"EXT4") == 0 ||
517 wcsicmp(FileSystem, L"FFS") == 0 ||
518 wcsicmp(FileSystem, L"REISERFS") == 0)
519 {
520 return PARTITION_LINUX;
521 }
522 else
523 {
524 /* Unknown file system */
525 DPRINT1("Unknown file system '%S'\n", FileSystem);
526 return PARTITION_ENTRY_UNUSED;
527 }
528 }
529
530
531 //
532 // Formatting routines
533 //
534
535 BOOLEAN
536 PreparePartitionForFormatting(
537 IN struct _PARTENTRY* PartEntry,
538 IN PCWSTR FileSystemName)
539 {
540 UCHAR PartitionType;
541
542 if (!FileSystemName || !*FileSystemName)
543 {
544 DPRINT1("No file system specified?\n");
545 return FALSE;
546 }
547
548 PartitionType = FileSystemToPartitionType(FileSystemName,
549 &PartEntry->StartSector,
550 &PartEntry->SectorCount);
551 if (PartitionType == PARTITION_ENTRY_UNUSED)
552 {
553 /* Unknown file system */
554 DPRINT1("Unknown file system '%S'\n", FileSystemName);
555 return FALSE;
556 }
557
558 SetPartitionType(PartEntry, PartitionType);
559
560 //
561 // FIXME: Do this now, or after the partition was actually formatted??
562 //
563 /* Set the new partition's file system proper */
564 RtlStringCbCopyW(PartEntry->FileSystem,
565 sizeof(PartEntry->FileSystem),
566 FileSystemName);
567
568 return TRUE;
569 }
570
571 /* EOF */