1package FusionInventory::Agent::Task::Inventory::MacOS::Storages; 2 3use strict; 4use warnings; 5 6use parent 'FusionInventory::Agent::Task::Inventory::Module'; 7 8use Scalar::Util qw/looks_like_number/; 9 10use FusionInventory::Agent::Tools; 11use FusionInventory::Agent::Tools::MacOS; 12 13sub isEnabled { 14 my (%params) = @_; 15 return 0 if $params{no_category}->{storage}; 16 return 1; 17} 18 19sub doInventory { 20 my (%params) = @_; 21 22 my $inventory = $params{inventory}; 23 my $logger = $params{logger}; 24 25 my $storages = [ 26 _getSerialATAStorages(logger => $logger), 27 _getDiscBurningStorages(logger => $logger), 28 _getCardReaderStorages(logger => $logger), 29 _getUSBStorages(logger => $logger), 30 _getFireWireStorages(logger => $logger) 31 ]; 32 foreach my $storage (@$storages) { 33 $inventory->addEntry( 34 section => 'STORAGES', 35 entry => $storage 36 ); 37 } 38} 39 40sub _getStorages { 41 my (%params) = @_; 42 43 my $infos = getSystemProfilerInfos( 44 type => 'SPStorageDataType', 45 logger => $params{logger}, 46 file => $params{file} 47 ); 48 49 # system profiler data structure: 50 # bus 51 # └── controller 52 # ├── device 53 # │ ├── subdevice 54 # │ │ └── key:value 55 # │ └── key:value 56 # └── key:value 57 58 my @storages; 59 my @busNames = ('ATA', 'SERIAL-ATA', 'USB', 'FireWire', 'Fibre Channel'); 60 foreach my $busName (@busNames) { 61 my $bus = $infos->{$busName}; 62 next unless $bus; 63 foreach my $controllerName (keys %{$bus}) { 64 my $controller = $bus->{$controllerName}; 65 foreach my $deviceName (keys %{$controller}) { 66 my $device = $controller->{$deviceName}; 67 next unless ref $device eq 'HASH'; 68 if (_isStorage($device)) { 69 push @storages, 70 _getStorage($device, $deviceName, $busName); 71 } else { 72 foreach my $subdeviceName (keys %{$device}) { 73 my $subdevice = $device->{$subdeviceName}; 74 next unless ref $subdevice eq 'HASH'; 75 push @storages, 76 _getStorage($subdevice, $subdeviceName, $busName) 77 if _isStorage($subdevice); 78 } 79 } 80 81 } 82 } 83 } 84 85 return @storages; 86} 87 88sub _getSerialATAStorages { 89 my (%params) = @_; 90 91 my $infos = getSystemProfilerInfos( 92 type => 'SPSerialATADataType', 93 format => 'xml', 94 logger => $params{logger}, 95 file => $params{file} 96 ); 97 return unless $infos->{storages}; 98 my @storages = (); 99 for my $hash (values %{$infos->{storages}}) { 100 next if $hash->{_name} =~ /controller/i; 101 my $storage = _extractStorage($hash); 102 $storage->{TYPE} = 'Disk drive'; 103 $storage->{INTERFACE} = 'SERIAL-ATA'; 104 push @storages, _sanitizedHash($storage); 105 } 106 107 return @storages; 108} 109 110sub _extractStorage { 111 my ($hash) = @_; 112 113 my $storage = { 114 NAME => $hash->{bsd_name} || $hash->{_name}, 115 MANUFACTURER => getCanonicalManufacturer($hash->{_name}), 116# TYPE => $bus_name eq 'FireWire' ? '1394' : $bus_name, 117 SERIAL => $hash->{device_serial}, 118 MODEL => $hash->{device_model} || $hash->{_name}, 119 FIRMWARE => $hash->{device_revision}, 120 DISKSIZE => _extractDiskSize($hash), 121 DESCRIPTION => $hash->{_name} 122 }; 123 124 if ($storage->{MODEL}) { 125 $storage->{MODEL} =~ s/\s*$storage->{MANUFACTURER}\s*//i; 126 } 127 128 return $storage; 129} 130 131sub _getDiscBurningStorages { 132 my (%params) = @_; 133 134 my @storages = (); 135 my $infos = getSystemProfilerInfos( 136 type => 'SPDiscBurningDataType', 137 format => 'xml', 138 logger => $params{logger}, 139 file => $params{file} 140 ); 141 return @storages unless $infos->{storages}; 142 143 for my $hash (values %{$infos->{storages}}) { 144 my $storage = _extractDiscBurning($hash); 145 $storage->{TYPE} = 'Disk burning'; 146 push @storages, _sanitizedHash($storage); 147 } 148 149 return @storages; 150} 151 152sub _extractDiscBurning { 153 my ($hash) = @_; 154 155 my $storage = { 156 NAME => $hash->{bsd_name} || $hash->{_name}, 157 MANUFACTURER => $hash->{manufacturer} ? getCanonicalManufacturer($hash->{manufacturer}) : getCanonicalManufacturer($hash->{_name}), 158 INTERFACE => $hash->{interconnect}, 159 MODEL => $hash->{_name}, 160 FIRMWARE => $hash->{firmware} 161 }; 162 163 if ($storage->{MODEL}) { 164 $storage->{MODEL} =~ s/\s*$storage->{MANUFACTURER}\s*//i; 165 } 166 167 return $storage; 168} 169 170sub _getCardReaderStorages { 171 my (%params) = @_; 172 173 my $infos = getSystemProfilerInfos( 174 type => 'SPCardReaderDataType', 175 format => 'xml', 176 logger => $params{logger}, 177 file => $params{file} 178 ); 179 return unless $infos->{storages}; 180 181 my @storages = (); 182 for my $hash (values %{$infos->{storages}}) { 183 my $storage; 184 if ($hash->{_name} eq 'spcardreader') { 185 $storage = _extractCardReader($hash); 186 $storage->{TYPE} = 'Card reader'; 187 } else { 188 $storage = _extractSdCard($hash); 189 $storage->{TYPE} = 'SD Card'; 190 } 191 push @storages, _sanitizedHash($storage); 192 } 193 194 return @storages; 195} 196 197sub _extractCardReader { 198 my ($hash) = @_; 199 200 my $storage = { 201 NAME => $hash->{bsd_name} || $hash->{_name}, 202 DESCRIPTION => $hash->{_name}, 203 SERIAL => $hash->{spcardreader_serialnumber}, 204 MODEL => $hash->{_name}, 205 FIRMWARE => $hash->{'spcardreader_revision-id'}, 206 MANUFACTURER => $hash->{'spcardreader_vendor-id'} 207 }; 208 209 return $storage; 210} 211 212sub _extractSdCard { 213 my ($hash) = @_; 214 215 my $storage = { 216 NAME => $hash->{bsd_name} || $hash->{_name}, 217 DESCRIPTION => $hash->{_name}, 218 DISKSIZE => _extractDiskSize($hash) 219 }; 220 221 return $storage; 222} 223 224sub _isStorage { 225 my ($device) = @_; 226 227 return 228 ($device->{'BSD Name'} && $device->{'BSD Name'} =~ /^disk\d+$/) || 229 ($device->{'Protocol'} && $device->{'Socket Type'}); 230} 231 232sub _getStorage { 233 my ($device, $device_name, $bus_name) = @_; 234 235 my $storage = { 236 NAME => $device_name, 237 MANUFACTURER => getCanonicalManufacturer($device_name), 238 TYPE => $bus_name eq 'FireWire' ? '1394' : $bus_name, 239 SERIAL => $device->{'Serial Number'}, 240 FIRMWARE => $device->{'Revision'}, 241 MODEL => $device->{'Model'}, 242 DISKSIZE => $device->{'Capacity'} 243 }; 244 245 if (!$device->{'Protocol'}) { 246 $storage->{DESCRIPTION} = 'Disk drive'; 247 } elsif ($device->{'Protocol'} eq 'ATAPI' || $device->{'Drive Type'}) { 248 $storage->{DESCRIPTION} = 'CD-ROM Drive'; 249 } 250 251 if ($storage->{DISKSIZE}) { 252 #e.g: Capacity: 320,07 GB (320 072 933 376 bytes) 253 $storage->{DISKSIZE} =~ s/\s*\(.*//; 254 $storage->{DISKSIZE} =~ s/,/./; 255 256 if ($storage->{DISKSIZE} =~ s/\s*TB//) { 257 $storage->{DISKSIZE} = int($storage->{DISKSIZE} * 1000 * 1000); 258 } elsif ($storage->{DISKSIZE} =~ s/\s+GB$//) { 259 $storage->{DISKSIZE} = int($storage->{DISKSIZE} * 1000 * 1000); 260 } 261 } 262 263 if ($storage->{MODEL}) { 264 $storage->{MODEL} =~ s/\s*$storage->{MANUFACTURER}\s*//i; 265 } 266 267 return $storage; 268} 269 270sub _getUSBStorages { 271 my (%params) = @_; 272 273 my $infos = getSystemProfilerInfos( 274 type => 'SPUSBDataType', 275 format => 'xml', 276 logger => $params{logger}, 277 file => $params{file} 278 ); 279 return unless $infos->{storages}; 280 281 my @storages = (); 282 for my $hash (values %{$infos->{storages}}) { 283 unless ($hash->{bsn_name} && $hash->{bsd_name} =~ /^disk/) { 284 next if $hash->{_name} eq 'Mass Storage Device'; 285 next if $hash->{_name} =~ /keyboard|controller|IR Receiver|built-in|hub|mouse|usb(?:\d+)?bus/i; 286 next if ($hash->{'Built-in_Device'} && $hash->{'Built-in_Device'} eq 'Yes'); 287 } 288 my $storage = _extractUSBStorage($hash); 289 $storage->{TYPE} = 'Disk drive'; 290 $storage->{INTERFACE} = 'USB'; 291 push @storages, _sanitizedHash($storage); 292 } 293 294 return @storages; 295} 296 297sub _extractUSBStorage { 298 my ($hash) = @_; 299 300 my $storage = { 301 NAME => $hash->{bsd_name} || $hash->{_name}, 302 DESCRIPTION => $hash->{_name}, 303 SERIAL => _extractValueInHashWithKeyPattern(qr/^(?:\w_)?serial_num$/, $hash), 304 MODEL => _extractValueInHashWithKeyPattern(qr/^(?:\w_)?device_model/, $hash) || $hash->{_name}, 305 FIRMWARE => _extractValueInHashWithKeyPattern(qr/^(?:\w_)?bcd_device$/, $hash), 306 MANUFACTURER => getCanonicalManufacturer(_extractValueInHashWithKeyPattern(qr/(?:\w+_)?manufacturer/, $hash)) || '', 307 DISKSIZE => _extractDiskSize($hash) 308 }; 309 310 return $storage; 311} 312 313sub _extractDiskSize { 314 my ($hash) = @_; 315 316 return $hash->{size_in_bytes} ? 317 getCanonicalSize($hash->{size_in_bytes} . ' bytes', 1024) : 318 getCanonicalSize($hash->{size}, 1024); 319} 320 321sub _extractValueInHashWithKeyPattern { 322 my ($pattern, $hash) = @_; 323 324 my $value = ''; 325 my @keyMatches = grep { $_ =~ $pattern } keys %$hash; 326 if (@keyMatches && (scalar @keyMatches) == 1) { 327 $value = $hash->{$keyMatches[0]}; 328 } 329 return $value; 330} 331 332sub _getFireWireStorages { 333 my (%params) = @_; 334 335 my $infos = getSystemProfilerInfos( 336 type => 'SPFireWireDataType', 337 format => 'xml', 338 logger => $params{logger}, 339 file => $params{file} 340 ); 341 return unless $infos->{storages}; 342 343 my @storages = (); 344 for my $hash (values %{$infos->{storages}}) { 345 my $storage = _extractFireWireStorage($hash); 346 $storage->{TYPE} = 'Disk drive'; 347 $storage->{INTERFACE} = 'FireWire'; 348 push @storages, _sanitizedHash($storage); 349 } 350 351 return @storages; 352} 353 354sub _extractFireWireStorage { 355 my ($hash) = @_; 356 357 my $storage = { 358 NAME => $hash->{bsd_name} || $hash->{_name}, 359 DESCRIPTION => $hash->{_name}, 360 SERIAL => _extractValueInHashWithKeyPattern(qr/^(?:\w_)?serial_num$/, $hash) || '', 361 MODEL => _extractValueInHashWithKeyPattern(qr/^(?:\w_)?product_id$/, $hash) || '', 362 FIRMWARE => _extractValueInHashWithKeyPattern(qr/^(?:\w_)?bcd_device$/, $hash) || '', 363 MANUFACTURER => getCanonicalManufacturer(_extractValueInHashWithKeyPattern(qr/(?:\w+_)?manufacturer/, $hash)) || '', 364 DISKSIZE => _extractDiskSize($hash) || '' 365 }; 366 367 return $storage; 368} 369 370sub _sanitizedHash { 371 my ($hash) = @_; 372 foreach my $key (keys(%{$hash})) { 373 if (defined($hash->{$key})) { 374 $hash->{$key} = trimWhitespace($hash->{$key}); 375 } else { 376 delete $hash->{$key}; 377 } 378 } 379 return $hash; 380} 381 3821; 383