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 "base/linux_util.h"
6
7#include <dirent.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <stdlib.h>
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <unistd.h>
14
15#include <vector>
16
17#include "base/command_line.h"
18#include "base/files/file_util.h"
19#include "base/memory/scoped_ptr.h"
20#include "base/memory/singleton.h"
21#include "base/path_service.h"
22#include "base/process/launch.h"
23#include "base/strings/string_util.h"
24#include "base/synchronization/lock.h"
25
26namespace {
27
28// Not needed for OS_CHROMEOS.
29#if defined(OS_LINUX)
30enum LinuxDistroState {
31  STATE_DID_NOT_CHECK  = 0,
32  STATE_CHECK_STARTED  = 1,
33  STATE_CHECK_FINISHED = 2,
34};
35
36// Helper class for GetLinuxDistro().
37class LinuxDistroHelper {
38 public:
39  // Retrieves the Singleton.
40  static LinuxDistroHelper* GetInstance() {
41    return Singleton<LinuxDistroHelper>::get();
42  }
43
44  // The simple state machine goes from:
45  // STATE_DID_NOT_CHECK -> STATE_CHECK_STARTED -> STATE_CHECK_FINISHED.
46  LinuxDistroHelper() : state_(STATE_DID_NOT_CHECK) {}
47  ~LinuxDistroHelper() {}
48
49  // Retrieve the current state, if we're in STATE_DID_NOT_CHECK,
50  // we automatically move to STATE_CHECK_STARTED so nobody else will
51  // do the check.
52  LinuxDistroState State() {
53    base::AutoLock scoped_lock(lock_);
54    if (STATE_DID_NOT_CHECK == state_) {
55      state_ = STATE_CHECK_STARTED;
56      return STATE_DID_NOT_CHECK;
57    }
58    return state_;
59  }
60
61  // Indicate the check finished, move to STATE_CHECK_FINISHED.
62  void CheckFinished() {
63    base::AutoLock scoped_lock(lock_);
64    DCHECK_EQ(STATE_CHECK_STARTED, state_);
65    state_ = STATE_CHECK_FINISHED;
66  }
67
68 private:
69  base::Lock lock_;
70  LinuxDistroState state_;
71};
72#endif  // if defined(OS_LINUX)
73
74}  // namespace
75
76namespace base {
77
78// Account for the terminating null character.
79static const int kDistroSize = 128 + 1;
80
81// We use this static string to hold the Linux distro info. If we
82// crash, the crash handler code will send this in the crash dump.
83char g_linux_distro[kDistroSize] =
84#if defined(OS_CHROMEOS)
85    "CrOS";
86#elif defined(OS_ANDROID)
87    "Android";
88#else  // if defined(OS_LINUX)
89    "Unknown";
90#endif
91
92std::string GetLinuxDistro() {
93#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
94  return g_linux_distro;
95#elif defined(OS_LINUX)
96  LinuxDistroHelper* distro_state_singleton = LinuxDistroHelper::GetInstance();
97  LinuxDistroState state = distro_state_singleton->State();
98  if (STATE_CHECK_FINISHED == state)
99    return g_linux_distro;
100  if (STATE_CHECK_STARTED == state)
101    return "Unknown"; // Don't wait for other thread to finish.
102  DCHECK_EQ(state, STATE_DID_NOT_CHECK);
103  // We do this check only once per process. If it fails, there's
104  // little reason to believe it will work if we attempt to run
105  // lsb_release again.
106  std::vector<std::string> argv;
107  argv.push_back("lsb_release");
108  argv.push_back("-d");
109  std::string output;
110  base::GetAppOutput(CommandLine(argv), &output);
111  if (output.length() > 0) {
112    // lsb_release -d should return: Description:<tab>Distro Info
113    const char field[] = "Description:\t";
114    if (output.compare(0, strlen(field), field) == 0) {
115      SetLinuxDistro(output.substr(strlen(field)));
116    }
117  }
118  distro_state_singleton->CheckFinished();
119  return g_linux_distro;
120#else
121  NOTIMPLEMENTED();
122  return "Unknown";
123#endif
124}
125
126void SetLinuxDistro(const std::string& distro) {
127  std::string trimmed_distro;
128  base::TrimWhitespaceASCII(distro, base::TRIM_ALL, &trimmed_distro);
129  base::strlcpy(g_linux_distro, trimmed_distro.c_str(), kDistroSize);
130}
131
132pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data,
133                              bool* syscall_supported) {
134  char buf[256];
135  snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
136
137  if (syscall_supported != NULL)
138    *syscall_supported = false;
139
140  DIR* task = opendir(buf);
141  if (!task) {
142    DLOG(WARNING) << "Cannot open " << buf;
143    return -1;
144  }
145
146  std::vector<pid_t> tids;
147  struct dirent* dent;
148  while ((dent = readdir(task))) {
149    char* endptr;
150    const unsigned long int tid_ul = strtoul(dent->d_name, &endptr, 10);
151    if (tid_ul == ULONG_MAX || *endptr)
152      continue;
153    tids.push_back(tid_ul);
154  }
155  closedir(task);
156
157  scoped_ptr<char[]> syscall_data(new char[expected_data.length()]);
158  for (std::vector<pid_t>::const_iterator
159       i = tids.begin(); i != tids.end(); ++i) {
160    const pid_t current_tid = *i;
161    snprintf(buf, sizeof(buf), "/proc/%d/task/%d/syscall", pid, current_tid);
162    int fd = open(buf, O_RDONLY);
163    if (fd < 0)
164      continue;
165    if (syscall_supported != NULL)
166      *syscall_supported = true;
167    bool read_ret = ReadFromFD(fd, syscall_data.get(), expected_data.length());
168    close(fd);
169    if (!read_ret)
170      continue;
171
172    if (0 == strncmp(expected_data.c_str(), syscall_data.get(),
173                     expected_data.length())) {
174      return current_tid;
175    }
176  }
177  return -1;
178}
179
180}  // namespace base
181