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 <CoreFoundation/CoreFoundation.h>
8#include <DiskArbitration/DASession.h>
9#include <DiskArbitration/DADisk.h>
10#include <IOKit/IOKitLib.h>
11#include <IOKit/network/IOEthernetController.h>
12#include <IOKit/network/IOEthernetInterface.h>
13#include <IOKit/network/IONetworkInterface.h>
14#include <sys/mount.h>
15
16#include "base/bind.h"
17#include "base/mac/foundation_util.h"
18#include "base/mac/scoped_cftyperef.h"
19#include "base/mac/scoped_ioobject.h"
20#include "base/strings/string_number_conversions.h"
21#include "base/strings/string_util.h"
22#include "base/strings/sys_string_conversions.h"
23#include "base/threading/thread_restrictions.h"
24#include "content/public/browser/browser_thread.h"
25
26namespace {
27
28using extensions::api::DeviceId;
29
30const char kRootDirectory[] = "/";
31
32typedef base::Callback<bool(const void* bytes, size_t size)>
33    IsValidMacAddressCallback;
34
35// Return the BSD name (e.g. '/dev/disk1') of the root directory by enumerating
36// through the mounted volumes .
37// Return "" if an error occured.
38std::string FindBSDNameOfSystemDisk() {
39  struct statfs* mounted_volumes;
40  int num_volumes = getmntinfo(&mounted_volumes, 0);
41  if (num_volumes == 0) {
42    VLOG(1) << "Cannot enumerate list of mounted volumes.";
43    return std::string();
44  }
45
46  for (int i = 0; i < num_volumes; i++) {
47    struct statfs* vol = &mounted_volumes[i];
48    if (std::string(vol->f_mntonname) == kRootDirectory) {
49      return std::string(vol->f_mntfromname);
50    }
51  }
52
53  VLOG(1) << "Cannot find disk mounted as '" << kRootDirectory << "'.";
54  return std::string();
55}
56
57// Return the Volume UUID property of a BSD disk name (e.g. '/dev/disk1').
58// Return "" if an error occured.
59std::string GetVolumeUUIDFromBSDName(const std::string& bsd_name) {
60  const CFAllocatorRef allocator = NULL;
61
62  base::ScopedCFTypeRef<DASessionRef> session(DASessionCreate(allocator));
63  if (session.get() == NULL) {
64    VLOG(1) << "Error creating DA Session.";
65    return std::string();
66  }
67
68  base::ScopedCFTypeRef<DADiskRef> disk(
69      DADiskCreateFromBSDName(allocator, session, bsd_name.c_str()));
70  if (disk.get() == NULL) {
71    VLOG(1) << "Error creating DA disk from BSD disk name.";
72    return std::string();
73  }
74
75  base::ScopedCFTypeRef<CFDictionaryRef> disk_description(
76      DADiskCopyDescription(disk));
77  if (disk_description.get() == NULL) {
78    VLOG(1) << "Error getting disk description.";
79    return std::string();
80  }
81
82  CFUUIDRef volume_uuid = base::mac::GetValueFromDictionary<CFUUIDRef>(
83      disk_description,
84      kDADiskDescriptionVolumeUUIDKey);
85  if (volume_uuid == NULL) {
86    VLOG(1) << "Error getting volume UUID of disk.";
87    return std::string();
88  }
89
90  base::ScopedCFTypeRef<CFStringRef> volume_uuid_string(
91      CFUUIDCreateString(allocator, volume_uuid));
92  if (volume_uuid_string.get() == NULL) {
93    VLOG(1) << "Error creating string from CSStringRef.";
94    return std::string();
95  }
96
97  return base::SysCFStringRefToUTF8(volume_uuid_string.get());
98}
99
100// Return Volume UUID property of disk mounted as "/".
101std::string GetVolumeUUID() {
102  base::ThreadRestrictions::AssertIOAllowed();
103
104  std::string result;
105  std::string bsd_name = FindBSDNameOfSystemDisk();
106  if (!bsd_name.empty()) {
107    VLOG(4) << "BSD name of root directory: '" << bsd_name << "'";
108    result = GetVolumeUUIDFromBSDName(bsd_name);
109  }
110  return result;
111}
112
113class MacAddressProcessor {
114 public:
115  MacAddressProcessor(const IsValidMacAddressCallback& is_valid_mac_address)
116    : is_valid_mac_address_(is_valid_mac_address) {
117  }
118
119  bool ProcessNetworkController(io_object_t network_controller) {
120    // Use the MAC address of the first network interface.
121    bool keep_going = true;
122    base::ScopedCFTypeRef<CFDataRef> mac_address_data(
123        static_cast<CFDataRef>(
124            IORegistryEntryCreateCFProperty(network_controller,
125                                            CFSTR(kIOMACAddress),
126                                            kCFAllocatorDefault,
127                                            0)));
128    if (!mac_address_data)
129      return keep_going;
130
131    const UInt8* mac_address = CFDataGetBytePtr(mac_address_data);
132    size_t mac_address_size = CFDataGetLength(mac_address_data);
133    if (!is_valid_mac_address_.Run(mac_address, mac_address_size))
134      return keep_going;
135
136    std::string mac_address_string = base::StringToLowerASCII(base::HexEncode(
137        mac_address, mac_address_size));
138
139    base::ScopedCFTypeRef<CFStringRef> provider_class(
140        static_cast<CFStringRef>(
141            IORegistryEntryCreateCFProperty(network_controller,
142                                            CFSTR(kIOProviderClassKey),
143                                            kCFAllocatorDefault,
144                                            0)));
145    if (provider_class) {
146      if (CFStringCompare(provider_class, CFSTR("IOPCIDevice"), 0) ==
147              kCFCompareEqualTo) {
148        // MAC address from built-in network card is always best choice.
149        found_mac_address_ = mac_address_string;
150        keep_going = false;
151        return keep_going;
152      }
153    }
154
155    // Fall back to using non built-in card MAC address, but keep looking.
156    found_mac_address_ = mac_address_string;
157    return keep_going;
158  }
159
160  std::string mac_address() const { return found_mac_address_; }
161
162 private:
163  const IsValidMacAddressCallback& is_valid_mac_address_;
164  std::string found_mac_address_;
165};
166
167std::string GetMacAddress(
168    const IsValidMacAddressCallback& is_valid_mac_address) {
169  base::ThreadRestrictions::AssertIOAllowed();
170
171  mach_port_t master_port;
172  kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &master_port);
173  if (kr != KERN_SUCCESS) {
174    LOG(ERROR) << "IOMasterPort failed: " << kr;
175    return "";
176  }
177
178  CFMutableDictionaryRef match_classes =
179      IOServiceMatching(kIOEthernetInterfaceClass);
180  if (!match_classes) {
181    LOG(ERROR) << "IOServiceMatching returned a NULL dictionary";
182    return "";
183  }
184
185  io_iterator_t iterator_ref;
186  kr = IOServiceGetMatchingServices(master_port,
187                                    match_classes,
188                                    &iterator_ref);
189  if (kr != KERN_SUCCESS) {
190    LOG(ERROR) << "IOServiceGetMatchingServices failed: " << kr;
191    return "";
192  }
193  base::mac::ScopedIOObject<io_iterator_t> iterator(iterator_ref);
194
195  MacAddressProcessor processor(is_valid_mac_address);
196  while (true) {
197    // Note: interface_service should not be released.
198    io_object_t interface_service = IOIteratorNext(iterator);
199    if (!interface_service)
200      break;
201
202    io_object_t controller_service_ref;
203    kr = IORegistryEntryGetParentEntry(interface_service,
204                                       kIOServicePlane,
205                                       &controller_service_ref);
206    if (kr != KERN_SUCCESS) {
207      LOG(ERROR) << "IORegistryEntryGetParentEntry failed: " << kr;
208    } else {
209      base::mac::ScopedIOObject<io_object_t> controller_service(
210          controller_service_ref);
211      bool keep_going = processor.ProcessNetworkController(controller_service);
212      if (!keep_going) {
213        break;
214      }
215    }
216  }
217  return processor.mac_address();
218}
219
220void GetRawDeviceIdImpl(const IsValidMacAddressCallback& is_valid_mac_address,
221                        const DeviceId::IdCallback& callback) {
222  base::ThreadRestrictions::AssertIOAllowed();
223
224  std::string raw_device_id;
225  std::string mac_address = GetMacAddress(is_valid_mac_address);
226  std::string disk_id = GetVolumeUUID();
227  if (!mac_address.empty() && !disk_id.empty()) {
228    raw_device_id = mac_address + disk_id;
229  }
230  content::BrowserThread::PostTask(
231      content::BrowserThread::UI,
232      FROM_HERE,
233      base::Bind(callback, raw_device_id));
234}
235
236}  // namespace
237
238namespace extensions {
239namespace api {
240
241// static
242void DeviceId::GetRawDeviceId(const IdCallback& callback) {
243  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
244
245  content::BrowserThread::PostTask(
246      content::BrowserThread::FILE,
247      FROM_HERE,
248      base::Bind(GetRawDeviceIdImpl,
249          base::Bind(DeviceId::IsValidMacAddress),
250          callback));
251}
252
253}  // namespace api
254}  // namespace extensions
255