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