blob: fc978a47dcf8fcab938ae49e441a4134a752abbd [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME COMPONENTS --
-- --
-- S Y S T E M . M M A P --
-- --
-- B o d y --
-- --
-- Copyright (C) 2007-2021, 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;