2 %% Copyright (C) 2006 Damien Katz
4 %% This program is free software; you can redistribute it and/or
5 %% modify it under the terms of the GNU General Public License
6 %% as published by the Free Software Foundation; either version 2
7 %% of the License, or (at your option) any later version.
9 %% This program is distributed in the hope that it will be useful,
10 %% but WITHOUT ANY WARRANTY; without even the implied warranty of
11 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 %% GNU General Public License for more details.
14 %% You should have received a copy of the GNU General Public License
15 %% along with this program; if not, write to the Free Software
16 %% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 -behaviour(gen_server
).
21 -export([open
/2, close
/1, pread
/3, pwrite
/3, expand
/2, bytes
/1, sync
/1, truncate
/2]).
22 -export([append_term
/2, pread_term
/2]).
23 -export([init
/1, terminate
/2, handle_call
/3, handle_cast
/2, code_change
/3, handle_info
/2]).
25 %%----------------------------------------------------------------------
26 %% Args: Valid Options are [create] and [create,overwrite].
27 %% Files are opened in read/write mode.
28 %% Returns: On success, {ok, Fd}
29 %% or {error, Reason} if the file could not be opened.
30 %%----------------------------------------------------------------------
32 open(Filepath
, Options
) ->
33 case gen_server:start_link(couch_file
, {Filepath
, Options
, self()}, []) of
35 % we got back an ok, but that doesn't really mean it was successful.
36 % Instead the true status has been sent back to us as a message.
37 % We do this because if the gen_server doesn't initialize properly,
38 % it generates a crash report that will get logged. This avoids
39 % that mess, because we don't want.
51 %%----------------------------------------------------------------------
52 %% Args: Pos is the offset from the beginning of the file, Bytes is
53 %% is the number of bytes to read.
54 %% Returns: {ok, Binary} where Binary is a binary data from disk
55 %% or {error, Reason}.
56 %%----------------------------------------------------------------------
58 pread(Fd
, Pos
, Bytes
) when Bytes
> 0 ->
59 gen_server:call(Fd
, {pread
, Pos
, Bytes
}).
62 %%----------------------------------------------------------------------
63 %% Args: Pos is the offset from the beginning of the file, Bin is
64 %% is the binary to write
66 %% or {error, Reason}.
67 %%----------------------------------------------------------------------
69 pwrite(Fd
, Pos
, Bin
) ->
70 gen_server:call(Fd
, {pwrite
, Pos
, Bin
}).
72 %%----------------------------------------------------------------------
73 %% Purpose: To append a segment of zeros to the end of the file.
74 %% Args: Bytes is the number of bytes to append to the file.
75 %% Returns: {ok, Pos} where Pos is the file offset to the beginning of
77 %% or {error, Reason}.
78 %%----------------------------------------------------------------------
80 expand(Fd
, Bytes
) when Bytes
> 0 ->
81 gen_server:call(Fd
, {expand
, Bytes
}).
84 %%----------------------------------------------------------------------
85 %% Purpose: To append an Erlang term to the end of the file.
86 %% Args: Erlang term to serialize and append to the file.
87 %% Returns: {ok, Pos} where Pos is the file offset to the beginning the
88 %% serialized term. Use pread_term to read the term back.
89 %% or {error, Reason}.
90 %%----------------------------------------------------------------------
92 append_term(Fd
, Term
) ->
93 gen_server:call(Fd
, {append_term
, Term
}).
96 %%----------------------------------------------------------------------
97 %% Purpose: Reads a term from a file that was written with append_term
98 %% Args: Pos, the offset into the file where the term is serialized.
99 %% Returns: {ok, Term}
100 %% or {error, Reason}.
101 %%----------------------------------------------------------------------
103 pread_term(Fd
, Pos
) ->
104 gen_server:call(Fd
, {pread_term
, Pos
}).
107 %%----------------------------------------------------------------------
108 %% Purpose: The length of a file, in bytes.
109 %% Returns: {ok, Bytes}
110 %% or {error, Reason}.
111 %%----------------------------------------------------------------------
115 gen_server:call(Fd
, bytes
).
117 %%----------------------------------------------------------------------
118 %% Purpose: Truncate a file to the number of bytes.
120 %% or {error, Reason}.
121 %%----------------------------------------------------------------------
124 gen_server:call(Fd
, {truncate
, Pos
}).
126 %%----------------------------------------------------------------------
127 %% Purpose: Ensure all bytes written to the file are flushed to disk.
129 %% or {error, Reason}.
130 %%----------------------------------------------------------------------
133 gen_server:call(Fd
, sync
).
135 %%----------------------------------------------------------------------
136 %% Purpose: Close the file. Is performed asynchronously.
138 %%----------------------------------------------------------------------
140 gen_server:cast(Fd
, close
).
144 init_status_ok(ReturnPid
, Fd
) ->
145 ReturnPid
! {self(), ok
}, % signal back ok
148 init_status_error(ReturnPid
, Error
) ->
149 ReturnPid
! {self(), Error
}, % signal back error status
150 self() ! self_close
, % tell ourself to close async
155 init({Filepath
, Options
, ReturnPid
}) ->
156 case lists:member(create
, Options
) of
158 filelib:ensure_dir(Filepath
),
159 case file:open(Filepath
, [read
, write
, raw
, binary]) of
161 {ok
, Length
} = file:position(Fd
, eof
),
164 % this means the file already exists and has data.
165 % FYI: We don't differentiate between empty files and non-existant
166 % files here. That could cause issues someday.
167 case lists:member(overwrite
, Options
) of
169 {ok
, 0} = file:position(Fd
, 0),
170 ok
= file:truncate(Fd
),
171 init_status_ok(ReturnPid
, Fd
);
174 init_status_error(ReturnPid
, {error
, file_exists
})
177 init_status_ok(ReturnPid
, Fd
)
180 init_status_error(ReturnPid
, Error
)
183 % open in read mode first, so we don't create the file if it doesn't exist.
184 case file:open(Filepath
, [read
, raw
]) of
186 {ok
, Fd
} = file:open(Filepath
, [read
, write
, raw
, binary]),
187 ok
= file:close(Fd_Read
),
188 init_status_ok(ReturnPid
, Fd
);
190 init_status_error(ReturnPid
, Error
)
195 terminate(_Reason
, nil
) ->
197 terminate(_Reason
, Fd
) ->
202 handle_call({pread
, Pos
, Bytes
}, _From
, Fd
) ->
203 {reply
, file:pread(Fd
, Pos
, Bytes
), Fd
};
204 handle_call({pwrite
, Pos
, Bin
}, _From
, Fd
) ->
205 {reply
, file:pwrite(Fd
, Pos
, Bin
), Fd
};
206 handle_call({expand
, Num
}, _From
, Fd
) ->
207 {ok
, Pos
} = file:position(Fd
, eof
),
208 {reply
, {file:pwrite(Fd
, Pos
+ Num
- 1, <<0>>), Pos
}, Fd
};
209 handle_call(bytes
, _From
, Fd
) ->
210 {reply
, file:position(Fd
, eof
), Fd
};
211 handle_call(sync
, _From
, Fd
) ->
212 {reply
, file:sync(Fd
), Fd
};
213 handle_call({truncate
, Pos
}, _From
, Fd
) ->
214 {ok
, Pos
} = file:position(Fd
, Pos
),
215 {reply
, file:truncate(Fd
), Fd
};
216 handle_call({append_term
, Term
}, _From
, Fd
) ->
217 Bin
= term_to_binary(Term
),
219 Bin2
= <<TermLen:32, Bin
/binary>>,
220 {ok
, Pos
} = file:position(Fd
, eof
),
221 {reply
, {file:pwrite(Fd
, Pos
, Bin2
), Pos
}, Fd
};
222 handle_call({pread_term
, Pos
}, _From
, Fd
) ->
224 = file:pread(Fd
, Pos
, 4),
225 {ok
, Bin
} = file:pread(Fd
, Pos
+ 4, TermLen
),
226 {reply
, {ok
, binary_to_term(Bin
)}, Fd
}.
229 handle_cast(close
, Fd
) ->
230 {stop
,normal
,Fd
}. % causes terminate to be called
232 code_change(_OldVsn
, State
, _Extra
) ->
235 handle_info(self_close
, State
) ->
237 handle_info(_Info
, State
) ->