summaryrefslogtreecommitdiff
path: root/src/perm.erl
blob: 5cd28894f7a654e39206a7b70b3516c1a5bf73fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
%%
%% Copyright (c) 2014 Kungliga Tekniska Högskolan
%% (KTH Royal Institute of Technology, Stockholm, Sweden).
%%

-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.

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.

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.

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 ->
	    check_error(fsync([Path, Rootdir | Dirs]), "Error in fsync");
        differ ->
            differ;
	{error, enoent} ->
            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_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.