summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/atomic.erl24
-rw-r--r--src/hex.erl1
-rw-r--r--src/perm.erl73
-rw-r--r--src/util.erl57
4 files changed, 97 insertions, 58 deletions
diff --git a/src/atomic.erl b/src/atomic.erl
new file mode 100644
index 0000000..5bf5670
--- /dev/null
+++ b/src/atomic.erl
@@ -0,0 +1,24 @@
+%%
+%% Copyright (c) 2014 Kungliga Tekniska Högskolan
+%% (KTH Royal Institute of Technology, Stockholm, Sweden).
+%%
+
+-module(atomic).
+-export([replacefile/2, readfile/1]).
+
+-spec replacefile(string(), binary()) -> ok.
+replacefile(Path, Content) ->
+ TempName = util:tempfilename(Path),
+ util:write_tempfile_and_rename(Path, TempName, Content),
+ util:fsync([Path, filename:dirname(Path)]).
+
+-spec readfile(string()) -> binary().
+readfile(Path) ->
+ case file:read_file(Path) of
+ {ok, Contents} ->
+ Contents;
+ {error, enoent} ->
+ noentry;
+ {error, Error} ->
+ util:exit_with_error(readfile, Error, "Error reading file")
+ end.
diff --git a/src/hex.erl b/src/hex.erl
index e3c8441..1eb1e6a 100644
--- a/src/hex.erl
+++ b/src/hex.erl
@@ -4,6 +4,7 @@
-module(hex).
-export([bin_to_hexstr/1,hexstr_to_bin/1]).
+-spec bin_to_hexstr(binary()) -> string().
bin_to_hexstr(Bin) ->
lists:flatten([io_lib:format("~2.16.0B", [X]) ||
X <- binary_to_list(Bin)]).
diff --git a/src/perm.erl b/src/perm.erl
index 5cd2889..ccb23bc 100644
--- a/src/perm.erl
+++ b/src/perm.erl
@@ -6,16 +6,7 @@
-module(perm).
-export([ensurefile/3, readfile/2]).
-fsync([]) ->
- ok;
-fsync([Name | Rest]) ->
- case fsyncport:fsync(Name) of
- ok ->
- fsync(Rest);
- {error, Error} ->
- {error, Error}
- end.
-
+-spec readfile_and_verify(string(), binary()) -> ok | differ | {error, atom()}.
readfile_and_verify(Name, Content) ->
case file:read_file(Name) of
{ok, ContentsRead} when Content == ContentsRead ->
@@ -26,21 +17,7 @@ readfile_and_verify(Name, Content) ->
{error, Error}
end.
-writefile(Name, NurseryName, Content) ->
- case file:open(NurseryName, [write, exclusive]) of
- {ok, File} ->
- %io:format("Write file: ~p~n", [Name]),
- ok = file:write(File, Content),
- file:close(File),
- Result = file:rename(NurseryName, Name),
- Result;
- {error, eexist} ->
- %% Should not happen, file name should be unique
- {error, eexist};
- {error, Error} ->
- {error, Error}
- end.
-
+-spec make_dir(string()) -> ok | {error, atom()}.
make_dir(Name) ->
case file:make_dir(Name) of
ok ->
@@ -51,6 +28,7 @@ make_dir(Name) ->
{error, Error}
end.
+-spec make_dirs([string()]) -> ok | {error, atom()}.
make_dirs([]) ->
ok;
make_dirs([Name | Rest]) ->
@@ -61,6 +39,7 @@ make_dirs([Name | Rest]) ->
{error, Error}
end.
+-spec path_for_key(string(), binary()) -> {[string()], string()}.
path_for_key(Rootdir, Key) ->
Name = hex:bin_to_hexstr(Key),
[C1, C2, C3, C4, C5, C6 | _] = Name,
@@ -70,49 +49,27 @@ path_for_key(Rootdir, Key) ->
Fullpath = Thirdlevel ++ "/" ++ Name,
{[Firstlevel, Secondlevel, Thirdlevel], Fullpath}.
-tempfilename(Base) ->
- {MegaSecs, Secs, MicroSecs} = now(),
- Filename = io_lib:format("~s-~s-~p.~p", [Base, os:getpid(),
- MegaSecs * 1000000 + Secs, MicroSecs]),
- Filename.
-
-exit_with_error(Error, Message) ->
- io:format("~s: ~w~n", [Message, Error]),
- exit({perm, fileerror, Message, Error}).
-
-check_error(ReturnValue, ErrorMessage) ->
- case ReturnValue of
- ok ->
- ok;
- {error, Error} ->
- exit_with_error(Error, ErrorMessage)
- end.
-
+-spec ensurefile(string(), binary(), binary()) -> ok | differ.
ensurefile(Rootdir, Key, Content) ->
{Dirs, Path} = path_for_key(Rootdir, Key),
case readfile_and_verify(Path, Content) of
ok ->
- check_error(fsync([Path, Rootdir | Dirs]), "Error in fsync");
+ util:fsync([Path, Rootdir | Dirs]);
differ ->
differ;
{error, enoent} ->
- check_error(make_dirs([Rootdir, Rootdir ++ "nursery/"] ++ Dirs),
- "Error creating directory"),
+ util:check_error(make_dirs([Rootdir, Rootdir ++ "nursery/"]
+ ++ Dirs),
+ makedir, "Error creating directory"),
NurseryName = Rootdir ++ "nursery/" ++
- tempfilename(hex:bin_to_hexstr(Key)),
- _Result = writefile(Path, NurseryName, Content),
- check_error(fsync([Path, Rootdir | Dirs]), "Error in fsync");
+ util:tempfilename(hex:bin_to_hexstr(Key)),
+ util:write_tempfile_and_rename(Path, NurseryName, Content),
+ util:fsync([Path, Rootdir | Dirs]);
{error, Error} ->
- exit_with_error(Error, "Error reading file")
+ util:exit_with_error(Error, readfile, "Error reading file")
end.
+-spec readfile(string(), binary()) -> binary().
readfile(Rootdir, Key) ->
{_Dirs, Path} = path_for_key(Rootdir, Key),
- case file:read_file(Path) of
- {ok, Contents} ->
- Contents;
- {error, enoent} ->
- noentry;
- {error, Error} ->
- exit_with_error(Error, "Error reading file")
- end.
+ atomic:readfile(Path).
diff --git a/src/util.erl b/src/util.erl
new file mode 100644
index 0000000..48ebbb0
--- /dev/null
+++ b/src/util.erl
@@ -0,0 +1,57 @@
+%%
+%% Copyright (c) 2014 Kungliga Tekniska Högskolan
+%% (KTH Royal Institute of Technology, Stockholm, Sweden).
+%%
+
+-module(util).
+-export([tempfilename/1, fsync/1, exit_with_error/3,
+ check_error/3, write_tempfile_and_rename/3]).
+
+-spec tempfilename(string()) -> string().
+tempfilename(Base) ->
+ {MegaSecs, Secs, MicroSecs} = now(),
+ Filename = io_lib:format("~s-~s-~p.~p", [Base, os:getpid(),
+ MegaSecs * 1000000 + Secs, MicroSecs]),
+ Filename.
+
+-spec fsync([string()]) -> ok.
+fsync([]) ->
+ ok;
+fsync([Name | Rest]) ->
+ case fsyncport:fsync(Name) of
+ ok ->
+ fsync(Rest);
+ {error, Error} ->
+ exit_with_error(fsync, Error, "Error in fsync")
+ end.
+
+-spec exit_with_error(atom(), atom(), string()) -> no_return().
+exit_with_error(Operation, Error, ErrorMessage) ->
+ io:format("~s(~w): ~w~n", [ErrorMessage, Operation, Error]),
+ exit({fileerror, Operation, Error, ErrorMessage}).
+
+-spec check_error(any(), atom(), string()) -> ok.
+check_error(ReturnValue, Operation, ErrorMessage) ->
+ case ReturnValue of
+ ok ->
+ ok;
+ {error, Error} ->
+ exit_with_error(Operation, Error, ErrorMessage)
+ end.
+
+-spec write_tempfile_and_rename(string(), string(), binary()) -> ok.
+write_tempfile_and_rename(Name, NurseryName, Content) ->
+ case file:open(NurseryName, [write, exclusive]) of
+ {ok, File} ->
+ ok = file:write(File, Content),
+ file:close(File),
+ check_error(file:rename(NurseryName, Name), rename,
+ "Error when renaming tempfile to final file");
+ {error, eexist} ->
+ %% Should not happen, file name should be unique
+ exit_with_error(writefile, eexist,
+ "File existed when creating tempfile");
+ {error, Error} ->
+ exit_with_error(writefile, Error,
+ "Error when creating tempfile")
+ end.