1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(ets).
21
22%% Interface to the Term store BIF's
23%% ets == Erlang Term Store
24
25-export([file2tab/1,
26	 file2tab/2,
27	 filter/3,
28	 foldl/3, foldr/3,
29	 match_delete/2,
30	 tab2file/2,
31	 tab2file/3,
32	 tabfile_info/1,
33	 from_dets/2,
34	 to_dets/2,
35	 init_table/2,
36	 test_ms/2,
37	 tab2list/1,
38         table/1,
39         table/2,
40	 fun2ms/1,
41	 match_spec_run/2,
42	 repair_continuation/2]).
43
44-export([i/0, i/1, i/2, i/3]).
45
46-export_type([tab/0, tid/0, match_spec/0, comp_match_spec/0, match_pattern/0]).
47
48%%-----------------------------------------------------------------------------
49
50-type access()     :: public | protected | private.
51-type tab()        :: atom() | tid().
52-type type()       :: set | ordered_set | bag | duplicate_bag.
53-type continuation() :: '$end_of_table'
54                      | {tab(),integer(),integer(),comp_match_spec(),list(),integer()}
55                      | {tab(),_,_,integer(),comp_match_spec(),list(),integer(),integer()}.
56
57-opaque tid()      :: reference().
58
59-type match_pattern() :: atom() | tuple().
60-type match_spec()    :: [{match_pattern(), [_], [_]}].
61
62%%-----------------------------------------------------------------------------
63
64%%% BIFs
65
66-export([all/0, delete/1, delete/2, delete_all_objects/1,
67         delete_object/2, first/1, give_away/3, info/1, info/2,
68         insert/2, insert_new/2, is_compiled_ms/1, last/1, lookup/2,
69         lookup_element/3, match/1, match/2, match/3, match_object/1,
70         match_object/2, match_object/3, match_spec_compile/1,
71         match_spec_run_r/3, member/2, new/2, next/2, prev/2,
72         rename/2, safe_fixtable/2, select/1, select/2, select/3,
73         select_count/2, select_delete/2, select_replace/2, select_reverse/1,
74         select_reverse/2, select_reverse/3, setopts/2, slot/2,
75         take/2,
76         update_counter/3, update_counter/4, update_element/3,
77         whereis/1]).
78
79%% internal exports
80-export([internal_request_all/0,
81         internal_delete_all/2,
82         internal_select_delete/2]).
83
84-spec all() -> [Tab] when
85      Tab :: tab().
86
87all() ->
88    receive_all(ets:internal_request_all(),
89                erlang:system_info(schedulers),
90                []).
91
92receive_all(_Ref, 0, All) ->
93    All;
94receive_all(Ref, N, All) ->
95    receive
96        {Ref, SchedAll} ->
97            receive_all(Ref, N-1, SchedAll ++ All)
98    end.
99
100-spec internal_request_all() -> reference().
101
102internal_request_all() ->
103    erlang:nif_error(undef).
104
105-spec delete(Tab) -> true when
106      Tab :: tab().
107
108delete(_) ->
109    erlang:nif_error(undef).
110
111-spec delete(Tab, Key) -> true when
112      Tab :: tab(),
113      Key :: term().
114
115delete(_, _) ->
116    erlang:nif_error(undef).
117
118-spec delete_all_objects(Tab) -> true when
119      Tab :: tab().
120
121delete_all_objects(Tab) ->
122    _ = ets:internal_delete_all(Tab, undefined),
123    true.
124
125-spec internal_delete_all(Tab, undefined) -> NumDeleted when
126      Tab :: tab(),
127      NumDeleted :: non_neg_integer().
128
129internal_delete_all(_, _) ->
130    erlang:nif_error(undef).
131
132-spec delete_object(Tab, Object) -> true when
133      Tab :: tab(),
134      Object :: tuple().
135
136delete_object(_, _) ->
137    erlang:nif_error(undef).
138
139-spec first(Tab) -> Key | '$end_of_table' when
140      Tab :: tab(),
141      Key :: term().
142
143first(_) ->
144    erlang:nif_error(undef).
145
146-spec give_away(Tab, Pid, GiftData) -> true when
147      Tab :: tab(),
148      Pid :: pid(),
149      GiftData :: term().
150
151give_away(_, _, _) ->
152    erlang:nif_error(undef).
153
154-spec info(Tab) -> InfoList | undefined when
155      Tab :: tab(),
156      InfoList :: [InfoTuple],
157      InfoTuple :: {compressed, boolean()}
158                 | {decentralized_counters, boolean()}
159                 | {heir, pid() | none}
160                 | {id, tid()}
161                 | {keypos, pos_integer()}
162                 | {memory, non_neg_integer()}
163                 | {name, atom()}
164                 | {named_table, boolean()}
165                 | {node, node()}
166                 | {owner, pid()}
167                 | {protection, access()}
168                 | {size, non_neg_integer()}
169                 | {type, type()}
170		 | {write_concurrency, boolean()}
171		 | {read_concurrency, boolean()}.
172
173info(_) ->
174    erlang:nif_error(undef).
175
176-spec info(Tab, Item) -> Value | undefined when
177      Tab :: tab(),
178      Item :: binary | compressed | decentralized_counters | fixed | heir | id | keypos | memory
179            | name | named_table | node | owner | protection
180            | safe_fixed | safe_fixed_monotonic_time | size | stats | type
181	    | write_concurrency | read_concurrency,
182      Value :: term().
183
184info(_, _) ->
185    erlang:nif_error(undef).
186
187-spec insert(Tab, ObjectOrObjects) -> true when
188      Tab :: tab(),
189      ObjectOrObjects :: tuple() | [tuple()].
190
191insert(_, _) ->
192    erlang:nif_error(undef).
193
194-spec insert_new(Tab, ObjectOrObjects) -> boolean() when
195      Tab :: tab(),
196      ObjectOrObjects :: tuple() | [tuple()].
197
198insert_new(_, _) ->
199    erlang:nif_error(undef).
200
201-spec is_compiled_ms(Term) -> boolean() when
202      Term :: term().
203
204is_compiled_ms(_) ->
205    erlang:nif_error(undef).
206
207-spec last(Tab) -> Key | '$end_of_table' when
208      Tab :: tab(),
209      Key :: term().
210
211last(_) ->
212    erlang:nif_error(undef).
213
214-spec lookup(Tab, Key) -> [Object] when
215      Tab :: tab(),
216      Key :: term(),
217      Object :: tuple().
218
219lookup(_, _) ->
220    erlang:nif_error(undef).
221
222-spec lookup_element(Tab, Key, Pos) -> Elem when
223      Tab :: tab(),
224      Key :: term(),
225      Pos :: pos_integer(),
226      Elem :: term() | [term()].
227
228lookup_element(_, _, _) ->
229    erlang:nif_error(undef).
230
231-spec match(Tab, Pattern) -> [Match] when
232      Tab :: tab(),
233      Pattern :: match_pattern(),
234      Match :: [term()].
235
236match(_, _) ->
237    erlang:nif_error(undef).
238
239-spec match(Tab, Pattern, Limit) -> {[Match], Continuation} |
240                                       '$end_of_table'  when
241      Tab :: tab(),
242      Pattern :: match_pattern(),
243      Limit :: pos_integer(),
244      Match :: [term()],
245      Continuation :: continuation().
246
247match(_, _, _) ->
248    erlang:nif_error(undef).
249
250-spec match(Continuation) -> {[Match], Continuation} |
251                                '$end_of_table'  when
252      Match :: [term()],
253      Continuation :: continuation().
254
255match(_) ->
256    erlang:nif_error(undef).
257
258-spec match_object(Tab, Pattern) -> [Object] when
259      Tab :: tab(),
260      Pattern :: match_pattern(),
261      Object :: tuple().
262
263match_object(_, _) ->
264    erlang:nif_error(undef).
265
266-spec match_object(Tab, Pattern, Limit) -> {[Object], Continuation} |
267                                           '$end_of_table' when
268      Tab :: tab(),
269      Pattern :: match_pattern(),
270      Limit :: pos_integer(),
271      Object :: tuple(),
272      Continuation :: continuation().
273
274match_object(_, _, _) ->
275    erlang:nif_error(undef).
276
277-spec match_object(Continuation) -> {[Object], Continuation} |
278                                    '$end_of_table' when
279      Object :: tuple(),
280      Continuation :: continuation().
281
282match_object(_) ->
283    erlang:nif_error(undef).
284
285-spec match_spec_compile(MatchSpec) -> CompiledMatchSpec when
286      MatchSpec :: match_spec(),
287      CompiledMatchSpec :: comp_match_spec().
288
289match_spec_compile(_) ->
290    erlang:nif_error(undef).
291
292-spec match_spec_run_r(List, CompiledMatchSpec, list()) -> list() when
293      List :: [term()],
294      CompiledMatchSpec :: comp_match_spec().
295
296match_spec_run_r(_, _, _) ->
297    erlang:nif_error(undef).
298
299-spec member(Tab, Key) -> boolean() when
300      Tab :: tab(),
301      Key :: term().
302
303member(_, _) ->
304    erlang:nif_error(undef).
305
306-spec new(Name, Options) -> tid() | atom() when
307      Name :: atom(),
308      Options :: [Option],
309      Option :: Type | Access | named_table | {keypos,Pos}
310              | {heir, Pid :: pid(), HeirData} | {heir, none} | Tweaks,
311      Type :: type(),
312      Access :: access(),
313      Tweaks :: {write_concurrency, boolean()}
314              | {read_concurrency, boolean()}
315              | {decentralized_counters, boolean()}
316              | compressed,
317      Pos :: pos_integer(),
318      HeirData :: term().
319
320new(_, _) ->
321    erlang:nif_error(undef).
322
323-spec next(Tab, Key1) -> Key2 | '$end_of_table' when
324      Tab :: tab(),
325      Key1 :: term(),
326      Key2 :: term().
327
328next(_, _) ->
329    erlang:nif_error(undef).
330
331-spec prev(Tab, Key1) -> Key2 | '$end_of_table' when
332      Tab :: tab(),
333      Key1 :: term(),
334      Key2 :: term().
335
336prev(_, _) ->
337    erlang:nif_error(undef).
338
339%% Shadowed by erl_bif_types: ets:rename/2
340-spec rename(Tab, Name) -> Name when
341      Tab :: tab(),
342      Name :: atom().
343
344rename(_, _) ->
345    erlang:nif_error(undef).
346
347-spec safe_fixtable(Tab, Fix) -> true when
348      Tab :: tab(),
349      Fix :: boolean().
350
351safe_fixtable(_, _) ->
352    erlang:nif_error(undef).
353
354-spec select(Tab, MatchSpec) -> [Match] when
355      Tab :: tab(),
356      MatchSpec :: match_spec(),
357      Match :: term().
358
359select(_, _) ->
360    erlang:nif_error(undef).
361
362-spec select(Tab, MatchSpec, Limit) -> {[Match],Continuation} |
363                                       '$end_of_table' when
364      Tab :: tab(),
365      MatchSpec :: match_spec(),
366      Limit :: pos_integer(),
367      Match :: term(),
368      Continuation :: continuation().
369
370select(_, _, _) ->
371    erlang:nif_error(undef).
372
373-spec select(Continuation) -> {[Match],Continuation} | '$end_of_table' when
374      Match :: term(),
375      Continuation :: continuation().
376
377select(_) ->
378    erlang:nif_error(undef).
379
380-spec select_count(Tab, MatchSpec) -> NumMatched when
381      Tab :: tab(),
382      MatchSpec :: match_spec(),
383      NumMatched :: non_neg_integer().
384
385select_count(_, _) ->
386    erlang:nif_error(undef).
387
388-spec select_delete(Tab, MatchSpec) -> NumDeleted when
389      Tab :: tab(),
390      MatchSpec :: match_spec(),
391      NumDeleted :: non_neg_integer().
392
393select_delete(Tab, [{'_',[],[true]}]) ->
394    ets:internal_delete_all(Tab, undefined);
395select_delete(Tab, MatchSpec) ->
396    ets:internal_select_delete(Tab, MatchSpec).
397
398-spec internal_select_delete(Tab, MatchSpec) -> NumDeleted when
399      Tab :: tab(),
400      MatchSpec :: match_spec(),
401      NumDeleted :: non_neg_integer().
402
403internal_select_delete(_, _) ->
404    erlang:nif_error(undef).
405
406-spec select_replace(Tab, MatchSpec) -> NumReplaced when
407      Tab :: tab(),
408      MatchSpec :: match_spec(),
409      NumReplaced :: non_neg_integer().
410
411select_replace(_, _) ->
412    erlang:nif_error(undef).
413
414-spec select_reverse(Tab, MatchSpec) -> [Match] when
415      Tab :: tab(),
416      MatchSpec :: match_spec(),
417      Match :: term().
418
419select_reverse(_, _) ->
420    erlang:nif_error(undef).
421
422-spec select_reverse(Tab, MatchSpec, Limit) -> {[Match],Continuation} |
423                                               '$end_of_table' when
424      Tab :: tab(),
425      MatchSpec :: match_spec(),
426      Limit :: pos_integer(),
427      Match :: term(),
428      Continuation :: continuation().
429
430select_reverse(_, _, _) ->
431    erlang:nif_error(undef).
432
433-spec select_reverse(Continuation) -> {[Match],Continuation} |
434                                      '$end_of_table' when
435      Continuation :: continuation(),
436      Match :: term().
437
438select_reverse(_) ->
439    erlang:nif_error(undef).
440
441-spec setopts(Tab, Opts) -> true when
442      Tab :: tab(),
443      Opts :: Opt | [Opt],
444      Opt :: {heir, pid(), HeirData} | {heir,none},
445      HeirData :: term().
446
447setopts(_, _) ->
448    erlang:nif_error(undef).
449
450-spec slot(Tab, I) -> [Object] | '$end_of_table' when
451      Tab :: tab(),
452      I :: non_neg_integer(),
453      Object :: tuple().
454
455slot(_, _) ->
456    erlang:nif_error(undef).
457
458-spec take(Tab, Key) -> [Object] when
459      Tab :: tab(),
460      Key :: term(),
461      Object :: tuple().
462
463take(_, _) ->
464    erlang:nif_error(undef).
465
466-spec update_counter(Tab, Key, UpdateOp) -> Result when
467      Tab :: tab(),
468      Key :: term(),
469      UpdateOp :: {Pos, Incr} | {Pos, Incr, Threshold, SetValue},
470      Pos :: integer(),
471      Incr :: integer(),
472      Threshold :: integer(),
473      SetValue :: integer(),
474      Result :: integer();
475                       (Tab, Key, [UpdateOp]) -> [Result] when
476      Tab :: tab(),
477      Key :: term(),
478      UpdateOp :: {Pos, Incr} | {Pos, Incr, Threshold, SetValue},
479      Pos :: integer(),
480      Incr :: integer(),
481      Threshold :: integer(),
482      SetValue :: integer(),
483      Result :: integer();
484                       (Tab, Key, Incr) -> Result when
485      Tab :: tab(),
486      Key :: term(),
487      Incr :: integer(),
488      Result :: integer().
489
490update_counter(_, _, _) ->
491    erlang:nif_error(undef).
492
493-spec update_counter(Tab, Key, UpdateOp, Default) -> Result when
494                        Tab :: tab(),
495                        Key :: term(),
496                        UpdateOp :: {Pos, Incr}
497                                  | {Pos, Incr, Threshold, SetValue},
498                        Pos :: integer(),
499                        Incr :: integer(),
500                        Threshold :: integer(),
501                        SetValue :: integer(),
502                        Result :: integer(),
503                        Default :: tuple();
504                    (Tab, Key, [UpdateOp], Default) -> [Result] when
505                        Tab :: tab(),
506                        Key :: term(),
507                        UpdateOp :: {Pos, Incr}
508                                  | {Pos, Incr, Threshold, SetValue},
509                        Pos :: integer(),
510                        Incr :: integer(),
511                        Threshold :: integer(),
512                        SetValue :: integer(),
513                        Result :: integer(),
514                        Default :: tuple();
515                    (Tab, Key, Incr, Default) -> Result when
516                        Tab :: tab(),
517                        Key :: term(),
518                        Incr :: integer(),
519                        Result :: integer(),
520                        Default :: tuple().
521
522update_counter(_, _, _, _) ->
523    erlang:nif_error(undef).
524
525-spec update_element(Tab, Key, ElementSpec :: {Pos, Value}) -> boolean() when
526      Tab :: tab(),
527      Key :: term(),
528      Pos :: pos_integer(),
529      Value :: term();
530                       (Tab, Key, ElementSpec :: [{Pos, Value}]) -> boolean() when
531      Tab :: tab(),
532      Key :: term(),
533      Pos :: pos_integer(),
534      Value :: term().
535
536update_element(_, _, _) ->
537    erlang:nif_error(undef).
538
539-spec whereis(TableName) -> tid() | undefined when
540    TableName :: atom().
541whereis(_) ->
542    erlang:nif_error(undef).
543
544%%% End of BIFs
545
546-opaque comp_match_spec() :: reference().
547
548-spec match_spec_run(List, CompiledMatchSpec) -> list() when
549      List :: [term()],
550      CompiledMatchSpec :: comp_match_spec().
551
552match_spec_run(List, CompiledMS) ->
553    lists:reverse(ets:match_spec_run_r(List, CompiledMS, [])).
554
555-spec repair_continuation(Continuation, MatchSpec) -> Continuation when
556      Continuation :: continuation(),
557      MatchSpec :: match_spec().
558
559%% $end_of_table is an allowed continuation in ets...
560repair_continuation('$end_of_table', _) ->
561    '$end_of_table';
562%% ordered_set
563repair_continuation(Untouched = {Table,Lastkey,EndCondition,N2,MSRef,L2,N3,N4}, MS)
564  when %% (is_atom(Table) or is_integer(Table)),
565       is_integer(N2),
566      %% is_reference(MSRef),
567       is_list(L2),
568       is_integer(N3),
569       is_integer(N4) ->
570    case ets:is_compiled_ms(MSRef) of
571	true ->
572	    Untouched;
573	false ->
574	    {Table,Lastkey,EndCondition,N2,ets:match_spec_compile(MS),L2,N3,N4}
575    end;
576%% set/bag/duplicate_bag
577repair_continuation(Untouched = {Table,N1,N2,MSRef,L,N3}, MS)
578  when %% (is_atom(Table) or is_integer(Table)),
579       is_integer(N1),
580       is_integer(N2),
581      %% is_reference(MSRef),
582       is_list(L),
583       is_integer(N3) ->
584    case ets:is_compiled_ms(MSRef) of
585	true ->
586	    Untouched;
587	false ->
588	    {Table,N1,N2,ets:match_spec_compile(MS),L,N3}
589    end.
590
591-spec fun2ms(LiteralFun) -> MatchSpec when
592      LiteralFun :: function(),
593      MatchSpec :: match_spec().
594
595fun2ms(ShellFun) when is_function(ShellFun) ->
596    %% Check that this is really a shell fun...
597    case erl_eval:fun_data(ShellFun) of
598        {fun_data,ImportList,Clauses} ->
599            case ms_transform:transform_from_shell(
600                   ?MODULE,Clauses,ImportList) of
601                {error,[{_,[{_,_,Code}|_]}|_],_} ->
602                    io:format("Error: ~ts~n",
603                              [ms_transform:format_error(Code)]),
604                    {error,transform_error};
605                Else ->
606                    Else
607            end;
608        _ ->
609            exit({badarg,{?MODULE,fun2ms,
610                          [function,called,with,real,'fun',
611                           should,be,transformed,with,
612                           parse_transform,'or',called,with,
613                           a,'fun',generated,in,the,
614                           shell]}})
615    end.
616
617-spec foldl(Function, Acc0, Tab) -> Acc1 when
618      Function :: fun((Element :: term(), AccIn) -> AccOut),
619      Tab :: tab(),
620      Acc0 :: term(),
621      Acc1 :: term(),
622      AccIn :: term(),
623      AccOut :: term().
624
625foldl(F, Accu, T) ->
626    ets:safe_fixtable(T, true),
627    First = ets:first(T),
628    try
629        do_foldl(F, Accu, First, T)
630    after
631	ets:safe_fixtable(T, false)
632    end.
633
634do_foldl(F, Accu0, Key, T) ->
635    case Key of
636	'$end_of_table' ->
637	    Accu0;
638	_ ->
639	    do_foldl(F,
640		     lists:foldl(F, Accu0, ets:lookup(T, Key)),
641		     ets:next(T, Key), T)
642    end.
643
644-spec foldr(Function, Acc0, Tab) -> Acc1 when
645      Function :: fun((Element :: term(), AccIn) -> AccOut),
646      Tab :: tab(),
647      Acc0 :: term(),
648      Acc1 :: term(),
649      AccIn :: term(),
650      AccOut :: term().
651
652foldr(F, Accu, T) ->
653    ets:safe_fixtable(T, true),
654    Last = ets:last(T),
655    try
656        do_foldr(F, Accu, Last, T)
657    after
658        ets:safe_fixtable(T, false)
659    end.
660
661do_foldr(F, Accu0, Key, T) ->
662    case Key of
663	'$end_of_table' ->
664	    Accu0;
665	_ ->
666	    do_foldr(F,
667		     lists:foldr(F, Accu0, ets:lookup(T, Key)),
668		     ets:prev(T, Key), T)
669    end.
670
671-spec from_dets(Tab, DetsTab) -> 'true' when
672      Tab :: tab(),
673      DetsTab :: dets:tab_name().
674
675from_dets(EtsTable, DetsTable) ->
676    case (catch dets:to_ets(DetsTable, EtsTable)) of
677	{error, Reason} ->
678	    erlang:error(Reason, [EtsTable,DetsTable]);
679	{'EXIT', {Reason1, _Stack1}} ->
680	    erlang:error(Reason1,[EtsTable,DetsTable]);
681	{'EXIT', EReason} ->
682	    erlang:error(EReason,[EtsTable,DetsTable]);
683	EtsTable ->
684	    true;
685	Unexpected -> %% Dets bug?
686	    erlang:error(Unexpected,[EtsTable,DetsTable])
687    end.
688
689-spec to_dets(Tab, DetsTab) -> DetsTab when
690      Tab :: tab(),
691      DetsTab :: dets:tab_name().
692
693to_dets(EtsTable, DetsTable) ->
694    case (catch dets:from_ets(DetsTable, EtsTable)) of
695	{error, Reason} ->
696	    erlang:error(Reason, [EtsTable,DetsTable]);
697	{'EXIT', {Reason1, _Stack1}} ->
698	    erlang:error(Reason1,[EtsTable,DetsTable]);
699	{'EXIT', EReason} ->
700	    erlang:error(EReason,[EtsTable,DetsTable]);
701	ok ->
702	    DetsTable;
703	Unexpected -> %% Dets bug?
704	    erlang:error(Unexpected,[EtsTable,DetsTable])
705    end.
706
707-spec test_ms(Tuple, MatchSpec) -> {'ok', Result} | {'error', Errors} when
708      Tuple :: tuple(),
709      MatchSpec :: match_spec(),
710      Result :: term(),
711      Errors :: [{'warning'|'error', string()}].
712
713test_ms(Term, MS) ->
714    case erlang:match_spec_test(Term, MS, table) of
715	{ok, Result, _Flags, _Messages} ->
716	    {ok, Result};
717	{error, _Errors} = Error ->
718	    Error
719    end.
720
721-spec init_table(Tab, InitFun) -> 'true' when
722      Tab :: tab(),
723      InitFun :: fun((Arg) -> Res),
724      Arg :: 'read' | 'close',
725      Res :: 'end_of_input' | {Objects :: [term()], InitFun} | term().
726
727init_table(Table, Fun) ->
728    ets:delete_all_objects(Table),
729    init_table_continue(Table, Fun(read)).
730
731init_table_continue(_Table, end_of_input) ->
732    true;
733init_table_continue(Table, {List, Fun}) when is_list(List), is_function(Fun) ->
734    case (catch init_table_sub(Table, List)) of
735	{'EXIT', Reason} ->
736	    (catch Fun(close)),
737	    exit(Reason);
738	true ->
739	    init_table_continue(Table, Fun(read))
740    end;
741init_table_continue(_Table, Error) ->
742    exit(Error).
743
744init_table_sub(_Table, []) ->
745    true;
746init_table_sub(Table, [H|T]) ->
747    ets:insert(Table, H),
748    init_table_sub(Table, T).
749
750-spec match_delete(Tab, Pattern) -> 'true' when
751      Tab :: tab(),
752      Pattern :: match_pattern().
753
754match_delete(Table, Pattern) ->
755    ets:select_delete(Table, [{Pattern,[],[true]}]),
756    true.
757
758%% Produce a list of tuples from a table
759
760-spec tab2list(Tab) -> [Object] when
761      Tab :: tab(),
762      Object :: tuple().
763
764tab2list(T) ->
765    ets:match_object(T, '_').
766
767-spec filter(tab(), function(), [term()]) -> [term()].
768
769filter(Tn, F, A) when is_atom(Tn) ; is_integer(Tn) ->
770    do_filter(Tn, ets:first(Tn), F, A, []).
771
772do_filter(_Tab, '$end_of_table', _, _, Ack) ->
773    Ack;
774do_filter(Tab, Key, F, A, Ack) ->
775    case apply(F, [ets:lookup(Tab, Key)|A]) of
776	false ->
777	    do_filter(Tab, ets:next(Tab, Key), F, A, Ack);
778	true ->
779            Ack2 = ets:lookup(Tab, Key) ++ Ack,
780	    do_filter(Tab, ets:next(Tab, Key), F, A, Ack2);
781	{true, Value} ->
782	    do_filter(Tab, ets:next(Tab, Key), F, A, [Value|Ack])
783    end.
784
785
786%% Dump a table to a file using the disk_log facility
787
788%% Options := [Option]
789%% Option := {extended_info,[ExtInfo]}
790%% ExtInfo := object_count | md5sum
791
792-define(MAJOR_F2T_VERSION,1).
793-define(MINOR_F2T_VERSION,0).
794
795-record(filetab_options,
796	{
797	  object_count = false :: boolean(),
798	  md5sum       = false :: boolean(),
799	  sync         = false :: boolean()
800	 }).
801
802-spec tab2file(Tab, Filename) -> 'ok' | {'error', Reason} when
803      Tab :: tab(),
804      Filename :: file:name(),
805      Reason :: term().
806
807tab2file(Tab, File) ->
808    tab2file(Tab, File, []).
809
810-spec tab2file(Tab, Filename, Options) -> 'ok' | {'error', Reason} when
811      Tab :: tab(),
812      Filename :: file:name(),
813      Options :: [Option],
814      Option :: {'extended_info', [ExtInfo]} | {'sync', boolean()},
815      ExtInfo :: 'md5sum' | 'object_count',
816      Reason :: term().
817
818tab2file(Tab, File, Options) ->
819    try
820	{ok, FtOptions} = parse_ft_options(Options),
821	_ = file:delete(File),
822	case file:read_file_info(File) of
823	    {error, enoent} -> ok;
824	    _ -> throw(eaccess)
825	end,
826	Name = make_ref(),
827	case disk_log:open([{name, Name}, {file, File}]) of
828	    {ok, Name} ->
829		ok;
830	    {error, Reason} ->
831		throw(Reason)
832	end,
833	try
834	    Info0 = case ets:info(Tab) of
835		       undefined ->
836			   %% erlang:error(badarg, [Tab, File, Options]);
837			   throw(badtab);
838		       I ->
839			   I
840	    end,
841	    Info = [list_to_tuple(Info0 ++
842				  [{major_version,?MAJOR_F2T_VERSION},
843				   {minor_version,?MINOR_F2T_VERSION},
844				   {extended_info,
845				    ft_options_to_list(FtOptions)}])],
846	    {LogFun, InitState} =
847	    case FtOptions#filetab_options.md5sum of
848		true ->
849		    {fun(Oldstate,Termlist) ->
850			     {NewState,BinList} =
851				 md5terms(Oldstate,Termlist),
852                             case disk_log:blog_terms(Name,BinList) of
853                                 ok -> NewState;
854                                 {error, Reason2} -> throw(Reason2)
855                             end
856		     end,
857		     erlang:md5_init()};
858		false ->
859		    {fun(_,Termlist) ->
860                             case disk_log:log_terms(Name,Termlist) of
861                                 ok -> true;
862                                 {error, Reason2} -> throw(Reason2)
863                             end
864		     end,
865		     true}
866	    end,
867	    ets:safe_fixtable(Tab,true),
868	    {NewState1,Num} = try
869				  NewState = LogFun(InitState,Info),
870				  dump_file(
871				      ets:select(Tab,[{'_',[],['$_']}],100),
872				      LogFun, NewState, 0)
873			      after
874				  (catch ets:safe_fixtable(Tab,false))
875			      end,
876	    EndInfo =
877	    case  FtOptions#filetab_options.object_count of
878		true ->
879		    [{count,Num}];
880		false ->
881		    []
882	    end ++
883	    case  FtOptions#filetab_options.md5sum of
884		true ->
885		    [{md5,erlang:md5_final(NewState1)}];
886		false ->
887		    []
888	    end,
889	    case EndInfo of
890		[] ->
891		    ok;
892		List ->
893		    LogFun(NewState1,[['$end_of_table',List]])
894	    end,
895	    case FtOptions#filetab_options.sync of
896	        true ->
897		    case disk_log:sync(Name) of
898		        ok -> ok;
899			{error, Reason2} -> throw(Reason2)
900		    end;
901                false ->
902		    ok
903            end,
904	    disk_log:close(Name)
905	catch
906	    throw:TReason ->
907		_ = disk_log:close(Name),
908		_ = file:delete(File),
909		throw(TReason);
910	    exit:ExReason ->
911		_ = disk_log:close(Name),
912		_ = file:delete(File),
913		exit(ExReason);
914	    error:ErReason:StackTrace ->
915		_ = disk_log:close(Name),
916		_ = file:delete(File),
917	        erlang:raise(error,ErReason,StackTrace)
918	end
919    catch
920	throw:TReason2 ->
921	    {error,TReason2};
922	exit:ExReason2 ->
923	    {error,ExReason2}
924    end.
925
926dump_file('$end_of_table', _LogFun, State, Num) ->
927    {State,Num};
928dump_file({Terms, Context}, LogFun, State, Num) ->
929    Count = length(Terms),
930    NewState = LogFun(State, Terms),
931    dump_file(ets:select(Context), LogFun, NewState, Num + Count).
932
933ft_options_to_list(#filetab_options{md5sum = MD5, object_count = PS}) ->
934    case PS of
935	true ->
936	    [object_count];
937	_ ->
938	    []
939    end ++
940	case MD5 of
941	    true ->
942		[md5sum];
943	    _ ->
944		[]
945	end.
946
947md5terms(State, []) ->
948    {State, []};
949md5terms(State, [H|T]) ->
950    B = term_to_binary(H),
951    NewState = erlang:md5_update(State, B),
952    {FinState, TL} = md5terms(NewState, T),
953    {FinState, [B|TL]}.
954
955parse_ft_options(Options) when is_list(Options) ->
956    {ok, parse_ft_options(Options, #filetab_options{}, false)}.
957
958parse_ft_options([], FtOpt, _) ->
959    FtOpt;
960parse_ft_options([{sync,true} | Rest], FtOpt, EI) ->
961    parse_ft_options(Rest, FtOpt#filetab_options{sync = true}, EI);
962parse_ft_options([{sync,false} | Rest], FtOpt, EI) ->
963    parse_ft_options(Rest, FtOpt, EI);
964parse_ft_options([{extended_info,L} | Rest], FtOpt0, false) ->
965    FtOpt1 = parse_ft_info_options(FtOpt0, L),
966    parse_ft_options(Rest, FtOpt1, true);
967parse_ft_options([Other | _], _, _) ->
968    throw({unknown_option, Other});
969parse_ft_options(Malformed, _, _) ->
970    throw({malformed_option, Malformed}).
971
972parse_ft_info_options(FtOpt,[]) ->
973    FtOpt;
974parse_ft_info_options(FtOpt,[object_count | T]) ->
975    parse_ft_info_options(FtOpt#filetab_options{object_count = true}, T);
976parse_ft_info_options(FtOpt,[md5sum | T]) ->
977    parse_ft_info_options(FtOpt#filetab_options{md5sum = true}, T);
978parse_ft_info_options(_,[Unexpected | _]) ->
979    throw({unknown_option,[{extended_info,[Unexpected]}]});
980parse_ft_info_options(_,Malformed) ->
981    throw({malformed_option,Malformed}).
982
983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
984%% Read a dumped file from disk and create a corresponding table
985%% Opts := [Opt]
986%% Opt := {verify,boolean()}
987%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988
989-spec file2tab(Filename) -> {'ok', Tab} | {'error', Reason} when
990      Filename :: file:name(),
991      Tab :: tab(),
992      Reason :: term().
993
994file2tab(File) ->
995    file2tab(File, []).
996
997-spec file2tab(Filename, Options) -> {'ok', Tab} | {'error', Reason} when
998      Filename :: file:name(),
999      Tab :: tab(),
1000      Options :: [Option],
1001      Option :: {'verify', boolean()},
1002      Reason :: term().
1003
1004file2tab(File, Opts) ->
1005    try
1006	{ok,Verify,TabArg} = parse_f2t_opts(Opts,false,[]),
1007	Name = make_ref(),
1008        {ok, Name} =
1009	    case disk_log:open([{name, Name},
1010				{file, File},
1011				{mode, read_only}]) of
1012		{ok, Name} ->
1013                    {ok, Name};
1014		{repaired, Name, _,_} -> %Uh? cannot happen?
1015		    case Verify of
1016			true ->
1017			    _ = disk_log:close(Name),
1018			    throw(badfile);
1019			false ->
1020                            {ok, Name}
1021		    end;
1022		{error, Other1} ->
1023		    throw({read_error, Other1});
1024		Other2 ->
1025		    throw(Other2)
1026	    end,
1027	{ok, Major, Minor, FtOptions, MD5State, FullHeader, DLContext} =
1028            try get_header_data(Name, Verify)
1029            catch
1030                badfile ->
1031                    _ = disk_log:close(Name),
1032                    throw(badfile)
1033            end,
1034	try
1035	    if
1036		Major > ?MAJOR_F2T_VERSION ->
1037		    throw({unsupported_file_version,{Major,Minor}});
1038		true ->
1039		    ok
1040	    end,
1041	    {ok, Tab, HeadCount} = create_tab(FullHeader, TabArg),
1042	    StrippedOptions =
1043	        case Verify of
1044		    true ->
1045			FtOptions;
1046		    false ->
1047			#filetab_options{}
1048		end,
1049	    {ReadFun,InitState} =
1050	        case StrippedOptions#filetab_options.md5sum of
1051		    true ->
1052			{fun({OldMD5State,OldCount,_OL,ODLContext} = OS) ->
1053				 case wrap_bchunk(Name,ODLContext,100,Verify) of
1054				     eof ->
1055					 {OS,[]};
1056				     {NDLContext,Blist} ->
1057					 {Termlist, NewMD5State,
1058					  NewCount,NewLast} =
1059					     md5_and_convert(Blist,
1060							     OldMD5State,
1061							     OldCount),
1062					 {{NewMD5State, NewCount,
1063					   NewLast,NDLContext},
1064					  Termlist}
1065				 end
1066			 end,
1067			 {MD5State,0,[],DLContext}};
1068		    false ->
1069			{fun({_,OldCount,_OL,ODLContext} = OS) ->
1070				 case wrap_chunk(Name,ODLContext,100,Verify) of
1071				     eof ->
1072					 {OS,[]};
1073				     {NDLContext,List} ->
1074					 {NewLast,NewCount,NewList} =
1075					     scan_for_endinfo(List, OldCount),
1076					 {{false,NewCount,NewLast,NDLContext},
1077					  NewList}
1078				 end
1079			 end,
1080			 {false,0,[],DLContext}}
1081		end,
1082	    try
1083		do_read_and_verify(ReadFun,InitState,Tab,
1084				   StrippedOptions,HeadCount,Verify)
1085	    catch
1086		throw:TReason ->
1087		    ets:delete(Tab),
1088		    throw(TReason);
1089		exit:ExReason ->
1090		    ets:delete(Tab),
1091		    exit(ExReason);
1092		error:ErReason:StackTrace ->
1093		    ets:delete(Tab),
1094		    erlang:raise(error,ErReason,StackTrace)
1095	    end
1096	after
1097	    _ = disk_log:close(Name)
1098	end
1099    catch
1100	throw:TReason2 ->
1101	    {error,TReason2};
1102	exit:ExReason2 ->
1103	    {error,ExReason2}
1104    end.
1105
1106do_read_and_verify(ReadFun,InitState,Tab,FtOptions,HeadCount,Verify) ->
1107    case load_table(ReadFun,InitState,Tab) of
1108	{ok,{_,FinalCount,[],_}} ->
1109	    case {FtOptions#filetab_options.md5sum,
1110		  FtOptions#filetab_options.object_count} of
1111		{false,false} ->
1112		    case Verify of
1113			false ->
1114			    ok;
1115			true ->
1116			    case FinalCount of
1117				HeadCount ->
1118				    ok;
1119				_ ->
1120				    throw(invalid_object_count)
1121			    end
1122		    end;
1123		_ ->
1124		    throw(badfile)
1125	    end,
1126	    {ok,Tab};
1127	{ok,{FinalMD5State,FinalCount,['$end_of_table',LastInfo],_}} ->
1128	    ECount = case lists:keyfind(count,1,LastInfo) of
1129			 {count,N} ->
1130			     N;
1131			 _ ->
1132			     false
1133		     end,
1134	    EMD5 = case lists:keyfind(md5,1,LastInfo) of
1135			 {md5,M} ->
1136			     M;
1137			 _ ->
1138			     false
1139		     end,
1140	    case FtOptions#filetab_options.md5sum of
1141		true ->
1142		    case erlang:md5_final(FinalMD5State) of
1143			EMD5 ->
1144			    ok;
1145			_MD5MisM ->
1146			    throw(checksum_error)
1147		    end;
1148		false ->
1149		    ok
1150	    end,
1151	    case FtOptions#filetab_options.object_count of
1152		true ->
1153		    case FinalCount of
1154			ECount ->
1155			    ok;
1156			_Other ->
1157			    throw(invalid_object_count)
1158		    end;
1159		false ->
1160		    %% Only use header count if no extended info
1161		    %% at all is present and verification is requested.
1162		    case {Verify,FtOptions#filetab_options.md5sum} of
1163			{true,false} ->
1164			    case FinalCount of
1165				HeadCount ->
1166				    ok;
1167				_Other2 ->
1168				     throw(invalid_object_count)
1169			    end;
1170			_ ->
1171			    ok
1172		    end
1173	    end,
1174	    {ok,Tab}
1175    end.
1176
1177parse_f2t_opts([],Verify,Tab) ->
1178    {ok,Verify,Tab};
1179parse_f2t_opts([{verify, true}|T],_OV,Tab) ->
1180    parse_f2t_opts(T,true,Tab);
1181parse_f2t_opts([{verify,false}|T],OV,Tab) ->
1182    parse_f2t_opts(T,OV,Tab);
1183parse_f2t_opts([{table,Tab}|T],OV,[]) ->
1184    parse_f2t_opts(T,OV,Tab);
1185parse_f2t_opts([Unexpected|_],_,_) ->
1186    throw({unknown_option,Unexpected});
1187parse_f2t_opts(Malformed,_,_) ->
1188    throw({malformed_option,Malformed}).
1189
1190count_mandatory([]) ->
1191    0;
1192count_mandatory([{Tag,_}|T]) when Tag =:= name;
1193				  Tag =:= type;
1194				  Tag =:= protection;
1195				  Tag =:= named_table;
1196				  Tag =:= keypos;
1197				  Tag =:= size ->
1198    1+count_mandatory(T);
1199count_mandatory([_|T]) ->
1200    count_mandatory(T).
1201
1202verify_header_mandatory(L) ->
1203    count_mandatory(L) =:= 6.
1204
1205wrap_bchunk(Name,C,N,true) ->
1206    case disk_log:bchunk(Name,C,N) of
1207	{_,_,X} when X > 0 ->
1208	    throw(badfile);
1209	{NC,Bin,_} ->
1210	    {NC,Bin};
1211	Y ->
1212	    Y
1213    end;
1214wrap_bchunk(Name,C,N,false) ->
1215    case disk_log:bchunk(Name,C,N) of
1216	{NC,Bin,_} ->
1217	    {NC,Bin};
1218	Y ->
1219	    Y
1220    end.
1221
1222wrap_chunk(Name,C,N,true) ->
1223    case disk_log:chunk(Name,C,N) of
1224	{_,_,X} when X > 0 ->
1225	    throw(badfile);
1226	{NC,TL,_} ->
1227	    {NC,TL};
1228	Y ->
1229	    Y
1230    end;
1231wrap_chunk(Name,C,N,false) ->
1232    case disk_log:chunk(Name,C,N) of
1233	{NC,TL,_} ->
1234	    {NC,TL};
1235	Y ->
1236	    Y
1237    end.
1238
1239get_header_data(Name,true) ->
1240    case wrap_bchunk(Name,start,1,true) of
1241	{C,[Bin]} when is_binary(Bin) ->
1242	    T = binary_to_term(Bin),
1243	    case T of
1244		Tup when is_tuple(Tup) ->
1245		    L = tuple_to_list(Tup),
1246		    case verify_header_mandatory(L) of
1247			false ->
1248			    throw(badfile);
1249			true ->
1250			    Major = case lists:keyfind(major,1,L) of
1251					{major,Maj} ->
1252					    Maj;
1253					_ ->
1254					    0
1255				    end,
1256			    Minor = case lists:keyfind(minor,1,L) of
1257					{minor,Min} ->
1258					    Min;
1259					_ ->
1260					    0
1261				    end,
1262			    FtOptions =
1263				case lists:keyfind(extended_info,1,L) of
1264				    {extended_info,I} when is_list(I) ->
1265					#filetab_options
1266					    {
1267					    object_count =
1268					      lists:member(object_count,I),
1269					    md5sum =
1270					      lists:member(md5sum,I)
1271					    };
1272				    _ ->
1273					#filetab_options{}
1274				end,
1275			    MD5Initial =
1276				case FtOptions#filetab_options.md5sum of
1277				    true ->
1278					X = erlang:md5_init(),
1279					erlang:md5_update(X,Bin);
1280				    false ->
1281					false
1282				end,
1283			    {ok, Major, Minor, FtOptions, MD5Initial, L, C}
1284		    end;
1285		_X ->
1286		    throw(badfile)
1287	    end;
1288	_Y ->
1289	    throw(badfile)
1290    end;
1291
1292get_header_data(Name, false) ->
1293   case wrap_chunk(Name, start, 1, false) of
1294       {C,[Tup]} when is_tuple(Tup) ->
1295	   L = tuple_to_list(Tup),
1296	   case verify_header_mandatory(L) of
1297	       false ->
1298		   throw(badfile);
1299	       true ->
1300		   Major = case lists:keyfind(major_version, 1, L) of
1301			       {major_version, Maj} ->
1302				   Maj;
1303			       _ ->
1304				   0
1305			   end,
1306		   Minor = case lists:keyfind(minor_version, 1, L) of
1307			       {minor_version, Min} ->
1308				   Min;
1309			       _ ->
1310				   0
1311			   end,
1312		   FtOptions =
1313		       case lists:keyfind(extended_info, 1, L) of
1314			   {extended_info, I} when is_list(I) ->
1315			       #filetab_options
1316					 {
1317					 object_count =
1318					 lists:member(object_count,I),
1319					 md5sum =
1320					 lists:member(md5sum,I)
1321					};
1322			   _ ->
1323			       #filetab_options{}
1324		       end,
1325		   {ok, Major, Minor, FtOptions, false, L, C}
1326	   end;
1327       _ ->
1328	   throw(badfile)
1329    end.
1330
1331md5_and_convert([], MD5State, Count) ->
1332    {[],MD5State,Count,[]};
1333md5_and_convert([H|T], MD5State, Count) when is_binary(H) ->
1334    case (catch binary_to_term(H)) of
1335	{'EXIT', _} ->
1336	    md5_and_convert(T,MD5State,Count);
1337	['$end_of_table',_Dat] = L ->
1338	   {[],MD5State,Count,L};
1339	Term ->
1340	    X = erlang:md5_update(MD5State, H),
1341	    {Rest,NewMD5,NewCount,NewLast} = md5_and_convert(T, X, Count+1),
1342	    {[Term | Rest],NewMD5,NewCount,NewLast}
1343    end.
1344
1345scan_for_endinfo([], Count) ->
1346    {[],Count,[]};
1347scan_for_endinfo([['$end_of_table',Dat]], Count) ->
1348    {['$end_of_table',Dat],Count,[]};
1349scan_for_endinfo([Term|T], Count) ->
1350    {NewLast,NCount,Rest} = scan_for_endinfo(T, Count+1),
1351    {NewLast,NCount,[Term | Rest]}.
1352
1353load_table(ReadFun, State, Tab) ->
1354    {NewState,NewData} = ReadFun(State),
1355    case NewData of
1356	[] ->
1357	    {ok,NewState};
1358	List ->
1359	    ets:insert(Tab, List),
1360	    load_table(ReadFun, NewState, Tab)
1361    end.
1362
1363create_tab(I, TabArg) ->
1364    {name, Name} = lists:keyfind(name, 1, I),
1365    {type, Type} = lists:keyfind(type, 1, I),
1366    {protection, P} = lists:keyfind(protection, 1, I),
1367    {keypos, _Kp} = Keypos = lists:keyfind(keypos, 1, I),
1368    {size, Sz} = lists:keyfind(size, 1, I),
1369    L1 = [Type, P, Keypos],
1370    L2 = case lists:keyfind(named_table, 1, I) of
1371             {named_table, true} -> [named_table | L1];
1372	     {named_table, false} -> L1
1373	 end,
1374    L3 = case lists:keyfind(compressed, 1, I) of
1375	     {compressed, true} -> [compressed | L2];
1376	     {compressed, false} -> L2;
1377	     false -> L2
1378	 end,
1379    L4 = case lists:keyfind(write_concurrency, 1, I) of
1380	     {write_concurrency, _}=Wcc -> [Wcc | L3];
1381	     _ -> L3
1382	 end,
1383    L5 = case lists:keyfind(read_concurrency, 1, I) of
1384	     {read_concurrency, _}=Rcc -> [Rcc | L4];
1385	     false -> L4
1386	 end,
1387    case TabArg of
1388        [] ->
1389	    try
1390		Tab = ets:new(Name, L5),
1391		{ok, Tab, Sz}
1392	    catch _:_ ->
1393		throw(cannot_create_table)
1394            end;
1395        _ ->
1396            {ok, TabArg, Sz}
1397    end.
1398
1399
1400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1401%% tabfile_info/1 reads the head information in an ets table dumped to
1402%% disk by means of file2tab and returns a list of the relevant table
1403%% information
1404%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1405
1406-spec tabfile_info(Filename) -> {'ok', TableInfo} | {'error', Reason} when
1407      Filename :: file:name(),
1408      TableInfo :: [InfoItem],
1409      InfoItem :: {'name', atom()}
1410                | {'type', Type}
1411                | {'protection', Protection}
1412                | {'named_table', boolean()}
1413                | {'keypos', non_neg_integer()}
1414                | {'size', non_neg_integer()}
1415                | {'extended_info', [ExtInfo]}
1416                | {'version', {Major :: non_neg_integer(),
1417                               Minor :: non_neg_integer()}},
1418      ExtInfo :: 'md5sum' | 'object_count',
1419      Type :: 'bag' | 'duplicate_bag' | 'ordered_set' | 'set',
1420      Protection :: 'private' | 'protected' | 'public',
1421      Reason :: term().
1422
1423tabfile_info(File) when is_list(File) ; is_atom(File) ->
1424    try
1425	Name = make_ref(),
1426        {ok, Name} =
1427	    case disk_log:open([{name, Name},
1428				{file, File},
1429				{mode, read_only}]) of
1430		{ok, Name} ->
1431                    {ok, Name};
1432		{repaired, Name, _,_} -> %Uh? cannot happen?
1433		    {ok, Name};
1434		{error, Other1} ->
1435		    throw({read_error, Other1});
1436		Other2 ->
1437		    throw(Other2)
1438	    end,
1439	{ok, Major, Minor, _FtOptions, _MD5State, FullHeader, _DLContext} =
1440            try get_header_data(Name, false)
1441            catch
1442                badfile ->
1443                    _ = disk_log:close(Name),
1444                    throw(badfile)
1445            end,
1446        case disk_log:close(Name) of
1447            ok -> ok;
1448            {error, Reason} -> throw(Reason)
1449        end,
1450	{value, N} = lists:keysearch(name, 1, FullHeader),
1451	{value, Type} = lists:keysearch(type, 1, FullHeader),
1452	{value, P} = lists:keysearch(protection, 1, FullHeader),
1453	{value, Val} = lists:keysearch(named_table, 1, FullHeader),
1454	{value, Kp} = lists:keysearch(keypos, 1, FullHeader),
1455	{value, Sz} = lists:keysearch(size, 1, FullHeader),
1456	Ei = case lists:keyfind(extended_info, 1, FullHeader) of
1457		 false -> {extended_info, []};
1458		 Ei0 -> Ei0
1459	     end,
1460	{ok, [N,Type,P,Val,Kp,Sz,Ei,{version,{Major,Minor}}]}
1461    catch
1462	throw:TReason ->
1463	    {error,TReason};
1464	exit:ExReason ->
1465	    {error,ExReason}
1466    end.
1467
1468-spec table(Tab) -> QueryHandle when
1469      Tab :: tab(),
1470      QueryHandle :: qlc:query_handle().
1471
1472table(Tab) ->
1473    table(Tab, []).
1474
1475-spec table(Tab, Options) -> QueryHandle when
1476      Tab :: tab(),
1477      QueryHandle :: qlc:query_handle(),
1478      Options :: [Option] | Option,
1479      Option :: {'n_objects', NObjects}
1480              | {'traverse', TraverseMethod},
1481      NObjects :: 'default' | pos_integer(),
1482      TraverseMethod :: 'first_next' | 'last_prev'
1483                      | 'select' | {'select', MatchSpec :: match_spec()}.
1484
1485table(Tab, Opts) ->
1486    case options(Opts, [traverse, n_objects]) of
1487        {badarg,_} ->
1488            erlang:error(badarg, [Tab, Opts]);
1489        [[Traverse, NObjs], QlcOptions] ->
1490            TF = case Traverse of
1491                     first_next ->
1492                         fun() -> qlc_next(Tab, ets:first(Tab)) end;
1493                     last_prev ->
1494                         fun() -> qlc_prev(Tab, ets:last(Tab)) end;
1495                     select ->
1496                         fun(MS) -> qlc_select(ets:select(Tab, MS, NObjs)) end;
1497                     {select, MS} ->
1498                         fun() -> qlc_select(ets:select(Tab, MS, NObjs)) end
1499                 end,
1500            PreFun = fun(_) -> ets:safe_fixtable(Tab, true) end,
1501            PostFun = fun() -> ets:safe_fixtable(Tab, false) end,
1502            InfoFun = fun(Tag) -> table_info(Tab, Tag) end,
1503            KeyEquality = case ets:info(Tab, type) of
1504                              ordered_set -> '==';
1505                              _ -> '=:='
1506                          end,
1507            LookupFun =
1508                case Traverse of
1509                    {select, _MS} ->
1510                        undefined;
1511                    _ ->
1512                        fun(_Pos, [K]) ->
1513                                ets:lookup(Tab, K);
1514                           (_Pos, Ks) ->
1515                                lists:flatmap(fun(K) -> ets:lookup(Tab, K)
1516                                              end, Ks)
1517                        end
1518                end,
1519            FormatFun =
1520                fun({all, _NElements, _ElementFun}) ->
1521                        As = [Tab | [Opts || _ <- [[]], Opts =/= []]],
1522                        {?MODULE, table, As};
1523                   ({match_spec, MS}) ->
1524                        {?MODULE, table,
1525                         [Tab, [{traverse, {select, MS}} |
1526                                listify(Opts)]]};
1527                   ({lookup, _KeyPos, [Value], _NElements, ElementFun}) ->
1528                        io_lib:format("~w:lookup(~w, ~w)",
1529                                      [?MODULE, Tab, ElementFun(Value)]);
1530                   ({lookup, _KeyPos, Values, _NElements, ElementFun}) ->
1531                        Vals = [ElementFun(V) || V <- Values],
1532                        io_lib:format("lists:flatmap(fun(V) -> "
1533                                      "~w:lookup(~w, V) end, ~w)",
1534                                      [?MODULE, Tab, Vals])
1535                end,
1536            qlc:table(TF, [{pre_fun, PreFun}, {post_fun, PostFun},
1537                           {info_fun, InfoFun}, {format_fun, FormatFun},
1538                           {key_equality, KeyEquality},
1539                           {lookup_fun, LookupFun}] ++ QlcOptions)
1540    end.
1541
1542table_info(Tab, num_of_objects) ->
1543    ets:info(Tab, size);
1544table_info(Tab, keypos) ->
1545    ets:info(Tab, keypos);
1546table_info(Tab, is_unique_objects) ->
1547    ets:info(Tab, type) =/= duplicate_bag;
1548table_info(Tab, is_sorted_key) ->
1549    ets:info(Tab, type) =:= ordered_set;
1550table_info(_Tab, _) ->
1551    undefined.
1552
1553qlc_next(_Tab, '$end_of_table') ->
1554    [];
1555qlc_next(Tab, Key) ->
1556    ets:lookup(Tab, Key) ++ fun() -> qlc_next(Tab, ets:next(Tab, Key)) end.
1557
1558qlc_prev(_Tab, '$end_of_table') ->
1559    [];
1560qlc_prev(Tab, Key) ->
1561    ets:lookup(Tab, Key) ++ fun() -> qlc_prev(Tab, ets:prev(Tab, Key)) end.
1562
1563qlc_select('$end_of_table') ->
1564    [];
1565qlc_select({Objects, Cont}) ->
1566    Objects ++ fun() -> qlc_select(ets:select(Cont)) end.
1567
1568options(Options, Keys) when is_list(Options) ->
1569    options(Options, Keys, []);
1570options(Option, Keys) ->
1571    options([Option], Keys, []).
1572
1573options(Options, [Key | Keys], L) when is_list(Options) ->
1574    V = case lists:keyfind(Key, 1, Options) of
1575            {n_objects, default} ->
1576                {ok, default_option(Key)};
1577            {n_objects, NObjs} when is_integer(NObjs), NObjs >= 1 ->
1578                {ok, NObjs};
1579            {traverse, select} ->
1580                {ok, select};
1581            {traverse, {select, _MS} = Select} ->
1582                {ok, Select};
1583            {traverse, first_next} ->
1584                {ok, first_next};
1585            {traverse, last_prev} ->
1586                {ok, last_prev};
1587	    {Key, _} ->
1588		badarg;
1589	    false ->
1590		Default = default_option(Key),
1591		{ok, Default}
1592	end,
1593    case V of
1594	badarg ->
1595	    {badarg, Key};
1596	{ok,Value} ->
1597	    NewOptions = lists:keydelete(Key, 1, Options),
1598	    options(NewOptions, Keys, [Value | L])
1599    end;
1600options(Options, [], L) ->
1601    [lists:reverse(L), Options].
1602
1603default_option(traverse) -> select;
1604default_option(n_objects) -> 100.
1605
1606listify(L) when is_list(L) ->
1607    L;
1608listify(T) ->
1609    [T].
1610
1611%% End of table/2.
1612
1613%% Print info about all tabs on the tty
1614-spec i() -> 'ok'.
1615
1616i() ->
1617    hform('id', 'name', 'type', 'size', 'mem', 'owner'),
1618    io:format(" -------------------------------------"
1619	      "---------------------------------------\n"),
1620    lists:foreach(fun prinfo/1, tabs()),
1621    ok.
1622
1623tabs() ->
1624    lists:sort(ets:all()).
1625
1626prinfo(Tab) ->
1627    case catch prinfo2(Tab) of
1628	{'EXIT', _} ->
1629	    io:format("~-10s ... unreadable \n", [to_string(Tab)]);
1630	ok ->
1631	    ok
1632    end.
1633prinfo2(Tab) ->
1634    Name = ets:info(Tab, name),
1635    Type = ets:info(Tab, type),
1636    Size = ets:info(Tab, size),
1637    Mem = ets:info(Tab, memory),
1638    Owner = ets:info(Tab, owner),
1639    hform(Tab, Name, Type, Size, Mem, is_reg(Owner)).
1640
1641is_reg(Owner) ->
1642    case process_info(Owner, registered_name) of
1643	{registered_name, Name} -> Name;
1644	_ -> Owner
1645    end.
1646
1647%%% Arndt: this code used to truncate over-sized fields. Now it
1648%%% pushes the remaining entries to the right instead, rather than
1649%%% losing information.
1650hform(A0, B0, C0, D0, E0, F0) ->
1651    [A,B,C,D,E,F] = [to_string(T) || T <- [A0,B0,C0,D0,E0,F0]],
1652    A1 = pad_right(A, 15),
1653    B1 = pad_right(B, 17),
1654    C1 = pad_right(C, 5),
1655    D1 = pad_right(D, 6),
1656    E1 = pad_right(E, 8),
1657    %% no need to pad the last entry on the line
1658    io:format(" ~s ~s ~s ~s ~s ~s\n", [A1,B1,C1,D1,E1,F]).
1659
1660pad_right(String, Len) ->
1661    if
1662	length(String) >= Len ->
1663	    String;
1664	true ->
1665	    [Space] = " ",
1666	    String ++ lists:duplicate(Len - length(String), Space)
1667    end.
1668
1669to_string(X) ->
1670    lists:flatten(io_lib:format("~p", [X])).
1671
1672%% view a specific table
1673-spec i(Tab) -> 'ok' when
1674      Tab :: tab().
1675
1676i(Tab) ->
1677    i(Tab, 40).
1678
1679-spec i(tab(), pos_integer()) -> 'ok'.
1680
1681i(Tab, Height) ->
1682    i(Tab, Height, 80).
1683
1684-spec i(tab(), pos_integer(), pos_integer()) -> 'ok'.
1685
1686i(Tab, Height, Width) ->
1687    First = ets:first(Tab),
1688    display_items(Height, Width, Tab, First, 1, 1).
1689
1690display_items(Height, Width, Tab, '$end_of_table', Turn, Opos) ->
1691    P = 'EOT  (q)uit (p)Digits (k)ill /Regexp -->',
1692    choice(Height, Width, P, eot, Tab, '$end_of_table', Turn, Opos);
1693display_items(Height, Width, Tab, Key, Turn, Opos) when Turn < Height ->
1694    do_display(Height, Width, Tab, Key, Turn, Opos);
1695display_items(Height, Width, Tab, Key, Turn, Opos) when Turn >=  Height ->
1696    P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
1697    choice(Height, Width, P, normal, Tab, Key, Turn, Opos).
1698
1699choice(Height, Width, P, Mode, Tab, Key, Turn, Opos) ->
1700    case get_line(P, "c\n") of
1701	"c\n" when Mode =:= normal ->
1702	    do_display(Height, Width, Tab, Key, 1, Opos);
1703	"c\n" when is_tuple(Mode), element(1, Mode) =:= re ->
1704	    {re, Re} = Mode,
1705	    re_search(Height, Width, Tab, Key, Re, 1, Opos);
1706	"q\n" ->
1707	    ok;
1708	"k\n" ->
1709	    ets:delete(Tab),
1710	    ok;
1711	[$p|Digs]  ->
1712	    catch case catch list_to_integer(nonl(Digs)) of
1713		      {'EXIT', _} ->
1714			  io:put_chars("Bad digits\n");
1715		      Number when Mode =:= normal ->
1716			  print_number(Tab, ets:first(Tab), Number);
1717		      Number when Mode =:= eot ->
1718			  print_number(Tab, ets:first(Tab), Number);
1719		      Number -> %% regexp
1720			  {re, Re} = Mode,
1721			  print_re_num(Tab, ets:first(Tab), Number, Re)
1722		  end,
1723	    choice(Height, Width, P, Mode, Tab, Key, Turn, Opos);
1724	[$/|Regexp]   -> %% from regexp
1725	    case re:compile(nonl(Regexp),[unicode]) of
1726		{ok,Re} ->
1727		    re_search(Height, Width, Tab, ets:first(Tab), Re, 1, 1);
1728		{error,{ErrorString,_Pos}} ->
1729		    io:format("~ts\n", [ErrorString]),
1730		    choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
1731	    end;
1732        eof ->
1733            ok;
1734	_  ->
1735	    choice(Height, Width, P, Mode, Tab, Key, Turn, Opos)
1736    end.
1737
1738get_line(P, Default) ->
1739    case line_string(io:get_line(P)) of
1740	"\n" ->
1741	    Default;
1742	L ->
1743	    L
1744    end.
1745
1746%% If the standard input is set to binary mode
1747%% convert it to a list so we can properly match.
1748line_string(Binary) when is_binary(Binary) -> unicode:characters_to_list(Binary);
1749line_string(Other) -> Other.
1750
1751nonl(S) -> string:trim(S, trailing, "$\n").
1752
1753print_number(Tab, Key, Num) ->
1754    Os = ets:lookup(Tab, Key),
1755    Len = length(Os),
1756    if
1757	(Num - Len) < 1 ->
1758	    O = lists:nth(Num, Os),
1759	    io:format("~p~n", [O]); %% use ppterm here instead
1760	true ->
1761	    print_number(Tab, ets:next(Tab, Key), Num - Len)
1762    end.
1763
1764do_display(Height, Width, Tab, Key, Turn, Opos) ->
1765    Objs = ets:lookup(Tab, Key),
1766    do_display_items(Height, Width, Objs, Opos),
1767    Len = length(Objs),
1768    display_items(Height, Width, Tab, ets:next(Tab, Key), Turn+Len, Opos+Len).
1769
1770do_display_items(Height, Width, [Obj|Tail], Opos) ->
1771    do_display_item(Height, Width, Obj, Opos),
1772    do_display_items(Height, Width, Tail, Opos+1);
1773do_display_items(_Height, _Width, [], Opos) ->
1774    Opos.
1775
1776do_display_item(_Height, Width, I, Opos)  ->
1777    L = to_string(I),
1778    L2 = if
1779	     length(L) > Width - 8 ->
1780                 string:slice(L, 0, Width-13) ++ "  ...";
1781	     true ->
1782		 L
1783	 end,
1784    io:format("<~-4w> ~s~n", [Opos,L2]).
1785
1786re_search(Height, Width, Tab, '$end_of_table', Re, Turn, Opos) ->
1787    P = 'EOT  (q)uit (p)Digits (k)ill /Regexp -->',
1788    choice(Height, Width, P, {re, Re}, Tab, '$end_of_table', Turn, Opos);
1789re_search(Height, Width, Tab, Key, Re, Turn, Opos) when Turn < Height ->
1790    re_display(Height, Width, Tab, Key, ets:lookup(Tab, Key), Re, Turn, Opos);
1791re_search(Height, Width, Tab, Key, Re, Turn, Opos)  ->
1792    P = '(c)ontinue (q)uit (p)Digits (k)ill /Regexp -->',
1793    choice(Height, Width, P, {re, Re}, Tab, Key, Turn, Opos).
1794
1795re_display(Height, Width, Tab, Key, [], Re, Turn, Opos) ->
1796    re_search(Height, Width, Tab, ets:next(Tab, Key), Re, Turn, Opos);
1797re_display(Height, Width, Tab, Key, [H|T], Re, Turn, Opos) ->
1798    Str = to_string(H),
1799    case re:run(Str, Re, [{capture,none}]) of
1800	match ->
1801	    do_display_item(Height, Width, H, Opos),
1802	    re_display(Height, Width, Tab, Key, T, Re, Turn+1, Opos+1);
1803	nomatch ->
1804	    re_display(Height, Width, Tab, Key, T, Re, Turn, Opos)
1805    end.
1806
1807print_re_num(_,'$end_of_table',_,_) -> ok;
1808print_re_num(Tab, Key, Num, Re) ->
1809    Os = re_match(ets:lookup(Tab, Key), Re),
1810    Len = length(Os),
1811    if
1812	(Num - Len) < 1 ->
1813	    O = lists:nth(Num, Os),
1814	    io:format("~p~n", [O]); %% use ppterm here instead
1815	true ->
1816	    print_re_num(Tab, ets:next(Tab, Key), Num - Len, Re)
1817    end.
1818
1819re_match([], _) -> [];
1820re_match([H|T], Re) ->
1821    case re:run(to_string(H), Re, [{capture,none}]) of
1822	match ->
1823	    [H|re_match(T,Re)];
1824	nomatch ->
1825	    re_match(T, Re)
1826    end.
1827