1 %%%-------------------------------------------------------------------
2 %%% File : check_torrent.erl
3 %%% Author : Jesper Louis Andersen <jlouis@succubus.local.domain>
4 %%% License : See COPYING
5 %%% Description : Code for checking the correctness of a torrent file map
7 %%% Created : 6 Jul 2007 by Jesper Louis Andersen <jlouis@succubus.local.domain>
8 %%%-------------------------------------------------------------------
9 -module(etorrent_fs_checker
).
11 -include("etorrent_piece.hrl").
14 -export([read_and_check_torrent
/3, load_torrent
/1, ensure_file_sizes_correct
/1,
17 %%====================================================================
19 %%====================================================================
21 %% Check the contents of torrent Id, backed by filesystem FS and report a
22 %% list of bad pieces.
23 check_torrent(FS
, Id
) ->
24 Pieces
= etorrent_piece_mgr:select(Id
),
26 fun (#piece
{ idpn
= {_
, PN
}, hash = Hash
}) ->
27 {ok
, Data
} = etorrent_fs:read_piece(FS
, PN
),
28 Hash
=/= crypto:sha(Data
)
30 [P#piece
.idpn
|| P
<- Pieces
,
33 read_and_check_torrent(Id
, SupervisorPid
, Path
) ->
35 {ok
, Torrent
, Files
, Infohash
} =
38 %% Ensure the files are filled up with garbage of correct size
39 ok
= ensure_file_sizes_correct(Files
),
41 %% Build the dictionary mapping pieces to file operations
42 {ok
, FilePieceList
, NumberOfPieces
} =
43 build_dictionary_on_files(Id
, Torrent
, Files
),
45 %% Check the contents of the torrent, updates the state of the piecemap
46 FS
= etorrent_t_sup:get_pid(SupervisorPid
, fs
),
47 case etorrent_fast_resume:query_state(Id
) of
48 seeding
-> initialize_pieces_seed(Id
, FilePieceList
);
49 {bitfield
, BF
} -> initialize_pieces_from_bitfield(Id
, BF
, NumberOfPieces
, FilePieceList
);
51 ok
= etorrent_piece_mgr:add_pieces(
53 [{PN
, Hash
, Fls
, not_fetched
} || {PN
, {Hash
, Fls
}} <- FilePieceList
]),
54 ok
= initialize_pieces_from_disk(FS
, Id
, FilePieceList
);
55 %% XXX: The next one here could initialize with not_fetched all over
57 ok
= etorrent_piece_mgr:add_pieces(
59 [{PN
, Hash
, Fls
, not_fetched
} || {PN
, {Hash
, Fls
}} <- FilePieceList
]),
60 ok
= initialize_pieces_from_disk(FS
, Id
, FilePieceList
)
63 {ok
, Torrent
, FS
, Infohash
, NumberOfPieces
}.
66 {ok
, Workdir
} = application:get_env(etorrent
, dir
),
67 P
= filename:join([Workdir
, Path
]),
68 Torrent
= etorrent_bcoding:parse(P
),
69 Files
= etorrent_metainfo:get_files(Torrent
),
70 Name
= etorrent_metainfo:get_name(Torrent
),
71 InfoHash
= etorrent_metainfo:get_infohash(Torrent
),
73 [{filename:join([Name
, Filename
]), Size
} ||
74 {Filename
, Size
} <- Files
],
75 {ok
, Torrent
, FilesToCheck
, InfoHash
}.
77 ensure_file_sizes_correct(Files
) ->
78 {ok
, Workdir
} = application:get_env(etorrent
, dir
),
81 F
= filename:join([Workdir
, Pth
]),
82 Sz
= filelib:file_size(F
),
88 fill_file_ensure_path(F
, Missing
)
94 initialize_pieces_seed(Id
, FilePieceList
) ->
95 etorrent_piece_mgr:add_pieces(
97 [{PN
, Hash
, Files
, fetched
} || {PN
, {Hash
, Files
}} <- FilePieceList
]).
99 initialize_pieces_from_bitfield(Id
, BitField
, NumPieces
, FilePieceList
) ->
100 {ok
, Set
} = etorrent_peer_communication:destruct_bitfield(NumPieces
, BitField
),
102 case gb_sets:is_element(PN
, Set
) of
107 Pieces
= [{PN
, Hash
, Files
, F(PN
)} || {PN
, {Hash
, Files
}} <- FilePieceList
],
108 etorrent_piece_mgr:add_pieces(Id
, Pieces
).
110 initialize_pieces_from_disk(FS
, Id
, FilePieceList
) ->
111 F
= fun(PN
, Hash
, Files
) ->
112 {ok
, Data
} = etorrent_fs:read_piece(FS
, PN
),
113 State
= case Hash
=:= crypto:sha(Data
) of
117 {PN
, Hash
, Files
, State
}
119 etorrent_piece_mgr:add_pieces(
121 [F(PN
, Hash
, Files
) || {PN
, {Hash
, Files
}} <- FilePieceList
]).
123 build_dictionary_on_files(TorrentId
, Torrent
, Files
) ->
124 Pieces
= etorrent_metainfo:get_pieces(Torrent
),
125 PSize
= etorrent_metainfo:get_piece_length(Torrent
),
126 LastPieceSize
= lists:sum([S
|| {_F
, S
} <- Files
]) rem PSize
,
127 {ok
, PieceList
, N
} = construct_fpmap(Files
, 0, PSize
, LastPieceSize
,
128 lists:zip(lists:seq(0, length(Pieces
)-1), Pieces
),
130 MappedPieces
= [{Num
, {Hash
, insert_into_path_map(Ops
, TorrentId
)}} ||
131 {Num
, {Hash
, Ops
}} <- PieceList
],
132 {ok
, MappedPieces
, N
}.
134 %%====================================================================
135 %% Internal functions
136 %%====================================================================
138 extract_piece(0, Fs
, Offset
, B
) ->
140 extract_piece(Left
, [], _O
, _Building
) ->
141 {error_need_files
, Left
};
142 extract_piece(Left
, [{Pth
, Sz
} | R
], Offset
, Building
) ->
143 case (Sz
- Offset
) > Left
of
145 % There is enough bytes left in Pth
146 {ok
, [{Pth
, Sz
} | R
], Offset
+Left
,
147 [{Pth
, Offset
, Left
} | Building
]};
149 % There is not enough space left in Pth
150 BytesWeCanGet
= Sz
- Offset
,
151 extract_piece(Left
- BytesWeCanGet
, R
, 0,
152 [{Pth
, Offset
, BytesWeCanGet
} | Building
])
155 construct_fpmap([], _Offset
, _PieceSize
, _LPS
, [], Done
, N
) ->
157 construct_fpmap([], _O
, _P
, _LPS
, _Pieces
, _D
, _N
) ->
159 construct_fpmap(FileList
, Offset
, PieceSize
, LastPieceSize
,
160 [{Num
, Hash
}], Done
, N
) -> % Last piece
161 {ok
, FL
, OS
, Ops
} = extract_piece(case LastPieceSize
of
165 FileList
, Offset
, []),
166 construct_fpmap(FL
, OS
, PieceSize
, LastPieceSize
, [],
167 [{Num
, {Hash
, lists:reverse(Ops
)}} | Done
], N
+1);
168 construct_fpmap(FileList
, Offset
, PieceSize
, LastPieceSize
,
169 [{Num
, Hash
} | Ps
], Done
, N
) ->
170 {ok
, FL
, OS
, Ops
} = extract_piece(PieceSize
, FileList
, Offset
, []),
171 construct_fpmap(FL
, OS
, PieceSize
, LastPieceSize
, Ps
,
172 [{Num
, {Hash
, lists:reverse(Ops
)}} | Done
], N
+1).
174 insert_into_path_map(Ops
, TorrentId
) ->
175 insert_into_path_map(Ops
, TorrentId
, none
, none
).
177 insert_into_path_map([], _
, _
, _
) -> [];
178 insert_into_path_map([{Path
, Offset
, Size
} | Next
], TorrentId
, Path
, Keyval
) ->
179 [{Keyval
, Offset
, Size
} | insert_into_path_map(Next
, TorrentId
, Path
, Keyval
)];
180 insert_into_path_map([{Path
, Offset
, Size
} | Next
], TorrentId
, _Key
, _KeyVal
) ->
181 KeyVal
= etorrent_path_map:select(Path
, TorrentId
),
182 [{KeyVal
, Offset
, Size
} | insert_into_path_map(Next
, TorrentId
, Path
, KeyVal
)].
185 fill_file_ensure_path(Path
, Missing
) ->
186 case file:open(Path
, [read
, write
, delayed_write
, binary, raw
]) of
188 fill_file(FD
, Missing
);
191 ok
= filelib:ensure_dir(Path
),
192 case file:open(Path
, [read
, write
, delayed_write
, binary, raw
]) of
194 fill_file(FD
, Missing
);
200 fill_file(FD
, Missing
) ->
201 {ok
, _NP
} = file:position(FD
, {eof
, Missing
-1}),
202 file:write(FD
, <<0>>),