1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/system/syslogs_provider.h" 6 7 #include "ash/shell.h" 8 #include "base/bind.h" 9 #include "base/bind_helpers.h" 10 #include "base/command_line.h" 11 #include "base/compiler_specific.h" 12 #include "base/file_util.h" 13 #include "base/files/file_path.h" 14 #include "base/logging.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/memory/singleton.h" 17 #include "base/message_loop/message_loop_proxy.h" 18 #include "base/strings/string_util.h" 19 #include "base/task_runner.h" 20 #include "base/threading/sequenced_worker_pool.h" 21 #include "chrome/browser/memory_details.h" 22 #include "chrome/common/chrome_switches.h" 23 #include "chromeos/network/network_event_log.h" 24 #include "components/feedback/feedback_util.h" 25 #include "content/public/browser/browser_thread.h" 26 #include "dbus/dbus_statistics.h" 27 28 using content::BrowserThread; 29 30 namespace chromeos { 31 namespace system { 32 33 const size_t kFeedbackMaxLength = 4 * 1024; 34 const size_t kFeedbackMaxLineCount = 40; 35 36 namespace { 37 38 const char kSysLogsScript[] = 39 "/usr/share/userfeedback/scripts/sysinfo_script_runner"; 40 const char kBzip2Command[] = 41 "/bin/bzip2"; 42 const char kMultilineQuote[] = "\"\"\""; 43 const char kNewLineChars[] = "\r\n"; 44 const char kInvalidLogEntry[] = "<invalid characters in log entry>"; 45 const char kEmptyLogEntry[] = "<no value>"; 46 47 const char kContextFeedback[] = "feedback"; 48 const char kContextSysInfo[] = "sysinfo"; 49 const char kContextNetwork[] = "network"; 50 51 // Reads a key from the input string erasing the read values + delimiters read 52 // from the initial string 53 std::string ReadKey(std::string* data) { 54 std::string key; 55 size_t equal_sign = data->find("="); 56 if (equal_sign == std::string::npos) 57 return key; 58 key = data->substr(0, equal_sign); 59 data->erase(0, equal_sign); 60 if (data->empty()) 61 return key; 62 // erase the equal to sign also 63 data->erase(0, 1); 64 return key; 65 } 66 67 // Reads a value from the input string; erasing the read values from 68 // the initial string; detects if the value is multiline and reads 69 // accordingly 70 std::string ReadValue(std::string* data) { 71 // Trim the leading spaces and tabs. In order to use a multi-line 72 // value, you have to place the multi-line quote on the same line as 73 // the equal sign. 74 // 75 // Why not use TrimWhitespace? Consider the following input: 76 // 77 // KEY1= 78 // KEY2=VALUE 79 // 80 // If we use TrimWhitespace, we will incorrectly trim the new line 81 // and assume that KEY1's value is "KEY2=VALUE" rather than empty. 82 base::TrimString(*data, " \t", data); 83 84 // If multiline value 85 if (StartsWithASCII(*data, std::string(kMultilineQuote), false)) { 86 data->erase(0, strlen(kMultilineQuote)); 87 size_t next_multi = data->find(kMultilineQuote); 88 if (next_multi == std::string::npos) { 89 // Error condition, clear data to stop further processing 90 data->erase(); 91 return std::string(); 92 } 93 std::string value = data->substr(0, next_multi); 94 data->erase(0, next_multi + 3); 95 return value; 96 } else { 97 // single line value 98 size_t endl_pos = data->find_first_of(kNewLineChars); 99 // if we don't find a new line, we just return the rest of the data 100 std::string value = data->substr(0, endl_pos); 101 data->erase(0, endl_pos); 102 return value; 103 } 104 } 105 106 // Returns a map of system log keys and values. 107 // 108 // Parameters: 109 // temp_filename: This is an out parameter that holds the name of a file in 110 // Reads a value from the input string; erasing the read values from 111 // the initial string; detects if the value is multiline and reads 112 // accordingly 113 // /tmp that contains the system logs in a KEY=VALUE format. 114 // If this parameter is NULL, system logs are not retained on 115 // the filesystem after this call completes. 116 // context: This is an in parameter specifying what context should be 117 // passed to the syslog collection script; currently valid 118 // values are "sysinfo" or "feedback"; in case of an invalid 119 // value, the script will currently default to "sysinfo" 120 121 LogDictionaryType* GetSystemLogs(base::FilePath* zip_file_name, 122 const std::string& context) { 123 // Create the temp file, logs will go here 124 base::FilePath temp_filename; 125 126 if (!base::CreateTemporaryFile(&temp_filename)) 127 return NULL; 128 129 std::string cmd = std::string(kSysLogsScript) + " " + context + " >> " + 130 temp_filename.value(); 131 132 // Ignore the return value - if the script execution didn't work 133 // stderr won't go into the output file anyway. 134 if (::system(cmd.c_str()) == -1) 135 LOG(WARNING) << "Command " << cmd << " failed to run"; 136 137 // Compress the logs file if requested. 138 if (zip_file_name) { 139 cmd = std::string(kBzip2Command) + " -c " + temp_filename.value() + " > " + 140 zip_file_name->value(); 141 if (::system(cmd.c_str()) == -1) 142 LOG(WARNING) << "Command " << cmd << " failed to run"; 143 } 144 // Read logs from the temp file 145 std::string data; 146 bool read_success = base::ReadFileToString(temp_filename, &data); 147 // if we were using an internal temp file, the user does not need the 148 // logs to stay past the ReadFile call - delete the file 149 base::DeleteFile(temp_filename, false); 150 151 if (!read_success) 152 return NULL; 153 154 // Parse the return data into a dictionary 155 LogDictionaryType* logs = new LogDictionaryType(); 156 while (data.length() > 0) { 157 std::string key = ReadKey(&data); 158 base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key); 159 if (!key.empty()) { 160 std::string value = ReadValue(&data); 161 if (base::IsStringUTF8(value)) { 162 base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value); 163 if (value.empty()) 164 (*logs)[key] = kEmptyLogEntry; 165 else 166 (*logs)[key] = value; 167 } else { 168 LOG(WARNING) << "Invalid characters in system log entry: " << key; 169 (*logs)[key] = kInvalidLogEntry; 170 } 171 } else { 172 // no more keys, we're done 173 break; 174 } 175 } 176 177 return logs; 178 } 179 180 } // namespace 181 182 class SyslogsProviderImpl : public SyslogsProvider { 183 public: 184 // SyslogsProvider implementation: 185 virtual base::CancelableTaskTracker::TaskId RequestSyslogs( 186 bool compress_logs, 187 SyslogsContext context, 188 const ReadCompleteCallback& callback, 189 base::CancelableTaskTracker* tracker) OVERRIDE; 190 191 static SyslogsProviderImpl* GetInstance(); 192 193 private: 194 friend struct DefaultSingletonTraits<SyslogsProviderImpl>; 195 196 // Reads system logs, compresses content if requested. 197 // Called from blocking pool thread. 198 void ReadSyslogs( 199 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, 200 bool compress_logs, 201 SyslogsContext context, 202 const ReadCompleteCallback& callback); 203 204 // Loads compressed logs and writes into |zip_content|. 205 void LoadCompressedLogs(const base::FilePath& zip_file, 206 std::string* zip_content); 207 208 SyslogsProviderImpl(); 209 210 // Gets syslogs context string from the enum value. 211 const char* GetSyslogsContextString(SyslogsContext context); 212 213 // If not canceled, run callback on originating thread (the thread on which 214 // ReadSyslogs was run). 215 static void RunCallbackIfNotCanceled( 216 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, 217 base::TaskRunner* origin_runner, 218 const ReadCompleteCallback& callback, 219 LogDictionaryType* logs, 220 std::string* zip_content); 221 222 DISALLOW_COPY_AND_ASSIGN(SyslogsProviderImpl); 223 }; 224 225 SyslogsProviderImpl::SyslogsProviderImpl() { 226 } 227 228 base::CancelableTaskTracker::TaskId SyslogsProviderImpl::RequestSyslogs( 229 bool compress_logs, 230 SyslogsContext context, 231 const ReadCompleteCallback& callback, 232 base::CancelableTaskTracker* tracker) { 233 base::CancelableTaskTracker::IsCanceledCallback is_canceled; 234 base::CancelableTaskTracker::TaskId id = 235 tracker->NewTrackedTaskId(&is_canceled); 236 237 ReadCompleteCallback callback_runner = 238 base::Bind(&SyslogsProviderImpl::RunCallbackIfNotCanceled, 239 is_canceled, base::MessageLoopProxy::current(), callback); 240 241 // Schedule a task which will run the callback later when complete. 242 BrowserThread::PostBlockingPoolTask( 243 FROM_HERE, 244 base::Bind(&SyslogsProviderImpl::ReadSyslogs, base::Unretained(this), 245 is_canceled, compress_logs, context, callback_runner)); 246 return id; 247 } 248 249 // Derived class from memoryDetails converts the results into a single string 250 // and adds a "mem_usage" entry to the logs, then forwards the result. 251 // Format of entry is (one process per line, reverse-sorted by size): 252 // Tab [Title1|Title2]: 50 MB 253 // Browser: 30 MB 254 // Tab [Title]: 20 MB 255 // Extension [Title]: 10 MB 256 // ... 257 class SyslogsMemoryHandler : public MemoryDetails { 258 public: 259 typedef SyslogsProvider::ReadCompleteCallback ReadCompleteCallback; 260 261 // |logs| is modified (see comment above) and passed to |request|. 262 // |zip_content| is passed to |request|. 263 SyslogsMemoryHandler(const ReadCompleteCallback& callback, 264 LogDictionaryType* logs, 265 std::string* zip_content); 266 267 virtual void OnDetailsAvailable() OVERRIDE; 268 269 private: 270 virtual ~SyslogsMemoryHandler(); 271 272 ReadCompleteCallback callback_; 273 274 LogDictionaryType* logs_; 275 std::string* zip_content_; 276 277 DISALLOW_COPY_AND_ASSIGN(SyslogsMemoryHandler); 278 }; 279 280 SyslogsMemoryHandler::SyslogsMemoryHandler( 281 const ReadCompleteCallback& callback, 282 LogDictionaryType* logs, 283 std::string* zip_content) 284 : callback_(callback), 285 logs_(logs), 286 zip_content_(zip_content) { 287 DCHECK(!callback_.is_null()); 288 } 289 290 void SyslogsMemoryHandler::OnDetailsAvailable() { 291 (*logs_)["mem_usage"] = ToLogString(); 292 callback_.Run(logs_, zip_content_); 293 } 294 295 SyslogsMemoryHandler::~SyslogsMemoryHandler() {} 296 297 // Called from blocking pool thread. 298 void SyslogsProviderImpl::ReadSyslogs( 299 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, 300 bool compress_logs, 301 SyslogsContext context, 302 const ReadCompleteCallback& callback) { 303 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 304 305 if (is_canceled.Run()) 306 return; 307 308 // Create temp file. 309 base::FilePath zip_file; 310 if (compress_logs && !base::CreateTemporaryFile(&zip_file)) { 311 LOG(ERROR) << "Cannot create temp file"; 312 compress_logs = false; 313 } 314 315 LogDictionaryType* logs = NULL; 316 logs = GetSystemLogs( 317 compress_logs ? &zip_file : NULL, 318 GetSyslogsContextString(context)); 319 320 std::string* zip_content = NULL; 321 if (compress_logs) { 322 // Load compressed logs. 323 zip_content = new std::string(); 324 LoadCompressedLogs(zip_file, zip_content); 325 base::DeleteFile(zip_file, false); 326 } 327 328 // Include dbus statistics summary 329 (*logs)["dbus"] = dbus::statistics::GetAsString( 330 dbus::statistics::SHOW_INTERFACE, 331 dbus::statistics::FORMAT_ALL); 332 333 // Include recent network log events 334 (*logs)["network_event_log"] = network_event_log::GetAsString( 335 network_event_log::OLDEST_FIRST, 336 "time,file,desc", 337 network_event_log::kDefaultLogLevel, 338 system::kFeedbackMaxLineCount); 339 340 // SyslogsMemoryHandler will clean itself up. 341 // SyslogsMemoryHandler::OnDetailsAvailable() will modify |logs| and call 342 // request->ForwardResult(logs, zip_content). 343 scoped_refptr<SyslogsMemoryHandler> 344 handler(new SyslogsMemoryHandler(callback, logs, zip_content)); 345 // TODO(jamescook): Maybe we don't need to update histograms here? 346 handler->StartFetch(MemoryDetails::UPDATE_USER_METRICS); 347 } 348 349 void SyslogsProviderImpl::LoadCompressedLogs(const base::FilePath& zip_file, 350 std::string* zip_content) { 351 DCHECK(zip_content); 352 if (!base::ReadFileToString(zip_file, zip_content)) { 353 LOG(ERROR) << "Cannot read compressed logs file from " << 354 zip_file.value().c_str(); 355 } 356 } 357 358 const char* SyslogsProviderImpl::GetSyslogsContextString( 359 SyslogsContext context) { 360 switch (context) { 361 case(SYSLOGS_FEEDBACK): 362 return kContextFeedback; 363 case(SYSLOGS_SYSINFO): 364 return kContextSysInfo; 365 case(SYSLOGS_NETWORK): 366 return kContextNetwork; 367 case(SYSLOGS_DEFAULT): 368 return kContextSysInfo; 369 default: 370 NOTREACHED(); 371 return ""; 372 } 373 } 374 375 // static 376 void SyslogsProviderImpl::RunCallbackIfNotCanceled( 377 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, 378 base::TaskRunner* origin_runner, 379 const ReadCompleteCallback& callback, 380 LogDictionaryType* logs, 381 std::string* zip_content) { 382 DCHECK(!is_canceled.is_null() && !callback.is_null()); 383 384 if (is_canceled.Run()) { 385 delete logs; 386 delete zip_content; 387 return; 388 } 389 390 // TODO(achuith (at) chromium.org): Maybe always run callback asynchronously? 391 if (origin_runner->RunsTasksOnCurrentThread()) { 392 callback.Run(logs, zip_content); 393 } else { 394 origin_runner->PostTask(FROM_HERE, base::Bind(callback, logs, zip_content)); 395 } 396 } 397 398 SyslogsProviderImpl* SyslogsProviderImpl::GetInstance() { 399 return Singleton<SyslogsProviderImpl, 400 DefaultSingletonTraits<SyslogsProviderImpl> >::get(); 401 } 402 403 SyslogsProvider* SyslogsProvider::GetInstance() { 404 return SyslogsProviderImpl::GetInstance(); 405 } 406 407 } // namespace system 408 } // namespace chromeos 409