| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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_access.h" | |
| 6 | |
| 7 #include "base/command_line.h" | |
| 8 #include "base/file_path.h" | |
| 9 #include "base/file_util.h" | |
| 10 #include "base/logging.h" | |
| 11 #include "base/observer_list.h" | |
| 12 #include "base/memory/scoped_ptr.h" | |
| 13 #include "base/memory/singleton.h" | |
| 14 #include "base/string_util.h" | |
| 15 #include "base/utf_string_conversions.h" | |
| 16 #include "chrome/browser/chromeos/name_value_pairs_parser.h" | |
| 17 #include "chrome/common/chrome_switches.h" | |
| 18 #include "content/browser/browser_thread.h" | |
| 19 | |
| 20 namespace chromeos { // NOLINT | |
| 21 | |
| 22 namespace { // NOLINT | |
| 23 | |
| 24 // The filepath to the timezone file that symlinks to the actual timezone file. | |
| 25 const char kTimezoneSymlink[] = "/var/lib/timezone/localtime"; | |
| 26 const char kTimezoneSymlink2[] = "/var/lib/timezone/localtime2"; | |
| 27 | |
| 28 // The directory that contains all the timezone files. So for timezone | |
| 29 // "US/Pacific", the actual timezone file is: "/usr/share/zoneinfo/US/Pacific" | |
| 30 const char kTimezoneFilesDir[] = "/usr/share/zoneinfo/"; | |
| 31 | |
| 32 // The system command that returns the hardware class. | |
| 33 const char kHardwareClassKey[] = "hardware_class"; | |
| 34 const char* kHardwareClassTool[] = { "crossystem", "hwid" }; | |
| 35 const char kUnknownHardwareClass[] = "unknown"; | |
| 36 | |
| 37 // Command to get machine hardware info and key/value delimiters. | |
| 38 // /tmp/machine-info is generated by platform/init/chromeos_startup. | |
| 39 const char* kMachineHardwareInfoTool[] = { "cat", "/tmp/machine-info" }; | |
| 40 const char kMachineHardwareInfoEq[] = "="; | |
| 41 const char kMachineHardwareInfoDelim[] = " \n"; | |
| 42 | |
| 43 // Command to get machine OS info and key/value delimiters. | |
| 44 const char* kMachineOSInfoTool[] = { "cat", "/etc/lsb-release" }; | |
| 45 const char kMachineOSInfoEq[] = "="; | |
| 46 const char kMachineOSInfoDelim[] = "\n"; | |
| 47 | |
| 48 // Command to get VPD info and key/value delimiters. | |
| 49 const char* kVpdTool[] = { "cat", "/var/log/vpd_2.0.txt" }; | |
| 50 const char kVpdEq[] = "="; | |
| 51 const char kVpdDelim[] = "\n"; | |
| 52 | |
| 53 // Fallback time zone ID used in case of an unexpected error. | |
| 54 const char kFallbackTimeZoneId[] = "America/Los_Angeles"; | |
| 55 | |
| 56 const char kSysLogsScript[] = | |
| 57 "/usr/share/userfeedback/scripts/sysinfo_script_runner"; | |
| 58 const char kBzip2Command[] = | |
| 59 "/bin/bzip2"; | |
| 60 const char kMultilineQuote[] = "\"\"\""; | |
| 61 const char kNewLineChars[] = "\r\n"; | |
| 62 const char kInvalidLogEntry[] = "<invalid characters in log entry>"; | |
| 63 const char kEmptyLogEntry[] = "<no value>"; | |
| 64 | |
| 65 const char kContextFeedback[] = "feedback"; | |
| 66 const char kContextSysInfo[] = "sysinfo"; | |
| 67 const char kContextNetwork[] = "network"; | |
| 68 | |
| 69 // Reads a key from the input string erasing the read values + delimiters read | |
| 70 // from the initial string | |
| 71 std::string ReadKey(std::string* data) { | |
| 72 size_t equal_sign = data->find("="); | |
| 73 if (equal_sign == std::string::npos) | |
| 74 return std::string(""); | |
| 75 std::string key = data->substr(0, equal_sign); | |
| 76 data->erase(0, equal_sign); | |
| 77 if (data->size() > 0) { | |
| 78 // erase the equal to sign also | |
| 79 data->erase(0,1); | |
| 80 return key; | |
| 81 } | |
| 82 return std::string(); | |
| 83 } | |
| 84 | |
| 85 // Reads a value from the input string; erasing the read values from | |
| 86 // the initial string; detects if the value is multiline and reads | |
| 87 // accordingly | |
| 88 std::string ReadValue(std::string* data) { | |
| 89 // Trim the leading spaces and tabs. In order to use a multi-line | |
| 90 // value, you have to place the multi-line quote on the same line as | |
| 91 // the equal sign. | |
| 92 // | |
| 93 // Why not use TrimWhitespace? Consider the following input: | |
| 94 // | |
| 95 // KEY1= | |
| 96 // KEY2=VALUE | |
| 97 // | |
| 98 // If we use TrimWhitespace, we will incorrectly trim the new line | |
| 99 // and assume that KEY1's value is "KEY2=VALUE" rather than empty. | |
| 100 TrimString(*data, " \t", data); | |
| 101 | |
| 102 // If multiline value | |
| 103 if (StartsWithASCII(*data, std::string(kMultilineQuote), false)) { | |
| 104 data->erase(0, strlen(kMultilineQuote)); | |
| 105 size_t next_multi = data->find(kMultilineQuote); | |
| 106 if (next_multi == std::string::npos) { | |
| 107 // Error condition, clear data to stop further processing | |
| 108 data->erase(); | |
| 109 return std::string(); | |
| 110 } | |
| 111 std::string value = data->substr(0, next_multi); | |
| 112 data->erase(0, next_multi + 3); | |
| 113 return value; | |
| 114 } else { // single line value | |
| 115 size_t endl_pos = data->find_first_of(kNewLineChars); | |
| 116 // if we don't find a new line, we just return the rest of the data | |
| 117 std::string value = data->substr(0, endl_pos); | |
| 118 data->erase(0, endl_pos); | |
| 119 return value; | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 // Returns a map of system log keys and values. | |
| 124 // | |
| 125 // Parameters: | |
| 126 // temp_filename: This is an out parameter that holds the name of a file in | |
| 127 // /tmp that contains the system logs in a KEY=VALUE format. | |
| 128 // If this parameter is NULL, system logs are not retained on | |
| 129 // the filesystem after this call completes. | |
| 130 // context: This is an in parameter specifying what context should be | |
| 131 // passed to the syslog collection script; currently valid | |
| 132 // values are "sysinfo" or "feedback"; in case of an invalid | |
| 133 // value, the script will currently default to "sysinfo" | |
| 134 | |
| 135 LogDictionaryType* GetSystemLogs(FilePath* zip_file_name, | |
| 136 const std::string& context) { | |
| 137 // Create the temp file, logs will go here | |
| 138 FilePath temp_filename; | |
| 139 | |
| 140 if (!file_util::CreateTemporaryFile(&temp_filename)) | |
| 141 return NULL; | |
| 142 | |
| 143 std::string cmd = std::string(kSysLogsScript) + " " + context + " >> " + | |
| 144 temp_filename.value(); | |
| 145 | |
| 146 // Ignore the return value - if the script execution didn't work | |
| 147 // stderr won't go into the output file anyway. | |
| 148 if (system(cmd.c_str()) == -1) | |
| 149 LOG(WARNING) << "Command " << cmd << " failed to run"; | |
| 150 | |
| 151 // Compress the logs file if requested. | |
| 152 if (zip_file_name) { | |
| 153 cmd = std::string(kBzip2Command) + " -c " + temp_filename.value() + " > " + | |
| 154 zip_file_name->value(); | |
| 155 if (system(cmd.c_str()) == -1) | |
| 156 LOG(WARNING) << "Command " << cmd << " failed to run"; | |
| 157 } | |
| 158 // Read logs from the temp file | |
| 159 std::string data; | |
| 160 bool read_success = file_util::ReadFileToString(temp_filename, | |
| 161 &data); | |
| 162 // if we were using an internal temp file, the user does not need the | |
| 163 // logs to stay past the ReadFile call - delete the file | |
| 164 file_util::Delete(temp_filename, false); | |
| 165 | |
| 166 if (!read_success) | |
| 167 return NULL; | |
| 168 | |
| 169 // Parse the return data into a dictionary | |
| 170 LogDictionaryType* logs = new LogDictionaryType(); | |
| 171 while (data.length() > 0) { | |
| 172 std::string key = ReadKey(&data); | |
| 173 TrimWhitespaceASCII(key, TRIM_ALL, &key); | |
| 174 if (!key.empty()) { | |
| 175 std::string value = ReadValue(&data); | |
| 176 if (IsStringUTF8(value)) { | |
| 177 TrimWhitespaceASCII(value, TRIM_ALL, &value); | |
| 178 if (value.empty()) | |
| 179 (*logs)[key] = kEmptyLogEntry; | |
| 180 else | |
| 181 (*logs)[key] = value; | |
| 182 } else { | |
| 183 LOG(WARNING) << "Invalid characters in system log entry: " << key; | |
| 184 (*logs)[key] = kInvalidLogEntry; | |
| 185 } | |
| 186 } else { | |
| 187 // no more keys, we're done | |
| 188 break; | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 return logs; | |
| 193 } | |
| 194 | |
| 195 class SystemAccessImpl : public SystemAccess { | |
| 196 public: | |
| 197 // SystemAccess.implementation: | |
| 198 virtual const icu::TimeZone& GetTimezone(); | |
| 199 virtual void SetTimezone(const icu::TimeZone& timezone); | |
| 200 virtual bool GetMachineStatistic(const std::string& name, | |
| 201 std::string* result); | |
| 202 virtual void AddObserver(Observer* observer); | |
| 203 virtual void RemoveObserver(Observer* observer); | |
| 204 | |
| 205 virtual Handle RequestSyslogs( | |
| 206 bool compress_logs, | |
| 207 SyslogsContext context, | |
| 208 CancelableRequestConsumerBase* consumer, | |
| 209 ReadCompleteCallback* callback); | |
| 210 | |
| 211 // Reads system logs, compresses content if requested. | |
| 212 // Called from FILE thread. | |
| 213 void ReadSyslogs( | |
| 214 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request, | |
| 215 bool compress_logs, | |
| 216 SyslogsContext context); | |
| 217 | |
| 218 // Loads compressed logs and writes into |zip_content|. | |
| 219 void LoadCompressedLogs(const FilePath& zip_file, | |
| 220 std::string* zip_content); | |
| 221 | |
| 222 static SystemAccessImpl* GetInstance(); | |
| 223 | |
| 224 private: | |
| 225 friend struct DefaultSingletonTraits<SystemAccessImpl>; | |
| 226 | |
| 227 SystemAccessImpl(); | |
| 228 | |
| 229 // Updates the machine statistcs by examining the system. | |
| 230 void UpdateMachineStatistics(); | |
| 231 | |
| 232 // Gets syslogs context string from the enum value. | |
| 233 const char* GetSyslogsContextString(SyslogsContext context); | |
| 234 | |
| 235 scoped_ptr<icu::TimeZone> timezone_; | |
| 236 ObserverList<Observer> observers_; | |
| 237 NameValuePairsParser::NameValueMap machine_info_; | |
| 238 | |
| 239 DISALLOW_COPY_AND_ASSIGN(SystemAccessImpl); | |
| 240 }; | |
| 241 | |
| 242 std::string GetTimezoneIDAsString() { | |
| 243 // Look at kTimezoneSymlink, see which timezone we are symlinked to. | |
| 244 char buf[256]; | |
| 245 const ssize_t len = readlink(kTimezoneSymlink, buf, | |
| 246 sizeof(buf)-1); | |
| 247 if (len == -1) { | |
| 248 LOG(ERROR) << "GetTimezoneID: Cannot read timezone symlink " | |
| 249 << kTimezoneSymlink; | |
| 250 return std::string(); | |
| 251 } | |
| 252 | |
| 253 std::string timezone(buf, len); | |
| 254 // Remove kTimezoneFilesDir from the beginning. | |
| 255 if (timezone.find(kTimezoneFilesDir) != 0) { | |
| 256 LOG(ERROR) << "GetTimezoneID: Timezone symlink is wrong " | |
| 257 << timezone; | |
| 258 return std::string(); | |
| 259 } | |
| 260 | |
| 261 return timezone.substr(strlen(kTimezoneFilesDir)); | |
| 262 } | |
| 263 | |
| 264 void SetTimezoneIDFromString(const std::string& id) { | |
| 265 // Change the kTimezoneSymlink symlink to the path for this timezone. | |
| 266 // We want to do this in an atomic way. So we are going to create the symlink | |
| 267 // at kTimezoneSymlink2 and then move it to kTimezoneSymlink | |
| 268 | |
| 269 FilePath timezone_symlink(kTimezoneSymlink); | |
| 270 FilePath timezone_symlink2(kTimezoneSymlink2); | |
| 271 FilePath timezone_file(kTimezoneFilesDir + id); | |
| 272 | |
| 273 // Make sure timezone_file exists. | |
| 274 if (!file_util::PathExists(timezone_file)) { | |
| 275 LOG(ERROR) << "SetTimezoneID: Cannot find timezone file " | |
| 276 << timezone_file.value(); | |
| 277 return; | |
| 278 } | |
| 279 | |
| 280 // Delete old symlink2 if it exists. | |
| 281 file_util::Delete(timezone_symlink2, false); | |
| 282 | |
| 283 // Create new symlink2. | |
| 284 if (symlink(timezone_file.value().c_str(), | |
| 285 timezone_symlink2.value().c_str()) == -1) { | |
| 286 LOG(ERROR) << "SetTimezoneID: Unable to create symlink " | |
| 287 << timezone_symlink2.value() << " to " << timezone_file.value(); | |
| 288 return; | |
| 289 } | |
| 290 | |
| 291 // Move symlink2 to symlink. | |
| 292 if (!file_util::ReplaceFile(timezone_symlink2, timezone_symlink)) { | |
| 293 LOG(ERROR) << "SetTimezoneID: Unable to move symlink " | |
| 294 << timezone_symlink2.value() << " to " | |
| 295 << timezone_symlink.value(); | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 const icu::TimeZone& SystemAccessImpl::GetTimezone() { | |
| 300 return *timezone_.get(); | |
| 301 } | |
| 302 | |
| 303 void SystemAccessImpl::SetTimezone(const icu::TimeZone& timezone) { | |
| 304 timezone_.reset(timezone.clone()); | |
| 305 icu::UnicodeString unicode; | |
| 306 timezone.getID(unicode); | |
| 307 std::string id; | |
| 308 UTF16ToUTF8(unicode.getBuffer(), unicode.length(), &id); | |
| 309 VLOG(1) << "Setting timezone to " << id; | |
| 310 chromeos::SetTimezoneIDFromString(id); | |
| 311 icu::TimeZone::setDefault(timezone); | |
| 312 FOR_EACH_OBSERVER(Observer, observers_, TimezoneChanged(timezone)); | |
| 313 } | |
| 314 | |
| 315 bool SystemAccessImpl::GetMachineStatistic( | |
| 316 const std::string& name, std::string* result) { | |
| 317 NameValuePairsParser::NameValueMap::iterator iter = machine_info_.find(name); | |
| 318 if (iter != machine_info_.end()) { | |
| 319 *result = iter->second; | |
| 320 return true; | |
| 321 } | |
| 322 return false; | |
| 323 } | |
| 324 | |
| 325 void SystemAccessImpl::AddObserver(Observer* observer) { | |
| 326 observers_.AddObserver(observer); | |
| 327 } | |
| 328 | |
| 329 void SystemAccessImpl::RemoveObserver(Observer* observer) { | |
| 330 observers_.RemoveObserver(observer); | |
| 331 } | |
| 332 | |
| 333 SystemAccessImpl::SystemAccessImpl() { | |
| 334 // Get Statistics | |
| 335 UpdateMachineStatistics(); | |
| 336 // Get Timezone | |
| 337 std::string id = GetTimezoneIDAsString(); | |
| 338 if (id.empty()) { | |
| 339 id = kFallbackTimeZoneId; | |
| 340 LOG(ERROR) << "Got an empty string for timezone, default to " << id; | |
| 341 } | |
| 342 icu::TimeZone* timezone = | |
| 343 icu::TimeZone::createTimeZone(icu::UnicodeString::fromUTF8(id)); | |
| 344 timezone_.reset(timezone); | |
| 345 icu::TimeZone::setDefault(*timezone); | |
| 346 VLOG(1) << "Timezone is " << id; | |
| 347 } | |
| 348 | |
| 349 void SystemAccessImpl::UpdateMachineStatistics() { | |
| 350 NameValuePairsParser parser(&machine_info_); | |
| 351 if (!parser.GetSingleValueFromTool(arraysize(kHardwareClassTool), | |
| 352 kHardwareClassTool, | |
| 353 kHardwareClassKey)) { | |
| 354 // Use kUnknownHardwareClass if the hardware class command fails. | |
| 355 parser.AddNameValuePair(kHardwareClassKey, kUnknownHardwareClass); | |
| 356 } | |
| 357 parser.ParseNameValuePairsFromTool(arraysize(kMachineHardwareInfoTool), | |
| 358 kMachineHardwareInfoTool, | |
| 359 kMachineHardwareInfoEq, | |
| 360 kMachineHardwareInfoDelim); | |
| 361 parser.ParseNameValuePairsFromTool(arraysize(kMachineOSInfoTool), | |
| 362 kMachineOSInfoTool, | |
| 363 kMachineOSInfoEq, | |
| 364 kMachineOSInfoDelim); | |
| 365 parser.ParseNameValuePairsFromTool( | |
| 366 arraysize(kVpdTool), kVpdTool, kVpdEq, kVpdDelim); | |
| 367 } | |
| 368 | |
| 369 CancelableRequestProvider::Handle SystemAccessImpl::RequestSyslogs( | |
| 370 bool compress_logs, | |
| 371 SyslogsContext context, | |
| 372 CancelableRequestConsumerBase* consumer, | |
| 373 ReadCompleteCallback* callback) { | |
| 374 // Register the callback request. | |
| 375 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request( | |
| 376 new CancelableRequest<ReadCompleteCallback>(callback)); | |
| 377 AddRequest(request, consumer); | |
| 378 | |
| 379 // Schedule a task on the FILE thread which will then trigger a request | |
| 380 // callback on the calling thread (e.g. UI) when complete. | |
| 381 BrowserThread::PostTask( | |
| 382 BrowserThread::FILE, FROM_HERE, | |
| 383 NewRunnableMethod( | |
| 384 this, &SystemAccessImpl::ReadSyslogs, request, | |
| 385 compress_logs, context)); | |
| 386 | |
| 387 return request->handle(); | |
| 388 } | |
| 389 | |
| 390 // Called from FILE thread. | |
| 391 void SystemAccessImpl::ReadSyslogs( | |
| 392 scoped_refptr<CancelableRequest<ReadCompleteCallback> > request, | |
| 393 bool compress_logs, | |
| 394 SyslogsContext context) { | |
| 395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 396 | |
| 397 if (request->canceled()) | |
| 398 return; | |
| 399 | |
| 400 if (compress_logs && !CommandLine::ForCurrentProcess()->HasSwitch( | |
| 401 switches::kCompressSystemFeedback)) | |
| 402 compress_logs = false; | |
| 403 | |
| 404 // Create temp file. | |
| 405 FilePath zip_file; | |
| 406 if (compress_logs && !file_util::CreateTemporaryFile(&zip_file)) { | |
| 407 LOG(ERROR) << "Cannot create temp file"; | |
| 408 compress_logs = false; | |
| 409 } | |
| 410 | |
| 411 LogDictionaryType* logs = NULL; | |
| 412 logs = chromeos::GetSystemLogs( | |
| 413 compress_logs ? &zip_file : NULL, | |
| 414 GetSyslogsContextString(context)); | |
| 415 | |
| 416 std::string* zip_content = NULL; | |
| 417 if (compress_logs) { | |
| 418 // Load compressed logs. | |
| 419 zip_content = new std::string(); | |
| 420 LoadCompressedLogs(zip_file, zip_content); | |
| 421 file_util::Delete(zip_file, false); | |
| 422 } | |
| 423 | |
| 424 // Will call the callback on the calling thread. | |
| 425 request->ForwardResult(Tuple2<LogDictionaryType*, | |
| 426 std::string*>(logs, zip_content)); | |
| 427 } | |
| 428 | |
| 429 | |
| 430 void SystemAccessImpl::LoadCompressedLogs(const FilePath& zip_file, | |
| 431 std::string* zip_content) { | |
| 432 DCHECK(zip_content); | |
| 433 if (!file_util::ReadFileToString(zip_file, zip_content)) { | |
| 434 LOG(ERROR) << "Cannot read compressed logs file from " << | |
| 435 zip_file.value().c_str(); | |
| 436 } | |
| 437 } | |
| 438 | |
| 439 const char* SystemAccessImpl::GetSyslogsContextString(SyslogsContext context) { | |
| 440 switch (context) { | |
| 441 case(SYSLOGS_FEEDBACK): | |
| 442 return kContextFeedback; | |
| 443 case(SYSLOGS_SYSINFO): | |
| 444 return kContextSysInfo; | |
| 445 case(SYSLOGS_NETWORK): | |
| 446 return kContextNetwork; | |
| 447 case(SYSLOGS_DEFAULT): | |
| 448 return kContextSysInfo; | |
| 449 default: | |
| 450 NOTREACHED(); | |
| 451 return ""; | |
| 452 } | |
| 453 } | |
| 454 | |
| 455 SystemAccessImpl* SystemAccessImpl::GetInstance() { | |
| 456 return Singleton<SystemAccessImpl, | |
| 457 DefaultSingletonTraits<SystemAccessImpl> >::get(); | |
| 458 } | |
| 459 | |
| 460 } // namespace | |
| 461 | |
| 462 SystemAccess* SystemAccess::GetInstance() { | |
| 463 return SystemAccessImpl::GetInstance(); | |
| 464 } | |
| 465 | |
| 466 } // namespace chromeos | |
| 467 | |
| 468 // Allows InvokeLater without adding refcounting. SystemAccessImpl is a | |
| 469 // Singleton and won't be deleted until it's last InvokeLater is run. | |
| 470 DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::SystemAccessImpl); | |
| OLD | NEW |