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