Home | History | Annotate | Download | only in system
      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