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/feedback/feedback_util.h" 22 #include "chrome/browser/memory_details.h" 23 #include "chrome/common/chrome_switches.h" 24 #include "chromeos/network/network_event_log.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 size_t equal_sign = data->find("="); 55 if (equal_sign == std::string::npos) 56 return std::string(""); 57 std::string key = data->substr(0, equal_sign); 58 data->erase(0, equal_sign); 59 if (data->size() > 0) { 60 // erase the equal to sign also 61 data->erase(0,1); 62 return key; 63 } 64 return std::string(); 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 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 { // single line value 97 size_t endl_pos = data->find_first_of(kNewLineChars); 98 // if we don't find a new line, we just return the rest of the data 99 std::string value = data->substr(0, endl_pos); 100 data->erase(0, endl_pos); 101 return value; 102 } 103 } 104 105 // Returns a map of system log keys and values. 106 // 107 // Parameters: 108 // temp_filename: This is an out parameter that holds the name of a file in 109 // Reads a value from the input string; erasing the read values from 110 // the initial string; detects if the value is multiline and reads 111 // accordingly 112 // /tmp that contains the system logs in a KEY=VALUE format. 113 // If this parameter is NULL, system logs are not retained on 114 // the filesystem after this call completes. 115 // context: This is an in parameter specifying what context should be 116 // passed to the syslog collection script; currently valid 117 // values are "sysinfo" or "feedback"; in case of an invalid 118 // value, the script will currently default to "sysinfo" 119 120 LogDictionaryType* GetSystemLogs(base::FilePath* zip_file_name, 121 const std::string& context) { 122 // Create the temp file, logs will go here 123 base::FilePath temp_filename; 124 125 if (!file_util::CreateTemporaryFile(&temp_filename)) 126 return NULL; 127 128 std::string cmd = std::string(kSysLogsScript) + " " + context + " >> " + 129 temp_filename.value(); 130 131 // Ignore the return value - if the script execution didn't work 132 // stderr won't go into the output file anyway. 133 if (::system(cmd.c_str()) == -1) 134 LOG(WARNING) << "Command " << cmd << " failed to run"; 135 136 // Compress the logs file if requested. 137 if (zip_file_name) { 138 cmd = std::string(kBzip2Command) + " -c " + temp_filename.value() + " > " + 139 zip_file_name->value(); 140 if (::system(cmd.c_str()) == -1) 141 LOG(WARNING) << "Command " << cmd << " failed to run"; 142 } 143 // Read logs from the temp file 144 std::string data; 145 bool read_success = file_util::ReadFileToString(temp_filename, 146 &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 TrimWhitespaceASCII(key, TRIM_ALL, &key); 159 if (!key.empty()) { 160 std::string value = ReadValue(&data); 161 if (IsStringUTF8(value)) { 162 TrimWhitespaceASCII(value, 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 CancelableTaskTracker::TaskId RequestSyslogs( 186 bool compress_logs, 187 SyslogsContext context, 188 const ReadCompleteCallback& callback, 189 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 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 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 CancelableTaskTracker::TaskId SyslogsProviderImpl::RequestSyslogs( 229 bool compress_logs, 230 SyslogsContext context, 231 const ReadCompleteCallback& callback, 232 CancelableTaskTracker* tracker) { 233 CancelableTaskTracker::IsCanceledCallback is_canceled; 234 CancelableTaskTracker::TaskId id = tracker->NewTrackedTaskId(&is_canceled); 235 236 ReadCompleteCallback callback_runner = 237 base::Bind(&SyslogsProviderImpl::RunCallbackIfNotCanceled, 238 is_canceled, base::MessageLoopProxy::current(), callback); 239 240 // Schedule a task which will run the callback later when complete. 241 BrowserThread::PostBlockingPoolTask( 242 FROM_HERE, 243 base::Bind(&SyslogsProviderImpl::ReadSyslogs, base::Unretained(this), 244 is_canceled, compress_logs, context, callback_runner)); 245 return id; 246 } 247 248 // Derived class from memoryDetails converts the results into a single string 249 // and adds a "mem_usage" entry to the logs, then forwards the result. 250 // Format of entry is (one process per line, reverse-sorted by size): 251 // Tab [Title1|Title2]: 50 MB 252 // Browser: 30 MB 253 // Tab [Title]: 20 MB 254 // Extension [Title]: 10 MB 255 // ... 256 class SyslogsMemoryHandler : public MemoryDetails { 257 public: 258 typedef SyslogsProvider::ReadCompleteCallback ReadCompleteCallback; 259 260 // |logs| is modified (see comment above) and passed to |request|. 261 // |zip_content| is passed to |request|. 262 SyslogsMemoryHandler(const ReadCompleteCallback& callback, 263 LogDictionaryType* logs, 264 std::string* zip_content); 265 266 virtual void OnDetailsAvailable() OVERRIDE; 267 268 private: 269 virtual ~SyslogsMemoryHandler(); 270 271 ReadCompleteCallback callback_; 272 273 LogDictionaryType* logs_; 274 std::string* zip_content_; 275 276 DISALLOW_COPY_AND_ASSIGN(SyslogsMemoryHandler); 277 }; 278 279 SyslogsMemoryHandler::SyslogsMemoryHandler( 280 const ReadCompleteCallback& callback, 281 LogDictionaryType* logs, 282 std::string* zip_content) 283 : callback_(callback), 284 logs_(logs), 285 zip_content_(zip_content) { 286 DCHECK(!callback_.is_null()); 287 } 288 289 void SyslogsMemoryHandler::OnDetailsAvailable() { 290 (*logs_)["mem_usage"] = ToLogString(); 291 callback_.Run(logs_, zip_content_); 292 } 293 294 SyslogsMemoryHandler::~SyslogsMemoryHandler() {} 295 296 // Called from blocking pool thread. 297 void SyslogsProviderImpl::ReadSyslogs( 298 const CancelableTaskTracker::IsCanceledCallback& is_canceled, 299 bool compress_logs, 300 SyslogsContext context, 301 const ReadCompleteCallback& callback) { 302 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 303 304 if (is_canceled.Run()) 305 return; 306 307 // Create temp file. 308 base::FilePath zip_file; 309 if (compress_logs && !file_util::CreateTemporaryFile(&zip_file)) { 310 LOG(ERROR) << "Cannot create temp file"; 311 compress_logs = false; 312 } 313 314 LogDictionaryType* logs = NULL; 315 logs = GetSystemLogs( 316 compress_logs ? &zip_file : NULL, 317 GetSyslogsContextString(context)); 318 319 std::string* zip_content = NULL; 320 if (compress_logs) { 321 // Load compressed logs. 322 zip_content = new std::string(); 323 LoadCompressedLogs(zip_file, zip_content); 324 base::DeleteFile(zip_file, false); 325 } 326 327 // Include dbus statistics summary 328 (*logs)["dbus"] = dbus::statistics::GetAsString( 329 dbus::statistics::SHOW_INTERFACE, 330 dbus::statistics::FORMAT_ALL); 331 332 // Include recent network log events 333 (*logs)["network_event_log"] = network_event_log::GetAsString( 334 network_event_log::OLDEST_FIRST, 335 "time,file,desc", 336 network_event_log::kDefaultLogLevel, 337 system::kFeedbackMaxLineCount); 338 339 // SyslogsMemoryHandler will clean itself up. 340 // SyslogsMemoryHandler::OnDetailsAvailable() will modify |logs| and call 341 // request->ForwardResult(logs, zip_content). 342 scoped_refptr<SyslogsMemoryHandler> 343 handler(new SyslogsMemoryHandler(callback, logs, zip_content)); 344 // TODO(jamescook): Maybe we don't need to update histograms here? 345 handler->StartFetch(MemoryDetails::UPDATE_USER_METRICS); 346 } 347 348 void SyslogsProviderImpl::LoadCompressedLogs(const base::FilePath& zip_file, 349 std::string* zip_content) { 350 DCHECK(zip_content); 351 if (!file_util::ReadFileToString(zip_file, zip_content)) { 352 LOG(ERROR) << "Cannot read compressed logs file from " << 353 zip_file.value().c_str(); 354 } 355 } 356 357 const char* SyslogsProviderImpl::GetSyslogsContextString( 358 SyslogsContext context) { 359 switch (context) { 360 case(SYSLOGS_FEEDBACK): 361 return kContextFeedback; 362 case(SYSLOGS_SYSINFO): 363 return kContextSysInfo; 364 case(SYSLOGS_NETWORK): 365 return kContextNetwork; 366 case(SYSLOGS_DEFAULT): 367 return kContextSysInfo; 368 default: 369 NOTREACHED(); 370 return ""; 371 } 372 } 373 374 // static 375 void SyslogsProviderImpl::RunCallbackIfNotCanceled( 376 const CancelableTaskTracker::IsCanceledCallback& is_canceled, 377 base::TaskRunner* origin_runner, 378 const ReadCompleteCallback& callback, 379 LogDictionaryType* logs, 380 std::string* zip_content) { 381 DCHECK(!is_canceled.is_null() && !callback.is_null()); 382 383 if (is_canceled.Run()) { 384 delete logs; 385 delete zip_content; 386 return; 387 } 388 389 // TODO(achuith (at) chromium.org): Maybe always run callback asynchronously? 390 if (origin_runner->RunsTasksOnCurrentThread()) { 391 callback.Run(logs, zip_content); 392 } else { 393 origin_runner->PostTask(FROM_HERE, base::Bind(callback, logs, zip_content)); 394 } 395 } 396 397 SyslogsProviderImpl* SyslogsProviderImpl::GetInstance() { 398 return Singleton<SyslogsProviderImpl, 399 DefaultSingletonTraits<SyslogsProviderImpl> >::get(); 400 } 401 402 SyslogsProvider* SyslogsProvider::GetInstance() { 403 return SyslogsProviderImpl::GetInstance(); 404 } 405 406 } // namespace system 407 } // namespace chromeos 408