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 26 namespace { 27 28 using extensions::api::DeviceId; 29 30 const char kRootDirectory[] = "/"; 31 32 typedef 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. 38 std::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. 59 std::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 "/". 101 std::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 113 class 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 = 137 StringToLowerASCII(base::HexEncode(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 167 std::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 220 void 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 238 namespace extensions { 239 namespace api { 240 241 // static 242 void 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