summaryrefslogtreecommitdiffstats
path: root/os_darwin
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--os_darwin.cpp782
-rw-r--r--os_darwin.h80
-rw-r--r--os_darwin/com.smartmontools.smartd.plist.in20
-rw-r--r--os_darwin/pkg/Distribution.in21
-rw-r--r--os_darwin/pkg/PackageInfo.in6
-rw-r--r--os_darwin/pkg/installer/README.html26
-rwxr-xr-xos_darwin/pkg/root/usr/local/sbin/smart-pkg-uninstall40
7 files changed, 975 insertions, 0 deletions
diff --git a/os_darwin.cpp b/os_darwin.cpp
new file mode 100644
index 0000000..74960b3
--- /dev/null
+++ b/os_darwin.cpp
@@ -0,0 +1,782 @@
+/*
+ * os_darwin.cpp
+ *
+ * Home page of code is: https://www.smartmontools.org
+ *
+ * Copyright (C) 2004-8 Geoffrey Keating <geoffk@geoffk.org>
+ * Copyright (C) 2014 Alex Samorukov <samm@os2.kiev.ua>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <mach/mach.h>
+#include <mach/mach_error.h>
+#include <mach/mach_init.h>
+#include <sys/utsname.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOReturn.h>
+#include <IOKit/IOBSD.h>
+#include <IOKit/storage/IOBlockStorageDevice.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <IOKit/storage/IOMedia.h>
+#include <IOKit/storage/ata/IOATAStorageDefines.h>
+#include <IOKit/storage/ata/ATASMARTLib.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "config.h"
+
+#include "atacmds.h"
+#include "scsicmds.h"
+#include "nvmecmds.h"
+#include "utility.h"
+#include "os_darwin.h"
+#include "dev_interface.h"
+
+#define ARGUSED(x) ((void)(x))
+// Needed by '-V' option (CVS versioning) of smartd/smartctl
+const char *os_darwin_cpp_cvsid="$Id: os_darwin.cpp 5209 2021-02-14 18:02:51Z samm2 $" \
+ATACMDS_H_CVSID CONFIG_H_CVSID OS_DARWIN_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID;
+
+// examples for smartctl
+static const char smartctl_examples[] =
+ "=================================================== SMARTCTL EXAMPLES =====\n\n"
+ " smartctl -a disk0 (Prints all SMART information)\n\n"
+ " smartctl -t long /dev/disk0 (Executes extended disk self-test)\n\n"
+ " smartctl --smart=on --saveauto=on /dev/rdisk0 (Enables SMART on first disk)\n\n"
+ " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/disk0\n"
+ " (Prints Self-Test & Attribute errors)\n\n"
+ " smartctl -a IOService:/MacRISC2PE/pci@f4000000/AppleMacRiscPCI/ata-6@D/AppleKauaiATA/ATADeviceNub@0/IOATABlockStorageDriver/IOATABlockStorageDevice\n"
+ " (You can use IOService: ...)\n\n"
+ " smartctl -c IODeviceTree:/pci@f4000000/ata-6@D/@0:0\n"
+ " (... Or IODeviceTree:)\n"
+ ;
+
+
+// Information that we keep about each device.
+
+static struct {
+ io_object_t ioob;
+ IOCFPlugInInterface **plugin;
+ IOATASMARTInterface **smartIf; // ATA devices
+ IONVMeSMARTInterface **smartIfNVMe;
+} devices[20];
+
+const char * dev_darwin_cpp_cvsid = "$Id: os_darwin.cpp 5209 2021-02-14 18:02:51Z samm2 $"
+ DEV_INTERFACE_H_CVSID;
+
+/////////////////////////////////////////////////////////////////////////////
+
+namespace os { // No need to publish anything, name provided for Doxygen
+
+/////////////////////////////////////////////////////////////////////////////
+/// Implement shared open/close routines with old functions.
+
+class darwin_smart_device
+: virtual public /*implements*/ smart_device
+{
+public:
+ explicit darwin_smart_device(const char * mode)
+ : smart_device(never_called),
+ m_fd(-1), m_mode(mode) { }
+
+ virtual ~darwin_smart_device();
+
+ virtual bool is_open() const override;
+
+ virtual bool open() override;
+
+ virtual bool close() override;
+
+protected:
+ /// Return filedesc for derived classes.
+ int get_fd() const
+ { return m_fd; }
+
+private:
+ int m_fd; ///< filedesc, -1 if not open.
+ const char * m_mode; ///< Mode string for deviceopen().
+};
+
+
+darwin_smart_device::~darwin_smart_device()
+{
+ if (m_fd >= 0)
+ darwin_smart_device::close();
+}
+
+bool darwin_smart_device::is_open() const
+{
+ return (m_fd >= 0);
+}
+
+// Determine whether 'dev' is a SMART-capable device.
+static bool is_smart_capable (io_object_t dev, const char * type) {
+ CFTypeRef smartCapableKey = NULL;
+ CFDictionaryRef diskChars;
+
+ // If the device has kIOPropertySMARTCapableKey, then it's capable,
+ // no matter what it looks like.
+ if (!strcmp("ATA", type)) {
+ smartCapableKey = IORegistryEntryCreateCFProperty
+ (dev, CFSTR (kIOPropertySMARTCapableKey),
+ kCFAllocatorDefault, 0);
+ }
+
+ else if (!strcmp("NVME", type)) {
+ smartCapableKey = IORegistryEntryCreateCFProperty
+ (dev, CFSTR (kIOPropertyNVMeSMARTCapableKey),
+ kCFAllocatorDefault, 0);
+ }
+
+ if (smartCapableKey)
+ {
+ CFRelease (smartCapableKey);
+ return true;
+ }
+
+ // If it's an kIOATABlockStorageDeviceClass then we're successful
+ // only if its ATA features indicate it supports SMART.
+ // This will be broken for NVMe, however it is not needed
+ if (IOObjectConformsTo (dev, kIOATABlockStorageDeviceClass)
+ && (diskChars = (CFDictionaryRef)IORegistryEntryCreateCFProperty
+ (dev, CFSTR (kIOPropertyDeviceCharacteristicsKey),
+ kCFAllocatorDefault, kNilOptions)) != NULL)
+ {
+ CFNumberRef diskFeatures = NULL;
+ UInt32 ataFeatures = 0;
+
+ if (CFDictionaryGetValueIfPresent (diskChars, CFSTR ("ATA Features"),
+ (const void **)&diskFeatures))
+ CFNumberGetValue (diskFeatures, kCFNumberLongType,
+ &ataFeatures);
+ CFRelease (diskChars);
+ if (diskFeatures)
+ CFRelease (diskFeatures);
+
+ return (ataFeatures & kIOATAFeatureSMART) != 0;
+ }
+ return false;
+}
+
+bool darwin_smart_device::open()
+{
+ // Acceptable device names are:
+ // /dev/disk*
+ // /dev/rdisk*
+ // disk*
+ // IOService:*
+ // IODeviceTree:*
+ size_t devnum;
+ const char *devname;
+ io_object_t disk;
+ const char *pathname = get_dev_name();
+ char *type = const_cast<char*>(m_mode);
+
+ if (!(strcmp("ATA", type) || strcmp("NVME", type)))
+ {
+ set_err (EINVAL);
+ return false;
+ }
+
+ // Find a free device number.
+ for (devnum = 0; devnum < sizeof (devices) / sizeof (devices[0]); devnum++)
+ if (! devices[devnum].ioob)
+ break;
+ if (devnum == sizeof (devices) / sizeof (devices[0]))
+ {
+ set_err (EMFILE);
+ return false;
+ }
+
+ devname = NULL;
+ if (strncmp (pathname, "/dev/rdisk", 10) == 0)
+ devname = pathname + 6;
+ else if (strncmp (pathname, "/dev/disk", 9) == 0)
+ devname = pathname + 5;
+ else if (strncmp (pathname, "disk", 4) == 0)
+ // allow user to just say 'disk0'
+ devname = pathname;
+
+ // Find the device. This part should be the same for the NVMe and ATA
+ if (devname)
+ {
+ CFMutableDictionaryRef matcher;
+ matcher = IOBSDNameMatching (kIOMasterPortDefault, 0, devname);
+ disk = IOServiceGetMatchingService (kIOMasterPortDefault, matcher);
+ }
+ else
+ {
+ disk = IORegistryEntryFromPath (kIOMasterPortDefault, pathname);
+ }
+ if (! disk)
+ {
+ set_err(ENOENT);
+ return false;
+ }
+ // Find a SMART-capable driver which is a parent of this device.
+ while (! is_smart_capable (disk, type))
+ {
+ IOReturn err;
+ io_object_t prevdisk = disk;
+
+ // Find this device's parent and try again.
+ err = IORegistryEntryGetParentEntry (disk, kIOServicePlane, &disk);
+ if (err != kIOReturnSuccess || ! disk)
+ {
+ set_err(ENODEV);
+ IOObjectRelease (prevdisk);
+ return false;
+ }
+ }
+
+ devices[devnum].ioob = disk;
+
+ {
+ SInt32 dummy;
+
+ devices[devnum].plugin = NULL;
+ devices[devnum].smartIf = NULL;
+ devices[devnum].smartIfNVMe = NULL;
+
+ CFUUIDRef pluginType = NULL;
+ CFUUIDRef smartInterfaceId = NULL;
+ void ** SMARTptr = NULL;
+
+ if (!strcmp("ATA", type)) {
+ pluginType = kIOATASMARTUserClientTypeID;
+ smartInterfaceId = kIOATASMARTInterfaceID;
+ SMARTptr = (void **)&devices[devnum].smartIf;
+ }
+ else if (!strcmp("NVME", type)) {
+ pluginType = kIONVMeSMARTUserClientTypeID;
+ smartInterfaceId = kIONVMeSMARTInterfaceID;
+ SMARTptr = (void **)&devices[devnum].smartIfNVMe;
+ }
+
+ // Create an interface to the ATA SMART library.
+ if (IOCreatePlugInInterfaceForService (disk,
+ pluginType,
+ kIOCFPlugInInterfaceID,
+ &devices[devnum].plugin,
+ &dummy) == kIOReturnSuccess)
+ (*devices[devnum].plugin)->QueryInterface
+ (devices[devnum].plugin,
+ CFUUIDGetUUIDBytes ( smartInterfaceId),
+ SMARTptr);
+ else
+ return set_err(ENOSYS, "IOCreatePlugInInterfaceForService failed");
+ }
+
+
+ m_fd = devnum;
+ if (m_fd < 0) {
+ set_err((errno==ENOENT || errno==ENOTDIR) ? ENODEV : errno);
+ return false;
+ }
+ return true;
+}
+
+bool darwin_smart_device::close()
+{
+ int fd = m_fd; m_fd = -1;
+ if (devices[fd].smartIf)
+ (*devices[fd].smartIf)->Release (devices[fd].smartIf);
+ if (devices[fd].smartIfNVMe)
+ (*devices[fd].smartIfNVMe)->Release (devices[fd].smartIfNVMe);
+ if (devices[fd].plugin)
+ IODestroyPlugInInterface (devices[fd].plugin);
+ IOObjectRelease (devices[fd].ioob);
+ devices[fd].ioob = MACH_PORT_NULL;
+ return true;
+}
+
+// makes a list of ATA or SCSI devices for the DEVICESCAN directive of
+// smartd. Returns number N of devices, or -1 if out of
+// memory. Allocates N+1 arrays: one of N pointers (devlist); the
+// other N arrays each contain null-terminated character strings. In
+// the case N==0, no arrays are allocated because the array of 0
+// pointers has zero length, equivalent to calling malloc(0).
+static int make_device_names (char*** devlist, const char* name) {
+ IOReturn err;
+ io_iterator_t i;
+ io_object_t device = MACH_PORT_NULL;
+ int result;
+ int index;
+
+ if (!(strcmp("ATA", name) || strcmp("NVME", name))) {
+ return 0;
+ }
+
+ err = IOServiceGetMatchingServices
+ (kIOMasterPortDefault, IOServiceMatching (kIOBlockStorageDeviceClass), &i);
+ if (err != kIOReturnSuccess)
+ return -1;
+
+ // Count the devices.
+ result = 0;
+ while ((device = IOIteratorNext (i)) != MACH_PORT_NULL) {
+ if (is_smart_capable (device, name))
+ result++;
+ IOObjectRelease (device);
+ }
+
+ // Create an array of service names.
+ IOIteratorReset (i);
+ if (! result)
+ goto error;
+ *devlist = (char**)calloc (result, sizeof (char *));
+ index = 0;
+ while ((device = IOIteratorNext (i)) != MACH_PORT_NULL) {
+ if (is_smart_capable (device, name))
+ {
+ io_string_t devName;
+ IORegistryEntryGetPath(device, kIOServicePlane, devName);
+ (*devlist)[index] = strdup (devName);
+ if (! (*devlist)[index])
+ goto error;
+ index++;
+ }
+ IOObjectRelease (device);
+ }
+
+ IOObjectRelease (i);
+ return result;
+
+ error:
+ if (device != MACH_PORT_NULL)
+ IOObjectRelease (device);
+ IOObjectRelease (i);
+ if (*devlist)
+ {
+ for (index = 0; index < result; index++)
+ if ((*devlist)[index])
+ free ((*devlist)[index]);
+ free (*devlist);
+ }
+ if(!result) // no devs found
+ return 0;
+
+ return -1;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/// Implement standard ATA support
+
+class darwin_ata_device
+: public /*implements*/ ata_device,
+ public /*extends*/ darwin_smart_device
+{
+public:
+ darwin_ata_device(smart_interface * intf, const char * dev_name, const char * req_type);
+ virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) override;
+
+protected:
+ // virtual int ata_command_interface(smart_command_set command, int select, char * data);
+};
+
+darwin_ata_device::darwin_ata_device(smart_interface * intf, const char * dev_name, const char * req_type)
+: smart_device(intf, dev_name, "ata", req_type),
+ darwin_smart_device("ATA")
+{
+}
+
+bool darwin_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out)
+{
+ if (!ata_cmd_is_ok(in,
+ true, // data_out_support
+ true, // multi_sector_support
+ false) // not supported by API
+ )
+ return false;
+
+ int select = 0;
+ char * data = (char *)in.buffer;
+ int fd = get_fd();
+ IOATASMARTInterface **ifp = devices[fd].smartIf;
+ IOATASMARTInterface *smartIf;
+ io_object_t disk = devices[fd].ioob;
+ IOReturn err;
+ int timeoutCount = 5;
+ int rc = 0;
+
+ if (! ifp)
+ return false;
+ smartIf = *ifp;
+ clear_err(); errno = 0;
+ do {
+ switch (in.in_regs.command) {
+ case ATA_IDENTIFY_DEVICE:
+ {
+ UInt32 dummy;
+ err = smartIf->GetATAIdentifyData (ifp, data, 512, &dummy);
+ if (err != kIOReturnSuccess && err != kIOReturnTimeout
+ && err != kIOReturnNotResponding)
+ printf ("identify failed: %#x\n", (unsigned) rc);
+ if (err == kIOReturnSuccess && isbigendian())
+ {
+ int i;
+ /* The system has already byte-swapped, undo it. */
+ for (i = 0; i < 256; i+=2)
+ swap2 (data + i);
+ }
+ }
+ break;
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ case ATA_CHECK_POWER_MODE:
+ errno = ENOTSUP;
+ err = -1;
+ break;
+ case ATA_SET_FEATURES:
+ switch(in.in_regs.features) {
+ case ATA_ENABLE_APM:
+ if (in.in_regs.sector_count) {
+ int l = (int) in.in_regs.sector_count;
+ CFNumberRef cfLevel = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &l);
+ kern_return_t r = IORegistryEntrySetCFProperty(disk, CFSTR("APM Level"), cfLevel);
+ CFRelease(cfLevel);
+ if (r) {
+ switch(r) {
+ case kIOReturnNotPrivileged:
+ return set_err(ENOSYS, "Use superuser to manage APM");
+ break;
+ case kIOReturnUnsupported:
+ return set_err(ENOSYS, "APM not supported");
+ break;
+ default:
+ return set_err(ENOSYS, "APM error: %u", r);
+ }
+ }
+ break;
+ }
+ default:
+ return set_err(ENOSYS, "Unsupported ATA feature");
+ }
+ break;
+ case ATA_SMART_CMD:
+ switch (in.in_regs.features) {
+ case ATA_SMART_READ_VALUES:
+ err = smartIf->SMARTReadData (ifp, (ATASMARTData *)data);
+ break;
+ case ATA_SMART_READ_THRESHOLDS:
+ err = smartIf->SMARTReadDataThresholds (ifp,
+ (ATASMARTDataThresholds *)data);
+ break;
+ case ATA_SMART_READ_LOG_SECTOR:
+ err = smartIf->SMARTReadLogAtAddress (ifp, in.in_regs.lba_low, data, 512 * in.in_regs.sector_count);
+ break;
+ case ATA_SMART_WRITE_LOG_SECTOR:
+ err = smartIf->SMARTWriteLogAtAddress (ifp, in.in_regs.lba_low, data, 512 * in.in_regs.sector_count);
+ break;
+ case ATA_SMART_ENABLE:
+ case ATA_SMART_DISABLE:
+ err = smartIf->SMARTEnableDisableOperations (ifp, in.in_regs.features == ATA_SMART_ENABLE);
+ break;
+ case ATA_SMART_STATUS:
+ if (in.out_needed.lba_high) // statuscheck
+ {
+ Boolean is_failing;
+ err = smartIf->SMARTReturnStatus (ifp, &is_failing);
+ if (err == kIOReturnSuccess && is_failing) {
+ err = -1; // thresholds exceeded condition
+ out.out_regs.lba_high = 0x2c; out.out_regs.lba_mid = 0xf4;
+ }
+ else {
+ out.out_regs.lba_high = 0xc2; out.out_regs.lba_mid = 0x4f;
+ }
+ break;
+ }
+ else err = 0;
+ break;
+ case ATA_SMART_AUTOSAVE:
+ err = smartIf->SMARTEnableDisableAutosave (ifp,
+ (in.in_regs.sector_count == 241 ? true : false));
+ break;
+ case ATA_SMART_IMMEDIATE_OFFLINE:
+ select = in.in_regs.lba_low;
+ if (select != SHORT_SELF_TEST && select != EXTEND_SELF_TEST)
+ {
+ errno = EINVAL;
+ return set_err(ENOSYS, "Unsupported SMART self-test mode");
+ }
+ err = smartIf->SMARTExecuteOffLineImmediate (ifp,
+ select == EXTEND_SELF_TEST);
+ break;
+ case ATA_SMART_AUTO_OFFLINE:
+ return set_err(ENOSYS, "SMART command not supported");
+ default:
+ return set_err(ENOSYS, "Unknown SMART command");
+ }
+ break;
+ default:
+ return set_err(ENOSYS, "Non-SMART commands not implemented");
+ }
+ } while ((err == kIOReturnTimeout || err == kIOReturnNotResponding)
+ && timeoutCount-- > 0);
+ if (err == kIOReturnExclusiveAccess)
+ errno = EBUSY;
+ rc = err == kIOReturnSuccess ? 0 : -1;
+ if (rc < 0) {
+ if (!get_errno())
+ set_err(errno);
+ return false;
+ }
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/// Implement platform interface
+
+class darwin_smart_interface
+: public /*implements*/ smart_interface
+{
+public:
+ virtual std::string get_os_version_str() override;
+
+ virtual std::string get_app_examples(const char * appname) override;
+
+ virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
+ const char * pattern = 0) override;
+
+protected:
+ virtual ata_device * get_ata_device(const char * name, const char * type) override;
+
+ virtual scsi_device * get_scsi_device(const char * name, const char * type) override;
+
+ virtual nvme_device * get_nvme_device(const char * name, const char * type,
+ unsigned nsid) override;
+
+ virtual smart_device * autodetect_smart_device(const char * name) override;
+
+};
+
+/////////////////////////////////////////////////////////////////////////////
+/// NVMe support
+
+class darwin_nvme_device
+: public /*implements*/ nvme_device,
+ public /*extends*/ darwin_smart_device
+{
+public:
+ darwin_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid);
+
+ virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
+};
+
+darwin_nvme_device::darwin_nvme_device(smart_interface * intf, const char * dev_name,
+ const char * req_type, unsigned nsid)
+: smart_device(intf, dev_name, "nvme", req_type),
+ nvme_device(nsid),
+ darwin_smart_device("NVME")
+{
+}
+
+bool darwin_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+ ARGUSED(out);
+ int fd = get_fd();
+ IONVMeSMARTInterface **ifp = devices[fd].smartIfNVMe;
+ IONVMeSMARTInterface *smartIfNVMe ;
+ IOReturn err = 0;
+ unsigned int page = in.cdw10 & 0xff;
+
+ if (! ifp)
+ return false;
+ smartIfNVMe = *ifp;
+ // currently only GetIdentifyData and GetLogPage are supported
+ switch (in.opcode) {
+ case smartmontools::nvme_admin_identify:
+ err = smartIfNVMe->GetIdentifyData(ifp, (struct nvme_id_ctrl *) in.buffer, in.nsid);
+ if (err)
+ return set_err(ENOSYS, "GetIdentifyData failed: system=0x%x, sub=0x%x, code=%d",
+ err_get_system(err), err_get_sub(err), err_get_code(err));
+ break;
+ case smartmontools::nvme_admin_get_log_page:
+ err = smartIfNVMe->GetLogPage(ifp, in.buffer, page, in.size / 4 - 1);
+ if (err)
+ return set_err(ENOSYS, "GetLogPage failed: system=0x%x, sub=0x%x, code=%d",
+ err_get_system(err), err_get_sub(err), err_get_code(err));
+ break;
+ default:
+ return set_err(ENOSYS, "NVMe admin command 0x%02x is not supported", in.opcode);
+ }
+ return true;
+}
+//////////////////////////////////////////////////////////////////////
+
+std::string darwin_smart_interface::get_os_version_str()
+{
+ // now we are just getting darwin runtime version, to get OSX version more things needs to be done, see
+ // http://stackoverflow.com/questions/11072804/how-do-i-determine-the-os-version-at-runtime-in-os-x-or-ios-without-using-gesta
+ struct utsname osname;
+ uname(&osname);
+ return strprintf("%s %s %s", osname.sysname, osname.release, osname.machine);
+}
+
+std::string darwin_smart_interface::get_app_examples(const char * appname)
+{
+ if (!strcmp(appname, "smartctl"))
+ return smartctl_examples;
+ return ""; // ... so don't print again.
+}
+
+ata_device * darwin_smart_interface::get_ata_device(const char * name, const char * type)
+{
+ return new darwin_ata_device(this, name, type);
+}
+
+scsi_device * darwin_smart_interface::get_scsi_device(const char *, const char *)
+{
+ return 0; // scsi devices are not supported [yet]
+}
+
+nvme_device * darwin_smart_interface::get_nvme_device(const char * name, const char * type,
+ unsigned nsid)
+{
+ return new darwin_nvme_device(this, name, type, nsid);
+}
+
+smart_device * darwin_smart_interface::autodetect_smart_device(const char * name)
+{ // TODO - refactor as a function
+ // Acceptable device names are:
+ // /dev/disk*
+ // /dev/rdisk*
+ // disk*
+ // IOService:*
+ // IODeviceTree:*
+ const char *devname = NULL;
+ io_object_t disk;
+
+ if (strncmp (name, "/dev/rdisk", 10) == 0)
+ devname = name + 6;
+ else if (strncmp (name, "/dev/disk", 9) == 0)
+ devname = name + 5;
+ else if (strncmp (name, "disk", 4) == 0)
+ // allow user to just say 'disk0'
+ devname = name;
+ // Find the device. This part should be the same for the NVMe and ATA
+ if (devname) {
+ CFMutableDictionaryRef matcher;
+ matcher = IOBSDNameMatching (kIOMasterPortDefault, 0, devname);
+ disk = IOServiceGetMatchingService (kIOMasterPortDefault, matcher);
+ }
+ else {
+ disk = IORegistryEntryFromPath (kIOMasterPortDefault, name);
+ }
+ if (! disk) {
+ return 0;
+ }
+ io_registry_entry_t tmpdisk=disk;
+
+
+ while (! is_smart_capable (tmpdisk, "ATA"))
+ {
+ IOReturn err;
+ io_object_t prevdisk = tmpdisk;
+
+ // Find this device's parent and try again.
+ err = IORegistryEntryGetParentEntry (tmpdisk, kIOServicePlane, &tmpdisk);
+ if (err != kIOReturnSuccess || ! tmpdisk)
+ {
+ IOObjectRelease (prevdisk);
+ break;
+ }
+ }
+ if (tmpdisk)
+ return new darwin_ata_device(this, name, "");
+ tmpdisk=disk;
+ while (! is_smart_capable (tmpdisk, "NVME"))
+ {
+ IOReturn err;
+ io_object_t prevdisk = tmpdisk;
+
+ // Find this device's parent and try again.
+ err = IORegistryEntryGetParentEntry (tmpdisk, kIOServicePlane, &tmpdisk);
+ if (err != kIOReturnSuccess || ! tmpdisk)
+ {
+ IOObjectRelease (prevdisk);
+ break;
+ }
+ }
+ if (tmpdisk)
+ return new darwin_nvme_device(this, name, "", 0);
+
+ // try ATA as a last option, for compatibility
+ return new darwin_ata_device(this, name, "");
+}
+
+static void free_devnames(char * * devnames, int numdevs)
+{
+ for (int i = 0; i < numdevs; i++)
+ free(devnames[i]);
+ free(devnames);
+}
+
+bool darwin_smart_interface::scan_smart_devices(smart_device_list & devlist,
+ const char * type, const char * pattern /*= 0*/)
+{
+ if (pattern) {
+ set_err(EINVAL, "DEVICESCAN with pattern not implemented yet");
+ return false;
+ }
+
+ // Make namelists
+ char * * atanames = 0; int numata = 0;
+ if (!type || !strcmp(type, "ata")) {
+ numata = make_device_names(&atanames, "ATA");
+ if (numata < 0) {
+ set_err(ENOMEM);
+ return false;
+ }
+ }
+ char * * nvmenames = 0; int numnvme = 0;
+ if (
+#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL
+ !type ||
+#else
+ type &&
+#endif
+ !strcmp(type, "nvme")) {
+ numnvme = make_device_names(&nvmenames, "NVME");
+ if (numnvme < 0) {
+ set_err(ENOMEM);
+ return false;
+ }
+ }
+
+ // Add to devlist
+ int i;
+ if (!type)
+ type="";
+ for (i = 0; i < numata; i++) {
+ ata_device * atadev = get_ata_device(atanames[i], type);
+ if (atadev)
+ devlist.push_back(atadev);
+ }
+ free_devnames(atanames, numata);
+
+ for (i = 0; i < numnvme; i++) {
+ nvme_device * nvmedev = get_nvme_device(nvmenames[i], type, 0); // default nsid
+ if (nvmedev)
+ devlist.push_back(nvmedev);
+ }
+ free_devnames(nvmenames, numnvme);
+
+ return true;
+}
+
+} // namespace
+
+
+/////////////////////////////////////////////////////////////////////////////
+/// Initialize platform interface and register with smi()
+
+void smart_interface::init()
+{
+ static os::darwin_smart_interface the_interface;
+ smart_interface::set(&the_interface);
+}
diff --git a/os_darwin.h b/os_darwin.h
new file mode 100644
index 0000000..a6b31a9
--- /dev/null
+++ b/os_darwin.h
@@ -0,0 +1,80 @@
+/*
+ * os_generic.h
+ *
+ * Home page of code is: http://www.smartmontools.org
+ *
+ * Copyright (C) 2004-8 Geoff Keating <geoffk@geoffk.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef OS_DARWIN_H_
+#define OS_DARWIN_H_
+
+#define OS_DARWIN_H_CVSID "$Id: os_darwin.h 5073 2020-06-24 08:08:38Z samm2 $\n"
+
+#define kIOATABlockStorageDeviceClass "IOATABlockStorageDevice"
+
+// Isn't in 10.3.9?
+
+#ifndef kIOPropertySMARTCapableKey
+#define kIOPropertySMARTCapableKey "SMART Capable"
+#endif
+
+// NVMe definitions based on Xcode SDK, see NVMeSMARTLibExternal.h
+#define kIOPropertyNVMeSMARTCapableKey "NVMe SMART Capable"
+
+// Constant to init driver
+#define kIONVMeSMARTUserClientTypeID CFUUIDGetConstantUUIDWithBytes(NULL, \
+ 0xAA, 0x0F, 0xA6, 0xF9, 0xC2, 0xD6, 0x45, 0x7F, 0xB1, 0x0B, \
+ 0x59, 0xA1, 0x32, 0x53, 0x29, 0x2F)
+
+// Constant to use plugin interface
+#define kIONVMeSMARTInterfaceID CFUUIDGetConstantUUIDWithBytes(NULL, \
+ 0xcc, 0xd1, 0xdb, 0x19, 0xfd, 0x9a, 0x4d, 0xaf, 0xbf, 0x95, \
+ 0x12, 0x45, 0x4b, 0x23, 0xa, 0xb6)
+
+typedef struct IONVMeSMARTInterface
+{
+ IUNKNOWN_C_GUTS;
+
+ UInt16 version;
+ UInt16 revision;
+
+ // NVMe smart data, returns nvme_smart_log structure
+ IOReturn ( *SMARTReadData )( void * interface,
+ struct nvme_smart_log * NVMeSMARTData );
+
+ // NVMe IdentifyData, returns nvme_id_ctrl per namespace
+ IOReturn ( *GetIdentifyData )( void * interface,
+ struct nvme_id_ctrl * NVMeIdentifyControllerStruct,
+ unsigned int ns );
+ UInt64 reserved0;
+ UInt64 reserved1;
+
+ // NumDWords Number of dwords for log page data, zero based.
+ IOReturn ( *GetLogPage )( void * interface, void * data, unsigned int logPageId, unsigned int numDWords);
+
+ UInt64 reserved2;
+ UInt64 reserved3;
+ UInt64 reserved4;
+ UInt64 reserved5;
+ UInt64 reserved6;
+ UInt64 reserved7;
+ UInt64 reserved8;
+ UInt64 reserved9;
+ UInt64 reserved10;
+ UInt64 reserved11;
+ UInt64 reserved12;
+ UInt64 reserved13;
+ UInt64 reserved14;
+ UInt64 reserved15;
+ UInt64 reserved16;
+ UInt64 reserved17;
+ UInt64 reserved18;
+ UInt64 reserved19;
+
+} IONVMeSMARTInterface;
+
+
+#endif /* OS_DARWIN_H_ */
diff --git a/os_darwin/com.smartmontools.smartd.plist.in b/os_darwin/com.smartmontools.smartd.plist.in
new file mode 100644
index 0000000..c025e45
--- /dev/null
+++ b/os_darwin/com.smartmontools.smartd.plist.in
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>KeepAlive</key>
+ <dict>
+ <key>SuccessfulExit</key>
+ <false/>
+ </dict>
+ <key>Label</key>
+ <string>com.smartmontools.smartd</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/usr/local/sbin/smartd</string>
+ <string>-n</string>
+ </array>
+ <key>RunAtLoad</key>
+ <true/>
+ </dict>
+</plist>
diff --git a/os_darwin/pkg/Distribution.in b/os_darwin/pkg/Distribution.in
new file mode 100644
index 0000000..df3ec9c
--- /dev/null
+++ b/os_darwin/pkg/Distribution.in
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<installer-gui-script minSpecVersion="2">
+ <allowed-os-versions>
+ <os-version min="10.5"/>
+ </allowed-os-versions>
+ <license file="license.txt" mime-type="text/plain"/>
+ <!-- <welcome file="welcome.rtf" mime-type="text/rtf"/> -->
+ <title>S.M.A.R.T. disk monitoring tools</title>
+ <pkg-ref id="com.smartmontools.pkg"/>
+ <choices-outline>
+ <line choice="default">
+ <line choice="com.smartmontools.pkg"/>
+ </line>
+ </choices-outline>
+ <choice id="default"/>
+ <choice id="com.smartmontools.pkg" visible="false">
+ <pkg-ref version="@version@" installKBytes="@size@" id="com.smartmontools.pkg">@pkgname@</pkg-ref>
+ </choice>
+ <domains enable_localSystem="true"/>
+ <options customize="never" rootVolumeOnly="true"/>
+</installer-gui-script>
diff --git a/os_darwin/pkg/PackageInfo.in b/os_darwin/pkg/PackageInfo.in
new file mode 100644
index 0000000..92ea7a2
--- /dev/null
+++ b/os_darwin/pkg/PackageInfo.in
@@ -0,0 +1,6 @@
+<pkg-info format-version="2" identifier="com.smartmontools.pkg" version="@version@" install-location="/" auth="root">
+<payload installKBytes="@size@" numberOfFiles="@files@"/>
+<!-- <scripts>
+ <postinstall file="./postinstall"/>
+</scripts> -->
+</pkg-info>
diff --git a/os_darwin/pkg/installer/README.html b/os_darwin/pkg/installer/README.html
new file mode 100644
index 0000000..6abdc80
--- /dev/null
+++ b/os_darwin/pkg/installer/README.html
@@ -0,0 +1,26 @@
+<html>
+<body>
+<h2>About this package</h2>
+The smartmontools package contains two utility programs (smartctl and smartd) to control
+and monitor storage systems using the Self-Monitoring, Analysis and Reporting
+Technology System (SMART) built into most modern ATA and SCSI harddisks.
+In many cases, these utilities will provide advanced warning of disk degradation and failure.
+<h2>Installing</h2>
+To install package click on the smartmontools.pkg icon and follow installation process. Files will be installed to the <b>/usr/local/</b> directory.
+<h2>Usage</h2>
+ If you are having trouble understanding the output of smartctl or smartd, please first read the manual pages installed on your system:
+<pre>
+ man 8 smartctl
+ man 8 smartd
+ man 8 update-smart-drivedb
+ man 5 smartd.conf
+</pre>
+To use smartmontools with USB drives please download and install
+<a href="https://github.com/kasbert/OS-X-SAT-SMART-Driver">Max OS X kernel driver for providing access to external drive SMART data</a>. SAT SMART Driver is a free open source project (published under Apple Public Source License) by Jarkko Sonninen.
+If you are using OS X El Capitan 10.11+ it is recommended to use signed version available from <a href="http://binaryfruit.com/drivedx/usb-drive-support/">DriveDx web site</a>.
+<p>
+More information could be found on the <a href="https://www.smartmontools.org">www.smartmontools.org</a> website.
+<h2>Uninstalling</h2>
+If you want to uninstall already installed package run <tt>'sudo smart-pkg-uninstall'</tt> in the terminal.
+</body>
+</html>
diff --git a/os_darwin/pkg/root/usr/local/sbin/smart-pkg-uninstall b/os_darwin/pkg/root/usr/local/sbin/smart-pkg-uninstall
new file mode 100755
index 0000000..72bb3fa
--- /dev/null
+++ b/os_darwin/pkg/root/usr/local/sbin/smart-pkg-uninstall
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+echo "Smartmontools package uninstaller:"
+
+# check if we are running with root uid
+if [[ $EUID -ne 0 ]]; then
+ echo " Error: this script must be run as root"
+ exit 1
+fi
+
+# check if package is installed
+pkgutil --info com.smartmontools.pkg > /dev/null 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo " Error: smartmontools package is not installed"
+ exit 1
+fi
+
+# smartmontools pkg could be installed only on system volume, so this should be safe
+cd /
+
+echo " - removing files"
+for str in `pkgutil --files com.smartmontools.pkg`
+do
+ if [ -f "$str" ]
+ then
+ rm -f "$str"
+ fi
+done
+echo " - removing empty directories"
+for str in `pkgutil --files com.smartmontools.pkg`
+do
+ if [ -d "$str" ]
+ then
+ rmdir -p "$str" 2>/dev/null
+ fi
+done
+
+echo " - removing package system entry"
+pkgutil --forget com.smartmontools.pkg
+echo "Done, smartmontolls package removed"