1 ------------------------------------------------------------------------------
3 -- GNAT RUN-TIME COMPONENTS --
5 -- S Y S T E M . M M A P --
9 -- Copyright (C) 2007-2018, AdaCore --
11 -- This library is free software; you can redistribute it and/or modify it --
12 -- under terms of the GNU General Public License as published by the Free --
13 -- Software Foundation; either version 3, or (at your option) any later --
14 -- version. This library is distributed in the hope that it will be useful, --
15 -- but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- --
16 -- TABILITY or FITNESS FOR A PARTICULAR PURPOSE. --
18 -- As a special exception under Section 7 of GPL version 3, you are granted --
19 -- additional permissions described in the GCC Runtime Library Exception, --
20 -- version 3.1, as published by the Free Software Foundation. --
22 -- You should have received a copy of the GNU General Public License and --
23 -- a copy of the GCC Runtime Library Exception along with this program; --
24 -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
25 -- <http://www.gnu.org/licenses/>. --
27 -- GNAT was originally developed by the GNAT team at New York University. --
28 -- Extensive contributions were provided by Ada Core Technologies Inc. --
30 ------------------------------------------------------------------------------
32 with Ada
.IO_Exceptions
;
33 with Ada
.Unchecked_Conversion
;
34 with Ada
.Unchecked_Deallocation
;
36 with System
.Strings
; use System
.Strings
;
38 with System
.Mmap
.OS_Interface
; use System
.Mmap
.OS_Interface
;
40 package body System
.Mmap
is
42 type Mapped_File_Record
is record
43 Current_Region
: Mapped_Region
;
44 -- The legacy API enables only one region to be mapped, directly
45 -- associated with the mapped file. This references this region.
48 -- Underlying OS-level file
51 type Mapped_Region_Record
is record
53 -- The file this region comes from. Be careful: for reading file, it is
54 -- valid to have it closed before one of its regions is free'd.
57 -- Whether the file this region comes from is open for writing.
60 -- Unbounded access to the mapped content.
62 System_Offset
: File_Size
;
63 -- Position in the file of the first byte actually mapped in memory
65 User_Offset
: File_Size
;
66 -- Position in the file of the first byte requested by the user
68 System_Size
: File_Size
;
69 -- Size of the region actually mapped in memory
71 User_Size
: File_Size
;
72 -- Size of the region requested by the user
75 -- Whether this region is actually memory mapped
78 -- If the file is opened for reading, wheter this region is writable
80 Buffer
: System
.Strings
.String_Access
;
81 -- When this region is not actually memory mapped, contains the
84 Mapping
: System_Mapping
;
85 -- Underlying OS-level data for the mapping, if any
88 Invalid_Mapped_Region_Record
: constant Mapped_Region_Record
:=
89 (null, False, null, 0, 0, 0, 0, False, False, null,
90 Invalid_System_Mapping
);
91 Invalid_Mapped_File_Record
: constant Mapped_File_Record
:=
92 (Invalid_Mapped_Region
, Invalid_System_File
);
94 Empty_String
: constant String := "";
95 -- Used to provide a valid empty Data for empty files, for instanc.
97 procedure Dispose
is new Ada
.Unchecked_Deallocation
98 (Mapped_File_Record
, Mapped_File
);
99 procedure Dispose
is new Ada
.Unchecked_Deallocation
100 (Mapped_Region_Record
, Mapped_Region
);
102 function Convert
is new Ada
.Unchecked_Conversion
103 (Standard
.System
.Address
, Str_Access
);
105 procedure Compute_Data
(Region
: Mapped_Region
);
106 -- Fill the Data field according to system and user offsets. The region
107 -- must actually be mapped or bufferized.
109 procedure From_Disk
(Region
: Mapped_Region
);
110 -- Read a region of some file from the disk
112 procedure To_Disk
(Region
: Mapped_Region
);
113 -- Write the region of the file back to disk if necessary, and free memory
115 ----------------------------
116 -- Open_Read_No_Exception --
117 ----------------------------
119 function Open_Read_No_Exception
121 Use_Mmap_If_Available
: Boolean := True) return Mapped_File
123 File
: constant System_File
:=
124 Open_Read
(Filename
, Use_Mmap_If_Available
);
126 if File
= Invalid_System_File
then
127 return Invalid_Mapped_File
;
130 return new Mapped_File_Record
'
131 (Current_Region => Invalid_Mapped_Region,
133 end Open_Read_No_Exception;
141 Use_Mmap_If_Available : Boolean := True) return Mapped_File
143 Res : constant Mapped_File :=
144 Open_Read_No_Exception (Filename, Use_Mmap_If_Available);
146 if Res = Invalid_Mapped_File then
147 raise Ada.IO_Exceptions.Name_Error
148 with "Cannot open " & Filename;
160 Use_Mmap_If_Available : Boolean := True) return Mapped_File
162 File : constant System_File :=
163 Open_Write (Filename, Use_Mmap_If_Available);
165 if File = Invalid_System_File then
166 raise Ada.IO_Exceptions.Name_Error
167 with "Cannot open " & Filename;
169 return new Mapped_File_Record'
170 (Current_Region
=> Invalid_Mapped_Region
,
179 procedure Close
(File
: in out Mapped_File
) is
181 -- Closing a closed file is allowed and should do nothing
183 if File
= Invalid_Mapped_File
then
187 if File
.Current_Region
/= null then
188 Free
(File
.Current_Region
);
191 if File
.File
/= Invalid_System_File
then
202 procedure Free
(Region
: in out Mapped_Region
) is
204 pragma Unreferenced
(Ignored
);
206 -- Freeing an already free'd file is allowed and should do nothing
208 if Region
= Invalid_Mapped_Region
then
212 if Region
.Mapping
/= Invalid_System_Mapping
then
213 Dispose_Mapping
(Region
.Mapping
);
225 Region
: in out Mapped_Region
;
226 Offset
: File_Size
:= 0;
227 Length
: File_Size
:= 0;
228 Mutable
: Boolean := False)
230 File_Length
: constant File_Size
:= Mmap
.Length
(File
);
232 Req_Offset
: constant File_Size
:= Offset
;
233 Req_Length
: File_Size
:= Length
;
234 -- Offset and Length of the region to map, used to adjust mapping
235 -- bounds, reflecting what the user will see.
237 Region_Allocated
: Boolean := False;
239 -- If this region comes from another file, or simply if the file is
240 -- writeable, we cannot re-use this mapping: free it first.
242 if Region
/= Invalid_Mapped_Region
244 (Region
.File
/= File
or else File
.File
.Write
)
249 if Region
= Invalid_Mapped_Region
then
250 Region
:= new Mapped_Region_Record
'(Invalid_Mapped_Region_Record);
251 Region_Allocated := True;
256 if Req_Offset >= File_Length then
257 -- If the requested offset goes beyond file size, map nothing
263 Length > File_Length - Req_Offset
265 -- If Length is 0 or goes beyond file size, map till end of file
267 Req_Length := File_Length - Req_Offset;
270 Req_Length := Length;
273 -- Past this point, the offset/length the user will see is fixed. On the
274 -- other hand, the system offset/length is either already defined, from
275 -- a previous mapping, or it is set to 0. In the latter case, the next
276 -- step will set them according to the mapping.
278 Region.User_Offset := Req_Offset;
279 Region.User_Size := Req_Length;
281 -- If the requested region is inside an already mapped region, adjust
282 -- user-requested data and do nothing else.
284 if (File.File.Write or else Region.Mutable = Mutable)
286 Req_Offset >= Region.System_Offset
288 (Req_Offset + Req_Length
289 <= Region.System_Offset + Region.System_Size)
291 Region.User_Offset := Req_Offset;
292 Compute_Data (Region);
295 elsif Region.Buffer /= null then
296 -- Otherwise, as we are not going to re-use the buffer, free it
298 System.Strings.Free (Region.Buffer);
299 Region.Buffer := null;
301 elsif Region.Mapping /= Invalid_System_Mapping then
302 -- Otherwise, there is a memory mapping that we need to unmap.
303 Dispose_Mapping (Region.Mapping);
306 -- mmap() will sometimes return NULL when the file exists but is empty,
307 -- which is not what we want, so in the case of a zero length file we
308 -- fall back to read(2)/write(2)-based mode.
310 if File_Length > 0 and then File.File.Mapped then
312 Region.System_Offset := Req_Offset;
313 Region.System_Size := Req_Length;
316 Region.System_Offset, Region.System_Size,
319 Region.Mapped := True;
320 Region.Mutable := Mutable;
323 -- There is no alignment requirement when manually reading the file.
325 Region.System_Offset := Req_Offset;
326 Region.System_Size := Req_Length;
327 Region.Mapped := False;
328 Region.Mutable := True;
332 Region.Write := File.File.Write;
333 Compute_Data (Region);
337 -- Before propagating any exception, free any region we allocated
340 if Region_Allocated then
352 Offset : File_Size := 0;
353 Length : File_Size := 0;
354 Mutable : Boolean := False)
357 Read (File, File.Current_Region, Offset, Length, Mutable);
366 Offset : File_Size := 0;
367 Length : File_Size := 0;
368 Mutable : Boolean := False) return Mapped_Region
370 Region : Mapped_Region := Invalid_Mapped_Region;
372 Read (File, Region, Offset, Length, Mutable);
380 function Length (File : Mapped_File) return File_Size is
382 return File.File.Length;
389 function Offset (Region : Mapped_Region) return File_Size is
391 return Region.User_Offset;
398 function Offset (File : Mapped_File) return File_Size is
400 return Offset (File.Current_Region);
407 function Last (Region : Mapped_Region) return Integer is
409 return Integer (Region.User_Size);
416 function Last (File : Mapped_File) return Integer is
418 return Last (File.Current_Region);
425 function To_Str_Access
426 (Str : System.Strings.String_Access) return Str_Access is
431 return Convert (Str.all'Address);
439 function Data (Region : Mapped_Region) return Str_Access is
448 function Data (File : Mapped_File) return Str_Access is
450 return Data (File.Current_Region);
457 function Is_Mutable (Region : Mapped_Region) return Boolean is
459 return Region.Mutable or Region.Write;
466 function Is_Mmapped (File : Mapped_File) return Boolean is
468 return File.File.Mapped;
475 function Get_Page_Size return Integer is
476 Result : constant File_Size := Get_Page_Size;
478 return Integer (Result);
481 ---------------------
482 -- Read_Whole_File --
483 ---------------------
485 function Read_Whole_File
487 Empty_If_Not_Found : Boolean := False)
488 return System.Strings.String_Access
490 File : Mapped_File := Open_Read (Filename);
491 Region : Mapped_Region renames File.Current_Region;
492 Result : String_Access;
496 if Region.Data /= null then
497 Result := new String'(String
498 (Region
.Data
(1 .. Last
(Region
))));
500 elsif Region
.Buffer
/= null then
501 Result
:= Region
.Buffer
;
502 Region
.Buffer
:= null; -- So that it is not deallocated
510 when Ada
.IO_Exceptions
.Name_Error
=>
511 if Empty_If_Not_Found
then
512 return new String'("");
526 procedure From_Disk (Region : Mapped_Region) is
528 pragma Assert (Region.File.all /= Invalid_Mapped_File_Record);
529 pragma Assert (Region.Buffer = null);
531 Region.Buffer := Read_From_Disk
532 (Region.File.File, Region.User_Offset, Region.User_Size);
533 Region.Mapped := False;
540 procedure To_Disk (Region : Mapped_Region) is
542 if Region.Write and then Region.Buffer /= null then
543 pragma Assert (Region.File.all /= Invalid_Mapped_File_Record);
546 Region.User_Offset, Region.User_Size,
550 System.Strings.Free (Region.Buffer);
551 Region.Buffer := null;
558 procedure Compute_Data (Region : Mapped_Region) is
559 Base_Data : Str_Access;
560 -- Address of the first byte actually mapped in memory
562 Data_Shift : constant Integer :=
563 Integer (Region.User_Offset - Region.System_Offset);
565 if Region.User_Size = 0 then
566 Region.Data := Convert (Empty_String'Address);
568 elsif Region.Mapped then
569 Base_Data := Convert (Region.Mapping.Address);
571 Base_Data := Convert (Region.Buffer.all'Address);
573 Region.Data := Convert (Base_Data (Data_Shift + 1)'Address);