1 |
|
-module(mnesia_api). |
2 |
|
|
3 |
|
-export([set_master/1, |
4 |
|
backup_mnesia/1, restore_mnesia/1, |
5 |
|
dump_mnesia/1, dump_table/2, load_mnesia/1, |
6 |
|
install_fallback_mnesia/1, |
7 |
|
mnesia_change_nodename/4, |
8 |
|
restore/1, mnesia_info/1]). |
9 |
|
|
10 |
|
-type info_result() :: {ok, #{binary() => binary() | [binary()] | integer()}}. |
11 |
|
-type info_error() :: {{internal_server_error | bad_key_error, binary()}, #{key => binary()}}. |
12 |
|
-type dump_error() :: table_does_not_exist | file_error | cannot_dump. |
13 |
|
-type restore_error() :: cannot_restore | file_not_found | not_a_log_file_error | |
14 |
|
table_does_not_exist. |
15 |
|
-type backup_error() :: wrong_filename | cannot_backup. |
16 |
|
-type load_error() :: cannot_load | bad_file_format | file_not_found. |
17 |
|
-type change_error() :: file_not_found | bad_file_format | cannot_change. |
18 |
|
|
19 |
|
-spec mnesia_info(Keys::[binary()]) -> {ok, [info_result() | info_error()]}. |
20 |
|
mnesia_info(null) -> |
21 |
2 |
Value = mnesia:system_info(all), |
22 |
2 |
Result = lists:foldl(fun({Key, Result}, AllAcc) -> |
23 |
84 |
AllAcc ++ [{ok, #{<<"result">> => convert_value(Result), <<"key">> => Key}}] |
24 |
|
end, [], Value), |
25 |
2 |
{ok, Result}; |
26 |
|
mnesia_info(Keys) -> |
27 |
2 |
Result = lists:foldl(fun |
28 |
|
(<<"all">>, Acc) -> |
29 |
:-( |
Acc ++ [{{bad_key_error, <<"Key \"all\" does not exist">>}, |
30 |
|
#{key => <<"all">>}}]; |
31 |
|
(Key, Acc) -> |
32 |
86 |
try mnesia:system_info(binary_to_atom(Key)) of |
33 |
|
Value -> |
34 |
84 |
Acc ++ [{ok, #{<<"result">> => convert_value(Value), <<"key">> => Key}}] |
35 |
|
catch |
36 |
|
_:{_, {badarg, _}} -> |
37 |
2 |
Acc ++ [{{bad_key_error, <<"Key \"", Key/binary, "\" does not exist">>}, |
38 |
|
#{key => Key}}]; |
39 |
|
_:_ -> |
40 |
:-( |
Acc ++ [{{internal_server_error, <<"Internal server error">>}, #{key => Key}}] |
41 |
|
end |
42 |
|
end, [], Keys), |
43 |
2 |
{ok, Result}. |
44 |
|
|
45 |
|
-spec dump_mnesia(file:name()) -> {dump_error(), io_lib:chars()} | {ok, []}. |
46 |
|
dump_mnesia(Path) -> |
47 |
6 |
Tabs = get_local_tables(), |
48 |
6 |
dump_tables(Path, Tabs). |
49 |
|
|
50 |
|
-spec dump_table(file:name(), string()) -> {dump_error(), io_lib:chars()} | {ok, []}. |
51 |
|
dump_table(Path, STable) -> |
52 |
6 |
Table = list_to_atom(STable), |
53 |
6 |
dump_tables(Path, [Table]). |
54 |
|
|
55 |
|
-spec backup_mnesia(file:name()) -> {backup_error(), io_lib:chars()} | {ok, []}. |
56 |
|
backup_mnesia(Path) -> |
57 |
8 |
case mnesia:backup(Path) of |
58 |
|
ok -> |
59 |
4 |
{ok, ""}; |
60 |
|
{error, {'EXIT', {error, enoent}}} -> |
61 |
2 |
{wrong_filename, io_lib:format("Wrong filename: ~p", [Path])}; |
62 |
|
{error, Reason} -> |
63 |
2 |
String = io_lib:format("Can't store backup in ~p at node ~p: ~p", |
64 |
|
[filename:absname(Path), node(), Reason]), |
65 |
2 |
{cannot_backup, String} |
66 |
|
end. |
67 |
|
|
68 |
|
-spec restore_mnesia(file:name()) -> {restore_error(), io_lib:chars()} | {ok, []}. |
69 |
|
restore_mnesia(Path) -> |
70 |
10 |
ErrorString=lists:flatten( io_lib:format("Can't restore backup from ~p at node ~p: ", |
71 |
|
[filename:absname(Path), node()])), |
72 |
10 |
case mnesia_api:restore(Path) of |
73 |
|
{atomic, _} -> |
74 |
2 |
{ok, ""}; |
75 |
|
{aborted, {no_exists, Table}} -> |
76 |
:-( |
String = io_lib:format("~sTable ~p does not exist.", [ErrorString, Table]), |
77 |
:-( |
{table_does_not_exist, String}; |
78 |
|
{aborted, enoent} -> |
79 |
4 |
String = ErrorString ++ "File not found.", |
80 |
4 |
{file_not_found, String}; |
81 |
|
{aborted, {not_a_log_file, Filename}} -> |
82 |
2 |
String = "Wrong file " ++ Filename ++ " structure", |
83 |
2 |
{not_a_log_file_error, String}; |
84 |
|
{aborted, Reason} -> |
85 |
2 |
String = io_lib:format("~s~p", [ErrorString, Reason]), |
86 |
2 |
{cannot_restore, String} |
87 |
|
end. |
88 |
|
|
89 |
|
-spec load_mnesia(file:name()) -> {load_error(), io_lib:chars()} | {ok, []}. |
90 |
|
load_mnesia(Path) -> |
91 |
8 |
case mnesia:load_textfile(Path) of |
92 |
|
{atomic, ok} -> |
93 |
2 |
{ok, ""}; |
94 |
|
{error, bad_header} -> |
95 |
2 |
{bad_file_format, "File has wrong format"}; |
96 |
|
{error, read} -> |
97 |
2 |
{bad_file_format, "File has wrong format"}; |
98 |
|
{error, open} -> |
99 |
2 |
{file_not_found, "File was not found"}; |
100 |
|
{error, Reason} -> |
101 |
:-( |
String = io_lib:format("Can't load dump in ~p at node ~p: ~p", |
102 |
|
[filename:absname(Path), node(), Reason]), |
103 |
:-( |
{cannot_load, String} |
104 |
|
end. |
105 |
|
|
106 |
|
-spec mnesia_change_nodename(node(), node(), _, _) -> {ok, _} | {change_error(), io_lib:chars()}. |
107 |
|
mnesia_change_nodename(From, To, Source, Target) -> |
108 |
6 |
Switch = |
109 |
|
fun |
110 |
|
(Node) when Node == From -> |
111 |
32 |
io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]), |
112 |
32 |
To; |
113 |
|
(Node) when Node == To -> |
114 |
:-( |
io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]), |
115 |
:-( |
Node; |
116 |
|
(Node) -> |
117 |
:-( |
io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]), |
118 |
:-( |
Node |
119 |
|
end, |
120 |
6 |
Convert = |
121 |
|
fun |
122 |
|
({schema, db_nodes, Nodes}, Acc) -> |
123 |
:-( |
io:format(" +++ db_nodes ~p~n", [Nodes]), |
124 |
:-( |
{[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc}; |
125 |
|
({schema, version, Version}, Acc) -> |
126 |
:-( |
io:format(" +++ version: ~p~n", [Version]), |
127 |
:-( |
{[{schema, version, Version}], Acc}; |
128 |
|
({schema, cookie, Cookie}, Acc) -> |
129 |
:-( |
io:format(" +++ cookie: ~p~n", [Cookie]), |
130 |
:-( |
{[{schema, cookie, Cookie}], Acc}; |
131 |
|
({schema, Tab, CreateList}, Acc) -> |
132 |
32 |
io:format("~n * Checking table: '~p'~n", [Tab]), |
133 |
32 |
Keys = [ram_copies, disc_copies, disc_only_copies], |
134 |
32 |
OptSwitch = |
135 |
|
fun({Key, Val}) -> |
136 |
576 |
case lists:member(Key, Keys) of |
137 |
|
true -> |
138 |
96 |
io:format(" + Checking key: '~p'~n", [Key]), |
139 |
96 |
{Key, lists:map(Switch, Val)}; |
140 |
480 |
false -> {Key, Val} |
141 |
|
end |
142 |
|
end, |
143 |
32 |
Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}, |
144 |
32 |
Res; |
145 |
|
(Other, Acc) -> |
146 |
2 |
{[Other], Acc} |
147 |
|
end, |
148 |
6 |
case mnesia:traverse_backup(Source, Target, Convert, switched) of |
149 |
2 |
{ok, _} = Result -> Result; |
150 |
|
{error, Reason} -> |
151 |
4 |
String = io_lib:format("Error while changing node's name ~p:~n~p", |
152 |
|
[node(), Reason]), |
153 |
4 |
case Reason of |
154 |
|
{_, enoent} -> |
155 |
2 |
{file_not_found, String}; |
156 |
|
{_, {not_a_log_file, _}} -> |
157 |
2 |
{bad_file_format, String}; |
158 |
|
_ -> |
159 |
:-( |
{cannot_change, String} |
160 |
|
end |
161 |
|
end. |
162 |
|
|
163 |
|
-spec install_fallback_mnesia(file:name()) -> |
164 |
|
{cannot_fallback, io_lib:chars()} | {ok, []}. |
165 |
|
install_fallback_mnesia(Path) -> |
166 |
2 |
case mnesia:install_fallback(Path) of |
167 |
|
ok -> |
168 |
:-( |
{ok, ""}; |
169 |
|
{error, Reason} -> |
170 |
2 |
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p", |
171 |
|
[filename:absname(Path), node(), Reason]), |
172 |
2 |
{cannot_fallback, String} |
173 |
|
end. |
174 |
|
|
175 |
|
-spec set_master(node()) -> {cannot_set, io_lib:chars()} | {ok, []}. |
176 |
|
set_master(Node) -> |
177 |
4 |
case mnesia:set_master_nodes([Node]) of |
178 |
|
ok -> |
179 |
4 |
{ok, ""}; |
180 |
|
{error, Reason} -> |
181 |
:-( |
String = io_lib:format("Can't set master node ~p at node ~p:~n~p", |
182 |
|
[Node, node(), Reason]), |
183 |
:-( |
{cannot_set, String} |
184 |
|
end. |
185 |
|
|
186 |
|
%--------------------------------------------------------------------------------------------------- |
187 |
|
% Helpers |
188 |
|
%--------------------------------------------------------------------------------------------------- |
189 |
|
|
190 |
|
-spec convert_value(any()) -> binary() | [{ok, any()}] | integer(). |
191 |
|
convert_value(Value) when is_binary(Value) -> |
192 |
:-( |
Value; |
193 |
|
convert_value(Value) when is_integer(Value) -> |
194 |
40 |
Value; |
195 |
|
convert_value(Value) when is_atom(Value) -> |
196 |
236 |
atom_to_binary(Value); |
197 |
|
convert_value([Head | _] = Value) when is_integer(Head) -> |
198 |
12 |
list_to_binary(Value); |
199 |
|
convert_value(Value) when is_list(Value) -> |
200 |
48 |
[{ok, convert_value(Item)} || Item <- Value]; |
201 |
|
convert_value(Value) -> |
202 |
16 |
list_to_binary(io_lib:format("~p", [Value])). |
203 |
|
|
204 |
|
-spec dump_tables(file:name(), list()) -> {dump_error(), io_lib:chars()} | {ok, []}. |
205 |
|
dump_tables(File, Tabs) -> |
206 |
12 |
case dump_to_textfile(Tabs, file:open(File, [write])) of |
207 |
|
ok -> |
208 |
6 |
{ok, ""}; |
209 |
|
{file_error, Reason} -> |
210 |
4 |
String = io_lib:format("Can't store dump in ~p at node ~p: ~p", |
211 |
|
[filename:absname(File), node(), Reason]), |
212 |
4 |
{file_error, String}; |
213 |
|
{error, Reason} -> |
214 |
2 |
String = io_lib:format("Can't store dump in ~p at node ~p: ~p", |
215 |
|
[filename:absname(File), node(), Reason]), |
216 |
2 |
case Reason of |
217 |
|
table_does_not_exist -> |
218 |
2 |
{table_does_not_exist, String}; |
219 |
|
_ -> |
220 |
:-( |
{cannot_dump, String} |
221 |
|
end |
222 |
|
end. |
223 |
|
|
224 |
|
%% @doc Mnesia database restore |
225 |
|
restore(Path) -> |
226 |
10 |
mnesia:restore(Path, [{keep_tables, keep_tables()}, |
227 |
|
{default_op, skip_tables}]). |
228 |
|
|
229 |
|
%% @doc This function returns a list of tables that should be kept from a |
230 |
|
%% previous version backup. |
231 |
|
%% Obsolete tables or tables created by modules which are no longer used are not |
232 |
|
%% restored and are ignored. |
233 |
|
-spec keep_tables() -> [atom()]. |
234 |
|
keep_tables() -> |
235 |
10 |
lists:flatten([acl, passwd, disco_publish, keep_modules_tables()]). |
236 |
|
|
237 |
|
%% @doc Returns the list of modules tables in use, according to the list of |
238 |
|
%% actually loaded modules |
239 |
|
-spec keep_modules_tables() -> [[atom()]]. % list of lists |
240 |
|
keep_modules_tables() -> |
241 |
10 |
lists:map(fun(Module) -> module_tables(Module) end, |
242 |
|
gen_mod:loaded_modules()). |
243 |
|
|
244 |
|
%% TODO: This mapping should probably be moved to a callback function in each module. |
245 |
|
%% @doc Mapping between modules and their tables |
246 |
|
-spec module_tables(_) -> [atom()]. |
247 |
:-( |
module_tables(mod_announce) -> [motd, motd_users]; |
248 |
:-( |
module_tables(mod_irc) -> [irc_custom]; |
249 |
:-( |
module_tables(mod_last) -> [last_activity]; |
250 |
:-( |
module_tables(mod_muc) -> [muc_room, muc_registered]; |
251 |
:-( |
module_tables(mod_offline) -> [offline_msg]; |
252 |
:-( |
module_tables(mod_privacy) -> [privacy]; |
253 |
:-( |
module_tables(mod_private) -> [private_storage]; |
254 |
:-( |
module_tables(mod_pubsub) -> [pubsub_node]; |
255 |
10 |
module_tables(mod_roster) -> [roster]; |
256 |
:-( |
module_tables(mod_shared_roster) -> [sr_group, sr_user]; |
257 |
10 |
module_tables(mod_vcard) -> [vcard, vcard_search]; |
258 |
100 |
module_tables(_Other) -> []. |
259 |
|
|
260 |
|
-spec get_local_tables() -> [any()]. |
261 |
|
get_local_tables() -> |
262 |
6 |
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)), |
263 |
6 |
Tabs = lists:filter( |
264 |
|
fun(T) -> |
265 |
94 |
case mnesia:table_info(T, storage_type) of |
266 |
20 |
disc_copies -> true; |
267 |
2 |
disc_only_copies -> true; |
268 |
72 |
_ -> false |
269 |
|
end |
270 |
|
end, Tabs1), |
271 |
6 |
Tabs. |
272 |
|
|
273 |
|
-spec dump_to_textfile(any(), |
274 |
|
{error, atom()} | {ok, pid() | {file_descriptor, atom() | tuple(), _}} |
275 |
|
) -> ok | {error, atom()} | {file_error, atom()}. |
276 |
|
dump_to_textfile(Tabs, {ok, F}) -> |
277 |
8 |
case get_info_about_tables(Tabs, F) of |
278 |
|
ok -> |
279 |
6 |
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs), |
280 |
6 |
file:close(F); |
281 |
|
{error, _} = Error -> |
282 |
2 |
Error |
283 |
|
end; |
284 |
|
dump_to_textfile(_, {error, Reason}) -> |
285 |
4 |
{file_error, Reason}. |
286 |
|
|
287 |
|
-spec get_info_about_tables(any(), pid()) -> ok | {error, atom()}. |
288 |
|
get_info_about_tables(Tabs, File) -> |
289 |
8 |
try |
290 |
8 |
Defs = lists:map( |
291 |
20 |
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)}, |
292 |
|
{attributes, mnesia:table_info(T, attributes)}]} |
293 |
|
end, |
294 |
|
Tabs), |
295 |
6 |
io:format(File, "~p.~n", [{tables, Defs}]) |
296 |
|
catch _:_ -> |
297 |
2 |
{error, table_does_not_exist} |
298 |
|
end. |
299 |
|
|
300 |
|
-spec dump_tab(pid(), atom()) -> ok. |
301 |
|
dump_tab(F, T) -> |
302 |
18 |
W = mnesia:table_info(T, wild_pattern), |
303 |
18 |
{atomic, All} = mnesia:transaction( |
304 |
18 |
fun() -> mnesia:match_object(T, W, read) end), |
305 |
18 |
lists:foreach( |
306 |
7 |
fun(Term) -> io:format(F, "~p.~n", [setelement(1, Term, T)]) end, All). |