3 * Copyright (C) 2002 ReactOS Team
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.
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.
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.
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/dirctl.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
24 * Hervé Poussineau (hpoussin@reactos.org)
25 * Pierre Schweitzer (pierre@reactos.org)
28 /* INCLUDES *****************************************************************/
35 /* FUNCTIONS ****************************************************************/
38 * FUNCTION: Retrieve the standard file information
42 NtfsGetStandardInformation(PNTFS_FCB Fcb
,
43 PDEVICE_OBJECT DeviceObject
,
44 PFILE_STANDARD_INFORMATION StandardInfo
,
47 UNREFERENCED_PARAMETER(DeviceObject
);
49 DPRINT1("NtfsGetStandardInformation(%p, %p, %p, %p)\n", Fcb
, DeviceObject
, StandardInfo
, BufferLength
);
51 if (*BufferLength
< sizeof(FILE_STANDARD_INFORMATION
))
52 return STATUS_BUFFER_TOO_SMALL
;
55 ASSERT(StandardInfo
!= NULL
);
58 RtlZeroMemory(StandardInfo
,
59 sizeof(FILE_STANDARD_INFORMATION
));
61 StandardInfo
->AllocationSize
= Fcb
->RFCB
.AllocationSize
;
62 StandardInfo
->EndOfFile
= Fcb
->RFCB
.FileSize
;
63 StandardInfo
->NumberOfLinks
= Fcb
->LinkCount
;
64 StandardInfo
->DeletePending
= FALSE
;
65 StandardInfo
->Directory
= NtfsFCBIsDirectory(Fcb
);
67 *BufferLength
-= sizeof(FILE_STANDARD_INFORMATION
);
69 return STATUS_SUCCESS
;
75 NtfsGetPositionInformation(PFILE_OBJECT FileObject
,
76 PFILE_POSITION_INFORMATION PositionInfo
,
79 DPRINT1("NtfsGetPositionInformation(%p, %p, %p)\n", FileObject
, PositionInfo
, BufferLength
);
81 if (*BufferLength
< sizeof(FILE_POSITION_INFORMATION
))
82 return STATUS_BUFFER_TOO_SMALL
;
84 PositionInfo
->CurrentByteOffset
.QuadPart
= FileObject
->CurrentByteOffset
.QuadPart
;
86 DPRINT("Getting position %I64x\n",
87 PositionInfo
->CurrentByteOffset
.QuadPart
);
89 *BufferLength
-= sizeof(FILE_POSITION_INFORMATION
);
91 return STATUS_SUCCESS
;
97 NtfsGetBasicInformation(PFILE_OBJECT FileObject
,
99 PDEVICE_OBJECT DeviceObject
,
100 PFILE_BASIC_INFORMATION BasicInfo
,
103 PFILENAME_ATTRIBUTE FileName
= &Fcb
->Entry
;
105 DPRINT1("NtfsGetBasicInformation(%p, %p, %p, %p, %p)\n", FileObject
, Fcb
, DeviceObject
, BasicInfo
, BufferLength
);
107 if (*BufferLength
< sizeof(FILE_BASIC_INFORMATION
))
108 return STATUS_BUFFER_TOO_SMALL
;
110 BasicInfo
->CreationTime
.QuadPart
= FileName
->CreationTime
;
111 BasicInfo
->LastAccessTime
.QuadPart
= FileName
->LastAccessTime
;
112 BasicInfo
->LastWriteTime
.QuadPart
= FileName
->LastWriteTime
;
113 BasicInfo
->ChangeTime
.QuadPart
= FileName
->ChangeTime
;
115 NtfsFileFlagsToAttributes(FileName
->FileAttributes
, &BasicInfo
->FileAttributes
);
117 *BufferLength
-= sizeof(FILE_BASIC_INFORMATION
);
119 return STATUS_SUCCESS
;
124 * FUNCTION: Retrieve the file name information
128 NtfsGetNameInformation(PFILE_OBJECT FileObject
,
130 PDEVICE_OBJECT DeviceObject
,
131 PFILE_NAME_INFORMATION NameInfo
,
136 UNREFERENCED_PARAMETER(FileObject
);
137 UNREFERENCED_PARAMETER(DeviceObject
);
139 DPRINT1("NtfsGetNameInformation(%p, %p, %p, %p, %p)\n", FileObject
, Fcb
, DeviceObject
, NameInfo
, BufferLength
);
141 ASSERT(NameInfo
!= NULL
);
144 /* If buffer can't hold at least the file name length, bail out */
145 if (*BufferLength
< (ULONG
)FIELD_OFFSET(FILE_NAME_INFORMATION
, FileName
[0]))
146 return STATUS_BUFFER_TOO_SMALL
;
148 /* Save file name length, and as much file len, as buffer length allows */
149 NameInfo
->FileNameLength
= wcslen(Fcb
->PathName
) * sizeof(WCHAR
);
151 /* Calculate amount of bytes to copy not to overflow the buffer */
152 BytesToCopy
= min(NameInfo
->FileNameLength
,
153 *BufferLength
- FIELD_OFFSET(FILE_NAME_INFORMATION
, FileName
[0]));
155 /* Fill in the bytes */
156 RtlCopyMemory(NameInfo
->FileName
, Fcb
->PathName
, BytesToCopy
);
158 /* Check if we could write more but are not able to */
159 if (*BufferLength
< NameInfo
->FileNameLength
+ (ULONG
)FIELD_OFFSET(FILE_NAME_INFORMATION
, FileName
[0]))
161 /* Return number of bytes written */
162 *BufferLength
-= FIELD_OFFSET(FILE_NAME_INFORMATION
, FileName
[0]) + BytesToCopy
;
163 return STATUS_BUFFER_OVERFLOW
;
166 /* We filled up as many bytes, as needed */
167 *BufferLength
-= (FIELD_OFFSET(FILE_NAME_INFORMATION
, FileName
[0]) + NameInfo
->FileNameLength
);
169 return STATUS_SUCCESS
;
175 NtfsGetInternalInformation(PNTFS_FCB Fcb
,
176 PFILE_INTERNAL_INFORMATION InternalInfo
,
179 DPRINT1("NtfsGetInternalInformation(%p, %p, %p)\n", Fcb
, InternalInfo
, BufferLength
);
181 ASSERT(InternalInfo
);
184 if (*BufferLength
< sizeof(FILE_INTERNAL_INFORMATION
))
185 return STATUS_BUFFER_TOO_SMALL
;
187 InternalInfo
->IndexNumber
.QuadPart
= Fcb
->MFTIndex
;
189 *BufferLength
-= sizeof(FILE_INTERNAL_INFORMATION
);
191 return STATUS_SUCCESS
;
196 NtfsGetNetworkOpenInformation(PNTFS_FCB Fcb
,
197 PDEVICE_EXTENSION DeviceExt
,
198 PFILE_NETWORK_OPEN_INFORMATION NetworkInfo
,
201 PFILENAME_ATTRIBUTE FileName
= &Fcb
->Entry
;
203 DPRINT1("NtfsGetNetworkOpenInformation(%p, %p, %p, %p)\n", Fcb
, DeviceExt
, NetworkInfo
, BufferLength
);
205 if (*BufferLength
< sizeof(FILE_NETWORK_OPEN_INFORMATION
))
206 return STATUS_BUFFER_TOO_SMALL
;
208 NetworkInfo
->CreationTime
.QuadPart
= FileName
->CreationTime
;
209 NetworkInfo
->LastAccessTime
.QuadPart
= FileName
->LastAccessTime
;
210 NetworkInfo
->LastWriteTime
.QuadPart
= FileName
->LastWriteTime
;
211 NetworkInfo
->ChangeTime
.QuadPart
= FileName
->ChangeTime
;
213 NetworkInfo
->EndOfFile
= Fcb
->RFCB
.FileSize
;
214 NetworkInfo
->AllocationSize
= Fcb
->RFCB
.AllocationSize
;
216 NtfsFileFlagsToAttributes(FileName
->FileAttributes
, &NetworkInfo
->FileAttributes
);
218 *BufferLength
-= sizeof(FILE_NETWORK_OPEN_INFORMATION
);
219 return STATUS_SUCCESS
;
224 NtfsGetSteamInformation(PNTFS_FCB Fcb
,
225 PDEVICE_EXTENSION DeviceExt
,
226 PFILE_STREAM_INFORMATION StreamInfo
,
230 FIND_ATTR_CONTXT Context
;
231 PNTFS_ATTR_RECORD Attribute
;
232 NTSTATUS Status
, BrowseStatus
;
233 PFILE_RECORD_HEADER FileRecord
;
234 PFILE_STREAM_INFORMATION CurrentInfo
= StreamInfo
, Previous
= NULL
;
236 if (*BufferLength
< sizeof(FILE_STREAM_INFORMATION
))
237 return STATUS_BUFFER_TOO_SMALL
;
239 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, DeviceExt
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
240 if (FileRecord
== NULL
)
242 DPRINT1("Not enough memory!\n");
243 return STATUS_INSUFFICIENT_RESOURCES
;
246 Status
= ReadFileRecord(DeviceExt
, Fcb
->MFTIndex
, FileRecord
);
247 if (!NT_SUCCESS(Status
))
249 DPRINT1("Can't find record!\n");
250 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
254 BrowseStatus
= FindFirstAttribute(&Context
, DeviceExt
, FileRecord
, FALSE
, &Attribute
);
255 while (NT_SUCCESS(BrowseStatus
))
257 if (Attribute
->Type
== AttributeData
)
259 CurrentSize
= FIELD_OFFSET(FILE_STREAM_INFORMATION
, StreamName
) + Attribute
->NameLength
* sizeof(WCHAR
) + wcslen(L
"::$DATA") * sizeof(WCHAR
);
261 if (CurrentSize
> *BufferLength
)
263 Status
= STATUS_BUFFER_OVERFLOW
;
267 CurrentInfo
->NextEntryOffset
= 0;
268 CurrentInfo
->StreamNameLength
= (Attribute
->NameLength
+ wcslen(L
"::$DATA")) * sizeof(WCHAR
);
269 CurrentInfo
->StreamSize
.QuadPart
= AttributeDataLength(Attribute
);
270 CurrentInfo
->StreamAllocationSize
.QuadPart
= AttributeAllocatedLength(Attribute
);
271 CurrentInfo
->StreamName
[0] = L
':';
272 RtlMoveMemory(&CurrentInfo
->StreamName
[1], (PWCHAR
)((ULONG_PTR
)Attribute
+ Attribute
->NameOffset
), CurrentInfo
->StreamNameLength
);
273 RtlMoveMemory(&CurrentInfo
->StreamName
[Attribute
->NameLength
+ 1], L
":$DATA", sizeof(L
":$DATA") - sizeof(UNICODE_NULL
));
275 if (Previous
!= NULL
)
277 Previous
->NextEntryOffset
= (ULONG_PTR
)CurrentInfo
- (ULONG_PTR
)Previous
;
279 Previous
= CurrentInfo
;
280 CurrentInfo
= (PFILE_STREAM_INFORMATION
)((ULONG_PTR
)CurrentInfo
+ CurrentSize
);
281 *BufferLength
-= CurrentSize
;
284 BrowseStatus
= FindNextAttribute(&Context
, &Attribute
);
287 FindCloseAttribute(&Context
);
288 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
293 * FUNCTION: Retrieve the specified file information
296 NtfsQueryInformation(PNTFS_IRP_CONTEXT IrpContext
)
298 FILE_INFORMATION_CLASS FileInformationClass
;
299 PIO_STACK_LOCATION Stack
;
300 PFILE_OBJECT FileObject
;
305 PDEVICE_OBJECT DeviceObject
;
306 NTSTATUS Status
= STATUS_SUCCESS
;
308 DPRINT1("NtfsQueryInformation(%p)\n", IrpContext
);
310 Irp
= IrpContext
->Irp
;
311 Stack
= IrpContext
->Stack
;
312 DeviceObject
= IrpContext
->DeviceObject
;
313 FileInformationClass
= Stack
->Parameters
.QueryFile
.FileInformationClass
;
314 FileObject
= IrpContext
->FileObject
;
315 Fcb
= FileObject
->FsContext
;
317 SystemBuffer
= Irp
->AssociatedIrp
.SystemBuffer
;
318 BufferLength
= Stack
->Parameters
.QueryFile
.Length
;
320 if (!ExAcquireResourceSharedLite(&Fcb
->MainResource
,
321 BooleanFlagOn(IrpContext
->Flags
, IRPCONTEXT_CANWAIT
)))
323 return NtfsMarkIrpContextForQueue(IrpContext
);
326 switch (FileInformationClass
)
328 case FileStandardInformation
:
329 Status
= NtfsGetStandardInformation(Fcb
,
335 case FilePositionInformation
:
336 Status
= NtfsGetPositionInformation(FileObject
,
341 case FileBasicInformation
:
342 Status
= NtfsGetBasicInformation(FileObject
,
349 case FileNameInformation
:
350 Status
= NtfsGetNameInformation(FileObject
,
357 case FileInternalInformation
:
358 Status
= NtfsGetInternalInformation(Fcb
,
363 case FileNetworkOpenInformation
:
364 Status
= NtfsGetNetworkOpenInformation(Fcb
,
365 DeviceObject
->DeviceExtension
,
370 case FileStreamInformation
:
371 Status
= NtfsGetSteamInformation(Fcb
,
372 DeviceObject
->DeviceExtension
,
377 case FileAlternateNameInformation
:
378 case FileAllInformation
:
379 DPRINT1("Unimplemented information class %u\n", FileInformationClass
);
380 Status
= STATUS_NOT_IMPLEMENTED
;
384 DPRINT1("Unimplemented information class %u\n", FileInformationClass
);
385 Status
= STATUS_INVALID_PARAMETER
;
388 ExReleaseResourceLite(&Fcb
->MainResource
);
390 if (NT_SUCCESS(Status
))
391 Irp
->IoStatus
.Information
=
392 Stack
->Parameters
.QueryFile
.Length
- BufferLength
;
394 Irp
->IoStatus
.Information
= 0;
400 * @name NtfsSetEndOfFile
403 * Sets the end of file (file size) for a given file.
406 * Pointer to an NTFS_FCB which describes the target file. Fcb->MainResource should have been
407 * acquired with ExAcquireResourceSharedLite().
410 * Pointer to a FILE_OBJECT describing the target file.
413 * Points to the target disk's DEVICE_EXTENSION
416 * ULONG describing the flags of the original IRP request (Irp->Flags).
418 * @param CaseSensitive
419 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
420 * if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
423 * Pointer to a LARGE_INTEGER which indicates the new end of file (file size).
426 * STATUS_SUCCESS if successful,
427 * STATUS_USER_MAPPED_FILE if trying to truncate a file but MmCanFileBeTruncated() returned false,
428 * STATUS_OBJECT_NAME_NOT_FOUND if there was no $DATA attribute associated with the target file,
429 * STATUS_INVALID_PARAMETER if there was no $FILENAME attribute associated with the target file,
430 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed,
431 * STATUS_ACCESS_DENIED if target file is a volume or if paging is involved.
433 * @remarks As this function sets the size of a file at the file-level
434 * (and not at the attribute level) it's not recommended to use this
435 * function alongside functions that operate on the data attribute directly.
439 NtfsSetEndOfFile(PNTFS_FCB Fcb
,
440 PFILE_OBJECT FileObject
,
441 PDEVICE_EXTENSION DeviceExt
,
443 BOOLEAN CaseSensitive
,
444 PLARGE_INTEGER NewFileSize
)
446 LARGE_INTEGER CurrentFileSize
;
447 PFILE_RECORD_HEADER FileRecord
;
448 PNTFS_ATTR_CONTEXT DataContext
;
449 ULONG AttributeOffset
;
450 NTSTATUS Status
= STATUS_SUCCESS
;
451 ULONGLONG AllocationSize
;
452 PFILENAME_ATTRIBUTE fileNameAttribute
;
453 ULONGLONG ParentMFTId
;
454 UNICODE_STRING filename
;
457 // Allocate non-paged memory for the file record
458 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, DeviceExt
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
459 if (FileRecord
== NULL
)
461 DPRINT1("Couldn't allocate memory for file record!");
462 return STATUS_INSUFFICIENT_RESOURCES
;
465 // read the file record
466 DPRINT("Reading file record...\n");
467 Status
= ReadFileRecord(DeviceExt
, Fcb
->MFTIndex
, FileRecord
);
468 if (!NT_SUCCESS(Status
))
470 // We couldn't get the file's record. Free the memory and return the error
471 DPRINT1("Can't find record for %wS!\n", Fcb
->ObjectName
);
472 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
476 DPRINT("Found record for %wS\n", Fcb
->ObjectName
);
478 CurrentFileSize
.QuadPart
= NtfsGetFileSize(DeviceExt
, FileRecord
, L
"", 0, (PULONGLONG
)&CurrentFileSize
);
480 // Are we trying to decrease the file size?
481 if (NewFileSize
->QuadPart
< CurrentFileSize
.QuadPart
)
483 // Is the file mapped?
484 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
,
487 DPRINT1("Couldn't decrease file size!\n");
488 return STATUS_USER_MAPPED_FILE
;
492 // Find the attribute with the data stream for our file
493 DPRINT("Finding Data Attribute...\n");
494 Status
= FindAttribute(DeviceExt
,
502 // Did we fail to find the attribute?
503 if (!NT_SUCCESS(Status
))
505 DPRINT1("No '%S' data stream associated with file!\n", Fcb
->Stream
);
506 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
510 // Get the size of the data attribute
511 CurrentFileSize
.QuadPart
= AttributeDataLength(&DataContext
->Record
);
513 // Are we enlarging the attribute?
514 if (NewFileSize
->QuadPart
> CurrentFileSize
.QuadPart
)
516 // is increasing the stream size not allowed?
517 if ((Fcb
->Flags
& FCB_IS_VOLUME
) ||
518 (IrpFlags
& IRP_PAGING_IO
))
520 // TODO - just fail for now
521 ReleaseAttributeContext(DataContext
);
522 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
523 return STATUS_ACCESS_DENIED
;
527 // set the attribute data length
528 Status
= SetAttributeDataLength(FileObject
, Fcb
, DataContext
, AttributeOffset
, FileRecord
, NewFileSize
);
529 if (!NT_SUCCESS(Status
))
531 ReleaseAttributeContext(DataContext
);
532 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
536 // now we need to update this file's size in every directory index entry that references it
537 // TODO: expand to work with every filename / hardlink stored in the file record.
538 fileNameAttribute
= GetBestFileNameFromRecord(Fcb
->Vcb
, FileRecord
);
539 if (fileNameAttribute
== NULL
)
541 DPRINT1("Unable to find FileName attribute associated with file!\n");
542 return STATUS_INVALID_PARAMETER
;
545 ParentMFTId
= fileNameAttribute
->DirectoryFileReferenceNumber
& NTFS_MFT_MASK
;
547 filename
.Buffer
= fileNameAttribute
->Name
;
548 filename
.Length
= fileNameAttribute
->NameLength
* sizeof(WCHAR
);
549 filename
.MaximumLength
= filename
.Length
;
551 AllocationSize
= ROUND_UP(NewFileSize
->QuadPart
, Fcb
->Vcb
->NtfsInfo
.BytesPerCluster
);
553 Status
= UpdateFileNameRecord(Fcb
->Vcb
,
557 NewFileSize
->QuadPart
,
561 ReleaseAttributeContext(DataContext
);
562 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
568 * @name NtfsSetInformation
571 * Sets the specified file information.
574 * Points to an NTFS_IRP_CONTEXT which describes the set operation
577 * STATUS_SUCCESS if successful,
578 * STATUS_NOT_IMPLEMENTED if trying to set an unimplemented information class,
579 * STATUS_USER_MAPPED_FILE if trying to truncate a file but MmCanFileBeTruncated() returned false,
580 * STATUS_OBJECT_NAME_NOT_FOUND if there was no $DATA attribute associated with the target file,
581 * STATUS_INVALID_PARAMETER if there was no $FILENAME attribute associated with the target file,
582 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed,
583 * STATUS_ACCESS_DENIED if target file is a volume or if paging is involved.
585 * @remarks Called by NtfsDispatch() in response to an IRP_MJ_SET_INFORMATION request.
586 * Only the FileEndOfFileInformation InformationClass is fully implemented. FileAllocationInformation
587 * is a hack and not a true implementation, but it's enough to make SetEndOfFile() work.
588 * All other information classes are TODO.
592 NtfsSetInformation(PNTFS_IRP_CONTEXT IrpContext
)
594 FILE_INFORMATION_CLASS FileInformationClass
;
595 PIO_STACK_LOCATION Stack
;
596 PDEVICE_EXTENSION DeviceExt
;
597 PFILE_OBJECT FileObject
;
602 PDEVICE_OBJECT DeviceObject
;
603 NTSTATUS Status
= STATUS_NOT_IMPLEMENTED
;
605 DPRINT1("NtfsSetInformation(%p)\n", IrpContext
);
607 Irp
= IrpContext
->Irp
;
608 Stack
= IrpContext
->Stack
;
609 DeviceObject
= IrpContext
->DeviceObject
;
610 DeviceExt
= DeviceObject
->DeviceExtension
;
611 FileInformationClass
= Stack
->Parameters
.QueryFile
.FileInformationClass
;
612 FileObject
= IrpContext
->FileObject
;
613 Fcb
= FileObject
->FsContext
;
615 SystemBuffer
= Irp
->AssociatedIrp
.SystemBuffer
;
616 BufferLength
= Stack
->Parameters
.QueryFile
.Length
;
618 if (!ExAcquireResourceSharedLite(&Fcb
->MainResource
,
619 BooleanFlagOn(IrpContext
->Flags
, IRPCONTEXT_CANWAIT
)))
621 return NtfsMarkIrpContextForQueue(IrpContext
);
624 switch (FileInformationClass
)
626 PFILE_END_OF_FILE_INFORMATION EndOfFileInfo
;
628 /* TODO: Allocation size is not actually the same as file end for NTFS,
629 however, few applications are likely to make the distinction. */
630 case FileAllocationInformation
:
631 DPRINT1("FIXME: Using hacky method of setting FileAllocationInformation.\n");
632 case FileEndOfFileInformation
:
633 EndOfFileInfo
= (PFILE_END_OF_FILE_INFORMATION
)SystemBuffer
;
634 Status
= NtfsSetEndOfFile(Fcb
,
638 (Stack
->Flags
& SL_CASE_SENSITIVE
),
639 &EndOfFileInfo
->EndOfFile
);
642 // TODO: all other information classes
645 DPRINT1("FIXME: Unimplemented information class %u\n", FileInformationClass
);
646 Status
= STATUS_NOT_IMPLEMENTED
;
649 ExReleaseResourceLite(&Fcb
->MainResource
);
651 if (NT_SUCCESS(Status
))
652 Irp
->IoStatus
.Information
=
653 Stack
->Parameters
.QueryFile
.Length
- BufferLength
;
655 Irp
->IoStatus
.Information
= 0;