| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT RUN-TIME COMPONENTS -- |
| -- -- |
| -- S Y S T E M . M M A P -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 2007-2022, AdaCore -- |
| -- -- |
| -- This library is free software; you can redistribute it and/or modify it -- |
| -- under terms of the GNU General Public License as published by the Free -- |
| -- Software Foundation; either version 3, 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 MERCHAN- -- |
| -- TABILITY or FITNESS FOR A PARTICULAR PURPOSE. -- |
| -- -- |
| -- As a special exception under Section 7 of GPL version 3, you are granted -- |
| -- additional permissions described in the GCC Runtime Library Exception, -- |
| -- version 3.1, as published by the Free Software Foundation. -- |
| -- -- |
| -- You should have received a copy of the GNU General Public License and -- |
| -- a copy of the GCC Runtime Library Exception along with this program; -- |
| -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -- |
| -- <http://www.gnu.org/licenses/>. -- |
| -- -- |
| -- GNAT was originally developed by the GNAT team at New York University. -- |
| -- Extensive contributions were provided by Ada Core Technologies Inc. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| with Ada.IO_Exceptions; |
| with Ada.Unchecked_Conversion; |
| with Ada.Unchecked_Deallocation; |
| |
| with System.Strings; use System.Strings; |
| |
| with System.Mmap.OS_Interface; use System.Mmap.OS_Interface; |
| |
| package body System.Mmap is |
| |
| type Mapped_File_Record is record |
| Current_Region : Mapped_Region; |
| -- The legacy API enables only one region to be mapped, directly |
| -- associated with the mapped file. This references this region. |
| |
| File : System_File; |
| -- Underlying OS-level file |
| end record; |
| |
| type Mapped_Region_Record is record |
| File : Mapped_File; |
| -- The file this region comes from. Be careful: for reading file, it is |
| -- valid to have it closed before one of its regions is free'd. |
| |
| Write : Boolean; |
| -- Whether the file this region comes from is open for writing. |
| |
| Data : Str_Access; |
| -- Unbounded access to the mapped content. |
| |
| System_Offset : File_Size; |
| -- Position in the file of the first byte actually mapped in memory |
| |
| User_Offset : File_Size; |
| -- Position in the file of the first byte requested by the user |
| |
| System_Size : File_Size; |
| -- Size of the region actually mapped in memory |
| |
| User_Size : File_Size; |
| -- Size of the region requested by the user |
| |
| Mapped : Boolean; |
| -- Whether this region is actually memory mapped |
| |
| Mutable : Boolean; |
| -- If the file is opened for reading, wheter this region is writable |
| |
| Buffer : System.Strings.String_Access; |
| -- When this region is not actually memory mapped, contains the |
| -- requested bytes. |
| |
| Mapping : System_Mapping; |
| -- Underlying OS-level data for the mapping, if any |
| end record; |
| |
| Invalid_Mapped_Region_Record : constant Mapped_Region_Record := |
| (null, False, null, 0, 0, 0, 0, False, False, null, |
| Invalid_System_Mapping); |
| Invalid_Mapped_File_Record : constant Mapped_File_Record := |
| (Invalid_Mapped_Region, Invalid_System_File); |
| |
| Empty_String : constant String := ""; |
| -- Used to provide a valid empty Data for empty files, for instanc. |
| |
| procedure Dispose is new Ada.Unchecked_Deallocation |
| (Mapped_File_Record, Mapped_File); |
| procedure Dispose is new Ada.Unchecked_Deallocation |
| (Mapped_Region_Record, Mapped_Region); |
| |
| function Convert is new Ada.Unchecked_Conversion |
| (Standard.System.Address, Str_Access); |
| |
| procedure Compute_Data (Region : Mapped_Region); |
| -- Fill the Data field according to system and user offsets. The region |
| -- must actually be mapped or bufferized. |
| |
| procedure From_Disk (Region : Mapped_Region); |
| -- Read a region of some file from the disk |
| |
| procedure To_Disk (Region : Mapped_Region); |
| -- Write the region of the file back to disk if necessary, and free memory |
| |
| ---------------------------- |
| -- Open_Read_No_Exception -- |
| ---------------------------- |
| |
| function Open_Read_No_Exception |
| (Filename : String; |
| Use_Mmap_If_Available : Boolean := True) return Mapped_File |
| is |
| File : constant System_File := |
| Open_Read (Filename, Use_Mmap_If_Available); |
| begin |
| if File = Invalid_System_File then |
| return Invalid_Mapped_File; |
| end if; |
| |
| return new Mapped_File_Record' |
| (Current_Region => Invalid_Mapped_Region, |
| File => File); |
| end Open_Read_No_Exception; |
| |
| --------------- |
| -- Open_Read -- |
| --------------- |
| |
| function Open_Read |
| (Filename : String; |
| Use_Mmap_If_Available : Boolean := True) return Mapped_File |
| is |
| Res : constant Mapped_File := |
| Open_Read_No_Exception (Filename, Use_Mmap_If_Available); |
| begin |
| if Res = Invalid_Mapped_File then |
| raise Ada.IO_Exceptions.Name_Error |
| with "Cannot open " & Filename; |
| else |
| return Res; |
| end if; |
| end Open_Read; |
| |
| ---------------- |
| -- Open_Write -- |
| ---------------- |
| |
| function Open_Write |
| (Filename : String; |
| Use_Mmap_If_Available : Boolean := True) return Mapped_File |
| is |
| File : constant System_File := |
| Open_Write (Filename, Use_Mmap_If_Available); |
| begin |
| if File = Invalid_System_File then |
| raise Ada.IO_Exceptions.Name_Error |
| with "Cannot open " & Filename; |
| else |
| return new Mapped_File_Record' |
| (Current_Region => Invalid_Mapped_Region, |
| File => File); |
| end if; |
| end Open_Write; |
| |
| ----------- |
| -- Close -- |
| ----------- |
| |
| procedure Close (File : in out Mapped_File) is |
| begin |
| -- Closing a closed file is allowed and should do nothing |
| |
| if File = Invalid_Mapped_File then |
| return; |
| end if; |
| |
| if File.Current_Region /= null then |
| Free (File.Current_Region); |
| end if; |
| |
| if File.File /= Invalid_System_File then |
| Close (File.File); |
| end if; |
| |
| Dispose (File); |
| end Close; |
| |
| ---------- |
| -- Free -- |
| ---------- |
| |
| procedure Free (Region : in out Mapped_Region) is |
| Ignored : Integer; |
| pragma Unreferenced (Ignored); |
| begin |
| -- Freeing an already free'd file is allowed and should do nothing |
| |
| if Region = Invalid_Mapped_Region then |
| return; |
| end if; |
| |
| if Region.Mapping /= Invalid_System_Mapping then |
| Dispose_Mapping (Region.Mapping); |
| end if; |
| To_Disk (Region); |
| Dispose (Region); |
| end Free; |
| |
| ---------- |
| -- Read -- |
| ---------- |
| |
| procedure Read |
| (File : Mapped_File; |
| Region : in out Mapped_Region; |
| Offset : File_Size := 0; |
| Length : File_Size := 0; |
| Mutable : Boolean := False) |
| is |
| File_Length : constant File_Size := Mmap.Length (File); |
| |
| Req_Offset : constant File_Size := Offset; |
| Req_Length : File_Size := Length; |
| -- Offset and Length of the region to map, used to adjust mapping |
| -- bounds, reflecting what the user will see. |
| |
| Region_Allocated : Boolean := False; |
| begin |
| -- If this region comes from another file, or simply if the file is |
| -- writeable, we cannot re-use this mapping: free it first. |
| |
| if Region /= Invalid_Mapped_Region |
| and then |
| (Region.File /= File or else File.File.Write) |
| then |
| Free (Region); |
| end if; |
| |
| if Region = Invalid_Mapped_Region then |
| Region := new Mapped_Region_Record'(Invalid_Mapped_Region_Record); |
| Region_Allocated := True; |
| end if; |
| |
| Region.File := File; |
| |
| if Req_Offset >= File_Length then |
| -- If the requested offset goes beyond file size, map nothing |
| |
| Req_Length := 0; |
| |
| elsif Length = 0 |
| or else |
| Length > File_Length - Req_Offset |
| then |
| -- If Length is 0 or goes beyond file size, map till end of file |
| |
| Req_Length := File_Length - Req_Offset; |
| |
| else |
| Req_Length := Length; |
| end if; |
| |
| -- Past this point, the offset/length the user will see is fixed. On the |
| -- other hand, the system offset/length is either already defined, from |
| -- a previous mapping, or it is set to 0. In the latter case, the next |
| -- step will set them according to the mapping. |
| |
| Region.User_Offset := Req_Offset; |
| Region.User_Size := Req_Length; |
| |
| -- If the requested region is inside an already mapped region, adjust |
| -- user-requested data and do nothing else. |
| |
| if (File.File.Write or else Region.Mutable = Mutable) |
| and then |
| Req_Offset >= Region.System_Offset |
| and then |
| (Req_Offset + Req_Length |
| <= Region.System_Offset + Region.System_Size) |
| then |
| Region.User_Offset := Req_Offset; |
| Compute_Data (Region); |
| return; |
| |
| elsif Region.Buffer /= null then |
| -- Otherwise, as we are not going to re-use the buffer, free it |
| |
| System.Strings.Free (Region.Buffer); |
| Region.Buffer := null; |
| |
| elsif Region.Mapping /= Invalid_System_Mapping then |
| -- Otherwise, there is a memory mapping that we need to unmap. |
| Dispose_Mapping (Region.Mapping); |
| end if; |
| |
| -- mmap() will sometimes return NULL when the file exists but is empty, |
| -- which is not what we want, so in the case of a zero length file we |
| -- fall back to read(2)/write(2)-based mode. |
| |
| if File_Length > 0 and then File.File.Mapped then |
| |
| Region.System_Offset := Req_Offset; |
| Region.System_Size := Req_Length; |
| Create_Mapping |
| (File.File, |
| Region.System_Offset, Region.System_Size, |
| Mutable, |
| Region.Mapping); |
| Region.Mapped := True; |
| Region.Mutable := Mutable; |
| |
| else |
| -- There is no alignment requirement when manually reading the file. |
| |
| Region.System_Offset := Req_Offset; |
| Region.System_Size := Req_Length; |
| Region.Mapped := False; |
| Region.Mutable := True; |
| From_Disk (Region); |
| end if; |
| |
| Region.Write := File.File.Write; |
| Compute_Data (Region); |
| |
| exception |
| when others => |
| -- Before propagating any exception, free any region we allocated |
| -- here. |
| |
| if Region_Allocated then |
| Dispose (Region); |
| end if; |
| raise; |
| end Read; |
| |
| ---------- |
| -- Read -- |
| ---------- |
| |
| procedure Read |
| (File : Mapped_File; |
| Offset : File_Size := 0; |
| Length : File_Size := 0; |
| Mutable : Boolean := False) |
| is |
| begin |
| Read (File, File.Current_Region, Offset, Length, Mutable); |
| end Read; |
| |
| ---------- |
| -- Read -- |
| ---------- |
| |
| function Read |
| (File : Mapped_File; |
| Offset : File_Size := 0; |
| Length : File_Size := 0; |
| Mutable : Boolean := False) return Mapped_Region |
| is |
| Region : Mapped_Region := Invalid_Mapped_Region; |
| begin |
| Read (File, Region, Offset, Length, Mutable); |
| return Region; |
| end Read; |
| |
| ------------ |
| -- Length -- |
| ------------ |
| |
| function Length (File : Mapped_File) return File_Size is |
| begin |
| return File.File.Length; |
| end Length; |
| |
| ------------ |
| -- Offset -- |
| ------------ |
| |
| function Offset (Region : Mapped_Region) return File_Size is |
| begin |
| return Region.User_Offset; |
| end Offset; |
| |
| ------------ |
| -- Offset -- |
| ------------ |
| |
| function Offset (File : Mapped_File) return File_Size is |
| begin |
| return Offset (File.Current_Region); |
| end Offset; |
| |
| ---------- |
| -- Last -- |
| ---------- |
| |
| function Last (Region : Mapped_Region) return Integer is |
| begin |
| return Integer (Region.User_Size); |
| end Last; |
| |
| ---------- |
| -- Last -- |
| ---------- |
| |
| function Last (File : Mapped_File) return Integer is |
| begin |
| return Last (File.Current_Region); |
| end Last; |
| |
| ------------------- |
| -- To_Str_Access -- |
| ------------------- |
| |
| function To_Str_Access |
| (Str : System.Strings.String_Access) return Str_Access is |
| begin |
| if Str = null then |
| return null; |
| else |
| return Convert (Str.all'Address); |
| end if; |
| end To_Str_Access; |
| |
| ---------- |
| -- Data -- |
| ---------- |
| |
| function Data (Region : Mapped_Region) return Str_Access is |
| begin |
| return Region.Data; |
| end Data; |
| |
| ---------- |
| -- Data -- |
| ---------- |
| |
| function Data (File : Mapped_File) return Str_Access is |
| begin |
| return Data (File.Current_Region); |
| end Data; |
| |
| ---------------- |
| -- Is_Mutable -- |
| ---------------- |
| |
| function Is_Mutable (Region : Mapped_Region) return Boolean is |
| begin |
| return Region.Mutable or Region.Write; |
| end Is_Mutable; |
| |
| ---------------- |
| -- Is_Mmapped -- |
| ---------------- |
| |
| function Is_Mmapped (File : Mapped_File) return Boolean is |
| begin |
| return File.File.Mapped; |
| end Is_Mmapped; |
| |
| ------------------- |
| -- Get_Page_Size -- |
| ------------------- |
| |
| function Get_Page_Size return Integer is |
| Result : constant File_Size := Get_Page_Size; |
| begin |
| return Integer (Result); |
| end Get_Page_Size; |
| |
| --------------------- |
| -- Read_Whole_File -- |
| --------------------- |
| |
| function Read_Whole_File |
| (Filename : String; |
| Empty_If_Not_Found : Boolean := False) |
| return System.Strings.String_Access |
| is |
| File : Mapped_File := Open_Read (Filename); |
| Region : Mapped_Region renames File.Current_Region; |
| Result : String_Access; |
| begin |
| Read (File); |
| |
| if Region.Data /= null then |
| Result := new String'(String |
| (Region.Data (1 .. Last (Region)))); |
| |
| elsif Region.Buffer /= null then |
| Result := Region.Buffer; |
| Region.Buffer := null; -- So that it is not deallocated |
| end if; |
| |
| Close (File); |
| |
| return Result; |
| |
| exception |
| when Ada.IO_Exceptions.Name_Error => |
| if Empty_If_Not_Found then |
| return new String'(""); |
| else |
| return null; |
| end if; |
| |
| when others => |
| Close (File); |
| return null; |
| end Read_Whole_File; |
| |
| --------------- |
| -- From_Disk -- |
| --------------- |
| |
| procedure From_Disk (Region : Mapped_Region) is |
| begin |
| pragma Assert (Region.File.all /= Invalid_Mapped_File_Record); |
| pragma Assert (Region.Buffer = null); |
| |
| Region.Buffer := Read_From_Disk |
| (Region.File.File, Region.User_Offset, Region.User_Size); |
| Region.Mapped := False; |
| end From_Disk; |
| |
| ------------- |
| -- To_Disk -- |
| ------------- |
| |
| procedure To_Disk (Region : Mapped_Region) is |
| begin |
| if Region.Write and then Region.Buffer /= null then |
| pragma Assert (Region.File.all /= Invalid_Mapped_File_Record); |
| Write_To_Disk |
| (Region.File.File, |
| Region.User_Offset, Region.User_Size, |
| Region.Buffer); |
| end if; |
| |
| System.Strings.Free (Region.Buffer); |
| Region.Buffer := null; |
| end To_Disk; |
| |
| ------------------ |
| -- Compute_Data -- |
| ------------------ |
| |
| procedure Compute_Data (Region : Mapped_Region) is |
| Base_Data : Str_Access; |
| -- Address of the first byte actually mapped in memory |
| |
| Data_Shift : constant Integer := |
| Integer (Region.User_Offset - Region.System_Offset); |
| begin |
| if Region.User_Size = 0 then |
| Region.Data := Convert (Empty_String'Address); |
| return; |
| elsif Region.Mapped then |
| Base_Data := Convert (Region.Mapping.Address); |
| else |
| Base_Data := Convert (Region.Buffer.all'Address); |
| end if; |
| Region.Data := Convert (Base_Data (Data_Shift + 1)'Address); |
| end Compute_Data; |
| |
| end System.Mmap; |