DOKAN ADA BINDINGS
version 1.1
by Dmitry A. Kazakov

(mailbox@dmitry-kazakov.de)
[Home]

This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As a special exception, if other files instantiate generics from this unit, or you link this unit with other files to produce an executable, this unit does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Public License.


Dokan is a user-space file system for Windows 32- and 64-bit. It consists of a driver and a library. The driver routes the I/O requests to the file system device to the library callback.

The software provided here is Ada bindings to the Dokan library. The bindings consist of thin and thick parts. The thin bindings closely follow the Dokan library entry points. As such they are very uncomfortable. The thick binding provide a higher-level access to the Dokan library. It is kept conform to the Ada 95, Ada 2005, Ada 2012 language standards.

Source distribution   dokan_1_1.tgz (tar + gzip)   [Download]

See also changes log.


[TOC][Next]

1. Thick bindings

The thick bindings are provided by the package Dokan.File_System.

[Back][TOC][Next]

1.1. File system

The type Abstract_File_System declared in Dokan.File_System represents a mounted instance of a file system, e.g. a volume:

type Abstract_File_System is
   abstract new
Ada.Finalization.Limited_Controlled with private;
type
Abstract_File_System_Ptr is access Abstract_File_System'Class;

The following operations are defined on it:

procedure Create_Directory
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Directory  : out Abstract_Directory_Ptr;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to create a directory. When the directory already exists ERROR_ALREADY_EXISTS is returned, which is not a fault. Name is the directory path (UTF-16). Process_ID identifies the process creating the directory. Directory is the directory object created. It is not null if the operation was successful. Error is the result Windows error code. Successful completion may return ERROR_SUCCESS or ERROR_ALREADY_EXISTS. When the file exists and is not a directory ERROR_DIRECTORY is returned.

procedure Create_File
          (  System         : in out Abstract_File_System;
             Name           : LPCWSTR;
             Process_ID     : ULONG;
             Desired_Access : NT_Access;
             Sharing        : File_Sharing_Mode;
             Disposition    : File_Open_Mode;
             Options        : File_Options;
             File           : out Abstract_Node_Ptr;
             Error          : out Error_Code
          )  is abstract;

This procedure is called to create a new file or open and an existing one. Name is the file path (UTF-16). Process_ID identifies the process creating the file. Desired_Access is the file access mode containing Windows access mask with bits like FILE_READ_DATA or FILE_READ_ATTRIBUTES. The parameter Sharing of Create_File specifies the file sharing mode. It can be an or-combination of sharing modes. It has the type:

type File_Sharing_Mode is mod 2**3;
Shared_Read   : constant File_Sharing_Mode := 2**0;
Shared_Write  : constant File_Sharing_Mode := 2**1;
Shared_Delete : constant File_Sharing_Mode := 2**2;

The function Image:

function Image (Mode : File_Sharing_Mode) return String;

returns textual representation of Mode. The parameter Disposition the file disposition. It is an enumeration type:

type File_Open_Mode is
     (  Overwrite,
        Create_New,
        Open_Or_Create,
        Open_Existing,
        Truncate
     );

The function Image:

function Image (Mode : File_Open_Mode) return String;

The following table specifies the behavior of Create_File depending on whether the file exists:

Mode File exists File does not exist
Disposition Error code Disposition Error code
Overwrite Truncate ERROR_ALREADY_EXISTS Create ERROR_SUCCESS
Create_New Fail ERROR_ALREADY_EXISTS Create ERROR_SUCCESS
Open_Or_Create Open ERROR_ALREADY_EXISTS Create ERROR_SUCCESS
Open_Existing Open ERROR_SUCCESS Fail ERROR_FILE_NOT_FOUND
Truncate Open ERROR_SUCCESS Fail ERROR_FILE_NOT_FOUND

The parameter Options specifies file opening options. It has the type:

type File_Options is mod 2**5;
Delete_On_Close   : constant File_Options := 2**0;
Write_Trough      : constant File_Options := 2**1;
Sequential_Scan   : constant File_Options := 2**2;
Random_Access     : constant File_Options := 2**3;
Overlapped_Access : constant File_Options := 2**4;

File is a pointer the newly created file object. When the operation fails it should be null. Otherwise it will be freed. The returned object is owned by the bindings. It will be freed when the file is closed. Error is the result Windows error code. The error code is negative when the operation has failed. It is zero on success and positive on warnings or additional information. When the path specifies a directory the implementation should fail with, e.g. with ERROR_INVALID_ACCESS.

procedure Check_Delete_Directory
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to verify if a directory can be deleted. When the directory is not empty the operation fails with ERROR_DIR_NOT_EMPTY. Name is the directory path (UTF-16). Process_ID identifies the process deleting the directory. Error is the result Windows error code. The result is ERROR_SUCCESS when the directory can be deleted. The operation does not delete the directory it will be actually deleted upon a call to Delete_Directory. Note that no directory node object is passed to the procedure because the directory need not to be open in order to perform the check.

procedure Check_Delete_File
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to verify if a file can be deleted. Name is the directory path (UTF-16). Process_ID identifies the process deleting the directory. Error is the result Windows error code. The result is ERROR_SUCCESS when the file can be deleted. The operation does not delete the file it will be actually deleted upon a call to Delete_File. Note that no file node object is passed to the procedure because the file need not to be open in order to perform the check.

procedure Delete_Directory
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to actually delete a directory after it was checked by a call to Check_Delete_Directory. Normally the operation should not fail because it was already checked. Name is the directory path (UTF-16). Process_ID identifies the process deleting the directory. Error is the result Windows error code.

procedure Delete_File
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to delete a file after it was checked by call to Check_Delete_File. Normally it should not fail because it already passed the check. Name is the file path (UTF-16). Process_ID identifies the process deleting the file. Error is result Windows error code.

procedure Do_Unmount
          (  System : in out Abstract_File_System
          )  is abstract;

This procedure is called when file system is being dismounted.

procedure Enable_Tracing
          (  System : in out Abstract_File_System;
             On     : Boolean
          );

This is used to enable or disable tracing in the bindings. Errors are always traced. When enabled by this procedure more tracing is done. Note that the Dokan library has tracing of its own. Dokan tracing is enabled when the volume is mounted. See also Get_Tracing.

procedure Finalize (System : in out Abstract_File_System);

This procedure is called upon object finalization. It dismounts the file system instance. Note that any derived type shall call this procedure from its implementation of Finalize when it overrides Finalize.

function Get_File_System_Flags
         (  System : Abstract_File_System
         )  return File_System_Flags is abstract;

This function returns the file system flags of the type File_System_Flags. See description of the Windows function GetVolumeInformation for further information.

function Get_File_System_Name
         (  System : Abstract_File_System
         )  return String is abstract;

This function returns the file system name in UTF-8 encoding. It can be any descriptive name.

function Get_Maximal_File_Length
         (  System : Abstract_File_System
         )  return Natural is abstract;

The function returns the maximal number of characters supported for a file name. Note that the value is in characters, not in bytes. The default implementation returns the value MAX_PATH.

procedure Get_Security
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Security   : SECURITY_INFORMATION;
             Descriptor : PSECURITY_DESCRIPTOR;
             Length     : in out ULONG;
             Error      : out Error_Code
          );

This procedure is called to obtain file or directory security. Process_ID identifies the process requesting information. Security specifies the parts of the security descriptor requests. Descriptor is a pointer to the buffer to receive the descriptor. Note that its actual length is specified by the parameter Length. It can be different from the nominal length of the type SECURITY_DESCRIPTOR as declared in Dokan.Win32API. When Descriptor is null or is too small, the procedure fails with ERROR_NOT_ENOUGH_MEMORY and sets the number of required bytes into Length. Upon success it must fill the buffer with the requested information in the so-called self-relative format and set Length to the actual length. Error is the result Windows error code.The package Dokan.Win32API provides a helper function CreateSecurity to ease creation of a security descriptor for newly created files and directories. The default implementation fails with ERROR_INVALID_FUNCTION in which case Dokan would use a default descriptor. See also Set_Security.

function Get_Tracing (System : Abstract_File_System) return Boolean;

This function returns true if tracing is enabled. See also Enable_Tracing.

function Get_Volume_Name
         (  System : Abstract_File_System
         )  return String is abstract;

This function returns the file system volume name in UTF-8 encoding.

function Get_Volume_Serial_Number
         (  System : Abstract_File_System
         )  return String is abstract;

This function returns the volume serial number.

procedure Get_Volume_Space
          (  System     : in out Abstract_File_System;
             Process_ID : ULONG;
             Total      : out Byte_Count;
             Free       : out Byte_Count;
             Available  : out Byte_Count
          );

This procedure is called to get file system statistics. Process_ID identifies the process requesting the file system space. The result may depend on the process if quotas are active, e.g. Available which is the number of bytes available for the user.

procedure Open_Directory
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Directory  : out Abstract_Directory_Ptr;
             Error      : out Error_Code
          );

This procedure is called to open a directory. Process_ID identifies the process opening the directory. Directory points to the newly created object of the type Abstract_Directory, or null upon failure. The directories are opened in order to list files in. Error is the result Windows error code. The default implementation returns ERROR_ACCESS_DENIED.

procedure Move
          (  System     : in out Abstract_File_System;
             Process_ID : ULONG;
             Old_Name   : LPCWSTR;
             New_Name   : LPCWSTR;
             Replace    : Boolean;
             Error      : out Error_Code
          );

This procedure is called to rename a file or directory. Process_ID identifies the process renaming the file or directory. Old_Name is the path of the file or directory to rename (UTF-16). New_Name is the new path (UTF-16). Replace is true if the operation is allowed to replace any existing files or directories. When Replace is false, then an attempt to replace an existing file must fail with ERROR_FILE_EXISTS. Error is the result Windows error code. The default implementation fails with ERROR_INVALID_NAME.

procedure Mount
          (  System       : in out Abstract_File_System;
             Mount_Point  : String;
             Options      : Option_Type := 0;
             Thread_Count : Natural     := 1
          );

The file system is initially not mounted. In order to mount it this procedure must be called. Mount_Point is the mounting point of the file system, e.g. a device letter like L:. Options is a combination of the Dokan library options defined in the package Dokan.Thin:

When Thread_Count is not 1, the file system operations are called concurrently from several system threads. In that case the implementation must protect the internal data from corruption. When Thread_Count is 5, some default number of threads is used. Additionally each instance of the file system runs a task that dispatches requests to the system. The task and threads are completed when the object is finalized. Use_Error is propagated when the file system is already mounted. Status_Error is propagated when there is no response from the dispatcher task.

procedure Set_Attributes
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Attributes : DWORD;
             Error      : out Error_Code
          );

This procedure is called to change file or directory attributes. The attribute values are combinations of constants starting with FILE_ATTRIBUTE_*. The bit FILE_ATTRIBUTE_NORMAL is ignored when used with other bits. Name is the file or directory path (UTF-16). Process_ID identifies the process setting attributes. Attributes is the file attributes to set. Error is the result Windows error code. The default implementation fails with ERROR_INVALID_DATA.

procedure Set_File_Time
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Created    : FILETIME;
             Accessed   : FILETIME;
             Written    : FILETIME;
             Error      : out Error_Code
          );

This procedure sets file or directory times. Name is the file or directory path (UTF-16). Process_ID identifies the process setting attributes. Attributes is the file attributes to set. File times must be ignored if they have values (0, 0). Error is the result Windows error code. The default implementation fails with ERROR_ACCESS_DENIED. For time conversions see From_Time and To_Time.

procedure Set_Security
          (  System     : in out Abstract_File_System;
             Name       : LPCWSTR;
             Process_ID : ULONG;
             Security   : SECURITY_INFORMATION;
             Descriptor : SECURITY_DESCRIPTOR;
             Error      : out Error_Code
          );

This procedure sets file or directory security. Name is the file or directory path (UTF-16). Process_ID identifies the process setting attributes. Security specifies the contents of the security descriptor specified by the parameter Descriptor. Typically an implementation would modify the file security using these parameters. Error is the result Windows error code. The default implementation fails with ERROR_INVALID_FUNCTION. See also Get_Security.

procedure Trace
          (  System : in out Abstract_File_System;
             Text   : String
          );
procedure
Trace
          (  System : in out Abstract_File_System;
             Text   : String;
             Error  : Exception_Occurrence
          );
procedure
Trace
          (  System : in out Abstract_File_System;
             Text   : String;
             Name   : LPCWSTR
          );
procedure
Trace
          (  System : in out Abstract_File_System;
             Text   : String;
             Name   : LPCWSTR;
             Error  : Exception_Occurrence
          );

This procedures are used to trace actions and errors. The default implementations do nothing.

procedure Unmount (System : in out Abstract_File_System);

This procedure is called to explicitly dismount the file system. Ultimately it calls to the abstract procedure Do_Unmount which actually does the work. The operation may take considerable time depending on the circumstances.

[Back][TOC][Next]

1.2. Abstract node

Abstract node represents an open file or directory of the file system. It is the abstract parent type for both files and directories:

type Abstract_Node is abstract
   new
Ada.Finalization.Limited_Controlled with private;
type
Abstract_Node_Ptr is access Abstract_Node'Class;

There is no need to check access rights in the implementations of the operation defined on the type and its descendants because normally access rights are checked before the file or directory is opened. The following operations are defined on the type:

procedure Close
          (  File       : in out Abstract_Node;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Delete     : Boolean;
             Error      : out Error_Code
          )  is abstract;

This procedure is called when to the open file or directory is closed. Name is the file or directory path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process. Delete is true if the file or directory is must be deleted when its finally closed. Error is the result Windows error code.

procedure Close
          (  File   : in out Abstract_Node;
             System : in out Abstract_File_System'Class;
             Error  : out Error_Code
          )  is abstract;

This variant of the procedure Close is called when the file remains open until dismounting the file system. In this case the file must be closed unconditionally. Error is the result Windows error code. The error code is ignored because there is no way to handle it.

procedure Finalize (File : in out Abstract_Node);

This procedure is called upon finalization of the object. If overridden the new implementation must call to the parent's one.

procedure Get_Information
          (  File       : in out Abstract_Node;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Attributes : out DWORD;
             Created    : out FILETIME;
             Accessed   : out FILETIME;
             Written    : out FILETIME;
             Size       : out Byte_Count;
             Links      : out DWORD;
             Index      : out File_Index;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to obtain information about the file or directory. Name is the file or directory path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process requesting information. Error is the result Windows error code. For time conversions see From_Time and To_Time.

[Back][TOC][Next]

1.3. Abstract directory

Abstract directory represents an open directory on the file system:

type Abstract_Directory is abstract
   new
Abstract_Node with private;
type
Abstract_Directory_Ptr is access Abstract_Directory'Class;

The following operations are defined on the type:

procedure Finalize (Directory : in out Abstract_Directory);

This procedure is called upon finalization of the object. If overridden the new implementation must call to the parent's one.

procedure Find
          (  Directory  : in out Abstract_Directory;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Fill       : FillFileData_Ptr;
             Info       : File_Info_Ptr;
             Error      : out Error_Code
          )  is abstract;

This procedure is called to enumerate files in the directory. Name is the directory path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process searching the directory. Fill is the procedure to call for each file found in the directory.  Fill is declared as follows:

type FillFileData_Ptr is access function
     (  Data : WIN32_FIND_DATAW;
        Info : File_Info_Ptr
     )  return int;
pragma Convention (Stdcall, FillFileData_Ptr);

Info is the value to pass to Fill. Its type is declared in Dokan.Thin. Here Data is the Windows data structure to filled with the file data. Info is the corresponding parameter passed to Find. The result is non-zero if the enumeration of files must be stopped. Error is the result Windows error code.

[Back][TOC][Next]

1.4. Abstract file

Abstract file represents an open file on the file system:

type Abstract_File is abstract
   new
Abstract_Node with private;
type
Abstract_File_Ptr is access Abstract_File'Class;

The following operations are defined on the type:

procedure Finalize (File : in out Abstract_File);

This procedure is called upon finalization of the object. If overridden the new implementation must call to the parent's one.

procedure Flush
          (  File       : in out Abstract_Directory;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Error      : out Error_Code
          );

This procedure is called to flush all file buffers. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process flushing the file. Error is the result Windows error code. The default implementation fails with ERROR_WRITE_FAULT.

procedure Lock
          (  File        : in out Abstract_Directory;
             Name        : LPCWSTR;
             System      : in out Abstract_File_System'Class;
             Process_ID  : ULONG;
             Byte_Offset : Byte_Count;
             Length      : Byte_Count;
             Error       : out Error_Code
          );

This procedure is called to lock a portion of the file. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process locking the file. Byte_Offset is the first byte to lock (zero-based). Length is the number of bytes to lock. Error is the result Windows error code. The default implementation fails with ERROR_INVALID_FUNCTION.

procedure Read
          (  File       : in out Abstract_Directory;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Buffer     : Storage_Pointers.Pointer;
             Length     : in out Storage_Count;
             Offset     : Byte_Count;
             Error      : out Error_Code
          );

This procedure is called to read a portion of the file. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process reading the file. Buffer is the read buffer pointer. The buffer is a C-compatible pointer to array of Storage_Elements:

package Storage_Pointers is
   new
Interfaces.C.Pointers
       (  Index              => Storage_Offset,
          Element            => Storage_Element,
          Element_Array      => Storage_Array,
          Default_Terminator => 0
       );

Length is the buffer length in storage elements. After successful read it is set to the number of actually read elements. Offset is the first byte to read (zero-based). Error is the result Windows error code. The default implementation fails with ERROR_READ_FAULT.

procedure Write
          (  File       : in out Abstract_Directory;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Buffer     : Storage_Pointers.Pointer;
             Length     : in out Storage_Count;
           [ Offset     : Byte_Count; ]
             Error      : out Error_Code
          );

This procedure is called to write a portion of the file. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process writing the file. Buffer is the write buffer pointer. Length is the buffer length in storage elements. After successful read it is set to the number of actually written elements. Offset is the first byte to write (zero-based). When omitted, the file is written starting at the file end. Error is the result Windows error code. The default implementation fails with ERROR_WRITE_FAULT.

procedure Set_Allocation_Size
          (  File       : in out Abstract_Directory;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Size       : Byte_Count;
             Error      : out Error_Code
          );

This procedure truncates or extends space used by the file. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process setting the file end. Size is the new file size in bytes. Error is the result Windows error code. The default implementation fails with ERROR_WRITE_FAULT.

procedure Set_End_Of
          (  File       : in out Abstract_Directory;
             Name       : LPCWSTR;
             System     : in out Abstract_File_System'Class;
             Process_ID : ULONG;
             Length     : Byte_Count;
             Error      : out Error_Code
          );

This procedure is called to set the file end. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process setting the file end. Length is the new file length in bytes. Error is the result Windows error code. The default implementation fails with ERROR_WRITE_FAULT.

procedure Unlock
          (  File        : in out Abstract_Directory;
             Name        : LPCWSTR;
             System      : in out Abstract_File_System'Class;
             Process_ID  : ULONG;
             Byte_Offset : Byte_Count;
             Length      : Byte_Count;
             Error       : out Error_Code
          );

This procedure is called to unlock a portion of the file. Name is the file path (UTF-16). System is the file system the file or directory is located on. Process_ID identifies the process unlocking the file. Byte_Offset is the first byte to unlock (zero-based). Length is the number of bytes to unlock. Error is the result Windows error code. The default implementation fails with ERROR_INVALID_FUNCTION.

[Back][TOC][Next]

1.5. Utility operations

The package Dokan.File_System provides the following utility operations:

function From_Time (Stamp : Time) return FILETIME;

This function converts Ada calendar's time to Windows' FILETIME. Time_Error is propagated on conversion errors.

function From_UTF8_String (Text : String) return Wide_String;

This function converts an UTF-8 encoded string to UTF-16 equivalent.

function Get_Error_Text (Code : Error_Code) return String;

This function gets Windows' error message text corresponding to Code.

function To_Time (Stamp : FILETIME) return Time;

This function converts Windows' FILETIME to Ada calendar's time. Time_Error is propagated on conversion errors.

function To_UTF8_String
         (  Text      : LPCWSTR;
            Normalize := True
         )  return String;
function
To_UTF8_String
         (  Text      : Wide_String;
            Normalize := True
         )  return String;

These functions convert an UTF-16 encoded string to UTF-8 equivalent. It also normalizes Text by replacing '/' with '\' when Normalize is true.

type File_System_Lock (System : access Abstract_File_System'Class) is
   new
Ada.Finalization.Limited_Controlled with private;
procedure Finalize   (Lock : in out File_System_Lock);
procedure Initialize (Lock : in out File_System_Lock);

An object of this type is created in a context where the file system must be locked for exclusive access. The locking is reentrant. The same task can create several such objects. Initialization of the object is blocked if another task has already created its own object. Finalization of the object releases other task if it was the last object created by the task.

[Back][TOC][Next]

1.6. Windows API

The package Dokan.Win32API defines a portion of Windows API. The following important data types are used throughout of bindings:

type Error_Code is new DWORD;

The type Error_Code is used in all operations implementing the file system to report the outcome. The child package Dokan.Win32API.Error_Codes defines major Windows error code constants.

type File_System_Flags is new DWORD;

Values of the type File_System_Flags describe properties of a file system.

type FILETIME is record
   LowDateTime  : DWORD;
   HighDateTime : DWORD;
end record;

Values of the type FILETIME describe Windows UTC file time stamp.

function Image
         (  Date         : FILETIME;
            Milliseconds : Boolean := False;
            UTC          : Boolean := False
         )  return String;

This function returns time stamp representation in the format YYYY-MM-DD HH:MM:SS.mmm. When Milliseconds is false, no seconds fraction appear. When UTC is true the output is the Coordinated Universal Time.

type LPCWSTR is access constant Wide_Character;
type LPWSTR  is access all Wide_Character;

The operations use the types LPCWSTR and LPWSTR for the file names. File names are NUL-terminated  UTF-16 encoded when communicating with the Dokan library. Everywhere outside that more convenient UTF-8 is used. The names are not converted from UTF-16 to UTF-8 to avoid unnecessary overhead. The implementation may choose the encoding it suits best. The utility operations To_UTF8_String and From_UTF8_String are provided for conversions between these encodings.

type NTSTATUS is new DWORD;

The type NTSTATUS is used when communicating with the kernel operations. The child packages Dokan.Win32API.NTSTATUS_Codes defines kernel status codes.

type NT_Access is new DWORD;

The type NT_Access corresponds to the Windows access mask with bits like FILE_READ_DATA or FILE_READ_ATTRIBUTES.

function Image (Access_Mask : NT_Access) return String;

This function is used to convert a mask into a human-readable form.

function DestroyPrivateObjectSecurity
         (  Object : access PSECURITY_DESCRIPTOR
         )  return BOOL;

This function frees security descriptor object and zeroes its pointer.

function Check
         (  Process_ID     : DWORD;
            Descriptor     : PSECURITY_DESCRIPTOR;
            Desired_Access : NT_Access;
            Mapping        : GENERIC_MAPPING := File_Access_Mapping
         )  return Error_Code;

This function checks if the process Process_ID has access specified in Desired_Access according to the security descriptor Descriptor. When Descriptor is null access is granted. The result is ERROR_SUCCESS if the access is granted. Mapping specifies mapping of generic access modes to the specific file access modes.

function CreateSecurity
         (  Process_ID   : DWORD;
            Is_Directory : BOOL;
            Parent       : PSECURITY_DESCRIPTOR := null
         )  return PSECURITY_DESCRIPTOR;

This procedure creates a default security descriptor for a new file created by the process Process_ID. Is_Directory is set to true for a directory. Parent is a pointer to the parent's descriptor to inherit from. Typically it is the descriptor of the containing directory. The result is null on errors. The result is returned in self-relative format. It must be freed using DestroyPrivateObjectSecurity.

type HANDLE_Holder is
   new
Ada.Finalization.Limited_Controlled with
record

   Resource : aliased HANDLE := HANDLE (System.Null_Address);
end record;

The type HANDLE_Holder is used to hold an open HANDLE. Its finalization closes the handle.


[Back][TOC][Next]

2. Samples

[Back][TOC][Next]

2.1. Memory-resident file system

The package Memory_FS provides a simplified sample implementation of a memory-resident file system. The implementation uses Simple Components for Ada, which is required for building the sample.

The package Memory_FS is located in the subdirectory example/memory-fs:

File memory_fs.ads:
with Ada.Exceptions;           use Ada.Exceptions;                          
with Dokan.File_System;        use Dokan.File_System;
with Dokan.Thin;               use Dokan.Thin;
with Dokan.Win32API;           use Dokan.Win32API;
with System.Storage_Elements;  use System.Storage_Elements;

with Ada.Text_IO;
with Generic_Unbounded_Array;
with Object.Handle;
with Tables.UTF8_Names;

package Memory_FS is
   --
   -- Memory_File_System -- A memory-resident file system
   --

   type Memory_File_System is new Abstract_File_System with private;

The Memory_File_System type is derived from Abstract_File_System.

File memory_fs.ads (continuation):
   --                                                                       
   -- Set_Trace_File -- Designate the trace output file
   --
   --    System      - File system
   --  [ File_Name ] - The trace file name, if missing standard output used
   --

   procedure Set_Trace_File
             (  System    : in out Memory_File_System;
                File_Name : String
             );
   procedure Set_Trace_File (System : in out Memory_File_System);

Here a two utility operations are defined for debugging purpose. The procedure Set_Trace_File redirects trace output to a specific file when the parameter File_Name is present or to the standard output, if omitted.

File memory_fs.ads (continuation):
private
   type
Node is abstract new Object.Entity with record                      
      Created    : FILETIME;
      Accessed   : FILETIME;
      Written    : FILETIME;
      Attributes : DWORD := 0;
      Security   : aliased PSECURITY_DESCRIPTOR;
   end record;
   type Node_Ptr is access Node'Class;

In the private part of the package the abstract base type of the file system nodes is defined. A node is either a file or a directory. The type Node is abstract and derived from Object.Enity in order to provide reference counting collection. The fields of Node are access times, attributes and the security descriptor to handle file access checks.

File memory_fs.ads (continuation):
   procedure Create_Security                                                
             (  File       : in out Node;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Parent     : PSECURITY_DESCRIPTOR;
                System     : in out Memory_File_System'Class
             )  is abstract;

 The abstract operation Create_Security is used to create security descriptor for a file or directory.

File memory_fs.ads (continuation):
   function Get_Size (File : Node) return Byte_Count is abstract;           

 The abstract operation Get_Size is defined to return the file system space allocated for a node.

File memory_fs.ads (continuation):
   procedure Finalize (File : in out Node);                                 
   procedure Initialize (File : in out Node);

 The initialization and finalization operations common for all nodes.

File memory_fs.ads (continuation):
   package Node_Handles is new Object.Handle (Node, Node_Ptr);              

Here the generic package Object.Handle is instantiated to provide handles to the node objects. When the last handle to a node disappears the node is deleted.

File memory_fs.ads (continuation):
   package Raw_Tables is new Tables (Node_Handles.Handle);                  

   procedure Check_Spelling (Name : String);
   function Check_Matched (Source : String; Pointer : Integer)
      return Boolean;

   package Directory is new Raw_Tables.UTF8_Names;
   use Directory;

 Here case-insensitive tables of handles to the files system nodes is defined through instantiation of the generic package Tables.UTF8_Names. The package requires two subroutines:

The implementation of Check_Spelling checks if its string parameter is a valid Windows file name. Check_Matched verifies if name is not followed by a letter, digit or any other legal file name character.

File memory_fs.ads (continuation):
   type Folder_Node is new Node with record                                 
      Data : Directory.Dictionary;
   end record;

The type Folder_Node represents a file system directory. It is derived from Node and contains a table of files from the directory. The table type is defined by the instance of Tables.UTF8_Names.

File memory_fs.ads (continuation):
   procedure Create_Security                                                
             (  File       : in out Folder_Node;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Parent     : PSECURITY_DESCRIPTOR;
                System     : in out Memory_File_System'Class
             );
   function Get_Size (File : Folder_Node) return Byte_Count;
   procedure Initialize (File : in out Folder_Node);

Here we override operations for the type Folder_Node.

File memory_fs.ads (continuation):
   package Storage_Arrays is                                                
      new
Generic_Unbounded_Array
          (  Index_Type        => Storage_Offset,
             Object_Type       => Storage_Element,
             Object_Array_Type => Storage_Array,
             Null_Element      => 0,
             Minimal_Size      => 1024,
             Increment         => 1024
          );
   use Storage_Arrays;

An instance of Generic_Unbounded_Array serves as a container of file contents. The array is indexed by Storage_Offset and contains Storage_Elements. A more sophisticated implementation could use non-contiguous storage allocation schema.

File memory_fs.ads (continuation):
   type File_Node is new Node with record                                   
      Length : Storage_Count := 0;
      Data   : Unbounded_Array;
   end record;
   function Get_Size (File : File_Node) return Byte_Count;
   procedure Initialize (File : in out File_Node);

Here a file system plain file node is defined. The node has current file length (Length component) and file contents (Data of Unbounded_Array).

File memory_fs.ads (continuation):
   procedure Create_Security                                                
             (  File       : in out File_Node;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Parent     : PSECURITY_DESCRIPTOR;
                System     : in out Memory_File_System'Class
             );
   function Get_Size (File : File_Node) return Byte_Count;
   procedure Initialize (File : in out File_Node);

Here we override operations for the type Folder_Node.

File memory_fs.ads (continuation):
   type Memory_File_System is new Abstract_File_System with record          
      Root       : Node_Handles.Handle;
      Into_File  : Boolean := False;
      Trace_File : Ada.Text_IO.File_Type;
   end record;

Now the file system itself is defined. It has the components:

File memory_fs.ads (continuation):
   procedure Check_Delete_Directory
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Error      : out Error_Code
             );
   procedure
Check_Delete_File
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Error      : out Error_Code
             );
   procedure
Create_Directory                                               
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Directory  : out Abstract_Directory_Ptr;
                Error      : out Error_Code
             );
   procedure Create_File
             (  System         : in out Memory_File_System;
                Name           : LPCWSTR;
                Process_ID     : ULONG;
                Desired_Access : NT_Access;;
                Sharing        : File_Sharing_Mode;
                Disposition    : File_Open_Mode;
                Options        : File_Options;
                File           : out Abstract_Node_Ptr;
                Error          : out Error_Code
             );
   procedure Delete_Directory
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Error      : out Error_Code
             );
   procedure Delete_File
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Error      : out Error_Code
             );
   procedure Do_Unmount (System : in out Memory_File_System);
   procedure Finalize (System : in out Memory_File_System);
   procedure Get_Security
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Security   : SECURITY_INFORMATION;
                Descriptor : PSECURITY_DESCRIPTOR;
                Length     : in out ULONG;
                Error      : out Error_Code
             );

Here some of the abstract operations of the base type are overridden.

File memory_fs.ads (continuation):
   function Find                                                            
            (  System : access Memory_File_System;
               Name   : LPCWSTR;
               Parent : access Node_Handles.Handle;
               Check  : Boolean := True
            )  return String;

This utility function is used internally to decompose a file path into a handle to the parent's directory and simple file name. Name is the file path UTF-16 encoded. Parent is the output handle for the directory part. Check is true when the file simple name must be checked. The function returns the simple name. Constraint_Error is propagated when the name is illegal. End_Error is propagated when a parent directory does not exist. Name_Error is propagated when a parent is not a directory.

File memory_fs.ads (continuation):
   procedure Find                                                           
             (  System : in out Memory_File_System;
                Name   : LPCWSTR;
                File   : out Node_Handles.Handle;
                Error  : out Error_Code
             );

This is a simplified version of Find used when an existing file should be searched for. Name is the file path UTF-16 encoded. File is a handle to the file specified. Error is the Windows error code set according to the search outcome.

File memory_fs.ads (continuation):
   function Get_File_System_Flags                                           
            (  System : Memory_File_System
            )  return File_System_Flags;
   function Get_File_System_Name
            (  System : Memory_File_System
            )  return String;
   function Get_Maximal_File_Length
            (  System : Memory_File_System
            )  return Natural;
   function Get_Volume_Name
            (  System : Memory_File_System
            )  return String;
   function Get_Volume_Serial_Number
            (  System : Memory_File_System
            )  return DWORD;
   procedure Get_Volume_Space
             (  System     : in out Memory_File_System;
                Process_ID : ULONG;
                Total      : out Byte_Count;
                Free       : out Byte_Count;
                Available  : out Byte_Count
             );
   procedure Initialize (System : in out Memory_File_System);
   procedure Move
             (  System     : in out Memory_File_System;
                Process_ID : ULONG;
                Old_Name   : LPCWSTR;
                New_Name   : LPCWSTR;
                Replace    : Boolean;
                Error      : out Error_Code
             );
   procedure Open_Directory
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Directory  : out Abstract_Directory_Ptr;
                Error      : out Error_Code
             );
   procedure Set_Attributes
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Attributes : DWORD;
                Error      : out Error_Code
             );
   procedure Set_File_Time
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Created    : FILETIME;
                Accessed   : FILETIME;
                Written    : FILETIME;
                Error      : out Error_Code
             );
   procedure Set_Security
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Security   : SECURITY_INFORMATION;
                Descriptor : SECURITY_DESCRIPTOR;
                Error      : out Error_Code
             );
   procedure Trace
             (  System : in out Memory_File_System;
                Text   : String
             );
   procedure Trace
             (  System : in out Memory_File_System;
                Text   : String;
                Error  : Exception_Occurrence
             );
   procedure Trace
             (  System : in out Memory_File_System;
                Text   : String;
                Name   : LPCWSTR
             );
   procedure Trace
             (  System : in out Memory_File_System;
                Text   : String;
                Name   : LPCWSTR;
                Error  : Exception_Occurrence
             );

Here is another portion of primitive operations gets overridden.

File memory_fs.ads (continuation):
   type Memory_File (File : access File_Node'Class) is                      
      new
Abstract_File with
   record

      Reference : Node_Handles.Handle;
      Mode      : NT_Access := 0;
   end record;

Memory_File is an implementation of Abstract_File which serves as a proxy to an open file system file. An object has the components:

File memory_fs.ads (continuation):
   procedure Close                                                          
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Delete     : Boolean;
                Error      : out Error_Code
             );
   procedure Close                                                          
             (  File       : in out Memory_File;
                System     : in out Abstract_File_System'Class;
                Error      : out Error_Code
             );
   procedure Flush
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Error      : out Error_Code
             );
   procedure Get_Information
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Attributes : out DWORD;
                Created    : out FILETIME;
                Accessed   : out FILETIME;
                Written    : out FILETIME;
                Size       : out Byte_Count;
                Links      : out DWORD;
                Index      : out File_Index;
                Error      : out Error_Code
             );
   procedure Read
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Buffer     : Storage_Pointers.Pointer;
                Length     : in out Storage_Count;
                Offset     : Byte_Count;
                Error      : out Error_Code
             );
   procedure Write
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Buffer     : Storage_Pointers.Pointer;
                Length     : in out Storage_Count;
                Error      : out Error_Code
             );
   procedure Write
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Buffer     : Storage_Pointers.Pointer;
                Length     : in out Storage_Count;
                Offset     : Byte_Count;
                Error      : out Error_Code
             );
   procedure Set_Allocation_Size
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Length     : Byte_Count;
                Error      : out Error_Code
             );
   procedure
Set_End_Of
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Length     : Byte_Count;
                Error      : out Error_Code
             );

The primitive operations of Abstract_File are overridden here.

File memory_fs.ads (continuation):
   type Memory_Folder (Folder : access Folder_Node'Class) is                
      new
Abstract_Directory with
   record

      Reference : Node_Handles.Handle;
   end record;

Memory_Folder is an implementation of Abstract_Directory which serves as a proxy to an open file system directory. An object keeps a handle to the file system directory object.

File memory_fs.ads (continuation):
   procedure Close                                                          
             (  Directory  : in out Memory_Folder;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Delete     : Boolean;
                Error      : out Error_Code
             );
   procedure Close                                                          
             (  Directory  : in out Memory_Folder;
                System     : in out Abstract_File_System'Class;
                Error      : out Error_Code
             );
   procedure Find
             (  Directory  : in out Memory_Folder;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Fill       : FillFileData_Ptr;
                Info       : File_Info_Ptr;
                Error      : out Error_Code
             );
   procedure Get_Information
             (  Directory  : in out Memory_Folder;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Attributes : out DWORD;
                Created    : out FILETIME;
                Accessed   : out FILETIME;
                Written    : out FILETIME;
                Size       : out Byte_Count;
                Links      : out DWORD;
                Index      : out File_Index;
                Error      : out Error_Code
             );
end Memory_FS;

This overrides operations of Abstract_Directory.

File memory_fs.adb:
with Ada.Calendar;                use Ada.Calendar;                         
with Ada.IO_Exceptions;           use Ada.IO_Exceptions;
with Dokan.Win32API;              use Dokan.Win32API;
with Dokan.Win32API.Error_Codes;  use Dokan.Win32API.Error_Codes;
with Strings_Edit.Quoted;         use Strings_Edit.Quoted;
with Strings_Edit.UTF8;           use Strings_Edit.UTF8;
with Strings_Edit.UTF8.Mapping;   use Strings_Edit.UTF8.Mapping;
with Strings_Edit.UTF8.Maps;      use Strings_Edit.UTF8.Maps;
with System.Storage_Pools;        use System.Storage_Pools;

with Interfaces.C;
with Strings_Edit.UTF8.Maps.Constants;
with System.Address_To_Access_Conversions;

package body Memory_FS is
   use
Node_Handles;
   use type DWORD;
   use type LONG;
   use type int;

   NUL : constant Wide_Character := Wide_Character'Val (0);

The implementation of the package Memory_FS follows.

File memory_fs.adb (continuation):
   Directory_Mask : constant NT_Access :=                                   
                    not (  FILE_LIST_DIRECTORY
                        or FILE_READ_PROPERTIES
                        or FILE_TRAVERSE
                        or FILE_READ_ATTRIBUTES
                        or FILE_DELETE_CHILD
                        or 16#130_000#
                        );

The constant Directory_Mask defines access modes allowed when opening a directory.

File memory_fs.adb (continuation):
   Supported_Attributes : constant DWORD :=                                 
                                   (  FILE_ATTRIBUTE_READONLY
                                   or FILE_ATTRIBUTE_TEMPORARY
                                   or FILE_ATTRIBUTE_DIRECTORY
                                   or FILE_ATTRIBUTE_NORMAL
                                   or FILE_ATTRIBUTE_HIDDEN
                                   or FILE_ATTRIBUTE_ARCHIVE
                                   );

The constant Supported_Attributes defines the file attributes we allow to set.

File memory_fs.adb (continuation):
   function Image (File : Node'Class) return String is                      
      package
Convert_Node is
         new
System.Address_To_Access_Conversions (Node'Class);
      use Convert_Node;
   begin
      return
Integer_Address'Image (To_Integer (File'Address));
   end Image;

This function is used to output node address when tracing it done.

File memory_fs.adb (continuation):
   procedure Check_Delete_Directory                                         
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Error      : out Error_Code
             )  is
      Lock   : File_System_Lock (System'Access);
      Parent : aliased Node_Handles.Handle;
   begin
      declare

         Simple : String := Find (System'Access, Name, Parent'Access);
         Offset : Natural;
         This   : Folder_Node'Class renames
                  Folder_Node'Class (Ptr (Parent).all);
      begin

This function checks of a directory can be deleted. First the file system is locked. Then function Find is called to split the file name into its name Simple and a handle to the directory node Parent. which is then dereferenced as This. If there is no parent End_Error is propagated.

File memory_fs.adb (continuation):
         Offset := Locate (This.Data, Simple);                              
         if Offset = 0 then
            Error := ERROR_FILE_NOT_FOUND;

The parent directory is searched for the file name. If there is no such file ERROR_FILE_NOT_FOUND is the result.

File memory_fs.adb (continuation):
         else
            declare                                                         

               File : Node'Class renames
                      Ptr (GetTag (This.Data, Offset)).all;
            begin
               if
File in File_Node'Class then
                  Error :=
                     Check (Process_ID, File.Security, GENERIC_ALL);

When the file is found, its handle is dereferenced. If it is a plain file its access is checked using the procedure Check for having full access.

File memory_fs.adb (continuation):
               elsif (  (  0                                                
                        /= (  File.Attributes
                           and
                              FILE_ATTRIBUTE_TEMPORARY
                        )  )
                     or else
                        GetSize (Folder_Node'Class (File).Data) = 0
                     )  then
                  Error :=
                     Check (Process_ID, File.Security, GENERIC_ALL);
               else
                  Error := ERROR_DIR_NOT_EMPTY;
               end if;
            end;
         end if;

When it is a directory and it is temporary or else empty then access is checked using the procedure Check for having full access. Otherwise the result is ERROR_DIR_NOT_EMPTY.

ile memory_fs.adb (continuation):
      end;                                                                  
   exception
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Check_Delete_Directory;

Finally, exceptions from Find are converted into the corresponding error codes.

File memory_fs.adb (continuation):
   procedure Check_Delete_File                                              
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Error      : out Error_Code
             )  is
      Lock   : File_System_Lock (System'Access);
      Parent : aliased Node_Handles.Handle;
   begin
      declare
         Simple : String := Find (System'Access, Name, Parent'Access);
         Offset : Natural;
         This   : Folder_Node'Class renames
                  Folder_Node'Class (Ptr (Parent).all);
      begin
         Offset := Locate (This.Data, Simple);
         if Offset = 0 then
            Error := ERROR_FILE_NOT_FOUND;
         else
            declare
                File : Node'Class renames
                       Ptr (GetTag (This.Data, Offset)).all;
            begin

The implementation of file deletion check starts exactly like Check_Delete_Directory.

File memory_fs.adb (continuation):
                if File in File_Node'Class then                             
                   Error :=
                      Check (Process_ID, File.Security, GENERIC_ALL);
                else
                   Error := ERROR_ACCESS_DENIED;
                end if;

If it is a plain file its access is checked with Check, otherwise, for a directory ERROR_ACCESS_DENIED is the result.

File memory_fs.adb (continuation):
            end;
         end if;
      end;                                                                  
   exception
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Check_Delete_File;

The completion is same as in Check_Delete_Directory.

File memory_fs.adb (continuation):
   function Check_Matched (Source : String; Pointer : Integer)              
      return Boolean is
   begin
      case
Source (Pointer) is
         when
Character'Val (0)..Character'Val (31) =>
            return True;
         when '<' | '>' | ':' | '"' | '/' | '\' | '|' | '*' =>
            return True;
         when others =>
            return False;
      end case;
   end Check_Matched;

The implementation checks if any of the forbidden characters follows the name, this is Source (Pointer). In this case the name can ends at Pointer and the returned value is true. Otherwise, the result is false.

File memory_fs.adb (continuation):
   procedure Check_Spelling (Name : String) is                              
      Pointer : Integer := Name'First;
      Symbol  : Code_Point;
   begin
      while
Pointer <= Name'Last loop
         Get (Name, Pointer, Symbol);
         case Symbol is
            when
0..31 =>
               Raise_Exception
               (  Constraint_Error'Identity,
                  "File name contains control characters"
               );
            when Character'Pos ('<') | Character'Pos ('>') |
                 Character'Pos (':') | Character'Pos ('"') |
                 Character'Pos ('/') | Character'Pos ('\') |
                 Character'Pos ('|') | Character'Pos ('?') |
                 Character'Pos ('*') =>
               Raise_Exception
               (  Constraint_Error'Identity,
                  "File name contains one of <,>,:,"",/,\,|,*"
               );
            when others =>
               null;
         end case;
      end loop;
   exception
      when
Constraint_Error =>
         raise;
      when others =>
         Raise_Exception
         (  Constraint_Error'Identity,
            "Wrong encoded file name"
         );
   end Check_Spelling;

The implementation of Check_Spelling raises Contraint_Error if the name contains a control character or else one of <, >, :, ", /, \, |, ?, *.

File memory_fs.adb (continuation):
   procedure Close                                                          
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Delete     : Boolean;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      This   : File_Node'Class renames File.File.all;
      Lock   : File_System_Lock (System'Access);
   begin

The implementation of closing file starts with casting the file system to Volume, accessing the file object as This  and finally locking the file system.

File memory_fs.adb (continuation):
      if Delete then                                                        
         Delete_File (System, Name, Error);

When the parameter Delete is true the file is deleted.

File memory_fs.adb (continuation):
      else                                                                  
         This.Accessed := From_Time (Clock);
         if 0 /= (File.Mode and FILE_GENERIC_WRITE) then
            This.Written := This.Accessed;
         end if;
         if Get_Tracing (Volume) then
            Trace
            (  Volume,
               (  "Closed file "
               &  Quote (To_UTF8_String (Name), ''')
               &  " (Creation: "
               &  Image (This.Created)
               &  ", Access: "
               &  Image (This.Accessed)
               &  ", Write: "
               &  Image (This.Written)
               &  ") Use count"
               &  Integer'Image (This.Use_Count)
               ),
               Name
            );
         end if;
      end if;
      Error := ERROR_SUCCESS;
   end Close;

When the file is to stay the time stamp Accessed is set to the current time. The utility function From_Time is used to convert Ada.Calendar.Time to Windows' FILETIME. If the file was open for writing the time stamp Written is also set.

File memory_fs.adb (continuation):
   procedure Close                                                          
             (  File   : in out Memory_File;
                System : in out Abstract_File_System'Class;
                Error  : out Error_Code
             )  is
   begin
      Error := ERROR_SUCCESS;
   end Close;

This variant of closing is called when the file system is dismounted. There is nothing to do because all file system will be destroyed anyway.

File memory_fs.adb (continuation):
   procedure Close                                                          
             (  Directory  : in out Memory_Folder;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Delete     : Boolean;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      This   : File_Node'Class renames File.File.all;
      Lock   : File_System_Lock (System'Access);
   begin
      if
Delete then
         Delete_Directory (System, Name, Error);
      else
         This.Accessed := From_Time (Clock);
         if 0 /= (File.Mode and FILE_GENERIC_WRITE) then
            This.Written := This.Accessed;
         end if;
         if Get_Tracing (Volume) then
            Trace
            (  Volume,
               (  "Closed directory "
               &  Quote (To_UTF8_String (Name), ''')
               &  " (Creation: "
               &  Image (This.Created)
               &  ", Access: "
               &  Image (This.Accessed)
               &  ", Write: "
               &  Image (This.Written)
               &  ") Use count"
               &  Integer'Image (This.Use_Count)
               ),
               Name
            );
         end if;
      end if;
      Error := ERROR_SUCCESS;
   end Close;

The implementation of closing directory is same as for a file.

File memory_fs.adb (continuation):
   procedure Close                                                          
             (  File   : in out Memory_Folder;
                System : in out Abstract_File_System'Class;
                Error  : out Error_Code
             )  is
   begin
      Error := ERROR_SUCCESS;
   end Close;

This variant of closing is called when the file system is dismounted. It is same as for closing file.

File memory_fs.adb (continuation):
   procedure Create_Directory                                               
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Directory  : out Abstract_Directory_Ptr;
                Error      : out Error_Code
             )  is
      Lock   : File_System_Lock (System'Access);
      Parent : aliased Node_Handles.Handle;
   begin
     
Directory := null;
      declare

         Folder : String := Find (System'Access, Name, Parent'Access);
      begin
         Check_Spelling (Folder);

The implementation of directory creation starts with searching for the directory parent using the function Find and getting the directory name. After that the directory name is checked using Check_Spelling.

File memory_fs.adb (continuation):
         declare                                                            
            This   : Folder_Node'Class renames
                     Folder_Node'Class (Ptr (Parent).all);
            Offset : Natural := Locate (This.Data, Folder);

Here we get the parent node object and locate the specified name in the table of its children (the component Data). The result is zero if there is no child with this name.

File memory_fs.adb (continuation):
            procedure Set_Directory is                                      
            begin

               Directory :=
                  new Memory_Folder
                      (  Folder_Node'Class
                         (  Ptr (GetTag (This.Data, Offset)).all
                         ) 'Unchecked_Access
                      );
               Memory_Folder (Directory.all).Reference :=
                  GetTag (This.Data, Offset);
            end Set_Directory;;

The procedure Set_Directory is a helper program that sets the result Directory. It creates an instance of Memory_Folder and initializes its member Reference to the directory object. That will hold the directory until it stays open.

File memory_fs.adb (continuation):
         begin                                                              
            if
Offset = 0 then
               Error :=
                  Check (Process_ID, This.Security, GENERIC_WRITE);
               if Error /= ERROR_SUCCESS then
                  return
;
               end if;

Since there is no child with the specified name. In order to create a new directory node we check if we have write rights on the parent directory using the procedure Check. If not we return with the reported access error.

File memory_fs.adb (continuation):
               declare                                                      
                  Item : Node_Handles.Handle;
               begin
                  Set (Item, new Folder_Node);
                  Add (This.Data, Folder, Item, Offset);

After successful checking access rights, we create directory node and add it into the parent's table of children.

File memory_fs.adb (continuation):
                  Create_Security                                           
                  (  Ptr (Item).all,
                     Name,
                     Process_ID,
                     This.Security,
                     System
                  );

Here we create security descriptor for the newly created directory.

File memory_fs.adb (continuation):
                  if Get_Tracing (Memory_File_System'Class (System))        
                  then
                     Trace
                     (  Memory_File_System'Class (System),
                        (  "Created directory at"
                        &  Image (Ptr (Item).all)
                        &  " in"
                        &  Image (This)
                        ),
                        Name
                     );
                  end if;
                  Set_Directory;
                  Error := ERROR_SUCCESS;
               end;

Finally, some tracing is done and the helper procedure Set_Directory defined previously is called to set the result Directory.

File memory_fs.adb (continuation):
            else                                                            
               if
(  Ptr (GetTag (This.Data, Offset)).all
                  in Folder_Node'Class
                  )
               then

A child with this name indeed exists so we check if it is a directory.

File memory_fs.adb (continuation):
                  Error :=                                                  
                     Check
                     (  Process_ID,
                        Ptr (GetTag (This.Data, Offset)).Security,
                        GENERIC_READ
                     );
                  if Error /= ERROR_SUCCESS then
                     return
;
                  end if;

The file is a directory, so we check if we have read access to it with the procedure Check. Without read access we immediately return.

File memory_fs.adb (continuation):
                  if Get_Tracing (Memory_File_System'Class (System))
                  then
                     Trace
                     (  Memory_File_System'Class (System),
                        (  "Opened directory at"
                        &  Image (Ptr (GetTag (This.Data, Offset)).all)
                        &  " in"
                        &  Image (This)
                        &  " Use count"
                        &  Integer'Image
                           (  Ptr (GetTag (This.Data, Offset)).Use_Count
                        )  ),
                        Name
                     );
                  end if;
                  Set_Directory;                                            
                  Error := ERROR_ALREADY_EXISTS;

Then we trace opening directory, call the helper procedure Set_Directory defined previously is called to set the result Directory and return ERROR_ALREADY_EXISTS which is not error.

File memory_fs.adb (continuation):
               else                                                         
                  Error := ERROR_DIRECTORY;
               end if;
            end if;
         end;

There is already a child with this name which is not a directory so we return ERROR_DIRECTORY.

File memory_fs.adb (continuation):
      end;                                                                  
   exception
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Create_Directory;

Finally we catch exceptions propagating on invalid name, non-existing parent, wrong parent name and return corresponding error codes.

File memory_fs.adb (continuation):
   procedure Create_File                                                    
             (  System         : in out Memory_File_System;
                Name           : LPCWSTR;
                Process_ID     : ULONG;
                Desired_Access : NT_Access;
                Sharing        : File_Sharing_Mode;
                Disposition    : File_Open_Mode;
                Options        : File_Options;
                File           : out Abstract_Node_Ptr;
                Error          : out Error_Code
             )  is
      Lock   : File_System_Lock (System'Access);
      Parent : aliased Node_Handles.Handle;
   begin
     
File := null;
      declare

         Directory : Boolean := False;
         Pattern   : Boolean := False;
         File_Name : String :=
                     Find (System'Access, Name, Parent'Access, False);

The implementation of file creation must be able both to create a new file or to open an existing file. It starts with finding the file parent directory and getting the file name.

File memory_fs.adb (continuation):
      begin                                                                 
         for
Index in File_Name'Range loop
            case
File_Name (Index) is
               when
'*' | '?' =>
                  Pattern := True;
                  exit;
               when others =>
                  null;
            end case;
         end loop;

Sometimes Windows uses file names containing wild-cards * and ?. Here we check if the name contains any of them and set the variable Pattern to true.

File memory_fs.adb (continuation):
         declare                                                            
            This   : Folder_Node'Class renames
                     Folder_Node'Class (Ptr (Parent).all);
            Offset : Natural;
            Item   : Node_Handles.Handle;

 Here we get the parent directory node. Item is a handle to the result.

File memory_fs.adb (continuation):
         begin                                                              
            if
File_Name'Length = 0 then -- Open root directory
               Directory := True;

When Find returns an empty file name we are in the root directory. We set the directory flag and check the directory disposition:

File memory_fs.adb (continuation):
               case Disposition is                                          
                  when
Open_Or_Create =>
                     if 0 /= (Desired_Access and Directory_Mask) then
                        Error := ERROR_ACCESS_DENIED;
                        return;
                     end if;

Here is the case when the root directory is to be opened or created when does not exist. We check if the desired access does not include anything not allowed for directories. If it does, ERROR_ACCESS_DENIED is returned.

File memory_fs.adb (continuation):
                     Error := Check                                         
                              (  Process_ID,
                                 Ptr (System.Root).Security,
                                 Desired_Access
                              );
                     if Error /= ERROR_SUCCESS then
                        return
;
                     end if
;
                     Error := ERROR_ALREADY_EXISTS;

The root directory access is checked with the procedure Check. ERROR_ALREADY_EXISTS will be returned later to indicate that an existing file is opened.

File memory_fs.adb (continuation):
                  when Dokan.File_System.Open_Existing =>                   
                     if 0 /= (Desired_Access and Directory_Mask) then
                        Error := ERROR_ACCESS_DENIED;
                        return;
                     end if
;
                     Error := Check
                              (  Process_ID,
                                 Ptr (System.Root).Security,
                                 Desired_Access
                              );
                     if Error /= ERROR_SUCCESS then
                        return
;
                     end if;
                     Error := ERROR_SUCCESS;

Here the root directory is opened only if exists. The access is checked with Check and ERROR_SUCCESS is set for later.

File memory_fs.adb (continuation):
                  when Truncate | Overwrite =>                              
                     Error := ERROR_ACCESS_DENIED;
                     return;
 

Opening to overwrite is rejected with ERROR_ACCESS_DENIED.

File memory_fs.adb (continuation):
                  when Dokan.File_System.Create_New =>                      
                     Error := ERROR_ALREADY_EXISTS;
                     return;

Creating new is rejected with ERROR_ALREADY_EXISTS. Note that since the parameter File is null, it is treated as error.

File memory_fs.adb (continuation):
               end case;                                                    
               Item := System.Root;

Finally Item is set to the root directory.

File memory_fs.adb (continuation):
         else                                                               
            if
Pattern then
               Offset := 0;
               declare
                  Pattern_Name : Wide_String :=
                                 From_UTF8_String (File_Name) & NUL;
               begin
                  for
Index in 1..GetSize (This.Data) loop
                     declare

                        Name : Wide_String :=
                               (  From_UTF8_String
                                  (  GetName (This.Data, Index)
                                  )
                               &  NUL
                               );
                     begin
                        if
0 /= IsNameInExpression
                                (  Addr (Pattern_Name),
                                   Addr (Name),
                                   1
                                )
                        then
                           Offset := Index;
                           exit;
                        end if;
                     end;
                  end loop;
               end;

When the name contains wild-cards we go through all children and look for the first child which name is matched by the pattern. For matching the function IsNameInExpression from the thin Dokan bindings is used. Dokan uses UTF-16 encoding. Note how the name is converted to UTF-16 using From_UTF8_String. Note also that NUL must be added to the name to make it working. When a child is found Offset is the child's position in the children table of the parent directory node. When no child is found Offset is zero.

File memory_fs.adb (continuation):
            else                                                            
               Offset := Locate (This.Data, File_Name);
            end if;

When the name contains no wild-cards we search the children table for the name. Again Offset is the child position in the table or zero.

File memory_fs.adb (continuation):
            if Offset = 0 then -- No file exists                            

When there is no child with the name, so we are to create the file. First we check the Disposition parameter.

File memory_fs.adb (continuation):
               case Disposition is                                          
                  when
Overwrite |
                       Dokan.File_System.Create_New |
                       Open_Or_Create =>
                     null;

When Disposition is Overwrite or Create_New, Create_New or Open_Or_Create, we will create file.

File memory_fs.adb (continuation):
                  when Dokan.File_System.Open_Existing | Truncate =>        
                     Error := ERROR_FILE_NOT_FOUND;
                     return;
               end case;

When Disposition is Open_Existing or Truncate, the implementation faults with code ERROR_FILE_NOT_FOUND.

File memory_fs.adb (continuation):
               Error :=                                                     
                  Check (Process_ID, This.Security, GENERIC_WRITE);
               if Error /= ERROR_SUCCESS then
                  return
;
               end if;

Here we check the parent directory with Check for write access.

File memory_fs.adb (continuation):
               Item := Ref (new File_Node);                                 
               Add (This.Data, File_Name, Item);
               This.Accessed := From_Time (Clock);
               This.Written  := This.Accessed;
               declare
                  New_File : Node'Class renames Ptr (Item).all;
               begin
                  Create_Security
                  (  New_File,
                     Name,
                     Process_ID,
                     This.Security,
                     System
                  );
               end;
               if Get_Tracing (Memory_File_System'Class (System))
               then
                  Trace
                  (  Memory_File_System'Class (System),
                     (  "Created file at"
                     &  Image (Ptr (Item).all)
                     &  " in"
                     &  Image (This)
                     ),
                     Name
                  );
               end if;

Now we create the new file, insert it into the parent directory This and create its security descriptor. Item is a handle to the new file handle. Finally some tracing is done.

File memory_fs.adb (continuation):
            else -- File exists                                             
               Item := GetTag (This.Data, Offset);
               Directory := Ptr (Item).all in Folder_Node'Class;

When there is a child with the name Item set to point to the child's node. The node is checked for being a directory and Directory is set. Then the Disposition parameter is checked:

File memory_fs.adb (continuation):
               case Disposition is                                          
                  when Open_Or_Create =>
                     if (  Directory
                        and then
                           0 /= (Desired_Access and Directory_Mask)
                        )  then
                        Error := ERROR_ACCESS_DENIED;
                        return;
                     end if;
                     Error := ERROR_ALREADY_EXISTS;

When Disposition is Open_Or_Create we check for a directory if the desired access is allowed. If this is OK the result will be ERROR_ALREADY_EXISTS.

File memory_fs.adb (continuation):
                  when Dokan.File_System.Open_Existing =>                   
                     if (  Directory
                        and then
                           0 /= (Desired_Access and Directory_Mask)
                        )  then
                        Error := ERROR_ACCESS_DENIED;
                        return;
                     end if;
                     Error := ERROR_SUCCESS;

When Disposition is Open_Existing we do the same except for the result on success will be ERROR_SUCCESS.

File memory_fs.adb (continuation):
                  when Truncate | Overwrite =>                              
                     if Directory then
                        Error := ERROR_ACCESS_DENIED;
                        return;
                     end if;
                     Error := ERROR_ALREADY_EXISTS;
                     File_Node'Class (Ptr (Item).all).Length := 0;

When Disposition is Open_Truncate or Overwrite and the child a directory the implementation fails with ERROR_ACCESS_DENIED. Otherwise the result is set to ERROR_ALREADY_EXIST. Since the file is overwritten its length is set to zero, which is equivalent to truncation and overwriting.

File memory_fs.adb (continuation):
                  when Dokan.File_System.Create_New =>                      
                     Error := ERROR_ALREADY_EXISTS;
                     return;
               end case;

When Disposition is Create_New the implementation fails with ERROR_ALREADY_EXISTS.

File memory_fs.adb (continuation):
               declare                                                     
                
  Access_Error : constant Error_Code :=
                                  Check
                                  (  Process_ID,
                                     Ptr (Item).Security,
                                     Desired_Access
                                  );
               begin
                  if
Access_Error /= ERROR_SUCCESS then
                     Error := Access_Error;
                     return;
                  end if;
               end
;
            end if
;

The access to the file is checked with Check against its security descriptor.

File memory_fs.adb (continuation):
            if Directory then                                              
               File := new Memory_Folder
                           (  Folder_Node'Class
                              (  Ptr (Item).all
                              ) 'Unchecked_Access
                           );

When a directory is opened an new instance of Memory_Folder is allocated.

File memory_fs.adb (continuation):
               declare                                                     
                  Result : Memory_Folder renames
                           Memory_Folder (File.all);
               begin
                  Result.Reference := Item;
                  if Get_Tracing (Memory_File_System'Class (System))
                  then
                     Trace
                     (  Memory_File_System'Class (System),
                        (  "Opened directory (as file) at"
                        &  Image (Result.Folder.all)
                        &  " in"
                        &  Image (This)
                        &  " Use count"
                        &  Integer'Image (Result.Folder.Use_Count - 1)
                        ),
                        Name
                     );
                   end if;
                end;

The component Reference of Memory_Folder is set to the file system node from the variable Item and tracing is performed.

File memory_fs.adb (continuation):
            else                                                           
               File := new Memory_File
                           (  File_Node'Class
                              (  Ptr (Item).all
                              ) 'Unchecked_Access
                           );

When a file is opened or create an new instance of Memory_File is allocated.

File memory_fs.adb (continuation):
               declare                                                     
                  Result : Memory_File renames Memory_File (File.all);
               begin
                  Result.Reference := Item;
                  Result.Mode := Desired_Access;
                  if Get_Tracing (Memory_File_System'Class (System))
                  then
                     Trace
                     (  Memory_File_System'Class (System),
                        (  "Opened file at"
                        &  Image (Result.File.all)
                        &  " in"
                        &  Image (This)
                        &  " Use count"
                        &  Integer'Image (Result.File.Use_Count - 1)
                        ),
                        Name
                     );
                  end if;
               end;

The component Reference of Memory_File is set to the file system node from the variable Item. The component Mode is set to the required access mode and tracing is performed.

File memory_fs.adb (continuation):
            end if;                                                        
         end;
      end;
   exception
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Create_File;

Here we catch exceptions propagating on invalid name, non-existing parent, wrong parent name and return corresponding error codes.

File memory_fs.adb (continuation):
   procedure Create_Security                                               
             (  File       : in out Folder_Node;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Parent     : PSECURITY_DESCRIPTOR;
                System     : in out Memory_File_System'Class
             )  is
   begin
      if
File.Security = null then
         File.Security := CreateSecurity (Process_ID, 1, Parent);
         if Get_Tracing (System) then
            if
File.Security = null then
               Trace (System, "No security set", Name);
            else
               Trace
               (  System,
                  "Set security " & Image (File.Security.all),
                  Name
               );
            end if;
         end if;
      end if;
   exception
      when
Error : others =>
         Trace (System, "File security creation fault", Name, Error);
   end Create_Security;

The implementation Create_Security for a directory creates a new security descriptor by calling CreateSecurity.

File memory_fs.adb (continuation):
   procedure Create_Security                                               
             (  File       : in out File_Node;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Parent     : PSECURITY_DESCRIPTOR;
                System     : in out Memory_File_System'Class
             )  is
   begin
      if
File.Security = null then
         File.Security := CreateSecurity (Process_ID, 1, Parent);
         if Get_Tracing (System) then
            if
File.Security = null then
               Trace (System, "No security set", Name);
            else
               Trace
               (  System,
                  "Set security " & Image (File.Security.all),
                  Name
               );
            end if;
         end if;
      end if;
   exception
      when
Error : others =>
         Trace (System, "File security creation fault", Name, Error);
   end Create_Security;

The implementation Create_Security for a file creates a new security descriptor by calling CreateSecurity.

File memory_fs.adb (continuation):
   procedure Delete_Directory                                              
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Error      : out Error_Code
             )  is
      Lock   : File_System_Lock (System'Access);
      Parent : aliased Node_Handles.Handle;
   begin
      declare

         Simple : String := Find (System'Access, Name, Parent'Access);
         Offset : Natural;
         This   : Folder_Node'Class renames
                  Folder_Node'Class (Ptr (Parent).all);

The implementation of directory deletion starts with finding the parent directory and getting the directory name.

File memory_fs.adb (continuation):
      begin                                                                
         Offset := Locate (This.Data, Simple);

Here the parent directory is searched for the specified directory name. Offset is zero when nothing found. Otherwise it is the position of the directory in the table of children.

File memory_fs.adb (continuation):
         if Offset = 0 then                                                
            Error := ERROR_FILE_NOT_FOUND;

When no file exists the implementation fails with the code ERROR_FILE_NOT_FOUND.

File memory_fs.adb (continuation):
         else                                                              
            declare

               File : Node'Class renames
                      Ptr (GetTag (This.Data, Offset)).all;

When there is a file its node is accessed as File.

File memory_fs.adb (continuation):
            begin                                                          
               if
File in File_Node'Class then
                  Directory.Delete (This.Data, Offset);
                  This.Accessed := From_Time (Clock);
                  This.Written  := This.Accessed;
                  if Get_Tracing (Memory_File_System'Class (System))
                  then
                     Trace
                     (  Memory_File_System'Class (System),
                        (  "Deleted directory "
                        &  Quote (To_UTF8_String (Name), ''')
                        ),
                        Name
                     );
                  end if;
                  Error := ERROR_SUCCESS;

When the node is a plain file it is deleted from the table of the parent's children. The access and write times of the parent directory are updated and tracing is done.

File memory_fs.adb (continuation):
               elsif (  (  0                                               
                        /= (  File.Attributes
                           and
                              FILE_ATTRIBUTE_TEMPORARY
                        )  )
                     or else
                        GetSize (Folder_Node'Class (File).Data) = 0
                     )  then
                  Directory.Delete (This.Data, Offset);
                  This.Accessed := From_Time (Clock);
                  This.Written  := This.Accessed;
                  Error := ERROR_SUCCESS;

When the node is a directory it is tested for either being temporary or else empty. Then it is deleted the same way a plain file would be.

File memory_fs.adb (continuation):
               else                                                        
                  Error := ERROR_DIR_NOT_EMPTY;
               end if;

Otherwise, the implementation fails with ERROR_DIR_NOT_EMPTY.

ile memory_fs.adb (continuation):
            end;                                                           
         end if;
      end;
   exception
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Delete_Directory;

Here we catch exceptions propagating on invalid name, non-existing parent, wrong parent name and return corresponding error codes.

File memory_fs.adb (continuation):
   procedure Delete_File                                                   
             (  System : in out Memory_File_System;
                Name   : LPCWSTR;
                Error  : out Error_Code
             )  is
      Lock   : File_System_Lock (System'Access);
      Parent : aliased Node_Handles.Handle;
   begin
      declare

         Simple : String := Find (System'Access, Name, Parent'Access);
         Offset : Natural;
         This   : Folder_Node'Class renames
                  Folder_Node'Class (Ptr (Parent).all);

The implementation file deletion is similar to directory deletion. It starts with finding the parent directory and getting the directory name.

File memory_fs.adb (continuation):
      begin                                                                
         Offset := Locate (This.Data, Simple);

Here the parent directory is searched for the specified file name. Offset is zero when nothing found. Otherwise it is the position of the file in the table of children.

File memory_fs.adb (continuation):
         if Offset = 0 then                                                
            Error := ERROR_FILE_NOT_FOUND;

When no file exists the implementation fails with the code ERROR_FILE_NOT_FOUND.

File memory_fs.adb (continuation):
         else                                                              
            declare

               File : Node'Class renames
                      Ptr (GetTag (This.Data, Offset)).all;

When there is a file its node is accessed as File.

File memory_fs.adb (continuation):
            begin                                                          
               if
File in File_Node'Class then
                  Directory.Delete (This.Data, Offset);
                  if Get_Tracing (Memory_File_System'Class (System))
                  then
                     Trace
                     (  Memory_File_System'Class (System),
                        (  "Deleted file "
                        &  Quote (To_UTF8_String (Name), ''')
                        ),
                        Name
                     );
                  end if;
                  This.Accessed := From_Time (Clock);
                  This.Written  := This.Accessed;
                  Error := ERROR_SUCCESS;

When the node is a plain file it is deleted by removing it from the parent's children table. The access and write times of the parent are updated.

File memory_fs.adb (continuation):
               else                                                        
                  Error := ERROR_ACCESS_DENIED;
               end if;

Otherwise, when the node is a directory, the implementation fails with ERROR_ACCESS_DENIED.

File memory_fs.adb (continuation):
            end;                                                           
         end if;
      end;
   exception
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Delete_File;

Here we catch exceptions propagating on invalid name, non-existing parent, wrong parent name and return corresponding error codes.

File memory_fs.adb (continuation):
   procedure Do_Unmount                                                    
             (  System : in out Memory_File_System
             )  is
   begin
      if
Get_Tracing (Memory_File_System'Class (System)) then
         Trace (Memory_File_System'Class (System), "Unmounting");
      end if;
   end Do_Unmount;

The implementation of Do_Unmount does not requires any specific actions. The only thing it does is tracing.

File memory_fs.adb (continuation):
   procedure Finalize (File : in out Node) is                              
      Result : BOOL;
   begin
      if
File.Security /= null then
         Result :=
            DestroyPrivateObjectSecurity
            (  File.Security'Unchecked_Access
            );
         File.Security := null;
      end if;
   end Finalize;

Finalization of a file system node deletes it security descriptor if any.

File memory_fs.adb (continuation):
   procedure Finalize (System : in out Memory_File_System) is              
   begin
      if
Get_Tracing (System) then
         Trace (System, "Finalizing memory file system");
      end if;
      Finalize (Abstract_File_System (System));
      if System.Into_File then
         Ada.Text_IO.Close (System.Trace_File);
      end if;
   end Finalize;

Finalization does tracing and then calls to the parent's type implementation.

File memory_fs.adb (continuation):
   function Find                                                           
            (  System : access Memory_File_System;
               Name   : LPCWSTR;
               Parent : access Node_Handles.Handle;
               Check  : Boolean := True
            )  return String is
      Path    : String  := To_UTF8_String (Name);
      Pointer : Integer := Path'First;
      Start   : Integer := Pointer;
      Current : Node_Handles.Handle renames Parent.all;

The utility function Find starts with conversion UTF-16 encoded Name to UTF-8. Current is renamed handle to the parent (parameter Parent).

File memory_fs.adb (continuation):
   begin                                                                   
     
Current := System.Root;
      if Get_Tracing (Memory_File_System'Class (System.all)) then
         if
Check then
            Trace
            (  Memory_File_System'Class (System.all),
               " Looking for " & Quote (Path) & " [checked]"
            );
         else
            Trace
            (  Memory_File_System'Class (System.all),
               " Looking for " & Quote (Path) & " [unchecked]"
            );
         end if;
      end if;

Parent is set to the file system root and tracing is done.

File memory_fs.adb (continuation):
      if Path'Length = 0 then -- Root directory                            
         return "";

When the path is empty the parent remains root directory and file name is returned empty.

File memory_fs.adb (continuation):
      elsif Path (Pointer) = '\' then -- We are in the root directory      
         Pointer := Pointer + 1;
         Start   := Pointer;
      end if
;

When the path starts with a slash, the slash is skipped. Start points to the beginning of a directory name. Pointer does to the current character to process. UTF-8 issues can be safely ignored because ASCII-7 characters are encoded in UTF-8 as is.

File memory_fs.adb (continuation):
      while Pointer <= Path'Last loop                                      
         if
Path (Pointer) = '\' then

Here we are looking for the next slash indicating end of the parent directory name.

File memory_fs.adb (continuation):
            if Ptr (Current).all not in Folder_Node'Class then             
               if
Get_Tracing (Memory_File_System'Class (System.all)) then
                  Trace
                  (  Memory_File_System'Class (System.all),
                     (  " Not a directory "
                     &  Quote (Path (Path'First..Start))
                  )  );
               end if;
               Raise_Exception
               (  Name_Error'Identity,
                  (  "Not a directory: "
                  &  Path (Path'First..Start)
               )  );
            end if;

When the handle to the current node is not a directory the directory name just found cannot be its child. The implementation does tracing and raises Name_Error to indicate wrong path.

File memory_fs.adb (continuation):
            declare                                                        
              
Folder : Folder_Node'Class renames
                        Folder_Node'Class (Ptr (Current).all);
               Offset : Natural :=
                        Locate
                        (  Folder.Data,
                           Path (Start..Pointer - 1)
                        );

When the node is a directory, the directory name Path (Start..Pointer - 1) is searched for amount its children. Offset is zero when no child is found.

File memory_fs.adb (continuation):
            begin                                                          
               if
Get_Tracing (Memory_File_System'Class (System.all))
               then
                 
Trace
                  (  Memory_File_System'Class (System.all),
                     (  "Searching "
                     &  Image (Folder)
                     &  " for "
                     &  Quote (Path (Start..Pointer - 1))
                     ),
                     Name
                  );
               end if;
               if
Offset = 0 then
                  if
Get_Tracing (System.all) then
                     Trace
                     (  System.all,
                        (  " Not exists "
                        &  Quote (Path (Start..Pointer - 1))
                     )  );
                  end if;
                  Raise_Exception
                  (  End_Error'Identity,
                     "Not exist " & Path (Start..Pointer - 1)
                  );
               end if;

When no child exists Offset is 0 and the path is wrong and End_Error is propagated.

File memory_fs.adb (continuation):
               Pointer := Pointer + 1;                                     
               Start   := Pointer;
               Current := GetTag (Folder.Data, Offset);
            end;

Otherwise, the child becomes current node. Pointer and Start are advanced behind the slash.

File memory_fs.adb (continuation):
         else                                                              
            Pointer := Pointer + 1;
         end if;
      end loop;

Characters different from slash are skipped. When the loop is existed Path (Start..Path'Last) is the file name.

File memory_fs.adb (continuation):
      if Check then                                                        
         if Get_Tracing (Memory_File_System'Class (System.all)) then
            Trace
            (  Memory_File_System'Class (System.all),
               (  "Checking "
               &  Quote (Path (Start..Path'Last))
            )  );
         end if;
         Check_Spelling (Path (Start..Path'Last));
         return Path (Start..Path'Last);

If Check is true, the file name is checked.

File memory_fs.adb (continuation):
      elsif Start > Path'Last then -- Special case                         
         return "";

Empty file name corresponds to the root directory.

File memory_fs.adb (continuation):
      else                                                                 
         return
Path (Start..Path'Last);
      end if;
   end Find;

Otherwise, Path (Start..Path'Last) is the file name.

File memory_fs.adb (continuation):
   procedure Find                                                          
             (  System : in out Memory_File_System;
                Name   : LPCWSTR;
                File   : out Node_Handles.Handle;
                Error  : out Error_Code
             )  is
      Parent : aliased Node_Handles.Handle;
   begin
      declare

         Simple : String := Find (System'Access, Name, Parent'Access);
         Offset : Natural;
         This   : Folder_Node'Class renames
                  Folder_Node'Class (Ptr (Parent).all);

This variant of Find searches for the specified name as a whole. It calls to Find splitting the path Name into a handle to the parent directory node and simple file name.

File memory_fs.adb (continuation):
      begin                                                                
         if
Simple = "" then -- Root directory
            File  := System.Root;
            Error := ERROR_SUCCESS;

When file name is empty the result is the root directory.

File memory_fs.adb (continuation):
         else                                                              
            Offset := Locate (This.Data, Simple);
            if Offset = 0 then
               Error := ERROR_FILE_NOT_FOUND;
            else
               File  := GetTag (This.Data, Offset);
               Error := ERROR_SUCCESS;
            end if;
         end if;
      end;

When file name is not empty the parent's children table is searched for it. When the search was unsuccessful is fault with ERROR_FILE_NOT_FOUND. Otherwise, File is set to the child found.

File memory_fs.adb (continuation):
   exception                                                               
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Find;

Here we catch exceptions propagating on invalid name, non-existing parent, wrong parent name and return corresponding error codes.

File memory_fs.adb (continuation):
   procedure Find                                                          
             (  Directory  : in out Memory_Folder;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Fill       : FillFileData_Ptr;
                Info       : File_Info_Ptr;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      Lock   : File_System_Lock (System'Access);

 The implementation of Find gets the file system object (Volume) and locks the file system.

File memory_fs.adb (continuation):
      function Add                                                         
               (  Item : Node'Class;
                  Name : Wide_String
               )  return Boolean is
         Data    : WIN32_FIND_DATAW;
         Pointer : Natural := 0;
         Size    : Byte_Count := Get_Size (Item);
         Result  : DWORD;
      begin
         Data.dwFileAttributes := Item.Attributes;
         Data.ftCreationTime   := Item.Created;
         Data.ftLastAccessTime := Item.Accessed;
         Data.ftLastWriteTime  := Item.Written;
         Data.nFileSizeHigh    := DWORD (Size / 2**32);
         Data.nFileSizeLow     := DWORD (Size mod 2**32);
         Data.dwReserved0      := 0;
         Data.dwReserved1      := 0;
         Data.FileName (1..Name'Length) := Name;
         if (  Name'Length > 0
            and then
               Name'Length < Data.FileName'Length
            and then
               Name (Name'Last) /= Wide_Character'Val (0)
            )  then
            Data.FileName (Name'Length + 1) := Wide_Character'Val (0);
         end if;
         Result :=
            GetShortPathNameW
            (  To_LPCWSTR (Name),
               To_LPWSTR (Data.AlternateFileName),
               Data.AlternateFileName'Length
            );
         return 0 /= Fill (Data, Info);
      end Add;

The internal subprogram Add is used to add each found child. The parameter Item is the file system node of the child. Name is the child's name in UTF-16, null-terminated. The structure WIN32_FIND_DATAW is filled with the file attributed, file times, file size. The file name (UTF-16) is also stored into it. The function GetShortPathNameW is used for the cAlternateFileName. Finally, user-defined callback Fill is called. The result is true if the callback returns non-zero.

File memory_fs.adb (continuation):
      Data : Memory_FS.Directory.Dictionary renames                        
             Directory.Folder.Data;
   begin
     
Error :=
         Check
         (  Process_ID,
            Directory.Folder.Security,
            FILE_GENERIC_READ
         );
      if Error /= ERROR_SUCCESS then
         return
;
      end if
;

First we check if the caller has access to read the directory.

File memory_fs.adb (continuation):
      for Index in 1..GetSize (Data) loop                                  
         if
Get_Tracing (System) then
            Trace
            (  System,
               (  "Found "
               &  Quote (GetName (Data, Index))
               &  " in"
               &  Image (Directory.Folder.all)
            )  );
         end if;
         exit when Add
                   (  Ptr (GetTag (Data, Index)).all,
                      From_UTF8_String (GetName (Data, Index)) & NUL
                   );
      end loop;
      Error := ERROR_SUCCESS;

Here we go through the directory's children and call Add for each of them. When Add returns false, the loop is exited prematurely, because the client cannot accept more data.

File memory_fs.adb (continuation):
   exception                                                               
      when
Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Find;

Exceptions propagating on invalid name, non-existing parent, wrong parent name are converted into the corresponding error codes.

File memory_fs.adb (continuation):
   procedure Flush                                                         
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Error      : out Error_Code
             )  is
   begin

      Error := ERROR_SUCCESS;
   end Flush;

Flush is always successful.

File memory_fs.adb (continuation):
   function Get_File_System_Flags                                          
            (  System : Memory_File_System
            )  return File_System_Flags is
   begin
      return
FILE_CASE_PRESERVED_NAMES
          or FILE_UNICODE_ON_DISK
          or FILE_PERSISTENT_ACLS;
   end Get_File_System_Flags;

The implementation of Get_File_System_Flags returns flags indicating support of preserved case of file names, Unicode and then access control lists are kept by the file system.

File memory_fs.adb (continuation):
   function Get_File_System_Name                                           
            (  System : Memory_File_System
            )  return String is
   begin
      return
"Memory file system";
   end Get_File_System_Name;

The file system name is returned as Memory file system.

File memory_fs.adb (continuation):

   procedure Get_Information                                               
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Attributes : out DWORD;
                Created    : out FILETIME;
                Accessed   : out FILETIME;
                Written    : out FILETIME;
                Size       : out Byte_Count;
                Links      : out DWORD;
                Index      : out File_Index;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      Lock   : File_System_Lock (System'Access);
      Item   : File_Node'Class renames File.File.all;
   begin
      Error :=
         Check
         (  Process_ID,
            Item.Security,
            FILE_READ_ATTRIBUTES
         );
      if Error = ERROR_SUCCESS then
         Attributes := Item.Attributes;
         Created    := Item.Created;
         Accessed   := Item.Accessed;
         Written    := Item.Written;
         Size       := Get_Size (Item);
         Links      := 0;
         Index      := File_Index (To_Integer (Item'Address));
         Error      := 0;
      end if
;
   end Get_Information;

 The implementation of Get_Information first checks read attributes access using Check. Then it takes file times from the file system node. The file size is returned by the primitive operation Get_Size.

File memory_fs.adb (continuation):
   procedure Get_Information                                               
             (  Directory  : in out Memory_Folder;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Attributes : out DWORD;
                Created    : out Win32.Winbase.FILETIME;
                Accessed   : out Win32.Winbase.FILETIME;
                Written    : out Win32.Winbase.FILETIME;
                Size       : out Byte_Count;
                Links      : out DWORD;
                Index      : out File_Index;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      Lock   : File_System_Lock (System'Access);
      Item   : Folder_Node'Class renames Directory.Folder.all;
   begin
      Error :=
         Check
         (  Process_ID,
            Item.Security,
            FILE_READ_ATTRIBUTES
         );
      if Error = ERROR_SUCCESS then
         Attributes := Item.Attributes;
         Created    := Item.Created;
         Accessed   := Item.Accessed;
         Written    := Item.Written;
         Size       := 0;
         Links      := 0;
         Index      := File_Index (To_Integer (Item'Address));
         Error      := 0;
      end if;
   end Get_Information;

The implementation for a directory is similar. The size is zero.

File memory_fs.adb (continuation):
   procedure Get_Security                                                  
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Security   : SECURITY_INFORMATION;
                Descriptor : PSECURITY_DESCRIPTOR;
                Length     : in out ULONG;
                Error      : out Error_Code
             )  is
      Lock : File_System_Lock (System'Access);
      File : Node_Handles.Handle;
   begin
      Find (System, Name, File, Error);
      if Error = ERROR_SUCCESS then

The implementation searches for the file.

File memory_fs.adb (continuation):
         declare                                                           
            Item : Node'Class renames Ptr (File).all;
            Size : aliased ULONG;
         begin
            if
Item.Security = null then
               Error := ERROR_INVALID_FUNCTION;

When there is no descriptor we return ERROR_INVALID_FUCTION and let Dokan to return a default security descriptor.

File memory_fs.adb (continuation):
            else                                                           
               Error :=
                  Check
                  (  Process_ID,
                     Item.Security,
                     FILE_READ_ATTRIBUTES
                  );

Then we check if the caller process has access rights to read file attributes.

File memory_fs.adb (continuation):
               if Error = ERROR_SUCCESS then                               
                  if
0 = GetPrivateObjectSecurity
                         (  Object        => Item.Security.all,
                            Information   => Security,
                            Descriptor    => Descriptor,
                            Length        => Length,
                            Return_Length => Size'Unchecked_Access
                         )  then
                     Length := Size;
                     Error  := GetLastError;
                     if ERROR_INSUFFICIENT_BUFFER = Error then
                        Error := ERROR_MORE_DATA;
                     end if;
                  else
                    
Length := Size;
                     Error  := ERROR_SUCCESS;
                  end if;
               end if;
            end if;
         end;
      end if;
   end Get_Security;

In case of success we use Windows' operation GetPrivateObjectSecurity to copy the descriptor into the user buffer. When it fails with ERROR_INSUFFICIENT_BUFFER we return ERROR_MORE_DATA with Length set to the required space.

File memory_fs.adb (continuation):
   function Get_Size (File : File_Node) return Byte_Count is               
   begin
      return
Byte_Count (File.Length);
   end Get_Size;

The implementation of Get_Size of File_Node returns the actual size of the file.

File memory_fs.adb (continuation):
   function Get_Size (File : Folder_Node) return Byte_Count is             
      Size : Byte_Count := 0;
   begin
      for
Index in 1..GetSize (File.Data) loop
         Size := Size + Get_Size (Ptr (GetTag (File.Data, Index)).all);
      end loop;
      return Size;
   end Get_Size;

The implementation of Get_Size of File_Folder returns recursively calculated size of all files in the directory.

File memory_fs.adb (continuation):
   function Get_Volume_Name                                                
            (  System : Memory_File_System
            )  return String is
   begin
      return
"RAM DISK";
   end Get_Volume_Name;

The implementation of Get_Volume_Name returns RAM DISK.

File memory_fs.adb (continuation):
   function Get_Volume_Serial_Number                                       
            (  System : Memory_File_System
            )  return DWORD is
   begin
      return
1;
   end Get_Volume_Serial_Number;

 The file system serial number is 1.

File memory_fs.adb (continuation):
   procedure Get_Volume_Space                                              
             (  System     : in out Memory_File_System;
                Process_ID : ULONG;
                Total      : out Byte_Count;
                Free       : out Byte_Count;
                Available  : out Byte_Count
             )  is
      Lock : Holder (System.Lock'Access);
   begin
      Total     := Get_Size (Ptr (System.Root).all);
      Free      := 0;
      Available := 0;
   end Get_Volume_Space;

The implementation of Get_Volume_Space calculates used space and returns it as total volume space. Free and available space are returned zero.

File memory_fs.adb (continuation):
   procedure Initialize (System : in out Memory_File_System) is            
   begin

      Set (System.Root, new Folder_Node);
      Initialize (Abstract_File_System (System));
   end Initialize;

Initialization of the file system creates the root directory node and then calls to the parent type initialization.

File memory_fs.adb (continuation):
   procedure Initialize (File : in out Node) is                            
      use
Object;
      Stamp : constant FILETIME := From_Time (Clock);
   begin
      Initialize (Entity (File));
      File.Created  := Stamp;
      File.Accessed := Stamp;
      File.Written  := Stamp;
   end Initialize;

Initialization of a file system node calls to the parent type initialization and sets file or directory times to the current time.

File memory_fs.adb (continuation):
   procedure Initialize (File : in out Folder_Node) is                     
   begin

      File.Attributes := FILE_ATTRIBUTE_DIRECTORY;
      Initialize (Node (File));
   end Initialize;

Initialization of a file system directory sets the directory attribute and calls to the parent's Initialize.

File memory_fs.adb (continuation):
   procedure Initialize (File : in out File_Node) is                       
   begin

      File.Attributes := 0;
      Initialize (Node (File));
   end Initialize;

Initialization of a file system file sets the directory attribute to zero and calls to the parent's Initialize.

File memory_fs.adb (continuation):
   procedure Move                                                          
             (  System     : in out Memory_File_System;
                Process_ID : ULONG;
                Old_Name   : LPCWSTR;
                New_Name   : LPCWSTR;
                Replace    : Boolean;
                Error      : out Error_Code
             )  is
       Lock : File_System_Lock (System'Access);
      use Strings_Edit.UTF8.Maps.Constants;
   begin
      declare

         Old_Path : String := To_UTF8_String (Old_Name);
         New_Path : String := To_UTF8_String (New_Name);
      begin

The implementation of Move renames a file. It starts with converting the old and the new file paths to UTF-8.

File memory_fs.adb (continuation):
         declare                                                           
            Old_Parent : aliased Node_Handles.Handle;
            New_Parent : aliased Node_Handles.Handle;
            Old_Simple : String :=
               Find (System'Access, Old_Name, Old_Parent'Access);
            New_Simple : String :=
               Find (System'Access, New_Name, New_Parent'Access);
            Old_Offset : Natural;
            New_Offset : Natural;
            File       : Node_Handles.Handle;
            Old_Folder : Folder_Node'Class renames
                         Folder_Node'Class (Ptr (Old_Parent).all);
            New_Folder : Folder_Node'Class renames
                         Folder_Node'Class (Ptr (New_Parent).all);

Both paths are split into parent and simple file name. The parents are then accessed as Old_Folder and New_Folder.

File memory_fs.adb (continuation):
         begin                                                             
            Old_Offset := Locate (Old_Folder.Data, Old_Simple);
            if Old_Offset = 0 then
               Error := ERROR_FILE_NOT_FOUND;
               return;

The old file is searched in its parent directory. When not found (Old_Offset is zero) the implementation fails with ERROR_FILE_NOT_FOUND.

File memory_fs.adb (continuation):
            elsif Is_Prefix (Old_Path, New_Path, Lower_Case_Map) then      
               if New_Path'Length = Old_Path'Length then
                  Error := 0;
                  return;

When the old path is contained by the new path (ignoring the case) and both have same length, the implementation succeeds, null renaming.

File memory_fs.adb (continuation):
               elsif New_Path (Old_Path'Length) = '\' then                 
                  Error := ERROR_IS_SUBST_PATH;
                  return;
               end if;
            end if;

When the old path in the new path is followed by slash, the renaming fails with ERROR_IS_SUBST_PATH as an attempt to rename directory into its own descendant.

File memory_fs.adb (continuation):
            New_Offset := Locate (New_Folder.Data, New_Simple);            
            if New_Offset = 0 then
               Error :=
                  Check
                  (  Process_ID,
                     Old_Folder.Security,
                     FILE_ALL_ACCESS
                  );
               if Error /= ERROR_SUCCESS then
                  return
;
               end if;
               Error :=
                  Check
                  (  Process_ID,
                     New_Folder.Security,
                     FILE_ALL_ACCESS
                  );
               if Error /= ERROR_SUCCESS then
                  return
;
               end if;

The new file is located in its parent directory. When no such file exists (New_Offset is zero), a handle to the file system node is stored. Then it is checked that the caller has access to both directories.

File memory_fs.adb (continuation):
               File := GetTag (Old_Folder.Data, Old_Offset);               
               Directory.Delete (Old_Folder.Data, Old_Offset);
               Add (New_Folder.Data, New_Simple, File);
               Error := ERROR_SUCCESS;

Then the file is removed from its parent directory and put into the new parent's directory under new name.

File memory_fs.adb (continuation):
            elsif Replace then                                             
               Error :=
                  Check
                  (  Process_ID,
                     Old_Folder.Security,
                     FILE_ALL_ACCESS
                  );
               if Error /= ERROR_SUCCESS then
                  return
;
               end if;
               Error :=
                  Check
                  (  Process_ID,
                     New_Folder.Security,
                     FILE_ALL_ACCESS
                  );
               if Error /= ERROR_SUCCESS then
                  return
;
               end if;

When there is a file with the same name and Replace is true, the old file can be replaced. Here we also check access rights first.

File memory_fs.adb (continuation):
               File := GetTag (Old_Folder.Data, Old_Offset);               
               Directory.Delete (Old_Folder.Data, Old_Offset);
               Directory.Replace (New_Folder.Data, New_Offset, File);
               Error := ERROR_SUCCESS;

The handle to the renamed file replaces the handle to the existing file in the new parent directory. This has the effect of removing the existing file or directory.

File memory_fs.adb (continuation):
            else                                                           
               Error := ERROR_FILE_EXISTS;
            end if;
         end;
      end;

When Replace is false, the implementation fails with ERROR_FILE_EXISTS.

File memory_fs.adb (continuation):
   exception                                                               
      when Constraint_Error =>
         Error := ERROR_INVALID_NAME;
      when End_Error =>
         Error := ERROR_PATH_NOT_FOUND;
      when Name_Error =>
         Error := ERROR_BAD_PATHNAME;
   end Move;

Exceptions propagating on invalid name, non-existing parent, wrong parent name are converted into the corresponding error codes.

File memory_fs.adb (continuation):
   procedure Open_Directory                                                
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Directory  : out Abstract_Directory_Ptr;
                Error      : out Error_Code
             )  is
      Lock : File_System_Lock (System'Access);
      File : Node_Handles.Handle;
   begin
      Find (System, Name, File, Error);

The implementation of Open_Directory searches for the file specified.

File memory_fs.adb (continuation):
      if Error = ERROR_SUCCESS then                                        
         declare

            Item : Node'Class renames Ptr (File).all;
         begin

When search was successful the corresponding file system node is accessed and checked for being a directory node.

File memory_fs.adb (continuation):
            if Item in Folder_Node'Class then                              
               Error :=
                  Check
                  (  Process_ID,
                     Item.Security,
                     FILE_LIST_DIRECTORY or FILE_READ_ATTRIBUTES
                  );

When the node is a directory we access rights to traverse and read attributes.

File memory_fs.adb (continuation):
                  if Error = ERROR_SUCCESS then
                     Directory :=
                        new Memory_Folder
                            (  Folder_Node'Class (Item)'Unchecked_Access
                            );
                     declare
                        Result : Memory_Folder renames
                                 Memory_Folder (Directory.all);
                     begin
                        Result.Reference := File;
                        if Get_Tracing (Memory_File_System'Class (System))
                        then
                           Trace
                           (  Memory_File_System'Class (System),
                              (  "Opened directory at"
                              &  Image (Item)
                              &  " Use count"
                              &  Integer'Image (Item.Use_Count - 1)
                              ),
                              Name
                           );
                         end if;
                      end;
                   end if;

When the node is a directory an instance of Memory_Folder is created and a reference to the directory is stored in it. Finally, some tracing is done.

File memory_fs.adb (continuation):
            else                                                           
               Error := ERROR_DIRECTORY;
            end if;
         end;
      end if;
   end Open_Directory;

When the node is a plain file, the implementation fails with the code ERROR_DIRECTORY.

File memory_fs.adb (continuation):
   procedure Read                                                          
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Buffer     : Storage_Pointers.Pointer;
                Length     : in out Storage_Offset;
                Offset     : Byte_Count;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      Lock   : File_System_Lock (System'Access);
      Self   : File_Node'Class renames File.File.all;
   begin
      if
0 = (File.Mode and FILE_GENERIC_READ) then
         Error := ERROR_ACCESS_DENIED;

The implementation of Read checks if the access mode allows Read_Access. If not the implementation fails with ERROR_ACCESS_DENIED.

File memory_fs.adb (continuation):
      elsif Offset > Byte_Count (Self.Length) then                         
         Error := ERROR_HANDLE_EOF;

If the Offset points beyond the file end the implementation fails with ERROR_HANDLE_EOF.

File memory_fs.adb (continuation):
      else                                                                 
         declare

            Count : constant Storage_Offset :=
                             Storage_Offset'Max
                             (  Self.Length - Storage_Offset (Offset),
                                0
                             );
         begin

When the offset is within the file Count is the number of bytes available to read.

File memory_fs.adb (continuation):
            if Count < Length then                                         
               Length := Count;
            end if;

When number of available bytes is less than the buffer length we reduce Length to the number of available bytes.

File memory_fs.adb (continuation):
            if Count > 0 then                                              
               Storage_Pointers.Copy_Array
               (  Source => Self.Data.Vector
                            (  Storage_Offset (Offset) + 1
                            ) 'Unchecked_Access,
                  Target => Buffer,
                  Length => Interfaces.C.ptrdiff_t (Length)
               );
            end if;
         end;
         Error := ERROR_SUCCESS;
      end if;
   end Read;

Then we copy Length bytes into the buffer using standard operation from the instance of Interfaces.C.Pointers.

File memory_fs.adb (continuation):
   procedure Set_Attributes                                                
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Attributes : DWORD;
                Error      : out Error_Code
             )  is
      Lock : File_System_Lock (System'Access);
      File : Node_Handles.Handle;
   begin
      Find (System, Name, File, Error);

The implementation of Set_Attributes starts with searching for the file.

File memory_fs.adb (continuation):
      if Error = ERROR_SUCCESS then                                        
         declare

            Item : Node'Class renames Ptr (File).all;
         begin

When the search was successful the file system node is accessed as Item.

File memory_fs.adb (continuation):
            Error :=                                                       
               Check (Process_ID, Item.Security, FILE_WRITE_ATTRIBUTES);
            if Error /= ERROR_SUCCESS then
               return
;
            end if;

First we check access rights to write attributes.

File memory_fs.adb (continuation):
            if Attributes = FILE_ATTRIBUTE_NORMAL then                     
               if Item in Folder_Node'Class then
                  Error := ERROR_INVALID_DATA;
               else
                  Item.Attributes := 0;
               end if;

When Attributes is FILE_ATTRIBUTE_NORMAL the node is checked for being a directory. For a directory the implementation fails with ERROR_INVALID_DATA. Otherwise, file attributes are zeroed.

File memory_fs.adb (continuation):
            elsif 0 = (Attributes and not Supported_Attributes) then       
               if
Item in Folder_Node'Class then
                  if
0 = (Attributes and FILE_ATTRIBUTE_DIRECTORY) then
                     Error := ERROR_INVALID_DATA;
                  else
                     Item.Attributes :=
                        Attributes and not FILE_ATTRIBUTE_NORMAL;
                  end if;

When no unsupported attributes appear in Attributes and the node is a directory Attributes is checked for having FILE_ATTRIBUTE_DIRECTORY set. When not set the implementation fails with ERROR_INVALID_DATA. Otherwise, attributes are set. Note that FILE_ATTRIBUTE_NORMAL is cleaned because it is not a proper attribute.

File memory_fs.adb (continuation):
               else                                                        
                  if
0 = (Attributes and FILE_ATTRIBUTE_DIRECTORY) then
                     Item.Attributes :=
                        Attributes and not FILE_ATTRIBUTE_NORMAL;
                  else
                     Error := ERROR_INVALID_DATA;
                  end if;
               end if;

When the node is a file the behavior is reverse. When FILE_ATTRIBUTE_DIRECTORY  is used the implementation fails.

File memory_fs.adb (continuation):
            else                                                           
               Error := ERROR_INVALID_DATA;
            end if;
         end;
      end if;
   end Set_Attributes;

When unsupported attributes are used, the implementation fails with ERROR_INVALID_DATA.

File memory_fs.adb (continuation):
   procedure Set_End_Of                                                    
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Length     : Byte_Count;
                Error      : out Error_Code
             )  is
      Volume : Memory_File_System'Class renames
               Memory_File_System'Class (System);
      Lock   : File_System_Lock (System'Access);
      Self   : File_Node'Class renames File.File.all;

The implementation of Set_End_Of accesses the file node.

File memory_fs.adb (continuation):
   begin                                                                   
      Error := Check (Process_ID, Self.Security, GENERIC_WRITE);

First we check of we have access write rights.

File memory_fs.adb (continuation):
      if Error = ERROR_SUCCESS then                                        
         if Length > Byte_Count (Self.Length) then
            begin

               Put (Self.Data, Storage_Offset (Length), 0);
            exception
               when others
=>
                  Error := ERROR_FILE_TOO_LARGE;
                  return;
            end;
         end if;
         Self.Length := Storage_Count (Length);
      end if;
   end Set_End_Of;

When check was successful and the set length is larger than the file we put that last byte to extend it. If extending the unbounded array fails ERROR_FILE_TO_LARGE is returned. Finally the file length is set to Length.

File memory_fs.adb (continuation):
   procedure Set_File_Time                                                 
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Created    : FILETIME;
                Accessed   : FILETIME;
                Written    : FILETIME;
                Error      : out Error_Code
             )  is
      Lock : File_System_Lock (System'Access);
      File : Node_Handles.Handle;
   begin
      Find (System, Name, File, Error);

The implementation of Set_File_Time searches for the file specified.

File memory_fs.adb (continuation):
      if Error = ERROR_SUCCESS then                                        
         declare

            Item : Node'Class renames Ptr (File).all;
         begin
            Error :=
               Check
               (  Process_ID,
                  Item.Security,
                  FILE_WRITE_ATTRIBUTES
               );

If the file or directory exists we check the access rights to write it.

File memory_fs.adb (continuation):
            if Error = ERROR_SUCCESS then                                  
               if
Created /= (0, 0) then
        
         Item.Created := Created;
               end if;
               if Accessed /= (0, 0) then
                  Item.Accessed := Accessed;
               end if;
               if
Written /= (0, 0) then
                  Item.Written := Written;
               end if;
            end if
;
         end
;
      end if;
   end Set_File_Time;

The file times are set unless the time stamp is not (0, 0).

File memory_fs.adb (continuation):
   procedure Set_Security                                                  
             (  System     : in out Memory_File_System;
                Name       : LPCWSTR;
                Process_ID : ULONG;
                Security   : SECURITY_INFORMATION;
                Descriptor : SECURITY_DESCRIPTOR;
                Error      : out Error_Code
             )  is
      Lock : File_System_Lock (System'Access);
      File : Node_Handles.Handle;
   begin
      Find (System, Name, File, Error);
      if Error /= ERROR_SUCCESS then
         return
;
      end if;

The implementation Set_Security locks the file system and searches the file.

File memory_fs.adb (continuation):
      declare                                                              
        
Item : Node'Class renames Ptr (File).all;
      begin
        
Error := Check (Process_ID, Item.Security, FILE_ALL_ACCESS);
         if Error = ERROR_SUCCESS then
            return
;
         end if;

When file is found and accessed as Item it is checked for full access.

File memory_fs.adb (continuation):
         declare                                                           
            Caller : HANDLE_Holder;
         begin
            Caller.Resource :=
               OpenProcess
               (  Desired_Access => PROCESS_QUERY_INFORMATION,
                  Inherit_Handle => 0,
                  Process_ID     => Process_ID
               );
            if not Is_Valid (Caller) then
               Error := GetLastError;
               return;
            end if;

We will require access to the caller's process in order to modify the security descriptor. The type HANDLE_Holder is declared to keep a handle to the process. Then the process is opened for PROCESS_QUERY_INFORMATION access. If this fails we stop with the error reported.

File memory_fs.adb (continuation):
            if Item.Security = null then -- No security set                
               Item.Security := LocalAlloc;
               if 0 = InitializeSecurityDescriptor (Item.Security) then
                  Error := GetLastError;
                  Item.Security := LocalFree (Item.Security);
                  return;
               end if;
            end if;

When no security descriptor was created, we allocate and create it new.

File memory_fs.adb (continuation):
            if 0 = SetPrivateObjectSecurity                                
                   (  Information  => Security,
                      Modification => Descriptor,
                      Object       => Item.Security'Unchecked_Access,
                      Mapping      => File_Access_Mapping,
                      Token        => Caller.Resource
                   )  then
               Error := GetLastError;
            else
               Error := ERROR_SUCCESS;
            end if;
         end;
      end;
   end Set_Security;

Here the security descriptor is modified using the specified descriptor using the default access mask mapping and the caller process token.

File memory_fs.adb (continuation):
   procedure Set_Trace_File                                                
             (  System    : in out Memory_File_System;
                File_Name : String
             )  is
      use Ada.Text_IO;
      Lock : Holder (System.Lock'Access);
   begin
      if
System.Into_File then
         Close (System.Trace_File);
         System.Into_File := False;
      end if;
      Create (System.Trace_File, Out_File, File_Name);
      Trace (System, "Memory file system trace");
      System.Into_File := True;
   end Set_Trace_File;

When a trace file is set the old trace file is closed if open. Then the new file is created.

File memory_fs.adb (continuation):
   procedure Set_Trace_File (System : in out Memory_File_System) is        
      use
Ada.Text_IO;
   begin
      if
System.Into_File then
         Close (System.Trace_File);
         System.Into_File := False;
      end if;
   end Set_Trace_File;

When no trace file path is given, the existing trace file is closed if any.

File memory_fs.adb (continuation):
   procedure Trace                                                         
             (  System : in out Memory_File_System;
                Text   : String
             )  is
      use
Ada.Text_IO;
   begin
      if
System.Into_File then
         Put_Line (System.Trace_File, Text);
         Flush (System.Trace_File);
      else
         Put_Line (Text);
         Flush;
      end if;
   end Trace;

The implementation of Trace writes into the trace file or else to the standard output.

File memory_fs.adb (continuation):
   procedure Trace                                                         
             (  System : in out Memory_File_System;
                Text   : String;
                Name   : LPCWSTR
             )  is
   begin
     
Trace (System, Text);
   end Trace;

This variant of trace adds file name to the trace message.

File memory_fs.adb (continuation):
   procedure Trace                                                         
             (  System : in out Memory_File_System;
                Text   : String;
                Error  : Exception_Occurrence
             )  is
   begin

      Trace (System, Text);
   end Trace;

This is a variant of tracing that adds exception occurrence to the trace message.

File memory_fs.adb (continuation):
   procedure Trace
             (  System : in out Memory_File_System;                        
                Text   : String;
                Name   : LPCWSTR;
                Error  : Exception_Occurrence
             )  is
   begin

      Trace (System, Text);
   end Trace;

This trace that adds file name and exception occurrence to the trace message.

File memory_fs.adb (continuation):
   procedure Write                                                         
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Buffer     : Storage_Array;
                Error      : out Error_Code
             )  is
      Volume  : Memory_File_System'Class renames
                Memory_File_System'Class (System);
      Lock    : File_System_Lock (System'Access);
      Self    : File_Node'Class renames File.File.all;
      Pointer : Storage_Pointers.Pointer := Buffer;
      Index   :  Storage_Offset  renames Self.Length;
   begin
      if
0 = (File.Mode and FILE_GENERIC_WRITE) then
        
Length := 0;
         Error  := ERROR_ACCESS_DENIED;

The implementation of Write access the file node and checks of the write-access was granted. If not the implementation fails with ERROR_ACCESS_DENIED.

File memory_fs.adb (continuation):
      else                                                                 
         for
Count in 1..Length loop
            Put (Self.Data, Index + 1, Pointer.all);
            Storage_Pointers.Increment (Pointer);
            Index := Index + 1;
         end loop
;
         Error := ERROR_SUCCESS;
      end if;

The contents of the buffer is appended to file starting from Length.

File memory_fs.adb (continuation):
   exception                                                               
      when
Storage_Error =>
         Length := 0;
         Error  := ERROR_HANDLE_DISK_FULL;
   end Write;

On storage error the implementation fails with ERROR_HANDLE_DISK_FULL.

File memory_fs.adb (continuation):
   procedure Write                                                         
             (  File       : in out Memory_File;
                Name       : LPCWSTR;
                System     : in out Abstract_File_System'Class;
                Process_ID : ULONG;
                Buffer     : Storage_Array;
                Offset     : Byte_Count;
                Error      : out Error_Code
             )  is
      Volume  : Memory_File_System'Class renames
                Memory_File_System'Class (System);
      Lock    : File_System_Lock (System'Access);
      Self    : File_Node'Class renames File.File.all;
      Pointer : Storage_Pointers.Pointer := Buffer;
      Index   : Storage_Offset := Storage_Offset (Offset);
   begin
      if
0 = (File.Mode and FILE_GENERIC_WRITE) then
         Length := 0;
         Error  := ERROR_ACCESS_DENIED;
      elsif Offset > Byte_Count (Self.Length) then
         Length := 0;
         Error  := ERROR_HANDLE_EOF;
      else
         for
Count in 1..Length loop
            Put (Self.Data, Index + 1, Pointer.all);
            Storage_Pointers.Increment (Pointer);
            Index := Index + 1;
         end loop
;
         Error := ERROR_SUCCESS;
      end if;
   exception
      when
Storage_Error =>
         Length := 0;
         Error  := ERROR_HANDLE_DISK_FULL;
   end Write;

end Memory_FS;

This variant of Write is different that it starts at the middle of the file. The file length is set to the maximum of the old length and the end of the overwritten file part.

The test application in the subdirectory example/memory-fs/test illustrates usage of the virtual file system:

File test.adb:
with Ada.Exceptions;     use Ada.Exceptions;                               
with Dokan.File_System;  use Dokan.File_System;
with Dokan.Thin;         use Dokan.Thin;
with Memory_FS;          use Memory_FS;
with Text_IO;            use Text_IO;

procedure Test is
   FS : Memory_File_System;
   Mount_Point : String := "R:\";

The object FS is an instance of the virtual file system. The file system is not visible until it gets mounted. Mount_Point is where the file system must be mounted at.

File test.adb:
begin                                                                      
   Set_Trace_File (FS, "trace.txt");
   Enable_Tracing (FS, True);

This activates tracing into the file trace.txt.

File test.adb:
   Put_Line ("Mounting the files system on " & Mount_Point);               
   Mount (FS, Mount_Point, Debug or Stderr, 1);

Here the file system is mounted, Debug and Stderr options are passed to the Dokan library.

File test.adb:
   Put_Line ("Hit enter to exit");                                         
   declare
      Input : String (1..80);
      Last  : Integer;
   begin
      Get_Line (Input, Last);
      Put_Line ("Exiting");
   end;

 This awaits user input to complete test. When user hits enter the application exists and finalization of the file system object automatically dismounts the file system.

File test.adb:
exception                                                                  
   when
Error : others =>
      Put_Line ("Error: " & Exception_Information (Error));
end Test;

This prints unanticipated exceptions.


[Back][TOC][Next]

3. Packages

The root directory contains only the packages required for software use. The examples are located in the subdirectory example/memory_fs. The Dokan distribution is contained in the subdirectory dokan-1.1.0.

[Back][TOC][Next]

3.1. Source packages

The following table describes the packages and other compilation units provided by the software.

Package Provides
Dokan The base package
       File_System Thick bindings to the Dokan library
Thin Thin bindings to the Dokan library
Win32API Windows API bindings used to deal with the Dokan library
       Error_Codes Windows error codes
NTSTATUS_Codes Windows kernel status codes

[Back][TOC][Next]

3.2. Tests and examples

The subdirectory example/memory_fs contains a sample of virtual memory-resident file system.


[Back][TOC][Next]

4. Installation

The software provides gpr project files, which can be used in the Gnat Programming Studio (GPS):

Project files Provides
dokan.gpr Bindings to the Dokan library
memory_fs.gpr Sample virtual  memory-resident file system

The Dokan driver must be installed to be able to run applications using the library. The driver marshals I/O requests from the Windows kernel to the user-space library and ultimately to the application. The installation executable is DokanSetup_redist.exe in the subdirectory dokan-1.1.0.


[Back][TOC][Next]

5. Changes log

The following versions were tested with the compilers:

Changes (5 Aug 2018) to the version 1.0:

The following versions were tested with the compilers:

First version released.


[Back][TOC]

6. Table of Contents

1. Thick bindings
    1.1. File system
    1.2. Abstract node
    1.3. Abstract directory
    1.4. Abstract file
    1.5. Utility operations
    1.6. Windows API
2. Samples
    2.1. Memory-resident file system
3. Packages
    3.1. Source packages
    3.2. Test and examples
4. Installation
5. Changes log
6. Table of contents