1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2018. 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(os).
21
22%% Provides a common operating system interface.
23
24-export([type/0, version/0, cmd/1, cmd/2, find_executable/1, find_executable/2]).
25
26-include("file.hrl").
27
28-export_type([env_var_name/0, env_var_value/0, env_var_name_value/0]).
29
30-export([getenv/0, getenv/1, getenv/2, putenv/2, unsetenv/1,
31         getpid/0, env/0, perf_counter/0,
32         perf_counter/1, set_signal/2, system_time/0,
33         system_time/1, timestamp/0]).
34
35-type os_command() :: atom() | io_lib:chars().
36-type os_command_opts() :: #{ max_size => non_neg_integer() | infinity }.
37
38-export_type([os_command/0, os_command_opts/0]).
39
40-type env_var_name() :: nonempty_string().
41
42-type env_var_value() :: string().
43
44-type env_var_name_value() :: nonempty_string().
45
46%% We must inline these functions so that the stacktrace points to
47%% the correct function.
48-compile({inline, [badarg_with_cause/2, badarg_with_info/1]}).
49
50-spec env() -> [{env_var_name(), env_var_value()}].
51env() ->
52    erlang:nif_error(undef).
53
54-spec getenv(VarName) -> Value | false when
55      VarName :: env_var_name(),
56      Value :: env_var_value().
57getenv(_VarName) ->
58    erlang:nif_error(undef).
59
60-spec getpid() -> Value when
61      Value :: string().
62
63getpid() ->
64    erlang:nif_error(undef).
65
66-spec perf_counter() -> Counter when
67      Counter :: integer().
68
69perf_counter() ->
70    erlang:nif_error(undef).
71
72-spec perf_counter(Unit) -> integer() when
73      Unit :: erlang:time_unit().
74
75perf_counter(Unit) ->
76    try
77        erlang:convert_time_unit(os:perf_counter(), perf_counter, Unit)
78    catch
79        error:_ ->
80            badarg_with_info([Unit])
81    end.
82
83-spec putenv(VarName, Value) -> true when
84      VarName :: env_var_name(),
85      Value :: env_var_value().
86putenv(_VarName, _Value) ->
87    erlang:nif_error(undef).
88
89-spec system_time() -> integer().
90
91system_time() ->
92    erlang:nif_error(undef).
93
94-spec system_time(Unit) -> integer() when
95      Unit :: erlang:time_unit().
96
97system_time(_Unit) ->
98    erlang:nif_error(undef).
99
100-spec timestamp() -> Timestamp when
101      Timestamp :: erlang:timestamp().
102
103timestamp() ->
104    erlang:nif_error(undef).
105
106-spec unsetenv(VarName) -> true when
107      VarName :: env_var_name().
108unsetenv(_VarName) ->
109    erlang:nif_error(undef).
110
111-spec set_signal(Signal, Option) -> 'ok' when
112      Signal :: 'sighup'  | 'sigquit' | 'sigabrt' | 'sigalrm' |
113                'sigterm' | 'sigusr1' | 'sigusr2' | 'sigchld' |
114                'sigstop' | 'sigtstp',
115      Option :: 'default' | 'handle' | 'ignore'.
116
117set_signal(_Signal, _Option) ->
118    erlang:nif_error(undef).
119
120%%% End of BIFs
121
122-spec getenv() -> [env_var_name_value()].
123getenv() ->
124    [lists:flatten([Key, $=, Value]) || {Key, Value} <- os:env() ].
125
126-spec getenv(VarName, DefaultValue) -> Value when
127      VarName :: env_var_name(),
128      DefaultValue :: env_var_value(),
129      Value :: env_var_value().
130getenv(VarName, DefaultValue) ->
131    try os:getenv(VarName) of
132        false ->
133           DefaultValue;
134        Value ->
135            Value
136    catch
137        error:_ ->
138            badarg_with_info([VarName, DefaultValue])
139    end.
140
141-spec type() -> {Osfamily, Osname} when
142      Osfamily :: unix | win32,
143      Osname :: atom().
144
145type() ->
146    erlang:system_info(os_type).
147
148-spec version() -> VersionString | {Major, Minor, Release} when
149      VersionString :: string(),
150      Major :: non_neg_integer(),
151      Minor :: non_neg_integer(),
152      Release :: non_neg_integer().
153version() ->
154    erlang:system_info(os_version).
155
156-spec find_executable(Name) -> Filename | 'false' when
157      Name :: string(),
158      Filename :: string().
159find_executable(Name) ->
160    find_executable(Name, os:getenv("PATH", "")).
161
162-spec find_executable(Name, Path) -> Filename | 'false' when
163      Name :: string(),
164      Path :: string(),
165      Filename :: string().
166find_executable(Name, Path) ->
167    Extensions = extensions(),
168    case filename:pathtype(Name) of
169	relative ->
170	    find_executable1(Name, split_path(Path), Extensions);
171	_ ->
172	    case verify_executable(Name, Extensions, Extensions) of
173		{ok, Complete} ->
174		    Complete;
175		error ->
176		    false
177	    end
178    end.
179
180find_executable1(Name, [Base|Rest], Extensions) ->
181    Complete0 = filename:join(Base, Name),
182    case verify_executable(Complete0, Extensions, Extensions) of
183	{ok, Complete} ->
184	    Complete;
185	error ->
186	    find_executable1(Name, Rest, Extensions)
187    end;
188find_executable1(_Name, [], _Extensions) ->
189    false.
190
191verify_executable(Name0, [Ext|Rest], OrigExtensions) ->
192    Name1 = Name0 ++ Ext,
193    case file:read_file_info(Name1) of
194	{ok, #file_info{type=regular,mode=Mode}}
195	when Mode band 8#111 =/= 0 ->
196	    %% XXX This test for execution permission is not fool-proof
197	    %% on Unix, since we test if any execution bit is set.
198	    {ok, Name1};
199	_ ->
200	    verify_executable(Name0, Rest, OrigExtensions)
201    end;
202verify_executable(Name, [], OrigExtensions) when OrigExtensions =/= [""] -> %% Windows
203    %% Will only happen on windows, hence case insensitivity
204    case can_be_full_name(string:lowercase(Name),OrigExtensions) of
205	true ->
206	    verify_executable(Name,[""],[""]);
207	_ ->
208	    error
209    end;
210verify_executable(_, [], _) ->
211    error.
212
213can_be_full_name(_Name,[]) ->
214    false;
215can_be_full_name(Name,[H|T]) ->
216    case lists:suffix(H,Name) of %% Name is in lowercase, cause this is a windows thing
217	true ->
218	    true;
219	_ ->
220	    can_be_full_name(Name,T)
221    end.
222
223split_path(Path) ->
224    case type() of
225	{win32, _} ->
226	    {ok,Curr} = file:get_cwd(),
227	    split_path(Path, $;, [], [Curr]);
228	_ ->
229	    split_path(Path, $:, [], [])
230    end.
231
232split_path([Sep|Rest], Sep, Current, Path) ->
233    split_path(Rest, Sep, [], [reverse_element(Current)|Path]);
234split_path([C|Rest], Sep, Current, Path) ->
235    split_path(Rest, Sep, [C|Current], Path);
236split_path([], _, Current, Path) ->
237    lists:reverse(Path, [reverse_element(Current)]).
238
239reverse_element([]) -> ".";
240reverse_element([$"|T]) ->	%"
241    case lists:reverse(T) of
242	[$"|List] -> List;	%"
243	List -> List ++ [$"]	%"
244    end;
245reverse_element(List) ->
246    lists:reverse(List).
247
248-spec extensions() -> [string(),...].
249%% Extensions in lower case
250extensions() ->
251    case type() of
252	{win32, _} -> [".exe",".com",".cmd",".bat"];
253	{unix, _} -> [""]
254    end.
255
256%% Executes the given command in the default shell for the operating system.
257-spec cmd(Command) -> string() when
258      Command :: os_command().
259cmd(Cmd) ->
260    try
261        cmd(Cmd, #{ })
262    catch
263        error:_ ->
264            badarg_with_info([Cmd])
265    end.
266
267-spec cmd(Command, Options) -> string() when
268      Command :: os_command(),
269      Options :: os_command_opts().
270cmd(Cmd, Opts) ->
271    try
272        do_cmd(Cmd, Opts)
273    catch
274        throw:badopt ->
275            badarg_with_cause([Cmd, Opts], badopt);
276        error:_ ->
277            badarg_with_info([Cmd, Opts])
278    end.
279
280do_cmd(Cmd, Opts) ->
281    MaxSize = get_option(max_size, Opts, infinity),
282    {SpawnCmd, SpawnOpts, SpawnInput, Eot} = mk_cmd(os:type(), validate(Cmd)),
283    Port = open_port({spawn, SpawnCmd}, [binary, stderr_to_stdout,
284                                         stream, in, hide | SpawnOpts]),
285    MonRef = erlang:monitor(port, Port),
286    true = port_command(Port, SpawnInput),
287    Bytes = get_data(Port, MonRef, Eot, [], 0, MaxSize),
288    demonitor(MonRef, [flush]),
289    String = unicode:characters_to_list(Bytes),
290    if  %% Convert to unicode list if possible otherwise return bytes
291	is_list(String) -> String;
292	true -> binary_to_list(Bytes)
293    end.
294
295get_option(Opt, Options, Default) ->
296    case Options of
297        #{Opt := Value} -> Value;
298        #{} -> Default;
299        _ -> throw(badopt)
300    end.
301
302mk_cmd({win32,Wtype}, Cmd) ->
303    Command = case {os:getenv("COMSPEC"),Wtype} of
304                  {false,windows} -> lists:concat(["command.com /c", Cmd]);
305                  {false,_} -> lists:concat(["cmd /c", Cmd]);
306                  {Cspec,_} -> lists:concat([Cspec," /c",Cmd])
307              end,
308    {Command, [], [], <<>>};
309mk_cmd(_,Cmd) ->
310    %% Have to send command in like this in order to make sh commands like
311    %% cd and ulimit available.
312    %%
313    %% We use an absolute path here because we do not want the path to be
314    %% searched in case a stale NFS handle is somewhere in the path before
315    %% the sh command.
316    {"/bin/sh -s unix:cmd", [out],
317     %% We insert a new line after the command, in case the command
318     %% contains a comment character.
319     %%
320     %% The </dev/null closes stdin, which means that programs
321     %% that use a closed stdin as an termination indicator works.
322     %% An example of such a program is 'more'.
323     %%
324     %% The "echo ^D" is used to indicate that the program has executed
325     %% and we should return any output we have gotten. We cannot use
326     %% termination of the child or closing of stdin/stdout as then
327     %% starting background jobs from os:cmd will block os:cmd.
328     %%
329     %% I tried changing this to be "better", but got bombarded with
330     %% backwards incompatibility bug reports, so leave this as it is.
331     ["(", unicode:characters_to_binary(Cmd), "\n) </dev/null; echo \"\^D\"\n"],
332     <<$\^D>>}.
333
334validate(Atom) when is_atom(Atom) ->
335    validate(atom_to_list(Atom));
336validate(List) when is_list(List) ->
337    case validate1(List) of
338        false ->
339            List;
340        true ->
341            %% Had zeros at end; remove them...
342            string:trim(List, trailing, [0])
343    end.
344
345validate1([0|Rest]) ->
346    validate2(Rest);
347validate1([C|Rest]) when is_integer(C), C > 0 ->
348    validate1(Rest);
349validate1([List|Rest]) when is_list(List) ->
350    validate1(List) or validate1(Rest);
351validate1([]) ->
352    false.
353
354%% Ensure that the rest is zero only...
355validate2([]) ->
356    true;
357validate2([0|Rest]) ->
358    validate2(Rest);
359validate2([List|Rest]) when is_list(List) ->
360    validate2(List),
361    validate2(Rest).
362
363get_data(Port, MonRef, Eot, Sofar, Size, Max) ->
364    receive
365	{Port, {data, Bytes}} ->
366            case eot(Bytes, Eot, Size, Max) of
367                more ->
368                    get_data(Port, MonRef, Eot, [Sofar, Bytes],
369                             Size + byte_size(Bytes), Max);
370                Last ->
371                    catch port_close(Port),
372                    flush_until_down(Port, MonRef),
373                    iolist_to_binary([Sofar, Last])
374            end;
375        {'DOWN', MonRef, _, _, _} ->
376	    flush_exit(Port),
377	    iolist_to_binary(Sofar)
378    end.
379
380eot(Bs, <<>>, Size, Max) when Size + byte_size(Bs) < Max ->
381    more;
382eot(Bs, <<>>, Size, Max) ->
383    binary:part(Bs, {0, Max - Size});
384eot(Bs, Eot, Size, Max) ->
385    case binary:match(Bs, Eot) of
386        {Pos, _} when Size + Pos < Max ->
387            binary:part(Bs,{0, Pos});
388        _ ->
389            eot(Bs, <<>>, Size, Max)
390    end.
391
392%% When port_close returns we know that all the
393%% messages sent have been sent and that the
394%% DOWN message is after them all.
395flush_until_down(Port, MonRef) ->
396    receive
397        {Port, {data, _Bytes}} ->
398            flush_until_down(Port, MonRef);
399        {'DOWN', MonRef, _, _, _} ->
400            flush_exit(Port)
401    end.
402
403%% The exit signal is always delivered before
404%% the down signal, so we can be sure that if there
405%% was an exit message sent, it will be in the
406%% mailbox now.
407flush_exit(Port) ->
408    receive
409        {'EXIT',  Port,  _} ->
410            ok
411    after 0 ->
412            ok
413    end.
414
415badarg_with_cause(Args, Cause) ->
416    erlang:error(badarg, Args, [{error_info, #{module => erl_kernel_errors,
417                                               cause => Cause}}]).
418badarg_with_info(Args) ->
419    erlang:error(badarg, Args, [{error_info, #{module => erl_kernel_errors}}]).
420