1// Copyright 2013 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/extensions/api/music_manager_private/device_id.h"
6
7#include <sys/socket.h>  // Must be included before ifaddrs.h.
8#include <ifaddrs.h>
9#include <net/if.h>
10#include <sys/ioctl.h>
11
12#include <map>
13
14#include "base/bind.h"
15#include "base/files/file_enumerator.h"
16#include "base/files/file_path.h"
17#include "base/files/file_util.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_util.h"
20#include "base/threading/thread_restrictions.h"
21#include "content/public/browser/browser_thread.h"
22
23namespace {
24
25using extensions::api::DeviceId;
26
27typedef base::Callback<bool(const void* bytes, size_t size)>
28    IsValidMacAddressCallback;
29
30const char kDiskByUuidDirectoryName[] = "/dev/disk/by-uuid";
31const char* kDeviceNames[] = {
32  "sda1", "hda1", "dm-0", "xvda1", "sda2", "hda2", "dm-1", "xvda2",
33};
34// Fedora 15 uses biosdevname feature where Embedded ethernet uses the
35// "em" prefix and PCI cards use the p[0-9]c[0-9] format based on PCI
36// slot and card information.
37const char* kNetDeviceNamePrefixes[] = {
38   "eth", "em", "en", "wl", "ww", "p0", "p1", "p2", "p3", "p4", "p5", "p6",
39   "p7", "p8", "p9", "wlan"
40};
41
42// Map from device name to disk uuid
43typedef std::map<base::FilePath, base::FilePath> DiskEntries;
44
45std::string GetDiskUuid() {
46  base::ThreadRestrictions::AssertIOAllowed();
47
48  DiskEntries disk_uuids;
49  base::FileEnumerator files(base::FilePath(kDiskByUuidDirectoryName),
50                             false,  // Recursive.
51                             base::FileEnumerator::FILES);
52  do {
53    base::FilePath file_path = files.Next();
54    if (file_path.empty())
55      break;
56
57    base::FilePath target_path;
58    if (!base::ReadSymbolicLink(file_path, &target_path))
59      continue;
60
61    base::FilePath device_name = target_path.BaseName();
62    base::FilePath disk_uuid = file_path.BaseName();
63    disk_uuids[device_name] = disk_uuid;
64  } while (true);
65
66  // Look for first device name matching an entry of |kDeviceNames|.
67  std::string result;
68  for (size_t i = 0; i < arraysize(kDeviceNames); i++) {
69    DiskEntries::iterator it =
70        disk_uuids.find(base::FilePath(kDeviceNames[i]));
71    if (it != disk_uuids.end()) {
72      DVLOG(1) << "Returning uuid: \"" << it->second.value()
73               << "\" for device \"" << it->first.value() << "\"";
74      result = it->second.value();
75      break;
76    }
77  }
78
79  // Log failure (at most once) for diagnostic purposes.
80  static bool error_logged = false;
81  if (result.empty() && !error_logged) {
82    error_logged = true;
83    LOG(ERROR) << "Could not find appropriate disk uuid.";
84    for (DiskEntries::iterator it = disk_uuids.begin();
85        it != disk_uuids.end(); ++it) {
86      LOG(ERROR) << "  DeviceID=" << it->first.value() << ", uuid="
87                 << it->second.value();
88    }
89  }
90
91  return result;
92}
93
94class MacAddressProcessor {
95 public:
96  explicit MacAddressProcessor(
97      const IsValidMacAddressCallback& is_valid_mac_address)
98      : is_valid_mac_address_(is_valid_mac_address) {
99  }
100
101  bool ProcessInterface(struct ifaddrs *ifaddr,
102                        const char* prefixes[],
103                        size_t prefixes_count) {
104    const int MAC_LENGTH = 6;
105    struct ifreq ifinfo;
106
107    memset(&ifinfo, 0, sizeof(ifinfo));
108    strncpy(ifinfo.ifr_name, ifaddr->ifa_name, sizeof(ifinfo.ifr_name) - 1);
109
110    int sd = socket(AF_INET, SOCK_DGRAM, 0);
111    int result = ioctl(sd, SIOCGIFHWADDR, &ifinfo);
112    close(sd);
113
114    if (result != 0)
115      return true;
116
117    const char* mac_address =
118        static_cast<const char*>(ifinfo.ifr_hwaddr.sa_data);
119    if (!is_valid_mac_address_.Run(mac_address, MAC_LENGTH))
120      return true;
121
122    if (!IsValidPrefix(ifinfo.ifr_name, prefixes, prefixes_count))
123      return true;
124
125    // Got one!
126    found_mac_address_ =
127        base::StringToLowerASCII(base::HexEncode(mac_address, MAC_LENGTH));
128    return false;
129  }
130
131  std::string mac_address() const { return found_mac_address_; }
132
133 private:
134  bool IsValidPrefix(const char* name,
135                     const char* prefixes[],
136                     size_t prefixes_count) {
137    for (size_t i = 0; i < prefixes_count; i++) {
138      if (strncmp(prefixes[i], name, strlen(prefixes[i])) == 0)
139        return true;
140    }
141    return false;
142  }
143
144  const IsValidMacAddressCallback& is_valid_mac_address_;
145  std::string found_mac_address_;
146};
147
148std::string GetMacAddress(
149    const IsValidMacAddressCallback& is_valid_mac_address) {
150  base::ThreadRestrictions::AssertIOAllowed();
151
152  struct ifaddrs* ifaddrs;
153  int rv = getifaddrs(&ifaddrs);
154  if (rv < 0) {
155    PLOG(ERROR) << "getifaddrs failed " << rv;
156    return "";
157  }
158
159  MacAddressProcessor processor(is_valid_mac_address);
160  for (struct ifaddrs* ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
161    bool keep_going = processor.ProcessInterface(
162        ifa, kNetDeviceNamePrefixes, arraysize(kNetDeviceNamePrefixes));
163    if (!keep_going)
164      break;
165  }
166  freeifaddrs(ifaddrs);
167  return processor.mac_address();
168}
169
170void GetRawDeviceIdImpl(const IsValidMacAddressCallback& is_valid_mac_address,
171                        const DeviceId::IdCallback& callback) {
172  base::ThreadRestrictions::AssertIOAllowed();
173
174  std::string disk_id = GetDiskUuid();
175  std::string mac_address = GetMacAddress(is_valid_mac_address);
176
177  std::string raw_device_id;
178  if (!mac_address.empty() && !disk_id.empty()) {
179    raw_device_id = mac_address + disk_id;
180  }
181
182  content::BrowserThread::PostTask(
183      content::BrowserThread::UI,
184      FROM_HERE,
185      base::Bind(callback, raw_device_id));
186}
187
188}  // namespace
189
190namespace extensions {
191namespace api {
192
193// static
194void DeviceId::GetRawDeviceId(const IdCallback& callback) {
195  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
196
197  content::BrowserThread::PostTask(
198      content::BrowserThread::FILE,
199      FROM_HERE,
200      base::Bind(GetRawDeviceIdImpl,
201          base::Bind(DeviceId::IsValidMacAddress),
202          callback));
203}
204
205}  // namespace api
206}  // namespace extensions
207