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