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