PageRenderTime 35ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/apps/couch/src/couch_file.erl

http://github.com/cloudant/bigcouch
Erlang | 625 lines | 447 code | 79 blank | 99 comment | 5 complexity | 439025d0248a12dbdcb97e61329ade76 MD5 | raw file
Possible License(s): Apache-2.0
  1. % Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. % use this file except in compliance with the License. You may obtain a copy of
  3. % the License at
  4. %
  5. % http://www.apache.org/licenses/LICENSE-2.0
  6. %
  7. % Unless required by applicable law or agreed to in writing, software
  8. % distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. % WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. % License for the specific language governing permissions and limitations under
  11. % the License.
  12. -module(couch_file).
  13. -behaviour(gen_server).
  14. -include("couch_db.hrl").
  15. -define(SIZE_BLOCK, 4096).
  16. -record(file, {
  17. fd,
  18. tail_append_begin = 0, % 09 UPGRADE CODE
  19. eof = 0
  20. }).
  21. -export([open/1, open/2, close/1, bytes/1, sync/1, append_binary/2,old_pread/3]).
  22. -export([append_term/2, pread_term/2, pread_iolist/2, write_header/2]).
  23. -export([pread_binary/2, read_header/1, truncate/2, upgrade_old_header/2]).
  24. -export([append_term_md5/2,append_binary_md5/2]).
  25. -export([init/1, terminate/2, handle_call/3, handle_cast/2, code_change/3, handle_info/2]).
  26. -export([delete/2,delete/3,init_delete_dir/1]).
  27. %%----------------------------------------------------------------------
  28. %% Args: Valid Options are [create] and [create,overwrite].
  29. %% Files are opened in read/write mode.
  30. %% Returns: On success, {ok, Fd}
  31. %% or {error, Reason} if the file could not be opened.
  32. %%----------------------------------------------------------------------
  33. open(Filepath) ->
  34. open(Filepath, []).
  35. open(Filepath, Options) ->
  36. case gen_server:start_link(couch_file,
  37. {Filepath, Options, self(), Ref = make_ref()}, []) of
  38. {ok, Fd} ->
  39. {ok, Fd};
  40. ignore ->
  41. % get the error
  42. receive
  43. {Ref, Pid, Error} ->
  44. case process_info(self(), trap_exit) of
  45. {trap_exit, true} -> receive {'EXIT', Pid, _} -> ok end;
  46. {trap_exit, false} -> ok
  47. end,
  48. case Error of
  49. {error, eacces} -> {file_permission_error, Filepath};
  50. _ -> Error
  51. end
  52. end;
  53. Error ->
  54. Error
  55. end.
  56. %%----------------------------------------------------------------------
  57. %% Purpose: To append an Erlang term to the end of the file.
  58. %% Args: Erlang term to serialize and append to the file.
  59. %% Returns: {ok, Pos} where Pos is the file offset to the beginning the
  60. %% serialized term. Use pread_term to read the term back.
  61. %% or {error, Reason}.
  62. %%----------------------------------------------------------------------
  63. append_term(Fd, Term) ->
  64. append_binary(Fd, term_to_binary(Term, [compressed, {minor_version,1}])).
  65. append_term_md5(Fd, Term) ->
  66. append_binary_md5(Fd, term_to_binary(Term, [compressed, {minor_version,1}])).
  67. %%----------------------------------------------------------------------
  68. %% Purpose: To append an Erlang binary to the end of the file.
  69. %% Args: Erlang term to serialize and append to the file.
  70. %% Returns: {ok, Pos} where Pos is the file offset to the beginning the
  71. %% serialized term. Use pread_term to read the term back.
  72. %% or {error, Reason}.
  73. %%----------------------------------------------------------------------
  74. append_binary(Fd, Bin) ->
  75. Size = iolist_size(Bin),
  76. gen_server:call(Fd, {append_bin,
  77. [<<0:1/integer,Size:31/integer>>, Bin]}, infinity).
  78. append_binary_md5(Fd, Bin) ->
  79. Size = iolist_size(Bin),
  80. gen_server:call(Fd, {append_bin,
  81. [<<1:1/integer,Size:31/integer>>, couch_util:md5(Bin), Bin]}, infinity).
  82. %%----------------------------------------------------------------------
  83. %% Purpose: Reads a term from a file that was written with append_term
  84. %% Args: Pos, the offset into the file where the term is serialized.
  85. %% Returns: {ok, Term}
  86. %% or {error, Reason}.
  87. %%----------------------------------------------------------------------
  88. pread_term(Fd, Pos) ->
  89. {ok, Bin} = pread_binary(Fd, Pos),
  90. {ok, binary_to_term(Bin)}.
  91. %%----------------------------------------------------------------------
  92. %% Purpose: Reads a binrary from a file that was written with append_binary
  93. %% Args: Pos, the offset into the file where the term is serialized.
  94. %% Returns: {ok, Term}
  95. %% or {error, Reason}.
  96. %%----------------------------------------------------------------------
  97. pread_binary(Fd, Pos) ->
  98. {ok, L} = pread_iolist(Fd, Pos),
  99. {ok, iolist_to_binary(L)}.
  100. pread_iolist(Fd, Pos) ->
  101. case gen_server:call(Fd, {pread_iolist, Pos}, infinity) of
  102. {ok, IoList, <<>>} ->
  103. {ok, IoList};
  104. {ok, IoList, Md5} ->
  105. case couch_util:md5(IoList) of
  106. Md5 ->
  107. {ok, IoList};
  108. _ ->
  109. exit({file_corruption, <<"file corruption">>})
  110. end;
  111. Error ->
  112. Error
  113. end.
  114. %%----------------------------------------------------------------------
  115. %% Purpose: The length of a file, in bytes.
  116. %% Returns: {ok, Bytes}
  117. %% or {error, Reason}.
  118. %%----------------------------------------------------------------------
  119. % length in bytes
  120. bytes(Fd) ->
  121. gen_server:call(Fd, bytes, infinity).
  122. %%----------------------------------------------------------------------
  123. %% Purpose: Truncate a file to the number of bytes.
  124. %% Returns: ok
  125. %% or {error, Reason}.
  126. %%----------------------------------------------------------------------
  127. truncate(Fd, Pos) ->
  128. gen_server:call(Fd, {truncate, Pos}, infinity).
  129. %%----------------------------------------------------------------------
  130. %% Purpose: Ensure all bytes written to the file are flushed to disk.
  131. %% Returns: ok
  132. %% or {error, Reason}.
  133. %%----------------------------------------------------------------------
  134. sync(Filepath) when is_list(Filepath) ->
  135. {ok, Fd} = file:open(Filepath, [append, raw]),
  136. try ok = file:sync(Fd) after ok = file:close(Fd) end;
  137. sync(Fd) ->
  138. gen_server:call(Fd, sync, infinity).
  139. %%----------------------------------------------------------------------
  140. %% Purpose: Close the file.
  141. %% Returns: ok
  142. %%----------------------------------------------------------------------
  143. close(Fd) ->
  144. gen_server:call(Fd, close, infinity).
  145. delete(RootDir, Filepath) ->
  146. delete(RootDir, Filepath, true).
  147. delete(RootDir, Filepath, Async) ->
  148. DelFile = filename:join([RootDir,".delete", ?b2l(couch_uuids:random())]),
  149. case file:rename(Filepath, DelFile) of
  150. ok ->
  151. if (Async) ->
  152. spawn(file, delete, [DelFile]),
  153. ok;
  154. true ->
  155. file:delete(DelFile)
  156. end;
  157. Error ->
  158. Error
  159. end.
  160. init_delete_dir(RootDir) ->
  161. Dir = filename:join(RootDir,".delete"),
  162. % note: ensure_dir requires an actual filename companent, which is the
  163. % reason for "foo".
  164. filelib:ensure_dir(filename:join(Dir,"foo")),
  165. filelib:fold_files(Dir, ".*", true,
  166. fun(Filename, _) ->
  167. ok = file:delete(Filename)
  168. end, ok).
  169. % 09 UPGRADE CODE
  170. old_pread(Fd, Pos, Len) ->
  171. {ok, <<RawBin:Len/binary>>, false} = gen_server:call(Fd, {pread, Pos, Len}, infinity),
  172. {ok, RawBin}.
  173. % 09 UPGRADE CODE
  174. upgrade_old_header(Fd, Sig) ->
  175. gen_server:call(Fd, {upgrade_old_header, Sig}, infinity).
  176. read_header(Fd) ->
  177. case gen_server:call(Fd, find_header, infinity) of
  178. {ok, Bin} ->
  179. {ok, binary_to_term(Bin)};
  180. Else ->
  181. Else
  182. end.
  183. write_header(Fd, Data) ->
  184. Bin = term_to_binary(Data),
  185. Md5 = couch_util:md5(Bin),
  186. % now we assemble the final header binary and write to disk
  187. FinalBin = <<Md5/binary, Bin/binary>>,
  188. gen_server:call(Fd, {write_header, FinalBin}, infinity).
  189. init_status_error(ReturnPid, Ref, Error) ->
  190. ReturnPid ! {Ref, self(), Error},
  191. ignore.
  192. % server functions
  193. init({Filepath, Options, ReturnPid, Ref}) ->
  194. process_flag(trap_exit, true),
  195. erlang:send_after(60000, self(), maybe_close),
  196. case lists:member(create, Options) of
  197. true ->
  198. filelib:ensure_dir(Filepath),
  199. case file:open(Filepath, [read, append, raw, binary]) of
  200. {ok, Fd} ->
  201. {ok, Length} = file:position(Fd, eof),
  202. case Length > 0 of
  203. true ->
  204. % this means the file already exists and has data.
  205. % FYI: We don't differentiate between empty files and non-existant
  206. % files here.
  207. case lists:member(overwrite, Options) of
  208. true ->
  209. {ok, 0} = file:position(Fd, 0),
  210. ok = file:truncate(Fd),
  211. ok = file:sync(Fd),
  212. maybe_track_open_os_files(Options),
  213. {ok, #file{fd=Fd}};
  214. false ->
  215. ok = file:close(Fd),
  216. init_status_error(ReturnPid, Ref, file_exists)
  217. end;
  218. false ->
  219. maybe_track_open_os_files(Options),
  220. {ok, #file{fd=Fd}}
  221. end;
  222. Error ->
  223. init_status_error(ReturnPid, Ref, Error)
  224. end;
  225. false ->
  226. % open in read mode first, so we don't create the file if it doesn't exist.
  227. case file:open(Filepath, [read, raw]) of
  228. {ok, Fd_Read} ->
  229. {ok, Fd} = file:open(Filepath, [read, append, raw, binary]),
  230. ok = file:close(Fd_Read),
  231. maybe_track_open_os_files(Options),
  232. {ok, Length} = file:position(Fd, eof),
  233. {ok, #file{fd=Fd, eof=Length}};
  234. Error ->
  235. init_status_error(ReturnPid, Ref, Error)
  236. end
  237. end.
  238. maybe_track_open_os_files(_FileOptions) ->
  239. couch_stats_collector:track_process_count({couchdb, open_os_files}).
  240. terminate(_Reason, #file{fd = nil}) ->
  241. ok;
  242. terminate(_Reason, #file{fd = Fd}) ->
  243. file:close(Fd).
  244. handle_call(close, _From, #file{fd=Fd}=File) ->
  245. {stop, normal, file:close(Fd), File#file{fd = nil}};
  246. handle_call({pread_iolist, Pos}, _From, File) ->
  247. {RawData, NextPos} = try
  248. % up to 8Kbs of read ahead
  249. read_raw_iolist_int(File, Pos, 2 * ?SIZE_BLOCK - (Pos rem ?SIZE_BLOCK))
  250. catch
  251. _:_ ->
  252. read_raw_iolist_int(File, Pos, 4)
  253. end,
  254. <<Prefix:1/integer, Len:31/integer, RestRawData/binary>> =
  255. iolist_to_binary(RawData),
  256. case Prefix of
  257. 1 ->
  258. {Md5, IoList} = extract_md5(
  259. maybe_read_more_iolist(RestRawData, 16 + Len, NextPos, File)),
  260. {reply, {ok, IoList, Md5}, File};
  261. 0 ->
  262. IoList = maybe_read_more_iolist(RestRawData, Len, NextPos, File),
  263. {reply, {ok, IoList, <<>>}, File}
  264. end;
  265. handle_call({pread, Pos, Bytes}, _From, #file{fd=Fd,tail_append_begin=TailAppendBegin}=File) ->
  266. {ok, Bin} = file:pread(Fd, Pos, Bytes),
  267. {reply, {ok, Bin, Pos >= TailAppendBegin}, File};
  268. handle_call(bytes, _From, #file{eof=Length}=File) ->
  269. {reply, {ok, Length}, File};
  270. handle_call(sync, _From, #file{fd=Fd}=File) ->
  271. {reply, file:sync(Fd), File};
  272. handle_call({truncate, Pos}, _From, #file{fd=Fd}=File) ->
  273. {ok, Pos} = file:position(Fd, Pos),
  274. case file:truncate(Fd) of
  275. ok ->
  276. {reply, ok, File#file{eof=Pos}};
  277. Error ->
  278. {reply, Error, File}
  279. end;
  280. handle_call({append_bin, Bin}, _From, #file{fd=Fd, eof=Pos}=File) ->
  281. Blocks = make_blocks(Pos rem ?SIZE_BLOCK, Bin),
  282. case file:write(Fd, Blocks) of
  283. ok ->
  284. {reply, {ok, Pos}, File#file{eof=Pos+iolist_size(Blocks)}};
  285. Error ->
  286. {reply, Error, File}
  287. end;
  288. handle_call({write_header, Bin}, _From, #file{fd=Fd, eof=Pos}=File) ->
  289. BinSize = size(Bin),
  290. case Pos rem ?SIZE_BLOCK of
  291. 0 ->
  292. Padding = <<>>;
  293. BlockOffset ->
  294. Padding = <<0:(8*(?SIZE_BLOCK-BlockOffset))>>
  295. end,
  296. FinalBin = [Padding, <<1, BinSize:32/integer>> | make_blocks(5, [Bin])],
  297. case file:write(Fd, FinalBin) of
  298. ok ->
  299. {reply, ok, File#file{eof=Pos+iolist_size(FinalBin)}};
  300. Error ->
  301. {reply, Error, File}
  302. end;
  303. handle_call({upgrade_old_header, Prefix}, _From, #file{fd=Fd}=File) ->
  304. case (catch read_old_header(Fd, Prefix)) of
  305. {ok, Header} ->
  306. TailAppendBegin = File#file.eof,
  307. Bin = term_to_binary(Header),
  308. Md5 = couch_util:md5(Bin),
  309. % now we assemble the final header binary and write to disk
  310. FinalBin = <<Md5/binary, Bin/binary>>,
  311. {reply, ok, _} = handle_call({write_header, FinalBin}, ok, File),
  312. ok = write_old_header(Fd, <<"upgraded">>, TailAppendBegin),
  313. {reply, ok, File#file{tail_append_begin=TailAppendBegin}};
  314. _Error ->
  315. case (catch read_old_header(Fd, <<"upgraded">>)) of
  316. {ok, TailAppendBegin} ->
  317. {reply, ok, File#file{tail_append_begin = TailAppendBegin}};
  318. _Error2 ->
  319. {reply, ok, File}
  320. end
  321. end;
  322. handle_call(find_header, _From, #file{fd=Fd, eof=Pos}=File) ->
  323. {reply, find_header(Fd, Pos div ?SIZE_BLOCK), File}.
  324. % 09 UPGRADE CODE
  325. -define(HEADER_SIZE, 2048). % size of each segment of the doubly written header
  326. % 09 UPGRADE CODE
  327. read_old_header(Fd, Prefix) ->
  328. {ok, Bin} = file:pread(Fd, 0, 2*(?HEADER_SIZE)),
  329. <<Bin1:(?HEADER_SIZE)/binary, Bin2:(?HEADER_SIZE)/binary>> = Bin,
  330. Result =
  331. % read the first header
  332. case extract_header(Prefix, Bin1) of
  333. {ok, Header1} ->
  334. case extract_header(Prefix, Bin2) of
  335. {ok, Header2} ->
  336. case Header1 == Header2 of
  337. true ->
  338. % Everything is completely normal!
  339. {ok, Header1};
  340. false ->
  341. % To get here we must have two different header versions with signatures intact.
  342. % It's weird but possible (a commit failure right at the 2k boundary). Log it and take the first.
  343. ?LOG_INFO("Header version differences.~nPrimary Header: ~p~nSecondary Header: ~p", [Header1, Header2]),
  344. {ok, Header1}
  345. end;
  346. Error ->
  347. % error reading second header. It's ok, but log it.
  348. ?LOG_INFO("Secondary header corruption (error: ~p). Using primary header.", [Error]),
  349. {ok, Header1}
  350. end;
  351. Error ->
  352. % error reading primary header
  353. case extract_header(Prefix, Bin2) of
  354. {ok, Header2} ->
  355. % log corrupt primary header. It's ok since the secondary is still good.
  356. ?LOG_INFO("Primary header corruption (error: ~p). Using secondary header.", [Error]),
  357. {ok, Header2};
  358. _ ->
  359. % error reading secondary header too
  360. % return the error, no need to log anything as the caller will be responsible for dealing with the error.
  361. Error
  362. end
  363. end,
  364. case Result of
  365. {ok, {pointer_to_header_data, Ptr}} ->
  366. pread_term(Fd, Ptr);
  367. _ ->
  368. Result
  369. end.
  370. % 09 UPGRADE CODE
  371. extract_header(Prefix, Bin) ->
  372. SizeOfPrefix = size(Prefix),
  373. SizeOfTermBin = ?HEADER_SIZE -
  374. SizeOfPrefix -
  375. 16, % md5 sig
  376. <<HeaderPrefix:SizeOfPrefix/binary, TermBin:SizeOfTermBin/binary, Sig:16/binary>> = Bin,
  377. % check the header prefix
  378. case HeaderPrefix of
  379. Prefix ->
  380. % check the integrity signature
  381. case couch_util:md5(TermBin) == Sig of
  382. true ->
  383. Header = binary_to_term(TermBin),
  384. {ok, Header};
  385. false ->
  386. header_corrupt
  387. end;
  388. _ ->
  389. unknown_header_type
  390. end.
  391. % 09 UPGRADE CODE
  392. write_old_header(Fd, Prefix, Data) ->
  393. TermBin = term_to_binary(Data),
  394. % the size of all the bytes written to the header, including the md5 signature (16 bytes)
  395. FilledSize = byte_size(Prefix) + byte_size(TermBin) + 16,
  396. {TermBin2, FilledSize2} =
  397. case FilledSize > ?HEADER_SIZE of
  398. true ->
  399. % too big!
  400. {ok, Pos} = append_binary(Fd, TermBin),
  401. PtrBin = term_to_binary({pointer_to_header_data, Pos}),
  402. {PtrBin, byte_size(Prefix) + byte_size(PtrBin) + 16};
  403. false ->
  404. {TermBin, FilledSize}
  405. end,
  406. ok = file:sync(Fd),
  407. % pad out the header with zeros, then take the md5 hash
  408. PadZeros = <<0:(8*(?HEADER_SIZE - FilledSize2))>>,
  409. Sig = couch_util:md5([TermBin2, PadZeros]),
  410. % now we assemble the final header binary and write to disk
  411. WriteBin = <<Prefix/binary, TermBin2/binary, PadZeros/binary, Sig/binary>>,
  412. ?HEADER_SIZE = size(WriteBin), % sanity check
  413. DblWriteBin = [WriteBin, WriteBin],
  414. ok = file:pwrite(Fd, 0, DblWriteBin),
  415. ok = file:sync(Fd).
  416. handle_cast(close, Fd) ->
  417. {stop,normal,Fd}.
  418. code_change(_OldVsn, State, _Extra) ->
  419. {ok, State}.
  420. handle_info(maybe_close, Fd) ->
  421. case process_info(self(), monitored_by) of
  422. {monitored_by, [_StatsCollector]} ->
  423. {stop, normal, Fd};
  424. {monitored_by, []} ->
  425. ?LOG_ERROR("~p ~p is un-monitored, maybe stats collector died",
  426. [?MODULE, self()]),
  427. {stop, normal, Fd};
  428. _Else ->
  429. erlang:send_after(10000, self(), maybe_close),
  430. {noreply, Fd}
  431. end;
  432. handle_info({'EXIT', _, normal}, Fd) ->
  433. {noreply, Fd};
  434. handle_info({'EXIT', _, Reason}, Fd) ->
  435. {stop, Reason, Fd}.
  436. find_header(_Fd, -1) ->
  437. no_valid_header;
  438. find_header(Fd, Block) ->
  439. case (catch load_header(Fd, Block)) of
  440. {ok, Bin} ->
  441. {ok, Bin};
  442. _Error ->
  443. find_header(Fd, Block -1)
  444. end.
  445. load_header(Fd, Block) ->
  446. {ok, <<1, HeaderLen:32/integer, RestBlock/binary>>} =
  447. file:pread(Fd, Block * ?SIZE_BLOCK, ?SIZE_BLOCK),
  448. TotalBytes = calculate_total_read_len(1, HeaderLen),
  449. case TotalBytes > byte_size(RestBlock) of
  450. false ->
  451. <<RawBin:TotalBytes/binary, _/binary>> = RestBlock;
  452. true ->
  453. {ok, Missing} = file:pread(
  454. Fd, (Block * ?SIZE_BLOCK) + 5 + byte_size(RestBlock),
  455. TotalBytes - byte_size(RestBlock)),
  456. RawBin = <<RestBlock/binary, Missing/binary>>
  457. end,
  458. <<Md5Sig:16/binary, HeaderBin/binary>> =
  459. iolist_to_binary(remove_block_prefixes(5, RawBin)),
  460. Md5Sig = couch_util:md5(HeaderBin),
  461. {ok, HeaderBin}.
  462. maybe_read_more_iolist(Buffer, DataSize, _, _)
  463. when DataSize =< byte_size(Buffer) ->
  464. <<Data:DataSize/binary, _/binary>> = Buffer,
  465. [Data];
  466. maybe_read_more_iolist(Buffer, DataSize, NextPos, File) ->
  467. {Missing, _} =
  468. read_raw_iolist_int(File, NextPos, DataSize - byte_size(Buffer)),
  469. [Buffer, Missing].
  470. -spec read_raw_iolist_int(#file{}, Pos::non_neg_integer(), Len::non_neg_integer()) ->
  471. {Data::iolist(), CurPos::non_neg_integer()}.
  472. read_raw_iolist_int(Fd, {Pos, _Size}, Len) -> % 0110 UPGRADE CODE
  473. read_raw_iolist_int(Fd, Pos, Len);
  474. read_raw_iolist_int(#file{fd=Fd, tail_append_begin=TAB}, Pos, Len) ->
  475. BlockOffset = Pos rem ?SIZE_BLOCK,
  476. TotalBytes = calculate_total_read_len(BlockOffset, Len),
  477. {ok, <<RawBin:TotalBytes/binary>>} = file:pread(Fd, Pos, TotalBytes),
  478. if Pos >= TAB ->
  479. {remove_block_prefixes(BlockOffset, RawBin), Pos + TotalBytes};
  480. true ->
  481. % 09 UPGRADE CODE
  482. <<ReturnBin:Len/binary, _/binary>> = RawBin,
  483. {[ReturnBin], Pos + Len}
  484. end.
  485. -spec extract_md5(iolist()) -> {binary(), iolist()}.
  486. extract_md5(FullIoList) ->
  487. {Md5List, IoList} = split_iolist(FullIoList, 16, []),
  488. {iolist_to_binary(Md5List), IoList}.
  489. calculate_total_read_len(0, FinalLen) ->
  490. calculate_total_read_len(1, FinalLen) + 1;
  491. calculate_total_read_len(BlockOffset, FinalLen) ->
  492. case ?SIZE_BLOCK - BlockOffset of
  493. BlockLeft when BlockLeft >= FinalLen ->
  494. FinalLen;
  495. BlockLeft ->
  496. FinalLen + ((FinalLen - BlockLeft) div (?SIZE_BLOCK -1)) +
  497. if ((FinalLen - BlockLeft) rem (?SIZE_BLOCK -1)) =:= 0 -> 0;
  498. true -> 1 end
  499. end.
  500. remove_block_prefixes(_BlockOffset, <<>>) ->
  501. [];
  502. remove_block_prefixes(0, <<_BlockPrefix,Rest/binary>>) ->
  503. remove_block_prefixes(1, Rest);
  504. remove_block_prefixes(BlockOffset, Bin) ->
  505. BlockBytesAvailable = ?SIZE_BLOCK - BlockOffset,
  506. case size(Bin) of
  507. Size when Size > BlockBytesAvailable ->
  508. <<DataBlock:BlockBytesAvailable/binary,Rest/binary>> = Bin,
  509. [DataBlock | remove_block_prefixes(0, Rest)];
  510. _Size ->
  511. [Bin]
  512. end.
  513. make_blocks(_BlockOffset, []) ->
  514. [];
  515. make_blocks(0, IoList) ->
  516. [<<0>> | make_blocks(1, IoList)];
  517. make_blocks(BlockOffset, IoList) ->
  518. case split_iolist(IoList, (?SIZE_BLOCK - BlockOffset), []) of
  519. {Begin, End} ->
  520. [Begin | make_blocks(0, End)];
  521. _SplitRemaining ->
  522. IoList
  523. end.
  524. %% @doc Returns a tuple where the first element contains the leading SplitAt
  525. %% bytes of the original iolist, and the 2nd element is the tail. If SplitAt
  526. %% is larger than byte_size(IoList), return the difference.
  527. -spec split_iolist(IoList::iolist(), SplitAt::non_neg_integer(), Acc::list()) ->
  528. {iolist(), iolist()} | non_neg_integer().
  529. split_iolist(List, 0, BeginAcc) ->
  530. {lists:reverse(BeginAcc), List};
  531. split_iolist([], SplitAt, _BeginAcc) ->
  532. SplitAt;
  533. split_iolist([<<Bin/binary>> | Rest], SplitAt, BeginAcc) when SplitAt > byte_size(Bin) ->
  534. split_iolist(Rest, SplitAt - byte_size(Bin), [Bin | BeginAcc]);
  535. split_iolist([<<Bin/binary>> | Rest], SplitAt, BeginAcc) ->
  536. <<Begin:SplitAt/binary,End/binary>> = Bin,
  537. split_iolist([End | Rest], 0, [Begin | BeginAcc]);
  538. split_iolist([Sublist| Rest], SplitAt, BeginAcc) when is_list(Sublist) ->
  539. case split_iolist(Sublist, SplitAt, BeginAcc) of
  540. {Begin, End} ->
  541. {Begin, [End | Rest]};
  542. SplitRemaining ->
  543. split_iolist(Rest, SplitAt - (SplitAt - SplitRemaining), [Sublist | BeginAcc])
  544. end;
  545. split_iolist([Byte | Rest], SplitAt, BeginAcc) when is_integer(Byte) ->
  546. split_iolist(Rest, SplitAt - 1, [Byte | BeginAcc]).