// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// devguid requires Windows.h be imported first.
#include "chrome/browser/extensions/api/image_writer_private/removable_storage_provider.h"

#include <windows.h>
#include <setupapi.h>

// LogSeverity is both a macro in setupapi.h and a typedef in base/logging.h
#undef LogSeverity

#include <winioctl.h>

#include <memory>

#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/scoped_handle.h"

namespace extensions {

namespace {

bool AddDeviceInfo(HANDLE interface_enumerator,
                   SP_DEVICE_INTERFACE_DATA* interface_data,
                   scoped_refptr<StorageDeviceList> device_list) {
  // Get the required buffer size by calling with a null output buffer.
  DWORD interface_detail_data_size;
  BOOL status = SetupDiGetDeviceInterfaceDetail(
      interface_enumerator,
      interface_data,
      NULL,                         // Output buffer.
      0,                            // Output buffer size.
      &interface_detail_data_size,  // Receives the buffer size.
      NULL);                        // Optional DEVINFO_DATA.

  if (status == FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    PLOG(ERROR) << "SetupDiGetDeviceInterfaceDetail failed";
    return false;
  }

  std::unique_ptr<char[]> interface_detail_data_buffer(
      new char[interface_detail_data_size]);

  SP_DEVICE_INTERFACE_DETAIL_DATA* interface_detail_data =
      reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(
          interface_detail_data_buffer.get());

  interface_detail_data->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);

  status = SetupDiGetDeviceInterfaceDetail(
      interface_enumerator,
      interface_data,
      interface_detail_data, // Output struct.
      interface_detail_data_size,  // Output struct size.
      NULL,                        // Receives required size, unneeded.
      NULL);                       // Optional DEVINFO_Data.

  if (status == FALSE) {
    PLOG(ERROR) << "SetupDiGetDeviceInterfaceDetail failed";
    return false;
  }

  // Open a handle to the device to send DeviceIoControl messages.
  base::win::ScopedHandle device_handle(CreateFile(
      interface_detail_data->DevicePath,
      // Desired access, which is none as we only need metadata.
      0,
      // Required to be read + write for devices.
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL,           // Optional security attributes.
      OPEN_EXISTING,  // Devices already exist.
      0,              // No optional flags.
      NULL));          // No template file.

  if (!device_handle.IsValid()) {
    PLOG(ERROR) << "Opening device handle failed.";
    return false;
  }

  DISK_GEOMETRY geometry;
  DWORD bytes_returned;
  status = DeviceIoControl(
      device_handle.Get(),           // Device handle.
      IOCTL_DISK_GET_DRIVE_GEOMETRY, // Flag to request disk size.
      NULL,                          // Optional additional parameters.
      0,                             // Optional parameter size.
      &geometry,                     // output buffer.
      sizeof(DISK_GEOMETRY),         // output size.
      &bytes_returned,               // Must be non-null. If overlapped is null,
                                     // then value is meaningless.
      NULL);                         // Optional unused overlapped parameter.

  if (status == FALSE) {
    PLOG(ERROR) << "DeviceIoControl";
    return false;
  }

  ULONGLONG disk_capacity = geometry.Cylinders.QuadPart *
    geometry.TracksPerCylinder *
    geometry.SectorsPerTrack *
    geometry.BytesPerSector;

  STORAGE_PROPERTY_QUERY query = STORAGE_PROPERTY_QUERY();
  query.PropertyId = StorageDeviceProperty;
  query.QueryType = PropertyStandardQuery;

  std::unique_ptr<char[]> output_buf(new char[1024]);
  status = DeviceIoControl(
      device_handle.Get(),            // Device handle.
      IOCTL_STORAGE_QUERY_PROPERTY,   // Flag to request device properties.
      &query,                         // Query parameters.
      sizeof(STORAGE_PROPERTY_QUERY), // query parameters size.
      output_buf.get(),               // output buffer.
      1024,                           // Size of buffer.
      &bytes_returned,                // Number of bytes returned.
                                      // Must not be null.
      NULL);                          // Optional unused overlapped perameter.

  if (status == FALSE) {
    PLOG(ERROR) << "Storage property query failed.";
    return false;
  }

  STORAGE_DEVICE_DESCRIPTOR* device_descriptor =
      reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(output_buf.get());

  if (!device_descriptor->RemovableMedia &&
      !(device_descriptor->BusType == BusTypeUsb)) {
    // Reject non-removable and non-USB devices.
    // Return true to indicate success but not add anything to the device list.
    return true;
  }

  // Create a drive identifier from the drive number.
  STORAGE_DEVICE_NUMBER device_number = {0};
  status = DeviceIoControl(
      device_handle.Get(),            // Device handle.
      IOCTL_STORAGE_GET_DEVICE_NUMBER,// Flag to request device number.
      NULL,                           // Query parameters, should be NULL.
      0,                              // Query parameters size, should be 0.
      &device_number,                 // output buffer.
      sizeof(device_number),          // Size of buffer.
      &bytes_returned,                // Number of bytes returned.
      NULL);                          // Optional unused overlapped perameter.

  if (status == FALSE) {
    PLOG(ERROR) << "Storage device number query failed.";
    return false;
  }

  std::string drive_id = "\\\\.\\PhysicalDrive";
  drive_id.append(base::NumberToString(device_number.DeviceNumber));

  api::image_writer_private::RemovableStorageDevice device;
  device.capacity = disk_capacity;
  device.storage_unit_id = drive_id;
  device.removable = device_descriptor->RemovableMedia == TRUE;

  if (device_descriptor->VendorIdOffset &&
      output_buf[device_descriptor->VendorIdOffset]) {
    device.vendor.assign(output_buf.get() + device_descriptor->VendorIdOffset);
  }

  std::string product_id;
  if (device_descriptor->ProductIdOffset &&
      output_buf[device_descriptor->ProductIdOffset]) {
    device.model.assign(output_buf.get() + device_descriptor->ProductIdOffset);
  }

  device_list->data.push_back(std::move(device));

  return true;
}

}  // namespace

// static
scoped_refptr<StorageDeviceList>
RemovableStorageProvider::PopulateDeviceList() {
  HDEVINFO interface_enumerator = SetupDiGetClassDevs(
      &DiskClassGuid,
      NULL, // Enumerator.
      NULL, // Parent window.
      // Only devices present & interface class.
      (DIGCF_PRESENT | DIGCF_INTERFACEDEVICE));

  if (interface_enumerator == INVALID_HANDLE_VALUE) {
    DPLOG(ERROR) << "SetupDiGetClassDevs failed.";
    return nullptr;
  }

  DWORD index = 0;
  SP_DEVICE_INTERFACE_DATA interface_data;
  interface_data.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);

  scoped_refptr<StorageDeviceList> device_list(new StorageDeviceList());
  while (SetupDiEnumDeviceInterfaces(
      interface_enumerator,
      NULL,                    // Device Info data.
      &GUID_DEVINTERFACE_DISK, // Only disk devices.
      index,
      &interface_data)) {
    AddDeviceInfo(interface_enumerator, &interface_data, device_list);
    index++;
  }

  DWORD error_code = GetLastError();

  if (error_code != ERROR_NO_MORE_ITEMS) {
    PLOG(ERROR) << "SetupDiEnumDeviceInterfaces failed";
    SetupDiDestroyDeviceInfoList(interface_enumerator);
    return nullptr;
  }

  SetupDiDestroyDeviceInfoList(interface_enumerator);
  return device_list;
}

} // namespace extensions
