%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2021. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(win32reg). -export([open/1, close/1, current_key/1, change_key/2, change_key_create/2, sub_keys/1, delete_key/1, value/2, values/1, set_value/3, delete_value/2, expand/1, format_error/1]). -export_type([reg_handle/0]). %% Key handles (always open). -define(hkey_classes_root, 16#80000000). -define(hkey_current_user, 16#80000001). -define(hkey_local_machine, 16#80000002). -define(hkey_users, 16#80000003). -define(hkey_performance_data, 16#80000004). -define(hkey_current_config, 16#80000005). -define(hkey_dyn_data, 16#80000006). %% Driver commands. -define(cmd_get_current, 0). -define(cmd_open_key, 1). -define(cmd_create_key, 2). -define(cmd_get_all_subkeys, 3). -define(cmd_get_value, 4). -define(cmd_get_all_values, 5). -define(cmd_set_value, 6). -define(cmd_delete_key, 7). -define(cmd_delete_value, 8). %% Data types. -define(reg_sc, 1). -define(reg_expand_sc, 2). -define(reg_binary, 3). -define(reg_dword, 4). %% Basic types internal to this file. -opaque reg_handle() :: {'win32reg',port()}. -type name() :: string() | 'default'. -type value() :: string() | integer() | binary(). %%% Exported functions. -spec open(OpenModeList) -> ReturnValue when OpenModeList :: [OpenMode], OpenMode :: 'read' | 'write', ReturnValue :: {'ok', RegHandle} | {'error', ErrorId :: 'enotsup'}, RegHandle :: reg_handle(). open(Modes) -> case os:type() of {win32, _} -> case open_mode(Modes, []) of {error, Reason} -> {error, Reason}; ModeStr -> P = open_port({spawn, "registry__drv__ " ++ ModeStr}, []), {ok, {win32reg, P}} end; _ -> {error, enotsup} end. -spec close(RegHandle) -> 'ok' when RegHandle :: reg_handle(). close({win32reg, Reg}) when is_port(Reg) -> unlink(Reg), exit(Reg, die), ok. -spec current_key(RegHandle) -> ReturnValue when RegHandle :: reg_handle(), ReturnValue :: {'ok', string()}. current_key({win32reg, Reg}) when is_port(Reg) -> Cmd = [?cmd_get_current], Reg ! {self(), {command, Cmd}}, {state, Hkey, Name} = get_result(Reg), Root = hkey_to_string(Hkey), {ok, case Name of [] -> Root; _ -> Root ++ [$\\|Name] end}. -spec change_key(RegHandle, Key) -> ReturnValue when RegHandle :: reg_handle(), Key :: string(), ReturnValue :: 'ok' | {'error', ErrorId :: atom()}. change_key({win32reg, Reg}, Key) when is_port(Reg) -> change_key(Reg, ?cmd_open_key, Key). -spec change_key_create(RegHandle, Key) -> ReturnValue when RegHandle :: reg_handle(), Key :: string(), ReturnValue :: 'ok' | {'error', ErrorId :: atom()}. change_key_create({win32reg, Reg}, Key) when is_port(Reg) -> change_key(Reg, ?cmd_create_key, Key). change_key(Reg, Cmd, Key) -> case parse_key(Key, Reg) of {ok, Hkey, Path} -> Reg ! {self(), {command, [Cmd, i32(Hkey), Path, 0]}}, get_result(Reg); {error, Reason} -> {error, Reason} end. -spec sub_keys(RegHandle) -> ReturnValue when RegHandle :: reg_handle(), ReturnValue :: {'ok', [SubKey]} | {'error', ErrorId :: atom()}, SubKey :: string(). sub_keys({win32reg, Reg}) when is_port(Reg) -> Cmd = [?cmd_get_all_subkeys], Reg ! {self(), {command, Cmd}}, collect_keys(Reg, []). -spec delete_key(RegHandle) -> ReturnValue when RegHandle :: reg_handle(), ReturnValue :: 'ok' | {'error', ErrorId :: atom()}. delete_key({win32reg, Reg}) when is_port(Reg) -> Cmd = [?cmd_get_current], Reg ! {self(), {command, Cmd}}, case get_result(Reg) of {state, _Hkey, []} -> {error, eperm}; {state, _Hkey, Rest} -> [Name|_] = split_key(Rest), ok = change_key(Reg, ?cmd_open_key, ".."), Cmd2 = [?cmd_delete_key], Reg ! {self(), {command, [Cmd2, Name, 0]}}, get_result(Reg); {error, Erorr} -> {error, Erorr} end. -spec set_value(RegHandle, Name, Value) -> ReturnValue when RegHandle :: reg_handle(), Name :: name(), Value :: value(), ReturnValue :: 'ok' | {'error', ErrorId :: atom()}. set_value({win32reg, Reg}, Name0, Value) when is_port(Reg) -> Name = case Name0 of default -> []; _ -> Name0 end, {Type, V} = term_to_value(Value), Cmd = [?cmd_set_value, Type, Name, 0, V], Reg ! {self(), {command, Cmd}}, get_result(Reg). -spec value(RegHandle, Name) -> ReturnValue when RegHandle :: reg_handle(), Name :: name(), ReturnValue :: {'ok', Value :: value()} | {'error', ErrorId :: atom()}. value({win32reg, Reg}, Name0) when is_port(Reg) -> Name = case Name0 of default -> []; _ -> Name0 end, Cmd = [?cmd_get_value, Name, 0], Reg ! {self(), {command, Cmd}}, case get_result(Reg) of {value, {default, Value}} when Name =:= [] -> {ok, Value}; {value, {Name, Value}} -> {ok, Value}; {error, Reason} -> {error, Reason} end. -spec values(RegHandle) -> ReturnValue when RegHandle :: reg_handle(), ReturnValue :: {'ok', [ValuePair]} | {'error', ErrorId :: atom()}, ValuePair :: {Name :: name(), Value :: value()}. values({win32reg, Reg}) when is_port(Reg) -> Cmd = [?cmd_get_all_values], Reg ! {self(), {command, Cmd}}, collect_values(Reg, []). -spec delete_value(RegHandle, Name) -> ReturnValue when RegHandle :: reg_handle(), Name :: name(), ReturnValue :: 'ok' | {'error', ErrorId :: atom()}. delete_value({win32reg, Reg}, Name0) when is_port(Reg) -> Name = case Name0 of default -> []; _ -> Name0 end, Cmd = [?cmd_delete_value, Name, 0], Reg ! {self(), {command, Cmd}}, get_result(Reg). -spec expand(String) -> ExpandedString when String :: string(), ExpandedString :: string(). expand(Value) -> expand(Value, [], []). expand([$%, $%|Rest], [], Result) -> expand(Rest, [], [$%|Result]); expand([$%, C|Rest], [], Result) -> expand(Rest, [C], Result); expand([C|Rest], [], Result) -> expand(Rest, [], [C|Result]); expand([$%|Rest], Env0, Result) -> Env = lists:reverse(Env0), expand(Rest, [], lists:reverse(os:getenv(Env, ""))++Result); expand([C|Rest], Env, Result) -> expand(Rest, [C|Env], Result); expand([], [], Result) -> lists:reverse(Result). -spec format_error(ErrorId) -> ErrorString when ErrorId :: atom(), ErrorString :: string(). format_error(ErrorId) -> erl_posix_msg:message(ErrorId). %%% Implementation. -spec collect_values(port(), [{name(), value()}]) -> {'ok', [{name(), value()}]} | {'error', ErrorId :: atom()}. collect_values(P, Result) -> case get_result(P) of ok -> {ok, lists:reverse(Result)}; {value, ValueData} -> collect_values(P, [ValueData|Result]); {error, Reason} -> {error, Reason} end. -spec collect_keys(port(), string()) -> {'ok', [string()]} | {'error', ErrorId :: atom()}. collect_keys(P, Result) -> case get_result(P) of ok -> {ok, lists:reverse(Result)}; {key, KeyData} -> collect_keys(P, [KeyData|Result]); {error, Reason} -> {error, Reason} end. get_result(P) -> receive {P, {data, Data}} -> get_result1(Data) end. get_result1([$e|Reason]) -> {error, list_to_atom(Reason)}; get_result1([$o]) -> ok; get_result1([$k|Name]) -> {key, Name}; get_result1([$v|Rest0]) -> {ok, Type, Rest1} = i32_on_head(Rest0), {ok, Name0, Value} = get_cstring(Rest1), Name = case Name0 of [] -> default; _ -> Name0 end, {value, {Name, encode_value(Type, Value)}}; get_result1([$s|Rest0]) -> {ok, Hkey, Name} = i32_on_head(Rest0), {state, Hkey, Name}. encode_value(?reg_sc, Value) -> Value; encode_value(?reg_expand_sc, Value) -> Value; encode_value(?reg_dword, Value) -> i32(Value); encode_value(_, Value) -> list_to_binary(Value). term_to_value(Int) when is_integer(Int) -> {i32(?reg_dword), i32(Int)}; term_to_value(String) when is_list(String) -> {i32(?reg_sc), [String, 0]}; term_to_value(Bin) when is_binary(Bin) -> {i32(?reg_binary), Bin}; term_to_value(_) -> exit(badarg). get_cstring(List) -> get_cstring(List, []). get_cstring([0|Rest], Result) -> {ok, lists:reverse(Result), Rest}; get_cstring([C|Rest], Result) -> get_cstring(Rest, [C|Result]); get_cstring([], Result) -> {ok, lists:reverse(Result), []}. i32(Int) when is_integer(Int) -> [(Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255]; i32([X1, X2, X3, X4]) -> (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4. i32_on_head([X1, X2, X3, X4 | Rest]) -> {ok, (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4, Rest}. parse_key([$\\|Rest], _) -> parse_root(Rest, []); parse_key(Key, Reg) -> parse_relative(Key, Reg). parse_relative(Path, Reg) -> Cmd = [?cmd_get_current], Reg ! {self(), {command, Cmd}}, {state, RootHandle, Name} = get_result(Reg), Original = split_key(Name), Relative = lists:reverse(split_key(Path)), case parse_relative1(Relative, Original) of {error,Error} -> {error,Error}; NewPath -> {ok, RootHandle, NewPath} end. parse_relative1([".."|_], []) -> {error,enoent}; parse_relative1([".."|T1], [_|T2]) -> parse_relative1(T1, T2); parse_relative1([Comp|Rest], Result) -> parse_relative1(Rest, [Comp|Result]); parse_relative1([], Result) -> reverse_and_join(Result, []). reverse_and_join([X|Rest], []) -> reverse_and_join(Rest, [X]); reverse_and_join([X|Rest], Result) -> reverse_and_join(Rest, [X, "\\" | Result]); reverse_and_join([], Result) -> Result. split_key(Key) -> split_key(Key, [], []). split_key([$\\|Rest], Current, Result) -> split_key(Rest, [], [lists:reverse(Current)|Result]); split_key([C|Rest], Current, Result) -> split_key(Rest, [C|Current], Result); split_key([], [], Result) -> Result; split_key([], Current, Result) -> [lists:reverse(Current)|Result]. parse_root([$\\|Rest], Result) -> Root = case lists:reverse(Result) of [$h, $k, $e, $y, $_|Root0] -> Root0; Root0 -> Root0 end, case root_to_handle(list_to_atom(Root)) of false -> {error, enoent}; Handle -> {ok, Handle, Rest} end; parse_root([C|Rest], Result) -> parse_root(Rest, [C|Result]); parse_root([], Result) -> parse_root([$\\], Result). root_to_handle(classes_root) -> ?hkey_classes_root; root_to_handle(hkcr) -> ?hkey_classes_root; root_to_handle(current_user) -> ?hkey_current_user; root_to_handle(hkcu) -> ?hkey_current_user; root_to_handle(local_machine) -> ?hkey_local_machine; root_to_handle(hklm) -> ?hkey_local_machine; root_to_handle(users) -> ?hkey_users; root_to_handle(hku) -> ?hkey_users; root_to_handle(current_config) -> ?hkey_current_config; root_to_handle(hkcc) -> ?hkey_current_config; root_to_handle(dyn_data) -> ?hkey_dyn_data; root_to_handle(hkdd) -> ?hkey_dyn_data; root_to_handle(performance_data) -> ?hkey_performance_data; root_to_handle(_) -> false. hkey_to_string(?hkey_classes_root) -> "\\hkey_classes_root"; hkey_to_string(?hkey_current_user) -> "\\hkey_current_user"; hkey_to_string(?hkey_local_machine) -> "\\hkey_local_machine"; hkey_to_string(?hkey_users) -> "\\hkey_users"; hkey_to_string(?hkey_performance_data) -> "\\hkey_performance_data"; hkey_to_string(?hkey_current_config) -> "\\hkey_current_config"; hkey_to_string(?hkey_dyn_data) -> "\\hkey_dyn_data". open_mode([read|Rest], Result) -> open_mode(Rest, [$r|Result]); open_mode([write|Rest], Result) -> open_mode(Rest, [$w|Result]); open_mode([], Result) -> Result; open_mode(_, _) -> {error, einval}.