[NTFS]
[reactos.git] / drivers / filesystems / ntfs / mft.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2002, 2014 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program 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 General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/mft.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
24 * Valentin Verkhovsky
25 * Pierre Schweitzer (pierre@reactos.org)
26 * Hervé Poussineau (hpoussin@reactos.org)
27 * Trevor Thompson
28 */
29
30 /* INCLUDES *****************************************************************/
31
32 #include "ntfs.h"
33
34 #define NDEBUG
35 #undef NDEBUG
36 #include <debug.h>
37
38 /* FUNCTIONS ****************************************************************/
39
40 PNTFS_ATTR_CONTEXT
41 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
42 {
43 PNTFS_ATTR_CONTEXT Context;
44
45 Context = ExAllocatePoolWithTag(NonPagedPool,
46 FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length,
47 TAG_NTFS);
48 RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
49 if (AttrRecord->IsNonResident)
50 {
51 LONGLONG DataRunOffset;
52 ULONGLONG DataRunLength;
53
54 Context->CacheRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
55 Context->CacheRunOffset = 0;
56 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
57 Context->CacheRunLength = DataRunLength;
58 if (DataRunOffset != -1)
59 {
60 /* Normal run. */
61 Context->CacheRunStartLCN =
62 Context->CacheRunLastLCN = DataRunOffset;
63 }
64 else
65 {
66 /* Sparse run. */
67 Context->CacheRunStartLCN = -1;
68 Context->CacheRunLastLCN = 0;
69 }
70 Context->CacheRunCurrentOffset = 0;
71 }
72
73 return Context;
74 }
75
76
77 VOID
78 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
79 {
80 ExFreePoolWithTag(Context, TAG_NTFS);
81 }
82
83
84 /**
85 * @name FindAttribute
86 * @implemented
87 *
88 * Searches a file record for an attribute matching the given type and name.
89 *
90 * @param Offset
91 * Optional pointer to a ULONG that will receive the offset of the found attribute
92 * from the beginning of the record. Can be set to NULL.
93 */
94 NTSTATUS
95 FindAttribute(PDEVICE_EXTENSION Vcb,
96 PFILE_RECORD_HEADER MftRecord,
97 ULONG Type,
98 PCWSTR Name,
99 ULONG NameLength,
100 PNTFS_ATTR_CONTEXT * AttrCtx,
101 PULONG Offset)
102 {
103 BOOLEAN Found;
104 NTSTATUS Status;
105 FIND_ATTR_CONTXT Context;
106 PNTFS_ATTR_RECORD Attribute;
107
108 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx);
109
110 Found = FALSE;
111 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
112 while (NT_SUCCESS(Status))
113 {
114 if (Attribute->Type == Type && Attribute->NameLength == NameLength)
115 {
116 if (NameLength != 0)
117 {
118 PWCHAR AttrName;
119
120 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
121 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
122 if (RtlCompareMemory(AttrName, Name, NameLength << 1) == (NameLength << 1))
123 {
124 Found = TRUE;
125 }
126 }
127 else
128 {
129 Found = TRUE;
130 }
131
132 if (Found)
133 {
134 /* Found it, fill up the context and return. */
135 DPRINT("Found context\n");
136 *AttrCtx = PrepareAttributeContext(Attribute);
137
138 if (Offset != NULL)
139 *Offset = Context.Offset;
140
141 FindCloseAttribute(&Context);
142 return STATUS_SUCCESS;
143 }
144 }
145
146 Status = FindNextAttribute(&Context, &Attribute);
147 }
148
149 FindCloseAttribute(&Context);
150 return STATUS_OBJECT_NAME_NOT_FOUND;
151 }
152
153
154 ULONG
155 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
156 {
157 if (AttrRecord->IsNonResident)
158 return AttrRecord->NonResident.AllocatedSize;
159 else
160 return AttrRecord->Resident.ValueLength;
161 }
162
163
164 ULONGLONG
165 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
166 {
167 if (AttrRecord->IsNonResident)
168 return AttrRecord->NonResident.DataSize;
169 else
170 return AttrRecord->Resident.ValueLength;
171 }
172
173
174 NTSTATUS
175 SetAttributeDataLength(PFILE_OBJECT FileObject,
176 PNTFS_FCB Fcb,
177 PNTFS_ATTR_CONTEXT AttrContext,
178 ULONG AttrOffset,
179 PFILE_RECORD_HEADER FileRecord,
180 PDEVICE_EXTENSION DeviceExt,
181 PLARGE_INTEGER DataSize)
182 {
183 if (AttrContext->Record.IsNonResident)
184 {
185 // do we need to increase the allocation size?
186 if (AttrContext->Record.NonResident.AllocatedSize < DataSize->QuadPart)
187 {
188 DPRINT1("FixMe: Increasing allocation size is unimplemented!\n");
189 return STATUS_NOT_IMPLEMENTED;
190 }
191
192 // TODO: is the file compressed, encrypted, or sparse?
193
194 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
195
196 // TODO: update the allocated size on-disk
197 DPRINT("Allocated Size: %I64u\n", AttrContext->Record.NonResident.AllocatedSize);
198
199 AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
200 AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
201
202 Fcb->RFCB.FileSize = *DataSize;
203 Fcb->RFCB.ValidDataLength = *DataSize;
204
205 DPRINT("Data Size: %I64u\n", Fcb->RFCB.FileSize.QuadPart);
206
207 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
208
209 // copy the attribute back into the FileRecord
210 RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length);
211
212 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
213
214 // write the updated file record back to disk
215 UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
216
217 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
218 }
219 else
220 {
221 // we can't yet handle resident attributes
222 DPRINT1("FixMe: Can't handle increasing length of resident attribute\n");
223 return STATUS_NOT_IMPLEMENTED;
224 }
225
226 return STATUS_SUCCESS;
227 }
228
229 ULONG
230 ReadAttribute(PDEVICE_EXTENSION Vcb,
231 PNTFS_ATTR_CONTEXT Context,
232 ULONGLONG Offset,
233 PCHAR Buffer,
234 ULONG Length)
235 {
236 ULONGLONG LastLCN;
237 PUCHAR DataRun;
238 LONGLONG DataRunOffset;
239 ULONGLONG DataRunLength;
240 LONGLONG DataRunStartLCN;
241 ULONGLONG CurrentOffset;
242 ULONG ReadLength;
243 ULONG AlreadyRead;
244 NTSTATUS Status;
245
246 if (!Context->Record.IsNonResident)
247 {
248 if (Offset > Context->Record.Resident.ValueLength)
249 return 0;
250 if (Offset + Length > Context->Record.Resident.ValueLength)
251 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
252 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
253 return Length;
254 }
255
256 /*
257 * Non-resident attribute
258 */
259
260 /*
261 * I. Find the corresponding start data run.
262 */
263
264 AlreadyRead = 0;
265
266 // FIXME: Cache seems to be non-working. Disable it for now
267 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
268 if (0)
269 {
270 DataRun = Context->CacheRun;
271 LastLCN = Context->CacheRunLastLCN;
272 DataRunStartLCN = Context->CacheRunStartLCN;
273 DataRunLength = Context->CacheRunLength;
274 CurrentOffset = Context->CacheRunCurrentOffset;
275 }
276 else
277 {
278 LastLCN = 0;
279 DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
280 CurrentOffset = 0;
281
282 while (1)
283 {
284 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
285 if (DataRunOffset != -1)
286 {
287 /* Normal data run. */
288 DataRunStartLCN = LastLCN + DataRunOffset;
289 LastLCN = DataRunStartLCN;
290 }
291 else
292 {
293 /* Sparse data run. */
294 DataRunStartLCN = -1;
295 }
296
297 if (Offset >= CurrentOffset &&
298 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
299 {
300 break;
301 }
302
303 if (*DataRun == 0)
304 {
305 return AlreadyRead;
306 }
307
308 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
309 }
310 }
311
312 /*
313 * II. Go through the run list and read the data
314 */
315
316 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
317 if (DataRunStartLCN == -1)
318 {
319 RtlZeroMemory(Buffer, ReadLength);
320 Status = STATUS_SUCCESS;
321 }
322 else
323 {
324 Status = NtfsReadDisk(Vcb->StorageDevice,
325 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
326 ReadLength,
327 Vcb->NtfsInfo.BytesPerSector,
328 (PVOID)Buffer,
329 FALSE);
330 }
331 if (NT_SUCCESS(Status))
332 {
333 Length -= ReadLength;
334 Buffer += ReadLength;
335 AlreadyRead += ReadLength;
336
337 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
338 {
339 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
340 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
341 if (DataRunOffset != (ULONGLONG)-1)
342 {
343 DataRunStartLCN = LastLCN + DataRunOffset;
344 LastLCN = DataRunStartLCN;
345 }
346 else
347 DataRunStartLCN = -1;
348 }
349
350 while (Length > 0)
351 {
352 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
353 if (DataRunStartLCN == -1)
354 RtlZeroMemory(Buffer, ReadLength);
355 else
356 {
357 Status = NtfsReadDisk(Vcb->StorageDevice,
358 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
359 ReadLength,
360 Vcb->NtfsInfo.BytesPerSector,
361 (PVOID)Buffer,
362 FALSE);
363 if (!NT_SUCCESS(Status))
364 break;
365 }
366
367 Length -= ReadLength;
368 Buffer += ReadLength;
369 AlreadyRead += ReadLength;
370
371 /* We finished this request, but there still data in this data run. */
372 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
373 break;
374
375 /*
376 * Go to next run in the list.
377 */
378
379 if (*DataRun == 0)
380 break;
381 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
382 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
383 if (DataRunOffset != -1)
384 {
385 /* Normal data run. */
386 DataRunStartLCN = LastLCN + DataRunOffset;
387 LastLCN = DataRunStartLCN;
388 }
389 else
390 {
391 /* Sparse data run. */
392 DataRunStartLCN = -1;
393 }
394 } /* while */
395
396 } /* if Disk */
397
398 Context->CacheRun = DataRun;
399 Context->CacheRunOffset = Offset + AlreadyRead;
400 Context->CacheRunStartLCN = DataRunStartLCN;
401 Context->CacheRunLength = DataRunLength;
402 Context->CacheRunLastLCN = LastLCN;
403 Context->CacheRunCurrentOffset = CurrentOffset;
404
405 return AlreadyRead;
406 }
407
408
409 /**
410 * @name WriteAttribute
411 * @implemented
412 *
413 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
414 * and it still needs more documentation / cleaning up.
415 *
416 * @param Vcb
417 * Volume Control Block indicating which volume to write the attribute to
418 *
419 * @param Context
420 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
421 *
422 * @param Offset
423 * Offset, in bytes, from the beginning of the attribute indicating where to start
424 * writing data
425 *
426 * @param Buffer
427 * The data that's being written to the device
428 *
429 * @param Length
430 * How much data will be written, in bytes
431 *
432 * @param RealLengthWritten
433 * Pointer to a ULONG which will receive how much data was written, in bytes
434 *
435 * @return
436 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
437 * writing to a sparse file.
438 *
439 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
440 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
441 *
442 */
443
444 NTSTATUS
445 WriteAttribute(PDEVICE_EXTENSION Vcb,
446 PNTFS_ATTR_CONTEXT Context,
447 ULONGLONG Offset,
448 const PUCHAR Buffer,
449 ULONG Length,
450 PULONG RealLengthWritten)
451 {
452 ULONGLONG LastLCN;
453 PUCHAR DataRun;
454 LONGLONG DataRunOffset;
455 ULONGLONG DataRunLength;
456 LONGLONG DataRunStartLCN;
457 ULONGLONG CurrentOffset;
458 ULONG WriteLength;
459 NTSTATUS Status;
460 PUCHAR SourceBuffer = Buffer;
461 LONGLONG StartingOffset;
462
463 DPRINT("WriteAttribute(%p, %p, %I64U, %p, %lu)\n", Vcb, Context, Offset, Buffer, Length);
464
465 // is this a resident attribute?
466 if (!Context->Record.IsNonResident)
467 {
468 DPRINT1("FIXME: Writing to resident NTFS records (small files) is not supported at this time.\n");
469 // (TODO: This should be really easy to implement)
470
471 /* LeftOver code from ReadAttribute(), may be helpful:
472 if (Offset > Context->Record.Resident.ValueLength)
473 return 0;
474 if (Offset + Length > Context->Record.Resident.ValueLength)
475 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
476 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
477 return Length;*/
478
479 return STATUS_NOT_IMPLEMENTED; // until we implement it
480 }
481
482 // This is a non-resident attribute.
483
484 // I. Find the corresponding start data run.
485
486 *RealLengthWritten = 0;
487
488 // FIXME: Cache seems to be non-working. Disable it for now
489 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
490 /*if (0)
491 {
492 DataRun = Context->CacheRun;
493 LastLCN = Context->CacheRunLastLCN;
494 DataRunStartLCN = Context->CacheRunStartLCN;
495 DataRunLength = Context->CacheRunLength;
496 CurrentOffset = Context->CacheRunCurrentOffset;
497 }
498 else*/
499 {
500 LastLCN = 0;
501 DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
502 CurrentOffset = 0;
503
504 while (1)
505 {
506 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
507 if (DataRunOffset != -1)
508 {
509 // Normal data run.
510 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
511 DataRunStartLCN = LastLCN + DataRunOffset;
512 LastLCN = DataRunStartLCN;
513 }
514 else
515 {
516 // Sparse data run. We can't support writing to sparse files yet
517 // (it may require increasing the allocation size).
518 DataRunStartLCN = -1;
519 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
520 return STATUS_NOT_IMPLEMENTED;
521 }
522
523 // Have we reached the data run we're trying to write to?
524 if (Offset >= CurrentOffset &&
525 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
526 {
527 break;
528 }
529
530 if (*DataRun == 0)
531 {
532 // We reached the last assigned cluster
533 // TODO: assign new clusters to the end of the file.
534 // (Presently, this code will never be reached, the write should have already failed by now)
535 return STATUS_END_OF_FILE;
536 }
537
538 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
539 }
540 }
541
542 // II. Go through the run list and write the data
543
544 /* REVIEWME -- As adapted from NtfsReadAttribute():
545 We seem to be making a special case for the first applicable data run, but I'm not sure why.
546 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
547
548 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
549
550 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
551
552 // Write the data to the disk
553 Status = NtfsWriteDisk(Vcb->StorageDevice,
554 StartingOffset,
555 WriteLength,
556 Vcb->NtfsInfo.BytesPerSector,
557 (PVOID)SourceBuffer);
558
559 // Did the write fail?
560 if (!NT_SUCCESS(Status))
561 {
562 Context->CacheRun = DataRun;
563 Context->CacheRunOffset = Offset;
564 Context->CacheRunStartLCN = DataRunStartLCN;
565 Context->CacheRunLength = DataRunLength;
566 Context->CacheRunLastLCN = LastLCN;
567 Context->CacheRunCurrentOffset = CurrentOffset;
568
569 return Status;
570 }
571
572 Length -= WriteLength;
573 SourceBuffer += WriteLength;
574 *RealLengthWritten += WriteLength;
575
576 // Did we write to the end of the data run?
577 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
578 {
579 // Advance to the next data run
580 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
581 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
582
583 if (DataRunOffset != (ULONGLONG)-1)
584 {
585 DataRunStartLCN = LastLCN + DataRunOffset;
586 LastLCN = DataRunStartLCN;
587 }
588 else
589 DataRunStartLCN = -1;
590 }
591
592 // Do we have more data to write?
593 while (Length > 0)
594 {
595 // Make sure we don't write past the end of the current data run
596 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
597
598 // Are we dealing with a sparse data run?
599 if (DataRunStartLCN == -1)
600 {
601 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
602 return STATUS_NOT_IMPLEMENTED;
603 }
604 else
605 {
606 // write the data to the disk
607 Status = NtfsWriteDisk(Vcb->StorageDevice,
608 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
609 WriteLength,
610 Vcb->NtfsInfo.BytesPerSector,
611 (PVOID)SourceBuffer);
612 if (!NT_SUCCESS(Status))
613 break;
614 }
615
616 Length -= WriteLength;
617 SourceBuffer += WriteLength;
618 *RealLengthWritten += WriteLength;
619
620 // We finished this request, but there's still data in this data run.
621 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
622 break;
623
624 // Go to next run in the list.
625
626 if (*DataRun == 0)
627 {
628 // that was the last run
629 if (Length > 0)
630 {
631 // Failed sanity check.
632 DPRINT1("Encountered EOF before expected!\n");
633 return STATUS_END_OF_FILE;
634 }
635
636 break;
637 }
638
639 // Advance to the next data run
640 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
641 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
642 if (DataRunOffset != -1)
643 {
644 // Normal data run.
645 DataRunStartLCN = LastLCN + DataRunOffset;
646 LastLCN = DataRunStartLCN;
647 }
648 else
649 {
650 // Sparse data run.
651 DataRunStartLCN = -1;
652 }
653 } // end while (Length > 0) [more data to write]
654
655 Context->CacheRun = DataRun;
656 Context->CacheRunOffset = Offset + *RealLengthWritten;
657 Context->CacheRunStartLCN = DataRunStartLCN;
658 Context->CacheRunLength = DataRunLength;
659 Context->CacheRunLastLCN = LastLCN;
660 Context->CacheRunCurrentOffset = CurrentOffset;
661
662 return Status;
663 }
664
665 NTSTATUS
666 ReadFileRecord(PDEVICE_EXTENSION Vcb,
667 ULONGLONG index,
668 PFILE_RECORD_HEADER file)
669 {
670 ULONGLONG BytesRead;
671
672 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
673
674 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
675 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
676 {
677 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
678 return STATUS_PARTIAL_COPY;
679 }
680
681 /* Apply update sequence array fixups. */
682 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
683 }
684
685 /**
686 * UpdateFileRecord
687 * @implemented
688 * Writes a file record to the master file table, at a given index.
689 */
690 NTSTATUS
691 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
692 ULONGLONG index,
693 PFILE_RECORD_HEADER file)
694 {
695 ULONG BytesWritten;
696 NTSTATUS Status = STATUS_SUCCESS;
697
698 DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
699
700 // Add the fixup array to prepare the data for writing to disk
701 AddFixupArray(Vcb, &file->Ntfs);
702
703 // write the file record to the master file table
704 Status = WriteAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
705
706 // TODO: Update MFT mirror
707
708 if (!NT_SUCCESS(Status))
709 {
710 DPRINT1("UpdateFileRecord failed: %I64u written, %u expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
711 }
712
713 return Status;
714 }
715
716
717 NTSTATUS
718 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
719 PNTFS_RECORD_HEADER Record)
720 {
721 USHORT *USA;
722 USHORT USANumber;
723 USHORT USACount;
724 USHORT *Block;
725
726 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
727 USANumber = *(USA++);
728 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
729 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
730
731 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
732
733 while (USACount)
734 {
735 if (*Block != USANumber)
736 {
737 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
738 return STATUS_UNSUCCESSFUL;
739 }
740 *Block = *(USA++);
741 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
742 USACount--;
743 }
744
745 return STATUS_SUCCESS;
746 }
747
748 NTSTATUS
749 AddFixupArray(PDEVICE_EXTENSION Vcb,
750 PNTFS_RECORD_HEADER Record)
751 {
752 USHORT *pShortToFixUp;
753 unsigned int ArrayEntryCount = Record->UsaCount - 1;
754 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
755 int i;
756
757 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
758
759 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
760
761 fixupArray->USN++;
762
763 for (i = 0; i < ArrayEntryCount; i++)
764 {
765 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
766
767 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
768 fixupArray->Array[i] = *pShortToFixUp;
769 *pShortToFixUp = fixupArray->USN;
770 Offset += Vcb->NtfsInfo.BytesPerSector;
771 }
772
773 return STATUS_SUCCESS;
774 }
775
776 NTSTATUS
777 ReadLCN(PDEVICE_EXTENSION Vcb,
778 ULONGLONG lcn,
779 ULONG count,
780 PVOID buffer)
781 {
782 LARGE_INTEGER DiskSector;
783
784 DiskSector.QuadPart = lcn;
785
786 return NtfsReadSectors(Vcb->StorageDevice,
787 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
788 count * Vcb->NtfsInfo.SectorsPerCluster,
789 Vcb->NtfsInfo.BytesPerSector,
790 buffer,
791 FALSE);
792 }
793
794
795 BOOLEAN
796 CompareFileName(PUNICODE_STRING FileName,
797 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
798 BOOLEAN DirSearch)
799 {
800 BOOLEAN Ret, Alloc = FALSE;
801 UNICODE_STRING EntryName;
802
803 EntryName.Buffer = IndexEntry->FileName.Name;
804 EntryName.Length =
805 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
806
807 if (DirSearch)
808 {
809 UNICODE_STRING IntFileName;
810 if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)
811 {
812 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
813 Alloc = TRUE;
814 }
815 else
816 {
817 IntFileName = *FileName;
818 }
819
820 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL);
821
822 if (Alloc)
823 {
824 RtlFreeUnicodeString(&IntFileName);
825 }
826
827 return Ret;
828 }
829 else
830 {
831 return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0);
832 }
833 }
834
835 #if 0
836 static
837 VOID
838 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
839 {
840 DPRINT1("Entry: %p\n", IndexEntry);
841 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
842 DPRINT1("\tLength: %u\n", IndexEntry->Length);
843 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
844 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
845 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
846 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
847 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
848 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
849 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
850 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
851 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
852 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
853 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
854 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
855 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
856 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
857 }
858 #endif
859
860 NTSTATUS
861 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
862 PFILE_RECORD_HEADER MftRecord,
863 PCHAR IndexRecord,
864 ULONG IndexBlockSize,
865 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
866 PINDEX_ENTRY_ATTRIBUTE LastEntry,
867 PUNICODE_STRING FileName,
868 PULONG StartEntry,
869 PULONG CurrentEntry,
870 BOOLEAN DirSearch,
871 ULONGLONG *OutMFTIndex)
872 {
873 NTSTATUS Status;
874 ULONG RecordOffset;
875 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
876 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
877 ULONGLONG IndexAllocationSize;
878 PINDEX_BUFFER IndexBuffer;
879
880 DPRINT("BrowseIndexEntries(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %p)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, OutMFTIndex);
881
882 IndexEntry = FirstEntry;
883 while (IndexEntry < LastEntry &&
884 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
885 {
886 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
887 *CurrentEntry >= *StartEntry &&
888 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
889 CompareFileName(FileName, IndexEntry, DirSearch))
890 {
891 *StartEntry = *CurrentEntry;
892 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
893 return STATUS_SUCCESS;
894 }
895
896 (*CurrentEntry) += 1;
897 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
898 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
899 }
900
901 /* If we're already browsing a subnode */
902 if (IndexRecord == NULL)
903 {
904 return STATUS_OBJECT_PATH_NOT_FOUND;
905 }
906
907 /* If there's no subnode */
908 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
909 {
910 return STATUS_OBJECT_PATH_NOT_FOUND;
911 }
912
913 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
914 if (!NT_SUCCESS(Status))
915 {
916 DPRINT("Corrupted filesystem!\n");
917 return Status;
918 }
919
920 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
921 Status = STATUS_OBJECT_PATH_NOT_FOUND;
922 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
923 {
924 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
925 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
926 if (!NT_SUCCESS(Status))
927 {
928 break;
929 }
930
931 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
932 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
933 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
934 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
935 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
936 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
937
938 Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex);
939 if (NT_SUCCESS(Status))
940 {
941 break;
942 }
943 }
944
945 ReleaseAttributeContext(IndexAllocationCtx);
946 return Status;
947 }
948
949 NTSTATUS
950 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
951 ULONGLONG MFTIndex,
952 PUNICODE_STRING FileName,
953 PULONG FirstEntry,
954 BOOLEAN DirSearch,
955 ULONGLONG *OutMFTIndex)
956 {
957 PFILE_RECORD_HEADER MftRecord;
958 PNTFS_ATTR_CONTEXT IndexRootCtx;
959 PINDEX_ROOT_ATTRIBUTE IndexRoot;
960 PCHAR IndexRecord;
961 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
962 NTSTATUS Status;
963 ULONG CurrentEntry = 0;
964
965 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
966
967 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
968 Vcb->NtfsInfo.BytesPerFileRecord,
969 TAG_NTFS);
970 if (MftRecord == NULL)
971 {
972 return STATUS_INSUFFICIENT_RESOURCES;
973 }
974
975 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
976 if (!NT_SUCCESS(Status))
977 {
978 ExFreePoolWithTag(MftRecord, TAG_NTFS);
979 return Status;
980 }
981
982 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
983 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
984 if (!NT_SUCCESS(Status))
985 {
986 ExFreePoolWithTag(MftRecord, TAG_NTFS);
987 return Status;
988 }
989
990 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
991 if (IndexRecord == NULL)
992 {
993 ReleaseAttributeContext(IndexRootCtx);
994 ExFreePoolWithTag(MftRecord, TAG_NTFS);
995 return STATUS_INSUFFICIENT_RESOURCES;
996 }
997
998 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
999 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1000 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1001 /* Index root is always resident. */
1002 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1003 ReleaseAttributeContext(IndexRootCtx);
1004
1005 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1006
1007 Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex);
1008
1009 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1010 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1011
1012 return Status;
1013 }
1014
1015 NTSTATUS
1016 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
1017 PUNICODE_STRING PathName,
1018 PFILE_RECORD_HEADER *FileRecord,
1019 PULONGLONG MFTIndex,
1020 ULONGLONG CurrentMFTIndex)
1021 {
1022 UNICODE_STRING Current, Remaining;
1023 NTSTATUS Status;
1024 ULONG FirstEntry = 0;
1025
1026 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex);
1027
1028 FsRtlDissectName(*PathName, &Current, &Remaining);
1029
1030 while (Current.Length != 0)
1031 {
1032 DPRINT("Current: %wZ\n", &Current);
1033
1034 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex);
1035 if (!NT_SUCCESS(Status))
1036 {
1037 return Status;
1038 }
1039
1040 if (Remaining.Length == 0)
1041 break;
1042
1043 FsRtlDissectName(Current, &Current, &Remaining);
1044 }
1045
1046 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1047 if (*FileRecord == NULL)
1048 {
1049 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1050 return STATUS_INSUFFICIENT_RESOURCES;
1051 }
1052
1053 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1054 if (!NT_SUCCESS(Status))
1055 {
1056 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1057 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1058 return Status;
1059 }
1060
1061 *MFTIndex = CurrentMFTIndex;
1062
1063 return STATUS_SUCCESS;
1064 }
1065
1066 NTSTATUS
1067 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
1068 PUNICODE_STRING PathName,
1069 PFILE_RECORD_HEADER *FileRecord,
1070 PULONGLONG MFTIndex)
1071 {
1072 return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
1073 }
1074
1075 NTSTATUS
1076 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
1077 PUNICODE_STRING SearchPattern,
1078 PULONG FirstEntry,
1079 PFILE_RECORD_HEADER *FileRecord,
1080 PULONGLONG MFTIndex,
1081 ULONGLONG CurrentMFTIndex)
1082 {
1083 NTSTATUS Status;
1084
1085 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex);
1086
1087 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex);
1088 if (!NT_SUCCESS(Status))
1089 {
1090 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
1091 return Status;
1092 }
1093
1094 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1095 if (*FileRecord == NULL)
1096 {
1097 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1098 return STATUS_INSUFFICIENT_RESOURCES;
1099 }
1100
1101 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1102 if (!NT_SUCCESS(Status))
1103 {
1104 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1105 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1106 return Status;
1107 }
1108
1109 *MFTIndex = CurrentMFTIndex;
1110
1111 return STATUS_SUCCESS;
1112 }
1113
1114 /* EOF */