[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);
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 PFILE_RECORD_HEADER Record)
751 {
752 USHORT *pShortToFixUp;
753 unsigned int ArrayEntryCount = Record->BytesAllocated / Vcb->NtfsInfo.BytesPerSector;
754 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
755 int i;
756
757 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->Ntfs.UsaOffset);
758
759 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
760
761 if (Record->BytesAllocated % Vcb->NtfsInfo.BytesPerSector != 0)
762 ArrayEntryCount++;
763
764 fixupArray->USN++;
765
766 for (i = 0; i < ArrayEntryCount; i++)
767 {
768 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
769
770 pShortToFixUp = (USHORT*)((UCHAR*)Record + Offset);
771 fixupArray->Array[i] = *pShortToFixUp;
772 *pShortToFixUp = fixupArray->USN;
773 Offset += Vcb->NtfsInfo.BytesPerSector;
774 }
775
776 return STATUS_SUCCESS;
777 }
778
779 NTSTATUS
780 ReadLCN(PDEVICE_EXTENSION Vcb,
781 ULONGLONG lcn,
782 ULONG count,
783 PVOID buffer)
784 {
785 LARGE_INTEGER DiskSector;
786
787 DiskSector.QuadPart = lcn;
788
789 return NtfsReadSectors(Vcb->StorageDevice,
790 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
791 count * Vcb->NtfsInfo.SectorsPerCluster,
792 Vcb->NtfsInfo.BytesPerSector,
793 buffer,
794 FALSE);
795 }
796
797
798 BOOLEAN
799 CompareFileName(PUNICODE_STRING FileName,
800 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
801 BOOLEAN DirSearch)
802 {
803 BOOLEAN Ret, Alloc = FALSE;
804 UNICODE_STRING EntryName;
805
806 EntryName.Buffer = IndexEntry->FileName.Name;
807 EntryName.Length =
808 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
809
810 if (DirSearch)
811 {
812 UNICODE_STRING IntFileName;
813 if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)
814 {
815 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
816 Alloc = TRUE;
817 }
818 else
819 {
820 IntFileName = *FileName;
821 }
822
823 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL);
824
825 if (Alloc)
826 {
827 RtlFreeUnicodeString(&IntFileName);
828 }
829
830 return Ret;
831 }
832 else
833 {
834 return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0);
835 }
836 }
837
838 #if 0
839 static
840 VOID
841 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
842 {
843 DPRINT1("Entry: %p\n", IndexEntry);
844 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
845 DPRINT1("\tLength: %u\n", IndexEntry->Length);
846 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
847 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
848 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
849 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
850 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
851 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
852 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
853 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
854 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
855 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
856 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
857 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
858 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
859 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
860 }
861 #endif
862
863 NTSTATUS
864 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
865 PFILE_RECORD_HEADER MftRecord,
866 PCHAR IndexRecord,
867 ULONG IndexBlockSize,
868 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
869 PINDEX_ENTRY_ATTRIBUTE LastEntry,
870 PUNICODE_STRING FileName,
871 PULONG StartEntry,
872 PULONG CurrentEntry,
873 BOOLEAN DirSearch,
874 ULONGLONG *OutMFTIndex)
875 {
876 NTSTATUS Status;
877 ULONG RecordOffset;
878 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
879 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
880 ULONGLONG IndexAllocationSize;
881 PINDEX_BUFFER IndexBuffer;
882
883 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);
884
885 IndexEntry = FirstEntry;
886 while (IndexEntry < LastEntry &&
887 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
888 {
889 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
890 *CurrentEntry >= *StartEntry &&
891 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
892 CompareFileName(FileName, IndexEntry, DirSearch))
893 {
894 *StartEntry = *CurrentEntry;
895 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
896 return STATUS_SUCCESS;
897 }
898
899 (*CurrentEntry) += 1;
900 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
901 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
902 }
903
904 /* If we're already browsing a subnode */
905 if (IndexRecord == NULL)
906 {
907 return STATUS_OBJECT_PATH_NOT_FOUND;
908 }
909
910 /* If there's no subnode */
911 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
912 {
913 return STATUS_OBJECT_PATH_NOT_FOUND;
914 }
915
916 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
917 if (!NT_SUCCESS(Status))
918 {
919 DPRINT("Corrupted filesystem!\n");
920 return Status;
921 }
922
923 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
924 Status = STATUS_OBJECT_PATH_NOT_FOUND;
925 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
926 {
927 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
928 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
929 if (!NT_SUCCESS(Status))
930 {
931 break;
932 }
933
934 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
935 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
936 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
937 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
938 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
939 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
940
941 Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex);
942 if (NT_SUCCESS(Status))
943 {
944 break;
945 }
946 }
947
948 ReleaseAttributeContext(IndexAllocationCtx);
949 return Status;
950 }
951
952 NTSTATUS
953 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
954 ULONGLONG MFTIndex,
955 PUNICODE_STRING FileName,
956 PULONG FirstEntry,
957 BOOLEAN DirSearch,
958 ULONGLONG *OutMFTIndex)
959 {
960 PFILE_RECORD_HEADER MftRecord;
961 PNTFS_ATTR_CONTEXT IndexRootCtx;
962 PINDEX_ROOT_ATTRIBUTE IndexRoot;
963 PCHAR IndexRecord;
964 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
965 NTSTATUS Status;
966 ULONG CurrentEntry = 0;
967
968 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
969
970 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
971 Vcb->NtfsInfo.BytesPerFileRecord,
972 TAG_NTFS);
973 if (MftRecord == NULL)
974 {
975 return STATUS_INSUFFICIENT_RESOURCES;
976 }
977
978 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
979 if (!NT_SUCCESS(Status))
980 {
981 ExFreePoolWithTag(MftRecord, TAG_NTFS);
982 return Status;
983 }
984
985 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
986 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
987 if (!NT_SUCCESS(Status))
988 {
989 ExFreePoolWithTag(MftRecord, TAG_NTFS);
990 return Status;
991 }
992
993 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
994 if (IndexRecord == NULL)
995 {
996 ReleaseAttributeContext(IndexRootCtx);
997 ExFreePoolWithTag(MftRecord, TAG_NTFS);
998 return STATUS_INSUFFICIENT_RESOURCES;
999 }
1000
1001 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1002 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1003 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1004 /* Index root is always resident. */
1005 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1006 ReleaseAttributeContext(IndexRootCtx);
1007
1008 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1009
1010 Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex);
1011
1012 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1013 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1014
1015 return Status;
1016 }
1017
1018 NTSTATUS
1019 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
1020 PUNICODE_STRING PathName,
1021 PFILE_RECORD_HEADER *FileRecord,
1022 PULONGLONG MFTIndex,
1023 ULONGLONG CurrentMFTIndex)
1024 {
1025 UNICODE_STRING Current, Remaining;
1026 NTSTATUS Status;
1027 ULONG FirstEntry = 0;
1028
1029 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex);
1030
1031 FsRtlDissectName(*PathName, &Current, &Remaining);
1032
1033 while (Current.Length != 0)
1034 {
1035 DPRINT("Current: %wZ\n", &Current);
1036
1037 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex);
1038 if (!NT_SUCCESS(Status))
1039 {
1040 return Status;
1041 }
1042
1043 if (Remaining.Length == 0)
1044 break;
1045
1046 FsRtlDissectName(Current, &Current, &Remaining);
1047 }
1048
1049 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1050 if (*FileRecord == NULL)
1051 {
1052 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1053 return STATUS_INSUFFICIENT_RESOURCES;
1054 }
1055
1056 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1057 if (!NT_SUCCESS(Status))
1058 {
1059 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1060 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1061 return Status;
1062 }
1063
1064 *MFTIndex = CurrentMFTIndex;
1065
1066 return STATUS_SUCCESS;
1067 }
1068
1069 NTSTATUS
1070 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
1071 PUNICODE_STRING PathName,
1072 PFILE_RECORD_HEADER *FileRecord,
1073 PULONGLONG MFTIndex)
1074 {
1075 return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
1076 }
1077
1078 NTSTATUS
1079 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
1080 PUNICODE_STRING SearchPattern,
1081 PULONG FirstEntry,
1082 PFILE_RECORD_HEADER *FileRecord,
1083 PULONGLONG MFTIndex,
1084 ULONGLONG CurrentMFTIndex)
1085 {
1086 NTSTATUS Status;
1087
1088 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex);
1089
1090 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex);
1091 if (!NT_SUCCESS(Status))
1092 {
1093 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
1094 return Status;
1095 }
1096
1097 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1098 if (*FileRecord == NULL)
1099 {
1100 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1101 return STATUS_INSUFFICIENT_RESOURCES;
1102 }
1103
1104 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1105 if (!NT_SUCCESS(Status))
1106 {
1107 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1108 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1109 return Status;
1110 }
1111
1112 *MFTIndex = CurrentMFTIndex;
1113
1114 return STATUS_SUCCESS;
1115 }
1116
1117 /* EOF */