1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 1998-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: %%% Purpose: Test NT specific utilities
   20: -module(nt_SUITE).
   21: 
   22: -include_lib("test_server/include/test_server.hrl").
   23: -include_lib("kernel/include/file.hrl").
   24: 
   25: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   26: 	 init_per_group/2,end_per_group/2,init_per_testcase/2,
   27: 	 end_per_testcase/2,nt/1,handle_eventlog/2,
   28: 	 middleman/1,service_basic/1, service_env/1, user_env/1, synced/1, 
   29: 	 service_prio/1, 
   30: 	 logout/1, debug/1, restart/1, restart_always/1,stopaction/1,
   31: 	 shutdown_io/0,do_shutdown_io/0]).
   32: -define(TEST_TIMEOUT, ?t:seconds(180)).
   33: 
   34: -define(TEST_SERVICES, [1,2,3,4,5,6,7,8,9,10,11]).
   35: 
   36: suite() -> [{ct_hooks,[ts_install_cth]}].
   37: 
   38: all() -> 
   39:     case os:type() of
   40: 	{win32, nt} ->
   41: 	    [nt, service_basic, service_env, user_env, synced,
   42: 	     service_prio, logout, debug, restart, restart_always,
   43: 	     stopaction];
   44: 	_ -> [nt]
   45:     end.
   46: 
   47: groups() -> 
   48:     [].
   49: 
   50: init_per_suite(Config) ->
   51:     Config.
   52: 
   53: end_per_suite(_Config) ->
   54:     ok.
   55: 
   56: init_per_group(_GroupName, Config) ->
   57:     Config.
   58: 
   59: end_per_group(_GroupName, Config) ->
   60:     Config.
   61: 
   62: 
   63: init_per_testcase(_Func, Config) ->
   64:     Dog = test_server:timetrap(?TEST_TIMEOUT),
   65:     [{watchdog, Dog} | Config].
   66: 
   67: end_per_testcase(_Func, Config) ->
   68:     lists:foreach(fun(X) -> 
   69: 			  catch remove_service("test_service_" ++ 
   70: 					 integer_to_list(X)) end,
   71: 		  ?TEST_SERVICES),
   72:     Dog = ?config(watchdog, Config),
   73:     catch test_server:timetrap_cancel(Dog),
   74:     ok.    
   75: 
   76: erlsrv() ->
   77:     os:find_executable(erlsrv).
   78: 
   79: 
   80: recv_prog_output(Port) -> 
   81:     receive
   82: 	{Port, {data, {eol,Data}}} ->
   83: 	    %%io:format("Got data: ~s~n", [Data]),
   84: 	    [ Data | recv_prog_output(Port)];
   85: 	_X ->
   86: 	    %%io:format("Got data: ~p~n", [_X]),
   87: 	    Port ! {self(), close},
   88: 	    receive
   89: 		_ ->
   90: 		    []
   91: 	    end
   92:     end.
   93: 
   94: 	    
   95: %%% X == parameters to erlsrv
   96: %%% returns command output without stderr
   97: do_command(X) ->
   98:     %%io:format("Command: ~s~n", [erlsrv() ++ " " ++ X]),
   99:     Port = open_port({spawn, erlsrv() ++ " " ++ X}, [stream, {line, 100}, eof, in]), 
  100:     Res = recv_prog_output(Port),
  101:     case Res of
  102: 	[] ->
  103: 	    failed;
  104: 	_Y ->
  105: 	    %%io:format("~p~n",[_Y]),
  106: 	    ok
  107:     end.
  108: 
  109: 
  110: create_service(Name) ->
  111:     ok = do_command("add " ++ Name).
  112: 
  113: start_service(Name) ->
  114:     ok = do_command("start " ++ Name).
  115: 
  116: stop_service(Name) ->
  117:     ok = do_command("stop " ++ Name).
  118: 
  119: remove_service(Name) ->
  120:     ok = do_command("remove " ++ Name).
  121: do_wait_for_it(_,0) ->
  122:     false;
  123: do_wait_for_it(FullName,N) ->
  124:     case net_adm:ping(FullName) of
  125: 	pong ->
  126: 	    true;
  127: 	_ ->
  128: 	    receive 
  129: 	    after 1000 ->
  130: 		    do_wait_for_it(FullName,N-1)
  131: 	    end
  132:     end.
  133: 
  134: wait_for_node(Name) ->
  135:     FullName = make_full_name(Name),
  136:     do_wait_for_it(FullName,30).
  137: 
  138: make_full_name(Name) ->
  139:     [_,Suffix] = string:tokens(atom_to_list(node()),"@"),
  140:     list_to_atom(Name ++ "@" ++ Suffix).
  141:     
  142: 
  143: %%% The following tests are only run on NT:
  144: 
  145: service_basic(doc) ->
  146:     ["Check some basic (cosmetic) service parameters"];
  147: service_basic(suite) -> [];
  148: service_basic(Config) when is_list(Config) ->
  149:     ?line Name = "test_service_20",
  150:     ?line IntName = Name++"_internal",
  151:     ?line Service = [{servicename,Name},
  152: 		     {args, ["-setcookie", 
  153: 		      atom_to_list(erlang:get_cookie())]},
  154: 		     {internalservicename,IntName},
  155: 		     {comment,"Epic comment"}],
  156:     ?line ok = erlsrv:store_service(Service),
  157:     ?line start_service(Name),
  158:     ?line true = wait_for_node(Name),
  159:     ?line S2 = erlsrv:get_service(Name),
  160:     ?line {value,{comment,"Epic comment"}} = lists:keysearch(comment,1,S2),
  161:     ?line {value,{internalservicename,IntName}} =
  162: 	lists:keysearch(internalservicename,1,S2),
  163:     ?line S3 = lists:keyreplace(comment,1,S2,{comment,"Basic comment"}),
  164:     ?line S4 = lists:keyreplace(internalservicename,1,S3,
  165: 				{internalservicename,"WillNotHappen"}),
  166:     ?line ok = erlsrv:store_service(S4),
  167:     ?line S5 = erlsrv:get_service(Name),
  168:     ?line {value,{comment,"Basic comment"}} = lists:keysearch(comment,1,S5),
  169:     ?line {value,{internalservicename,IntName}} =
  170: 	lists:keysearch(internalservicename,1,S5),
  171:     ?line NewName = "test_service_21",
  172:     ?line S6 = erlsrv:new_service(NewName,S5,[]), % should remove 
  173: 						  % internalservicename
  174:     ?line ok = erlsrv:store_service(S6),
  175:     ?line S7 = erlsrv:get_service(NewName),
  176:     ?line {value,{comment,"Basic comment"}} = lists:keysearch(comment,1,S7),
  177:     ?line {value,{internalservicename,[$t,$e,$s,$t | _]}} =
  178: 	lists:keysearch(internalservicename,1,S7),
  179:     ?line remove_service(Name),
  180:     ?line remove_service(NewName),
  181:     ok.
  182: 
  183: service_env(doc) ->
  184:     ["Check that service name and executable is in the environment of the " ++ 
  185:      "erlang process created by erlsrv."];
  186: service_env(suite) -> [];
  187: service_env(Config) when is_list(Config) ->
  188:     ?line Name = "test_service_2",
  189:     ?line Service = [{servicename,Name},
  190: 		     {args, ["-setcookie", 
  191: 		      atom_to_list(erlang:get_cookie())]}],
  192:     ?line ok = erlsrv:store_service(Service),
  193:     ?line start_service(Name),
  194:     ?line true = wait_for_node(Name),
  195:     ?line Name = rpc:call(make_full_name(Name),os,getenv,
  196: 			  ["ERLSRV_SERVICE_NAME"]),
  197:     ?line "erlsrv.exe" = filename:basename(
  198: 			   hd(
  199: 			     string:tokens(
  200: 			       rpc:call(make_full_name(Name),
  201: 					os,
  202: 					getenv,
  203: 					["ERLSRV_EXECUTABLE"]),
  204: 			       "\""))),
  205:     ?line remove_service(Name),
  206:     ok.
  207: user_env(doc) ->
  208:     ["Check that the user defined environment is ADDED to the service's"++
  209:      " normal dito."];
  210: user_env(suite) -> [];
  211: user_env(Config) when is_list(Config) ->
  212:     ?line Name = "test_service_3",
  213:     ?line Service = [{servicename,Name},{env,[{"HUBBA","BUBBA"}]},
  214: 		     {args, ["-setcookie", 
  215: 		      atom_to_list(erlang:get_cookie())]}],
  216:     ?line ok = erlsrv:store_service(Service),
  217:     ?line start_service(Name),
  218:     ?line true = wait_for_node(Name),
  219:     ?line true = rpc:call(make_full_name(Name),os,getenv,
  220: 			  ["SystemDrive"]) =/= false,
  221:     ?line "BUBBA" = rpc:call(make_full_name(Name),os,getenv,["HUBBA"]),
  222:     ?line remove_service(Name),
  223:     ok.
  224: synced(doc) -> 
  225:     ["Check that services are stopped and started syncronous and that"++
  226:      " failed stopactions kill the erlang machine anyway."];
  227: synced(suite) -> [];
  228: synced(Config) when is_list(Config) ->
  229:     ?line Name0 = "test_service_4",
  230:     ?line Service0 = [{servicename,Name0},
  231: 		      {machine, "N:\\nickeNyfikenPaSjukhus"}],
  232:     ?line ok = erlsrv:store_service(Service0),
  233:     ?line true = (catch start_service(Name0)) =/= ok,
  234:     ?line remove_service(Name0),
  235:     ?line Name = "test_service_5",
  236:     ?line Service = [{servicename,Name},
  237: 		     {stopaction,"erlang:info(garbage_collection)."},
  238: 		     {args, ["-setcookie",      
  239: 			     atom_to_list(erlang:get_cookie())]}],
  240:     ?line ok = erlsrv:store_service(Service),
  241:     ?line start_service(Name),
  242:     ?line true = wait_for_node(Name),
  243:     ?line T1 = calendar:datetime_to_gregorian_seconds(
  244: 		 calendar:universal_time()),
  245:     ?line stop_service(Name),
  246:     ?line Diff1 = calendar:datetime_to_gregorian_seconds(
  247: 		    calendar:universal_time()) - T1,
  248:     ?line true = Diff1 > 30,
  249:     ?line start_service(Name),
  250:     ?line true = wait_for_node(Name),
  251:     ?line T2 = calendar:datetime_to_gregorian_seconds(
  252: 		 calendar:universal_time()),
  253:     ?line remove_service(Name),
  254:     ?line Diff2 = calendar:datetime_to_gregorian_seconds(
  255: 		    calendar:universal_time()) - T2,
  256:     ?line true = Diff2 > 30,
  257:     ok.
  258: service_prio(doc) ->
  259:     ["Check that a service with higher prio create port programs with "
  260:      "higher prio."]; 
  261: service_prio(suite) -> [];
  262: service_prio(Config) when is_list(Config) ->
  263:     ?line Name = "test_service_6",
  264:     ?line Service = [{servicename,Name},{prio,"high"},
  265: 		     {env, [{"HEART_COMMAND","echo off"}]},
  266: 		     {args, ["-setcookie", 
  267: 			     atom_to_list(erlang:get_cookie()),
  268: 			     "-heart"]}],
  269:     ?line ok = erlsrv:store_service(Service),
  270:     ?line {ok, OldProcs} = get_current_procs(Config),
  271:     ?line start_service(Name),
  272:     ?line {ok, NewProcs} = get_current_procs(Config),
  273:     timer:sleep(2000),
  274:     ?line {ok, NewProcs2} = get_current_procs(Config),
  275:     ?line remove_service(Name),
  276:     ?line Diff = arrived_procs(OldProcs,NewProcs),
  277:     io:format("NewProcs ~p~n after sleep~n ~p~n",[Diff, arrived_procs(OldProcs,NewProcs2)]),
  278:     %% Not really correct, could fail if another heart is
  279:     %% started at the same time...
  280:     ?line {value, {"heart.exe",_,"high"}} = 
  281: 	lists:keysearch("heart.exe",1,Diff),
  282:     ok.
  283: logout(doc) -> 
  284:     ["Check that logout does not kill services"];
  285: logout(suite) -> [];
  286: logout(Config) when is_list(Config) ->
  287:     ?line {comment, "Have to be run manually by registering a service with " ++
  288: 	   "heart, logout and log in again and then examine that the heart " ++
  289: 	   "process id is not changed."}.
  290: debug(doc) ->
  291:     ["Check the debug options to erlsrv."];
  292: debug(suite) -> [];
  293: debug(Config) when is_list(Config) ->
  294:     ?line Name0 = "test_service_7",
  295: 
  296:     %% We used to set the privdir as temporary directory, but for some
  297:     %% reason we don't seem to have write access to that directory,
  298:     %% so we'll use the directory specified in the next line.
  299:     ?line TempDir = "C:/TEMP",
  300:     ?line Service0 = [{servicename,Name0},
  301:   		      {workdir,filename:nativename(TempDir)},
  302: 		      {debugtype,"reuse"},
  303: 		      {args, ["-setcookie", 
  304: 			      atom_to_list(erlang:get_cookie())]}],
  305:     ?line ok = erlsrv:store_service(Service0),
  306:     ?line T1 = calendar:datetime_to_gregorian_seconds(
  307: 		 calendar:local_time()),
  308:     %% sleep a little
  309:     ?line receive after 2000 -> ok end,
  310:     ?line start_service(Name0),
  311:     ?line true = wait_for_node(Name0),
  312:     ?line LF = filename:join(TempDir, Name0++".debug"),
  313:     ?line {ok,Info0} = file:read_file_info(LF),
  314:     ?line T2 = calendar:datetime_to_gregorian_seconds(
  315: 		 Info0#file_info.mtime),
  316:     ?line true = T2 > T1,
  317:     ?line remove_service(Name0),
  318:     ?line file:delete(LF),
  319:     ?line Name1 = "test_service_8",
  320:     ?line Service1 = [{servicename,Name1},
  321: 		      {workdir, filename:nativename(TempDir)},
  322: 		      {debugtype,"new"},
  323: 		      {args, ["-setcookie", 
  324: 			      atom_to_list(erlang:get_cookie())]}],
  325:     ?line ok = erlsrv:store_service(Service1),
  326:     ?line T3 = calendar:datetime_to_gregorian_seconds(
  327: 		 calendar:local_time()),
  328:     %% sleep a little
  329:     ?line receive after 2000 -> ok end,
  330:     ?line NF = next_logfile(TempDir, Name1),
  331:     ?line start_service(Name1),
  332:     ?line true = wait_for_node(Name1),
  333:     ?line {ok,Info1} = file:read_file_info(NF),
  334:     ?line T4 = calendar:datetime_to_gregorian_seconds(
  335: 		 Info1#file_info.mtime),
  336:     ?line true = T4 > T3,
  337:     ?line remove_service(Name1),
  338:     ?line file:delete(NF),
  339:     ok.
  340: 
  341: restart(doc) ->
  342:     ["Check the restart options to erlsrv"];
  343: restart(suite) -> [];
  344: restart(Config) when is_list(Config) ->
  345:     ?line Name = "test_service_9",
  346:     ?line Service = [{servicename,Name},
  347: 		     {workdir, filename:nativename(logdir(Config))},
  348: 		     {onfail,"restart"},
  349: 		     {args, ["-setcookie", 
  350: 			     atom_to_list(erlang:get_cookie())]}],
  351:     ?line ok = erlsrv:store_service(Service),
  352:     ?line start_service(Name),
  353:     ?line true = wait_for_node(Name),
  354:     ?line receive after 20000 -> ok end,
  355:     ?line rpc:call(make_full_name(Name),erlang,halt,[]),
  356:     ?line receive after 1000 -> ok end,
  357:     ?line true = wait_for_node(Name),
  358:     ?line rpc:call(make_full_name(Name),erlang,halt,[]),
  359:     ?line receive after 1000 -> ok end,
  360:     ?line false = wait_for_node(Name),
  361:     ?line remove_service(Name),
  362:     ok.
  363: 
  364: restart_always(doc) ->
  365:     ["Check the restart options to erlsrv"];
  366: restart_always(suite) -> [];
  367: restart_always(Config) when is_list(Config) ->
  368:     ?line Name = "test_service_10",
  369:     ?line Service = [{servicename,Name},
  370: 		     {workdir, filename:nativename(logdir(Config))},
  371: 		     {onfail,"restart_always"},
  372: 		     {args, ["-setcookie", 
  373: 			     atom_to_list(erlang:get_cookie())]}],
  374:     ?line ok = erlsrv:store_service(Service),
  375:     ?line start_service(Name),
  376:     ?line true = wait_for_node(Name),
  377:     ?line rpc:call(make_full_name(Name),erlang,halt,[]),
  378:     ?line receive after 1000 -> ok end,
  379:     ?line true = wait_for_node(Name),
  380:     ?line rpc:call(make_full_name(Name),erlang,halt,[]),
  381:     ?line receive after 1000 -> ok end,
  382:     ?line true = wait_for_node(Name),
  383:     ?line remove_service(Name),
  384:     ok.
  385: stopaction(doc) ->
  386:     ["Check that stopaction does not hang output while shutting down"];
  387: stopaction(suite) -> [];
  388: stopaction(Config) when is_list(Config) ->
  389:     ?line Name = "test_service_11",
  390:     %% Icky, I prepend the first element in the codepath, cause
  391:     %% I "suppose" it's the one to where I am. 
  392:     ?line Service = [{servicename,Name},
  393: 		     {stopaction,atom_to_list(?MODULE) ++ ":shutdown_io()."},
  394: 		     {args, ["-setcookie", 
  395: 			     atom_to_list(erlang:get_cookie()),
  396: 			     "-pa", hd(code:get_path())]}],
  397:     ?line ok = erlsrv:store_service(Service),
  398:     ?line start_service(Name),
  399:     ?line true = wait_for_node(Name),
  400:     ?line T1 = calendar:datetime_to_gregorian_seconds(
  401: 		 calendar:universal_time()),
  402:     ?line stop_service(Name),
  403:     ?line Diff1 = calendar:datetime_to_gregorian_seconds(
  404: 		    calendar:universal_time()) - T1,
  405:     ?line true = Diff1 < 30,
  406:     ?line remove_service(Name),
  407:     ok.
  408: 
  409: 
  410: %%% This test is run on all platforms, but just gives a comment on 
  411: %%% other platforms than NT.
  412: 
  413: nt(doc) ->
  414:     ["Run NT specific tests."];
  415: nt(suite) ->
  416:     [];
  417: nt(Config) when is_list(Config) ->
  418:     case os:type() of
  419: 	{win32,nt} ->
  420: 	    nt_run();
  421: 	_ ->
  422: 	    {skipped, "This test case is intended for Win NT only."}
  423:     end.
  424: 
  425: 
  426: nt_run() ->
  427:     ?line start_all(),
  428:     ?line create_service("test_service_1"),
  429:     ?line R = start_look_for_single("System","ErlSrv","Informational",
  430: 				    ".*test_service_1.*started.*"),
  431:     ?line start_service("test_service_1"),
  432:     ?line Res = look_for_single(R),
  433:     ?line io:format("Result from eventlog: ~p~n",
  434: 	      [Res]),
  435:     ?line remove_service("test_service_1"),
  436:     ?line stop_all(),
  437:     ok.
  438: 
  439: start_all() ->
  440:     Pid1 = spawn_link(?MODULE,middleman,[[]]),
  441:     register(?MODULE,Pid1),
  442:     _Pid2 = nteventlog:start("log_testing",
  443: 			     {?MODULE,handle_eventlog,[Pid1]}).
  444: 
  445: stop_all() ->
  446:     ?MODULE ! stop,
  447:     nteventlog:stop().
  448: 
  449: start_look_for_single(Cat,Fac,Sev,MessRE) ->
  450:     Ref = make_ref(),
  451:     ?MODULE ! {lookfor, {self(), Ref, {Cat,Fac,Sev,MessRE}}},    
  452:     Ref.
  453: 
  454: look_for_single(Ref) ->
  455:     receive
  456: 	{Ref,Time,Mes} ->
  457: 	    {Time,Mes}
  458:     after 60000 ->
  459: 	    timeout
  460:     end.
  461: 
  462: 
  463: %%% Mes = {Time,Category,Facility,Severity,Message}
  464: handle_eventlog(Mes,Pid) ->
  465:     Pid ! Mes.
  466: 
  467: %%% Waitfor = [{Pid, Ref, {Category,Facility,Severity,MessageRE}} ...]
  468: middleman(Waitfor) ->
  469:     receive 
  470: 	{Time,Category,Facility,Severity,Message} ->
  471: 	    io:format("Middleman got ~s...", [Message]),
  472: 	    case match_event({Time,Category,Facility,Severity,Message},
  473: 			     Waitfor) of
  474: 		{ok, {Pid,Ref,Time,Mes}, Rest} ->
  475: 		    io:format("matched~n"),
  476: 		    Pid ! {Ref,Time,Mes},
  477: 		    middleman(Rest);
  478: 		_ ->
  479: 		    io:format("no match~n"),
  480: 		    middleman(Waitfor)
  481: 	    end;
  482: 	{lookfor, X} ->
  483: 	    io:format("Middleman told to look for ~p~n", [X]),
  484: 	    middleman([X|Waitfor]);
  485: 	stop ->
  486: 	    stopped;
  487: 	_ ->
  488: 	    middleman(Waitfor)
  489:     end.
  490: 
  491: 
  492: %%% Matches events, not tail recursive.
  493: match_event(_X, []) ->
  494:     nomatch;
  495: match_event({Time,Cat,Fac,Sev,Mes},[{Pid,Ref,{Cat,Fac,Sev,MesRE}} | Tail]) ->
  496:     case re:run(Mes,MesRE,[{capture,none}]) of
  497: 	match ->
  498: 	    %%io:format("Match!~n"),
  499: 	    {ok,{Pid,Ref,Time,Mes},Tail};
  500: 	nomatch ->
  501: 	    %%io:format("No match~n"),
  502: 	    case match_event({Time,Cat,Fac,Sev,Mes},Tail) of
  503: 		{ok,X,Rest} ->
  504: 		    {ok,X,[{Pid,Ref,{Cat,Fac,Sev,MesRE}} | Rest]};
  505: 		X ->
  506: 		    X
  507: 	    end
  508:     end;
  509: match_event(X,[Y | T]) ->
  510:     %%io:format("X == ~p, Y == ~p~n",[X,Y]),
  511:     case match_event(X,T) of
  512: 	{ok,Z,R} ->
  513: 	    {ok,Z,[Y|R]};
  514: 	XX ->
  515: 	    XX
  516:     end.
  517: 
  518: arrived_procs(_,[]) ->
  519:     [];
  520: arrived_procs(OldProcs,[{Executable, Pid, Priority} | TNewProcs]) ->
  521:     case lists:keysearch(Pid,2,OldProcs) of
  522: 	{value, _} ->
  523: 	    arrived_procs(OldProcs, TNewProcs);
  524: 	false ->
  525: 	    [{Executable, Pid, Priority} | arrived_procs(OldProcs, TNewProcs)]
  526:     end.
  527:     
  528: 
  529: get_current_procs(Config) -> 
  530:     ?line P = open_port({spawn,nt_info(Config) ++ " -E"},
  531: 			[{line,10000}]),
  532:     ?line L = receive
  533: 		  {P,{data,{eol,D}}} ->
  534: 		      D;
  535: 		  _ -> "error. "
  536: 	      end,
  537:     ?line P ! {self(), close},
  538:     ?line receive
  539: 	      {P, closed} -> ok
  540: 	  end,
  541:     ?line {done,{ok,Tok,_},_} = erl_scan:tokens([],L,0),
  542:     ?line erl_parse:parse_term(Tok).
  543: 
  544: nt_info(Config) when is_list(Config) ->
  545:     ?line filename:join(?config(data_dir, Config), "nt_info").
  546: 
  547: 
  548: logdir(Config) ->
  549:     ?line ?config(priv_dir, Config).
  550: 
  551: look_for_next(Template,L,N) ->
  552:     ?line FN = Template ++ integer_to_list(N),
  553:     ?line case lists:member(FN,L) of
  554: 	true ->
  555: 	    ?line look_for_next(Template,L,N+1);
  556: 	false ->
  557: 	    ?line FN
  558:     end.
  559: 
  560: next_logfile(LD, Servicename) ->
  561:     ?line {ok, Files} = file:list_dir(LD),
  562:     ?line Ftmpl = Servicename ++ ".debug.",
  563:     ?line filename:join(LD,look_for_next(Ftmpl,Files,1)).
  564: 
  565: %%% Functions run by the service
  566: 
  567: do_shutdown_io() ->
  568:     receive
  569:     after 2000 ->
  570: 	    io:format("IO in shutting down...~n"),
  571: 	    erlang:halt()
  572:     end.
  573: 
  574: shutdown_io() ->
  575:     spawn(?MODULE,do_shutdown_io,[]).