1
% Copyright 2007, 2008 Damien Katz <damien_katz@yahoo.com>
3
% Licensed under the Apache License, Version 2.0 (the "License");
4
% you may not use this file except in compliance with the License.
5
% You may obtain a copy of the License at
7
% http://www.apache.org/licenses/LICENSE-2.0
9
% Unless required by applicable law or agreed to in writing, software
10
% distributed under the License is distributed on an "AS IS" BASIS,
11
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
% See the License for the specific language governing permissions and
13
% limitations under the License.
16
-behaviour(gen_server).
18
-export([open/2, close/1, pread/3, pwrite/3, expand/2, bytes/1, sync/1, truncate/2]).
19
-export([append_term/2, pread_term/2]).
20
-export([init/1, terminate/2, handle_call/3, handle_cast/2, code_change/3, handle_info/2]).
22
%%----------------------------------------------------------------------
23
%% Args: Valid Options are [create] and [create,overwrite].
24
%% Files are opened in read/write mode.
25
%% Returns: On success, {ok, Fd}
26
%% or {error, Reason} if the file could not be opened.
27
%%----------------------------------------------------------------------
29
open(Filepath, Options) ->
30
case gen_server:start_link(couch_file, {Filepath, Options, self()}, []) of
32
% we got back an ok, but that doesn't really mean it was successful.
33
% Instead the true status has been sent back to us as a message.
34
% We do this because if the gen_server doesn't initialize properly,
35
% it generates a crash report that will get logged. This avoids
36
% that mess, because we don't want.
48
%%----------------------------------------------------------------------
49
%% Args: Pos is the offset from the beginning of the file, Bytes is
50
%% is the number of bytes to read.
51
%% Returns: {ok, Binary} where Binary is a binary data from disk
52
%% or {error, Reason}.
53
%%----------------------------------------------------------------------
55
pread(Fd, Pos, Bytes) when Bytes > 0 ->
56
gen_server:call(Fd, {pread, Pos, Bytes}).
59
%%----------------------------------------------------------------------
60
%% Args: Pos is the offset from the beginning of the file, Bin is
61
%% is the binary to write
63
%% or {error, Reason}.
64
%%----------------------------------------------------------------------
66
pwrite(Fd, Pos, Bin) ->
67
gen_server:call(Fd, {pwrite, Pos, Bin}).
69
%%----------------------------------------------------------------------
70
%% Purpose: To append a segment of zeros to the end of the file.
71
%% Args: Bytes is the number of bytes to append to the file.
72
%% Returns: {ok, Pos} where Pos is the file offset to the beginning of
74
%% or {error, Reason}.
75
%%----------------------------------------------------------------------
77
expand(Fd, Bytes) when Bytes > 0 ->
78
gen_server:call(Fd, {expand, Bytes}).
81
%%----------------------------------------------------------------------
82
%% Purpose: To append an Erlang term to the end of the file.
83
%% Args: Erlang term to serialize and append to the file.
84
%% Returns: {ok, Pos} where Pos is the file offset to the beginning the
85
%% serialized term. Use pread_term to read the term back.
86
%% or {error, Reason}.
87
%%----------------------------------------------------------------------
89
append_term(Fd, Term) ->
90
gen_server:call(Fd, {append_term, Term}).
93
%%----------------------------------------------------------------------
94
%% Purpose: Reads a term from a file that was written with append_term
95
%% Args: Pos, the offset into the file where the term is serialized.
96
%% Returns: {ok, Term}
97
%% or {error, Reason}.
98
%%----------------------------------------------------------------------
100
pread_term(Fd, Pos) ->
101
gen_server:call(Fd, {pread_term, Pos}).
104
%%----------------------------------------------------------------------
105
%% Purpose: The length of a file, in bytes.
106
%% Returns: {ok, Bytes}
107
%% or {error, Reason}.
108
%%----------------------------------------------------------------------
112
gen_server:call(Fd, bytes).
114
%%----------------------------------------------------------------------
115
%% Purpose: Truncate a file to the number of bytes.
117
%% or {error, Reason}.
118
%%----------------------------------------------------------------------
121
gen_server:call(Fd, {truncate, Pos}).
123
%%----------------------------------------------------------------------
124
%% Purpose: Ensure all bytes written to the file are flushed to disk.
126
%% or {error, Reason}.
127
%%----------------------------------------------------------------------
130
gen_server:call(Fd, sync).
132
%%----------------------------------------------------------------------
133
%% Purpose: Close the file. Is performed asynchronously.
135
%%----------------------------------------------------------------------
137
gen_server:cast(Fd, close).
141
init_status_ok(ReturnPid, Fd) ->
142
ReturnPid ! {self(), ok}, % signal back ok
145
init_status_error(ReturnPid, Error) ->
146
ReturnPid ! {self(), Error}, % signal back error status
147
self() ! self_close, % tell ourself to close async
152
init({Filepath, Options, ReturnPid}) ->
153
case lists:member(create, Options) of
155
filelib:ensure_dir(Filepath),
156
case file:open(Filepath, [read, write, raw, binary]) of
158
{ok, Length} = file:position(Fd, eof),
161
% this means the file already exists and has data.
162
% FYI: We don't differentiate between empty files and non-existant
163
% files here. That could cause issues someday.
164
case lists:member(overwrite, Options) of
166
{ok, 0} = file:position(Fd, 0),
167
ok = file:truncate(Fd),
168
init_status_ok(ReturnPid, Fd);
171
init_status_error(ReturnPid, {error, file_exists})
174
init_status_ok(ReturnPid, Fd)
177
init_status_error(ReturnPid, Error)
180
% open in read mode first, so we don't create the file if it doesn't exist.
181
case file:open(Filepath, [read, raw]) of
183
{ok, Fd} = file:open(Filepath, [read, write, raw, binary]),
184
ok = file:close(Fd_Read),
185
init_status_ok(ReturnPid, Fd);
187
init_status_error(ReturnPid, Error)
192
terminate(_Reason, nil) ->
194
terminate(_Reason, Fd) ->
199
handle_call({pread, Pos, Bytes}, _From, Fd) ->
200
{reply, file:pread(Fd, Pos, Bytes), Fd};
201
handle_call({pwrite, Pos, Bin}, _From, Fd) ->
202
{reply, file:pwrite(Fd, Pos, Bin), Fd};
203
handle_call({expand, Num}, _From, Fd) ->
204
{ok, Pos} = file:position(Fd, eof),
205
{reply, {file:pwrite(Fd, Pos + Num - 1, <<0>>), Pos}, Fd};
206
handle_call(bytes, _From, Fd) ->
207
{reply, file:position(Fd, eof), Fd};
208
handle_call(sync, _From, Fd) ->
209
{reply, file:sync(Fd), Fd};
210
handle_call({truncate, Pos}, _From, Fd) ->
211
{ok, Pos} = file:position(Fd, Pos),
212
{reply, file:truncate(Fd), Fd};
213
handle_call({append_term, Term}, _From, Fd) ->
214
Bin = term_to_binary(Term),
216
Bin2 = <<TermLen:32, Bin/binary>>,
217
{ok, Pos} = file:position(Fd, eof),
218
{reply, {file:pwrite(Fd, Pos, Bin2), Pos}, Fd};
219
handle_call({pread_term, Pos}, _From, Fd) ->
221
= file:pread(Fd, Pos, 4),
222
{ok, Bin} = file:pread(Fd, Pos + 4, TermLen),
223
{reply, {ok, binary_to_term(Bin)}, Fd}.
226
handle_cast(close, Fd) ->
227
{stop,normal,Fd}. % causes terminate to be called
229
code_change(_OldVsn, State, _Extra) ->
232
handle_info(self_close, State) ->
234
handle_info(_Info, State) ->