/*
 * dev_interface.h
 *
 * Home page of code is: https://www.smartmontools.org
 *
 * Copyright (C) 2008-22 Christian Franke
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#ifndef DEV_INTERFACE_H
#define DEV_INTERFACE_H

#define DEV_INTERFACE_H_CVSID "$Id: dev_interface.h 5455 2023-02-12 05:13:17Z dpgilbert $\n"

#include "utility.h"

#include <stdexcept>
#include <string>
#include <vector>

/////////////////////////////////////////////////////////////////////////////
// Common functionality for all device types

// Forward declarations
class smart_interface;
class ata_device;
class scsi_device;
class nvme_device;

/// Base class for all devices
class smart_device
{
// Types
public:
  /// Device info strings
  struct device_info {
    device_info()
      { }
    device_info(const char * d_name, const char * d_type, const char * r_type)
      : dev_name(d_name), info_name(d_name),
        dev_type(d_type), req_type(r_type)
      { }

    std::string dev_name;  ///< Device (path)name
    std::string info_name; ///< Informal name
    std::string dev_type;  ///< Actual device type
    std::string req_type;  ///< Device type requested by user, empty if none
  };

  /// Error (number,message) pair
  struct error_info {
    explicit error_info(int n = 0)
      : no(n) { }
    error_info(int n, const char * m)
      : no(n), msg(m) { }
    void clear()
      { no = 0; msg.erase(); }

    int no;          ///< Error number
    std::string msg; ///< Error message
  };

// Construction
protected:
  /// Constructor to init interface and device info.
  /// Must be called in implementation classes.
  smart_device(smart_interface * intf, const char * dev_name,
    const char * dev_type, const char * req_type);

  /// Dummy enum for dummy constructor.
  enum do_not_use_in_implementation_classes { never_called };
  /// Dummy constructor for abstract classes.
  /// Must never be called in implementation classes.
  explicit smart_device(do_not_use_in_implementation_classes);

public:
  virtual ~smart_device();

// Attributes
public:
  ///////////////////////////////////////////////
  // Dynamic downcasts to actual device flavor

  /// Return true if ATA device
  bool is_ata() const
    { return !!m_ata_ptr; }
  /// Return true if SCSI device
  bool is_scsi() const
    { return !!m_scsi_ptr; }
  /// Return true if NVMe device
  bool is_nvme() const
    { return !!m_nvme_ptr; }

  /// Downcast to ATA device.
  ata_device * to_ata()
    { return m_ata_ptr; }
  /// Downcast to ATA device (const).
  const ata_device * to_ata() const
    { return m_ata_ptr; }
  /// Downcast to SCSI device.
  scsi_device * to_scsi()
    { return m_scsi_ptr; }
  /// Downcast to SCSI device (const).
  const scsi_device * to_scsi() const
    { return m_scsi_ptr; }
  /// Downcast to NVMe device.
  nvme_device * to_nvme()
    { return m_nvme_ptr; }
  /// Downcast to NVMe device (const).
  const nvme_device * to_nvme() const
    { return m_nvme_ptr; }

  ///////////////////////////////////////////////
  // Device information

  /// Get device info struct.
  const device_info & get_info() const
    { return m_info; }

  /// Get device (path)name.
  const char * get_dev_name() const
    { return m_info.dev_name.c_str(); }
  /// Get informal name.
  const char * get_info_name() const
    { return m_info.info_name.c_str(); }
  /// Get device type.
  const char * get_dev_type() const
    { return m_info.dev_type.c_str(); }
  /// Get type requested by user, empty if none.
  const char * get_req_type() const
    { return m_info.req_type.c_str(); }

protected:
  /// R/W access to device info struct.
  device_info & set_info()
    { return m_info; }

public:
  ///////////////////////////////////////////////
  // Last error information

  /// Get last error info struct.
  const error_info & get_err() const
    { return m_err; }
  /// Get last error number.
  int get_errno() const
    { return m_err.no; }
  /// Get last error message.
  const char * get_errmsg() const
    { return m_err.msg.c_str(); }

  /// Return true if last error indicates an unsupported system call.
  /// Default implementation returns true on ENOSYS and ENOTSUP.
  virtual bool is_syscall_unsup() const;

  /// Set last error number and message.
  /// Printf()-like formatting is supported.
  /// Returns false always to allow use as a return expression.
  bool set_err(int no, const char * msg, ...)
    __attribute_format_printf(3, 4);

  /// Set last error info struct.
  bool set_err(const error_info & err)
    { m_err = err; return false; }

  /// Clear last error info.
  void clear_err()
    { m_err.clear(); }

  /// Set last error number and default message.
  /// Message is retrieved from interface's get_msg_for_errno(no).
  bool set_err(int no);

  /// Get current number of allocated 'smart_device' objects.
  static int get_num_objects()
    { return s_num_objects; }

// Operations
public:
  ///////////////////////////////////////////////
  // Device open/close
  // Must be implemented in derived class

  /// Return true if device is open.
  virtual bool is_open() const = 0;

  /// Open device, return false on error.
  virtual bool open() = 0;

  /// Close device, return false on error.
  virtual bool close() = 0;

  /// Open device with autodetection support.
  /// May return another device for further access.
  /// In this case, the original pointer is no longer valid.
  /// Default implementation calls 'open()' and returns 'this'.
  virtual smart_device * autodetect_open();

  ///////////////////////////////////////////////
  // Support for checking power mode reported by operating system

  /// Early test if device is powered up or down.
  /// Can be used without calling 'open()' first!
  /// Return true when device is powered down, false when
  /// powered up. If this function is not implemented or
  /// the mode cannot be determined, return false.
  /// Default implementation returns false.
  virtual bool is_powered_down();

  ///////////////////////////////////////////////
  // Support for tunnelled devices

  /// Return true if other device is owned by this device.
  /// Default implementation returns false.
  virtual bool owns(const smart_device * dev) const;

  /// Release ownership of other device.
  /// Default implementation does nothing.
  virtual void release(const smart_device * dev);

protected:
  /// Get interface which produced this object.
  smart_interface * smi()
    { return m_intf; }
  /// Get interface which produced this object (const).
  const smart_interface * smi() const
    { return m_intf; }

// Implementation
private:
  smart_interface * m_intf;
  device_info m_info;
  error_info m_err;

  // Pointers for to_ata(), to_scsi(), to_nvme()
  // set by ATA/SCSI/NVMe interface classes.
  friend class ata_device;
  ata_device * m_ata_ptr;
  friend class scsi_device;
  scsi_device * m_scsi_ptr;
  friend class nvme_device;
  nvme_device * m_nvme_ptr;

  // Number of objects.
  static int s_num_objects;

  // Prevent copy/assignment
  smart_device(const smart_device &);
  void operator=(const smart_device &);
};


/////////////////////////////////////////////////////////////////////////////
// ATA specific interface

/// ATA register value and info whether it has ever been set
// (Automatically set by first assignment)
class ata_register
{
public:
  ata_register()
    : m_val(0x00), m_is_set(false) { }

  ata_register & operator=(unsigned char x)
    { m_val = x; m_is_set = true; return * this; }

  unsigned char val() const
    { return m_val; }
  operator unsigned char() const
    { return m_val; }

  bool is_set() const
    { return m_is_set; }

private:
  unsigned char m_val; ///< Register value
  bool m_is_set; ///< true if set
};

/// ATA Input registers (for 28-bit commands)
struct ata_in_regs
{
  // ATA-6/7 register names  // ATA-3/4/5        // ATA-8
  ata_register features;     // features         // features
  ata_register sector_count; // sector count     // count
  ata_register lba_low;      // sector number    // ]
  ata_register lba_mid;      // cylinder low     // ] lba
  ata_register lba_high;     // cylinder high    // ]
  ata_register device;       // device/head      // device
  ata_register command;      // command          // command

  /// Return true if any register is set
  bool is_set() const
    { return (features.is_set() || sector_count.is_set()
      || lba_low.is_set() || lba_mid.is_set() || lba_high.is_set()
      || device.is_set() || command.is_set());                    }
};

/// ATA Output registers (for 28-bit commands)
struct ata_out_regs
{
  ata_register error;
  ata_register sector_count;
  ata_register lba_low;
  ata_register lba_mid;
  ata_register lba_high;
  ata_register device;
  ata_register status;

  /// Return true if any register is set
  bool is_set() const
    { return (error.is_set() || sector_count.is_set()
      || lba_low.is_set() || lba_mid.is_set() || lba_high.is_set()
      || device.is_set() || status.is_set());                      }
};


/// 16-bit alias to a 8-bit ATA register pair.
class ata_reg_alias_16
{
public:
  ata_reg_alias_16(ata_register & lo, ata_register & hi)
    : m_lo(lo), m_hi(hi) { }

  ata_reg_alias_16 & operator=(unsigned short x)
    { m_lo = (unsigned char) x;
      m_hi = (unsigned char)(x >> 8);
      return * this;                   }

  unsigned short val() const
    { return m_lo | (m_hi << 8); }
  operator unsigned short() const
    { return m_lo | (m_hi << 8); }

private:
  ata_register & m_lo, & m_hi;

  // References must not be copied.
  ata_reg_alias_16(const ata_reg_alias_16 &);
  void operator=(const ata_reg_alias_16 &);
};


/// 48-bit alias to six 8-bit ATA registers (for LBA).
class ata_reg_alias_48
{
public:
  ata_reg_alias_48(ata_register & ll, ata_register & lm, ata_register & lh,
                   ata_register & hl, ata_register & hm, ata_register & hh)
    : m_ll(ll), m_lm(lm), m_lh(lh),
      m_hl(hl), m_hm(hm), m_hh(hh)
    { }

  ata_reg_alias_48 & operator=(uint64_t x)
    {
      m_ll = (unsigned char) x;
      m_lm = (unsigned char)(x >>  8);
      m_lh = (unsigned char)(x >> 16);
      m_hl = (unsigned char)(x >> 24);
      m_hm = (unsigned char)(x >> 32);
      m_hh = (unsigned char)(x >> 40);
      return * this;
    }

  uint64_t val() const
    {
      return (   (unsigned)m_ll
              | ((unsigned)m_lm <<  8)
              | ((unsigned)m_lh << 16)
              | ((unsigned)m_hl << 24)
              | ((uint64_t)m_hm << 32)
              | ((uint64_t)m_hh << 40));
    }

  operator uint64_t() const
    { return val(); }

private:
  ata_register & m_ll, & m_lm, & m_lh,
               & m_hl, & m_hm, & m_hh;

  // References must not be copied.
  ata_reg_alias_48(const ata_reg_alias_48 &);
  void operator=(const ata_reg_alias_48 &);
};


/// ATA Input registers for 48-bit commands
// See section 4.14 of T13/1532D Volume 1 Revision 4b
//
// Uses ATA-6/7 method to specify 16-bit registers as
// recent (low byte) and previous (high byte) content of
// 8-bit registers.
//
// (ATA-8 ACS does not longer follow this scheme, it uses
// abstract registers with sufficient size and leaves the
// actual mapping to the transport layer.)
//
struct ata_in_regs_48bit
: public ata_in_regs   // "most recently written" registers
{
  ata_in_regs prev;  ///< "previous content"

  // 16-bit aliases for above pair.
  ata_reg_alias_16 features_16;
  ata_reg_alias_16 sector_count_16;
  ata_reg_alias_16 lba_low_16;
  ata_reg_alias_16 lba_mid_16;
  ata_reg_alias_16 lba_high_16;

  // 48-bit alias to all 8-bit LBA registers.
  ata_reg_alias_48 lba_48;

  /// Return true if 48-bit command
  bool is_48bit_cmd() const
    { return prev.is_set(); }

  /// Return true if 48-bit command with any nonzero high byte
  bool is_real_48bit_cmd() const
    { return (   prev.features || prev.sector_count
              || prev.lba_low || prev.lba_mid || prev.lba_high); }

  ata_in_regs_48bit();
};


/// ATA Output registers for 48-bit commands
struct ata_out_regs_48bit
: public ata_out_regs   // read with HOB=0
{
  ata_out_regs prev;  ///< read with HOB=1

  // 16-bit aliases for above pair.
  ata_reg_alias_16 sector_count_16;
  ata_reg_alias_16 lba_low_16;
  ata_reg_alias_16 lba_mid_16;
  ata_reg_alias_16 lba_high_16;

  // 48-bit alias to all 8-bit LBA registers.
  ata_reg_alias_48 lba_48;

  ata_out_regs_48bit();
};


/// Flags for each ATA output register
struct ata_out_regs_flags
{
  bool error, sector_count, lba_low, lba_mid, lba_high, device, status;

  /// Return true if any flag is set.
  bool is_set() const
    { return (   error || sector_count || lba_low
              || lba_mid || lba_high || device || status); }

  /// Default constructor clears all flags.
  ata_out_regs_flags()
    : error(false), sector_count(false), lba_low(false), lba_mid(false),
      lba_high(false), device(false), status(false) { }
};


/// ATA pass through input parameters
struct ata_cmd_in
{
  ata_in_regs_48bit in_regs;  ///< Input registers
  ata_out_regs_flags out_needed; ///< True if output register value needed
  enum { no_data = 0, data_in, data_out } direction; ///< I/O direction
  void * buffer; ///< Pointer to data buffer
  unsigned size; ///< Size of buffer

  /// Prepare for 28-bit DATA IN command
  void set_data_in(void * buf, unsigned nsectors)
    {
      buffer = buf;
      in_regs.sector_count = nsectors;
      direction = data_in;
      size = nsectors * 512;
    }

  /// Prepare for 28-bit DATA OUT command
  void set_data_out(const void * buf, unsigned nsectors)
    {
      buffer = const_cast<void *>(buf);
      in_regs.sector_count = nsectors;
      direction = data_out;
      size = nsectors * 512;
    }

  /// Prepare for 48-bit DATA IN command
  void set_data_in_48bit(void * buf, unsigned nsectors)
    {
      buffer = buf;
      // Note: This also sets 'in_regs.is_48bit_cmd()'
      in_regs.sector_count_16 = nsectors;
      direction = data_in;
      size = nsectors * 512;
    }

  ata_cmd_in();
};

/// ATA pass through output parameters
struct ata_cmd_out
{
  ata_out_regs_48bit out_regs; ///< Output registers

  ata_cmd_out();
};

/// ATA device access
class ata_device
: virtual public /*extends*/ smart_device
{
public:
  /// ATA pass through.
  /// Return false on error.
  /// Must be implemented in derived class.
  virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) = 0;

  /// ATA pass through without output registers.
  /// Return false on error.
  /// Calls ata_pass_through(in, dummy), cannot be reimplemented.
  bool ata_pass_through(const ata_cmd_in & in);

  /// Return true if OS caches ATA identify sector.
  /// Default implementation returns false.
  virtual bool ata_identify_is_cached() const;

protected:
  /// Flags for ata_cmd_is_supported().
  enum {
    supports_data_out = 0x01, // PIO DATA OUT
    supports_smart_status = 0x02, // read output registers for SMART STATUS only
    supports_output_regs = 0x04, // read output registers for all commands
    supports_multi_sector = 0x08, // more than one sector (1 DRQ/sector variant)
    supports_48bit_hi_null = 0x10, // 48-bit commands with null high bytes only
    supports_48bit = 0x20, // all 48-bit commands
  };

  /// Check command input parameters.
  /// Return false if required features are not implemented.
  /// Calls set_err(...) accordingly.
  bool ata_cmd_is_supported(const ata_cmd_in & in, unsigned flags,
    const char * type = 0);

  /// Check command input parameters (old version).
  // TODO: Remove if no longer used.
  bool ata_cmd_is_ok(const ata_cmd_in & in,
    bool data_out_support = false,
    bool multi_sector_support = false,
    bool ata_48bit_support = false)
    {
      return ata_cmd_is_supported(in,
        (data_out_support ? supports_data_out : 0) |
        supports_output_regs |
        (multi_sector_support ? supports_multi_sector : 0) |
        (ata_48bit_support ? supports_48bit : 0));
    }

  /// Hide/unhide ATA interface.
  void hide_ata(bool hide = true)
    { m_ata_ptr = (!hide ? this : 0); }

  /// Default constructor, registers device as ATA.
  ata_device()
    : smart_device(never_called)
    { hide_ata(false); }
};


/////////////////////////////////////////////////////////////////////////////
// SCSI specific interface

struct scsi_cmnd_io;

enum scsi_cmd_support
{
  SC_SUPPORT_UNKNOWN = 0,
  SC_NO_SUPPORT,
  SC_SUPPORT,
};

/// SCSI device access
class scsi_device
: virtual public /*extends*/ smart_device
{
public:
  /// SCSI pass through.
  /// Returns false on error.
  virtual bool scsi_pass_through(scsi_cmnd_io * iop) = 0;

  // Call scsi_pass_through and check sense.
  bool scsi_pass_through_and_check(scsi_cmnd_io * iop,
                                   const char * msg = "");

  /// Always try READ CAPACITY(10) (rcap10) first but once we know
  /// rcap16 is needed, use it instead.
  void set_rcap16_first()
    { rcap16_first = true; }

  bool use_rcap16() const
    { return rcap16_first; }

  void set_spc4_or_higher() { spc4_or_above = true; }

  bool is_spc4_or_higher() const { return spc4_or_above; }

  bool query_cmd_support();

  bool checked_cmd_support() const { return rsoc_queried; }

  enum scsi_cmd_support cmd_support_level(uint8_t opcode, bool sa_valid,
                                          uint16_t sa,
                                          bool for_lsense_spc = false) const;

protected:
  /// Hide/unhide SCSI interface.
  void hide_scsi(bool hide = true)
    { m_scsi_ptr = (!hide ? this : 0); }

  /// Default constructor, registers device as SCSI.
  scsi_device()
    : smart_device(never_called),
      rcap16_first(false),
      spc4_or_above(false),
      rsoc_queried(false),
      rsoc_sup(SC_SUPPORT_UNKNOWN),
      logsense_sup(SC_SUPPORT_UNKNOWN),
      logsense_spc_sup(SC_SUPPORT_UNKNOWN),
      rcap16_sup(SC_SUPPORT_UNKNOWN),
      rdefect10_sup(SC_SUPPORT_UNKNOWN),
      rdefect12_sup(SC_SUPPORT_UNKNOWN)
    { hide_scsi(false); }

private:
  bool rcap16_first;
  bool spc4_or_above;

  bool rsoc_queried;
  scsi_cmd_support rsoc_sup;
  scsi_cmd_support logsense_sup;
  scsi_cmd_support logsense_spc_sup;
  scsi_cmd_support rcap16_sup;
  scsi_cmd_support rdefect10_sup;
  scsi_cmd_support rdefect12_sup;
};


/////////////////////////////////////////////////////////////////////////////
// NVMe specific interface

/// NVMe pass through input parameters
struct nvme_cmd_in
{
  unsigned char opcode; ///< Opcode (CDW0 07:00)
  unsigned nsid; ///< Namespace ID
  unsigned cdw10, cdw11, cdw12, cdw13, cdw14, cdw15; ///< Cmd specific

  void * buffer; ///< Pointer to data buffer
  unsigned size; ///< Size of buffer

  enum {
    no_data = 0x0, data_out = 0x1, data_in = 0x2, data_io = 0x3
  };

  /// Get I/O direction from opcode
  unsigned char direction() const
    { return (opcode & 0x3); }

  // Prepare for DATA IN command
  void set_data_in(unsigned char op, void * buf, unsigned sz)
    {
      opcode = op;
      if (direction() != data_in)
        throw std::logic_error("invalid opcode for DATA IN");
      buffer = buf;
      size = sz;
    }

  nvme_cmd_in()
    : opcode(0), nsid(0),
      cdw10(0), cdw11(0), cdw12(0), cdw13(0), cdw14(0), cdw15(0),
      buffer(0), size(0)
    { }
};

/// NVMe pass through output parameters
struct nvme_cmd_out
{
   unsigned result; ///< Command specific result (DW0)
   unsigned short status; ///< Status Field (DW3 31:17)
   bool status_valid; ///< true if status is valid

   nvme_cmd_out()
     : result(0), status(0), status_valid(false)
     { }
};

/// NVMe device access
class nvme_device
: virtual public /*extends*/ smart_device
{
public:
  /// NVMe pass through.
  /// Return false on error.
  virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) = 0;

  /// Get namespace id.
  unsigned get_nsid() const
    { return m_nsid; }

protected:
  /// Hide/unhide NVMe interface.
  void hide_nvme(bool hide = true)
    { m_nvme_ptr = (!hide ? this : 0); }

  /// Constructor requires namespace ID, registers device as NVMe.
  explicit nvme_device(unsigned nsid)
    : smart_device(never_called),
      m_nsid(nsid)
    { hide_nvme(false); }

  /// Set namespace id.
  /// Should be called in open() function if get_nsid() returns 0.
  void set_nsid(unsigned nsid)
    { m_nsid = nsid; }

  /// Set last error number and message if pass-through returns NVMe error status.
  /// Returns false always to allow use as a return expression.
  bool set_nvme_err(nvme_cmd_out & out, unsigned status, const char * msg = 0);

private:
  unsigned m_nsid;
};


/////////////////////////////////////////////////////////////////////////////
/// Smart pointer class for device pointers

template <class Dev>
class any_device_auto_ptr
{
public:
  typedef Dev device_type;

  /// Construct from optional pointer to device
  /// and optional pointer to base device.
  explicit any_device_auto_ptr(device_type * dev = 0,
                               smart_device * base_dev = 0)
    : m_dev(dev), m_base_dev(base_dev) { }

  /// Destructor deletes device object.
  ~any_device_auto_ptr()
    { reset(); }

  /// Assign a new pointer.
  /// Throws if a pointer is already assigned.
  void operator=(device_type * dev)
    {
      if (m_dev)
        fail();
      m_dev = dev;
    }

  /// Delete device object and clear the pointer.
  void reset()
    {
      if (m_dev) {
        if (m_base_dev && m_dev->owns(m_base_dev))
          m_dev->release(m_base_dev);
        delete m_dev;
        m_dev = 0;
      }
    }

  /// Return the pointer and release ownership.
  device_type * release()
    {
      device_type * dev = m_dev;
      m_dev = 0;
      return dev;
    }

  /// Replace the pointer.
  /// Used to call dev->autodetect_open().
  void replace(device_type * dev)
    { m_dev = dev; }

  /// Return the pointer.
  device_type * get() const
    { return m_dev; }

  /// Pointer dereferencing.
  device_type & operator*() const
    { return *m_dev; }

  /// Pointer dereferencing.
  device_type * operator->() const
    { return m_dev; }

  /// For (ptr != 0) check.
  operator bool() const
    { return !!m_dev; }

  /// For (ptr == 0) check.
  bool operator !() const
    { return !m_dev; }

private:
  device_type * m_dev;
  smart_device * m_base_dev;

  void fail() const
    { throw std::logic_error("any_device_auto_ptr: wrong usage"); }

  // Prevent copy/assignment
  any_device_auto_ptr(const any_device_auto_ptr<Dev> &);
  void operator=(const any_device_auto_ptr<Dev> &);
};

typedef any_device_auto_ptr<smart_device> smart_device_auto_ptr;
typedef any_device_auto_ptr<ata_device>   ata_device_auto_ptr;
typedef any_device_auto_ptr<scsi_device>  scsi_device_auto_ptr;
typedef any_device_auto_ptr<nvme_device>  nvme_device_auto_ptr;


/////////////////////////////////////////////////////////////////////////////
// smart_device_list

/// List of devices for DEVICESCAN
class smart_device_list
{
// Construction
public:
  smart_device_list()
    { }

  ~smart_device_list()
    {
      for (unsigned i = 0; i < m_list.size(); i++)
        delete m_list[i];
    }

// Attributes
  unsigned size() const
    { return m_list.size(); }

// Operations
  void clear()
    {
      for (unsigned i = 0; i < m_list.size(); i++)
        delete m_list[i];
      m_list.clear();
    }


  void push_back(smart_device * dev)
    { m_list.push_back(dev); }

  void push_back(smart_device_auto_ptr & dev)
    {
      m_list.push_back(dev.get());
      dev.release();
    }

  smart_device * at(unsigned i)
    { return m_list.at(i); }

  const smart_device * at(unsigned i) const
    { return m_list.at(i); }

  smart_device * release(unsigned i)
    {
      smart_device * dev = m_list.at(i);
      m_list[i] = 0;
      return dev;
    }

  void append(smart_device_list & devlist)
    {
      for (unsigned i = 0; i < devlist.size(); i++) {
        smart_device * dev = devlist.at(i);
        if (!dev)
          continue;
        push_back(dev);
        devlist.m_list.at(i) = 0;
      }
    }

// Implementation
private:
  std::vector<smart_device *> m_list;

  // Prevent copy/assignment
  smart_device_list(const smart_device_list &);
  void operator=(const smart_device_list &);
};


/// List of types for DEVICESCAN
typedef std::vector<std::string> smart_devtype_list;


/////////////////////////////////////////////////////////////////////////////
// smart_interface

/// The platform interface abstraction
class smart_interface
{
public:
  /// Initialize platform interface and register with smi().
  /// Must be implemented by platform module and register interface with set()
  static void init();

  smart_interface()
    { }

  virtual ~smart_interface()
    { }

  /// Return info string about build host and/or OS version.
  /// Default implementation returns SMARTMONTOOLS_BUILD_HOST.
  virtual std::string get_os_version_str();

  /// Return valid args for device type option/directive.
  /// Default implementation returns "ata, scsi, sat, usb*..."
  /// concatenated with result from get_valid_custom_dev_types_str().
  virtual std::string get_valid_dev_types_str();

  /// Return example string for program 'appname'.
  /// Default implementation returns empty string.
  /// For the migration of print_smartctl_examples(),
  /// function is allowed to print examples to stdout.
  /// TODO: Remove this hack.
  virtual std::string get_app_examples(const char * appname);

  /// Disable/Enable system auto standby/sleep mode.
  /// Return false if unsupported or if system is running
  /// on battery.
  /// Default implementation returns false.
  virtual bool disable_system_auto_standby(bool disable);


  ///////////////////////////////////////////////
  // Last error information

  /// Get last error info struct.
  const smart_device::error_info & get_err() const
    { return m_err; }
  /// Get last error number.
  int get_errno() const
    { return m_err.no; }
  /// Get last error message.
  const char * get_errmsg() const
    { return m_err.msg.c_str(); }

  /// Set last error number and message.
  /// Printf()-like formatting is supported.
  /// Returns false always to allow use as a return expression.
  bool set_err(int no, const char * msg, ...)
    __attribute_format_printf(3, 4);

  /// Set last error number and message.
  /// Printf()-like formatting is supported.
  /// Returns nullptr always to allow use as a return expression
  /// of any pointer type.
  // (Not using 'std::nullptr_t' because it requires <cstddef>)
  decltype(nullptr) set_err_np(int no, const char * msg, ...)
    __attribute_format_printf(3, 4);

  /// Set last error info struct.
  bool set_err(const smart_device::error_info & err)
    { m_err = err; return false; }

  /// Clear last error info.
  void clear_err()
    { m_err.clear(); }

  /// Set last error number and default message.
  /// Message is retrieved from get_msg_for_errno(no).
  bool set_err(int no);

  /// Set last error number and default message to any error_info.
  /// Used by set_err(no).
  bool set_err_var(smart_device::error_info * err, int no);

  /// Convert error number into message, used by set_err(no).
  /// Default implementation returns strerror(no).
  virtual const char * get_msg_for_errno(int no);

  ///////////////////////////////////////////////////////////////////////////
  // Device factory:

  /// Return device object for device 'name' with some 'type'.
  /// 'type' is 0 if not specified by user.
  /// Return 0 on error.
  /// Default implementation selects between ata, scsi and custom device.
  virtual smart_device * get_smart_device(const char * name, const char * type);

  /// Fill 'devlist' with devices of some 'type' with device names
  /// specified by some optional 'pattern'.
  /// Use platform specific default if 'type' is empty or 0.
  /// Return false on error.
  /// Default implementation returns false;
  virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
    const char * pattern = 0);

  /// Fill 'devlist' with devices of all 'types' with device names
  /// specified by some optional 'pattern'.
  /// Use platform specific default if 'types' is empty.
  /// Return false on error.
  /// Default implementation calls above function for all types
  /// and concatenates the results.
  virtual bool scan_smart_devices(smart_device_list & devlist,
    const smart_devtype_list & types, const char * pattern = 0);

  /// Return unique device name which is (only) suitable for duplicate detection.
  /// Default implementation resolves symlinks on POSIX systems and appends
  /// " [type]" if is_raid_dev_type(type)' returns true.
  virtual std::string get_unique_dev_name(const char * name, const char * type) const;

  /// Return true if the 'type' string contains a RAID drive number.
  /// Default implementation returns true if 'type' starts with '[^,]+,[0-9]'
  /// but not with 'sat,'.
  virtual bool is_raid_dev_type(const char * type) const;

protected:
  /// Return standard ATA device.
  virtual ata_device * get_ata_device(const char * name, const char * type) = 0;

  /// Return standard SCSI device.
  virtual scsi_device * get_scsi_device(const char * name, const char * type) = 0;

  /// Return standard NVMe device.
  /// Default implementation returns 0.
  virtual nvme_device * get_nvme_device(const char * name, const char * type,
    unsigned nsid);

  /// Autodetect device if no device type specified.
  virtual smart_device * autodetect_smart_device(const char * name) = 0;

  /// Return device for platform specific 'type'.
  /// Default implementation returns 0.
  virtual smart_device * get_custom_smart_device(const char * name, const char * type);

  /// Return valid 'type' args accepted by above.
  /// This is called in get_valid_dev_types_str().
  /// Default implementation returns empty string.
  virtual std::string get_valid_custom_dev_types_str();

  /// Return ATA->SCSI of NVMe->SCSI filter for a SAT, SNT or USB 'type'.
  /// Uses get_sat_device and get_snt_device.
  /// Return 0 and delete 'scsidev' on error.
  virtual smart_device * get_scsi_passthrough_device(const char * type, scsi_device * scsidev);

  /// Return ATA->SCSI filter for a SAT or USB 'type'.
  /// Device 'scsidev' is used for SCSI access.
  /// Return 0 and delete 'scsidev' on error.
  /// Override only if platform needs special handling.
  virtual ata_device * get_sat_device(const char * type, scsi_device * scsidev);
  //{ implemented in scsiata.cpp }

  /// Return NVMe->SCSI filter for a SNT or USB 'type'.
  /// Device 'scsidev' is used for SCSI access.
  /// Return 0 and delete 'scsidev' on error.
  /// Override only if platform needs special handling.
  virtual nvme_device * get_snt_device(const char * type, scsi_device * scsidev);
  //{ implemented in scsinvme.cpp }

  /// Return filter for Intelliprop controllers.
  virtual ata_device * get_intelliprop_device(const char * type, ata_device * atadev);
  //{ implemented in dev_intelliprop.cpp }

  /// Return JMB93x->ATA filter.
  /// Device 'smartdev' is used for ATA or SCSI R/W access.
  /// Return 0 and delete 'scsidev' on error.
  /// Override only if platform needs special handling.
  virtual ata_device * get_jmb39x_device(const char * type, smart_device * smartdev);
  //{ implemented in dev_jmb39x_raid.cpp }

public:
  /// Try to detect a SAT device behind a SCSI interface.
  /// Inquiry data can be passed if available.
  /// Return appropriate device if yes, otherwise 0.
  /// Override only if platform needs special handling.
  virtual ata_device * autodetect_sat_device(scsi_device * scsidev,
    const unsigned char * inqdata, unsigned inqsize);
  //{ implemented in scsiata.cpp }

  /// Get type name for USB device with known VENDOR:PRODUCT ID.
  /// Return name if device known and supported, otherwise 0.
  virtual const char * get_usb_dev_type_by_id(int vendor_id, int product_id,
                                              int version = -1);
  //{ implemented in scsiata.cpp }

protected:
  /// Set interface to use, must be called from init().
  static void set(smart_interface * intf)
    { s_instance = intf; }

// Implementation
private:
  smart_device::error_info m_err;

  friend smart_interface * smi(); // below
  static smart_interface * s_instance; ///< Pointer to the interface object.

  // Prevent copy/assignment
  smart_interface(const smart_interface &);
  void operator=(const smart_interface &);
};


/////////////////////////////////////////////////////////////////////////////
// smi()

/// Global access to the (usually singleton) smart_interface
inline smart_interface * smi()
  { return smart_interface::s_instance; }

/////////////////////////////////////////////////////////////////////////////

#endif // DEV_INTERFACE_H
