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