1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-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(beam_lib).
21-behaviour(gen_server).
22
23%% Avoid warning for local function error/1 clashing with autoimported BIF.
24-compile({no_auto_import,[error/1]}).
25%% Avoid warning for local function error/2 clashing with autoimported BIF.
26-compile({no_auto_import,[error/2]}).
27-export([info/1,
28	 cmp/2,
29	 cmp_dirs/2,
30	 chunks/2,
31	 chunks/3,
32	 all_chunks/1,
33	 diff_dirs/2,
34	 strip/1,
35	 strip/2,
36	 strip_files/1,
37	 strip_files/2,
38	 strip_release/1,
39	 strip_release/2,
40	 significant_chunks/0,
41	 build_module/1,
42	 version/1,
43	 md5/1,
44	 format_error/1]).
45
46%% The following functions implement encrypted debug info.
47
48-export([crypto_key_fun/1, clear_crypto_key_fun/0]).
49-export([init/1,handle_call/3,handle_cast/2,handle_info/2,
50	 terminate/2,code_change/3]).
51-export([make_crypto_key/2, get_crypto_key/1]).	%Utilities used by compiler
52
53-export_type([attrib_entry/0, compinfo_entry/0, labeled_entry/0, label/0]).
54-export_type([chnk_rsn/0]).
55
56-import(lists, [append/1, delete/2, foreach/2, keysort/2,
57		member/2, reverse/1, sort/1, splitwith/2]).
58
59%%-------------------------------------------------------------------------
60
61-type beam() :: file:filename() | binary().
62-type debug_info() :: {DbgiVersion :: atom(), Backend :: module(), Data :: term()} | 'no_debug_info'.
63
64-type forms()     :: [erl_parse:abstract_form() | erl_parse:form_info()].
65
66-type abst_code() :: {AbstVersion :: atom(), forms()} | 'no_abstract_code'.
67-type dataB()     :: binary().
68-type index()     :: non_neg_integer().
69-type label()     :: integer().
70
71-type chunkid()   :: nonempty_string(). % approximation of the strings below
72%% "Abst" | "Dbgi" | "Attr" | "CInf" | "ExpT" | "ImpT" | "LocT" | "Atom" | "AtU8".
73-type chunkname() :: 'abstract_code' | 'debug_info'
74                   | 'attributes' | 'compile_info'
75                   | 'exports' | 'labeled_exports'
76                   | 'imports' | 'indexed_imports'
77                   | 'locals' | 'labeled_locals'
78                   | 'atoms'.
79-type chunkref()  :: chunkname() | chunkid().
80
81-type attrib_entry()   :: {Attribute :: atom(), [AttributeValue :: term()]}.
82-type compinfo_entry() :: {InfoKey :: atom(), term()}.
83-type labeled_entry()  :: {Function :: atom(), arity(), label()}.
84
85-type chunkdata() :: {chunkid(), dataB()}
86                   | {'abstract_code', abst_code()}
87                   | {'debug_info', debug_info()}
88                   | {'attributes', [attrib_entry()]}
89                   | {'compile_info', [compinfo_entry()]}
90                   | {'exports', [{atom(), arity()}]}
91                   | {'labeled_exports', [labeled_entry()]}
92                   | {'imports', [mfa()]}
93                   | {'indexed_imports', [{index(), module(), Function :: atom(), arity()}]}
94                   | {'locals', [{atom(), arity()}]}
95                   | {'labeled_locals', [labeled_entry()]}
96                   | {'atoms', [{integer(), atom()}]}.
97
98%% Error reasons
99-type info_rsn()  :: {'chunk_too_big', file:filename(),
100		      chunkid(), ChunkSize :: non_neg_integer(),
101                      FileSize :: non_neg_integer()}
102                   | {'invalid_beam_file', file:filename(),
103                      Position :: non_neg_integer()}
104                   | {'invalid_chunk', file:filename(), chunkid()}
105                   | {'missing_chunk', file:filename(), chunkid()}
106                   | {'not_a_beam_file', file:filename()}
107                   | {'file_error', file:filename(), file:posix()}.
108-type chnk_rsn()  :: {'unknown_chunk', file:filename(), atom()}
109                   | {'key_missing_or_invalid', file:filename(),
110		      'abstract_code' | 'debug_info'}
111                   | {'missing_backend', file:filename(), module()}
112                   | info_rsn().
113-type cmp_rsn()   :: {'modules_different', module(), module()}
114                   | {'chunks_different', chunkid()}
115                   | 'different_chunks'
116                   | info_rsn().
117
118%%-------------------------------------------------------------------------
119
120%%
121%%  Exported functions
122%%
123
124-spec info(Beam) -> [InfoPair] | {'error', 'beam_lib', info_rsn()} when
125      Beam :: beam(),
126      InfoPair :: {'file', Filename :: file:filename()}
127                | {'binary', Binary :: binary()}
128                | {'module', Module :: module()}
129                | {'chunks', [{ChunkId :: chunkid(),
130                               Pos :: non_neg_integer(),
131                               Size :: non_neg_integer()}]}.
132
133info(File) ->
134    read_info(beam_filename(File)).
135
136-spec chunks(Beam, ChunkRefs) ->
137                    {'ok', {module(), [chunkdata()]}} |
138                    {'error', 'beam_lib', chnk_rsn()} when
139      Beam :: beam(),
140      ChunkRefs :: [chunkref()].
141
142chunks(File, Chunks) ->
143    read_chunk_data(File, Chunks).
144
145-spec chunks(Beam, ChunkRefs, Options) ->
146                    {'ok', {module(), [ChunkResult]}} |
147                    {'error', 'beam_lib', chnk_rsn()} when
148      Beam :: beam(),
149      ChunkRefs :: [chunkref()],
150      Options :: ['allow_missing_chunks'],
151      ChunkResult :: chunkdata() | {ChunkRef :: chunkref(), 'missing_chunk'}.
152
153chunks(File, Chunks, Options) ->
154    try read_chunk_data(File, Chunks, Options)
155    catch Error -> Error end.
156
157-spec all_chunks(beam()) ->
158           {'ok', 'beam_lib', [{chunkid(), dataB()}]} | {'error', 'beam_lib', info_rsn()}.
159
160all_chunks(File) ->
161    read_all_chunks(File).
162
163-spec cmp(Beam1, Beam2) -> 'ok' | {'error', 'beam_lib', cmp_rsn()} when
164      Beam1 :: beam(),
165      Beam2 :: beam().
166
167cmp(File1, File2) ->
168    try cmp_files(File1, File2)
169    catch Error -> Error end.
170
171-spec cmp_dirs(Dir1, Dir2) ->
172           {Only1, Only2, Different} | {'error', 'beam_lib', Reason} when
173      Dir1 :: atom() | file:filename(),
174      Dir2 :: atom() | file:filename(),
175      Only1 :: [file:filename()],
176      Only2 :: [file:filename()],
177      Different :: [{Filename1 :: file:filename(), Filename2 :: file:filename()}],
178      Reason :: {'not_a_directory', term()} | info_rsn().
179
180cmp_dirs(Dir1, Dir2) ->
181    catch compare_dirs(Dir1, Dir2).
182
183-spec diff_dirs(Dir1, Dir2) -> 'ok' | {'error', 'beam_lib', Reason} when
184      Dir1 :: atom() | file:filename(),
185      Dir2 :: atom() | file:filename(),
186      Reason :: {'not_a_directory', term()} | info_rsn().
187
188diff_dirs(Dir1, Dir2) ->
189    catch diff_directories(Dir1, Dir2).
190
191-spec strip(Beam1) ->
192        {'ok', {module(), Beam2}} | {'error', 'beam_lib', info_rsn()} when
193      Beam1 :: beam(),
194      Beam2 :: beam().
195
196strip(FileName) ->
197    strip(FileName, []).
198
199-spec strip(Beam1, AdditionalChunks) ->
200        {'ok', {module(), Beam2}} | {'error', 'beam_lib', info_rsn()} when
201      Beam1 :: beam(),
202      AdditionalChunks :: [chunkid()],
203      Beam2 :: beam().
204
205strip(FileName, AdditionalChunks) ->
206    try strip_file(FileName, AdditionalChunks)
207    catch Error -> Error end.
208
209-spec strip_files(Files) ->
210        {'ok', [{module(), Beam}]} | {'error', 'beam_lib', info_rsn()} when
211      Files :: [beam()],
212      Beam :: beam().
213
214strip_files(Files) ->
215    strip_files(Files, []).
216
217-spec strip_files(Files, AdditionalChunks) ->
218        {'ok', [{module(), Beam}]} | {'error', 'beam_lib', info_rsn()} when
219      Files :: [beam()],
220      AdditionalChunks :: [chunkid()],
221      Beam :: beam().
222
223strip_files(Files, AdditionalChunks) when is_list(Files) ->
224    try strip_fils(Files, AdditionalChunks)
225    catch Error -> Error end.
226
227-spec strip_release(Dir) ->
228        {'ok', [{module(), file:filename()}]}
229      | {'error', 'beam_lib', Reason} when
230      Dir :: atom() | file:filename(),
231      Reason :: {'not_a_directory', term()} | info_rsn().
232
233strip_release(Root) ->
234    strip_release(Root, []).
235
236-spec strip_release(Dir, AdditionalChunks) ->
237        {'ok', [{module(), file:filename()}]}
238      | {'error', 'beam_lib', Reason} when
239      Dir :: atom() | file:filename(),
240      AdditionalChunks :: [chunkid()],
241      Reason :: {'not_a_directory', term()} | info_rsn().
242
243strip_release(Root, AdditionalChunks) ->
244    catch strip_rel(Root, AdditionalChunks).
245
246-spec version(Beam) ->
247                     {'ok', {module(), [Version :: term()]}} |
248                     {'error', 'beam_lib', chnk_rsn()} when
249      Beam :: beam().
250
251version(File) ->
252    case catch read_chunk_data(File, [attributes]) of
253	{ok, {Module, [{attributes, Attrs}]}} ->
254	    {vsn, Version} = lists:keyfind(vsn, 1, Attrs),
255	    {ok, {Module, Version}};
256	Error ->
257	    Error
258    end.
259
260-spec md5(Beam) ->
261        {'ok', {module(), MD5}} | {'error', 'beam_lib', chnk_rsn()} when
262      Beam :: beam(),
263      MD5 :: binary().
264
265md5(File) ->
266    case catch read_significant_chunks(File, md5_chunks()) of
267	{ok, {Module, Chunks0}} ->
268	    Chunks = filter_funtab(Chunks0),
269	    {ok, {Module, erlang:md5([C || {_Id, C} <- Chunks])}};
270	Error ->
271	    Error
272    end.
273
274-spec format_error(Reason) -> io_lib:chars() when
275      Reason :: term().
276
277format_error({error, Error}) ->
278    format_error(Error);
279format_error({error, Module, Error}) ->
280    Module:format_error(Error);
281format_error({unknown_chunk, File, ChunkName}) ->
282    io_lib:format("~tp: Cannot find chunk ~p~n", [File, ChunkName]);
283format_error({invalid_chunk, File, ChunkId}) ->
284    io_lib:format("~tp: Invalid contents of chunk ~p~n", [File, ChunkId]);
285format_error({not_a_beam_file, File}) ->
286    io_lib:format("~tp: Not a BEAM file~n", [File]);
287format_error({file_error, File, Reason}) ->
288    io_lib:format("~tp: ~tp~n", [File, file:format_error(Reason)]);
289format_error({missing_chunk, File, ChunkId}) ->
290    io_lib:format("~tp: Not a BEAM file: no IFF \"~s\" chunk~n",
291		  [File, ChunkId]);
292format_error({invalid_beam_file, File, Pos}) ->
293    io_lib:format("~tp: Invalid format of BEAM file near byte number ~p~n",
294		  [File, Pos]);
295format_error({chunk_too_big, File, ChunkId, Size, Len}) ->
296    io_lib:format("~tp: Size of chunk \"~s\" is ~p bytes, "
297		  "but only ~p bytes could be read~n",
298		  [File, ChunkId, Size, Len]);
299format_error({chunks_different, Id}) ->
300    io_lib:format("Chunk \"~s\" differs in the two files~n", [Id]);
301format_error(different_chunks) ->
302    "The two files have different chunks\n";
303format_error({modules_different, Module1, Module2}) ->
304    io_lib:format("Module names ~p and ~p differ in the two files~n",
305		  [Module1, Module2]);
306format_error({not_a_directory, Name}) ->
307    io_lib:format("~tp: Not a directory~n", [Name]);
308format_error({key_missing_or_invalid, File, ChunkId}) ->
309    io_lib:format("~tp: Cannot decrypt ~ts because key is missing or invalid",
310		  [File, ChunkId]);
311format_error(badfun) ->
312    "not a fun or the fun has the wrong arity";
313format_error(exists) ->
314    "a fun has already been installed";
315format_error({missing_backend, File, Backend}) ->
316    io_lib:format("~tp: Cannot retrieve abstract code because the backend ~p is missing",
317		  [File, Backend]);
318format_error(E) ->
319    io_lib:format("~tp~n", [E]).
320
321%%
322%% Exported functions for encrypted debug info.
323%%
324
325-type mode()           :: 'des3_cbc'.
326-type crypto_fun_arg() :: 'init'
327                        | 'clear'
328                        | {'debug_info', mode(), module(), file:filename()}.
329-type crypto_fun()     :: fun((crypto_fun_arg()) -> term()).
330
331-spec crypto_key_fun(CryptoKeyFun) -> 'ok' | {'error', Reason} when
332      CryptoKeyFun :: crypto_fun(),
333      Reason :: badfun | exists | term().
334
335crypto_key_fun(F) ->
336    call_crypto_server({crypto_key_fun, F}).
337
338-spec clear_crypto_key_fun() -> 'undefined' | {'ok', Result} when
339      Result :: 'undefined' | term().
340
341clear_crypto_key_fun() ->
342    call_crypto_server(clear_crypto_key_fun).
343
344-spec make_crypto_key(mode(), string()) ->
345        {mode(), [binary()], binary(), integer()}.
346
347make_crypto_key(des3_cbc=Type, String) ->
348    <<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
349    <<K3:8/binary,IVec:8/binary>> = erlang:md5([First|reverse(String)]),
350    {Type,[K1,K2,K3],IVec,8}.
351
352-spec build_module(Chunks) -> {'ok', Binary} when
353      Chunks :: [{chunkid(), dataB()}],
354      Binary :: binary().
355
356build_module(Chunks0) ->
357    Chunks = list_to_binary(build_chunks(Chunks0)),
358    Size = byte_size(Chunks),
359    0 = Size rem 4, % Assertion: correct padding?
360    {ok, <<"FOR1", (Size+4):32, "BEAM", Chunks/binary>>}.
361
362
363%%
364%%  Local functions
365%%
366
367read_info(File) ->
368    try
369        {ok, Module, Data} = scan_beam(File, info),
370        [if
371             is_binary(File) -> {binary, File};
372             true -> {file, File}
373         end, {module, Module}, {chunks, Data}]
374    catch Error -> Error end.
375
376diff_directories(Dir1, Dir2) ->
377    {OnlyDir1, OnlyDir2, Diff} = compare_dirs(Dir1, Dir2),
378    diff_only(Dir1, OnlyDir1),
379    diff_only(Dir2, OnlyDir2),
380    foreach(fun(D) -> io:format("** different: ~tp~n", [D]) end, Diff),
381    ok.
382
383diff_only(_Dir, []) ->
384    ok;
385diff_only(Dir, Only) ->
386    io:format("Only in ~tp: ~tp~n", [Dir, Only]).
387
388%% -> {OnlyInDir1, OnlyInDir2, Different} | throw(Error)
389compare_dirs(Dir1, Dir2) ->
390    R1 = sofs:relation(beam_files(Dir1)),
391    R2 = sofs:relation(beam_files(Dir2)),
392    F1 = sofs:domain(R1),
393    F2 = sofs:domain(R2),
394    {O1, Both, O2} = sofs:symmetric_partition(F1, F2),
395    OnlyL1 = sofs:image(R1, O1),
396    OnlyL2 = sofs:image(R2, O2),
397    B1 = sofs:to_external(sofs:restriction(R1, Both)),
398    B2 = sofs:to_external(sofs:restriction(R2, Both)),
399    Diff = compare_files(B1, B2, []),
400    {sofs:to_external(OnlyL1), sofs:to_external(OnlyL2), Diff}.
401
402compare_files([], [], Acc) ->
403    lists:reverse(Acc);
404compare_files([{_,F1} | R1], [{_,F2} | R2], Acc) ->
405    NAcc = case catch cmp_files(F1, F2) of
406	       {error, _Mod, _Reason} ->
407		   [{F1, F2} | Acc];
408	       ok ->
409		   Acc
410	   end,
411    compare_files(R1, R2, NAcc).
412
413beam_files(Dir) ->
414    ok = assert_directory(Dir),
415    L = filelib:wildcard(filename:join(Dir, "*.beam")),
416    [{filename:basename(Path), Path} || Path <- L].
417
418%% -> ok | throw(Error)
419cmp_files(File1, File2) ->
420    {ok, {M1, L1}} = read_all_but_useless_chunks(File1),
421    {ok, {M2, L2}} = read_all_but_useless_chunks(File2),
422    if
423	M1 =:= M2 ->
424	    cmp_lists(L1, L2);
425	true ->
426	    error({modules_different, M1, M2})
427    end.
428
429cmp_lists([], []) ->
430    ok;
431cmp_lists([{Id, C1} | R1], [{Id, C2} | R2]) ->
432    if
433	C1 =:= C2 ->
434	    cmp_lists(R1, R2);
435	true ->
436	    error({chunks_different, Id})
437    end;
438cmp_lists(_, _) ->
439    error(different_chunks).
440
441strip_rel(Root, AdditionalChunks) ->
442    ok = assert_directory(Root),
443    strip_fils(filelib:wildcard(filename:join(Root, "lib/*/ebin/*.beam")), AdditionalChunks).
444
445%% -> {ok, [{Mod, BinaryOrFileName}]} | throw(Error)
446strip_fils(Files, AdditionalChunks) ->
447    {ok, [begin {ok, Reply} = strip_file(F, AdditionalChunks), Reply end || F <- Files]}.
448
449%% -> {ok, {Mod, FileName}} | {ok, {Mod, binary()}} | throw(Error)
450strip_file(File, AdditionalChunks) ->
451    {ok, {Mod, Chunks}} = read_significant_chunks(File, AdditionalChunks ++ significant_chunks()),
452    {ok, Stripped0} = build_module(Chunks),
453    Stripped = compress(Stripped0),
454    case File of
455	_ when is_binary(File) ->
456	    {ok, {Mod, Stripped}};
457	_ ->
458	    FileName = beam_filename(File),
459	    case file:open(FileName, [raw, binary, write]) of
460		{ok, Fd} ->
461		    case file:write(Fd, Stripped) of
462			ok ->
463			    ok = file:close(Fd),
464			    {ok, {Mod, FileName}};
465			Error ->
466			    ok = file:close(Fd),
467			    file_error(FileName, Error)
468		    end;
469		Error ->
470		    file_error(FileName, Error)
471	    end
472    end.
473
474build_chunks([{Id, Data} | Chunks]) ->
475    BId = list_to_binary(Id),
476    Size = byte_size(Data),
477    Chunk = [<<BId/binary, Size:32>>, Data | pad(Size)],
478    [Chunk | build_chunks(Chunks)];
479build_chunks([]) ->
480    [].
481
482pad(Size) ->
483    case Size rem 4 of
484	0 -> [];
485	Rem -> lists:duplicate(4 - Rem, 0)
486    end.
487
488%% -> {ok, {Module, Chunks}} | throw(Error)
489read_all_but_useless_chunks(File0) when is_atom(File0);
490					is_list(File0);
491					is_binary(File0) ->
492    File = beam_filename(File0),
493    {ok, Module, ChunkIds0} = scan_beam(File, info),
494    ChunkIds = [Name || {Name,_,_} <- ChunkIds0,
495			not is_useless_chunk(Name)],
496    {ok, Module, Chunks} = scan_beam(File, ChunkIds),
497    {ok, {Module, lists:reverse(Chunks)}}.
498
499is_useless_chunk("CInf") -> true;
500is_useless_chunk(_) -> false.
501
502%% -> {ok, {Module, Chunks}} | throw(Error)
503read_significant_chunks(File, ChunkList) ->
504    case read_chunk_data(File, ChunkList, [allow_missing_chunks]) of
505	{ok, {Module, Chunks0}} ->
506	    Mandatory = mandatory_chunks(),
507	    Chunks = filter_significant_chunks(Chunks0, Mandatory, File, Module),
508	    {ok, {Module, Chunks}}
509    end.
510
511filter_significant_chunks([{_, Data}=Pair|Cs], Mandatory, File, Mod)
512  when is_binary(Data) ->
513    [Pair|filter_significant_chunks(Cs, Mandatory, File, Mod)];
514filter_significant_chunks([{Id, missing_chunk}|Cs], Mandatory, File, Mod) ->
515    case member(Id, Mandatory) of
516	false ->
517	    filter_significant_chunks(Cs, Mandatory, File, Mod);
518	true ->
519	    error({missing_chunk, File, Id})
520    end;
521filter_significant_chunks([], _, _, _) -> [].
522
523filter_funtab([{"FunT"=Tag, <<L:4/binary, Data0/binary>>}|Cs]) ->
524    Data = filter_funtab_1(Data0, <<0:32>>),
525    Funtab = <<L/binary, (iolist_to_binary(Data))/binary>>,
526    [{Tag, Funtab}|filter_funtab(Cs)];
527filter_funtab([H|T]) ->
528    [H|filter_funtab(T)];
529filter_funtab([]) -> [].
530
531filter_funtab_1(<<Important:20/binary,_OldUniq:4/binary,T/binary>>, Zero) ->
532    [Important,Zero|filter_funtab_1(T, Zero)];
533filter_funtab_1(Tail, _) when is_binary(Tail) -> [Tail].
534
535read_all_chunks(File0) when is_atom(File0);
536			    is_list(File0);
537			    is_binary(File0) ->
538    try
539        File = beam_filename(File0),
540        {ok, Module, ChunkIds0} = scan_beam(File, info),
541        ChunkIds = [Name || {Name,_,_} <- ChunkIds0],
542        {ok, Module, Chunks} = scan_beam(File, ChunkIds),
543        {ok, Module, lists:reverse(Chunks)}
544    catch Error -> Error end.
545
546read_chunk_data(File0, ChunkNames) ->
547    try read_chunk_data(File0, ChunkNames, [])
548    catch Error -> Error end.
549
550%% -> {ok, {Module, Symbols}} | throw(Error)
551read_chunk_data(File0, ChunkNames0, Options)
552  when is_atom(File0); is_list(File0); is_binary(File0) ->
553    File = beam_filename(File0),
554    {ChunkIds, Names, Optional} = check_chunks(ChunkNames0, File, [], [], []),
555    AllowMissingChunks = member(allow_missing_chunks, Options),
556    {ok, Module, Chunks} = scan_beam(File, ChunkIds, AllowMissingChunks, Optional),
557    AT = ets:new(beam_symbols, []),
558    T = {empty, AT},
559    try chunks_to_data(Names, Chunks, File, Chunks, Module, T, [])
560    after ets:delete(AT)
561    end.
562
563%% -> {ok, list()} | throw(Error)
564check_chunks([atoms | Ids], File, IL, L, O) ->
565    check_chunks(Ids, File, ["Atom", "AtU8" | IL],
566		 [{atom_chunk, atoms} | L], ["Atom", "AtU8" | O]);
567check_chunks([abstract_code | Ids], File, IL, L, O) ->
568    check_chunks(Ids, File, ["Abst", "Dbgi" | IL],
569		 [{abst_chunk, abstract_code} | L], ["Abst", "Dbgi" | O]);
570check_chunks([ChunkName | Ids], File, IL, L, O) when is_atom(ChunkName) ->
571    ChunkId = chunk_name_to_id(ChunkName, File),
572    check_chunks(Ids, File, [ChunkId | IL], [{ChunkId, ChunkName} | L], O);
573check_chunks([ChunkId | Ids], File, IL, L, O) -> % when is_list(ChunkId)
574    check_chunks(Ids, File, [ChunkId | IL], [{ChunkId, ChunkId} | L], O);
575check_chunks([], _File, IL, L, O) ->
576    {lists:usort(IL), reverse(L), O}.
577
578%% -> {ok, Module, Data} | throw(Error)
579scan_beam(File, What) ->
580    scan_beam(File, What, false, []).
581
582%% -> {ok, Module, Data} | throw(Error)
583scan_beam(File, What0, AllowMissingChunks, OptionalChunks) ->
584    case scan_beam1(File, What0) of
585	{missing, _FD, Mod, Data, What} when AllowMissingChunks ->
586	    {ok, Mod, [{Id, missing_chunk} || Id <- What] ++ Data};
587	{missing, FD, Mod, Data, What} ->
588	    case What -- OptionalChunks of
589		[] -> {ok, Mod, Data};
590		[Missing | _] -> error({missing_chunk, filename(FD), Missing})
591	    end;
592	R ->
593	    R
594    end.
595
596%% -> {ok, Module, Data} | throw(Error)
597scan_beam1(File, What) ->
598    FD = open_file(File),
599    case catch scan_beam2(FD, What) of
600	Error when error =:= element(1, Error) ->
601	    throw(Error);
602	R ->
603	    R
604    end.
605
606scan_beam2(FD, What) ->
607    case pread(FD, 0, 12) of
608	{NFD, {ok, <<"FOR1", _Size:32, "BEAM">>}} ->
609	    Start = 12,
610	    scan_beam(NFD, Start, What, 17, []);
611	_Error ->
612	    error({not_a_beam_file, filename(FD)})
613    end.
614
615scan_beam(_FD, _Pos, [], Mod, Data) when Mod =/= 17 ->
616    {ok, Mod, Data};
617scan_beam(FD, Pos, What, Mod, Data) ->
618    case pread(FD, Pos, 8) of
619	{_NFD, eof} when Mod =:= 17 ->
620	    error({missing_chunk, filename(FD), "Atom"});
621	{_NFD, eof} when What =:= info ->
622	    {ok, Mod, reverse(Data)};
623	{NFD, eof} ->
624	    {missing, NFD, Mod, Data, What};
625	{NFD, {ok, <<IdL:4/binary, Sz:32>>}} ->
626	    Id = binary_to_list(IdL),
627	    Pos1 = Pos + 8,
628	    Pos2 = (4 * trunc((Sz+3) / 4)) + Pos1,
629	    get_data(What, Id, NFD, Sz, Pos1, Pos2, Mod, Data);
630	{_NFD, {ok, _ChunkHead}} ->
631	    error({invalid_beam_file, filename(FD), Pos})
632    end.
633
634get_atom_data(Cs, Id, FD, Size, Pos, Pos2, Data, Encoding) ->
635    NewCs = del_chunk(Id, Cs),
636    {NFD, Chunk} = get_chunk(Id, Pos, Size, FD),
637    <<_Num:32, Chunk2/binary>> = Chunk,
638    {Module, _} = extract_atom(Chunk2, Encoding),
639    C = case Cs of
640	    info ->
641		{Id, Pos, Size};
642	    _ ->
643		{Id, Chunk}
644	end,
645    scan_beam(NFD, Pos2, NewCs, Module, [C | Data]).
646
647get_data(Cs, "Atom" = Id, FD, Size, Pos, Pos2, _Mod, Data) ->
648    get_atom_data(Cs, Id, FD, Size, Pos, Pos2, Data, latin1);
649get_data(Cs, "AtU8" = Id, FD, Size, Pos, Pos2, _Mod, Data) ->
650    get_atom_data(Cs, Id, FD, Size, Pos, Pos2, Data, utf8);
651get_data(info, Id, FD, Size, Pos, Pos2, Mod, Data) ->
652    scan_beam(FD, Pos2, info, Mod, [{Id, Pos, Size} | Data]);
653get_data(Chunks, Id, FD, Size, Pos, Pos2, Mod, Data) ->
654    {NFD, NewData} = case member(Id, Chunks) of
655			 true ->
656			     {FD1, Chunk} = get_chunk(Id, Pos, Size, FD),
657			     {FD1, [{Id, Chunk} | Data]};
658			 false ->
659			     {FD, Data}
660	      end,
661    NewChunks = del_chunk(Id, Chunks),
662    scan_beam(NFD, Pos2, NewChunks, Mod, NewData).
663
664del_chunk(_Id, info) ->
665    info;
666del_chunk(Id, Chunks) ->
667    delete(Id, Chunks).
668
669%% -> {NFD, binary()} | throw(Error)
670get_chunk(Id, Pos, Size, FD) ->
671    case pread(FD, Pos, Size) of
672	{NFD, eof} when Size =:= 0 -> % cannot happen
673	    {NFD, <<>>};
674	{_NFD, eof} when Size > 0 ->
675	    error({chunk_too_big, filename(FD), Id, Size, 0});
676	{_NFD, {ok, Chunk}} when Size > byte_size(Chunk) ->
677	    error({chunk_too_big, filename(FD), Id, Size, byte_size(Chunk)});
678	{NFD, {ok, Chunk}} -> % when Size =:= size(Chunk)
679	    {NFD, Chunk}
680    end.
681
682chunks_to_data([{atom_chunk, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) ->
683    {NewAtoms, Ret} = chunk_to_data(Name, <<"">>, File, Cs, Atoms, Module),
684    chunks_to_data(CNs, Chunks, File, Cs, Module, NewAtoms, [Ret | L]);
685chunks_to_data([{abst_chunk, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) ->
686    DbgiChunk = proplists:get_value("Dbgi", Chunks, <<"">>),
687    {NewAtoms, Ret} =
688	case catch chunk_to_data(debug_info, DbgiChunk, File, Cs, Atoms, Module) of
689	    {DbgiAtoms, {debug_info, {debug_info_v1, Backend, Metadata}}} ->
690		try Backend:debug_info(erlang_v1, Module, Metadata, []) of
691		    {ok, Code} -> {DbgiAtoms, {abstract_code, {raw_abstract_v1, Code}}};
692		    {error, _} -> {DbgiAtoms, {abstract_code, no_abstract_code}}
693                catch
694                    error:undef ->
695                        error({missing_backend,File,Backend})
696                end;
697            {error,beam_lib,{key_missing_or_invalid,Path,debug_info}} ->
698                error({key_missing_or_invalid,Path,abstract_code});
699	    _ ->
700		AbstChunk = proplists:get_value("Abst", Chunks, <<"">>),
701		chunk_to_data(Name, AbstChunk, File, Cs, Atoms, Module)
702	end,
703    chunks_to_data(CNs, Chunks, File, Cs, Module, NewAtoms, [Ret | L]);
704chunks_to_data([{Id, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) ->
705    {_Id, Chunk} = lists:keyfind(Id, 1, Chunks),
706    {NewAtoms, Ret} = chunk_to_data(Name, Chunk, File, Cs, Atoms, Module),
707    chunks_to_data(CNs, Chunks, File, Cs, Module, NewAtoms, [Ret | L]);
708chunks_to_data([], _Chunks, _File, _Cs, Module, _Atoms, L) ->
709    {ok, {Module, reverse(L)}}.
710
711chunk_to_data(Id, missing_chunk, _File, _Cs, AtomTable, _Mod) ->
712    %% Missing chunk, only happens when 'allow_missing_chunks' is on.
713    {AtomTable, {Id, missing_chunk}};
714chunk_to_data(attributes=Id, Chunk, File, _Cs, AtomTable, _Mod) ->
715    try
716	Term = binary_to_term(Chunk),
717	{AtomTable, {Id, attributes(Term)}}
718    catch
719	error:badarg ->
720	    error({invalid_chunk, File, chunk_name_to_id(Id, File)})
721    end;
722chunk_to_data(compile_info=Id, Chunk, File, _Cs, AtomTable, _Mod) ->
723    try
724	{AtomTable, {Id, binary_to_term(Chunk)}}
725    catch
726	error:badarg ->
727	    error({invalid_chunk, File, chunk_name_to_id(Id, File)})
728    end;
729chunk_to_data(debug_info=Id, Chunk, File, _Cs, AtomTable, Mod) ->
730    case Chunk of
731	<<>> ->
732	    {AtomTable, {Id, no_debug_info}};
733	<<0:8,N:8,Mode0:N/binary,Rest/binary>> ->
734	    Mode = binary_to_atom(Mode0, utf8),
735	    Term = decrypt_chunk(Mode, Mod, File, Id, Rest),
736	    {AtomTable, {Id, anno_from_term(Term)}};
737	_ ->
738	    case catch binary_to_term(Chunk) of
739		{'EXIT', _} ->
740		    error({invalid_chunk, File, chunk_name_to_id(Id, File)});
741		Term ->
742                    {AtomTable, {Id, anno_from_term(Term)}}
743	    end
744    end;
745chunk_to_data(abstract_code=Id, Chunk, File, _Cs, AtomTable, Mod) ->
746    %% Before Erlang/OTP 20.0.
747    case Chunk of
748	<<>> ->
749	    {AtomTable, {Id, no_abstract_code}};
750	<<0:8,N:8,Mode0:N/binary,Rest/binary>> ->
751	    Mode = binary_to_atom(Mode0, utf8),
752	    Term = decrypt_chunk(Mode, Mod, File, Id, Rest),
753	    {AtomTable, {Id, old_anno_from_term(Term)}};
754	_ ->
755	    case catch binary_to_term(Chunk) of
756		{'EXIT', _} ->
757		    error({invalid_chunk, File, chunk_name_to_id(Id, File)});
758		Term ->
759                    try
760                        {AtomTable, {Id, old_anno_from_term(Term)}}
761                    catch
762                        _:_ ->
763                            error({invalid_chunk, File,
764                                   chunk_name_to_id(Id, File)})
765                    end
766	    end
767    end;
768chunk_to_data(atoms=Id, _Chunk, _File, Cs, AtomTable0, _Mod) ->
769    AtomTable = ensure_atoms(AtomTable0, Cs),
770    Atoms = ets:tab2list(AtomTable),
771    {AtomTable, {Id, lists:sort(Atoms)}};
772chunk_to_data(ChunkName, Chunk, File,
773	      Cs, AtomTable, _Mod) when is_atom(ChunkName) ->
774    case catch symbols(Chunk, AtomTable, Cs, ChunkName) of
775	{ok, NewAtomTable, S} ->
776	    {NewAtomTable, {ChunkName, S}};
777	{'EXIT', _} ->
778	    error({invalid_chunk, File, chunk_name_to_id(ChunkName, File)})
779    end;
780chunk_to_data(ChunkId, Chunk, _File,
781	      _Cs, AtomTable, _Module) when is_list(ChunkId) ->
782    {AtomTable, {ChunkId, Chunk}}. % Chunk is a binary
783
784chunk_name_to_id(indexed_imports, _) -> "ImpT";
785chunk_name_to_id(imports, _)         -> "ImpT";
786chunk_name_to_id(exports, _)         -> "ExpT";
787chunk_name_to_id(labeled_exports, _) -> "ExpT";
788chunk_name_to_id(locals, _)          -> "LocT";
789chunk_name_to_id(labeled_locals, _)  -> "LocT";
790chunk_name_to_id(attributes, _)      -> "Attr";
791chunk_name_to_id(abstract_code, _)   -> "Abst";
792chunk_name_to_id(debug_info, _)      -> "Dbgi";
793chunk_name_to_id(compile_info, _)    -> "CInf";
794chunk_name_to_id(Other, File) ->
795    error({unknown_chunk, File, Other}).
796
797%% Extract attributes
798
799attributes(Attrs) ->
800    attributes(keysort(1, Attrs), []).
801
802attributes([], R) ->
803    reverse(R);
804attributes(L, R) ->
805    K = element(1, hd(L)),
806    {L1, L2} = splitwith(fun(T) -> element(1, T) =:= K end, L),
807    V = append([A || {_, A} <- L1]),
808    attributes(L2, [{K, V} | R]).
809
810%% Extract symbols
811
812symbols(<<_Num:32, B/binary>>, AT0, Cs, Name) ->
813    AT = ensure_atoms(AT0, Cs),
814    symbols1(B, AT, Name, [], 1).
815
816symbols1(<<I1:32, I2:32, I3:32, B/binary>>, AT, Name, S, Cnt) ->
817    Symbol = symbol(Name, AT, I1, I2, I3, Cnt),
818    symbols1(B, AT, Name, [Symbol|S], Cnt+1);
819symbols1(<<>>, AT, _Name, S, _Cnt) ->
820    {ok, AT, sort(S)}.
821
822symbol(indexed_imports, AT, I1, I2, I3, Cnt) ->
823    {Cnt, atm(AT, I1), atm(AT, I2), I3};
824symbol(imports, AT, I1, I2, I3, _Cnt) ->
825    {atm(AT, I1), atm(AT, I2), I3};
826symbol(labeled_exports, AT, I1, I2, I3, _Cnt) ->
827    {atm(AT, I1), I2, I3};
828symbol(labeled_locals, AT, I1, I2, I3, _Cnt) ->
829    {atm(AT, I1), I2, I3};
830symbol(_, AT, I1, I2, _I3, _Cnt) ->
831    {atm(AT, I1), I2}.
832
833atm(AT, N) ->
834    [{_N, S}] = ets:lookup(AT, N),
835    S.
836
837%% AT is updated.
838ensure_atoms({empty, AT}, Cs) ->
839    case lists:keyfind("AtU8", 1, Cs) of
840	{_Id, AtomChunk} when is_binary(AtomChunk) ->
841	    extract_atoms(AtomChunk, AT, utf8);
842	_ ->
843	    {_Id, AtomChunk} = lists:keyfind("Atom", 1, Cs),
844	    extract_atoms(AtomChunk, AT, latin1)
845    end,
846    AT;
847ensure_atoms(AT, _Cs) ->
848    AT.
849
850extract_atoms(<<_Num:32, B/binary>>, AT, Encoding) ->
851    extract_atoms(B, 1, AT, Encoding).
852
853extract_atoms(<<>>, _I, _AT, _Encoding) ->
854    true;
855extract_atoms(B, I, AT, Encoding) ->
856    {Atom, B1} = extract_atom(B, Encoding),
857    true = ets:insert(AT, {I, Atom}),
858    extract_atoms(B1, I+1, AT, Encoding).
859
860extract_atom(<<Len, B/binary>>, Encoding) ->
861    <<SB:Len/binary, Tail/binary>> = B,
862    {binary_to_atom(SB, Encoding), Tail}.
863
864%%% Utils.
865
866-record(bb, {pos = 0 :: integer(),
867	     bin :: binary(),
868	     source :: binary() | string()}).
869
870open_file(Binary0) when is_binary(Binary0) ->
871    Binary = maybe_uncompress(Binary0),
872    #bb{bin = Binary, source = Binary};
873open_file(FileName) ->
874    case file:open(FileName, [read, raw, binary]) of
875	{ok, Fd} ->
876	    read_all(Fd, FileName, []);
877	Error ->
878	    file_error(FileName, Error)
879    end.
880
881read_all(Fd, FileName, Bins) ->
882    case file:read(Fd, 1 bsl 18) of
883	{ok, Bin} ->
884	    read_all(Fd, FileName, [Bin | Bins]);
885	eof ->
886	    ok = file:close(Fd),
887	    #bb{bin = maybe_uncompress(reverse(Bins)), source = FileName};
888	Error ->
889	    ok = file:close(Fd),
890	    file_error(FileName, Error)
891    end.
892
893pread(FD, AtPos, Size) ->
894    #bb{pos = Pos, bin = Binary} = FD,
895    Skip = AtPos-Pos,
896    case Binary of
897	<<_:Skip/binary, B:Size/binary, Bin/binary>> ->
898	    NFD = FD#bb{pos = AtPos+Size, bin = Bin},
899	    {NFD, {ok, B}};
900	<<_:Skip/binary, Bin/binary>> when byte_size(Bin) > 0 ->
901	    NFD = FD#bb{pos = AtPos+byte_size(Bin), bin = <<>>},
902	    {NFD, {ok, Bin}};
903        _ ->
904            {FD, eof}
905    end.
906
907filename(BB) when is_binary(BB#bb.source) ->
908    BB#bb.source;
909filename(BB) ->
910    list_to_atom(BB#bb.source).
911
912beam_filename(Bin) when is_binary(Bin) ->
913    Bin;
914beam_filename(File) ->
915    filename:rootname(File, ".beam") ++ ".beam".
916
917%% Do not attempt to uncompress if we have the proper .beam format.
918%% This clause matches binaries given as input.
919maybe_uncompress(<<"FOR1",_/binary>>=Binary) ->
920    Binary;
921%% This clause matches the iolist read from files.
922maybe_uncompress([<<"FOR1",_/binary>>|_]=IOData) ->
923    iolist_to_binary(IOData);
924maybe_uncompress(IOData) ->
925    try
926	zlib:gunzip(IOData)
927    catch
928	_:_ -> iolist_to_binary(IOData)
929    end.
930
931compress(IOData) ->
932    zlib:gzip(IOData).
933
934%% -> ok | throw(Error)
935assert_directory(FileName) ->
936    case filelib:is_dir(FileName) of
937	true ->
938	    ok;
939	false ->
940	    error({not_a_directory, FileName})
941    end.
942
943-spec file_error(file:filename(), {'error',atom()}) -> no_return().
944
945file_error(FileName, {error, Reason}) ->
946    error({file_error, FileName, Reason}).
947
948-spec error(term()) -> no_return().
949
950error(Reason) ->
951    throw({error, ?MODULE, Reason}).
952
953%% The following chunks must be kept when stripping a BEAM file.
954
955significant_chunks() ->
956    ["Line" | md5_chunks()].
957
958%% The following chunks are significant when calculating the MD5
959%% for a module. They are listed in the order that they should be MD5:ed.
960
961md5_chunks() ->
962    ["Atom", "AtU8", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"].
963
964%% The following chunks are mandatory in every Beam file.
965
966mandatory_chunks() ->
967    ["Code", "ExpT", "ImpT", "StrT"].
968
969%%% ====================================================================
970%%% The rest of the file handles encrypted debug info.
971%%%
972%%% Encrypting the debug info is only useful if you want to
973%%% have the debug info available all the time (maybe even in a live
974%%% system), but don't want to risk that anyone else but yourself
975%%% can use it.
976%%% ====================================================================
977
978-record(state, {crypto_key_f :: crypto_fun() | 'undefined'}).
979
980-define(CRYPTO_KEY_SERVER, beam_lib__crypto_key_server).
981
982decrypt_chunk(Type, Module, File, Id, Bin) ->
983    try
984	KeyString = get_crypto_key({debug_info, Type, Module, File}),
985	{Type,Key,IVec,_BlockSize} = make_crypto_key(Type, KeyString),
986	ok = start_crypto(),
987	NewBin = crypto:crypto_one_time(des_ede3_cbc, Key, IVec, Bin, false),
988	binary_to_term(NewBin)
989    catch
990	_:_ ->
991	    error({key_missing_or_invalid, File, Id})
992    end.
993
994old_anno_from_term({raw_abstract_v1, Forms}) ->
995    {raw_abstract_v1, anno_from_forms(Forms)};
996old_anno_from_term({Tag, Forms}) when Tag =:= abstract_v1;
997                                      Tag =:= abstract_v2 ->
998    try {Tag, anno_from_forms(Forms)}
999    catch
1000        _:_ ->
1001            {Tag, Forms}
1002    end;
1003old_anno_from_term(T) ->
1004    T.
1005
1006anno_from_term({debug_info_v1=Tag1, erl_abstract_code=Tag2, {Forms, Opts}}) ->
1007    try {Tag1, Tag2, {anno_from_forms(Forms), Opts}}
1008    catch
1009        _:_ ->
1010            {Tag1, Tag2, {Forms, Opts}}
1011    end;
1012anno_from_term(T) ->
1013    T.
1014
1015anno_from_forms(Forms0) ->
1016    %% Forms with record field types created before OTP 19.0 are
1017    %% replaced by well-formed record forms holding the type
1018    %% information.
1019    Forms = epp:restore_typed_record_fields(Forms0),
1020    [erl_parse:anno_from_term(Form) || Form <- Forms].
1021
1022start_crypto() ->
1023    case crypto:start() of
1024	{error, {already_started, _}} ->
1025	    ok;
1026	ok ->
1027	    ok
1028    end.
1029
1030get_crypto_key(What) ->
1031    call_crypto_server({get_crypto_key, What}).
1032
1033call_crypto_server(Req) ->
1034    try
1035	gen_server:call(?CRYPTO_KEY_SERVER, Req, infinity)
1036    catch
1037	exit:{noproc,_} ->
1038	    %% Not started.
1039	    call_crypto_server_1(Req);
1040	exit:{normal,_} ->
1041	    %% The process finished just as we called it.
1042	    call_crypto_server_1(Req)
1043    end.
1044
1045call_crypto_server_1(Req) ->
1046    case gen_server:start({local,?CRYPTO_KEY_SERVER}, ?MODULE, [], []) of
1047	{ok, _} -> ok;
1048	{error, {already_started, _}} -> ok
1049    end,
1050    erlang:yield(),
1051    call_crypto_server(Req).
1052
1053-spec init([]) -> {'ok', #state{}}.
1054
1055init([]) ->
1056    {ok, #state{}}.
1057
1058-type calls() :: 'clear_crypto_key_fun'
1059               | {'crypto_key_fun', _}
1060               | {'get_crypto_key', _}.
1061
1062-spec handle_call(calls(), {pid(), term()}, #state{}) ->
1063        {'noreply', #state{}} |
1064	{'reply', 'error' | {'error','badfun' | 'exists'}, #state{}} |
1065	{'stop', 'normal', 'undefined' | {'ok', term()}, #state{}}.
1066
1067handle_call({get_crypto_key, _}=R, From, #state{crypto_key_f=undefined}=S) ->
1068    case crypto_key_fun_from_file() of
1069	error ->
1070	    {reply, error, S};
1071	F when is_function(F) ->
1072	    %% The init function for the fun has already been called.
1073	    handle_call(R, From, S#state{crypto_key_f=F})
1074    end;
1075handle_call({get_crypto_key, What}, From, #state{crypto_key_f=F}=S) ->
1076    try
1077	Result = F(What),
1078	%% The result may hold information that we don't want
1079	%% lying around. Reply first, then GC, then noreply.
1080	gen_server:reply(From, Result),
1081	erlang:garbage_collect(),
1082	{noreply, S}
1083    catch
1084	_:_ ->
1085	    {reply, error, S}
1086    end;
1087handle_call({crypto_key_fun, F}, {_,_} = From, S) ->
1088    case S#state.crypto_key_f of
1089	undefined ->
1090	    if is_function(F, 1) ->
1091		    {Result, Fun, Reply} =
1092			case catch F(init) of
1093			    ok ->
1094				{true, F, ok};
1095			    {ok, F1} when is_function(F1) ->
1096				if
1097				    is_function(F1, 1) ->
1098					{true, F1, ok};
1099				    true ->
1100					{false, undefined,
1101					 {error, badfun}}
1102				end;
1103			    {error, Reason} ->
1104				{false, undefined, {error, Reason}};
1105			    {'EXIT', Reason} ->
1106				{false, undefined, {error, Reason}}
1107			end,
1108		    gen_server:reply(From, Reply),
1109		    erlang:garbage_collect(),
1110		    NewS = case Result of
1111			       true ->
1112				   S#state{crypto_key_f = Fun};
1113			       false ->
1114				   S
1115			   end,
1116		    {noreply, NewS};
1117	       true ->
1118		    {reply, {error, badfun}, S}
1119	    end;
1120	OtherF when is_function(OtherF) ->
1121	    {reply, {error, exists}, S}
1122    end;
1123handle_call(clear_crypto_key_fun, _From, S) ->
1124    case S#state.crypto_key_f of
1125	undefined ->
1126	    {stop,normal,undefined,S};
1127	F ->
1128	    Result = (catch F(clear)),
1129	    {stop,normal,{ok,Result},S}
1130    end.
1131
1132-spec handle_cast(term(), #state{}) -> {'noreply', #state{}}.
1133
1134handle_cast(_, State) ->
1135    {noreply, State}.
1136
1137-spec handle_info(term(), #state{}) -> {'noreply', #state{}}.
1138
1139handle_info(_, State) ->
1140    {noreply, State}.
1141
1142-spec code_change(term(), #state{}, term()) -> {'ok', #state{}}.
1143
1144code_change(_OldVsn, State, _Extra) ->
1145    {ok, State}.
1146
1147-spec terminate(term(), #state{}) -> 'ok'.
1148
1149terminate(_Reason, _State) ->
1150    ok.
1151
1152crypto_key_fun_from_file() ->
1153    case init:get_argument(home) of
1154	{ok,[[Home]]} ->
1155	    crypto_key_fun_from_file_1([".",Home]);
1156	_ ->
1157	    crypto_key_fun_from_file_1(["."])
1158    end.
1159
1160crypto_key_fun_from_file_1(Path) ->
1161    case f_p_s(Path, ".erlang.crypt") of
1162	{ok, KeyInfo, _} ->
1163	    try_load_crypto_fun(KeyInfo);
1164	_ ->
1165	    error
1166    end.
1167
1168f_p_s(P, F) ->
1169    case file:path_script(P, F) of
1170	{error, enoent} ->
1171	    {error, enoent};
1172	{error, {Line, _Mod, _Term}=E} ->
1173	    error("file:path_script(~tp,~tp): error on line ~p: ~ts~n",
1174		  [P, F, Line, file:format_error(E)]),
1175	    ok;
1176	{error, E} when is_atom(E) ->
1177	    error("file:path_script(~tp,~tp): ~ts~n",
1178		  [P, F, file:format_error(E)]),
1179	    ok;
1180	Other ->
1181	    Other
1182    end.
1183
1184try_load_crypto_fun(KeyInfo) when is_list(KeyInfo) ->
1185    T = ets:new(keys, [private, set]),
1186    foreach(
1187      fun({debug_info, Mode, M, Key}) when is_atom(M) ->
1188	      ets:insert(T, {{debug_info,Mode,M,[]}, Key});
1189	 ({debug_info, Mode, [], Key}) ->
1190	      ets:insert(T, {{debug_info, Mode, [], []}, Key});
1191	 (Other) ->
1192	      error("unknown key: ~p~n", [Other])
1193      end, KeyInfo),
1194    fun({debug_info, Mode, M, F}) ->
1195	    alt_lookup_key(
1196	      [{debug_info,Mode,M,F},
1197	       {debug_info,Mode,M,[]},
1198	       {debug_info,Mode,[],[]}], T);
1199       (clear) ->
1200	    ets:delete(T);
1201       (_) ->
1202	    error
1203    end;
1204try_load_crypto_fun(KeyInfo) ->
1205    error("unrecognized crypto key info: ~p\n", [KeyInfo]).
1206
1207alt_lookup_key([H|T], Tab) ->
1208    case ets:lookup(Tab, H) of
1209	[] ->
1210	    alt_lookup_key(T, Tab);
1211	[{_, Val}] ->
1212	    Val
1213    end;
1214alt_lookup_key([], _) ->
1215    error.
1216
1217error(Fmt, Args) ->
1218    error_logger:error_msg(Fmt, Args),
1219    error.
1220