1 /*
2
3 Copyright (c) 2013, Arvid Norberg
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
30
31 */
32
33 #include <sys/stat.h> // for chmod
34
35 #include "libtorrent/session.hpp"
36 #include "test.hpp"
37 #include "settings.hpp"
38 #include "setup_transfer.hpp"
39 #include "test_utils.hpp"
40 #include "libtorrent/create_torrent.hpp"
41 #include "libtorrent/alert_types.hpp"
42 #include "libtorrent/torrent_info.hpp"
43 #include "libtorrent/torrent_status.hpp"
44 #include "libtorrent/hex.hpp" // to_hex
45 #include "libtorrent/aux_/path.hpp"
46
47 namespace {
48
49 namespace
50 {
is_checking(int const state)51 bool is_checking(int const state)
52 {
53 return state == lt::torrent_status::checking_files
54 #if TORRENT_ABI_VERSION == 1
55 || state == lt::torrent_status::queued_for_checking
56 #endif
57 || state == lt::torrent_status::checking_resume_data;
58 }
59 }
60
61 enum
62 {
63 // make sure we don't accidentally require files to be writable just to
64 // check their hashes
65 read_only_files = 1,
66
67 // make sure we detect corrupt files and mark the appropriate pieces
68 // as not had
69 corrupt_files = 2,
70
71 incomplete_files = 4,
72
73 // make the files not be there when starting up, move the files in place and
74 // force-recheck. Make sure the stat cache is cleared and let us pick up the
75 // new files
76 force_recheck = 8,
77
78 // files that are *bigger* than expected still considered OK. We don't
79 // truncate files willy-nilly. Checking is a read-only operation.
80 extended_files = 16,
81 };
82
test_checking(int flags)83 void test_checking(int flags)
84 {
85 using namespace lt;
86
87 std::printf("\n==== TEST CHECKING %s%s%s%s%s=====\n\n"
88 , (flags & read_only_files) ? "read-only-files ":""
89 , (flags & corrupt_files) ? "corrupt ":""
90 , (flags & incomplete_files) ? "incomplete ":""
91 , (flags & force_recheck) ? "force_recheck ":""
92 , (flags & extended_files) ? "extended_files ":"");
93
94 error_code ec;
95 create_directory("test_torrent_dir", ec);
96 if (ec) fprintf(stdout, "ERROR: creating directory test_torrent_dir: (%d) %s\n"
97 , ec.value(), ec.message().c_str());
98
99 file_storage fs;
100 std::srand(10);
101 int piece_size = 0x4000;
102
103 static std::array<const int, 46> const file_sizes
104 {{ 0, 5, 16 - 5, 16000, 17, 10, 8000, 8000, 1,1,1,1,1,100,1,1,1,1,100,1,1,1,1,1,1
105 ,1,1,1,1,1,1,13,65000,34,75,2,30,400,500,23000,900,43000,400,4300,6, 4 }};
106
107 create_random_files("test_torrent_dir", file_sizes, &fs);
108
109 lt::create_torrent t(fs, piece_size, 0x4000
110 , lt::create_torrent::optimize_alignment);
111
112 // calculate the hash for all pieces
113 set_piece_hashes(t, ".", ec);
114 if (ec) std::printf("ERROR: set_piece_hashes: (%d) %s\n"
115 , ec.value(), ec.message().c_str());
116
117 std::vector<char> buf;
118 bencode(std::back_inserter(buf), t.generate());
119 auto ti = std::make_shared<torrent_info>(buf, ec, from_span);
120
121 std::printf("generated torrent: %s test_torrent_dir\n"
122 , aux::to_hex(ti->info_hash()).c_str());
123
124 // truncate every file in half
125 if (flags & (incomplete_files | extended_files))
126 {
127 for (std::size_t i = 0; i < file_sizes.size(); ++i)
128 {
129 char name[1024];
130 std::snprintf(name, sizeof(name), "test%d", int(i));
131 char dirname[200];
132 std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5);
133 std::string path = combine_path("test_torrent_dir", dirname);
134 path = combine_path(path, name);
135
136 file f(path, open_mode::read_write, ec);
137 if (ec) std::printf("ERROR: opening file \"%s\": (%d) %s\n"
138 , path.c_str(), ec.value(), ec.message().c_str());
139 if (flags & extended_files)
140 {
141 f.set_size(file_sizes[i] + 10, ec);
142 }
143 else
144 {
145 f.set_size(file_sizes[i] / 2, ec);
146 }
147 if (ec) std::printf("ERROR: truncating file \"%s\": (%d) %s\n"
148 , path.c_str(), ec.value(), ec.message().c_str());
149 }
150 }
151
152 // overwrite the files with new random data
153 if (flags & corrupt_files)
154 {
155 std::printf("corrupt file test. overwriting files\n");
156 // increase the size of some files. When they're read only that forces
157 // the checker to open them in write-mode to truncate them
158 static std::array<const int, 46> const file_sizes2
159 {{ 0, 5, 16 - 5, 16001, 30, 10, 8000, 8000, 1,1,1,1,1,100,1,1,1,1,100,1,1,1,1,1,1
160 ,1,1,1,1,1,1,13,65000,34,75,2,30,400,500,23000,900,43000,400,4300,6, 4}};
161 create_random_files("test_torrent_dir", file_sizes2);
162 }
163
164 // make the files read only
165 if (flags & read_only_files)
166 {
167 std::printf("making files read-only\n");
168 for (std::size_t i = 0; i < file_sizes.size(); ++i)
169 {
170 char name[1024];
171 std::snprintf(name, sizeof(name), "test%d", int(i));
172 char dirname[200];
173 std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5);
174
175 std::string path = combine_path("test_torrent_dir", dirname);
176 path = combine_path(path, name);
177 std::printf(" %s\n", path.c_str());
178
179 #ifdef TORRENT_WINDOWS
180 SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_READONLY);
181 #else
182 chmod(path.c_str(), S_IRUSR);
183 #endif
184 }
185 }
186
187 if (flags & force_recheck)
188 {
189 remove_all("test_torrent_dir_tmp", ec);
190 if (ec) std::printf("ERROR: removing \"test_torrent_dir_tmp\": (%d) %s\n"
191 , ec.value(), ec.message().c_str());
192 rename("test_torrent_dir", "test_torrent_dir_tmp", ec);
193 if (ec) std::printf("ERROR: renaming dir \"test_torrent_dir\": (%d) %s\n"
194 , ec.value(), ec.message().c_str());
195 }
196
197 lt::session ses1(settings());
198
199 add_torrent_params p;
200 p.save_path = ".";
201 p.ti = ti;
202 torrent_handle tor1 = ses1.add_torrent(p, ec);
203 TEST_CHECK(!ec);
204
205 if (flags & force_recheck)
206 {
207 // first make sure the session tries to check for the file and can't find
208 // them
209 libtorrent::alert const* a = wait_for_alert(
210 ses1, torrent_checked_alert::alert_type, "checking");
211 TEST_CHECK(a);
212
213 // now, move back the files and force-recheck. make sure we pick up the
214 // files this time
215 remove_all("test_torrent_dir", ec);
216 if (ec) fprintf(stdout, "ERROR: removing \"test_torrent_dir\": (%d) %s\n"
217 , ec.value(), ec.message().c_str());
218 rename("test_torrent_dir_tmp", "test_torrent_dir", ec);
219 if (ec) fprintf(stdout, "ERROR: renaming dir \"test_torrent_dir_tmp\": (%d) %s\n"
220 , ec.value(), ec.message().c_str());
221 tor1.force_recheck();
222 }
223
224 torrent_status st;
225 for (int i = 0; i < 20; ++i)
226 {
227 print_alerts(ses1, "ses1");
228
229 st = tor1.status();
230
231 std::printf("%d %f %s\n", st.state, st.progress_ppm / 10000.0, st.errc.message().c_str());
232
233 if (!is_checking(st.state) || st.errc) break;
234 std::this_thread::sleep_for(lt::milliseconds(500));
235 }
236
237 if (flags & incomplete_files)
238 {
239 TEST_CHECK(!st.is_seeding);
240
241 std::this_thread::sleep_for(lt::milliseconds(500));
242 st = tor1.status();
243 TEST_CHECK(!st.is_seeding);
244 }
245
246 if (flags & corrupt_files)
247 {
248 TEST_CHECK(!st.is_seeding);
249
250 TEST_CHECK(!st.errc);
251 if (st.errc)
252 std::printf("error: %s\n", st.errc.message().c_str());
253 }
254
255 if ((flags & (incomplete_files | corrupt_files)) == 0)
256 {
257 TEST_CHECK(st.is_seeding);
258 if (st.errc)
259 std::printf("error: %s\n", st.errc.message().c_str());
260 }
261
262 // make the files writable again
263 if (flags & read_only_files)
264 {
265 for (std::size_t i = 0; i < file_sizes.size(); ++i)
266 {
267 char name[1024];
268 std::snprintf(name, sizeof(name), "test%d", int(i));
269 char dirname[200];
270 std::snprintf(dirname, sizeof(dirname), "test_dir%d", int(i) / 5);
271
272 std::string path = combine_path("test_torrent_dir", dirname);
273 path = combine_path(path, name);
274 #ifdef TORRENT_WINDOWS
275 SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_NORMAL);
276 #else
277 chmod(path.c_str(), S_IRUSR | S_IWUSR);
278 #endif
279 }
280 }
281
282 remove_all("test_torrent_dir", ec);
283 if (ec) std::printf("ERROR: removing test_torrent_dir: (%d) %s\n"
284 , ec.value(), ec.message().c_str());
285 }
286
287 } // anonymous namespace
288
TORRENT_TEST(checking)289 TORRENT_TEST(checking)
290 {
291 test_checking(0);
292 }
293
TORRENT_TEST(read_only_corrupt)294 TORRENT_TEST(read_only_corrupt)
295 {
296 test_checking(read_only_files | corrupt_files);
297 }
298
TORRENT_TEST(read_only)299 TORRENT_TEST(read_only)
300 {
301 test_checking(read_only_files);
302 }
303
TORRENT_TEST(incomplete)304 TORRENT_TEST(incomplete)
305 {
306 test_checking(incomplete_files);
307 }
308
TORRENT_TEST(extended)309 TORRENT_TEST(extended)
310 {
311 test_checking(extended_files);
312 }
313
TORRENT_TEST(corrupt)314 TORRENT_TEST(corrupt)
315 {
316 test_checking(corrupt_files);
317 }
318
TORRENT_TEST(force_recheck)319 TORRENT_TEST(force_recheck)
320 {
321 test_checking(force_recheck);
322 }
323
TORRENT_TEST(discrete_checking)324 TORRENT_TEST(discrete_checking)
325 {
326 using namespace lt;
327 printf("\n==== TEST CHECKING discrete =====\n\n");
328 error_code ec;
329 create_directory("test_torrent_dir", ec);
330 if (ec) printf("ERROR: creating directory test_torrent_dir: (%d) %s\n", ec.value(), ec.message().c_str());
331
332 int const megabyte = 0x100000;
333 int const piece_size = 2 * megabyte;
334 static std::array<int const, 2> const file_sizes{{ 9 * megabyte, 3 * megabyte }};
335
336 file_storage fs;
337 create_random_files("test_torrent_dir", file_sizes, &fs);
338 TEST_EQUAL(fs.num_files(), 2);
339
340 lt::create_torrent t(fs, piece_size, 1, lt::create_torrent::optimize_alignment);
341 set_piece_hashes(t, ".", ec);
342 if (ec) printf("ERROR: set_piece_hashes: (%d) %s\n", ec.value(), ec.message().c_str());
343
344 std::vector<char> buf;
345 bencode(std::back_inserter(buf), t.generate());
346 auto ti = std::make_shared<torrent_info>(buf, ec, from_span);
347 printf("generated torrent: %s test_torrent_dir\n", aux::to_hex(ti->info_hash().to_string()).c_str());
348
349 // we have two files, but there's a padfile now too
350 TEST_EQUAL(ti->num_files(), 3);
351
352 {
353 session ses1(settings());
354 add_torrent_params p;
355 p.file_priorities.resize(std::size_t(ti->num_files()));
356 p.file_priorities[0] = 1_pri;
357 p.save_path = ".";
358 p.ti = ti;
359 torrent_handle tor1 = ses1.add_torrent(p, ec);
360 // change the priority of a file while checking and make sure it doesn't interrupt the checking.
361 std::vector<download_priority_t> prio(std::size_t(ti->num_files()), 0_pri);
362 prio[2] = 1_pri;
363 tor1.prioritize_files(prio);
364 TEST_CHECK(wait_for_alert(ses1, torrent_checked_alert::alert_type
365 , "torrent checked", pop_alerts::pop_all, seconds(50)));
366 TEST_CHECK(tor1.status({}).is_seeding);
367 }
368 remove_all("test_torrent_dir", ec);
369 if (ec) fprintf(stdout, "ERROR: removing test_torrent_dir: (%d) %s\n", ec.value(), ec.message().c_str());
370 }
371