From 29ac49eabca61c4a9e0c3a0d8f9ba57ab516ebae Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 25 Sep 2014 01:35:33 +0200 Subject: Permanent storage implementation --- Makefile | 2 + c_src/erlport.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ c_src/erlport.h | 15 +++++++ c_src/fsynchelper.c | 64 ++++++++++++++++++++++++++++++ c_src/net_read_write.c | 93 +++++++++++++++++++++++++++++++++++++++++++ c_src/net_read_write.h | 10 +++++ src/fsyncport.erl | 88 +++++++++++++++++++++++++++++++++++++++++ src/perm.erl | 95 ++++++++++++++++++++++++++++++++++++++++++++ src/plop_sup.erl | 5 +++ 9 files changed, 477 insertions(+) create mode 100644 c_src/erlport.c create mode 100644 c_src/erlport.h create mode 100644 c_src/fsynchelper.c create mode 100644 c_src/net_read_write.c create mode 100644 c_src/net_read_write.h create mode 100644 src/fsyncport.erl create mode 100644 src/perm.erl diff --git a/Makefile b/Makefile index 2efdd34..4e54096 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ build all: + (cd c_src && make all) + cp c_src/fsynchelper priv/fsynchelper erl -make clean: -rm ebin/*.beam diff --git a/c_src/erlport.c b/c_src/erlport.c new file mode 100644 index 0000000..5e5c17c --- /dev/null +++ b/c_src/erlport.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2014 Kungliga Tekniska Högskolan + * (KTH Royal Institute of Technology, Stockholm, Sweden). + */ + +#include +#include +#include +#include +#include +#include + +#include "net_read_write.h" +#include "erlport.h" + +static ssize_t +read_length(size_t length_size) +{ + unsigned char buf[2]; + + if (length_size != 2) { + return -1; + } + + if (length_size > sizeof(buf)) { + return -1; + } + + ssize_t ret; + + ret = net_read(0, (char *)buf, length_size); + + if (ret != (ssize_t) length_size) { + return -1; + } + + return (ssize_t)(((unsigned long)buf[0] << 8) | (unsigned long)buf[1]); +} + +ssize_t +read_command(char *buf, size_t maxlen) +{ + ssize_t len; + + len = read_length(2); + + if (len < 0) { + return -1; + } + + if (len > (ssize_t) maxlen) { + return -1; + } + return net_read(0, buf, (size_t)len); +} + +static int +write_length(size_t len, size_t length_size) +{ + unsigned char buf[2]; + + if (length_size != 2) { + return -1; + } + + buf[0] = (len >> 8) & 0xff; + buf[1] = len & 0xff; + + ssize_t ret; + + ret = net_write(1, (char *)buf, length_size); + + if (ret < 0) { + return -1; + } + + if (ret != (ssize_t) length_size) { + return -1; + } + + return 0; +} + +static int +write_reply(char *msg, size_t len) +{ + ssize_t ret; + + ret = write_length(len, 2); + if (ret < 0) { + return -1; + } + ret = net_write(1, msg, len); + if (ret < 0) { + return -1; + } + + return 0; +} + +int +write_status(char *msg) +{ + return write_reply(msg, strlen(msg)); +} diff --git a/c_src/erlport.h b/c_src/erlport.h new file mode 100644 index 0000000..49e1b7c --- /dev/null +++ b/c_src/erlport.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2014 Kungliga Tekniska Högskolan + * (KTH Royal Institute of Technology, Stockholm, Sweden). + */ + +#ifndef ERLPORT_H +#define ERLPORT_H + +ssize_t +read_command(char *buf, size_t len); + +int +write_status(char *msg); + +#endif diff --git a/c_src/fsynchelper.c b/c_src/fsynchelper.c new file mode 100644 index 0000000..e6a04be --- /dev/null +++ b/c_src/fsynchelper.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014 Kungliga Tekniska Högskolan + * (KTH Royal Institute of Technology, Stockholm, Sweden). + */ + +#include +#include +#include +#include + +#include +#include + +#include "erlport.h" + +static int +dosync(int fd) +{ +#ifdef F_FULLFSYNC + int ret = fcntl(fd, F_FULLFSYNC); +#else + int ret = fsync(fd); +#endif + return ret; +} + +int +main() +{ + char buf[100]; + ssize_t len; + + /* XXX: exits when command size is 0 */ + + while ((len = read_command(buf, sizeof(buf)-1)) > 0) { + buf[len] = '\0'; + while (1) { + int fd; + + fd = open(buf, O_RDONLY); + if (fd == -1) { + /* XXX: better errors */ + write_status("openerror"); + break; + } + + if (dosync(fd) == 0) { + write_status("ok"); + } else if (errno == EBADF) { + write_status("ebadf"); + } else if (errno == EINTR) { + close(fd); + continue; + } else { + write_status("fsyncerror"); + } + + close(fd); + break; + } + } + + return 0; +} diff --git a/c_src/net_read_write.c b/c_src/net_read_write.c new file mode 100644 index 0000000..f8f14f0 --- /dev/null +++ b/c_src/net_read_write.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 1995, 1996, 1997, 1998 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "net_read_write.h" + +/* + * Like read but never return partial data. + */ + +ssize_t +net_read (int fd, void *buf, size_t nbytes) +{ + char *cbuf = (char *)buf; + ssize_t count; + size_t rem = nbytes; + + while (rem > 0) { + count = read (fd, cbuf, rem); + if (count < 0) { + if (errno == EINTR) + continue; + else + return count; + } else if (count == 0) { + return count; + } + cbuf += (size_t) count; + rem -= (size_t) count; + } + return (ssize_t)nbytes; +} + +/* + * Like write but never return partial data. + */ + +ssize_t +net_write (int fd, const void *buf, size_t nbytes) +{ + const char *cbuf = (const char *)buf; + ssize_t count; + size_t rem = nbytes; + + while (rem > 0) { + count = write (fd, cbuf, rem); + if (count < 0) { + if (errno == EINTR) + continue; + else + return count; + } + cbuf += (size_t)count; + rem -= (size_t)count; + } + return (ssize_t)nbytes; +} diff --git a/c_src/net_read_write.h b/c_src/net_read_write.h new file mode 100644 index 0000000..80b92b3 --- /dev/null +++ b/c_src/net_read_write.h @@ -0,0 +1,10 @@ +#ifndef NET_READ_WRITE_H +#define NET_READ_WRITE_H + +ssize_t +net_read (int, void *, size_t); + +ssize_t +net_write (int, const void *, size_t); + +#endif 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/perm.erl b/src/perm.erl new file mode 100644 index 0000000..2ce5b46 --- /dev/null +++ b/src/perm.erl @@ -0,0 +1,95 @@ +%% +%% Copyright (c) 2014 Kungliga Tekniska Högskolan +%% (KTH Royal Institute of Technology, Stockholm, Sweden). +%% + +-module(perm). +-export([ensurefile/3]). + +fsync(Name) -> + fsyncport:fsync(Name). + +readfile_and_verify(Name, Content) -> + case file:read_file(Name) of + {ok, ContentsReadBinary} -> + ContentsRead = binary_to_list(ContentsReadBinary), + if Content == ContentsRead -> + ok; + true -> + {error, "File contents differ"} + end; + {error, Error} -> + {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. + +make_dir(Name) -> + case file:make_dir(Name) of + ok -> + ok; + {error, eexist} -> + ok; + {error, Error} -> + {error, Error} + end. + +make_dirs([]) -> + ok; +make_dirs([Name | Rest]) -> + case make_dir(Name) of + ok -> + make_dirs(Rest); + {error, Error} -> + {error, Error} + end. + +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}. + +tempfilename(Base) -> + {MegaSecs, Secs, MicroSecs} = now(), + Filename = io_lib:format("~s-~s-~p.~p", [Base, os:getpid(), + MegaSecs * 1000000 + Secs, MicroSecs]), + Filename. + +ensurefile(Rootdir, Key, Content) -> + {Dirs, Path} = path_for_key(Rootdir, Key), + case readfile_and_verify(Path, Content) of + ok -> + lists:foreach(fun (Dir) -> fsync(Dir) end, [Path, Rootdir | Dirs]); + {error, enoent} -> + case make_dirs([Rootdir, Rootdir ++ "nursery/"] ++ Dirs) of + ok -> + NurseryName = Rootdir ++ "nursery/" ++ + tempfilename(hex:bin_to_hexstr(Key)), + _Result = writefile(Path, NurseryName, Content), + lists:foreach(fun (Dir) -> + fsync(Dir) + end, + [Path, Rootdir | Dirs]); %% XXX check results + {error, Error} -> + io:format("Error creating directory: ~w~n", [Error]) + end; + {error, Error} -> + exit({perm, fileerror, "Error reading file", Error}) + end. 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, -- cgit v1.1 From eb95f6951e7a4abd2b7685b2de07de90b90ee0d2 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 25 Sep 2014 08:35:07 +0200 Subject: perm: Don't crash if file content is different, tell caller instead. Better error handling. --- src/perm.erl | 54 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/perm.erl b/src/perm.erl index 2ce5b46..34f431c 100644 --- a/src/perm.erl +++ b/src/perm.erl @@ -6,17 +6,25 @@ -module(perm). -export([ensurefile/3]). -fsync(Name) -> - fsyncport:fsync(Name). +fsync([]) -> + ok; +fsync([Name | Rest]) -> + case fsyncport:fsync(Name) of + ok -> + fsync(Rest); + {error, Error} -> + {error, Error} + end. readfile_and_verify(Name, Content) -> case file:read_file(Name) of {ok, ContentsReadBinary} -> ContentsRead = binary_to_list(ContentsReadBinary), - if Content == ContentsRead -> + if + Content == ContentsRead -> ok; - true -> - {error, "File contents differ"} + true -> + differ end; {error, Error} -> {error, Error} @@ -72,24 +80,32 @@ tempfilename(Base) -> 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. + ensurefile(Rootdir, Key, Content) -> {Dirs, Path} = path_for_key(Rootdir, Key), case readfile_and_verify(Path, Content) of ok -> - lists:foreach(fun (Dir) -> fsync(Dir) end, [Path, Rootdir | Dirs]); + check_error(fsync([Path, Rootdir | Dirs]), "Error in fsync"); + differ -> + differ; {error, enoent} -> - case make_dirs([Rootdir, Rootdir ++ "nursery/"] ++ Dirs) of - ok -> - NurseryName = Rootdir ++ "nursery/" ++ - tempfilename(hex:bin_to_hexstr(Key)), - _Result = writefile(Path, NurseryName, Content), - lists:foreach(fun (Dir) -> - fsync(Dir) - end, - [Path, Rootdir | Dirs]); %% XXX check results - {error, Error} -> - io:format("Error creating directory: ~w~n", [Error]) - end; + check_error(make_dirs([Rootdir, Rootdir ++ "nursery/"] ++ Dirs), + "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"); {error, Error} -> - exit({perm, fileerror, "Error reading file", Error}) + exit_with_error(Error, "Error reading file") end. -- cgit v1.1 From 11f8efc7fb27935761c38cf32f41836193ae97f4 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 25 Sep 2014 09:04:48 +0200 Subject: perm: Added readfile function. --- src/perm.erl | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/perm.erl b/src/perm.erl index 34f431c..5cd2889 100644 --- a/src/perm.erl +++ b/src/perm.erl @@ -4,7 +4,7 @@ %% -module(perm). --export([ensurefile/3]). +-export([ensurefile/3, readfile/2]). fsync([]) -> ok; @@ -18,14 +18,10 @@ fsync([Name | Rest]) -> readfile_and_verify(Name, Content) -> case file:read_file(Name) of - {ok, ContentsReadBinary} -> - ContentsRead = binary_to_list(ContentsReadBinary), - if - Content == ContentsRead -> - ok; - true -> - differ - end; + {ok, ContentsRead} when Content == ContentsRead -> + ok; + {ok, _ContentsRead} -> + differ; {error, Error} -> {error, Error} end. @@ -109,3 +105,14 @@ ensurefile(Rootdir, Key, Content) -> {error, Error} -> exit_with_error(Error, "Error reading file") end. + +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. -- cgit v1.1 From 62e2a6e4849d342f90a3860554bf44df4e563d3b Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 25 Sep 2014 15:18:39 +0200 Subject: Added atomic module --- src/atomic.erl | 24 +++++++++++++++++++ src/hex.erl | 1 + src/perm.erl | 73 ++++++++++++---------------------------------------------- src/util.erl | 57 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 58 deletions(-) create mode 100644 src/atomic.erl create mode 100644 src/util.erl 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. -- cgit v1.1 From f5fd5ac81904160c6bd4ccda04e333ca44461b22 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Thu, 25 Sep 2014 17:57:31 +0200 Subject: Add Makefile to c_src --- Makefile | 5 ++++- c_src/Makefile | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 c_src/Makefile diff --git a/Makefile b/Makefile index 4e54096..b4bb715 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ build all: (cd c_src && make all) - cp c_src/fsynchelper priv/fsynchelper + mkdir -p priv + cp c_src/fsynchelper priv/ erl -make clean: + (cd c_src && make clean) + -rm priv/fsynchelper -rm ebin/*.beam diff --git a/c_src/Makefile b/c_src/Makefile new file mode 100644 index 0000000..338dc6d --- /dev/null +++ b/c_src/Makefile @@ -0,0 +1,13 @@ +CC = gcc +CFLAGS = -Wall +LDFLAGS = + +PORTS = fsynchelper + +all: $(PORTS) + +clean: + rm -f *.o $(PORTS) + +fsynchelper: net_read_write.o erlport.o fsynchelper.o + $(CC) $(LDFLAGS) -o fsynchelper net_read_write.o erlport.o fsynchelper.o -- cgit v1.1 From 7fe4225f5969d0c5135363dcfa99d7511100a0b8 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 26 Sep 2014 03:27:43 +0200 Subject: Added implementation of index file --- src/index.erl | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/index.erl diff --git a/src/index.erl b/src/index.erl new file mode 100644 index 0000000..5382485 --- /dev/null +++ b/src/index.erl @@ -0,0 +1,81 @@ +%% +%% 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. + +-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), + file:close(File); + {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), + file:close(File); + {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. -- cgit v1.1 From 9c0d01c3059a7f82ac9acd5574755feed2f351a8 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Fri, 26 Sep 2014 10:28:47 +0200 Subject: index: Added fsync --- src/index.erl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/index.erl b/src/index.erl index 5382485..5fd468b 100644 --- a/src/index.erl +++ b/src/index.erl @@ -7,6 +7,10 @@ %% 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]). @@ -28,7 +32,8 @@ add(Basepath, Index, Entry) when is_binary(Entry), size(Entry) == ?ENTRYSIZE -> end, EntryText = hex:bin_to_hexstr(Entry) ++ "\n", ok = file:write(File, EntryText), - file:close(File); + ok = file:close(File), + util:fsync([Basepath, filename:dirname(Basepath)]); {error, Error} -> util:exit_with_error(Error, writefile, "Error opening file for writing") @@ -39,7 +44,8 @@ truncate(Basepath, Index) -> {ok, File} -> {ok, _Position} = file:position(File, Index * ?ENTRYSIZEINFILE), ok = file:truncate(File), - file:close(File); + ok = file:close(File), + util:fsync([Basepath, filename:dirname(Basepath)]); {error, Error} -> util:exit_with_error(Error, writefile, "Error opening file for writing") -- cgit v1.1