1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 // This command-line program generates the set of files needed for the crash-
11 // cache unit tests (DiskCacheTest,CacheBackend_Recover*). This program only
12 // works properly on debug mode, because the crash functionality is not compiled
13 // on release builds of the cache.
14
15 #include <string>
16
17 #include "base/at_exit.h"
18 #include "base/check.h"
19 #include "base/command_line.h"
20 #include "base/files/file_util.h"
21 #include "base/message_loop/message_pump_type.h"
22 #include "base/path_service.h"
23 #include "base/process/kill.h"
24 #include "base/process/launch.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/string_util.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/task/single_thread_task_executor.h"
29 #include "base/threading/thread.h"
30 #include "base/time/time.h"
31 #include "net/base/net_errors.h"
32 #include "net/base/net_export.h"
33 #include "net/base/test_completion_callback.h"
34 #include "net/disk_cache/backend_cleanup_tracker.h"
35 #include "net/disk_cache/blockfile/backend_impl.h"
36 #include "net/disk_cache/blockfile/rankings.h"
37 #include "net/disk_cache/disk_cache.h"
38 #include "net/disk_cache/disk_cache_test_util.h"
39
40 using base::Time;
41
42 enum Errors {
43 GENERIC = -1,
44 ALL_GOOD = 0,
45 INVALID_ARGUMENT = 1,
46 CRASH_OVERWRITE,
47 NOT_REACHED
48 };
49
50 using disk_cache::RankCrashes;
51
52 // Starts a new process, to generate the files.
RunSlave(RankCrashes action)53 int RunSlave(RankCrashes action) {
54 base::FilePath exe;
55 base::PathService::Get(base::FILE_EXE, &exe);
56
57 base::CommandLine cmdline(exe);
58 cmdline.AppendArg(base::NumberToString(action));
59
60 base::Process process = base::LaunchProcess(cmdline, base::LaunchOptions());
61 if (!process.IsValid()) {
62 printf("Unable to run test %d\n", action);
63 return GENERIC;
64 }
65
66 int exit_code;
67
68 if (!process.WaitForExit(&exit_code)) {
69 printf("Unable to get return code, test %d\n", action);
70 return GENERIC;
71 }
72 if (ALL_GOOD != exit_code)
73 printf("Test %d failed, code %d\n", action, exit_code);
74
75 return exit_code;
76 }
77
78 // Main loop for the master process.
MasterCode()79 int MasterCode() {
80 for (int i = disk_cache::NO_CRASH + 1; i < disk_cache::MAX_CRASH; i++) {
81 int ret = RunSlave(static_cast<RankCrashes>(i));
82 if (ALL_GOOD != ret)
83 return ret;
84 }
85
86 return ALL_GOOD;
87 }
88
89 // -----------------------------------------------------------------------
90
91 namespace disk_cache {
92 NET_EXPORT_PRIVATE extern RankCrashes g_rankings_crash;
93 }
94
95 const char kCrashEntryName[] = "the first key";
96
97 // Creates the destinaton folder for this run, and returns it on full_path.
CreateTargetFolder(const base::FilePath & path,RankCrashes action,base::FilePath * full_path)98 bool CreateTargetFolder(const base::FilePath& path, RankCrashes action,
99 base::FilePath* full_path) {
100 const char* const folders[] = {
101 "",
102 "insert_empty1",
103 "insert_empty2",
104 "insert_empty3",
105 "insert_one1",
106 "insert_one2",
107 "insert_one3",
108 "insert_load1",
109 "insert_load2",
110 "remove_one1",
111 "remove_one2",
112 "remove_one3",
113 "remove_one4",
114 "remove_head1",
115 "remove_head2",
116 "remove_head3",
117 "remove_head4",
118 "remove_tail1",
119 "remove_tail2",
120 "remove_tail3",
121 "remove_load1",
122 "remove_load2",
123 "remove_load3"
124 };
125 static_assert(std::size(folders) == disk_cache::MAX_CRASH, "sync folders");
126 DCHECK(action > disk_cache::NO_CRASH && action < disk_cache::MAX_CRASH);
127
128 *full_path = path.AppendASCII(folders[action]);
129
130 if (base::PathExists(*full_path))
131 return false;
132
133 return base::CreateDirectory(*full_path);
134 }
135
136 // Makes sure that any pending task is processed.
FlushQueue(disk_cache::Backend * cache)137 void FlushQueue(disk_cache::Backend* cache) {
138 net::TestCompletionCallback cb;
139 int rv =
140 reinterpret_cast<disk_cache::BackendImpl*>(cache)->FlushQueueForTest(
141 cb.callback());
142 cb.GetResult(rv); // Ignore the result;
143 }
144
CreateCache(const base::FilePath & path,base::Thread * thread,disk_cache::Backend ** cache,net::TestCompletionCallback * cb)145 bool CreateCache(const base::FilePath& path,
146 base::Thread* thread,
147 disk_cache::Backend** cache,
148 net::TestCompletionCallback* cb) {
149 int size = 1024 * 1024;
150 disk_cache::BackendImpl* backend = new disk_cache::BackendImpl(
151 path, /* cleanup_tracker = */ nullptr, thread->task_runner().get(),
152 net::DISK_CACHE, /* net_log = */ nullptr);
153 backend->SetMaxSize(size);
154 backend->SetFlags(disk_cache::kNoRandom);
155 backend->Init(cb->callback());
156 *cache = backend;
157 return (cb->WaitForResult() == net::OK && !(*cache)->GetEntryCount());
158 }
159
160 // Generates the files for an empty and one item cache.
SimpleInsert(const base::FilePath & path,RankCrashes action,base::Thread * cache_thread)161 int SimpleInsert(const base::FilePath& path, RankCrashes action,
162 base::Thread* cache_thread) {
163 net::TestCompletionCallback cb;
164 disk_cache::Backend* cache;
165 if (!CreateCache(path, cache_thread, &cache, &cb))
166 return GENERIC;
167
168 const char* test_name = "some other key";
169
170 if (action <= disk_cache::INSERT_EMPTY_3) {
171 test_name = kCrashEntryName;
172 disk_cache::g_rankings_crash = action;
173 }
174
175 TestEntryResultCompletionCallback cb_create;
176 disk_cache::EntryResult result = cb_create.GetResult(
177 cache->CreateEntry(test_name, net::HIGHEST, cb_create.callback()));
178 if (result.net_error() != net::OK)
179 return GENERIC;
180
181 result.ReleaseEntry()->Close();
182 FlushQueue(cache);
183
184 DCHECK(action <= disk_cache::INSERT_ONE_3);
185 disk_cache::g_rankings_crash = action;
186 test_name = kCrashEntryName;
187
188 result = cb_create.GetResult(
189 cache->CreateEntry(test_name, net::HIGHEST, cb_create.callback()));
190 if (result.net_error() != net::OK)
191 return GENERIC;
192
193 return NOT_REACHED;
194 }
195
196 // Generates the files for a one item cache, and removing the head.
SimpleRemove(const base::FilePath & path,RankCrashes action,base::Thread * cache_thread)197 int SimpleRemove(const base::FilePath& path, RankCrashes action,
198 base::Thread* cache_thread) {
199 DCHECK(action >= disk_cache::REMOVE_ONE_1);
200 DCHECK(action <= disk_cache::REMOVE_TAIL_3);
201
202 net::TestCompletionCallback cb;
203 disk_cache::Backend* cache;
204 if (!CreateCache(path, cache_thread, &cache, &cb))
205 return GENERIC;
206
207 TestEntryResultCompletionCallback cb_create;
208 disk_cache::EntryResult result = cb_create.GetResult(
209 cache->CreateEntry(kCrashEntryName, net::HIGHEST, cb_create.callback()));
210 if (result.net_error() != net::OK)
211 return GENERIC;
212
213 result.ReleaseEntry()->Close();
214 FlushQueue(cache);
215
216 if (action >= disk_cache::REMOVE_TAIL_1) {
217 result = cb_create.GetResult(cache->CreateEntry(
218 "some other key", net::HIGHEST, cb_create.callback()));
219 if (result.net_error() != net::OK)
220 return GENERIC;
221
222 result.ReleaseEntry()->Close();
223 FlushQueue(cache);
224 }
225
226 result = cb_create.GetResult(
227 cache->OpenEntry(kCrashEntryName, net::HIGHEST, cb_create.callback()));
228 if (result.net_error() != net::OK)
229 return GENERIC;
230
231 disk_cache::g_rankings_crash = action;
232 disk_cache::Entry* entry = result.ReleaseEntry();
233 entry->Doom();
234 entry->Close();
235 FlushQueue(cache);
236
237 return NOT_REACHED;
238 }
239
HeadRemove(const base::FilePath & path,RankCrashes action,base::Thread * cache_thread)240 int HeadRemove(const base::FilePath& path, RankCrashes action,
241 base::Thread* cache_thread) {
242 DCHECK(action >= disk_cache::REMOVE_HEAD_1);
243 DCHECK(action <= disk_cache::REMOVE_HEAD_4);
244
245 net::TestCompletionCallback cb;
246 disk_cache::Backend* cache;
247 if (!CreateCache(path, cache_thread, &cache, &cb))
248 return GENERIC;
249
250 TestEntryResultCompletionCallback cb_create;
251 disk_cache::EntryResult result = cb_create.GetResult(
252 cache->CreateEntry("some other key", net::HIGHEST, cb_create.callback()));
253 if (result.net_error() != net::OK)
254 return GENERIC;
255
256 result.ReleaseEntry()->Close();
257 FlushQueue(cache);
258 result = cb_create.GetResult(
259 cache->CreateEntry(kCrashEntryName, net::HIGHEST, cb_create.callback()));
260 if (result.net_error() != net::OK)
261 return GENERIC;
262
263 result.ReleaseEntry()->Close();
264 FlushQueue(cache);
265
266 result = cb_create.GetResult(
267 cache->OpenEntry(kCrashEntryName, net::HIGHEST, cb_create.callback()));
268 if (result.net_error() != net::OK)
269 return GENERIC;
270
271 disk_cache::g_rankings_crash = action;
272 disk_cache::Entry* entry = result.ReleaseEntry();
273 entry->Doom();
274 entry->Close();
275 FlushQueue(cache);
276
277 return NOT_REACHED;
278 }
279
280 // Generates the files for insertion and removals on heavy loaded caches.
LoadOperations(const base::FilePath & path,RankCrashes action,base::Thread * cache_thread)281 int LoadOperations(const base::FilePath& path, RankCrashes action,
282 base::Thread* cache_thread) {
283 DCHECK(action >= disk_cache::INSERT_LOAD_1);
284
285 // Work with a tiny index table (16 entries).
286 disk_cache::BackendImpl* cache = new disk_cache::BackendImpl(
287 path, 0xf, /*cleanup_tracker=*/nullptr, cache_thread->task_runner().get(),
288 net::DISK_CACHE, nullptr);
289 if (!cache->SetMaxSize(0x100000))
290 return GENERIC;
291
292 // No experiments and use a simple LRU.
293 cache->SetFlags(disk_cache::kNoRandom);
294 net::TestCompletionCallback cb;
295 cache->Init(cb.callback());
296 if (cb.WaitForResult() != net::OK || cache->GetEntryCount())
297 return GENERIC;
298
299 int seed = static_cast<int>(Time::Now().ToInternalValue());
300 srand(seed);
301
302 TestEntryResultCompletionCallback cb_create;
303 for (int i = 0; i < 100; i++) {
304 std::string key = GenerateKey(true);
305 disk_cache::EntryResult result = cb_create.GetResult(
306 cache->CreateEntry(key, net::HIGHEST, cb_create.callback()));
307 if (result.net_error() != net::OK)
308 return GENERIC;
309 result.ReleaseEntry()->Close();
310 FlushQueue(cache);
311 if (50 == i && action >= disk_cache::REMOVE_LOAD_1) {
312 result = cb_create.GetResult(cache->CreateEntry(
313 kCrashEntryName, net::HIGHEST, cb_create.callback()));
314 if (result.net_error() != net::OK)
315 return GENERIC;
316 result.ReleaseEntry()->Close();
317 FlushQueue(cache);
318 }
319 }
320
321 if (action <= disk_cache::INSERT_LOAD_2) {
322 disk_cache::g_rankings_crash = action;
323
324 disk_cache::EntryResult result = cb_create.GetResult(cache->CreateEntry(
325 kCrashEntryName, net::HIGHEST, cb_create.callback()));
326 if (result.net_error() != net::OK)
327 return GENERIC;
328 result.ReleaseEntry(); // leaks.
329 }
330
331 disk_cache::EntryResult result = cb_create.GetResult(
332 cache->OpenEntry(kCrashEntryName, net::HIGHEST, cb_create.callback()));
333 if (result.net_error() != net::OK)
334 return GENERIC;
335
336 disk_cache::g_rankings_crash = action;
337
338 disk_cache::Entry* entry = result.ReleaseEntry();
339 entry->Doom();
340 entry->Close();
341 FlushQueue(cache);
342
343 return NOT_REACHED;
344 }
345
346 // Main function on the child process.
SlaveCode(const base::FilePath & path,RankCrashes action)347 int SlaveCode(const base::FilePath& path, RankCrashes action) {
348 base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
349
350 base::FilePath full_path;
351 if (!CreateTargetFolder(path, action, &full_path)) {
352 printf("Destination folder found, please remove it.\n");
353 return CRASH_OVERWRITE;
354 }
355
356 base::Thread cache_thread("CacheThread");
357 if (!cache_thread.StartWithOptions(
358 base::Thread::Options(base::MessagePumpType::IO, 0)))
359 return GENERIC;
360
361 if (action <= disk_cache::INSERT_ONE_3)
362 return SimpleInsert(full_path, action, &cache_thread);
363
364 if (action <= disk_cache::INSERT_LOAD_2)
365 return LoadOperations(full_path, action, &cache_thread);
366
367 if (action <= disk_cache::REMOVE_ONE_4)
368 return SimpleRemove(full_path, action, &cache_thread);
369
370 if (action <= disk_cache::REMOVE_HEAD_4)
371 return HeadRemove(full_path, action, &cache_thread);
372
373 if (action <= disk_cache::REMOVE_TAIL_3)
374 return SimpleRemove(full_path, action, &cache_thread);
375
376 if (action <= disk_cache::REMOVE_LOAD_3)
377 return LoadOperations(full_path, action, &cache_thread);
378
379 return NOT_REACHED;
380 }
381
382 // -----------------------------------------------------------------------
383
main(int argc,const char * argv[])384 int main(int argc, const char* argv[]) {
385 // Setup an AtExitManager so Singleton objects will be destructed.
386 base::AtExitManager at_exit_manager;
387
388 if (argc < 2)
389 return MasterCode();
390
391 char* end;
392 RankCrashes action = static_cast<RankCrashes>(strtol(argv[1], &end, 0));
393 if (action <= disk_cache::NO_CRASH || action >= disk_cache::MAX_CRASH) {
394 printf("Invalid action\n");
395 return INVALID_ARGUMENT;
396 }
397
398 base::FilePath path;
399 base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &path);
400 path = path.AppendASCII("net");
401 path = path.AppendASCII("data");
402 path = path.AppendASCII("cache_tests");
403 path = path.AppendASCII("new_crashes");
404
405 return SlaveCode(path, action);
406 }
407