summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/EFI/Firmware/FatPkg/EnhancedFatDxe/FileSpace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/FatPkg/EnhancedFatDxe/FileSpace.c')
-rw-r--r--src/VBox/Devices/EFI/Firmware/FatPkg/EnhancedFatDxe/FileSpace.c744
1 files changed, 744 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/FatPkg/EnhancedFatDxe/FileSpace.c b/src/VBox/Devices/EFI/Firmware/FatPkg/EnhancedFatDxe/FileSpace.c
new file mode 100644
index 00000000..78b26a58
--- /dev/null
+++ b/src/VBox/Devices/EFI/Firmware/FatPkg/EnhancedFatDxe/FileSpace.c
@@ -0,0 +1,744 @@
+/** @file
+ Routines dealing with disk spaces and FAT table entries.
+
+Copyright (c) 2005 - 2013, Intel Corporation. All rights reserved.<BR>
+SPDX-License-Identifier: BSD-2-Clause-Patent
+
+
+
+**/
+
+#include "Fat.h"
+
+
+/**
+
+ Get the FAT entry of the volume, which is identified with the Index.
+
+ @param Volume - FAT file system volume.
+ @param Index - The index of the FAT entry of the volume.
+
+ @return The buffer of the FAT entry
+
+**/
+STATIC
+VOID *
+FatLoadFatEntry (
+ IN FAT_VOLUME *Volume,
+ IN UINTN Index
+ )
+{
+ UINTN Pos;
+ EFI_STATUS Status;
+
+ if (Index > (Volume->MaxCluster + 1)) {
+ Volume->FatEntryBuffer = (UINT32) -1;
+ return &Volume->FatEntryBuffer;
+ }
+ //
+ // Compute buffer position needed
+ //
+ switch (Volume->FatType) {
+ case Fat12:
+ Pos = FAT_POS_FAT12 (Index);
+ break;
+
+ case Fat16:
+ Pos = FAT_POS_FAT16 (Index);
+ break;
+
+ default:
+ Pos = FAT_POS_FAT32 (Index);
+ }
+ //
+ // Set the position and read the buffer
+ //
+ Volume->FatEntryPos = Volume->FatPos + Pos;
+ Status = FatDiskIo (
+ Volume,
+ ReadFat,
+ Volume->FatEntryPos,
+ Volume->FatEntrySize,
+ &Volume->FatEntryBuffer,
+ NULL
+ );
+ if (EFI_ERROR (Status)) {
+ Volume->FatEntryBuffer = (UINT32) -1;
+ }
+
+ return &Volume->FatEntryBuffer;
+}
+
+/**
+
+ Get the FAT entry value of the volume, which is identified with the Index.
+
+ @param Volume - FAT file system volume.
+ @param Index - The index of the FAT entry of the volume.
+
+ @return The value of the FAT entry.
+
+**/
+STATIC
+UINTN
+FatGetFatEntry (
+ IN FAT_VOLUME *Volume,
+ IN UINTN Index
+ )
+{
+ VOID *Pos;
+ UINT8 *En12;
+ UINT16 *En16;
+ UINT32 *En32;
+ UINTN Accum;
+
+ Pos = FatLoadFatEntry (Volume, Index);
+
+ if (Index > (Volume->MaxCluster + 1)) {
+ return (UINTN) -1;
+ }
+
+ switch (Volume->FatType) {
+ case Fat12:
+ En12 = Pos;
+ Accum = En12[0] | (En12[1] << 8);
+ Accum = FAT_ODD_CLUSTER_FAT12 (Index) ? (Accum >> 4) : (Accum & FAT_CLUSTER_MASK_FAT12);
+ Accum = Accum | ((Accum >= FAT_CLUSTER_SPECIAL_FAT12) ? FAT_CLUSTER_SPECIAL_EXT : 0);
+ break;
+
+ case Fat16:
+ En16 = Pos;
+ Accum = *En16;
+ Accum = Accum | ((Accum >= FAT_CLUSTER_SPECIAL_FAT16) ? FAT_CLUSTER_SPECIAL_EXT : 0);
+ break;
+
+ default:
+ En32 = Pos;
+ Accum = *En32 & FAT_CLUSTER_MASK_FAT32;
+ Accum = Accum | ((Accum >= FAT_CLUSTER_SPECIAL_FAT32) ? FAT_CLUSTER_SPECIAL_EXT : 0);
+ }
+
+ return Accum;
+}
+
+/**
+
+ Set the FAT entry value of the volume, which is identified with the Index.
+
+ @param Volume - FAT file system volume.
+ @param Index - The index of the FAT entry of the volume.
+ @param Value - The new value of the FAT entry.
+
+ @retval EFI_SUCCESS - Set the new FAT entry value successfully.
+ @retval EFI_VOLUME_CORRUPTED - The FAT type of the volume is error.
+ @return other - An error occurred when operation the FAT entries.
+
+**/
+STATIC
+EFI_STATUS
+FatSetFatEntry (
+ IN FAT_VOLUME *Volume,
+ IN UINTN Index,
+ IN UINTN Value
+ )
+{
+ VOID *Pos;
+ UINT8 *En12;
+ UINT16 *En16;
+ UINT32 *En32;
+ UINTN Accum;
+ EFI_STATUS Status;
+ UINTN OriginalVal;
+
+ if (Index < FAT_MIN_CLUSTER) {
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ OriginalVal = FatGetFatEntry (Volume, Index);
+ if (Value == FAT_CLUSTER_FREE && OriginalVal != FAT_CLUSTER_FREE) {
+ Volume->FatInfoSector.FreeInfo.ClusterCount += 1;
+ if (Index < Volume->FatInfoSector.FreeInfo.NextCluster) {
+ Volume->FatInfoSector.FreeInfo.NextCluster = (UINT32) Index;
+ }
+ } else if (Value != FAT_CLUSTER_FREE && OriginalVal == FAT_CLUSTER_FREE) {
+ if (Volume->FatInfoSector.FreeInfo.ClusterCount != 0) {
+ Volume->FatInfoSector.FreeInfo.ClusterCount -= 1;
+ }
+ }
+ //
+ // Make sure the entry is in memory
+ //
+ Pos = FatLoadFatEntry (Volume, Index);
+
+ //
+ // Update the value
+ //
+ switch (Volume->FatType) {
+ case Fat12:
+ En12 = Pos;
+ Accum = En12[0] | (En12[1] << 8);
+ Value = Value & FAT_CLUSTER_MASK_FAT12;
+
+ if (FAT_ODD_CLUSTER_FAT12 (Index)) {
+ Accum = (Value << 4) | (Accum & 0xF);
+ } else {
+ Accum = Value | (Accum & FAT_CLUSTER_UNMASK_FAT12);
+ }
+
+ En12[0] = (UINT8) (Accum & 0xFF);
+ En12[1] = (UINT8) (Accum >> 8);
+ break;
+
+ case Fat16:
+ En16 = Pos;
+ *En16 = (UINT16) Value;
+ break;
+
+ default:
+ En32 = Pos;
+ *En32 = (*En32 & FAT_CLUSTER_UNMASK_FAT32) | (UINT32) (Value & FAT_CLUSTER_MASK_FAT32);
+ }
+ //
+ // If the volume's dirty bit is not set, set it now
+ //
+ if (!Volume->FatDirty && Volume->FatType != Fat12) {
+ Volume->FatDirty = TRUE;
+ FatAccessVolumeDirty (Volume, WriteFat, &Volume->DirtyValue);
+ }
+ //
+ // Write the updated fat entry value to the volume
+ // The fat is the first fat, and other fat will be in sync
+ // when the FAT cache flush back.
+ //
+ Status = FatDiskIo (
+ Volume,
+ WriteFat,
+ Volume->FatEntryPos,
+ Volume->FatEntrySize,
+ &Volume->FatEntryBuffer,
+ NULL
+ );
+ return Status;
+}
+
+/**
+
+ Free the cluster chain.
+
+ @param Volume - FAT file system volume.
+ @param Cluster - The first cluster of cluster chain.
+
+ @retval EFI_SUCCESS - The cluster chain is freed successfully.
+ @retval EFI_VOLUME_CORRUPTED - There are errors in the file's clusters.
+
+**/
+STATIC
+EFI_STATUS
+FatFreeClusters (
+ IN FAT_VOLUME *Volume,
+ IN UINTN Cluster
+ )
+{
+ UINTN LastCluster;
+
+ while (!FAT_END_OF_FAT_CHAIN (Cluster)) {
+ if (Cluster == FAT_CLUSTER_FREE || Cluster >= FAT_CLUSTER_SPECIAL) {
+
+ DEBUG ((EFI_D_INIT | EFI_D_ERROR, "FatShrinkEof: cluster chain corrupt\n"));
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ LastCluster = Cluster;
+ Cluster = FatGetFatEntry (Volume, Cluster);
+ FatSetFatEntry (Volume, LastCluster, FAT_CLUSTER_FREE);
+ }
+
+ return EFI_SUCCESS;
+}
+
+/**
+
+ Allocate a free cluster and return the cluster index.
+
+ @param Volume - FAT file system volume.
+
+ @return The index of the free cluster
+
+**/
+STATIC
+UINTN
+FatAllocateCluster (
+ IN FAT_VOLUME *Volume
+ )
+{
+ UINTN Cluster;
+
+ //
+ // Start looking at FatFreePos for the next unallocated cluster
+ //
+ if (Volume->DiskError) {
+ return (UINTN) FAT_CLUSTER_LAST;
+ }
+
+ for (;;) {
+ //
+ // If the end of the list, return no available cluster
+ //
+ if (Volume->FatInfoSector.FreeInfo.NextCluster > (Volume->MaxCluster + 1)) {
+ if (Volume->FreeInfoValid && 0 < (INT32) (Volume->FatInfoSector.FreeInfo.ClusterCount)) {
+ Volume->FreeInfoValid = FALSE;
+ }
+
+ FatComputeFreeInfo (Volume);
+ if (Volume->FatInfoSector.FreeInfo.NextCluster > (Volume->MaxCluster + 1)) {
+ return (UINTN) FAT_CLUSTER_LAST;
+ }
+ }
+
+ Cluster = FatGetFatEntry (Volume, Volume->FatInfoSector.FreeInfo.NextCluster);
+ if (Cluster == FAT_CLUSTER_FREE) {
+ break;
+ }
+ //
+ // Try the next cluster
+ //
+ Volume->FatInfoSector.FreeInfo.NextCluster += 1;
+ }
+
+ Cluster = Volume->FatInfoSector.FreeInfo.NextCluster;
+ Volume->FatInfoSector.FreeInfo.NextCluster += 1;
+ return Cluster;
+}
+
+/**
+
+ Count the number of clusters given a size.
+
+ @param Volume - The file system volume.
+ @param Size - The size in bytes.
+
+ @return The number of the clusters.
+
+**/
+STATIC
+UINTN
+FatSizeToClusters (
+ IN FAT_VOLUME *Volume,
+ IN UINTN Size
+ )
+{
+ UINTN Clusters;
+
+ Clusters = Size >> Volume->ClusterAlignment;
+ if ((Size & (Volume->ClusterSize - 1)) > 0) {
+ Clusters += 1;
+ }
+
+ return Clusters;
+}
+
+/**
+
+ Shrink the end of the open file base on the file size.
+
+ @param OFile - The open file.
+
+ @retval EFI_SUCCESS - Shrinked successfully.
+ @retval EFI_VOLUME_CORRUPTED - There are errors in the file's clusters.
+
+**/
+EFI_STATUS
+FatShrinkEof (
+ IN FAT_OFILE *OFile
+ )
+{
+ FAT_VOLUME *Volume;
+ UINTN NewSize;
+ UINTN CurSize;
+ UINTN Cluster;
+ UINTN LastCluster;
+
+ Volume = OFile->Volume;
+ ASSERT_VOLUME_LOCKED (Volume);
+
+ NewSize = FatSizeToClusters (Volume, OFile->FileSize);
+
+ //
+ // Find the address of the last cluster
+ //
+ Cluster = OFile->FileCluster;
+ LastCluster = FAT_CLUSTER_FREE;
+
+ if (NewSize != 0) {
+
+ for (CurSize = 0; CurSize < NewSize; CurSize++) {
+ if (Cluster == FAT_CLUSTER_FREE || Cluster >= FAT_CLUSTER_SPECIAL) {
+
+ DEBUG ((EFI_D_INIT | EFI_D_ERROR, "FatShrinkEof: cluster chain corrupt\n"));
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ LastCluster = Cluster;
+ Cluster = FatGetFatEntry (Volume, Cluster);
+ }
+
+ FatSetFatEntry (Volume, LastCluster, (UINTN) FAT_CLUSTER_LAST);
+
+ } else {
+ //
+ // Check to see if the file is already completely truncated
+ //
+ if (Cluster == FAT_CLUSTER_FREE) {
+ return EFI_SUCCESS;
+ }
+ //
+ // The file is being completely truncated.
+ //
+ OFile->FileCluster = FAT_CLUSTER_FREE;
+ }
+ //
+ // Set CurrentCluster == FileCluster
+ // to force a recalculation of Position related stuffs
+ //
+ OFile->FileCurrentCluster = OFile->FileCluster;
+ OFile->FileLastCluster = LastCluster;
+ OFile->Dirty = TRUE;
+ //
+ // Free the remaining cluster chain
+ //
+ return FatFreeClusters (Volume, Cluster);
+}
+
+/**
+
+ Grow the end of the open file base on the NewSizeInBytes.
+
+ @param OFile - The open file.
+ @param NewSizeInBytes - The new size in bytes of the open file.
+
+ @retval EFI_SUCCESS - The file is grown successfully.
+ @retval EFI_UNSUPPORTED - The file size is larger than 4GB.
+ @retval EFI_VOLUME_CORRUPTED - There are errors in the files' clusters.
+ @retval EFI_VOLUME_FULL - The volume is full and can not grow the file.
+
+**/
+EFI_STATUS
+FatGrowEof (
+ IN FAT_OFILE *OFile,
+ IN UINT64 NewSizeInBytes
+ )
+{
+ FAT_VOLUME *Volume;
+ EFI_STATUS Status;
+ UINTN Cluster;
+ UINTN CurSize;
+ UINTN NewSize;
+ UINTN LastCluster;
+ UINTN NewCluster;
+ UINTN ClusterCount;
+
+ //
+ // For FAT file system, the max file is 4GB.
+ //
+ if (NewSizeInBytes > 0x0FFFFFFFFL) {
+ return EFI_UNSUPPORTED;
+ }
+
+ Volume = OFile->Volume;
+ ASSERT_VOLUME_LOCKED (Volume);
+ //
+ // If the file is already large enough, do nothing
+ //
+ CurSize = FatSizeToClusters (Volume, OFile->FileSize);
+ NewSize = FatSizeToClusters (Volume, (UINTN) NewSizeInBytes);
+
+ if (CurSize < NewSize) {
+ //
+ // If we haven't found the files last cluster do it now
+ //
+ if ((OFile->FileCluster != 0) && (OFile->FileLastCluster == 0)) {
+ Cluster = OFile->FileCluster;
+ ClusterCount = 0;
+
+ while (!FAT_END_OF_FAT_CHAIN (Cluster)) {
+ if (Cluster < FAT_MIN_CLUSTER || Cluster > Volume->MaxCluster + 1) {
+
+ DEBUG (
+ (EFI_D_INIT | EFI_D_ERROR,
+ "FatGrowEof: cluster chain corrupt\n")
+ );
+ Status = EFI_VOLUME_CORRUPTED;
+ goto Done;
+ }
+
+ ClusterCount++;
+ OFile->FileLastCluster = Cluster;
+ Cluster = FatGetFatEntry (Volume, Cluster);
+ }
+
+ if (ClusterCount != CurSize) {
+ DEBUG (
+ (EFI_D_INIT | EFI_D_ERROR,
+ "FatGrowEof: cluster chain size does not match file size\n")
+ );
+ Status = EFI_VOLUME_CORRUPTED;
+ goto Done;
+ }
+
+ }
+ //
+ // Loop until we've allocated enough space
+ //
+ LastCluster = OFile->FileLastCluster;
+
+ while (CurSize < NewSize) {
+ NewCluster = FatAllocateCluster (Volume);
+ if (FAT_END_OF_FAT_CHAIN (NewCluster)) {
+ if (LastCluster != FAT_CLUSTER_FREE) {
+ FatSetFatEntry (Volume, LastCluster, (UINTN) FAT_CLUSTER_LAST);
+ OFile->FileLastCluster = LastCluster;
+ }
+
+ Status = EFI_VOLUME_FULL;
+ goto Done;
+ }
+
+ if (NewCluster < FAT_MIN_CLUSTER || NewCluster > Volume->MaxCluster + 1) {
+ Status = EFI_VOLUME_CORRUPTED;
+ goto Done;
+ }
+
+ if (LastCluster != 0) {
+ FatSetFatEntry (Volume, LastCluster, NewCluster);
+ } else {
+ OFile->FileCluster = NewCluster;
+ OFile->FileCurrentCluster = NewCluster;
+ }
+
+ LastCluster = NewCluster;
+ CurSize += 1;
+
+ //
+ // Terminate the cluster list
+ //
+ // Note that we must do this EVERY time we allocate a cluster, because
+ // FatAllocateCluster scans the FAT looking for a free cluster and
+ // "LastCluster" is no longer free! Usually, FatAllocateCluster will
+ // start looking with the cluster after "LastCluster"; however, when
+ // there is only one free cluster left, it will find "LastCluster"
+ // a second time. There are other, less predictable scenarios
+ // where this could happen, as well.
+ //
+ FatSetFatEntry (Volume, LastCluster, (UINTN) FAT_CLUSTER_LAST);
+ OFile->FileLastCluster = LastCluster;
+ }
+ }
+
+ OFile->FileSize = (UINTN) NewSizeInBytes;
+ OFile->Dirty = TRUE;
+ return EFI_SUCCESS;
+
+Done:
+ FatShrinkEof (OFile);
+ return Status;
+}
+
+/**
+
+ Seek OFile to requested position, and calculate the number of
+ consecutive clusters from the position in the file
+
+ @param OFile - The open file.
+ @param Position - The file's position which will be accessed.
+ @param PosLimit - The maximum length current reading/writing may access
+
+ @retval EFI_SUCCESS - Set the info successfully.
+ @retval EFI_VOLUME_CORRUPTED - Cluster chain corrupt.
+
+**/
+EFI_STATUS
+FatOFilePosition (
+ IN FAT_OFILE *OFile,
+ IN UINTN Position,
+ IN UINTN PosLimit
+ )
+{
+ FAT_VOLUME *Volume;
+ UINTN ClusterSize;
+ UINTN Cluster;
+ UINTN StartPos;
+ UINTN Run;
+
+ Volume = OFile->Volume;
+ ClusterSize = Volume->ClusterSize;
+
+ ASSERT_VOLUME_LOCKED (Volume);
+
+ //
+ // If this is the fixed root dir, then compute its position
+ // from its fixed info in the fat bpb
+ //
+ if (OFile->IsFixedRootDir) {
+ OFile->PosDisk = Volume->RootPos + Position;
+ Run = OFile->FileSize - Position;
+ } else {
+ //
+ // Run the file's cluster chain to find the current position
+ // If possible, run from the current cluster rather than
+ // start from beginning
+ // Assumption: OFile->Position is always consistent with
+ // OFile->FileCurrentCluster.
+ // OFile->Position is not modified outside this function;
+ // OFile->FileCurrentCluster is modified outside this function
+ // to be the same as OFile->FileCluster
+ // when OFile->FileCluster is updated, so make a check of this
+ // and invalidate the original OFile->Position in this case
+ //
+ Cluster = OFile->FileCurrentCluster;
+ StartPos = OFile->Position;
+ if (Position < StartPos || OFile->FileCluster == Cluster) {
+ StartPos = 0;
+ Cluster = OFile->FileCluster;
+ }
+
+ while (StartPos + ClusterSize <= Position) {
+ StartPos += ClusterSize;
+ if (Cluster == FAT_CLUSTER_FREE || (Cluster >= FAT_CLUSTER_SPECIAL)) {
+ DEBUG ((EFI_D_INIT | EFI_D_ERROR, "FatOFilePosition:"" cluster chain corrupt\n"));
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ Cluster = FatGetFatEntry (Volume, Cluster);
+ }
+
+ if (Cluster < FAT_MIN_CLUSTER || Cluster > Volume->MaxCluster + 1) {
+ return EFI_VOLUME_CORRUPTED;
+ }
+
+ OFile->PosDisk = Volume->FirstClusterPos +
+ LShiftU64 (Cluster - FAT_MIN_CLUSTER, Volume->ClusterAlignment) +
+ Position - StartPos;
+ OFile->FileCurrentCluster = Cluster;
+ OFile->Position = StartPos;
+
+ //
+ // Compute the number of consecutive clusters in the file
+ //
+ Run = StartPos + ClusterSize - Position;
+ if (!FAT_END_OF_FAT_CHAIN (Cluster)) {
+ while ((FatGetFatEntry (Volume, Cluster) == Cluster + 1) && Run < PosLimit) {
+ Run += ClusterSize;
+ Cluster += 1;
+ }
+ }
+ }
+
+ OFile->PosRem = Run;
+ return EFI_SUCCESS;
+}
+
+/**
+
+ Get the size of directory of the open file.
+
+ @param Volume - The File System Volume.
+ @param Cluster - The Starting cluster.
+
+ @return The physical size of the file starting at the input cluster, if there is error in the
+ cluster chain, the return value is 0.
+
+**/
+UINTN
+FatPhysicalDirSize (
+ IN FAT_VOLUME *Volume,
+ IN UINTN Cluster
+ )
+{
+ UINTN Size;
+ ASSERT_VOLUME_LOCKED (Volume);
+ //
+ // Run the cluster chain for the OFile
+ //
+ Size = 0;
+ //
+ // N.B. ".." directories on some media do not contain a starting
+ // cluster. In the case of "." or ".." we don't need the size anyway.
+ //
+ if (Cluster != 0) {
+ while (!FAT_END_OF_FAT_CHAIN (Cluster)) {
+ if (Cluster == FAT_CLUSTER_FREE || Cluster >= FAT_CLUSTER_SPECIAL) {
+ DEBUG (
+ (EFI_D_INIT | EFI_D_ERROR,
+ "FATDirSize: cluster chain corrupt\n")
+ );
+ return 0;
+ }
+
+ Size += Volume->ClusterSize;
+ Cluster = FatGetFatEntry (Volume, Cluster);
+ }
+ }
+
+ return Size;
+}
+
+/**
+
+ Get the physical size of a file on the disk.
+
+ @param Volume - The file system volume.
+ @param RealSize - The real size of a file.
+
+ @return The physical size of a file on the disk.
+
+**/
+UINT64
+FatPhysicalFileSize (
+ IN FAT_VOLUME *Volume,
+ IN UINTN RealSize
+ )
+{
+ UINTN ClusterSizeMask;
+ UINT64 PhysicalSize;
+ ClusterSizeMask = Volume->ClusterSize - 1;
+ PhysicalSize = (RealSize + ClusterSizeMask) & (~((UINT64) ClusterSizeMask));
+ return PhysicalSize;
+}
+
+/**
+
+ Update the free cluster info of FatInfoSector of the volume.
+
+ @param Volume - FAT file system volume.
+
+**/
+VOID
+FatComputeFreeInfo (
+ IN FAT_VOLUME *Volume
+ )
+{
+ UINTN Index;
+
+ //
+ // If we don't have valid info, compute it now
+ //
+ if (!Volume->FreeInfoValid) {
+
+ Volume->FreeInfoValid = TRUE;
+ Volume->FatInfoSector.FreeInfo.ClusterCount = 0;
+ for (Index = Volume->MaxCluster + 1; Index >= FAT_MIN_CLUSTER; Index--) {
+ if (Volume->DiskError) {
+ break;
+ }
+
+ if (FatGetFatEntry (Volume, Index) == FAT_CLUSTER_FREE) {
+ Volume->FatInfoSector.FreeInfo.ClusterCount += 1;
+ Volume->FatInfoSector.FreeInfo.NextCluster = (UINT32) Index;
+ }
+ }
+
+ Volume->FatInfoSector.Signature = FAT_INFO_SIGNATURE;
+ Volume->FatInfoSector.InfoBeginSignature = FAT_INFO_BEGIN_SIGNATURE;
+ Volume->FatInfoSector.InfoEndSignature = FAT_INFO_END_SIGNATURE;
+ }
+}