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
17
18with DragonFly.FileStatus;
19
20package body DragonFly.HAMMER.Ghosts is
21
22   package DFS renames DragonFly.FileStatus;
23
24
25   ----------------------------
26   --  scan_for_file_ghosts  --
27   ----------------------------
28
29   procedure scan_for_file_ghosts (
30      directory_path   : in String;
31      file_ghosts      : out Filename_Container.Vector;
32      directory_ghosts : out Filename_Container.Vector)
33   is
34      use type DIR.File_Kind;
35      descriptor     : file_descriptor;
36      just_directory : constant String := DIR.Containing_Directory
37                                          (directory_path & "/")  & "/";
38   begin
39      file_ghosts.Clear;
40      directory_ghosts.Clear;
41
42      if DIR.Exists (directory_path) and then
43         DIR.Kind (directory_path) = DIR.Directory
44      then
45         descriptor := HB.open_file_for_reading (just_directory);
46         if descriptor > 0 then
47            establish_baseline (just_directory);
48            scan_history_of_directory (
49               fd             => descriptor,
50               directory_path => just_directory,
51               ghosts_file    => file_ghosts,
52               ghosts_dir     => directory_ghosts);
53            HB.close_file (descriptor);
54            SortEntry.Sort (file_ghosts);
55            SortEntry.Sort (directory_ghosts);
56         end if;
57      end if;
58   end scan_for_file_ghosts;
59
60
61   --------------------------
62   --  establish_baseline  --
63   --------------------------
64
65   procedure establish_baseline (directory_path : in String)
66   is
67   begin
68      living_files.Clear;
69      living_dirs.Clear;
70      declare
71         Search     : DIR.Search_Type;
72         item       : DIR.Directory_Entry_Type;
73         dir_filter : constant DIR.Filter_Type := (True, False, False);
74      begin
75         DIR.Start_Search (Search    => Search,
76                           Directory => directory_path,
77                           Pattern   => "",
78                           Filter    => dir_filter);
79         while DIR.More_Entries (Search) loop
80            DIR.Get_Next_Entry (Search => Search, Directory_Entry => item);
81            living_dirs.Append (DIR.Simple_Name (item));
82         end loop;
83      end;
84      declare
85         Search     : DIR.Search_Type;
86         item       : DIR.Directory_Entry_Type;
87         ord_filter : constant DIR.Filter_Type := (False, True, False);
88      begin
89         DIR.Start_Search (Search    => Search,
90                           Directory => directory_path,
91                           Pattern   => "",
92                           Filter    => ord_filter);
93         while DIR.More_Entries (Search) loop
94            DIR.Get_Next_Entry (Search => Search, Directory_Entry => item);
95            living_files.Append (DIR.Simple_Name (item));
96         end loop;
97      end;
98   end establish_baseline;
99
100
101   ------------------------------------------
102   --  Format %016x : format_as_hex (tid)  --
103   ------------------------------------------
104
105   function format_as_hex (bignum : HB.hammer_tid) return String is
106   begin
107      return zeropad_hex (uInt64 (bignum));
108   end format_as_hex;
109
110
111   ------------------------
112   --  scan_transaction  --
113   ------------------------
114
115   procedure scan_transaction (
116      directory_path : in String;
117      transaction    : in String;
118      fkind          : in DIR.File_Kind;
119      ghost          : in out Filename_Container.Vector)
120   is
121      use type DIR.File_Kind;
122      filter : DIR.Filter_Type := (others => False);
123      Search : DIR.Search_Type;
124      item   : DIR.Directory_Entry_Type;
125
126   begin
127      filter (fkind) := True;
128      DIR.Start_Search (Search    => Search,
129                        Directory => directory_path & transaction,
130                        Pattern   => "",
131                        Filter    => filter);
132      while DIR.More_Entries (Search) loop
133         DIR.Get_Next_Entry (Search => Search, Directory_Entry => item);
134         declare
135            ndx      : Filename_Container.Extended_Index;
136            filename : constant String := DIR.Simple_Name (item);
137            truedir  : constant String := transaction & "/" & filename;
138            traxpath : constant String := directory_path & truedir;
139            okay     : constant Boolean :=
140                          not (filename = "." or filename = "..");
141            found    : Boolean := False;
142         begin
143            if okay then
144               if fkind = DIR.Directory then
145                  ndx := living_dirs.Find_Index (Item => filename);
146                  if ndx = Filename_Container.No_Index then
147                     for k in 1 .. Natural (ghost.Length) loop
148                        if not found and then
149                           ghost.Element (k) (22 .. ghost.Element (k)'Last) =
150                              filename
151                        then
152                           found := True;
153                        end if;
154                     end loop;
155                     if not found and then
156                        directory_has_files (traxpath)
157                     then
158                        ghost.Append (truedir);
159                     end if;
160                  end if;
161               else
162                  ndx := living_files.Find_Index (Item => filename);
163                  if ndx = Filename_Container.No_Index then
164                     ndx := ghost.Find_Index (Item => filename);
165                     if ndx = Filename_Container.No_Index then
166                        declare
167                           inode   : DFS.inode_data;
168                           result  : Int32;
169                        begin
170                           DFS.stat (traxpath, inode, result);
171                           if result < 0 then
172                              raise FILE_Failure;
173                           end if;
174                           if (inode.st_mode and DFS.S_IFIFO) > 0 then
175                              raise Fake_Transaction;
176                           end if;
177                           ghost.Append (filename);
178                        exception
179                           when FILE_Failure => null;
180                           when Fake_Transaction => null;
181                        end;
182                     end if;
183                  end if;
184               end if;
185            end if;
186         end;
187      end loop;
188
189   end scan_transaction;
190
191
192   ---------------------------------
193   --  scan_history_of_directory  --
194   ---------------------------------
195
196   procedure scan_history_of_directory (
197      fd             : in file_descriptor;
198      directory_path : in String;
199      ghosts_file    : in out Filename_Container.Vector;
200      ghosts_dir     : in out Filename_Container.Vector)
201   is
202      history : HB.hammer_ioc_history;
203      eject   : Boolean;
204      tids    : TID_Container.Vector;
205      ndx     : TID_Container.Extended_Index;
206   begin
207      history            := HB.initialize_history;
208      history.nxt_key    := HB.HAMMER_MAX_KEY;
209      history.head.flags := HB.HAMMER_IOC_HISTORY_ATKEY;
210      HB.retrieve_history (open_file => fd, history => history);
211      tids.Clear;
212      scan_loop :
213         loop
214            for j in 0 .. history.count - 1 loop
215               ndx := tids.Find_Index (Item => history.hist_ary (j).tid);
216               if ndx = TID_Container.No_Index then
217                  tids.Append (history.hist_ary (j).tid);
218               end if;
219            end loop;
220            exit scan_loop when
221               (history.head.flags and HB.HAMMER_IOC_HISTORY_EOF) > 0;
222
223            if (history.head.flags and HB.HAMMER_IOC_HISTORY_NEXT_KEY) > 0 and
224               history.key /= history.nxt_key
225            then
226               history.key := history.nxt_key;
227               history.nxt_key := HB.HAMMER_MAX_KEY;
228               eject := False;
229            end if;
230            if (history.head.flags and HB.HAMMER_IOC_HISTORY_NEXT_TID) > 0 and
231               history.beg_tid /= history.nxt_tid
232            then
233               history.beg_tid := history.nxt_tid;
234               eject := False;
235            end if;
236            exit scan_loop when eject;
237            HB.retrieve_history (open_file => fd, history => history);
238         end loop scan_loop;
239      SortTid.Sort (tids);
240      for x in reverse 1 .. Natural (tids.Length) loop
241         declare
242            temptrx : constant String := "@@0x" & format_as_hex (tids (x));
243            inode   : DFS.inode_data;
244            result  : Int32;
245         begin
246            DFS.stat (directory_path & temptrx, inode, result);
247            if result < 0 then
248               raise FILE_Failure;
249            end if;
250            if (inode.st_mode and DFS.S_IFDIR) = 0 then
251               raise FILE_Failure;
252            end if;
253            scan_transaction (
254               directory_path => directory_path,
255               transaction    => temptrx,
256               fkind          => DIR.Directory,
257               ghost          => ghosts_dir);
258            scan_transaction (
259               directory_path => directory_path,
260               transaction    => temptrx,
261               fkind          => DIR.Ordinary_File,
262               ghost          => ghosts_file);
263            exception
264               when FILE_Failure => null;
265         end;
266      end loop;
267
268   end scan_history_of_directory;
269
270
271   ---------------------------
272   --  directory_has_file  --
273   ---------------------------
274
275   function directory_has_files (directory_path : String) return Boolean
276   is
277      search : DIR.Search_Type;
278      filter : DIR.Filter_Type := (others => True);
279      data   : DIR.Directory_Entry_Type;
280      result : Boolean := False;
281   begin
282      if not DIR.Exists (directory_path) then
283         return False;
284      end if;
285      DIR.Start_Search (
286         Search    => search,
287         Directory => directory_path,
288         Pattern   => "",
289         Filter    => filter);
290      while not result and DIR.More_Entries (search) loop
291         DIR.Get_Next_Entry (search, data);
292         declare
293            sname : constant String := DIR.Simple_Name (data);
294            okay  : constant Boolean := not (sname = "." or sname = "..");
295         begin
296            if okay then
297               result := True;
298            end if;
299         end;
300      end loop;
301      DIR.End_Search (search);
302      return result;
303   end directory_has_files;
304
305end DragonFly.HAMMER.Ghosts;
306