smartmontools SVN Rev 5409
Utility to control and monitor storage systems with "S.M.A.R.T."
dev_interface.cpp
Go to the documentation of this file.
1/*
2 * dev_interface.cpp
3 *
4 * Home page of code is: https://www.smartmontools.org
5 *
6 * Copyright (C) 2008-21 Christian Franke
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11#include "config.h"
12
13#include "dev_interface.h"
14#include "dev_tunnelled.h"
15#include "atacmds.h" // ATA_SMART_CMD/STATUS
16#include "scsicmds.h" // scsi_cmnd_io
17#include "utility.h"
18
19#include <errno.h>
20#include <stdarg.h>
21#include <stdlib.h> // realpath()
22#include <stdexcept>
23
24const char * dev_interface_cpp_cvsid = "$Id: dev_interface.cpp 5219 2021-06-04 16:39:50Z chrfranke $"
26
27/////////////////////////////////////////////////////////////////////////////
28// smart_device
29
31
32smart_device::smart_device(smart_interface * intf, const char * dev_name,
33 const char * dev_type, const char * req_type)
34: m_intf(intf), m_info(dev_name, dev_type, req_type),
35 m_ata_ptr(0), m_scsi_ptr(0), m_nvme_ptr(0)
36{
38}
39
41: m_intf(0), m_ata_ptr(0), m_scsi_ptr(0), m_nvme_ptr(0)
42{
43 throw std::logic_error("smart_device: wrong constructor called in implementation class");
44}
45
47{
49}
50
52{
53 if (get_errno() == ENOSYS)
54 return true;
55#ifdef ENOTSUP
56 if (get_errno() == ENOTSUP)
57 return true;
58#endif
59 return false;
60}
61
62bool smart_device::set_err(int no, const char * msg, ...)
63{
64 if (!msg)
65 return set_err(no);
66 m_err.no = no;
67 va_list ap; va_start(ap, msg);
68 m_err.msg = vstrprintf(msg, ap);
69 va_end(ap);
70 return false;
71}
72
74{
75 return smi()->set_err_var(&m_err, no);
76}
77
79{
80 open();
81 return this;
82}
83
85{
86 return false;
87}
88
89bool smart_device::owns(const smart_device * /*dev*/) const
90{
91 return false;
92}
93
95{
96}
97
98
99/////////////////////////////////////////////////////////////////////////////
100// ata_device
101
103: features_16(features, prev.features),
104 sector_count_16(sector_count, prev.sector_count),
105 lba_low_16(lba_low, prev.lba_low),
106 lba_mid_16(lba_mid, prev.lba_mid),
107 lba_high_16(lba_high, prev.lba_high),
108 lba_48( lba_low, lba_mid, lba_high,
109 prev.lba_low, prev.lba_mid, prev.lba_high)
110{
111}
112
114: sector_count_16(sector_count, prev.sector_count),
115 lba_low_16(lba_low, prev.lba_low),
116 lba_mid_16(lba_mid, prev.lba_mid),
117 lba_high_16(lba_high, prev.lba_high),
118 lba_48( lba_low, lba_mid, lba_high,
119 prev.lba_low, prev.lba_mid, prev.lba_high)
120{
121}
122
124: direction(no_data),
125 buffer(0),
126 size(0)
127{
128}
129
131{
132}
133
135{
136 ata_cmd_out dummy;
137 return ata_pass_through(in, dummy);
138}
139
141 unsigned flags, const char * type /* = 0 */)
142{
143 // Check DATA IN/OUT
144 switch (in.direction) {
145 case ata_cmd_in::no_data: break;
146 case ata_cmd_in::data_in: break;
147 case ata_cmd_in::data_out: break;
148 default:
149 return set_err(EINVAL, "Invalid data direction %d", (int)in.direction);
150 }
151
152 // Check buffer size
153 if (in.direction == ata_cmd_in::no_data) {
154 if (in.size)
155 return set_err(EINVAL, "Buffer size %u > 0 for NO DATA command", in.size);
156 }
157 else {
158 if (!in.buffer)
159 return set_err(EINVAL, "Buffer not set for DATA IN/OUT command");
160 unsigned count = (in.in_regs.prev.sector_count<<16)|in.in_regs.sector_count;
161 // TODO: Add check for sector count == 0
162 if (count * 512 != in.size)
163 return set_err(EINVAL, "Sector count %u does not match buffer size %u", count, in.size);
164 }
165
166 // Check features
167 const char * errmsg = 0;
169 errmsg = "DATA OUT ATA commands not implemented";
170 else if ( in.out_needed.is_set() && !(flags & supports_output_regs)
171 && !( in.in_regs.command == ATA_SMART_CMD
174 errmsg = "Read of ATA output registers not implemented";
175 else if (!(in.size == 0 || in.size == 512) && !(flags & supports_multi_sector))
176 errmsg = "Multi-sector ATA commands not implemented";
178 errmsg = "48-bit ATA commands not implemented";
179 else if (in.in_regs.is_real_48bit_cmd() && !(flags & supports_48bit))
180 errmsg = "48-bit ATA commands not fully implemented";
181
182 if (errmsg)
183 return set_err(ENOSYS, "%s%s%s%s", errmsg,
184 (type ? " [" : ""), (type ? type : ""), (type ? "]" : ""));
185
186 return true;
187}
188
190{
191 return false;
192}
193
194/////////////////////////////////////////////////////////////////////////////
195// scsi_device
196
198 const char * msg)
199{
200 // Provide sense buffer
201 unsigned char sense[32] = {0, };
202 iop->sensep = sense;
203 iop->max_sense_len = sizeof(sense);
205
206 // Run cmd
207 if (!scsi_pass_through(iop)) {
208 if (scsi_debugmode > 0)
209 pout("%sscsi_pass_through() failed, errno=%d [%s]\n",
210 msg, get_errno(), get_errmsg());
211 return false;
212 }
213
214 // Check sense
215 scsi_sense_disect sinfo;
216 scsi_do_sense_disect(iop, &sinfo);
217 int err = scsiSimpleSenseFilter(&sinfo);
218 if (err) {
219 if (scsi_debugmode > 0)
220 pout("%sscsi error: %s\n", msg, scsiErrString(err));
221 return set_err(EIO, "scsi error %s", scsiErrString(err));
222 }
223
224 return true;
225}
226
227/////////////////////////////////////////////////////////////////////////////
228// nvme_device
229
230bool nvme_device::set_nvme_err(nvme_cmd_out & out, unsigned status, const char * msg /* = 0 */)
231{
232 if (!status)
233 throw std::logic_error("nvme_device: set_nvme_err() called with status=0");
234
235 out.status = status;
236 out.status_valid = true;
237 return set_err(EIO, "%sNVMe Status 0x%02x", (msg ? msg : ""), status);
238}
239
240
241/////////////////////////////////////////////////////////////////////////////
242// tunnelled_device_base
243
245: smart_device(never_called),
246 m_tunnel_base_dev(tunnel_dev)
247{
248}
249
251{
252 delete m_tunnel_base_dev;
253}
254
256{
258}
259
261{
263 return set_err(ENOSYS);
264 if (!m_tunnel_base_dev->open())
266 return true;
267}
268
270{
272 return true;
273 if (!m_tunnel_base_dev->close())
275 return true;
276}
277
279{
280 return (m_tunnel_base_dev && (m_tunnel_base_dev == dev));
281}
282
284{
285 if (m_tunnel_base_dev == dev)
287}
288
289
290/////////////////////////////////////////////////////////////////////////////
291// smart_interface
292
293// Pointer to (usually singleton) interface object returned by ::smi()
295
297{
298 return SMARTMONTOOLS_BUILD_HOST;
299}
300
302{
303 // default
304 std::string s =
305 "ata, scsi[+TYPE], nvme[,NSID], sat[,auto][,N][+TYPE], usbcypress[,X], "
306 "usbjmicron[,p][,x][,N], usbprolific, usbsunplus, sntasmedia, sntjmicron[,NSID], "
307 "sntrealtek, intelliprop,N[+TYPE], jmb39x[-q],N[,sLBA][,force][+TYPE], "
308 "jms56x,N[,sLBA][,force][+TYPE]";
309 // append custom
310 std::string s2 = get_valid_custom_dev_types_str();
311 if (!s2.empty()) {
312 s += ", "; s += s2;
313 }
314 return s;
315}
316
317std::string smart_interface::get_app_examples(const char * /*appname*/)
318{
319 return "";
320}
321
323{
324 return set_err(ENOSYS);
325}
326
327bool smart_interface::set_err(int no, const char * msg, ...)
328{
329 if (!msg)
330 return set_err(no);
331 m_err.no = no;
332 va_list ap; va_start(ap, msg);
333 m_err.msg = vstrprintf(msg, ap);
334 va_end(ap);
335 return false;
336}
337
339{
340 return set_err_var(&m_err, no);
341}
342
344{
345 err->no = no;
346 err->msg = get_msg_for_errno(no);
347 if (err->msg.empty() && no != 0)
348 err->msg = strprintf("Unknown error %d", no);
349 return false;
350}
351
353{
354 return strerror(no);
355}
356
357std::string smart_interface::get_unique_dev_name(const char * name, const char * type) const
358{
359 std::string unique_name;
360#if defined(HAVE_UNISTD_H) && !defined(_WIN32) && !defined(__OS2__)
361 char * p = realpath(name, (char *)0); // nullptr requires POSIX.1.2008 compatibility
362 if (p) {
363 unique_name = p;
364 free(p);
365 }
366 else
367#endif
368 unique_name = name;
369
370 if (*type && is_raid_dev_type(type)) {
371 // -d TYPE options must match if RAID drive number is specified
372 unique_name += " ["; unique_name += type; unique_name += ']';
373 }
374 return unique_name;
375}
376
377bool smart_interface::is_raid_dev_type(const char * type) const
378{
379 if (!strchr(type, ','))
380 return false;
381 if (str_starts_with(type, "sat,"))
382 return false;
383 int i;
384 if (sscanf(type, "%*[^,],%d", &i) != 1)
385 return false;
386 return true;
387}
388
389
390/////////////////////////////////////////////////////////////////////////////
391// Default device factory
392
393smart_device * smart_interface::get_smart_device(const char * name, const char * type)
394{
395 clear_err();
396
397 // Call platform specific autodetection if no device type specified
398 smart_device * dev;
399 if (!type || !*type) {
400 dev = autodetect_smart_device(name);
401 if (!dev && !get_errno())
402 set_err(EINVAL, "Unable to detect device type");
403 return dev;
404 }
405
406 // First check for platform specific device types
407 dev = get_custom_smart_device(name, type);
408 if (dev || get_errno())
409 return dev;
410
411 if (!strcmp(type, "ata"))
412 dev = get_ata_device(name, type);
413 else if (!strcmp(type, "scsi"))
414 dev = get_scsi_device(name, type);
415
416 else if (str_starts_with(type, "nvme")) {
417 int n1 = -1, n2 = -1, len = strlen(type);
418 unsigned nsid = 0; // invalid namespace id -> use default
419 sscanf(type, "nvme%n,0x%x%n", &n1, &nsid, &n2);
420 if (!(n1 == len || n2 == len)) {
421 set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type);
422 return 0;
423 }
424 dev = get_nvme_device(name, type, nsid);
425 }
426 // TODO: Unify handling of '-d TYPE...+BASETYPE...'
427 else if ( (str_starts_with(type, "sat") && (!type[3] || strchr(",+", type[3])))
428 || str_starts_with(type, "scsi+")
429 || str_starts_with(type, "usb") ) {
430 // Split "sat...+base..." -> ("sat...", "base...")
431 unsigned satlen = strcspn(type, "+");
432 std::string sattype(type, satlen);
433 const char * basetype = (type[satlen] ? type+satlen+1 : "");
434 // Recurse to allocate base device, default is standard SCSI
435 if (!*basetype)
436 basetype = "scsi";
437 smart_device_auto_ptr basedev( get_smart_device(name, basetype) );
438 if (!basedev) {
439 set_err(EINVAL, "Type '%s+...': %s", sattype.c_str(), get_errmsg());
440 return 0;
441 }
442 // Result must be SCSI
443 if (!basedev->is_scsi()) {
444 set_err(EINVAL, "Type '%s+...': Device type '%s' is not SCSI", sattype.c_str(), basetype);
445 return 0;
446 }
447 // Attach SAT tunnel
448 return get_sat_device(sattype.c_str(), basedev.release()->to_scsi());
449 }
450
451 else if (str_starts_with(type, "snt")) {
452 smart_device_auto_ptr basedev( get_smart_device(name, "scsi") );
453 if (!basedev) {
454 set_err(EINVAL, "Type '%s': %s", type, get_errmsg());
455 return 0;
456 }
457
458 return get_snt_device(type, basedev.release()->to_scsi());
459 }
460
461 else if (str_starts_with(type, "jmb39x") || str_starts_with(type, "jms56x")) {
462 // Split "jmb39x...+base..." -> ("jmb39x...", "base...")
463 unsigned jmblen = strcspn(type, "+");
464 std::string jmbtype(type, jmblen);
465 const char * basetype = (type[jmblen] ? type+jmblen+1 : "");
466 // Recurse to allocate base device, default is standard SCSI
467 if (!*basetype)
468 basetype = "scsi";
469 smart_device_auto_ptr basedev( get_smart_device(name, basetype) );
470 if (!basedev) {
471 set_err(EINVAL, "Type '%s+...': %s", jmbtype.c_str(), get_errmsg());
472 return 0;
473 }
474 // Attach JMB39x tunnel
475 return get_jmb39x_device(jmbtype.c_str(), basedev.release());
476 }
477
478 else if (str_starts_with(type, "intelliprop")) {
479 // Split "intelliprop...+base..." -> ("intelliprop...", "base...")
480 unsigned itllen = strcspn(type, "+");
481 std::string itltype(type, itllen);
482 const char * basetype = (type[itllen] ? type+itllen+1 : "");
483 // Recurse to allocate base device, default is standard ATA
484 if (!*basetype)
485 basetype = "ata";
486 smart_device_auto_ptr basedev( get_smart_device(name, basetype) );
487 if (!basedev) {
488 set_err(EINVAL, "Type '%s': %s", type, get_errmsg());
489 return 0;
490 }
491 // Result must be ATA
492 if (!basedev->is_ata()) {
493 set_err(EINVAL, "Type '%s': Device type '%s' is not ATA", type, basetype);
494 return 0;
495 }
496 return get_intelliprop_device(itltype.c_str(), basedev.release()->to_ata());
497 }
498
499 else {
500 set_err(EINVAL, "Unknown device type '%s'", type);
501 return 0;
502 }
503 if (!dev && !get_errno())
504 set_err(EINVAL, "Not a device of type '%s'", type);
505 return dev;
506}
507
509 const char * /*type*/, const char * /*pattern*/ /* = 0 */)
510{
511 return set_err(ENOSYS);
512}
513
515 const smart_devtype_list & types, const char * pattern /* = 0 */)
516{
517 unsigned n = types.size();
518 if (n == 0)
519 return scan_smart_devices(devlist, (const char *)0, pattern);
520 if (n == 1)
521 return scan_smart_devices(devlist, types.front().c_str(), pattern);
522
523 for (unsigned i = 0; i < n; i++) {
524 smart_device_list tmplist;
525 if (!scan_smart_devices(tmplist, types[i].c_str(), pattern))
526 return false;
527 devlist.append(tmplist);
528 }
529
530 return true;
531}
532
533nvme_device * smart_interface::get_nvme_device(const char * /*name*/, const char * /*type*/, unsigned /*nsid*/)
534{
535 set_err(ENOSYS, "NVMe devices are not supported in this version of smartmontools");
536 return 0;
537}
538
539smart_device * smart_interface::get_custom_smart_device(const char * /*name*/, const char * /*type*/)
540{
541 return 0;
542}
543
545{
546 return "";
547}
548
550{
551 if (!strncmp(type, "snt", 3)) {
552 return get_snt_device(type, scsidev);
553 }
554
555 return get_sat_device(type, scsidev);
556}
#define ATA_SMART_STATUS
Definition: atacmds.h:91
#define ATA_SMART_CMD
Definition: atacmds.h:56
Smart pointer class for device pointers.
device_type * release()
Return the pointer and release ownership.
@ supports_output_regs
@ supports_48bit_hi_null
@ supports_multi_sector
@ supports_smart_status
virtual bool ata_identify_is_cached() const
Return true if OS caches ATA identify sector.
bool ata_cmd_is_supported(const ata_cmd_in &in, unsigned flags, const char *type=0)
Check command input parameters.
virtual bool ata_pass_through(const ata_cmd_in &in, ata_cmd_out &out)=0
ATA pass through.
NVMe device access.
bool set_nvme_err(nvme_cmd_out &out, unsigned status, const char *msg=0)
Set last error number and message if pass-through returns NVMe error status.
SCSI device access.
bool scsi_pass_through_and_check(scsi_cmnd_io *iop, const char *msg="")
virtual bool scsi_pass_through(scsi_cmnd_io *iop)=0
SCSI pass through.
List of devices for DEVICESCAN.
void append(smart_device_list &devlist)
Base class for all devices.
Definition: dev_interface.h:33
int get_errno() const
Get last error number.
const error_info & get_err() const
Get last error info struct.
error_info m_err
virtual void release(const smart_device *dev)
Release ownership of other device.
virtual bool is_powered_down()
Early test if device is powered up or down.
smart_interface * smi()
Get interface which produced this object.
const char * get_errmsg() const
Get last error message.
virtual bool close()=0
Close device, return false on error.
bool set_err(int no, const char *msg,...) __attribute_format_printf(3
Set last error number and message.
do_not_use_in_implementation_classes
Dummy enum for dummy constructor.
Definition: dev_interface.h:72
virtual smart_device * autodetect_open()
Open device with autodetection support.
virtual bool owns(const smart_device *dev) const
Return true if other device is owned by this device.
virtual bool is_syscall_unsup() const
Return true if last error indicates an unsupported system call.
virtual bool is_open() const =0
Return true if device is open.
static int s_num_objects
smart_device(smart_interface *intf, const char *dev_name, const char *dev_type, const char *req_type)
Constructor to init interface and device info.
virtual bool open()=0
Open device, return false on error.
virtual ~smart_device()
The platform interface abstraction.
virtual std::string get_app_examples(const char *appname)
Return example string for program 'appname'.
virtual const char * get_msg_for_errno(int no)
Convert error number into message, used by set_err(no).
void clear_err()
Clear last error info.
virtual nvme_device * get_snt_device(const char *type, scsi_device *scsidev)
Return NVMe->SCSI filter for a SNT or USB 'type'.
Definition: scsinvme.cpp:396
static smart_interface * s_instance
Pointer to the interface object.
virtual ata_device * get_jmb39x_device(const char *type, smart_device *smartdev)
Return JMB93x->ATA filter.
virtual ata_device * get_sat_device(const char *type, scsi_device *scsidev)
Return ATA->SCSI filter for a SAT or USB 'type'.
Definition: scsiata.cpp:1389
virtual std::string get_unique_dev_name(const char *name, const char *type) const
Return unique device name which is (only) suitable for duplicate detection.
virtual smart_device * get_smart_device(const char *name, const char *type)
Return device object for device 'name' with some 'type'.
smart_device::error_info m_err
virtual ata_device * get_ata_device(const char *name, const char *type)=0
Return standard ATA device.
const char * get_errmsg() const
Get last error message.
virtual ata_device * get_intelliprop_device(const char *type, ata_device *atadev)
Return filter for Intelliprop controllers.
int get_errno() const
Get last error number.
virtual smart_device * get_custom_smart_device(const char *name, const char *type)
Return device for platform specific 'type'.
virtual std::string get_os_version_str()
Return info string about build host and/or OS version.
virtual std::string get_valid_custom_dev_types_str()
Return valid 'type' args accepted by above.
virtual bool disable_system_auto_standby(bool disable)
Disable/Enable system auto standby/sleep mode.
virtual bool scan_smart_devices(smart_device_list &devlist, const char *type, const char *pattern=0)
Fill 'devlist' with devices of some 'type' with device names specified by some optional 'pattern'.
virtual smart_device * autodetect_smart_device(const char *name)=0
Autodetect device if no device type specified.
virtual std::string get_valid_dev_types_str()
Return valid args for device type option/directive.
virtual scsi_device * get_scsi_device(const char *name, const char *type)=0
Return standard SCSI device.
virtual smart_device * get_scsi_passthrough_device(const char *type, scsi_device *scsidev)
Return ATA->SCSI of NVMe->SCSI filter for a SAT, SNT or USB 'type'.
virtual nvme_device * get_nvme_device(const char *name, const char *type, unsigned nsid)
Return standard NVMe device.
bool set_err_var(smart_device::error_info *err, int no)
Set last error number and default message to any error_info.
bool set_err(int no, const char *msg,...) __attribute_format_printf(3
Set last error number and message.
virtual bool is_raid_dev_type(const char *type) const
Return true if the 'type' string contains a RAID drive number.
virtual bool open() override
Open device, return false on error.
virtual bool owns(const smart_device *dev) const override
Return true if other device is owned by this device.
virtual void release(const smart_device *dev) override
Release ownership of other device.
smart_device * m_tunnel_base_dev
Definition: dev_tunnelled.h:43
virtual ~tunnelled_device_base()
virtual bool is_open() const override
Return true if device is open.
virtual bool close() override
Close device, return false on error.
tunnelled_device_base(smart_device *tunnel_dev)
const char * dev_interface_cpp_cvsid
#define DEV_INTERFACE_H_CVSID
Definition: dev_interface.h:14
std::vector< std::string > smart_devtype_list
List of types for DEVICESCAN.
u16 flags
Definition: megaraid.h:14
u32 count
Definition: megaraid.h:1
ptr_t buffer
Definition: megaraid.h:3
u16 s[6]
Definition: megaraid.h:18
u32 size
Definition: megaraid.h:0
uint32_t nsid
#define ENOTSUP
Definition: os_linux.cpp:87
int scsiSimpleSenseFilter(const struct scsi_sense_disect *sinfo)
Definition: scsicmds.cpp:347
void scsi_do_sense_disect(const struct scsi_cmnd_io *io_buf, struct scsi_sense_disect *out)
Definition: scsicmds.cpp:325
const char * scsiErrString(int scsiErr)
Definition: scsicmds.cpp:390
unsigned char scsi_debugmode
Definition: scsicmds.cpp:45
#define SCSI_TIMEOUT_DEFAULT
Definition: scsicmds.h:372
const char const char va_list ap
Definition: smartctl.cpp:1316
void pout(const char *fmt,...)
Definition: smartd.cpp:1326
ATA pass through input parameters.
enum ata_cmd_in::@29 direction
I/O direction.
void * buffer
Pointer to data buffer.
ata_in_regs_48bit in_regs
Input registers.
unsigned size
Size of buffer.
ata_out_regs_flags out_needed
True if output register value needed.
ATA pass through output parameters.
bool is_48bit_cmd() const
Return true if 48-bit command.
bool is_real_48bit_cmd() const
Return true if 48-bit command with any nonzero high byte.
ata_in_regs prev
"previous content"
ata_register sector_count
ata_register features
ata_register command
bool is_set() const
Return true if any flag is set.
NVMe pass through output parameters.
bool status_valid
true if status is valid
unsigned short status
Status Field (DW3 31:17)
uint8_t * sensep
Definition: scsicmds.h:120
size_t max_sense_len
Definition: scsicmds.h:122
unsigned timeout
Definition: scsicmds.h:123
Error (number,message) pair.
Definition: dev_interface.h:52
std::string msg
Error message.
Definition: dev_interface.h:61
int no
Error number.
Definition: dev_interface.h:60
std::string strprintf(const char *fmt,...)
Definition: utility.cpp:780
std::string std::string vstrprintf(const char *fmt, va_list ap)
bool str_starts_with(const char *str, const char *prefix)
Definition: utility.h:51