DOKAN ADA BINDINGS
version 2.0
by Dmitry A. Kazakov
(mailbox@dmitry-kazakov.de)
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_2_0.tgz (tar + gzip) |
See also changes log.
The thick bindings are provided by the package Dokan.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;
Caller : HANDLE;
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). Caller is an access token of 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;
Caller : HANDLE;
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). Caller is an access token of 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 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.
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.
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;
Caller : HANDLE;
Total : out Byte_Count;
Free : out Byte_Count;
Available : out Byte_Count
);
This procedure is called to get file system statistics. Caller is an access token of 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;
Caller : HANDLE;
Directory : out Abstract_Directory_Ptr;
Error : out Error_Code
);
This procedure is called to open a directory. Caller is an access token of 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 sets ERROR_ACCESS_DENIED in Error and null in Directory.
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. 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;
Threads : Boolean := True;
Timeout : Duration := 2.0
);
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 Threads is true, the file system operations are called concurrently from several system threads. In that case the implementation must protect the internal data from corruption. 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. Timeout is the time to wait until the system becomes mounted, if less or equal to zero the procedure ends without waiting. 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 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.
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.
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 Check_Delete
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System;
Error : out Error_Code
) is abstract;
This procedure is called to verify if a file or directory can be deleted. When it is a non-empty directory is not empty the operation fails with ERROR_DIR_NOT_EMPTY. Name is the file or directory path (UTF-16). Error is the result Windows error code. The result is ERROR_SUCCESS when the file or directory can be deleted. The operation does not delete anything the file or directory will be actually deleted upon a call to Delete when the file or directory is closed.
procedure Delete
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System
) is abstract;
This procedure is called to actually delete a file or directory after it was checked by a call to Check_Delete. Normally the operation should not fail because it was already checked. Name is the directory path (UTF-16).
procedure Close
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System'Class;
Delete : Boolean;
Error : out Error_Code
) is abstract;
This procedure is called when an 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. Delete is true if the file or directory is must be deleted. Typically it would call Delete. 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.
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;
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 a file or directory. Name is the file or directory path (UTF-16). System is the file system the file or directory is located on. Error is the result Windows error code. For time conversions see From_Time and To_Time.
procedure Get_Security
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System;
Security : SECURITY_INFORMATION;
Descriptor : PSECURITY_DESCRIPTOR;
Length : in out ULONG;
Error : out Error_Code
);
This procedure is called to obtain file or directory security descriptor. Name is the file or directory path (UTF-16). System is the file system the file or directory is located on. 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 one of ERROR_NOT_ENOUGH_MEMORY, ERROR_INSUFFICIENT_BUFFER, ERROR_MORE_DATA 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.
procedure Set_Attributes
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System;
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). System is the file system the file or directory is located on. 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
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System;
Created : FILETIME;
Accessed : FILETIME;
Written : FILETIME;
Error : out Error_Code
);
This procedure sets the file or directory times. Name is the file or directory path (UTF-16). System is the file system the file or directory is located on. 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
( File : in out Abstract_Node;
Name : LPCWSTR;
System : in out Abstract_File_System;
Security : SECURITY_INFORMATION;
Descriptor : SECURITY_DESCRIPTOR;
Error : out Error_Code
);
This procedure modifies the file or directory security. Name is the file or directory path (UTF-16). System is the file system the file or directory is located on. 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.
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:
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;
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. 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.
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:
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_File;
Name : LPCWSTR;
System : in out Abstract_File_System'Class;
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. Error is the result Windows error code. The default implementation fails with ERROR_WRITE_FAULT.
procedure Lock
( File : in out Abstract_File;
Name : LPCWSTR;
System : in out Abstract_File_System'Class;
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. 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_File;
Name : LPCWSTR;
System : in out Abstract_File_System'Class;
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. 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_File;
Name : LPCWSTR;
System : in out Abstract_File_System'Class;
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. 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_File;
Name : LPCWSTR;
System : in out Abstract_File_System'Class;
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. 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;
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. 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;
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. 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.
The package Dokan.File_System provides the following utility operations:
function Create_Default_Security_Descriptor
( Caller : HANDLE;
Is_Directory : Boolean;
Parent : access SECURITY_DESCRIPTOR := null;
Descriptor : out PSECURITY_DESCRIPTOR;
Error : out Error_Code
);
This function creates a security descriptor for a newly created file or directory. Caller is access token of the process creating the file or directory. This is the parameter Caller of Create_File, for example. Is_Directory is true for a new directory. Parent is the security descriptor of the container directory. It is optional. Descriptor is a new descriptor allocated on successful completion. It must be freed using LocalFree if no more needed. Note that operations modifying descriptors reallocate them in the local heap. Error is the result. Descriptor is allocated only if Error is set to ERROR_SUCCESS.
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.
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.
The package Dokan.Win32API defines a portion of Windows API. The following important data types are used throughout of bindings:
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.
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.
The type NTSTATUS is used when communicating with the kernel operations. The child packages Dokan.Win32API.NTSTATUS_Codes defines kernel status codes.
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
( Token : HANDLE;
Descriptor : PSECURITY_DESCRIPTOR;
Desired_Access : NT_Access;
Mapping : GENERIC_MAPPING := File_Access_Mapping
) return Error_Code;
This function checks if the process access token Token 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.
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.
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):
-- -- Set_Trace_Error -- Designate the trace output file -- -- System - File system -- -- Use the standard error file -- procedure Set_Trace_Error (System : in out Memory_File_System); |
This procedure is used to redirect tracing to the standard error. It can be useful in combination with the Dokan debug standard error output.
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; Caller : HANDLE; 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_Unlocked (File : Node) return Byte_Count is abstract; |
The abstract operation Get_Size_Unlocked 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; Caller : HANDLE; Parent : PSECURITY_DESCRIPTOR; System : in out Memory_File_System'Class ); function Get_Size_Unlocked (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_Unlocked (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; Caller : HANDLE; Parent : PSECURITY_DESCRIPTOR; System : in out Memory_File_System'Class ); function Get_Size_Unlocked (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
Trace_File_Type is (File, Output, Error); 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:
procedure Create_Directory ( System : in out Memory_File_System; Name : LPCWSTR; Caller : HANDLE; Directory : out Abstract_Directory_Ptr; Error : out Error_Code ); procedure Create_File ( System : in out Memory_File_System; Name : LPCWSTR; Caller : HANDLE; 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 Do_Unmount (System : in out Memory_File_System); procedure Finalize (System : in out Memory_File_System); |
Here some of the abstract operations of the base type are overridden.
File memory_fs.ads (continuation): function Find_Unlocked ( 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_Unlocked ( 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; procedure Get_Security_Unlocked ( System : in out Memory_File_System; Name : LPCWSTR; Security : SECURITY_INFORMATION; Descriptor : PSECURITY_DESCRIPTOR; Length : in out ULONG; Error : out Error_Code ); 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; 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; Old_Name : LPCWSTR; New_Name : LPCWSTR; Replace : Boolean; Error : out Error_Code ); procedure Open_Directory ( System : in out Memory_File_System; Name : LPCWSTR; Caller : HANDLE; Directory : out Abstract_Directory_Ptr; Error : out Error_Code ); procedure Set_Security_Unlocked ( System : in out Memory_File_System; Name : LPCWSTR; 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:
procedure Check_Delete ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Error : out Error_Code ); procedure Close ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; 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 Delete ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class ); procedure Flush ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Error : out Error_Code ); procedure Get_Information ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; 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 Get_Security ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : PSECURITY_DESCRIPTOR; Length : out ULONG; Error : out Error_Code ); procedure Read ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; 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; Length : Byte_Count; Error : out Error_Code ); procedure Set_Attributes ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Attributes : DWORD; Error : out Error_Code ); procedure Set_End_Of ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Length : Byte_Count; Error : out Error_Code ); procedure Set_File_Time ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Created : FILETIME; Accessed : FILETIME; Written : FILETIME; Error : out Error_Code ); procedure Set_Security ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : SECURITY_DESCRIPTOR; Error : out Error_Code ); procedure Write ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; 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; Buffer : Storage_Pointers.Pointer; Length : in out Storage_Count; Offset : 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 Check_Delete ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Error : out Error_Code ); 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 Delete ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class ); procedure Find ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; 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; 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 Get_Security ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : PSECURITY_DESCRIPTOR; Length : out ULONG; Error : out Error_Code ); procedure Set_Attributes ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Attributes : DWORD; Error : out Error_Code ); procedure Set_File_Time ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Created : FILETIME; Accessed : FILETIME; Written : FILETIME; Error : out Error_Code ); procedure Set_Security ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : SECURITY_DESCRIPTOR; 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 Ada.Task_Identification; 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): 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 : in out Memory_Folder; Name : LPCWSTR; System : in out Memory_File_System'Class; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Parent : aliased Node_Handles.Handle; begin declare Simple : constant String := Find_Unlocked ( Memory_File_System'Class (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 or else GetTag (This.Data, Offset) /= Directory.Reference ) then Error := ERROR_FILE_NOT_FOUND; |
The parent directory is searched for the file name and checks if that is indeed the directory. 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):
else if ( ( 0 /= ( Directory.Folder.Attributes and FILE_ATTRIBUTE_TEMPORARY ) ) or else GetSize (Directory.Folder.Data) = 0 ) then Error := ERROR_SUCCESS; else Error := ERROR_DIR_NOT_EMPTY; end if; end; end if; |
When it is a directory and it is temporary or else empty the result is ERROR_SUCCESS. 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; |
Finally, exceptions are converted into the corresponding error codes.
File memory_fs.adb (continuation): procedure
Check_Delete
( File : in out Memory_File; Name : LPCWSTR; System : in out Memory_File_System'Class; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Parent : aliased Node_Handles.Handle; begin declare Simple : constant String := Find_Unlocked ( Memory_File_System'Class (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 or else GetTag (This.Data, Offset) /= File.Reference ) then Error := ERROR_FILE_NOT_FOUND; else Error := ERROR_SUCCESS; end if; |
The implementation checks if the file indeed exist.
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 Check_Delete_File; |
The completion is same as in Check_Delete for a 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; Delete : Boolean; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); This : File_Node'Class renames File.File.all; 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 Memory_FS.Delete (File, Name, System); |
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 (System) then Trace ( System, ( "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; Delete : Boolean; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); This : File_Node'Class renames File.File.all; begin if Delete then This.Attributes := This.Attributes or FILE_ATTRIBUTE_TEMPORARY; Memory_FS.Delete (Directory, Name, System); else This.Accessed := From_Time (Clock); if Get_Tracing (System) then Trace ( System, ( "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; Caller : Dokan.Win32API.HANDLE; 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 : constant String := Find_Unlocked (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 (Caller, 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, Caller, This.Security, System ); |
Here we create the 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 " & Quote (Folder) & " 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 ( Caller, 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 " & Quote (Folder) & " at" & Image (Ptr (Item).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; Caller : Dokan.Win32API.HANDLE; 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 : constant String := Find_Unlocked ( 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 => Error := Check ( Caller, Ptr (System.Root).Security, Desired_Access ); if Error /= ERROR_SUCCESS then return; end if; Error := ERROR_ALREADY_EXISTS; |
Here is the case when the root directory is to be opened or created when does not exist. 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 =>
Error := Check ( Caller, 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 : constant Wide_String := From_UTF8_String (File_Name) & NUL; begin for Index in 1..GetSize (This.Data) loop declare Name : constant 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 (Caller, 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; Create_Security ( Ptr (Item).all, Name, Caller, This.Security, System ); if Get_Tracing (Memory_File_System'Class (System)) then Trace ( Memory_File_System'Class (System), ( "Created file " & Quote (File_Name) & " 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 => Error := ERROR_ALREADY_EXISTS; |
When Disposition is Open_Or_Create the result will be ERROR_ALREADY_EXISTS.
File memory_fs.adb (continuation):
when Dokan.File_System.Open_Existing => Error := ERROR_SUCCESS; |
When Disposition is Open_Existing the result on success will be ERROR_SUCCESS.
File memory_fs.adb (continuation):
when Truncate | Overwrite => Error := ERROR_ALREADY_EXISTS; if not Directory then File_Node'Class (Ptr (Item).all).Length := 0; end if; |
When Disposition is Open_Truncate or Overwrite 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 ( Caller, 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 " & Quote (File_Name) & " (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 " & Quote (File_Name) & " 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; Caller : Dokan.Win32API.HANDLE; Parent : PSECURITY_DESCRIPTOR; System : in out Memory_File_System'Class ) is Lock : File_System_Lock (System'Access); Error : Error_Code; begin if File.Security = null then Create_Default_Security_Descriptor ( Caller, True, Parent, File.Security, Error ); 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", 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", 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 : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class ) is Lock : File_System_Lock (System'Access); Parent : aliased Node_Handles.Handle; begin declare Simple : constant String := Find_Unlocked ( Memory_File_System'Class (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
and then GetTag (This.Data, Offset) = Directory.Reference ) then if Get_Tracing (System) then Trace ( System, ( "Deleting directory " & Quote (Simple) & " at" & Image (Directory.Folder.all) & " in" & Image (This) ), Name ); end if; Delete (This.Data, Offset); This.Accessed := From_Time (Clock); This.Written := This.Accessed; end if; |
When file exists and same it is deleted and the parent directory access times are modified.
File memory_fs.adb (continuation): exception
when Error : Constraint_Error | End_Error | Name_Error => if Get_Tracing (Memory_File_System'Class (System)) then Trace ( System, "Deleting directory", Name, Error ); end if; end Delete; |
Upon error nothing useful can be done.
File memory_fs.adb (continuation): procedure
Delete ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class ) is Lock : File_System_Lock (System'Access); Parent : aliased Node_Handles.Handle; begin declare Simple : constant String := Find_Unlocked ( Memory_File_System'Class (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
and then GetTag (This.Data, Offset) = File.Reference ) then if Get_Tracing (System) then Trace ( System, ( "Deleting file " & Quote (Simple) & " at" & Image (Directory.Folder.all) & " in" & Image (This) ), Name ); end if; Delete (This.Data, Offset); This.Accessed := From_Time (Clock); This.Written := This.Accessed; end if; |
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.
ile memory_fs.adb (continuation): exception
when Error : Constraint_Error | End_Error | Name_Error => if Get_Tracing (Memory_File_System'Class (System)) then Trace ( System, "Deleting file", Name, Error ); end if; end Delete; |
Here we catch exceptions propagating on errors, but cannot do anything beyond tracing.
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 : constant String := Find_Unlocked ( 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), ( "Deleting file" & Quote (Simple) & " at" & Image (File) & " in" & Image (This) ), Name ); end if; Directory.Delete (This.Data, Offset); 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
begin if File.Security /= null then File.Security := LocalFree (File.Security); 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; Unmount (System); 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 ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Fill : FillFileData_Ptr; Info : File_Info_Ptr; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); |
The implementation of function Find locks file system first.
File memory_fs.adb (continuation):
function Add
( Item : Node'Class; Name : Wide_String ) return Boolean is Data : WIN32_FIND_DATAW; Pointer : Natural := 0; Size : constant Byte_Count := Get_Size_Unlocked (Item); Result : DWORD; begin |
The local subprogram Add is called for each found file. It returns true to indicate end of search. Size is set to calculated length of the file.
File memory_fs.adb (continuation):
begin Data.FileAttributes := Item.Attributes; Data.CreationTime := Item.Created; Data.LastAccessTime := Item.Accessed; Data.LastWriteTime := Item.Written; Data.FileSizeHigh := DWORD (Size / 2**32); Data.FileSizeLow := DWORD (Size mod 2**32); Data.Reserved0 := 0; Data.Reserved1 := 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 ); |
Here the file information is stored into the file search data structure.
File memory_fs.adb (continuation):
return 0 /= Fill
(Data, Info); end Add; |
Finally the Fill callback is called to store the file information.
File memory_fs.adb (continuation): Data :
Memory_FS.Directory.Dictionary renames
Directory.Folder.Data; begin 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; |
The implementation of file search walks the directory files and calls Add for each of them until it returns true or there is no more files.
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 and return corresponding error codes.
File memory_fs.adb (continuation): function
Find_Unlocked ( 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; Last : Integer := Path'Last; Current : Node_Handles.Handle renames Parent.all; |
The utility function 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 (System.all) then if Check then Trace ( System.all, " Looking for " & Quote (Path) & " [checked]" ); else Trace ( 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 Last := Last - 1; end if; if 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 <= 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 (System.all) then Trace ( 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 (System.all) then Trace ( 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 (System.all) then Trace ( 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, root directory return ""; |
Empty file name corresponds to the root directory.
File memory_fs.adb (continuation): else return Path (Start..Path'Last); end if; end Find_Unlocked; |
Otherwise, Path (Start..Path'Last) is the file name.
File memory_fs.adb (continuation): procedure
Find_Unlocked ( 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 : constant String Find_Unlocked ( 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_Unlocked; |
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
Flush ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; 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; 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 Lock : File_System_Lock (System'Access); Item : File_Node'Class renames File.File.all; begin Attributes := Item.Attributes; Created := Item.Created; Accessed := Item.Accessed; Written := Item.Written; Size := Get_Size_Unlocked (Item); Links := 0; Index := File_Index (To_Integer (Item'Address)); Error := 0; end Get_Information; |
The implementation of Get_Information 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; 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 Lock : File_System_Lock (System'Access); Item : Folder_Node'Class renames Directory.Folder.all; begin Attributes := Item.Attributes; Created := Item.Created; Accessed := Item.Accessed; Written := Item.Written; Size := Get_Size_Unlocked (Item); Links := 0; Index := File_Index (To_Integer (Item'Address)); Error := 0; end Get_Information; |
The implementation for a directory is similar. The size is zero.
File memory_fs.adb (continuation): procedure
Get_Security
( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : PSECURITY_DESCRIPTOR; Length : in out ULONG; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); begin Get_Security_Unlocked ( System => Memory_File_System (System), Name => Name, File => File.File.all, Security => Security, Descriptor => Descriptor, Length => Length, Error => Error ); end Get_Security; procedure Get_Security ( File : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : PSECURITY_DESCRIPTOR; Length : in out ULONG; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); begin Get_Security_Unlocked ( System => Memory_File_System (System), Name => Name, File => Directory.Folder.all, Security => Security, Descriptor => Descriptor, Length => Length, Error => Error ); end Get_Security; |
The implementations call to the file system implementation that is same for files and directories.
File memory_fs.adb (continuation): procedure
Get_Security_Unlocked
( System : in out Memory_File_System; Name : LPCWSTR; File : in out Node'Class; Security : SECURITY_INFORMATION; Descriptor : PSECURITY_DESCRIPTOR; Length : in out ULONG; Error : out Error_Code ) is Size : aliased DWORD; begin if File.Security then Length := 0; Error := ERROR_INVALID_FUNCTION; |
When the security descriptor is absent, which should not happen the implementation returns ERROR_INVALID_FUNCTION to fall back to Dokan.
File memory_fs.adb (continuation):
elsif 0
= GetPrivateObjectSecurity
( Object => File.Security.all, Information => Security, Descriptor => Descriptor, Length => Length, Return_Length => Size'Unchecked_Access ) then Error := GetLastError; Length := Size; else Length := Size; Error := ERROR_SUCCESS; end if; end Get_Security_Unlocked; |
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_Unlocked (File : File_Node) return Byte_Count
is begin return Byte_Count (File.Length); end Get_Size_Unlocked; |
The implementation of Get_Size of File_Node returns the actual size of the file.
File memory_fs.adb (continuation): function
Get_Size_Unlocked (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_Unlocked; |
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)); |
Initialization of the file system creates the root directory node and then calls to the parent type initialization.
File memory_fs.adb (continuation):
declare
Caller : HANDLE_Holder; begin if 0 /= OpenProcessToken ( GetCurrentProcess, TOKEN_QUERY, Caller.Resource'Unchecked_Access ) then Create_Security ( Ptr (System.Root).all, To_LPCWSTR ("Root"), Caller.Resource, null, System ); end if; end; end Initialize; |
The last step is to create the security descriptor of the root directory. For the access token the one of the calling process is used. The type HANDLE_Holder is declared to keep a handle to the process. Then the process is opened for TOKEN_QUERY access. If this fails we stop. Note that there is no error processing as it is not possible to anything reasonable in Initialize anyway.
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 and then New_Path (Old_Path'Length) = '\' ) then Error := ERROR_IS_SUBST_PATH; return; end if; end if; |
When the old path is contained by the new path (ignoring the case) 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 |
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.
File memory_fs.adb (continuation):
File := GetTag (Old_Folder.Data, Old_Offset); if Get_Tracing (Memory_File_System'Class (System)) then Trace ( Memory_File_System'Class (System), ( "Moving file " & Quote (Old_Simple) & " at" & Image (Ptr (File).all) & " from" & Image (Old_Folder) & " to" & Image (New_Folder) & " renaming to " & Quote (New_Simple) ), Old_Name ); end if; 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 File := GetTag (Old_Folder.Data, Old_Offset); if Get_Tracing (Memory_File_System'Class (System)) then Trace ( Memory_File_System'Class (System), ( "Replacing file " & Quote (Old_Simple) & " at" & Image (Ptr (File).all) & " from" & Image (Old_Folder) & " to" & Image (New_Folder) ), Old_Name ); end if; Directory.Delete (Old_Folder.Data, Old_Offset); Directory.Replace (New_Folder.Data, New_Offset, File); Error := ERROR_SUCCESS; |
When there is a file with the same name and Replace is true, the old file can be replaced. 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_Unlocked (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; Buffer : Storage_Pointers.Pointer; Length : in out Storage_Offset; Offset : Byte_Count; Error : out Error_Code ) is 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_Allocation_Size ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Length : Byte_Count; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Self : File_Node'Class renames File.File.all; begin if Length > Byte_Count (Self.Length) then Put (Self.Data, Storage_Offset (Length), 0); end if; |
The implementation of Set_Allocation_Size checks if the allocated space is less than the requested size. If so expands the files by putting a zero at the index of the requested size. That will cause it to expand the container Self.Data.
File memory_fs.adb (continuation): Self.Length :=
Storage_Count (Length);
Error := ERROR_SUCCESS; exception when others => Error := ERROR_FILE_TOO_LARGE; end Set_Allocation_Size; |
The new size is set unless a memory allocation occur. In which case the result is ERROR_FILE_TOO_LARGE.
File memory_fs.adb (continuation): procedure Set_Attributes ( File : in out Memory_File; Name : LPCWSTR; Attributes : DWORD; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Self : File_Node'Class renames File.File.all; begin if Attributes = FILE_ATTRIBUTE_NORMAL then Self.Attributes := 0; Error := ERROR_SUCCESS; |
The implementation of Set_Attributes for a file when Attributes is FILE_ATTRIBUTE_NORMAL sets attributes to zero.
File memory_fs.adb (continuation):
elsif 0 = (Attributes and not
Supported_Attributes) then
if 0 = (Attributes and FILE_ATTRIBUTE_DIRECTORY) then Self.Attributes := Attributes and not FILE_ATTRIBUTE_NORMAL; Error := ERROR_SUCCESS; |
Otherwise if no unsupported attributs are used, the attributes are set. Note that FILE_ATTRIBUTE_NORMAL is cleaned because it is not a proper attribute.
File memory_fs.adb (continuation):
else Error := ERROR_INVALID_DATA; end if; end Set_Attributes; |
When unsupported attributes are used, the implementation fails with ERROR_INVALID_DATA.
File memory_fs.adb (continuation): procedure Set_Attributes ( Directory : in out Memory_Folder; Name : LPCWSTR; Attributes : DWORD; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Self : Folder_Node'Class renames File.File.all; begin if Attributes = FILE_ATTRIBUTE_NORMAL then Error := ERROR_INVALID_DATA; |
The implementation of Set_Attributes for a directory when Attributes is FILE_ATTRIBUTE_NORMAL fails with ERROR_INVALID_DATA.
File memory_fs.adb (continuation):
elsif 0 = (Attributes and not
Supported_Attributes) then
if 0 = (Attributes and FILE_ATTRIBUTE_DIRECTORY) then Error := ERROR_INVALID_DATA; else Self.Attributes := Attributes and not FILE_ATTRIBUTE_NORMAL; Error := ERROR_SUCCESS; end if; |
When no unsupported attributes are used the result is ERROR_SUCCESS unless FILE_ATTRIBUTE_DIRECTORY is cleared.
File memory_fs.adb (continuation):
else Error := ERROR_INVALID_DATA; 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; Length : Byte_Count; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Self : File_Node'Class renames File.File.all; begin |
The implementation of Set_End_Of accesses the file node.
File memory_fs.adb (continuation):
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 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 ( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Created : FILETIME; Accessed : FILETIME; Written : FILETIME; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Self : File_Node'Class renames File.File.all; begin |
The implementation of Set_File_Time for a file first locks the file system.
File memory_fs.adb (continuation):
if Error = ERROR_SUCCESS then
if Created /= (0, 0) then Self.Created := Created; end if; if Accessed /= (0, 0) then Self.Accessed := Accessed; end if; if Written /= (0, 0) then Self.Written := Written; end if; 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_File_Time ( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Created : FILETIME; Accessed : FILETIME; Written : FILETIME; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); Self : Folder_Node'Class renames Directory.Folder.all; begin |
The implementation of Set_File_Time for a directory is similar to the implementation for a file.
File memory_fs.adb (continuation):
if Error = ERROR_SUCCESS then
if Created /= (0, 0) then Self.Created := Created; end if; if Accessed /= (0, 0) then Self.Accessed := Accessed; end if; if Written /= (0, 0) then Self.Written := Written; end if; 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
( File : in out Memory_File; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : SECURITY_DESCRIPTOR; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); begin Set_Security_Unlocked ( System => Memory_File_System (System), Name => Name, File => File.File.all, Security => Security, Descriptor => Descriptor, Error => Error ); end Set_Security; |
The implementation Set_Security calls to the generic implementation of the memory file system.
File memory_fs.adb (continuation): procedure
Set_Security
( Directory : in out Memory_Folder; Name : LPCWSTR; System : in out Abstract_File_System'Class; Security : SECURITY_INFORMATION; Descriptor : SECURITY_DESCRIPTOR; Error : out Error_Code ) is Lock : File_System_Lock (System'Access); begin Set_Security_Unlocked ( System => Memory_File_System (System), Name => Name, File => Directory.Folder.all, Security => Security, Descriptor => Descriptor, Error => Error ); end Set_Security; |
The implementation Set_Security calls to the generic implementation of the memory file system.
File memory_fs.adb (continuation): procedure
Set_Security_Unlocked
( System : in out Memory_File_System; Name : LPCWSTR; File : in out Node'Class; Security : SECURITY_INFORMATION; Descriptor : SECURITY_DESCRIPTOR; Error : out Error_Code ) is begin if 0 = SetPrivateObjectSecurity ( Information => Security, Modification => Descriptor, Object => File.Security'Unchecked_Access, Mapping => File_Access_Mapping ) then Error := GetLastError; else if Get_Tracing (System) then Trace ( System, "New security " & Image (File.Security.all), Name ); end if; Error := ERROR_SUCCESS; end if; end Set_Security_Unlocked; |
The implementation Set_Security simply call windows SetPrivateObjectSecurity which modifies the descriptor and reallocates it if neceassry.
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); end if; System.Trace_Mode := Output; 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
Set_Trace_Error (System : in out Memory_File_System)
is use Ada.Text_IO; begin if System.Into_File then Close (System.Trace_File); end if; System.Trace_Mode := Error; 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; use Ada.Task_Identification; function Context return String is begin return " @" & Image (Current_Task); end Context; begin case System.Trace_Mode is when File => Put_Line (System.Trace_File, Text & Context); Flush (System.Trace_File); when Output => Put_Line (Text & Context); Flush; when Error => Put_Line (Standard_Error, Text & Context); Flush (Standard_Error); end case; end Trace; |
The implementation of Trace writes into the trace file or else to the standard or error output.
File memory_fs.adb (continuation): procedure
Trace ( System : in out Memory_File_System; Text : String; Name : LPCWSTR ) is begin Trace (System, Text & " " & Quote (To_UTF8_String (Name))); 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 & " fault: " & Exception_Information (Error)); 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 & " " & Quote (To_UTF8_String (Name)) & " fault: " & Exception_Information (Error) ) ); 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; Buffer : Storage_Array; Error : out Error_Code ) is 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; Buffer : Storage_Array; Offset : Byte_Count; Error : out Error_Code ) is 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.
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.
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 |
The subdirectory example/memory_fs contains a sample of virtual memory-resident file system. The subdirectory example/memory_fs/test has the test for the memory-resident system. It mounts the system on the letter R.
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.exe in the subdirectory dokany-2.0.0.2000.
The following versions were tested with the compilers:
Changes (16 January 2022) to the version 1.5.0:
Changes (20 November 2021) to the version 1.1:
Changes (5 Aug 2018) to the version 1.0:
The following versions were tested with the compilers:
First version released.
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