1%%%  This code was developped by IDEALX (http://IDEALX.org/) and
2%%%  contributors (their names can be found in the CONTRIBUTORS file).
3%%%  Copyright (C) 2000-2001 IDEALX
4%%%
5%%%  This program is free software; you can redistribute it and/or modify
6%%%  it under the terms of the GNU General Public License as published by
7%%%  the Free Software Foundation; either version 2 of the License, or
8%%%  (at your option) any later version.
9%%%
10%%%  This program is distributed in the hope that it will be useful,
11%%%  but WITHOUT ANY WARRANTY; without even the implied warranty of
12%%%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13%%%  GNU General Public License for more details.
14%%%
15%%%  You should have received a copy of the GNU General Public License
16%%%  along with this program; if not, write to the Free Software
17%%%  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18%%%
19
20%%% In addition, as a special exception, you have the permission to
21%%% link the code of this program with any library released under
22%%% the EPL license and distribute linked combinations including
23%%% the two.
24
25-module(ts_utils).
26-vc('$Id$ ').
27-author('nicolas.niclausse@niclux.org').
28
29-include("ts_macros.hrl").
30
31%% to get file_info record definition
32-include_lib("kernel/include/file.hrl").
33
34%% user interface
35-export([debug/3, debug/4, get_val/1, init_seed/0, chop/1, elapsed/2,
36         now_sec/0, node_to_hostname/1, add_time/2, keyumerge/3, key1search/2,
37         level2int/1, mkey1search/2, datestr/0, datestr/1, size_or_length/1,
38         erl_system_args/0, erl_system_args/1, setsubdir/1, export_text/1,
39         foreach_parallel/2, spawn_par/3, inet_setopts/3, resolve/2,
40         stop_all/2, stop_all/3, stop_all/4, join/2, split/2, split2/2, split2/3,
41         make_dir/1, make_dir_raw/1, is_ip/1, from_https/1, to_https/1, keymax/2,
42         check_sum/3, check_sum/5, clean_str/1, file_to_list/1, term_to_list/1,
43         decode_base64/1, encode_base64/1, to_lower/1,
44         randomstr/1,urandomstr/1,urandomstr_noflat/1, eval/1, list_to_number/1,
45         time2sec/1, time2sec_hires/1, read_file_raw/1, init_seed/1, jsonpath/2,
46         concat_atoms/1, ceiling/1, accept_loop/3, append_to_filename/3, splitchar/2,
47         randombinstr/1,urandombinstr/1,log_transaction/1,conv_entities/1, wildcard/2,
48         ensure_all_started/2, pmap/2, pmap/3, get_node_id/0, filtermap/2, new_ets/2,
49         is_controller/0, spread_list/1, pack/1, random_alphanumstr/1]).
50
51level2int("debug")     -> ?DEB;
52level2int("info")      -> ?INFO;
53level2int("notice")    -> ?NOTICE;
54level2int("warning")   -> ?WARN;
55level2int("error")     -> ?ERR;
56level2int("critical")  -> ?CRIT;
57level2int("emergency") -> ?EMERG.
58
59-define(QUOT,""").
60-define(APOS,"'").
61-define(AMP,"&").
62-define(GT,">").
63-define(LT,"<").
64-define(DUPSTR_SIZE,20).
65-define(DUPSTR,"qxvmvtglimieyhemzlxc").
66-define(DUPBINSTR_SIZE,20).
67-define(DUPBINSTR,<<"qxvmvtglimieyhemzlxc">>).
68
69%%----------------------------------------------------------------------
70%% Func: get_val/1
71%% Purpose: return environnement variable value for the current application
72%% Returns: Value | {undef_var, Var}
73%%----------------------------------------------------------------------
74get_val(Var) ->
75    case application:get_env(Var) of
76        {ok, Val} ->
77            ensure_string(Var, Val);
78        undefined -> % undef, application not started, try to get var from stdlib
79            case application:get_env(stdlib,Var) of
80                undefined -> {undef_var, Var};
81                {ok,Val}  -> ensure_string(Var, Val)
82            end
83    end.
84
85
86
87%% ensure atom to string conversion of environnement variable
88%% This is intended to fix a problem making tsung run under Windows
89%%  I convert parameter that are called from the command-line
90ensure_string(log_file, Atom) when is_atom(Atom) ->
91    atom_to_list(Atom);
92ensure_string(proxy_log_file, Atom) when is_atom(Atom) ->
93    atom_to_list(Atom);
94ensure_string(config_file, Atom) when is_atom(Atom) ->
95    atom_to_list(Atom);
96ensure_string(exclude_tag, Atom) when is_atom(Atom) ->
97    atom_to_list(Atom);
98ensure_string(_, Other) ->
99    Other.
100
101%%----------------------------------------------------------------------
102%% Func: debug/3
103%% Purpose: print debug message if level is high enough
104%%----------------------------------------------------------------------
105debug(From, Message, Level) ->
106    debug(From, Message, [], Level).
107
108debug(From, Message, Args, Level) ->
109    Debug_level = ?config(debug_level),
110    if
111        Level =< Debug_level ->
112            error_logger:info_msg("~20s:(~p:~p) "++ Message,
113                                  [From, Level, self()] ++ Args);
114        true ->
115            nodebug
116    end.
117
118%%----------------------------------------------------------------------
119%% Func: elapsed/2
120%% Purpose: print elapsed time in milliseconds
121%% Returns: integer
122%%----------------------------------------------------------------------
123elapsed({Before1, Before2, Before3}, {After1, After2, After3}) ->
124    After  = After1  * 1000000000  + After2  * 1000 + After3/1000,
125    Before = Before1 * 1000000000  + Before2 * 1000 + Before3/1000,
126    case After - Before of
127        Neg when Neg < 0 -> % time duration must not be negative
128             0;
129        Val -> Val
130    end;
131elapsed(Before, After)->
132    Elapsed=After-Before,
133    MicroSec = erlang:convert_time_unit(Elapsed, native, micro_seconds),
134    MicroSec / 1000.
135
136%%----------------------------------------------------------------------
137%% Func: chop/1
138%% Purpose: remove trailing "\n"
139%%----------------------------------------------------------------------
140chop(String) ->
141    string:strip(String, right, 10).
142
143%%----------------------------------------------------------------------
144%% Func: clean_str/1
145%% Purpose: remove "\n" and space at the beginning and at that end of a string
146%%----------------------------------------------------------------------
147clean_str(String) ->
148    Str1 = string:strip(String, both, 10),
149    Str2 = string:strip(Str1),
150    Str3 = string:strip(Str2, both, 10),
151    string:strip(Str3).
152
153
154%%----------------------------------------------------------------------
155%% Func: init_seed/1
156%%----------------------------------------------------------------------
157init_seed(now)->
158    init_seed();
159init_seed(A) when is_integer(A)->
160    %% in case of a distributed test, we don't want each launcher to
161    %% have the same seed, therefore, we need to know the id of the
162    %% node to set a reproductible but different seed for each launcher.
163    Id=get_node_id(),
164    ?DebugF("Seeding with ~p on node ~p~n",[Id,node()]),
165    random:seed(1000*Id,-1000*A*Id,1000*A*A);
166init_seed({A,B}) when is_integer(A) and is_integer(B)->
167    Id=get_node_id(),
168    ?DebugF("Seeding with ~p ~p ~p on node ~p~n",[A,B,Id,node()]),
169    %% init_seed with 2 args is called by ts_client, with increasing
170    %% values of A, and fixed B. If the seeds are too closed, the
171    %% initial pseudo random values will be quite closed to each
172    %% other. Trying to avoid this by using a multiplier big enough
173    %% (because the algorithm use mod 30XXX , see random.erl).
174    random:seed(4000*A*B*Id,-4000*B*A*Id,4000*Id*Id*A);
175init_seed({A,B,C}) ->
176    random:seed(A,B,C).
177
178get_node_id() ->
179    case string:tokens(atom_to_list(node()),"@") of
180        ["tsung_control"++_,_]    -> 123456;
181        ["tsung"++Tail,_]         ->
182            {match, [I]} = re:run(Tail, "\\d+$", [{capture, all, list}]), %" add comment for erlang-mode bug
183            list_to_integer(I);
184        _                         -> 654321
185    end.
186
187%% @spec is_controller() -> true|false
188%% @doc return true if the caller is running on the controller node
189%% @end
190is_controller() ->
191    case string:tokens(atom_to_list(node()),"@") of
192        ["tsung_control"++_,_]    -> true;
193        _ ->false
194    end.
195
196%%----------------------------------------------------------------------
197%% Func: init_seed/0
198%%----------------------------------------------------------------------
199init_seed()->
200    init_seed(?TIMESTAMP).
201
202%%----------------------------------------------------------------------
203%% Func: now_sec/0
204%% Purpose: returns unix like elapsed time in sec
205%% TODO: we should use erlang:system_time(seconds) when we drop < R18 compat
206%%----------------------------------------------------------------------
207now_sec() ->
208    time2sec(?TIMESTAMP).
209
210time2sec({MSec, Seconds, _}) ->
211    Seconds+1000000*MSec.
212
213time2sec_hires({MSec, Seconds, MuSec}) ->
214    Seconds+1000000*MSec+MuSec/1000000.
215
216%%----------------------------------------------------------------------
217%% Func: add_time/2
218%% Purpose: add given Seconds to given Time (same format as now())
219%%----------------------------------------------------------------------
220add_time({MSec, Seconds, MicroSec}, SecToAdd) when is_integer(SecToAdd)->
221    NewSec = Seconds +SecToAdd,
222    case NewSec < 1000000 of
223        true -> {MSec, NewSec, MicroSec};
224        false ->{MSec+ (NewSec div 1000000), NewSec-1000000, MicroSec}
225    end;
226add_time(Time, SecToAdd) when is_integer(SecToAdd)->
227	MicroSec = erlang:convert_time_unit(Time, native, micro_seconds)+SecToAdd*1000000,
228	erlang:convert_time_unit(MicroSec, micro_seconds, native).
229
230node_to_hostname(Node) ->
231    [_Nodename, Hostname] = string:tokens( atom_to_list(Node), "@"),
232    {ok, Hostname}.
233
234to_lower(String)->
235    string:to_lower(String).
236
237encode_base64(String)->
238    base64:encode_to_string(String).
239
240decode_base64(Base64)->
241    base64:decode_to_string(Base64).
242
243%%----------------------------------------------------------------------
244%% Func: filtermap/2
245%% Purpose lists:zf is called lists:filtermap in erlang R16B1 and newer
246%%
247%%----------------------------------------------------------------------
248filtermap(Fun, List)->
249    case erlang:function_exported(lists, filtermap, 2) of
250        true ->
251            lists:filtermap(Fun,List);
252        false ->
253            lists:zf(Fun,List)
254    end.
255
256%%----------------------------------------------------------------------
257%% Func: key1search/2
258%% Purpose: wrapper around httpd_utils module funs (maybe one day
259%%          these functions will be added to the stdlib)
260%%----------------------------------------------------------------------
261key1search(Tuple,String)->
262    proplists:get_value(String,Tuple).
263
264%%----------------------------------------------------------------------
265%% Func: mkey1search/2
266%% Purpose: multiple key1search:
267%%  Take as input list of {Key, Value} tuples (length 2).
268%%  Return the list of values corresponding to a given key
269%%  It is assumed here that there might be several identical keys in the list
270%%  unlike the lists:key... functions.
271%%----------------------------------------------------------------------
272mkey1search(List, Key) ->
273    Results = lists:foldl(
274                fun({MatchKey, Value}, Acc) when MatchKey == Key ->
275                        [Value | Acc];
276                   ({_OtherKey, _Value}, Acc) ->
277                        Acc
278                end,
279                [],
280                List),
281    case Results of
282        [] -> undefined;
283        Results -> lists:reverse(Results)
284    end.
285
286%%----------------------------------------------------------------------
287%% datestr/0
288%% Purpose: print date as a string 'YYYYMMDD-HHMM'
289%%----------------------------------------------------------------------
290datestr()->
291    datestr(erlang:localtime()).
292
293%%----------------------------------------------------------------------
294%% datestr/1
295%%----------------------------------------------------------------------
296datestr({{Y,M,D},{H,Min,_S}})->
297    io_lib:format("~w~2.10.0b~2.10.0b-~2.10.0b~2.10.0b",[Y,M,D,H,Min]).
298
299%%----------------------------------------------------------------------
300%% erl_system_args/0
301%%----------------------------------------------------------------------
302erl_system_args()->
303    erl_system_args(extended).
304erl_system_args(basic)->
305    Rsh = case  init:get_argument(rsh) of
306              {ok,[[Value]]}  -> " -rsh " ++ Value;
307              _ -> " "
308          end,
309    lists:append([Rsh, " -detached -setcookie  ",
310                  atom_to_list(erlang:get_cookie()) ]);
311erl_system_args(extended)->
312    BasicArgs  = erl_system_args(basic),
313    SetArg = fun(A) -> case init:get_argument(A) of
314                           error     -> " ";
315                           {ok,[[]]} -> " -" ++atom_to_list(A)++" ";
316                           {ok,[[Val|_]]} when is_list(Val)-> " -" ++atom_to_list(A)++" "++Val++" "
317                       end
318             end,
319    Shared = SetArg(shared),
320    Hybrid = SetArg(hybrid),
321    case  {?config(smp_disable), erlang:system_info(otp_release)} of
322        {true,"R"++_} ->
323            Smp = " -smp disable ";
324        {true,V} when (V =:= "17" orelse V =:= "18" orelse V =:= "19") ->
325            Smp = " -smp disable ";
326        {true,_} ->
327            Smp = " +S 1  ";
328        _    ->
329            Smp = SetArg(smp)
330    end,
331    Inet = case init:get_argument(kernel) of
332               {ok,[["inetrc",InetRcFile]]} ->
333                   ?LOGF("Get inetrc= ~p~n",[InetRcFile],?NOTICE),
334                   " -kernel inetrc '"++ InetRcFile ++ "'" ;
335               _ -> " "
336           end,
337    Proto = case init:get_argument(proto_dist) of
338                {ok,[["inet6_tcp"]]}->
339                    ?LOG("IPv6 used for erlang distribution~n",?NOTICE),
340                   " -proto_dist inet6_tcp " ;
341               _ -> " "
342           end,
343    ListenMin = case application:get_env(kernel,inet_dist_listen_min) of
344                    undefined -> "";
345                    {ok, Min} -> " -kernel inet_dist_listen_min " ++ integer_to_list(Min)++ " "
346                end,
347    ListenMax = case application:get_env(kernel,inet_dist_listen_max) of
348                    undefined -> "";
349                    {ok, Max} -> " -kernel inet_dist_listen_max " ++ integer_to_list(Max)++" "
350                end,
351    SSLCache = case application:get_env(ssl,session_cb) of
352                   {ok, CB} when is_atom(CB) -> " -ssl session_cb " ++ atom_to_list(CB)++" ";
353                   _ -> ""
354               end,
355    SSLLifetime = case application:get_env(ssl,session_lifetime) of
356                   {ok, Time} when is_integer(Time) -> " -ssl session_lifetime " ++ integer_to_list(Time)++" ";
357                   _ -> ""
358               end,
359    SSLCacheSize = case application:get_env(tsung,ssl_session_cache) of
360                   {ok, Reuse} when is_integer(Reuse)-> " -tsung reuse_sessions " ++ integer_to_list(Reuse)++" ";
361                   _ -> ""
362               end,
363    Threads= "+A "++integer_to_list(erlang:system_info(thread_pool_size))++" ",
364    ProcessMax="+P "++integer_to_list(erlang:system_info(process_limit))++" ",
365    Mea = case  erlang:system_info(version) of
366              "5.3" ++ _Tail     -> " +Mea r10b ";
367              _ -> " "
368          end,
369    lists:append([BasicArgs, Shared, Hybrid, Smp, Mea, Inet, Proto, Threads,
370                  ProcessMax,ListenMin,ListenMax,SSLCache,SSLLifetime,SSLCacheSize]).
371
372%%----------------------------------------------------------------------
373%% setsubdir/1
374%% Purpose: all log files are created in a directory whose name is the
375%%          start date of the test.
376%% ----------------------------------------------------------------------
377setsubdir(FileName) ->
378    Date = datestr(),
379    Path = filename:dirname(FileName),
380    Base = filename:basename(FileName),
381    Dir  = filename:join(Path, Date),
382    case file:make_dir(Dir) of
383        ok ->
384            {ok, {Dir, Base}};
385        {error, eexist} ->
386            ?DebugF("Directory ~s already exist~n",[Dir]),
387            {ok, {Dir, Base}};
388        Err ->
389            ?LOGF("Can't create directory ~s (~p)!~n",[Dir, Err],?EMERG),
390            {error, Err}
391    end.
392
393%%----------------------------------------------------------------------
394%% export_text/1
395%% Purpose: Escape special characters `<', `&', `'' and `"' flattening
396%%          the text.
397%%----------------------------------------------------------------------
398export_text(T) ->
399    export_text(T, []).
400
401export_text(Bin, Cont) when is_binary(Bin) ->
402    export_text(binary_to_list(Bin), Cont);
403export_text([], Exported) ->
404    lists:flatten(lists:reverse(Exported));
405export_text([$< | T], Cont) ->
406    export_text(T, [?LT | Cont]);
407export_text([$> | T], Cont) ->
408    export_text(T, [?GT | Cont]);
409export_text([$& | T], Cont) ->
410    export_text(T, [?AMP | Cont]);
411export_text([$' | T], Cont) -> %'
412    export_text(T, [?APOS | Cont]);
413export_text([$" | T], Cont) -> %"
414    export_text(T, [?QUOT | Cont]);
415export_text([C | T], Cont) ->
416    export_text(T, [C | Cont]).
417
418%%----------------------------------------------------------------------
419%% stop_all/2
420%%----------------------------------------------------------------------
421stop_all(Host, Name) ->
422    stop_all(Host, Name, "Tsung").
423
424stop_all([Host],Name,MsgName)  ->
425    VoidFun = fun(_A)-> ok end,
426    stop_all([Host],Name,MsgName, VoidFun).
427
428stop_all([Host],Name,MsgName,Fun) when is_atom(Host) ->
429    _List= net_adm:world_list([Host]),
430    global:sync(),
431    case global:whereis_name(Name) of
432        undefined ->
433            Msg = MsgName ++" is not running on " ++ atom_to_list(Host),
434            erlang:display(Msg);
435        Pid ->
436            Controller_Node = node(Pid),
437            Fun(Controller_Node),
438            slave:stop(Controller_Node)
439    end;
440stop_all(_,_,_,_)->
441    erlang:display("Bad Hostname").
442
443%%----------------------------------------------------------------------
444%% make_dir/1
445%% Purpose: create directory. Missing parent directories ARE created
446%%----------------------------------------------------------------------
447make_dir(DirName) ->
448    make_dir_rec(DirName,file).
449
450make_dir_raw(DirName) ->
451    make_dir_rec(DirName,prim_file).
452
453make_dir_rec(DirName,FileMod) when is_list(DirName) ->
454    case  FileMod:read_file_info(DirName) of
455        {ok, #file_info{type=directory}} ->
456            ok;
457        {error,enoent} ->
458            make_dir_rec("", FileMod,filename:split(DirName));
459        {error, Reason}  ->
460            {error,Reason}
461    end.
462
463make_dir_rec(_Path, _FileMod, []) ->
464    ok;
465make_dir_rec(Path, FileMod,[Parent|Childs]) ->
466    CurrentDir=filename:join([Path,Parent]),
467    case  FileMod:read_file_info(CurrentDir) of
468        {ok, #file_info{type=directory}} ->
469            make_dir_rec(CurrentDir, FileMod,Childs);
470        {error,enoent} ->
471            case FileMod:make_dir(CurrentDir) of
472                ok ->
473                    make_dir_rec(CurrentDir, FileMod, Childs);
474                {error, eexist} ->
475                    make_dir_rec(CurrentDir, FileMod, Childs);
476                Error ->
477                    Error
478            end;
479        {error, Reason}  ->
480            {error,Reason}
481    end.
482
483%% check if a string is an IPv4 address (as "192.168.0.1")
484is_ip(String) when is_list(String) ->
485    EightBit="(2[0-4][0-9]|25[0-5]|1[0-9][0-9]|[0-9][0-9]|[0-9])",
486    RegExp = lists:append(["^",EightBit,"\.",EightBit,"\.",EightBit,"\.",EightBit,"$"]), %"
487    case re:run(String, RegExp) of
488       {match,_} -> true;
489       _ -> false
490    end;
491is_ip(_) -> false.
492
493%%----------------------------------------------------------------------
494%% to_https/1
495%% Purpose: rewrite https URL, to act as a pure non ssl proxy
496%%----------------------------------------------------------------------
497to_https({url, "http://-"++Rest})-> "https://" ++ Rest;
498to_https({url, URL})-> URL;
499to_https({request, {body,Data}}) when is_list(Data) ->
500    %% body request, no headers
501    {ok, re:replace(Data,"http://-","https://",[global])};
502to_https({request, S="CONNECT"++_Rest}) -> {ok,S};
503to_https({request, []}) -> {ok, []};
504to_https({request, String}) when is_list(String) ->
505    EndOfHeader = string:str(String, "\r\n\r\n"),
506    Header = string:substr(String, 1, EndOfHeader - 1) ++ "\r\n",
507    Body = string:substr(String, EndOfHeader + 4),
508    ReOpts=[global,{return,list}],
509    TmpHeader = re:replace(Header,"http://-","https://",ReOpts),
510    TmpHeader2 = re:replace(TmpHeader,"Accept-Encoding: [0-9,a-z_ ]+\r\n","",ReOpts++[caseless]),
511    RealHeader = re:replace(TmpHeader2,"Host: -","Host: ",ReOpts++[caseless]),
512    RealBody = re:replace(Body,"http://-","https://",ReOpts),
513    RealString = RealHeader++  "\r\n" ++ RealBody,
514    {ok, RealString}.
515
516
517%% @spec from_https(string()) -> {ok, string() | iodata()}
518%% @doc replace https links with 'http://-'
519%% @end
520from_https(String) when is_list(String)->
521    ReOpts=[{newline,crlf},multiline,global,caseless],
522    %% remove Secure from Set-Cookie (TSUN-120)
523    TmpData = re:replace(String,"(.*set-cookie:.*); *secure(.*$.*$)","\\1\\2",ReOpts),
524    Data=re:replace(TmpData,"https://","http://-",[global]),
525    {ok, Data}.
526
527%% concatenate a list of atoms
528concat_atoms(Atoms) when is_list(Atoms) ->
529    String =lists:foldl(fun(A,Acc) ->
530                                Acc++atom_to_list(A) end, "", Atoms),
531    list_to_atom(String).
532
533%% A Perl-style join --- concatenates all strings in Strings,
534%% separated by Sep.
535join(_Sep, []) -> [];
536join(Sep, List) when is_list(List)->
537    ToStr = fun(A) when is_integer(A) -> integer_to_list(A);
538               (A) when is_list(A) -> A;
539               (A) when is_float(A) -> io_lib:format("~.3f",[A]);
540               (A) when is_atom(A) -> atom_to_list(A);
541               (A) when is_binary(A) -> binary_to_list(A)
542            end,
543    string:join(lists:map(ToStr,List), Sep).
544
545%% split a string given a string (at first occurence of char)
546split(String,Chr) when is_list(String), is_list(Chr) ->
547    re:split(String,Chr,[{return,list}]);
548
549split(String,Chr) when is_binary(String), is_binary(Chr) ->
550    binary:split(String,[Chr],[global]).
551
552
553%% split a string given a char (faster)
554splitchar(String,Chr) ->
555    splitchar2(String,Chr,[],[]).
556splitchar2([],_,[],Acc) ->
557    lists:reverse(Acc);
558splitchar2([],_,AccChr,Acc) ->
559    lists:reverse([lists:reverse(AccChr)|Acc]);
560splitchar2([Chr|String],Chr,AccChr,Acc) ->
561    splitchar2(String,Chr,[],[lists:reverse(AccChr)|Acc]);
562splitchar2([Other|String],Chr,AccChr,Acc) ->
563    splitchar2(String,Chr,[Other|AccChr],Acc).
564
565
566%% split a string in 2 (at first occurence of char)
567split2(String,Chr) ->
568    split2(String,Chr,nostrip).
569
570split2(String,Chr,strip) -> % split and strip blanks
571    {A, B} = split2(String,Chr,nostrip),
572    {string:strip(A), string:strip(B)};
573split2(String,Chr,nostrip) ->
574    case string:chr(String, Chr) of
575        0   -> {String,[]};
576        Pos -> {string:substr(String,1,Pos-1), string:substr(String,Pos+1)}
577    end.
578
579
580foreach_parallel(Fun, List)->
581    SpawnFun = fun(A) -> spawn(?MODULE, spawn_par, lists:append([[Fun,self()], [A]])) end,
582    lists:foreach(SpawnFun, List),
583    wait_pids(length(List)).
584
585wait_pids(0) -> done;
586wait_pids(N) ->
587    receive
588        {ok, _Pid, _Res } ->
589            wait_pids(N-1)
590    after ?TIMEOUT_PARALLEL_SPAWN ->
591            {error, {timout, N}} % N missing answer
592    end.
593
594spawn_par(Fun, PidFrom, Args) ->
595    Res = Fun(Args),
596    PidFrom ! {ok, self(), Res}.
597
598%%----------------------------------------------------------------------
599%% Func: inet_setopts/3
600%% Purpose: set inet options depending on the protocol (gen_tcp, gen_udp,
601%%  ssl)
602%%----------------------------------------------------------------------
603inet_setopts(_, none, _) -> %socket was closed before
604    none;
605inet_setopts(ssl6, Socket, Opts) ->
606    inet_setopts(ssl, Socket, Opts);
607inet_setopts(ssl, Socket, Opts) ->
608    case ssl:setopts(Socket, Opts) of
609        ok ->
610            Socket;
611        {error, closed} ->
612            none;
613        Error ->
614            ?LOGF("Error while setting ssl options ~p ~p ~n", [Opts, Error], ?ERR),
615            none
616    end;
617inet_setopts(gen_tcp6, Socket,  Opts)->
618    inet_setopts(gen_tcp, Socket,  Opts);
619inet_setopts(gen_udp6, Socket,  Opts)->
620    inet_setopts(gen_udp, Socket,  Opts);
621inet_setopts(_Type, Socket,  Opts)->
622    case inet:setopts(Socket, Opts) of
623        ok ->
624            Socket;
625        {error, closed} ->
626            none;
627        Error ->
628            ?LOGF("Error while setting inet options ~p ~p ~n", [Opts, Error], ?ERR),
629            none
630    end.
631
632%%----------------------------------------------------------------------
633%% Func: check_sum/3
634%% Purpose: check sum of int equals 100.
635%% Args: List of tuples, index of int in tuple, Error msg
636%% Returns ok | {error, {bad_sum, Msg}}
637%%----------------------------------------------------------------------
638check_sum(RecList, Index, ErrorMsg) ->
639    %% popularity may be a float number. 5.10-2 precision
640    check_sum(RecList, Index, 100, 0.05, ErrorMsg).
641check_sum(RecList, Index, Total, Epsilon, ErrorMsg) ->
642    %% we use the tuple representation of a record !
643    Sum = lists:foldl(fun(X, Sum) -> element(Index,X)+Sum end, 0, RecList),
644    Delta = abs(Sum - Total),
645    case Delta < Epsilon of
646        true -> ok;
647        false -> {error, {bad_sum, Sum ,ErrorMsg}}
648    end.
649
650%%----------------------------------------------------------------------
651%% Func: file_to_list/1
652%% Purpose: read a file line by line and put them in a list
653%% Args: filename
654%% Returns {ok, List} | {error, Reason}
655%%----------------------------------------------------------------------
656file_to_list(FileName) ->
657    case file:open(FileName, [read]) of
658        {error, Reason} ->
659            {error, Reason};
660        {ok , File} ->
661            Lines = read_lines(File),
662            file:close(File),
663            {ok, Lines}
664    end.
665
666read_lines(FD) ->read_lines(FD,io:get_line(FD,""),[]).
667
668read_lines(_FD, eof, L) ->
669    lists:reverse(L);
670read_lines(FD, Line, L) ->
671    read_lines(FD, io:get_line(FD,""),[chop(Line)|L]).
672
673%%----------------------------------------------------------------------
674%% Func: keyumerge/3
675%% Purpose: Same as lists:keymerge, but remove duplicates (use items from A)
676%% Returns: List
677%%----------------------------------------------------------------------
678keyumerge(_N,[],B)->B;
679keyumerge(N,[A|Rest],B)->
680    Key = element(N,A),
681    % remove old values if it exists
682    NewB = lists:keydelete(Key, N, B),
683    keyumerge(N,Rest, [A|NewB]).
684
685%%----------------------------------------------------------------------
686%% Func: keymax/2
687%% Purpose: Return Max of Nth element of a list of tuples
688%% Returns: Number
689%%----------------------------------------------------------------------
690keymax(_N,[])-> 0;
691keymax(N,[L])-> element(N,L);
692keymax(N,[E|Tail])->
693    keymax(N,Tail,element(N,E)).
694
695keymax(_N,[],Max)-> Max;
696keymax(N,[E|Tail],Max)->
697    keymax(N,Tail,lists:max([Max,element(N,E)])).
698
699%%--------------------------------------------------------------------
700%% Function: resolve/2
701%% Description: return cached hostname or gethostbyaddr for given ip
702%%--------------------------------------------------------------------
703resolve(Ip, Cache) ->
704    case lists:keysearch(Ip, 1, Cache) of
705        {value, {Ip, ReverseHostname}} ->
706            {ReverseHostname, Cache};
707        false ->
708            case inet:gethostbyaddr(Ip) of
709                {ok, {hostent,ReverseHostname,_,inet,_,_}} ->
710                    %% cache dns result and return it
711                    ?LOGF("Add ~p -> ~p to DNS cache ~n", [Ip, ReverseHostname],?DEB),
712                    {ReverseHostname, [{Ip, ReverseHostname} | Cache]};
713                {error, Reason} ->
714                    ?LOGF("DNS resolution error on ~p: ~p~n", [Ip, Reason],?WARN),
715                    %% cache dns name as IP : {ip, ip} and return Ip
716                    NewCache = lists:keymerge(1, Cache, [{Ip, Ip}]),
717                    {Ip, NewCache}
718            end
719    end.
720
721%%----------------------------------------------------------------------
722%% @spec urandomstr_noflat(Size::integer()) ->string()
723%% @doc generate pseudo-random list of given size. Implemented by
724%% duplicating list of fixed size to be faster. unflatten version
725%% @end
726%%----------------------------------------------------------------------
727urandomstr_noflat(Size) when is_integer(Size) , Size >= ?DUPSTR_SIZE ->
728    Msg= lists:duplicate(Size div ?DUPSTR_SIZE,?DUPSTR),
729    case Size rem ?DUPSTR_SIZE of
730        0->
731            Msg;
732        Rest ->
733            lists:append(Msg,urandomstr_noflat(Rest))
734    end;
735urandomstr_noflat(Size)  when is_integer(Size), Size >= 0 ->
736    lists:nthtail(?DUPSTR_SIZE-Size, ?DUPSTR).
737
738%%----------------------------------------------------------------------
739%% @spec urandombinstr(Size::integer()) ->binary()
740%% @doc same as urandomstr/1, but returns a binary.
741%% @end
742%%----------------------------------------------------------------------
743urandombinstr(Size) when is_integer(Size) , Size >= ?DUPBINSTR_SIZE ->
744    Loop = Size div ?DUPBINSTR_SIZE,
745    Rest = Size rem ?DUPBINSTR_SIZE,
746    Res=lists:foldl(fun(_X,Acc)-> <<Acc/binary, ?DUPBINSTR/binary>> end, << >>,lists:seq(1,Loop)),
747    << Res/binary, ?DUPBINSTR:Rest/binary>>;
748urandombinstr(Size) when is_integer(Size), Size >= 0 ->
749    <<?DUPBINSTR:Size/binary>> .
750
751%%----------------------------------------------------------------------
752%% @spec urandomstr(Size::integer()) ->string()
753%% @doc same as urandomstr_noflat/1, but returns a flat list.
754%% @end
755%%----------------------------------------------------------------------
756urandomstr(Size) when is_integer(Size), Size >= 0 ->
757    lists:flatten(urandomstr_noflat(Size)).
758
759%%----------------------------------------------------------------------
760%% @spec randomstr(Size::integer()) ->string()
761%% @doc returns a random string. slow if Size is high.
762%% @end
763%%----------------------------------------------------------------------
764randomstr(Size) when is_integer(Size), Size >= 0 ->
765     lists:map(fun (_) -> random:uniform(25) + $a  end, lists:seq(1,Size)).
766
767random_alphanumstr(Size) when is_integer(Size), Size >= 0 ->
768    AllowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
769    S = length(AllowedChars),
770    lists:map(fun (_) -> lists:nth(random:uniform(S), AllowedChars) end, lists:seq(1,Size)).
771
772%%----------------------------------------------------------------------
773%% @spec randombinstr(Size::integer()) ->binary()
774%% @doc returns a random binary string. slow if Size is high.
775%% @end
776%%----------------------------------------------------------------------
777randombinstr(0) -> <<>>;
778randombinstr(Size) when is_integer(Size), Size > 0 ->
779    randombinstr(Size,<<>>).
780randombinstr(0,Bin) -> Bin;
781randombinstr(Size,Bin) ->
782    C=random:uniform(25)+$a,
783    randombinstr(Size-1, << Bin/binary, C >>).
784
785
786%%----------------------------------------------------------------------
787%% @spec eval(string()) -> term()
788%% @doc evaluate strings as Erlang code at runtime
789%% @end
790%%----------------------------------------------------------------------
791eval(Code) ->
792    {ok, Scanned, _} = erl_scan:string(lists:flatten(Code)),
793    {ok, Parsed} = erl_parse:parse_exprs(Scanned),
794    {value, Result, _} = erl_eval:exprs(Parsed,  erl_eval:new_bindings()),
795    Result.
796
797%%----------------------------------------------------------------------
798%% @spec list_to_number(string()) -> integer() | float()
799%% @doc  convert a 'number' to either int or float
800%% @end
801%%----------------------------------------------------------------------
802list_to_number(Number) ->
803    try list_to_integer(Number) of
804        Int -> Int
805  catch
806      error:_Reason ->
807          list_to_float(Number)
808  end.
809
810term_to_list(I) when is_integer(I)->
811    integer_to_list(I);
812term_to_list(I) when is_atom(I)->
813    atom_to_list(I);
814term_to_list(I) when is_list(I)->
815    I;
816term_to_list(I) when is_float(I)->
817    float_to_list(I);
818term_to_list(B) when is_binary(B)->
819    binary_to_list(B).
820
821read_file_raw(File) when is_list(File) ->
822    case {file:open(File,[read,raw,binary]), file:read_file_info(File)} of
823        { {ok,IODev}, {ok,#file_info{size=Size} } } ->
824            case file:pread(IODev,0,Size) of
825                {ok, Res} ->
826                    file:close(IODev),
827                    {ok, Res, Size};
828                Else ->
829                    ?LOGF("pread file ~p of size ~p: ~p~n",[File,Size,Else],?NOTICE),
830                    file:close(IODev),
831                    Else
832            end;
833        {{ok,IODev}, {error, Reason} } ->
834            file:close(IODev),
835            {error,Reason};
836        {{error,Reason},_} ->
837            {error, Reason}
838    end.
839
840
841%%----------------------------------------------------------------------
842%% @spec jsonpath(JSONPath::string(),JSON::iolist()) -> term()
843%% @doc  very limited implementation of JSONPath from JSON struct.
844%% @end
845%%----------------------------------------------------------------------
846jsonpath("$."++JSONPath,JSON) ->
847    jsonpath(JSONPath,JSON);
848jsonpath(JSONPath,JSON) ->
849    Fun= fun(A) ->
850                case catch list_to_integer(A) of
851                    I when is_integer(I) ->
852                        I+1;
853                    _Error ->
854                        list_to_binary(A)
855                end
856          end,
857    Str=re:replace(JSONPath,"\\[(.*?)\\]","\.\\1",[{return,list},global]),
858    Keys=lists:map(Fun, string:tokens(Str,".")),
859    json_get_bin(Keys,JSON).
860json_get_bin([],Val) ->
861    Val;
862json_get_bin([_Key|_Keys],undefined) ->
863    undefined;
864json_get_bin([N|Keys],L) when is_integer(N), N =< length(L) ->
865    Val =  lists:nth(N,L),
866    json_get_bin(Keys,Val);
867json_get_bin([N|Keys], L) when N =:= <<"*">>, is_list(L) ->
868    lists:map(fun(A) -> json_get_bin(Keys,A) end, L);
869json_get_bin([N|Keys],Val) when N =:= <<"*">> ->
870    json_get_bin(Keys,Val);
871json_get_bin([<<"?",Expr/binary>> | Keys],L) when  is_list(L) ->
872    case string:tokens(binary_to_list(Expr),"=") of
873        [Key,Val] ->
874            Fun = fun(S) -> case json_get_bin([list_to_binary(Key)],S) of
875                                Int when is_integer(Int) ->
876                                    integer_to_list(Int) =:= Val;
877                                Other when is_binary(Other)->
878                                    binary_to_list(Other) =:= Val
879                            end
880                  end,
881            ?LOG("ok~n",?ERR),
882            case lists:filter(Fun,L) of
883                [] ->
884                    undefined;
885                [Res] ->
886                    json_get_bin(Keys,Res);
887                Res ->
888                    lists:map(fun(A) -> json_get_bin(Keys,A) end, Res)
889            end;
890        _ ->
891            undefined
892    end;
893json_get_bin([Key|Keys],{struct,JSON}) when is_list(JSON) ->
894    Val =  proplists:get_value(Key,JSON),
895    json_get_bin(Keys,Val);
896json_get_bin(_,_) ->
897    undefined.
898
899%% Map function F over list L in parallel.
900pmap(F, L) ->
901    Parent = self(),
902    [receive {Pid, Result} -> Result end || Pid <- [spawn(fun() -> Parent ! {self(), F(X)} end) || X <- L]].
903
904
905%% Map function F over list L in parallel, with maximum NProcs in parallel
906%% FIXME: handle timeout
907pmap(F, L, NProcs) ->
908    pmap(F, L, NProcs,"").
909
910pmap(F, L, NProcs, Res) when length(L) > NProcs->
911    {Head, Tail} = lists:split(NProcs,L),
912    Parent = self(),
913    lists:foldl(fun(X, Acc) -> spawn(fun() -> Parent ! {pmap, self(), F(X), Acc} end), Acc+1  end, 0, Head),
914    NewRes = wait_result(NProcs,[]),
915    pmap(F,Tail, NProcs, Res ++ NewRes);
916
917pmap(F, L, _NProcs, Acc) ->
918    Acc ++ pmap(F,L).
919
920wait_result(0, Res)->
921    {_Ids, RealRes} = lists:unzip(lists:keysort(1, Res)),
922    RealRes;
923wait_result(Nprocs, Res)->
924    receive
925        {pmap, _Pid, Result, Id} ->
926            NewRes = Res ++ [{Id, Result}],
927            wait_result(Nprocs-1, NewRes)
928    end.
929
930%%
931ceiling(X) ->
932    T = erlang:trunc(X),
933        case (X - T) of
934        Neg when Neg < 0 ->
935                T;
936                   Pos when Pos > 0 -> T + 1;
937        _ -> T
938    end.
939
940%%--------------------------------------------------------------------
941%% Func: accept_loop/3
942%% Purpose: infinite listen/accept loop, delegating handling of accepts
943%%          to the gen_server proper.
944%% Returns: only returns by throwing an exception
945%%--------------------------------------------------------------------
946accept_loop(PPid, Tag, ServerSock)->
947    case
948        case gen_tcp:accept(ServerSock) of
949            {ok, ClientSock} ->
950                ok = gen_tcp:controlling_process(ClientSock, PPid),
951                gen_server:call(PPid, {accepted, Tag, ClientSock});
952            Error ->
953                gen_server:call(PPid, {accept_error, Tag, Error})
954        end
955        of
956        continue ->
957            accept_loop(PPid, Tag, ServerSock);
958        _->
959            normal
960    end.
961
962
963append_to_filename(Filename, From, To) ->
964    case re:replace(Filename,From,To, [{return,list},global] ) of
965        Filename ->  Filename ++"." ++ To;
966        RealName -> RealName
967    end.
968
969
970log_transaction([]) ->
971    "-";
972log_transaction([{TransactionName,_}| _Tail]) ->
973    TransactionName.
974
975%%--------------------------------------------------------------------
976%% Func: conv_entities/1
977%% Purpose: Convert html entities to string
978%%--------------------------------------------------------------------
979conv_entities(Binary)->
980    conv_entities(Binary,[]).
981conv_entities(<< >>,Acc) ->
982    list_to_binary(Acc);
983conv_entities(<< "&amp;", T/binary >> ,Acc) ->
984    conv_entities(T,[ Acc, << "&">>]);
985conv_entities(<< "&lt;", T/binary >>,Acc) ->
986    conv_entities(T,[ Acc, << "<">>]);
987conv_entities(<< "&gt;", T/binary >>,Acc) ->
988    conv_entities(T,[ Acc, << ">">>]);
989conv_entities(<<"&quot;", T/binary >>,Acc) ->
990    conv_entities(T,[ Acc, << "\"">>]);
991conv_entities(<<"&apos;", T/binary >>,Acc) ->
992    conv_entities(T,[ Acc, << "'">>]);
993conv_entities(<<H:1/binary, T/binary >>,Acc) ->
994    conv_entities(T,[ Acc, H]).
995
996%% start an application and it's dependencies recursively
997%% does the same as application:ensure_all_started (only in R16B2)
998ensure_all_started(App, Type) ->
999    start_ok(App, Type, application:start(App, Type)).
1000
1001start_ok(_App, _Type, ok) -> ok;
1002start_ok(_App, _Type, {error, {already_started, _App}}) -> ok;
1003start_ok(App, Type, {error, {not_started, Dep}}) ->
1004    ok = ensure_all_started(Dep, Type),
1005    ensure_all_started(App, Type);
1006start_ok(App, _Type, {error, Reason}) ->
1007    erlang:error({app_start_failed, App, Reason}).
1008
1009wildcard(Wildcard,Names) ->
1010    PatternTmp = re:replace("^"++Wildcard,"\\*",".*",[{return,list}]),
1011    Pattern = re:replace(PatternTmp,"\\?",".{1}",[{return,list}]) ++ "$" ,
1012    lists:filter(fun(N) -> re:run(N, Pattern) =/= nomatch end, Names).
1013
1014%% dummy comment with a " "to circumvent an  erlang-mode bug in emacs"
1015
1016%%--------------------------------------------------------------------
1017%% Func: new_ets/1
1018%% Purpose: Wrapper for ets:new/1 used in external modules
1019%% @spec new_ets(Prefix::binary(), UserId::integer()) -> string()
1020%% @doc init an ets:table
1021%% @end
1022%%--------------------------------------------------------------------
1023new_ets(Prefix, UserId)->
1024    EtsName = binary_to_list(Prefix) ++ "_" ++ integer_to_list(UserId),
1025    ?LOGF("create ets:table ~p ~n", [EtsName], ?INFO),
1026    ets:new(list_to_atom(EtsName), []).
1027
1028
1029size_or_length(Data) when is_binary(Data) ->
1030    size(Data);
1031size_or_length(Data) when is_list(Data) ->
1032    length(Data).
1033
1034%% given a list with successives duplicates, try to spread duplicates
1035%% all over the list. e.g. [a,a,a,b,b,c,c] -> [a,b,c,a,b,c,a]
1036spread_list(List) ->
1037    spread_list2(pack(List),[]).
1038
1039spread_list2([], Res) ->
1040    Res;
1041spread_list2(PackedList, OldRes) ->
1042    Fun = fun([A], {Res, ResTail})       -> {[A|Res], ResTail};
1043             ([A|ATail], {Res, ResTail}) -> {[A|Res], [ATail|ResTail]}
1044          end,
1045    {Res, Tail} = lists:foldl(Fun, {[],[]}, PackedList),
1046    spread_list2(lists:reverse(Tail), OldRes ++ lists:reverse(Res)).
1047
1048%% pack duplicates into sublists
1049%% http://lambdafoo.com/blog/2008/02/26/99-erlang-problems-1-15/
1050pack([]) ->
1051    [];
1052pack([H|[]]) ->
1053    [[H]];
1054pack([H,H|C]) ->
1055    [Head|Tail] = pack([H|C]),
1056    X = lists:append([H],Head),
1057    [X|Tail];
1058pack([H,H2|C]) ->
1059    if H =/= H2 ->
1060      [[H]|pack([H2|C])]
1061    end.
1062