diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/atomic.erl | 24 | ||||
-rw-r--r-- | src/fsyncport.erl | 88 | ||||
-rw-r--r-- | src/hex.erl | 1 | ||||
-rw-r--r-- | src/index.erl | 87 | ||||
-rw-r--r-- | src/perm.erl | 75 | ||||
-rw-r--r-- | src/plop_sup.erl | 5 | ||||
-rw-r--r-- | src/util.erl | 57 |
7 files changed, 337 insertions, 0 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/fsyncport.erl b/src/fsyncport.erl new file mode 100644 index 0000000..8bc8c60 --- /dev/null +++ b/src/fsyncport.erl @@ -0,0 +1,88 @@ +%% +%% Copyright (c) 2014 Kungliga Tekniska Högskolan +%% (KTH Royal Institute of Technology, Stockholm, Sweden). +%% + +-module(fsyncport). +-export([start_link/0, stop/0, init/1]). +-export([fsync/1]). + +start_link() -> + Pid = spawn(?MODULE, init, [code:priv_dir(plop) ++ "/fsynchelper"]), + {ok, Pid}. +stop() -> + fsyncport ! stop. + +fsync(Path) -> + call_port({fsync, Path}). + +call_port(Msg) -> + fsyncport ! {call, self(), Msg}, + receive + {fsyncport, Result} -> + Result + end. + +init(ExtPrg) -> + register(fsyncport, self()), + process_flag(trap_exit, true), + Ports = lists:map(fun(_N) -> open_port({spawn_executable, ExtPrg}, + [{packet, 2}]) end, + lists:seq(1, 32)), + loop(Ports). + +loop(Ports) -> + loop(Ports, dict:new(), queue:new()). +loop(IdlePorts, BusyPorts, Waiting) -> + receive + {call, Caller, {fsync, Path}} -> + case IdlePorts of + [] -> + loop(IdlePorts, + BusyPorts, + queue:in({Caller, Path}, Waiting)); + [Port | Rest] -> + Port ! {self(), {command, Path}}, + loop(Rest, + dict:store(Port, {Caller, os:timestamp()}, BusyPorts), + Waiting) + end; + + {Port, {data, Data}} when is_port(Port) -> + {Caller, Starttime} = dict:fetch(Port, BusyPorts), + Stoptime = os:timestamp(), + statreport({fsync, Stoptime, Starttime}), + Caller ! {fsyncport, list_to_atom(Data)}, + case queue:out(Waiting) of + {empty, _} -> + loop([Port | IdlePorts], + dict:erase(Port, BusyPorts), + Waiting); + {{value, {NewCaller, NewPath}}, NewWaiting} -> + IdlePorts = [], + Port ! {self(), {command, NewPath}}, + loop(IdlePorts, + dict:store(Port, {NewCaller, os:timestamp()}, + BusyPorts), + NewWaiting) + end; + stop -> + lists:foreach(fun (Port) -> + Port ! {self(), close} + end, + IdlePorts), + lists:foreach(fun ({Port, {_Caller, _Starttime}}) -> + Port ! {self(), close} + end, + dict:to_list(BusyPorts)), + receive + {Port, closed} when is_port(Port) -> + exit(normal) %% XXX exits when first port is closed + end; + {'EXIT', Port, _Reason} when is_port(Port) -> + %% XXX supervisor doesn't restart fsyncport, why? + exit(port_terminated) + end. + +statreport(_Entry) -> + none. 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/index.erl b/src/index.erl new file mode 100644 index 0000000..5fd468b --- /dev/null +++ b/src/index.erl @@ -0,0 +1,87 @@ +%% +%% Copyright (c) 2014 Kungliga Tekniska Högskolan +%% (KTH Royal Institute of Technology, Stockholm, Sweden). +%% + +%% Implements an interface to a file pair (basename and basename.chksum) +%% that stores an ordered list of fixed-size entries. Entries can be +%% added at the end and are retrieved by index. The list can also be +%% truncated. +%% +%% Writes(add, truncate, addlast) need to be serialized. + +%% TODO: Checksums + +-module(index). +-export([get/2, add/3, addlast/2, truncate/2]). + +-define(ENTRYSIZE, 32). +-define(ENTRYSIZEINFILE, (?ENTRYSIZE*2+1)). + +-spec add(string(), integer() | last, binary()) -> ok. +add(Basepath, Index, Entry) when is_binary(Entry), size(Entry) == ?ENTRYSIZE -> + case file:open(Basepath, [read, write, binary]) of + {ok, File} -> + {ok, Position} = file:position(File, eof), + case Index of + last when Position rem ?ENTRYSIZEINFILE == 0 -> + ok; + Index when is_integer(Index), + Index * ?ENTRYSIZEINFILE == Position -> + ok + end, + EntryText = hex:bin_to_hexstr(Entry) ++ "\n", + ok = file:write(File, EntryText), + ok = file:close(File), + util:fsync([Basepath, filename:dirname(Basepath)]); + {error, Error} -> + util:exit_with_error(Error, writefile, + "Error opening file for writing") + end. + +truncate(Basepath, Index) -> + case file:open(Basepath, [read, write, binary]) of + {ok, File} -> + {ok, _Position} = file:position(File, Index * ?ENTRYSIZEINFILE), + ok = file:truncate(File), + ok = file:close(File), + util:fsync([Basepath, filename:dirname(Basepath)]); + {error, Error} -> + util:exit_with_error(Error, writefile, + "Error opening file for writing") + end. + + +-spec addlast(string(), integer()) -> ok. +addlast(Basepath, Entry) -> + add(Basepath, last, Entry). + +decodedata(EntryText) when length(EntryText) == ?ENTRYSIZEINFILE -> + case [lists:last(EntryText)] of + "\n" -> + hex:hexstr_to_bin(lists:droplast(EntryText)); + _ -> + util:exit_with_error(badformat, readindex, + "Index line not ending with linefeed") + end. + +-spec get(string(), integer()) -> binary(). +get(Basepath, Index) -> + case file:open(Basepath, [read, binary]) of + {ok, File} -> + {ok, Filesize} = file:position(File, eof), + if + Index * ?ENTRYSIZEINFILE + ?ENTRYSIZEINFILE =< Filesize -> + {ok, _Position} = file:position(File, + Index * ?ENTRYSIZEINFILE), + {ok, EntryText} = file:read(File, ?ENTRYSIZEINFILE), + Entry = decodedata(binary_to_list(EntryText)), + file:close(File), + Entry; + true -> + noentry + end; + {error, Error} -> + util:exit_with_error(Error, readfile, + "Error opening file for reading") + end. diff --git a/src/perm.erl b/src/perm.erl new file mode 100644 index 0000000..ccb23bc --- /dev/null +++ b/src/perm.erl @@ -0,0 +1,75 @@ +%% +%% Copyright (c) 2014 Kungliga Tekniska Högskolan +%% (KTH Royal Institute of Technology, Stockholm, Sweden). +%% + +-module(perm). +-export([ensurefile/3, readfile/2]). + +-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 -> + ok; + {ok, _ContentsRead} -> + differ; + {error, Error} -> + {error, Error} + end. + +-spec make_dir(string()) -> ok | {error, atom()}. +make_dir(Name) -> + case file:make_dir(Name) of + ok -> + ok; + {error, eexist} -> + ok; + {error, Error} -> + {error, Error} + end. + +-spec make_dirs([string()]) -> ok | {error, atom()}. +make_dirs([]) -> + ok; +make_dirs([Name | Rest]) -> + case make_dir(Name) of + ok -> + make_dirs(Rest); + {error, Error} -> + {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, + Firstlevel = Rootdir ++ [C1, C2], + Secondlevel = Firstlevel ++ "/" ++ [C3, C4], + Thirdlevel = Secondlevel ++ "/" ++ [C5, C6], + Fullpath = Thirdlevel ++ "/" ++ Name, + {[Firstlevel, Secondlevel, Thirdlevel], Fullpath}. + +-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 -> + util:fsync([Path, Rootdir | Dirs]); + differ -> + differ; + {error, enoent} -> + util:check_error(make_dirs([Rootdir, Rootdir ++ "nursery/"] + ++ Dirs), + makedir, "Error creating directory"), + NurseryName = Rootdir ++ "nursery/" ++ + util:tempfilename(hex:bin_to_hexstr(Key)), + util:write_tempfile_and_rename(Path, NurseryName, Content), + util:fsync([Path, Rootdir | Dirs]); + {error, Error} -> + 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), + atomic:readfile(Path). diff --git a/src/plop_sup.erl b/src/plop_sup.erl index a5ce905..bcb9756 100644 --- a/src/plop_sup.erl +++ b/src/plop_sup.erl @@ -23,6 +23,11 @@ init(Args) -> permanent, 10000, worker, [db]}, + {fsync, + {fsyncport, start_link, []}, + permanent, + 10000, + worker, [fsyncport]}, {the_ht, {ht, start_link, []}, permanent, 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. |