Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(173)

Side by Side Diff: user_collector.cc

Issue 6297004: crash-reporter: Add diagnostics to help diagnose failures in the wild (Closed) Base URL: http://git.chromium.org/git/crash-reporter.git@master
Patch Set: respond to review Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « user_collector.h ('k') | user_collector_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "crash-reporter/user_collector.h" 5 #include "crash-reporter/user_collector.h"
6 6
7 #include <grp.h> // For struct group. 7 #include <grp.h> // For struct group.
8 #include <pcrecpp.h>
9 #include <pcrecpp.h>
8 #include <pwd.h> // For struct passwd. 10 #include <pwd.h> // For struct passwd.
9 #include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS. 11 #include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS.
10 12
11 #include <string> 13 #include <string>
12 #include <vector> 14 #include <vector>
13 15
14 #include "base/file_util.h" 16 #include "base/file_util.h"
15 #include "base/logging.h" 17 #include "base/logging.h"
16 #include "base/string_util.h" 18 #include "base/string_util.h"
17 #include "crash-reporter/system_logging.h" 19 #include "crash-reporter/system_logging.h"
18 #include "gflags/gflags.h" 20 #include "gflags/gflags.h"
19 21
20 #pragma GCC diagnostic ignored "-Wstrict-aliasing" 22 #pragma GCC diagnostic ignored "-Wstrict-aliasing"
21 DEFINE_bool(core2md_failure_test, false, "Core2md failure test"); 23 DEFINE_bool(core2md_failure, false, "Core2md failure test");
22 DEFINE_bool(directory_failure_test, false, "Spool directory failure test"); 24 DEFINE_bool(directory_failure, false, "Spool directory failure test");
23 DEFINE_string(filter_in, "", 25 DEFINE_string(filter_in, "",
24 "Ignore all crashes but this for testing"); 26 "Ignore all crashes but this for testing");
25 #pragma GCC diagnostic error "-Wstrict-aliasing" 27 #pragma GCC diagnostic error "-Wstrict-aliasing"
26 28
27 static const char kCollectionErrorSignature[] = 29 static const char kCollectionErrorSignature[] =
28 "crash_reporter-user-collection"; 30 "crash_reporter-user-collection";
29 // This procfs file is used to cause kernel core file writing to 31 // This procfs file is used to cause kernel core file writing to
30 // instead pipe the core file into a user space process. See 32 // instead pipe the core file into a user space process. See
31 // core(5) man page. 33 // core(5) man page.
32 static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern"; 34 static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern";
(...skipping 28 matching lines...) Expand all
61 our_path_ = our_path; 63 our_path_ = our_path;
62 initialized_ = true; 64 initialized_ = true;
63 generate_diagnostics_ = generate_diagnostics; 65 generate_diagnostics_ = generate_diagnostics;
64 } 66 }
65 67
66 UserCollector::~UserCollector() { 68 UserCollector::~UserCollector() {
67 } 69 }
68 70
69 std::string UserCollector::GetPattern(bool enabled) const { 71 std::string UserCollector::GetPattern(bool enabled) const {
70 if (enabled) { 72 if (enabled) {
71 return StringPrintf("|%s --signal=%%s --pid=%%p", our_path_.c_str()); 73 // Combine the three crash attributes into one parameter to try to reduce
74 // the size of the invocation line for crash_reporter since the kernel
75 // has a fixed-sized (128B) buffer that it will truncate into. Note that
76 // the kernel does not support quoted arguments in core_pattern.
77 return StringPrintf("|%s --user=%%p:%%s:%%e", our_path_.c_str());
72 } else { 78 } else {
73 return "core"; 79 return "core";
74 } 80 }
75 } 81 }
76 82
77 bool UserCollector::SetUpInternal(bool enabled) { 83 bool UserCollector::SetUpInternal(bool enabled) {
78 CHECK(initialized_); 84 CHECK(initialized_);
79 logger_->LogInfo("%s user crash handling", 85 logger_->LogInfo("%s user crash handling",
80 enabled ? "Enabling" : "Disabling"); 86 enabled ? "Enabling" : "Disabling");
81 if (file_util::WriteFile(FilePath(core_pipe_limit_file_), 87 if (file_util::WriteFile(FilePath(core_pipe_limit_file_),
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
162 return false; 168 return false;
163 } 169 }
164 const char *number = ids[kind].c_str(); 170 const char *number = ids[kind].c_str();
165 char *end_number = NULL; 171 char *end_number = NULL;
166 *id = strtol(number, &end_number, 10); 172 *id = strtol(number, &end_number, 10);
167 if (*end_number != '\0') 173 if (*end_number != '\0')
168 return false; 174 return false;
169 return true; 175 return true;
170 } 176 }
171 177
172 void UserCollector::LogCollectionError(const std::string &error_message) {
173 error_log_.append(error_message.c_str());
174 error_log_.append("\n");
175 logger_->LogError(error_message.c_str());
176 }
177
178 void UserCollector::EnqueueCollectionErrorLog(pid_t pid, 178 void UserCollector::EnqueueCollectionErrorLog(pid_t pid,
179 const std::string &exec) { 179 const std::string &exec) {
180 FilePath crash_path; 180 FilePath crash_path;
181 logger_->LogInfo("Writing conversion problems as separate crash report."); 181 logger_->LogInfo("Writing conversion problems as separate crash report.");
182 if (!GetCreatedCrashDirectoryByEuid(0, &crash_path, NULL)) { 182 if (!GetCreatedCrashDirectoryByEuid(0, &crash_path, NULL)) {
183 logger_->LogError("Could not even get log directory; out of space?"); 183 logger_->LogError("Could not even get log directory; out of space?");
184 return; 184 return;
185 } 185 }
186 std::string dump_basename = FormatDumpBasename(exec, time(NULL), pid); 186 std::string dump_basename = FormatDumpBasename(exec, time(NULL), pid);
187 std::string error_log = logger_->get_accumulator();
188 FilePath diag_log_path = GetCrashPath(crash_path, dump_basename, "diaglog");
189 if (GetLogContents(FilePath(kDefaultLogConfig), kCollectionErrorSignature,
190 diag_log_path)) {
191 // We load the contents of diag_log into memory and append it to
192 // the error log. We cannot just append to files because we need
193 // to always create new files to prevent attack.
194 std::string diag_log_contents;
195 file_util::ReadFileToString(diag_log_path, &diag_log_contents);
196 error_log.append(diag_log_contents);
197 file_util::Delete(diag_log_path, false);
198 }
187 FilePath log_path = GetCrashPath(crash_path, dump_basename, "log"); 199 FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
188 FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta"); 200 FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
189 // We must use WriteNewFile instead of file_util::WriteFile as we do 201 // We must use WriteNewFile instead of file_util::WriteFile as we do
190 // not want to write with root access to a symlink that an attacker 202 // not want to write with root access to a symlink that an attacker
191 // might have created. 203 // might have created.
192 WriteNewFile(log_path, error_log_.data(), error_log_.length()); 204 WriteNewFile(log_path, error_log.data(), error_log.length());
193 AddCrashMetaData("sig", kCollectionErrorSignature); 205 AddCrashMetaData("sig", kCollectionErrorSignature);
194 WriteCrashMetaData(meta_path, exec, log_path.value()); 206 WriteCrashMetaData(meta_path, exec, log_path.value());
195 } 207 }
196 208
197 bool UserCollector::CopyOffProcFiles(pid_t pid, 209 bool UserCollector::CopyOffProcFiles(pid_t pid,
198 const FilePath &container_dir) { 210 const FilePath &container_dir) {
199 if (!file_util::CreateDirectory(container_dir)) { 211 if (!file_util::CreateDirectory(container_dir)) {
200 LogCollectionError(StringPrintf("Could not create %s", 212 logger_->LogError("Could not create %s",
201 container_dir.value().c_str())); 213 container_dir.value().c_str());
202 return false; 214 return false;
203 } 215 }
204 FilePath process_path = GetProcessPath(pid); 216 FilePath process_path = GetProcessPath(pid);
205 if (!file_util::PathExists(process_path)) { 217 if (!file_util::PathExists(process_path)) {
206 LogCollectionError(StringPrintf("Path %s does not exist", 218 logger_->LogError("Path %s does not exist", process_path.value().c_str());
207 process_path.value().c_str()));
208 return false; 219 return false;
209 } 220 }
210 static const char *proc_files[] = { 221 static const char *proc_files[] = {
211 "auxv", 222 "auxv",
212 "cmdline", 223 "cmdline",
213 "environ", 224 "environ",
214 "maps", 225 "maps",
215 "status" 226 "status"
216 }; 227 };
217 for (unsigned i = 0; i < arraysize(proc_files); ++i) { 228 for (unsigned i = 0; i < arraysize(proc_files); ++i) {
218 if (!file_util::CopyFile(process_path.Append(proc_files[i]), 229 if (!file_util::CopyFile(process_path.Append(proc_files[i]),
219 container_dir.Append(proc_files[i]))) { 230 container_dir.Append(proc_files[i]))) {
220 LogCollectionError(StringPrintf("Could not copy %s file", 231 logger_->LogError("Could not copy %s file", proc_files[i]);
221 proc_files[i]));
222 return false; 232 return false;
223 } 233 }
224 } 234 }
225 return true; 235 return true;
226 } 236 }
227 237
228 bool UserCollector::GetCreatedCrashDirectory(pid_t pid, 238 bool UserCollector::GetCreatedCrashDirectory(pid_t pid,
229 FilePath *crash_file_path, 239 FilePath *crash_file_path,
230 bool *out_of_capacity) { 240 bool *out_of_capacity) {
231 FilePath process_path = GetProcessPath(pid); 241 FilePath process_path = GetProcessPath(pid);
232 std::string status; 242 std::string status;
233 if (FLAGS_directory_failure_test) { 243 if (FLAGS_directory_failure) {
234 LogCollectionError("Purposefully failing to create spool directory"); 244 logger_->LogError("Purposefully failing to create spool directory");
235 return false; 245 return false;
236 } 246 }
237 if (!file_util::ReadFileToString(process_path.Append("status"), 247 if (!file_util::ReadFileToString(process_path.Append("status"),
238 &status)) { 248 &status)) {
239 LogCollectionError("Could not read status file"); 249 logger_->LogError("Could not read status file");
250 logger_->LogInfo("Path %s FileExists: %d",
251 process_path.value().c_str(),
252 file_util::DirectoryExists(process_path));
240 return false; 253 return false;
241 } 254 }
242 int process_euid; 255 int process_euid;
243 if (!GetIdFromStatus(kUserId, kIdEffective, status, &process_euid)) { 256 if (!GetIdFromStatus(kUserId, kIdEffective, status, &process_euid)) {
244 LogCollectionError("Could not find euid in status file"); 257 logger_->LogError("Could not find euid in status file");
245 return false; 258 return false;
246 } 259 }
247 if (!GetCreatedCrashDirectoryByEuid(process_euid, 260 if (!GetCreatedCrashDirectoryByEuid(process_euid,
248 crash_file_path, 261 crash_file_path,
249 out_of_capacity)) { 262 out_of_capacity)) {
250 LogCollectionError("Could not create crash directory"); 263 logger_->LogError("Could not create crash directory");
251 return false; 264 return false;
252 } 265 }
253 return true; 266 return true;
254 } 267 }
255 268
256 bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) { 269 bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) {
257 // Copy off all stdin to a core file. 270 // Copy off all stdin to a core file.
258 FilePath stdin_path("/dev/fd/0"); 271 FilePath stdin_path("/dev/fd/0");
259 if (file_util::CopyFile(stdin_path, core_path)) { 272 if (file_util::CopyFile(stdin_path, core_path)) {
260 return true; 273 return true;
261 } 274 }
262 275
263 LogCollectionError("Could not write core file"); 276 logger_->LogError("Could not write core file");
264 // If the file system was full, make sure we remove any remnants. 277 // If the file system was full, make sure we remove any remnants.
265 file_util::Delete(core_path, false); 278 file_util::Delete(core_path, false);
266 return false; 279 return false;
267 } 280 }
268 281
269 bool UserCollector::RunCoreToMinidump(const FilePath &core_path, 282 bool UserCollector::RunCoreToMinidump(const FilePath &core_path,
270 const FilePath &procfs_directory, 283 const FilePath &procfs_directory,
271 const FilePath &minidump_path, 284 const FilePath &minidump_path,
272 const FilePath &temp_directory) { 285 const FilePath &temp_directory) {
273 FilePath output_path = temp_directory.Append("output"); 286 FilePath output_path = temp_directory.Append("output");
274 std::vector<const char *> core2md_arguments; 287 std::vector<const char *> core2md_arguments;
275 core2md_arguments.push_back(kCoreToMinidumpConverterPath); 288 core2md_arguments.push_back(kCoreToMinidumpConverterPath);
276 core2md_arguments.push_back(core_path.value().c_str()); 289 core2md_arguments.push_back(core_path.value().c_str());
277 core2md_arguments.push_back(procfs_directory.value().c_str()); 290 core2md_arguments.push_back(procfs_directory.value().c_str());
278 core2md_arguments.push_back(minidump_path.value().c_str()); 291 core2md_arguments.push_back(minidump_path.value().c_str());
279 292
280 if (FLAGS_core2md_failure_test) { 293 if (FLAGS_core2md_failure) {
281 // To test how core2md errors are propagaged, cause an error 294 // To test how core2md errors are propagaged, cause an error
282 // by forgetting a required argument. 295 // by forgetting a required argument.
283 core2md_arguments.pop_back(); 296 core2md_arguments.pop_back();
284 } 297 }
285 298
286 int errorlevel = ForkExecAndPipe(core2md_arguments, 299 int errorlevel = ForkExecAndPipe(core2md_arguments,
287 output_path.value().c_str()); 300 output_path.value().c_str());
288 301
289 std::string output; 302 std::string output;
290 file_util::ReadFileToString(output_path, &output); 303 file_util::ReadFileToString(output_path, &output);
291 if (errorlevel != 0) { 304 if (errorlevel != 0) {
292 LogCollectionError(StringPrintf("Problem during %s [result=%d]: %s", 305 logger_->LogError("Problem during %s [result=%d]: %s",
293 kCoreToMinidumpConverterPath, 306 kCoreToMinidumpConverterPath,
294 errorlevel, 307 errorlevel,
295 output.c_str())); 308 output.c_str());
296 return false; 309 return false;
297 } 310 }
298 311
299 if (!file_util::PathExists(minidump_path)) { 312 if (!file_util::PathExists(minidump_path)) {
300 LogCollectionError(StringPrintf("Minidump file %s was not created", 313 logger_->LogError("Minidump file %s was not created",
301 minidump_path.value().c_str())); 314 minidump_path.value().c_str());
302 return false; 315 return false;
303 } 316 }
304 return true; 317 return true;
305 } 318 }
306 319
307 bool UserCollector::ConvertCoreToMinidump(pid_t pid, 320 bool UserCollector::ConvertCoreToMinidump(pid_t pid,
308 const FilePath &container_dir, 321 const FilePath &container_dir,
309 const FilePath &core_path, 322 const FilePath &core_path,
310 const FilePath &minidump_path) { 323 const FilePath &minidump_path) {
311 if (!CopyOffProcFiles(pid, container_dir)) { 324 if (!CopyOffProcFiles(pid, container_dir)) {
(...skipping 15 matching lines...) Expand all
327 } 340 }
328 341
329 return conversion_result; 342 return conversion_result;
330 } 343 }
331 344
332 bool UserCollector::ConvertAndEnqueueCrash(int pid, 345 bool UserCollector::ConvertAndEnqueueCrash(int pid,
333 const std::string &exec, 346 const std::string &exec,
334 bool *out_of_capacity) { 347 bool *out_of_capacity) {
335 FilePath crash_path; 348 FilePath crash_path;
336 if (!GetCreatedCrashDirectory(pid, &crash_path, out_of_capacity)) { 349 if (!GetCreatedCrashDirectory(pid, &crash_path, out_of_capacity)) {
337 LogCollectionError("Unable to find/create process-specific crash path"); 350 logger_->LogError("Unable to find/create process-specific crash path");
338 return false; 351 return false;
339 } 352 }
340 353
341 // Directory like /tmp/crash_reporter.1234 which contains the 354 // Directory like /tmp/crash_reporter.1234 which contains the
342 // procfs entries and other temporary files used during conversion. 355 // procfs entries and other temporary files used during conversion.
343 FilePath container_dir = FilePath("/tmp").Append( 356 FilePath container_dir = FilePath("/tmp").Append(
344 StringPrintf("crash_reporter.%d", pid)); 357 StringPrintf("crash_reporter.%d", pid));
358 // Delete a pre-existing directory from crash reporter that may have
359 // been left around for diagnostics from a failed conversion attempt.
360 // If we don't, existing files can cause forking to fail.
361 file_util::Delete(container_dir, true);
345 std::string dump_basename = FormatDumpBasename(exec, time(NULL), pid); 362 std::string dump_basename = FormatDumpBasename(exec, time(NULL), pid);
346 FilePath core_path = GetCrashPath(crash_path, dump_basename, "core"); 363 FilePath core_path = GetCrashPath(crash_path, dump_basename, "core");
347 FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta"); 364 FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
348 FilePath minidump_path = GetCrashPath(crash_path, dump_basename, "dmp"); 365 FilePath minidump_path = GetCrashPath(crash_path, dump_basename, "dmp");
349 FilePath log_path = GetCrashPath(crash_path, dump_basename, "log"); 366 FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
350 367
351 if (GetLogContents(FilePath(kDefaultLogConfig), exec, log_path)) 368 if (GetLogContents(FilePath(kDefaultLogConfig), exec, log_path))
352 AddCrashMetaData("log", log_path.value()); 369 AddCrashMetaData("log", log_path.value());
353 370
354 if (!ConvertCoreToMinidump(pid, container_dir, core_path, 371 if (!ConvertCoreToMinidump(pid, container_dir, core_path,
(...skipping 14 matching lines...) Expand all
369 file_util::Delete(core_path, false); 386 file_util::Delete(core_path, false);
370 } else { 387 } else {
371 logger_->LogInfo("Leaving core file at %s due to developer image", 388 logger_->LogInfo("Leaving core file at %s due to developer image",
372 core_path.value().c_str()); 389 core_path.value().c_str());
373 } 390 }
374 391
375 file_util::Delete(container_dir, true); 392 file_util::Delete(container_dir, true);
376 return true; 393 return true;
377 } 394 }
378 395
379 bool UserCollector::HandleCrash(int signal, int pid, const char *force_exec) { 396 bool UserCollector::ParseCrashAttributes(const std::string &crash_attributes,
397 pid_t *pid, int *signal,
398 std::string *kernel_supplied_name) {
399 pcrecpp::RE re("(\\d+):(\\d+):(.*)");
400 return re.FullMatch(crash_attributes, pid, signal, kernel_supplied_name);
401 }
402
403 bool UserCollector::HandleCrash(const std::string &crash_attributes,
404 const char *force_exec) {
380 CHECK(initialized_); 405 CHECK(initialized_);
406 int pid = 0;
407 int signal = 0;
408 std::string kernel_supplied_name;
409
410 if (!ParseCrashAttributes(crash_attributes, &pid, &signal,
411 &kernel_supplied_name)) {
412 logger_->LogError("Invalid parameter: --user=%s", crash_attributes.c_str());
413 return false;
414 }
415
381 std::string exec; 416 std::string exec;
382 if (force_exec) { 417 if (force_exec) {
383 exec.assign(force_exec); 418 exec.assign(force_exec);
384 } else if (!GetExecutableBaseNameFromPid(pid, &exec)) { 419 } else if (!GetExecutableBaseNameFromPid(pid, &exec)) {
385 // If for some reason we don't have the base name, avoid completely 420 // If we cannot find the exec name, use the kernel supplied name.
386 // failing by indicating an unknown name. 421 // We don't always use the kernel's since it truncates the name to
387 exec = "unknown"; 422 // 16 characters.
423 exec = StringPrintf("supplied_%s", kernel_supplied_name.c_str());
388 } 424 }
389 425
390 // Allow us to test the crash reporting mechanism successfully even if 426 // Allow us to test the crash reporting mechanism successfully even if
391 // other parts of the system crash. 427 // other parts of the system crash.
392 if (!FLAGS_filter_in.empty() && 428 if (!FLAGS_filter_in.empty() &&
393 (FLAGS_filter_in == "none" || 429 (FLAGS_filter_in == "none" ||
394 FLAGS_filter_in != exec)) { 430 FLAGS_filter_in != exec)) {
395 // We use a different format message to make it more obvious in tests 431 // We use a different format message to make it more obvious in tests
396 // which crashes are test generated and which are real. 432 // which crashes are test generated and which are real.
397 logger_->LogWarning("Ignoring crash from %s[%d] while filter_in=%s", 433 logger_->LogWarning("Ignoring crash from %s[%d] while filter_in=%s.",
398 exec.c_str(), pid, FLAGS_filter_in.c_str()); 434 exec.c_str(), pid, FLAGS_filter_in.c_str());
399 return true; 435 return true;
400 } 436 }
401 437
402 bool feedback = is_feedback_allowed_function_(); 438 bool feedback = is_feedback_allowed_function_();
403 const char *handling_string = "handling"; 439 const char *handling_string = "handling";
404 if (!feedback) { 440 if (!feedback) {
405 handling_string = "ignoring - no consent"; 441 handling_string = "ignoring - no consent";
406 } 442 }
407 443
408 // Treat Chrome crashes as if the user opted-out. We stop counting Chrome 444 // Treat Chrome crashes as if the user opted-out. We stop counting Chrome
409 // crashes towards user crashes, so user crashes really mean non-Chrome 445 // crashes towards user crashes, so user crashes really mean non-Chrome
410 // user-space crashes. 446 // user-space crashes.
411 if (exec == "chrome") { 447 if (exec == "chrome") {
412 feedback = false; 448 feedback = false;
413 handling_string = "ignoring - chrome crash"; 449 handling_string = "ignoring - chrome crash";
414 } 450 }
415 451
416 logger_->LogWarning("Received crash notification for %s[%d] sig %d (%s)", 452 logger_->LogWarning("Received crash notification for %s[%d] sig %d (%s)",
417 exec.c_str(), pid, signal, handling_string); 453 exec.c_str(), pid, signal, handling_string);
418 454
419 if (feedback) { 455 if (feedback) {
420 count_crash_function_(); 456 count_crash_function_();
421 457
422 if (generate_diagnostics_) { 458 if (generate_diagnostics_) {
423 bool out_of_capacity = false; 459 bool out_of_capacity = false;
424 if (!ConvertAndEnqueueCrash(pid, exec, &out_of_capacity)) { 460 logger_->set_accumulating(true);
461 bool convert_and_enqueue_result =
462 ConvertAndEnqueueCrash(pid, exec, &out_of_capacity);
463 logger_->set_accumulating(false);
464 if (!convert_and_enqueue_result) {
425 if (!out_of_capacity) 465 if (!out_of_capacity)
426 EnqueueCollectionErrorLog(pid, exec); 466 EnqueueCollectionErrorLog(pid, exec);
427 return false; 467 return false;
428 } 468 }
429 } 469 }
430 } 470 }
431 471
432 return true; 472 return true;
433 } 473 }
OLDNEW
« no previous file with comments | « user_collector.h ('k') | user_collector_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698