1--
2--  Copyright (c) 2014-15 John Marino <draco@marino.st>
3--
4--  Permission to use, copy, modify, and distribute this software for any
5--  purpose with or without fee is hereby granted, provided that the above
6--  copyright notice and this permission notice appear in all copies.
7--
8--  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9--  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10--  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11--  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12--  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13--  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14--  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15--
16
17with Ada.Text_IO;
18with Ada.Strings.Fixed;
19with Ada.Calendar.Formatting;
20with Ada.Calendar.Conversions;
21with Ada.Directories;
22with Ada.Strings;
23with Ada.Strings.Fixed;
24with DragonFly.FileStatus;
25
26package body DragonFly.HAMMER.History is
27
28   package TIO renames Ada.Text_IO;
29   package CFM renames Ada.Calendar.Formatting;
30   package CCV renames Ada.Calendar.Conversions;
31   package DIR renames Ada.Directories;
32   package SFX renames Ada.Strings.Fixed;
33   package STR renames Ada.Strings;
34   package DFS renames DragonFly.FileStatus;
35
36
37   --------------------
38   --  scan_history  --
39   --------------------
40
41   procedure scan_history (path : in String; result : out scan_result)
42   is
43      descriptor : file_descriptor;
44      history    : HB.hammer_ioc_history;
45      formhack   : TraxTime;
46   begin
47      result.state      := clean;
48      result.path_check := not_found;
49
50      declare
51      begin
52         descriptor := HB.open_file_for_reading (path);
53         result.path_check := found;
54      exception
55         when FILE_Failure => null;
56      end;
57      if result.path_check = not_found then
58         --  If we are here, then descriptor was never opened
59         if DIR.Exists (path) then
60            --  The file exists, but couldn't be opened.  No read perms!
61            result.path_check := secret;
62            return;
63         end if;
64         declare
65            dname    : Trax;
66            delname  : Trax;
67            delstamp : uInt32;
68         begin
69            dname := match_against_directory_trax (path);
70            descriptor := HB.open_file_for_reading (path => path & dname);
71            collect_history (fd => descriptor, filename => path,
72               suffix => delname, timestamp => delstamp);
73            HB.close_file (descriptor);
74            if delname = failed_search then
75               raise Match_Failure;
76            end if;
77            descriptor := HB.open_file_for_reading (path => path & delname);
78            HB.close_file (descriptor);
79            formhack := format_timestamp (delstamp);
80            result.path_check := deleted;
81            result.history.Append (New_Item => (delname, formhack));
82         exception
83            when FILE_Failure => raise Match_Failure;
84         end;
85      end if;
86
87
88      if result.path_check = found then
89         history := HB.initialize_history;
90         HB.retrieve_history (open_file => descriptor, history => history);
91         if (history.head.flags and HB.HAMMER_IOC_HISTORY_UNSYNCED) > 0 then
92            result.state := dirty;
93         end if;
94         History_Loop :
95            loop
96               for j in 0 .. history.count - 1 loop
97                  formhack := format_timestamp (history.hist_ary (j).time32);
98                  declare
99                     testfd  : file_descriptor;
100                     fname   : constant Trax := "@@0x" &
101                                  format_as_hex (history.hist_ary (j).tid);
102                     inode   : DFS.inode_data;
103                     statres : Int32;
104                  begin
105                     DFS.stat (path & fname, inode, statres);
106                     if statres < 0 then
107                        raise FILE_Failure;
108                     end if;
109                     if (inode.st_mode and DFS.S_IFIFO) > 0 then
110                        raise Fake_Transaction;
111                     end if;
112                     testfd := HB.open_file_for_reading (path & fname);
113                     HB.close_file (testfd);
114                     result.history.Append (New_Item => (fname, formhack));
115                  exception
116                     when FILE_Failure => null;
117                     when Fake_Transaction => null;
118                  end;
119               end loop;
120               exit History_Loop when
121                  (history.head.flags and HB.HAMMER_IOC_HISTORY_EOF) > 0;
122               exit History_Loop when
123                  (history.head.flags and HB.HAMMER_IOC_HISTORY_NEXT_KEY) > 0;
124               exit History_Loop when
125                  (history.head.flags and HB.HAMMER_IOC_HISTORY_NEXT_TID) = 0;
126               history.beg_tid := history.nxt_tid;
127               HB.retrieve_history (
128                  open_file => descriptor,
129                  history   => history);
130            end loop History_Loop;
131         HB.close_file (descriptor);
132      end if;
133
134   exception
135      when IOCTL_Failure | Match_Failure => null;
136      when Error : others =>
137         TIO.Put_Line (Ada.Exceptions.Exception_Information (Error));
138   end scan_history;
139
140
141   ------------------------------------
142   --  match_against_directory_trax  --
143   ------------------------------------
144
145   function match_against_directory_trax (filename : String) return Trax is
146      fd       : file_descriptor;
147      slash    : Natural;
148      result   : String := failed_search;
149      dontcare : uInt32;
150   begin
151      slash := SFX.Index (
152                  Source => filename,
153                  Pattern => "/",
154                  Going => STR.Backward);
155      if slash = 0 then
156         fd := HB.open_file_for_reading (".");
157      else
158         fd := HB.open_file_for_reading (
159            filename (filename'First .. slash - 1));
160      end if;
161      if fd > 0 then
162         collect_history (fd => fd, filename => filename, suffix => result,
163            timestamp => dontcare);
164         HB.close_file (fd);
165      end if;
166      if result = failed_search then
167         raise Match_Failure;
168      end if;
169      return result;   -- except will be caught upstream if fd < 0
170   end match_against_directory_trax;
171
172
173   -----------------------
174   --  collect_history  --
175   -----------------------
176
177   procedure collect_history (
178               fd        : in file_descriptor;
179               filename  : in String;
180               suffix    : out Trax;
181               timestamp : out uInt32)
182   is
183      history : HB.hammer_ioc_history;
184      top_tid : HB.hammer_tid := 0;
185      eject   : Boolean;
186   begin
187      suffix := failed_search;
188      history := HB.initialize_history;
189      history.nxt_key := HB.HAMMER_MAX_KEY;
190      history.head.flags := HB.HAMMER_IOC_HISTORY_ATKEY;
191      HB.retrieve_history (open_file => fd, history => history);
192      loop
193         eject := True;
194         for j in 0 .. history.count - 1 loop
195            if history.hist_ary (j).tid > top_tid then
196               declare
197                  temptrx : constant String := "@@0x" &
198                               format_as_hex (history.hist_ary (j).tid);
199                  testfd  : file_descriptor;
200                  inode   : DFS.inode_data;
201                  result  : Int32;
202               begin
203                  DFS.stat (filename & temptrx, inode, result);
204                  if result < 0 then
205                     raise FILE_Failure;
206                  end if;
207                  if (inode.st_mode and DFS.S_IFIFO) > 0 then
208                     raise Fake_Transaction;
209                  end if;
210                  testfd := HB.open_file_for_reading (filename & temptrx);
211                  if testfd > 0 then
212                     top_tid   := history.hist_ary (j).tid;
213                     timestamp := history.hist_ary (j).time32;
214                     suffix    := temptrx;
215                     HB.close_file (testfd);
216                  end if;
217               exception
218                  when FILE_Failure => null;
219                  when Fake_Transaction => null;
220               end;
221            end if;
222         end loop;
223         exit when (history.head.flags and HB.HAMMER_IOC_HISTORY_EOF) > 0;
224         if (history.head.flags and HB.HAMMER_IOC_HISTORY_NEXT_KEY) > 0 and
225            history.key /= history.nxt_key
226         then
227            history.key := history.nxt_key;
228            history.nxt_key := HB.HAMMER_MAX_KEY;
229            eject := False;
230         end if;
231         if (history.head.flags and HB.HAMMER_IOC_HISTORY_NEXT_TID) > 0 and
232            history.beg_tid /= history.nxt_tid
233         then
234            history.beg_tid := history.nxt_tid;
235            eject := False;
236         end if;
237         exit when eject;
238         HB.retrieve_history (open_file => fd, history => history);
239      end loop;
240   end collect_history;
241
242
243   ------------------------------------------
244   --  Format %016x : format_as_hex (tid)  --
245   ------------------------------------------
246
247   function format_as_hex (bignum : HB.hammer_tid) return String is
248   begin
249      return zeropad_hex (uInt64 (bignum));
250   end format_as_hex;
251
252
253   ------------------------
254   --  format_timestamp  --
255   ------------------------
256
257   function format_timestamp (raw : uInt32) return TraxTime is
258      hack : HB.IC.long := HB.IC.long (raw);
259      hack_time : CAL.Time;
260   begin
261      hack_time := CCV.To_Ada_Time (Unix_Time => hack);
262      return CFM.Image (Date => hack_time, Time_Zone => tz_offset);
263   end format_timestamp;
264
265
266   ------------------------------
267   --  modification_timestamp  --
268   ------------------------------
269
270   function modification_timestamp (path : in String) return TraxTime is
271      modtime : Ada.Calendar.Time;
272   begin
273      modtime := DIR.Modification_Time (path);
274      return CFM.Image (Date => modtime, Time_Zone => tz_offset);
275   exception
276      when others =>
277         return "1776-07-04 16:00:00";
278   end modification_timestamp;
279
280end DragonFly.HAMMER.History;
281