1--  GHDL Run Time (GRT) -  VHDL files subprograms.
2--  Copyright (C) 2002 - 2014 Tristan Gingold
3--
4--  This program is free software: you can redistribute it and/or modify
5--  it under the terms of the GNU General Public License as published by
6--  the Free Software Foundation, either version 2 of the License, or
7--  (at your option) any later version.
8--
9--  This program is distributed in the hope that it will be useful,
10--  but WITHOUT ANY WARRANTY; without even the implied warranty of
11--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12--  GNU General Public License for more details.
13--
14--  You should have received a copy of the GNU General Public License
15--  along with this program.  If not, see <gnu.org/licenses>.
16--
17--  As a special exception, if other files instantiate generics from this
18--  unit, or you link this unit with other files to produce an executable,
19--  this unit does not by itself cause the resulting executable to be
20--  covered by the GNU General Public License. This exception does not
21--  however invalidate any other reasons why the executable file might be
22--  covered by the GNU Public License.
23with Grt.Stdio; use Grt.Stdio;
24with Grt.C; use Grt.C;
25with Grt.Table;
26with System; use System;
27pragma Elaborate_All (Grt.Table);
28
29package body Grt.Files_Operations is
30   subtype C_Files is Grt.Stdio.FILEs;
31
32   --  The end of lines
33   C_LF : constant int := 10;   --  \n
34   C_CR : constant int := 13;   --  \r
35
36   Auto_Flush : constant Boolean := False;
37
38   type File_Entry_Type is record
39      --  The corresponding C stream.
40      Stream : C_Files;
41
42      Signature : Ghdl_C_String;
43
44      --  Open kind: r, a or w.
45      Kind : Character;
46
47      Is_Text : Boolean;
48
49      --  True if the file entry is used.
50      Is_Alive : Boolean;
51   end record;
52
53   package Files_Table is new Grt.Table
54     (Table_Component_Type => File_Entry_Type,
55      Table_Index_Type => Ghdl_File_Index,
56      Table_Low_Bound => 1,
57      Table_Initial => 2);
58
59   --  Get the C stream for INDEX.
60   procedure Get_File
61     (Index : Ghdl_File_Index; Res : out C_Files; Status : out Op_Status) is
62   begin
63      if Index not in Files_Table.First .. Files_Table.Last then
64         Status := Op_Bad_Index;
65      else
66         Status := Op_Ok;
67         Res := Files_Table.Table (Index).Stream;
68      end if;
69   end Get_File;
70
71   --  Assume INDEX is correct.
72   function Is_Open (Index : Ghdl_File_Index) return Boolean is
73   begin
74      return Files_Table.Table (Index).Stream /= NULL_Stream;
75   end Is_Open;
76
77   --  Assume INDEX is correct.
78   function Get_Kind (Index : Ghdl_File_Index) return Character is
79   begin
80      return Files_Table.Table (Index).Kind;
81   end Get_Kind;
82
83   procedure Check_File_Mode
84     (Index : Ghdl_File_Index; Is_Text : Boolean; Status : out Op_Status) is
85   begin
86      if Files_Table.Table (Index).Is_Text /= Is_Text then
87         Status := Op_Bad_Mode;
88      else
89         Status := Op_Ok;
90      end if;
91   end Check_File_Mode;
92
93   procedure Check_Read
94     (Index : Ghdl_File_Index; Is_Text : Boolean; Status : out Op_Status) is
95   begin
96      Check_File_Mode (Index, Is_Text, Status);
97      if Status /= Op_Ok then
98         return;
99      end if;
100
101      --  LRM08 5.5.2 File operations
102      --  It is an error if the access mode of the file object is write-only
103      --  or if the file object is not open.
104      if not Is_Open (Index) then
105         Status := Op_Not_Open;
106         return;
107      end if;
108      if Get_Kind (Index) /= 'r' then
109         Status := Op_Read_Write_File;
110         return;
111      end if;
112
113      Status := Op_Ok;
114   end Check_Read;
115
116   procedure Check_Write
117     (Index : Ghdl_File_Index; Is_Text : Boolean; Status : out Op_Status) is
118   begin
119      Check_File_Mode (Index, Is_Text, Status);
120      if Status /= Op_Ok then
121         return;
122      end if;
123
124      --  LRM08 5.5.2 File operations
125      --  It is an error if the access mode of the file object is read-only
126      --  or if the file object is not open.
127      if not Is_Open (Index) then
128         Status := Op_Not_Open;
129         return;
130      end if;
131      if Get_Kind (Index) = 'r' then
132         Status := Op_Write_Read_File;
133         return;
134      end if;
135
136      Status := Op_Ok;
137   end Check_Write;
138
139   function Create_File
140     (Is_Text : Boolean; Kind : Character; Sig : Ghdl_C_String)
141     return Ghdl_File_Index is
142   begin
143      Files_Table.Append ((Stream => NULL_Stream,
144                           Signature => Sig,
145                           Kind => Kind,
146                           Is_Text => Is_Text,
147                           Is_Alive => True));
148      return Files_Table.Last;
149   end Create_File;
150
151   procedure Destroy_File
152     (Is_Text : Boolean; Index : Ghdl_File_Index; Status : out Op_Status)
153   is
154      Cstream : C_Files;
155   begin
156      Get_File (Index, Cstream, Status);
157      if Status /= Op_Ok then
158         return;
159      end if;
160      if Cstream /= NULL_Stream then
161         Status := Op_Not_Closed;
162         return;
163      end if;
164      Check_File_Mode (Index, Is_Text, Status);
165      if Status /= Op_Ok then
166         return;
167      end if;
168
169      --  Cleanup.
170      Files_Table.Table (Index).Is_Alive := False;
171      if Index = Files_Table.Last then
172         while Files_Table.Last >= Files_Table.First
173           and then Files_Table.Table (Files_Table.Last).Is_Alive = False
174         loop
175            Files_Table.Decrement_Last;
176         end loop;
177      end if;
178   end Destroy_File;
179
180   function Ghdl_Text_File_Elaborate return Ghdl_File_Index is
181   begin
182      return Create_File (True, ' ', null);
183   end Ghdl_Text_File_Elaborate;
184
185   function Ghdl_File_Elaborate (Sig : Ghdl_C_String) return Ghdl_File_Index
186   is
187   begin
188      return Create_File (False, ' ', Sig);
189   end Ghdl_File_Elaborate;
190
191   procedure Ghdl_Text_File_Finalize
192     (File : Ghdl_File_Index; Status : out Op_Status) is
193   begin
194      Destroy_File (True, File, Status);
195   end Ghdl_Text_File_Finalize;
196
197   procedure Ghdl_File_Finalize
198     (File : Ghdl_File_Index; Status : out Op_Status) is
199   begin
200      Destroy_File (False, File, Status);
201   end Ghdl_File_Finalize;
202
203   procedure Ghdl_File_Endfile
204     (File : Ghdl_File_Index; Status : out Op_Status)
205   is
206      Stream : C_Files;
207      C : int;
208   begin
209      Get_File (File, Stream, Status);
210      if Status /= Op_Ok then
211         return;
212      end if;
213
214      --  LRM93 3.4.1 File Operations
215      --  LRM08 5.5.2 File Operations
216      --  It is an error if ENDFILE is called on a file object that is not
217      --  open.
218      if Stream = NULL_Stream then
219         Status := Op_Not_Open;
220         return;
221      end if;
222
223      --  Default: returns True.
224      Status := Op_End_Of_File;
225
226      --  LRM93 3.4.1 File Operations
227      --  LRM08 5.5.2 File Operations
228      --  Function ENDFILE always returns TRUE for an open file object whose
229      --  access mode is write-only.
230      if Get_Kind (File) /= 'r' then
231         return;
232      end if;
233
234      if feof (Stream) /= 0 then
235         return;
236      end if;
237      C := fgetc (Stream);
238      if C < 0 then
239         return;
240      end if;
241      if ungetc (C, Stream) /= C then
242         Status := Op_Ungetc_Error;
243         return;
244      end if;
245
246      Status := Op_Ok;
247      return;
248   end Ghdl_File_Endfile;
249
250   function Simple_Open (Name : Ghdl_C_String; Mode : Ghdl_C_String)
251                        return C_Files is
252   begin
253      return fopen (To_Address (Name), To_Address (Mode));
254   end Simple_Open;
255
256   Sig_Header : constant String := "#GHDL-BINARY-FILE-0.0" & Nl;
257
258   Std_Output_Name : constant String := "STD_OUTPUT" & NUL;
259   Std_Input_Name : constant String := "STD_INPUT" & NUL;
260
261   procedure File_Open (File : Ghdl_File_Index;
262                        Mode : Ghdl_I32;
263                        Name : Ghdl_C_String;
264                        Status : out Op_Status)
265   is
266      Str_Mode : String (1 .. 3);
267      F : C_Files;
268      Sig : Ghdl_C_String;
269      Sig_Len : Natural;
270      Kind : Character;
271   begin
272      Get_File (File, F, Status);
273      if Status /= Op_Ok then
274         return;
275      end if;
276
277      if F /= NULL_Stream then
278         --  File was already open.
279         Status := Op_Not_Closed;
280         return;
281      end if;
282
283      case Mode is
284         when Read_Mode =>
285            Kind := 'r';
286         when Write_Mode =>
287            Kind := 'w';
288         when Append_Mode =>
289            Kind := 'a';
290         when others =>
291            --  Bad mode, cannot happen.
292            Status := Op_Bad_Mode;
293            return;
294      end case;
295
296      if Strcmp (Name, To_Ghdl_C_String (Std_Input_Name'Address)) = 0 then
297         if Mode /= Read_Mode then
298            Status := Op_Mode_Error;
299            return;
300         end if;
301         F := stdin;
302      elsif Strcmp (Name, To_Ghdl_C_String (Std_Output_Name'Address)) = 0 then
303         if Mode /= Write_Mode then
304            Status := Op_Mode_Error;
305            return;
306         end if;
307         F := stdout;
308      else
309         Str_Mode (1) := Kind;
310         if Files_Table.Table (File).Is_Text then
311            Str_Mode (2) := NUL;
312         else
313            Str_Mode (2) := 'b';
314            Str_Mode (3) := NUL;
315         end if;
316         F := Open_Handler (Name, To_Ghdl_C_String (Str_Mode'Address));
317         if F = NULL_Stream then
318            Status := Op_Name_Error;
319            return;
320         end if;
321         -- if Grt.Options.Unbuffered_Writes and Mode /= Read_Mode then
322         --    setbuf (F, NULL_voids);
323         -- end if;
324      end if;
325
326      Sig := Files_Table.Table (File).Signature;
327      if Sig /= null then
328         Sig_Len := strlen (Sig);
329         case Mode is
330            when Write_Mode =>
331               if fwrite (Sig_Header'Address, 1, Sig_Header'Length, F)
332                 /= Sig_Header'Length
333               then
334                  Status := Op_Write_Error;
335                  return;
336               end if;
337               if fwrite (Sig (1)'Address, 1, size_t (Sig_Len), F)
338                 /= size_t (Sig_Len)
339               then
340                  Status := Op_Write_Error;
341                  return;
342               end if;
343            when Read_Mode =>
344               declare
345                  Hdr : String (1 .. Sig_Header'Length);
346                  Sig_Buf : String (1 .. Sig_Len);
347               begin
348                  if fread (Hdr'Address, 1, Hdr'Length, F) /= Hdr'Length then
349                     Status := Op_Read_Error;
350                     return;
351                  end if;
352                  if Hdr /= Sig_Header then
353                     Status := Op_Signature_Error;
354                     return;
355                  end if;
356                  if fread (Sig_Buf'Address, 1, Sig_Buf'Length, F)
357                    /= Sig_Buf'Length
358                  then
359                     Status := Op_Read_Error;
360                     return;
361                  end if;
362                  if Sig_Buf /= Sig (1 .. Sig_Len) then
363                     Status := Op_Signature_Error;
364                     return;
365                  end if;
366               end;
367            when Append_Mode =>
368               null;
369            when others =>
370               null;
371         end case;
372      end if;
373
374      Files_Table.Table (File).Stream := F;
375      Files_Table.Table (File).Kind := Kind;
376
377      Status := Op_Ok;
378   end File_Open;
379
380   procedure Ghdl_Text_File_Open (File : Ghdl_File_Index;
381                                  Mode : Ghdl_I32;
382                                  Name : Ghdl_C_String;
383                                  Status : out Op_Status) is
384   begin
385      Check_File_Mode (File, True, Status);
386      if Status /= Op_Ok then
387         return;
388      end if;
389
390      File_Open (File, Mode, Name, Status);
391   end Ghdl_Text_File_Open;
392
393   procedure Ghdl_File_Open (File : Ghdl_File_Index;
394                             Mode : Ghdl_I32;
395                             Name : Ghdl_C_String;
396                             Status : out Op_Status) is
397   begin
398      Check_File_Mode (File, False, Status);
399      if Status /= Op_Ok then
400         return;
401      end if;
402
403      File_Open (File, Mode, Name, Status);
404   end Ghdl_File_Open;
405
406   procedure Ghdl_Text_Write (File : Ghdl_File_Index; Str : Std_String_Ptr;
407                                                      Status : out Op_Status)
408   is
409      Res : C_Files;
410      Len : size_t;
411      R : size_t;
412   begin
413      Get_File (File, Res, Status);
414      if Status /= Op_Ok then
415         return;
416      end if;
417      Check_Write (File, True, Status);
418      if Status /= Op_Ok then
419         return;
420      end if;
421
422      Len := size_t (Str.Bounds.Dim_1.Length);
423      if Len = 0 then
424         Status := Op_Ok;
425         return;
426      end if;
427
428      R := fwrite (Str.Base (0)'Address, Len, 1, Res);
429      if R /= 1 then
430         Status := Op_Write_Error;
431         return;
432      end if;
433
434      if Auto_Flush then
435         fflush (Res);
436      end if;
437
438      Status := Op_Ok;
439   end Ghdl_Text_Write;
440
441   procedure Ghdl_Write_Scalar (File : Ghdl_File_Index;
442                                Ptr : Ghdl_Ptr;
443                                Length : Ghdl_Index_Type;
444                                Status : out Op_Status)
445   is
446      Res : C_Files;
447      R : size_t;
448   begin
449      Get_File (File, Res, Status);
450      if Status /= Op_Ok then
451         return;
452      end if;
453      Check_Write (File, False, Status);
454      if Status /= Op_Ok then
455         return;
456      end if;
457
458      R := fwrite (System.Address (Ptr), size_t (Length), 1, Res);
459      if R /= 1 then
460         Status := Op_Write_Error;
461         return;
462      end if;
463      if Auto_Flush then
464         fflush (Res);
465      end if;
466
467      Status := Op_Ok;
468   end Ghdl_Write_Scalar;
469
470   procedure Ghdl_Read_Scalar (File : Ghdl_File_Index;
471                               Ptr : Ghdl_Ptr;
472                               Length : Ghdl_Index_Type;
473                               Status : out Op_Status)
474   is
475      Res : C_Files;
476      R : size_t;
477   begin
478      Get_File (File, Res, Status);
479      if Status /= Op_Ok then
480         return;
481      end if;
482      Check_Read (File, False, Status);
483      if Status /= Op_Ok then
484         return;
485      end if;
486
487      R := fread (System.Address (Ptr), size_t (Length), 1, Res);
488      if R /= 1 then
489         Status := Op_Read_Error;
490         return;
491      end if;
492
493      Status := Op_Ok;
494   end Ghdl_Read_Scalar;
495
496   procedure Ghdl_Text_Read_Length (File : Ghdl_File_Index;
497                                    Str : Std_String_Ptr;
498                                    Status : out Op_Status;
499                                    Length : out Std_Integer)
500   is
501      Stream : C_Files;
502      C : int;
503      Len : Ghdl_Index_Type;
504   begin
505      Length := 0;
506      Get_File (File, Stream, Status);
507      if Status /= Op_Ok then
508         return;
509      end if;
510      Check_Read (File, True, Status);
511      if Status /= Op_Ok then
512         return;
513      end if;
514
515      Len := Str.Bounds.Dim_1.Length;
516      --  Read until EOL (or EOF).
517      --  Store as much as possible.
518      for I in Ghdl_Index_Type loop
519         C := fgetc (Stream);
520         if C < 0 then
521            Length := Std_Integer (I);
522            Status := Op_End_Of_File;
523            return;
524         end if;
525         if I < Len then
526            Str.Base (I) := Character'Val (C);
527         end if;
528         --  End of line is '\n' or LF or character # 10.
529         if C = C_LF then
530            Length := Std_Integer (I + 1);
531            Status := Op_Ok;
532            return;
533         end if;
534      end loop;
535      Length := 0;
536      Status := Op_Ok;
537   end Ghdl_Text_Read_Length;
538
539   procedure Ghdl_Untruncated_Text_Read (File : Ghdl_File_Index;
540                                         Buf : Ghdl_C_String;
541                                         Len : in out Std_Integer;
542                                         Status : out Op_Status)
543   is
544      Stream : C_Files;
545      L : Natural;
546      C : int;
547   begin
548      Get_File (File, Stream, Status);
549      if Status /= Op_Ok then
550         return;
551      end if;
552      Check_Read (File, True, Status);
553      if Status /= Op_Ok then
554         return;
555      end if;
556
557      --  Default status.
558      Status := Op_Ok;
559
560      --  Read at most LEN characters, stop at EOL.
561      L := 0;
562      for I in 1 .. Len loop
563         C := fgetc (Stream);
564         if C < 0 then
565            Status := Op_End_Of_File;
566            exit;
567         end if;
568         --  Be nice with DOS files: handle CR/CR+LF/LF.
569         --  Note: LF+CR is not handled, so that on unix we don't need
570         --  to read the next line.
571         --  Always return LF as end of line.
572         if C = C_CR then
573            C := fgetc (Stream);
574            if C > 0 and C /= C_LF then
575               C := ungetc (C, Stream);
576               pragma Assert (C >= 0);
577            end if;
578            C := C_LF;
579         end if;
580         L := L + 1;
581         Buf (L) := Character'Val (C);
582         exit when C = C_LF;
583      end loop;
584
585      Len := Std_Integer (L);
586   end Ghdl_Untruncated_Text_Read;
587
588   procedure File_Close
589     (File : Ghdl_File_Index; Is_Text : Boolean; Status : out Op_Status)
590   is
591      Stream : C_Files;
592   begin
593      Get_File (File, Stream, Status);
594      if Status /= Op_Ok then
595         return;
596      end if;
597      Check_File_Mode (File, Is_Text, Status);
598      if Status /= Op_Ok then
599         return;
600      end if;
601
602      --  LRM 3.4.1  File Operations
603      --  If F is not associated with an external file, then FILE_CLOSE has
604      --  no effect.
605      if Stream = NULL_Stream then
606         Status := Op_Ok;
607         return;
608      end if;
609
610      if fclose (Stream) /= 0 then
611         Status := Op_Close_Error;
612         return;
613      end if;
614      Files_Table.Table (File).Stream := NULL_Stream;
615      Status := Op_Ok;
616   end File_Close;
617
618   procedure Ghdl_Text_File_Close
619     (File : Ghdl_File_Index; Status : out Op_Status) is
620   begin
621      File_Close (File, True, Status);
622   end Ghdl_Text_File_Close;
623
624   procedure Ghdl_File_Close
625     (File : Ghdl_File_Index; Status : out Op_Status) is
626   begin
627      File_Close (File, False, Status);
628   end Ghdl_File_Close;
629
630   procedure Ghdl_File_Flush (File : Ghdl_File_Index; Status : out Op_Status)
631   is
632      Stream : C_Files;
633   begin
634      Get_File (File, Stream, Status);
635      if Status /= Op_Ok then
636         return;
637      end if;
638
639      --  LRM08 5.5.2 File Operations
640      --  For the WRITE and FLUSH procedures, it is an error if the access
641      --  mode of the file object is read-only or if the file is not open.
642      if Stream = NULL_Stream then
643         Status := Op_Not_Open;
644         return;
645      end if;
646      if Get_Kind (File) = 'r' then
647         Status := Op_Write_Read_File;
648         return;
649      end if;
650
651      fflush (Stream);
652      Status := Op_Ok;
653   end Ghdl_File_Flush;
654end Grt.Files_Operations;
655