From f671a647e5480a709f09a66252995bf07e512e32 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 30 Jun 2008 21:50:56 +0200 Subject: a lot of love --- .gitignore | 8 + Makefile | 18 +- gnome-disk-health.glade | 620 ++++++++++++++++++ gnome-disk-health.vala | 137 ++++ skdump.c | 52 +- sktest.c | 66 ++ smart.c | 1615 ++++++++++++++++++++++++++--------------------- smart.h | 181 ++++-- smart.vapi | 139 ++++ smartkitd.vala | 390 ++++++++++++ 10 files changed, 2436 insertions(+), 790 deletions(-) create mode 100644 gnome-disk-health.glade create mode 100644 gnome-disk-health.vala create mode 100644 sktest.c create mode 100644 smart.vapi create mode 100644 smartkitd.vala diff --git a/.gitignore b/.gitignore index 884e119..05900f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ skdump +sktest +gnome-disk-health +gnome-disk-health.c +gnome-disk-health.h +gnome-disk-health.ui +smartkitd +smartkitd.c +smartkitd.h *.o diff --git a/Makefile b/Makefile index 6621354..687adb5 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,22 @@ -CFLAGS=`pkg-config --cflags glib-2.0` -pipe -Wall -W -O0 -g +CFLAGS=`pkg-config --cflags glib-2.0` -pipe -Wall -W -O0 -g -I. LIBS=`pkg-config --libs glib-2.0` +all: skdump sktest smartkitd gnome-disk-health gnome-disk-health.ui + skdump: smart.o skdump.o $(CC) -o $@ $^ $(CFLAGS) $(LIBS) +sktest: smart.o sktest.o + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +smartkitd: smart.c smartkitd.vala + valac --save-temps -g -o $@ --vapidir=. --pkg=smart --pkg=hal --pkg=dbus-glib-1 --Xcc=-I. $^ + +gnome-disk-health: gnome-disk-health.vala + valac --save-temps -g -o $@ --pkg=gtk+-2.0 --pkg=dbus-glib-1 $^ + +gnome-disk-health.ui: gnome-disk-health.glade + gtk-builder-convert $< $@ + clean: - rm -f skdump *.o + rm -f skdump sktest *.o smartkitd gnome-disk-health gnome-disk-health.ui diff --git a/gnome-disk-health.glade b/gnome-disk-health.glade new file mode 100644 index 0000000..5453e84 --- /dev/null +++ b/gnome-disk-health.glade @@ -0,0 +1,620 @@ + + + + + + 5 + Disk Health + GTK_WIN_POS_CENTER_ON_PARENT + drive-harddisk + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + 2 + + + True + True + + + True + 12 + + + True + + + True + 0 + 0 + 12 + 12 + 6 + 64 + drive-harddisk + + + False + + + + + True + 12 + 5 + 2 + 6 + 6 + + + True + 0 + label + + + 1 + 2 + 4 + 5 + + + + + True + 0 + label + + + 1 + 2 + 3 + 4 + + + + + True + 0 + label + + + 1 + 2 + 2 + 3 + + + + + True + 0 + label + + + 1 + 2 + 1 + 2 + + + + + True + 0 + label + + + 1 + 2 + + + + + True + 1 + <b>Firmware:</b> + True + + + 4 + 5 + GTK_FILL + + + + + True + 1 + <b>Serial Number:</b> + True + + + 3 + 4 + GTK_FILL + + + + + True + 1 + <b>Model:</b> + True + + + 2 + 3 + GTK_FILL + + + + + True + 1 + <b>Size:</b> + True + + + 1 + 2 + GTK_FILL + + + + + True + 1 + <b>Path:</b> + True + + + GTK_FILL + GTK_FILL + + + + + 1 + + + + + False + + + + + True + + + False + 1 + + + + + True + 24 + + + True + Disk health functionality available (S.M.A.R.T.) + + + False + + + + + True + Disk is healthy. + + + False + 1 + + + + + True + A few sectors have been reallocated. + + + False + 2 + + + + + True + Current disk temperature is 40°C. + + + False + 3 + + + + + 16 + 2 + + + + + + + True + Summary + + + tab + False + + + + + True + 12 + 6 + + + True + 0 + S.M.A.R.T. Attributes: + + + False + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + + + + + 1 + + + + + True + True + + + True + 7 + 2 + 6 + 6 + + + True + 0 + label + + + 1 + 2 + 6 + 7 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>Verdict:</b> + True + + + 6 + 7 + GTK_FILL + GTK_FILL + + + + + True + 0 + label + + + 1 + 2 + 5 + 6 + GTK_FILL + GTK_FILL + + + + + True + 0 + label + + + 1 + 2 + 4 + 5 + GTK_FILL + GTK_FILL + + + + + True + 0 + label + + + 1 + 2 + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + 0 + label + + + 1 + 2 + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 0 + label + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 0 + label + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>Type:</b> + True + + + 5 + 6 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>Threshold:</b> + True + + + 4 + 5 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>Worst Value:</b> + True + + + 3 + 4 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>Current Value:</b> + True + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>ID:</b> + True + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 1 + <b>Name:</b> + True + + + GTK_FILL + GTK_FILL + + + + + + + True + Explanation + + + label_item + + + + + False + 2 + + + + + 1 + + + + + True + Details + + + tab + 1 + False + + + + + True + 12 + 16 + + + True + 6 + + + True + + + + + True + True + True + Start Self-Test + 0 + + + False + 1 + + + + + False + + + + + True + Self-test functionality available. Approximate run-time is 120 min. + True + + + False + 1 + + + + + 2 + + + + + True + Self-Test + + + tab + 2 + False + + + + + 1 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-close + True + 0 + + + + + False + GTK_PACK_END + + + + + + diff --git a/gnome-disk-health.vala b/gnome-disk-health.vala new file mode 100644 index 0000000..011f7c0 --- /dev/null +++ b/gnome-disk-health.vala @@ -0,0 +1,137 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + +using DBus; +using GLib; +using Gtk; + +public class DiskHealth : Gtk.Builder { + + string uifile = "gnome-disk-health.ui"; + + public bool create_widgets(string disk_string) { + try { + add_from_file (uifile); + Gtk.Widget window = (Gtk.Widget) get_object("DiskHealthDialog"); + + if (!fill_in_data(disk_string)) + return false; + + window.show_all(); + window.destroy += Gtk.main_quit; + } catch (GLib.Error e) { + stderr.printf("Failed to create main window: %s\n", e.message); + return false; + } + + return true; + } + + private DBus.Connection connection; + private dynamic DBus.Object manager; + private DBus.ObjectPath dbus_path; + private dynamic DBus.Object disk; + + public bool fill_in_data(string disk_string) { + + try { + connection = DBus.Bus.get(DBus.BusType.SYSTEM); + + manager = connection.get_object("net.poettering.SmartKit", "/", "net.poettering.SmartKit.Manager"); + + DBus.ObjectPath p; + + try { + p = manager.getDiskByPath(disk_string); + } catch (DBus.Error e) { + try { + p = manager.getDiskByUDI(disk_string); + } catch (DBus.Error e) { + return false; + } + } + + stderr.printf("Using D-Bus path %s\n", p); + + disk = connection.get_object("net.poettering.SmartKit", p, "net.poettering.SmartKit.Disk"); + + ((Gtk.Label) get_object("pathLabel")).set_label(disk.getPath()); + ((Gtk.Label) get_object("sizeLabel")).set_label(pretty_size(disk.getSize())); + + bool b = disk.isIdentifyAvailable(); + + if (b) { + ((Gtk.Label) get_object("modelLabel")).set_label(disk.getIdentifyModel()); + ((Gtk.Label) get_object("serialLabel")).set_label(disk.getIdentifySerial()); + ((Gtk.Label) get_object("firmwareLabel")).set_label(disk.getIdentifyFirmware()); + } else { + ((Gtk.Label) get_object("modelLabel")).set_label("n/a"); + ((Gtk.Label) get_object("serialLabel")).set_label("n/a"); + ((Gtk.Label) get_object("firmwareLabel")).set_label("n/a"); + } + + if (b) + b = disk.isSmartAvailable(); + + if (b) { + ((Gtk.Label) get_object("smartLabel")).set_label("Disk health functionality (S.M.A.R.T.) is available."); + } else { + ((Gtk.Label) get_object("smartLabel")).set_markup("Disk health functionality (S.M.A.R.T.) is not available."); + ((Gtk.Label) get_object("healthLabel")).set_label(""); + ((Gtk.Label) get_object("badSectorsLabel")).set_label(""); + ((Gtk.Label) get_object("temperatureLabel")).set_label(""); + } + + } catch (DBus.Error e) { + stderr.printf("D-Bus error: %s\n", e.message); + return false; + } + + return true; + } + + public static string pretty_size(uint64 size) { + + if (size >= (uint64)1024*(uint64)1024*(uint64)1024*(uint64)1024) + return "%0.1f TiB".printf((double) size/1024/1024/1024/1024); + else if (size >= (uint64)1024*(uint64)1024*(uint64)1024) + return "%0.1f GiB".printf((double) size/1024/1024/1024); + else if (size >= (uint64)1024*(uint64)1024) + return "%0.1f MiB".printf((double) size/1024/1024); + else if (size >= (uint64)1024) + return "%0.1f KiB".printf((double) size/1024); + else + return "%u B".printf((uint) size); + } + + + public static int main (string[] args) { + Gtk.init(ref args); + + var dh = new DiskHealth(); + + if (dh.create_widgets(args[1])) + Gtk.main (); + + return 0; + } +} diff --git a/skdump.c b/skdump.c index 6fa9167..7d1cdb6 100644 --- a/skdump.c +++ b/skdump.c @@ -1,23 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + #include #include #include "smart.h" int main(int argc, char *argv[]) { - int ret; - const char *device; - SkDevice *d; + int ret; + const char *device; + SkDisk *d; + + if (argc != 2) { + g_printerr("%s [DEVICE]", argv[0]); + return 1; + } - device = argc >= 2 ? argv[1] : "/dev/sda"; + device = argv[1]; - if ((ret = sk_disk_open(device, &d)) < 0) { - g_printerr("Failed to open disk %s: %s\n", device, strerror(errno)); - return 1; - } + if ((ret = sk_disk_open(device, &d)) < 0) { + g_printerr("Failed to open disk %s: %s\n", device, g_strerror(errno)); + return 1; + } - sk_disk_dump(d); + if ((ret = sk_disk_dump(d)) < 0) { + g_printerr("Failed to dump disk data: %s\n", g_strerror(errno)); + return 1; + } - sk_disk_free(d); + sk_disk_free(d); - return 0; + return 0; } diff --git a/sktest.c b/sktest.c new file mode 100644 index 0000000..374b11c --- /dev/null +++ b/sktest.c @@ -0,0 +1,66 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + +#include +#include + +#include "smart.h" + +int main(int argc, char *argv[]) { + int ret; + const char *device; + SkDisk *d; + SkSmartSelfTest test; + + if (argc < 3) { + g_printerr("%s [DEVICE] [short|extended|conveyance]\n", argv[0]); + return 1; + } + + device = argv[1]; + + if (!g_strcasecmp(argv[2], sk_smart_self_test_to_string(SK_SMART_SELF_TEST_SHORT))) + test = SK_SMART_SELF_TEST_SHORT; + else if (!g_strcasecmp(argv[2], sk_smart_self_test_to_string(SK_SMART_SELF_TEST_EXTENDED))) + test = SK_SMART_SELF_TEST_EXTENDED; + else if (!(g_strcasecmp(argv[2], sk_smart_self_test_to_string(SK_SMART_SELF_TEST_CONVEYANCE)))) + test = SK_SMART_SELF_TEST_CONVEYANCE; + else { + g_printerr("Unknown test '%s'.\n", argv[2]); + return 1; + } + + if ((ret = sk_disk_open(device, &d)) < 0) { + g_printerr("Failed to open disk %s: %s\n", device, g_strerror(errno)); + return 1; + } + + if ((ret = sk_disk_smart_self_test(d, test)) < 0) { + g_printerr("Failed to start sel-test: %s\n", g_strerror(errno)); + return 1; + + } + + sk_disk_free(d); + + return 0; +} diff --git a/smart.c b/smart.c index 7d4c483..4cd9c6f 100644 --- a/smart.c +++ b/smart.c @@ -1,3 +1,25 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + #ifdef HAVE_CONFIG_H #include #endif @@ -6,6 +28,8 @@ #include #include #include +#include +#include #include #include #include @@ -19,390 +43,397 @@ #define SK_TIMEOUT 2000 typedef enum SkDirection { - SK_DIRECTION_NONE, - SK_DIRECTION_IN, - SK_DIRECTION_OUT, - _SK_DIRECTION_MAX + SK_DIRECTION_NONE, + SK_DIRECTION_IN, + SK_DIRECTION_OUT, + _SK_DIRECTION_MAX } SkDirection; typedef enum SkDiskType { - SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */ - SK_DISK_TYPE_ATA, - SK_DISK_TYPE_UNKNOWN, - _SK_DISK_TYPE_MAX + SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */ + SK_DISK_TYPE_ATA, + SK_DISK_TYPE_UNKNOWN, + _SK_DISK_TYPE_MAX } SkDiskType; struct SkDisk { - gchar *name; - int fd; - SkDiskType type; + gchar *name; + int fd; + SkDiskType type; - guint64 size; + guint64 size; - guint8 identify[512]; - guint8 smart_data[512]; - guint8 smart_threshold_data[512]; + guint8 identify[512]; + guint8 smart_data[512]; + guint8 smart_threshold_data[512]; - gboolean identify_data_valid:1; - gboolean smart_data_valid:1; - gboolean smart_threshold_data_valid:1; + gboolean identify_data_valid:1; + gboolean smart_data_valid:1; + gboolean smart_threshold_data_valid:1; - SkIdentifyParsedData identify_parsed_data; - SkSmartParsedData smart_parsed_data; + SkIdentifyParsedData identify_parsed_data; + SkSmartParsedData smart_parsed_data; }; /* ATA commands */ typedef enum SkAtaCommand { - SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC, - SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1, - SK_ATA_COMMAND_SMART = 0xB0, - SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5 + SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC, + SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1, + SK_ATA_COMMAND_SMART = 0xB0, + SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5 } SkAtaCommand; /* ATA SMART subcommands (ATA8 7.52.1) */ typedef enum SkSmartCommand { - SK_SMART_COMMAND_READ_DATA = 0xD0, - SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1, - SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4, - SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8, - SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9, - SK_SMART_COMMAND_RETURN_STATUS = 0xDA + SK_SMART_COMMAND_READ_DATA = 0xD0, + SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1, + SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4, + SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8, + SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9, + SK_SMART_COMMAND_RETURN_STATUS = 0xDA } SkSmartCommand; -/* ATA SMART test type (ATA8 7.52.5.2) */ -typedef enum SkSmartTest { - SK_SMART_TEST_OFFLINE_FULL = 0, - SK_SMART_TEST_OFFLINE_SHORT = 1, - SK_SMART_TEST_OFFLINE_EXTENDED = 2, - SK_SMART_TEST_OFFLINE_CONVEYANCE = 3, - SK_SMART_TEST_OFFLINE_SELECTIVE = 4, +static gboolean disk_smart_is_available(SkDisk *d) { + return d->identify_data_valid && !!(d->identify[164] & 1); +} + +static gboolean disk_smart_is_enabled(SkDisk *d) { + return d->identify_data_valid && !!(d->identify[170] & 1); +} - SK_SMART_TEST_CAPTIVE_SHORT = 129, - SK_SMART_TEST_CAPTIVE_EXTENDED = 130, - SK_SMART_TEST_CAPTIVE_CONVEYANCE = 131, - SK_SMART_TEST_CAPTIVE_SELECTIVE = 132, +static gboolean disk_smart_is_conveyance_test_available(SkDisk *d) { + g_assert(d->smart_data_valid); - SK_SMART_TEST_CAPTIVE_MASK = 128, - SK_SMART_TEST_ABORT = 127 -} SkSmartTest; + return !!(d->smart_data[367] & 32); +} +static gboolean disk_smart_is_short_and_extended_test_available(SkDisk *d) { + g_assert(d->smart_data_valid); -static gboolean disk_smart_is_available(SkDisk *d) { - return d->identify_data_valid && !!(d->identify[164] & 1); + return !!(d->smart_data[367] & 16); } -static gboolean disk_smart_is_enabled(SkDisk *d) { - return d->identify_data_valid && !!(d->identify[170] & 1); +static gboolean disk_smart_is_start_test_available(SkDisk *d) { + g_assert(d->smart_data_valid); + + return !!(d->smart_data[367] & 1); +} + +static gboolean disk_smart_is_abort_test_available(SkDisk *d) { + g_assert(d->smart_data_valid); + + return !!(d->smart_data[367] & 41); } static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) { - guint8 *bytes = cmd_data; - int ret; + guint8 *bytes = cmd_data; + int ret; - g_assert(d->type == SK_DISK_TYPE_ATA); + g_assert(d->type == SK_DISK_TYPE_ATA); - switch (direction) { + switch (direction) { - case SK_DIRECTION_OUT: + case SK_DIRECTION_OUT: - /* We could use HDIO_DRIVE_TASKFILE here, but that's a - * deprecated ioctl(), hence we don't do it. */ + /* We could use HDIO_DRIVE_TASKFILE here, but + * that's a deprecated ioctl(), hence we don't + * do it. And we don't need writing anyway. */ - errno = ENOTSUP; - return -1; + errno = ENOTSUP; + return -1; - case SK_DIRECTION_IN: { - guint8 *ioctl_data; + case SK_DIRECTION_IN: { + guint8 *ioctl_data; - /* We have HDIO_DRIVE_CMD which can only read, but not write, - * and cannot do LBA. We use it for all read commands. */ + /* We have HDIO_DRIVE_CMD which can only read, but not write, + * and cannot do LBA. We use it for all read commands. */ - ioctl_data = g_alloca(4 + *len); - memset(ioctl_data, 0, 4 + *len); + ioctl_data = g_alloca(4 + *len); + memset(ioctl_data, 0, 4 + *len); - ioctl_data[0] = (guint8) command; /* COMMAND */ - ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3]; /* SECTOR/NSECTOR */ - ioctl_data[2] = bytes[1]; /* FEATURE */ - ioctl_data[3] = bytes[3]; /* NSECTOR */ + ioctl_data[0] = (guint8) command; /* COMMAND */ + ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3]; /* SECTOR/NSECTOR */ + ioctl_data[2] = bytes[1]; /* FEATURE */ + ioctl_data[3] = bytes[3]; /* NSECTOR */ - if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0) - return ret; + if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0) + return ret; - memset(bytes, 0, 12); - bytes[11] = ioctl_data[0]; - bytes[1] = ioctl_data[1]; - bytes[3] = ioctl_data[2]; + memset(bytes, 0, 12); + bytes[11] = ioctl_data[0]; + bytes[1] = ioctl_data[1]; + bytes[3] = ioctl_data[2]; - memcpy(data, ioctl_data+4, *len); + memcpy(data, ioctl_data+4, *len); - return ret; - } + return ret; + } - case SK_DIRECTION_NONE: { - guint8 ioctl_data[7]; + case SK_DIRECTION_NONE: { + guint8 ioctl_data[7]; - /* We have HDIO_DRIVE_TASK which can neither read nor - * write, but can do LBA. We use it for all commands that - * do neither read nor write */ + /* We have HDIO_DRIVE_TASK which can neither read nor + * write, but can do LBA. We use it for all commands that + * do neither read nor write */ - memset(ioctl_data, 0, sizeof(ioctl_data)); + memset(ioctl_data, 0, sizeof(ioctl_data)); - ioctl_data[0] = (guint8) command; /* COMMAND */ - ioctl_data[1] = bytes[1]; /* FEATURE */ - ioctl_data[2] = bytes[3]; /* NSECTOR */ + ioctl_data[0] = (guint8) command; /* COMMAND */ + ioctl_data[1] = bytes[1]; /* FEATURE */ + ioctl_data[2] = bytes[3]; /* NSECTOR */ - ioctl_data[3] = bytes[9]; /* LBA LOW */ - ioctl_data[4] = bytes[8]; /* LBA MID */ - ioctl_data[5] = bytes[7]; /* LBA HIGH */ - ioctl_data[6] = bytes[10]; /* SELECT */ + ioctl_data[3] = bytes[9]; /* LBA LOW */ + ioctl_data[4] = bytes[8]; /* LBA MID */ + ioctl_data[5] = bytes[7]; /* LBA HIGH */ + ioctl_data[6] = bytes[10]; /* SELECT */ - if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data))) - return ret; + if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data))) + return ret; - memset(bytes, 0, 12); - bytes[11] = ioctl_data[0]; - bytes[1] = ioctl_data[1]; - bytes[3] = ioctl_data[2]; + memset(bytes, 0, 12); + bytes[11] = ioctl_data[0]; + bytes[1] = ioctl_data[1]; + bytes[3] = ioctl_data[2]; - bytes[9] = ioctl_data[3]; - bytes[8] = ioctl_data[4]; - bytes[7] = ioctl_data[5]; + bytes[9] = ioctl_data[3]; + bytes[8] = ioctl_data[4]; + bytes[7] = ioctl_data[5]; - bytes[10] = ioctl_data[6]; + bytes[10] = ioctl_data[6]; - return ret; - } + return ret; + } - default: - g_assert_not_reached(); - return -1; - } + default: + g_assert_not_reached(); + return -1; + } } /* Sends a SCSI command block */ static int sg_io(int fd, int direction, - const void *cdb, size_t cdb_len, - void *data, size_t data_len, - void *sense, size_t sense_len) { + const void *cdb, size_t cdb_len, + void *data, size_t data_len, + void *sense, size_t sense_len) { - struct sg_io_hdr io_hdr; + struct sg_io_hdr io_hdr; - memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); - io_hdr.interface_id = 'S'; - io_hdr.cmdp = (unsigned char*) cdb; - io_hdr.cmd_len = cdb_len; - io_hdr.dxferp = data; - io_hdr.dxfer_len = data_len; - io_hdr.sbp = sense; - io_hdr.mx_sb_len = sense_len; - io_hdr.dxfer_direction = direction; - io_hdr.timeout = SK_TIMEOUT; + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = cdb_len; + io_hdr.dxferp = data; + io_hdr.dxfer_len = data_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sense_len; + io_hdr.dxfer_direction = direction; + io_hdr.timeout = SK_TIMEOUT; - return ioctl(fd, SG_IO, &io_hdr); + return ioctl(fd, SG_IO, &io_hdr); } static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) { - guint8 *bytes = cmd_data; - guint8 cdb[16]; - guint8 sense[32]; - guint8 *desc = sense+8; - int ret; + guint8 *bytes = cmd_data; + guint8 cdb[16]; + guint8 sense[32]; + guint8 *desc = sense+8; + int ret; - static const int direction_map[] = { - [SK_DIRECTION_NONE] = SG_DXFER_NONE, - [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV, - [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV - }; + static const int direction_map[] = { + [SK_DIRECTION_NONE] = SG_DXFER_NONE, + [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV, + [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV + }; - g_assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH); + g_assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH); - /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8 - * ATA Command Pass-Through": - * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */ + /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8 + * ATA Command Pass-Through": + * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */ - memset(cdb, 0, sizeof(cdb)); + memset(cdb, 0, sizeof(cdb)); - cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */ + cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */ - if (direction == SK_DIRECTION_NONE) { - cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */ - cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */ + if (direction == SK_DIRECTION_NONE) { + cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */ + cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */ - } else if (direction == SK_DIRECTION_IN) { - cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ - cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + } else if (direction == SK_DIRECTION_IN) { + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ - } else if (direction == SK_DIRECTION_OUT) { - cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */ - cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */ - } + } else if (direction == SK_DIRECTION_OUT) { + cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */ + cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */ + } - cdb[3] = bytes[0]; /* FEATURES */ - cdb[4] = bytes[1]; + cdb[3] = bytes[0]; /* FEATURES */ + cdb[4] = bytes[1]; - cdb[5] = bytes[2]; /* SECTORS */ - cdb[6] = bytes[3]; + cdb[5] = bytes[2]; /* SECTORS */ + cdb[6] = bytes[3]; - cdb[8] = bytes[9]; /* LBA LOW */ - cdb[10] = bytes[8]; /* LBA MED */ - cdb[12] = bytes[7]; /* LBA HIGH */ + cdb[8] = bytes[9]; /* LBA LOW */ + cdb[10] = bytes[8]; /* LBA MED */ + cdb[12] = bytes[7]; /* LBA HIGH */ - cdb[13] = bytes[10] & 0x4F; /* SELECT */ - cdb[14] = (guint8) command; + cdb[13] = bytes[10] & 0x4F; /* SELECT */ + cdb[14] = (guint8) command; - if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0) - return ret; + if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0) + return ret; - if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) { - errno = EIO; - return -1; - } + if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) { + errno = EIO; + return -1; + } - memset(bytes, 0, 12); + memset(bytes, 0, 12); - bytes[1] = desc[3]; - bytes[2] = desc[4]; - bytes[3] = desc[5]; - bytes[9] = desc[7]; - bytes[8] = desc[9]; - bytes[7] = desc[10]; - bytes[10] = desc[12]; - bytes[11] = desc[13]; + bytes[1] = desc[3]; + bytes[2] = desc[4]; + bytes[3] = desc[5]; + bytes[9] = desc[7]; + bytes[8] = desc[9]; + bytes[7] = desc[10]; + bytes[10] = desc[12]; + bytes[11] = desc[13]; - return ret; + return ret; } static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) { - static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) = { - [SK_DISK_TYPE_ATA] = disk_ata_command, - [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command, - }; + static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) = { + [SK_DISK_TYPE_ATA] = disk_ata_command, + [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command, + }; - g_assert(d); - g_assert(d->type <= _SK_DISK_TYPE_MAX); - g_assert(direction <= _SK_DIRECTION_MAX); + g_assert(d); + g_assert(d->type <= _SK_DISK_TYPE_MAX); + g_assert(direction <= _SK_DIRECTION_MAX); - g_assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0)); - g_assert(direction != SK_DIRECTION_NONE || (!data && !len)); + g_assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0)); + g_assert(direction != SK_DIRECTION_NONE || (!data && !len)); - return disk_command_table[d->type](d, command, direction, cmd_data, data, len); + return disk_command_table[d->type](d, command, direction, cmd_data, data, len); } static int disk_identify_device(SkDisk *d) { - guint16 cmd[6]; - int ret; - size_t len = 512; + guint16 cmd[6]; + int ret; + size_t len = 512; - memset(cmd, 0, sizeof(cmd)); + memset(cmd, 0, sizeof(cmd)); - cmd[1] = GUINT16_TO_BE(1); + cmd[1] = GUINT16_TO_BE(1); - if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0) - return ret; + if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0) + return ret; - if (len != 512) { - errno = EIO; - return -1; - } + if (len != 512) { + errno = EIO; + return -1; + } - d->identify_data_valid = TRUE; + d->identify_data_valid = TRUE; - return 0; + return 0; } -int sk_disk_check_power_mode(SkDisk *d, gboolean *mode) { - int ret; - guint16 cmd[6]; +int sk_disk_check_sleep_mode(SkDisk *d, gboolean *awake) { + int ret; + guint16 cmd[6]; - if (!d->identify_data_valid) { - errno = ENOTSUP; - return -1; - } + if (!d->identify_data_valid) { + errno = ENOTSUP; + return -1; + } - memset(cmd, 0, sizeof(cmd)); + memset(cmd, 0, sizeof(cmd)); - if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0) - return ret; + if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0) + return ret; - if (cmd[0] != 0 || (GUINT16_FROM_BE(cmd[5]) & 1) != 0) { - errno = EIO; - return -1; - } + if (cmd[0] != 0 || (GUINT16_FROM_BE(cmd[5]) & 1) != 0) { + errno = EIO; + return -1; + } - *mode = GUINT16_FROM_BE(cmd[1]) == 0xFF; + *awake = GUINT16_FROM_BE(cmd[1]) == 0xFF; - return 0; + return 0; } static int disk_smart_enable(SkDisk *d, gboolean b) { - guint16 cmd[6]; + guint16 cmd[6]; - if (!disk_smart_is_available(d)) { - errno = ENOTSUP; - return -1; - } + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } - memset(cmd, 0, sizeof(cmd)); + memset(cmd, 0, sizeof(cmd)); - cmd[0] = GUINT16_TO_BE(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS); - cmd[2] = GUINT16_TO_BE(0x0000U); - cmd[3] = GUINT16_TO_BE(0x00C2U); - cmd[4] = GUINT16_TO_BE(0x4F00U); + cmd[0] = GUINT16_TO_BE(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS); + cmd[2] = GUINT16_TO_BE(0x0000U); + cmd[3] = GUINT16_TO_BE(0x00C2U); + cmd[4] = GUINT16_TO_BE(0x4F00U); - return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0); + return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0); } int sk_disk_smart_read_data(SkDisk *d) { - guint16 cmd[6]; - int ret; - size_t len = 512; + guint16 cmd[6]; + int ret; + size_t len = 512; - if (!disk_smart_is_available(d)) { - errno = ENOTSUP; - return -1; - } + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } - memset(cmd, 0, sizeof(cmd)); + memset(cmd, 0, sizeof(cmd)); - cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_DATA); - cmd[1] = GUINT16_TO_BE(1); - cmd[2] = GUINT16_TO_BE(0x0000U); - cmd[3] = GUINT16_TO_BE(0x00C2U); - cmd[4] = GUINT16_TO_BE(0x4F00U); + cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_DATA); + cmd[1] = GUINT16_TO_BE(1); + cmd[2] = GUINT16_TO_BE(0x0000U); + cmd[3] = GUINT16_TO_BE(0x00C2U); + cmd[4] = GUINT16_TO_BE(0x4F00U); - if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0) - return ret; + if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0) + return ret; - d->smart_data_valid = TRUE; + d->smart_data_valid = TRUE; - return ret; + return ret; } static int disk_smart_read_thresholds(SkDisk *d) { - guint16 cmd[6]; - int ret; - size_t len = 512; + guint16 cmd[6]; + int ret; + size_t len = 512; - if (!disk_smart_is_available(d)) { - errno = ENOTSUP; - return -1; - } + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } - memset(cmd, 0, sizeof(cmd)); + memset(cmd, 0, sizeof(cmd)); - cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_THRESHOLDS); - cmd[1] = GUINT16_TO_BE(1); - cmd[2] = GUINT16_TO_BE(0x0000U); - cmd[3] = GUINT16_TO_BE(0x00C2U); - cmd[4] = GUINT16_TO_BE(0x4F00U); + cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_THRESHOLDS); + cmd[1] = GUINT16_TO_BE(1); + cmd[2] = GUINT16_TO_BE(0x0000U); + cmd[3] = GUINT16_TO_BE(0x00C2U); + cmd[4] = GUINT16_TO_BE(0x4F00U); - if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0) - return ret; + if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0) + return ret; - d->smart_threshold_data_valid = TRUE; + d->smart_threshold_data_valid = TRUE; - return ret; + return ret; } /* int disk_smart_status(SkDisk *d, SmartLogAddress a, gboolean *b) { */ @@ -415,626 +446,766 @@ static int disk_smart_read_thresholds(SkDisk *d) { /* cmd[3] = GUINT16_TO_BE(0x00C2U); */ /* cmd[4] = GUINT16_TO_BE(0x4F00U | (guint16) a); */ -/* ret = disk_ata_command(SK_ATA_SMART, cmd, sizeof(cmd), NULL, 0); */ +/* ret = disk_command(SK_ATA_SMART, cmd, sizeof(cmd), NULL, 0); */ /* return ret; */ /* } */ -/* int disk_smart_immediate_offline(SkDisk *d, SmartTestType type) { */ -/* guint16 cmd[6]; */ +int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) { + guint16 cmd[6]; + int ret; -/* memset(cmd, 0, sizeof(cmd)); */ + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } -/* cmd[0] = GUINT16_TO_BE(SMART_EXECUTE_OFFLINE_IMMEDIATE); */ -/* cmd[2] = GUINT16_TO_BE(0x0000U); */ -/* cmd[3] = GUINT16_TO_BE(0x00C2U); */ -/* cmd[4] = GUINT16_TO_BE(0x4F00U | (guint16) type); */ + if (!d->smart_data_valid) + if ((ret = sk_disk_smart_read_data(d)) < 0) + return -1; -/* return disk_ata_command(SK_ATA_SMART, cmd, sizeof(cmd), NULL, 0); */ -/* } */ + g_assert(d->smart_data_valid); + + if (test != SK_SMART_SELF_TEST_SHORT && + test != SK_SMART_SELF_TEST_EXTENDED && + test != SK_SMART_SELF_TEST_CONVEYANCE && + test != SK_SMART_SELF_TEST_ABORT) { + errno = EINVAL; + return -1; + } + + if (!disk_smart_is_start_test_available(d) + || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d)) + || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d)) + || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) { + errno = ENOTSUP; + return -1; + } + + if (test == SK_SMART_SELF_TEST_ABORT && + !disk_smart_is_abort_test_available(d)) { + errno = ENOTSUP; + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + + cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE); + cmd[2] = GUINT16_TO_BE(0x0000U); + cmd[3] = GUINT16_TO_BE(0x00C2U); + cmd[4] = GUINT16_TO_BE(0x4F00U | (guint16) test); + + return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL); +} static void swap_strings(gchar *s, size_t len) { - g_assert((len & 1) == 0); - - for (; len > 0; s += 2, len -= 2) { - gchar t; - t = s[0]; - s[0] = s[1]; - s[1] = t; - } + g_assert((len & 1) == 0); + + for (; len > 0; s += 2, len -= 2) { + gchar t; + t = s[0]; + s[0] = s[1]; + s[1] = t; + } } static void clean_strings(gchar *s) { - gchar *e; + gchar *e; - for (e = s; *e; e++) - if (!g_ascii_isprint(*e)) - *e = ' '; + for (e = s; *e; e++) + if (!g_ascii_isprint(*e)) + *e = ' '; } static void drop_spaces(gchar *s) { - gchar *d = s; - gboolean prev_space = FALSE; - - s += strspn(s, " "); - - for (;*s; s++) { - - if (prev_space) { - if (*s != ' ') { - prev_space = FALSE; - *(d++) = ' '; - } - } else { - if (*s == ' ') - prev_space = TRUE; - else - *(d++) = *s; + gchar *d = s; + gboolean prev_space = FALSE; + + s += strspn(s, " "); + + for (;*s; s++) { + + if (prev_space) { + if (*s != ' ') { + prev_space = FALSE; + *(d++) = ' '; + } + } else { + if (*s == ' ') + prev_space = TRUE; + else + *(d++) = *s; + } } - } - *d = 0; + *d = 0; } static void read_string(gchar *d, guint8 *s, size_t len) { - memcpy(d, s, len); - d[len] = 0; - swap_strings(d, len); - clean_strings(d); - drop_spaces(d); + memcpy(d, s, len); + d[len] = 0; + swap_strings(d, len); + clean_strings(d); + drop_spaces(d); } int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) { - if (!d->identify_data_valid) { - errno = ENOENT; - return -1; - } + if (!d->identify_data_valid) { + errno = ENOENT; + return -1; + } - read_string(d->identify_parsed_data.serial, d->identify+20, 20); - read_string(d->identify_parsed_data.firmware, d->identify+46, 8); - read_string(d->identify_parsed_data.model, d->identify+54, 40); + read_string(d->identify_parsed_data.serial, d->identify+20, 20); + read_string(d->identify_parsed_data.firmware, d->identify+46, 8); + read_string(d->identify_parsed_data.model, d->identify+54, 40); - *ipd = &d->identify_parsed_data; + *ipd = &d->identify_parsed_data; - return 0; + return 0; } int sk_disk_smart_is_available(SkDisk *d, gboolean *b) { - if (!d->identify_data_valid) { - errno = ENOTSUP; - return -1; - } + if (!d->identify_data_valid) { + errno = ENOTSUP; + return -1; + } - *b = disk_smart_is_available(d); - return 0; + *b = disk_smart_is_available(d); + return 0; } int sk_disk_identify_is_available(SkDisk *d, gboolean *b) { - *b = d->identify_data_valid; - return 0; + *b = d->identify_data_valid; + return 0; } const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) { - static const char* const map[] = { - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.", - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.", - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.", - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.", - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.", - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.", - [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status" - }; - - if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX) + static const char* const map[] = { + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.", + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.", + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.", + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.", + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.", + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.", + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status" + }; + + if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX) + return NULL; + + return map[status]; +} + +const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) { + + static const char* const map[] = { + [SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER] = "The previous self-test routine completed without error or no self-test has ever been run.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL] = "A fatal error or unknown test error occurred while the device was executing its self-test routine and the device was unable to complete the self-test routine.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN] = "The previous self-test completed having a test element that failed and the test element that failed.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO] = "The previous self-test completed having the servo (and/or seek) test element of the test failed.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING] = "The previous self-test completed having a test element that failed and the device is suspected of having handling damage.", + [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress" + }; + + if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX) + return NULL; + + return map[status]; +} + +const char* sk_smart_self_test_to_string(SkSmartSelfTest test) { + + switch (test) { + case SK_SMART_SELF_TEST_SHORT: + return "short"; + case SK_SMART_SELF_TEST_EXTENDED: + return "extended"; + case SK_SMART_SELF_TEST_CONVEYANCE: + return "conveyance"; + case SK_SMART_SELF_TEST_ABORT: + return "abort"; + } + return NULL; +} - return map[status]; +gboolean sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) { + + if (!d->start_test_available) + return FALSE; + + switch (test) { + case SK_SMART_SELF_TEST_SHORT: + case SK_SMART_SELF_TEST_EXTENDED: + return d->short_and_extended_test_available; + case SK_SMART_SELF_TEST_CONVEYANCE: + return d->conveyance_test_available; + case SK_SMART_SELF_TEST_ABORT: + return d->abort_test_available; + default: + return FALSE; + } +} + +unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) { + + if (!sk_smart_self_test_available(d, test)) + return 0; + + switch (test) { + case SK_SMART_SELF_TEST_SHORT: + return d->short_test_polling_minutes; + case SK_SMART_SELF_TEST_EXTENDED: + return d->extended_test_polling_minutes; + case SK_SMART_SELF_TEST_CONVEYANCE: + return d->conveyance_test_polling_minutes; + default: + return 0; + } } typedef struct SkSmartAttributeInfo { - const char *name; - SkSmartAttributeUnit unit; + const char *name; + SkSmartAttributeUnit unit; } SkSmartAttributeInfo; /* This data is stolen from smartmontools */ static const SkSmartAttributeInfo const attribute_info[255] = { - [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [2] = { "throughput-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, - [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [8] = { "seek-time-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [11] = { "calibration-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [12] = { "power-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [13] = { "read-soft-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [187] = { "reported-uncorrect", SK_SMART_ATTRIBUTE_UNIT_SECTORS }, - [189] = { "high-fly-writes", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_KELVIN }, - [191] = { "g-sense-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [192] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [193] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_KELVIN }, - [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS }, - [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS }, - [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [204] = { "shock-count-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [205] = { "shock-rate-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN}, - [209] = { "offline-seek-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, - [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [225] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, - [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [228] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, - [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, - [231] = { "temperature-celsius-1", SK_SMART_ATTRIBUTE_UNIT_KELVIN }, - [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, - [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [2] = { "throughput-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, + [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [8] = { "seek-time-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [11] = { "calibration-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [12] = { "power-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [13] = { "read-soft-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [187] = { "reported-uncorrect", SK_SMART_ATTRIBUTE_UNIT_SECTORS }, + [189] = { "high-fly-writes", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_KELVIN }, + [191] = { "g-sense-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [192] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [193] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_KELVIN }, + [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS }, + [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS }, + [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [204] = { "shock-count-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [205] = { "shock-rate-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN}, + [209] = { "offline-seek-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, + [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [225] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, + [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [228] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE }, + [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN }, + [231] = { "temperature-celsius-1", SK_SMART_ATTRIBUTE_UNIT_KELVIN }, + [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS }, + [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE } }; -static void make_pretty(SkSmartAttribute *a) { - guint64 fourtyeight; - - if (!a->name) - return; - - if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN) - return; - - fourtyeight = - ((guint64) a->raw[0]) | - (((guint64) a->raw[1]) << 8) | - (((guint64) a->raw[2]) << 16) | - (((guint64) a->raw[3]) << 24) | - (((guint64) a->raw[4]) << 32) | - (((guint64) a->raw[5]) << 40); - - if (!strcmp(a->name, "spin-up-time")) - a->pretty_value = fourtyeight & 0xFFFF; - else if (!strcmp(a->name, "airflow-temperature-celsius") || - !strcmp(a->name, "temperature-celsius-1") || - !strcmp(a->name, "temperature-celsius-2")) { - a->pretty_value = (fourtyeight & 0xFFFF) + 273; - } else if (!strcmp(a->name, "power-on-minutes")) - a->pretty_value = fourtyeight * 60 * 1000; - else if (!strcmp(a->name, "power-on-seconds")) - a->pretty_value = fourtyeight * 1000; - else if (!strcmp(a->name, "power-on-hours") || - !strcmp(a->name, "loaded-hours") || - !strcmp(a->name, "head-flying-hours")) - a->pretty_value = fourtyeight * 60 * 60 * 1000; - else - a->pretty_value = fourtyeight; +static void make_pretty(SkSmartAttributeParsedData *a) { + guint64 fourtyeight; + + if (!a->name) + return; + + if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN) + return; + + fourtyeight = + ((guint64) a->raw[0]) | + (((guint64) a->raw[1]) << 8) | + (((guint64) a->raw[2]) << 16) | + (((guint64) a->raw[3]) << 24) | + (((guint64) a->raw[4]) << 32) | + (((guint64) a->raw[5]) << 40); + + if (!strcmp(a->name, "spin-up-time")) + a->pretty_value = fourtyeight & 0xFFFF; + else if (!strcmp(a->name, "airflow-temperature-celsius") || + !strcmp(a->name, "temperature-celsius-1") || + !strcmp(a->name, "temperature-celsius-2")) { + a->pretty_value = (fourtyeight & 0xFFFF) + 273; + } else if (!strcmp(a->name, "power-on-minutes")) + a->pretty_value = fourtyeight * 60 * 1000; + else if (!strcmp(a->name, "power-on-seconds")) + a->pretty_value = fourtyeight * 1000; + else if (!strcmp(a->name, "power-on-hours") || + !strcmp(a->name, "loaded-hours") || + !strcmp(a->name, "head-flying-hours")) + a->pretty_value = fourtyeight * 60 * 60 * 1000; + else + a->pretty_value = fourtyeight; } -static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, guint8 id, SkSmartAttributeInfo *space) { - const SkIdentifyParsedData *ipd; +static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, guint8 id) { + const SkIdentifyParsedData *ipd; - /* These are the simple cases */ - if (attribute_info[id].name) - return &attribute_info[id]; + /* These are the simple cases */ + if (attribute_info[id].name) + return &attribute_info[id]; + + /* These are the complex ones */ + if (sk_disk_identify_parse(d, &ipd) < 0) + return NULL; + + switch (id) { + /* We might want to add further special cases/quirks + * here eventually. */ + + case 9: { + + static const SkSmartAttributeInfo maxtor = { + "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS + }; + static const SkSmartAttributeInfo fujitsu = { + "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS + }; + static const SkSmartAttributeInfo others = { + "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS + }; + + if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR")) + return &maxtor; + else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU")) + return &fujitsu; + + return &others; + } + } - /* These are the complex ones */ - if (sk_disk_identify_parse(d, &ipd) < 0) return NULL; - - switch (id) { - case 9: - - if (strstr(ipd->model, "Maxtor")) - space->name = "power-on-minutes"; - else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU")) - space->name = "power-on-seconds"; - else - space->name = "power-on-hours"; - - space->unit = SK_SMART_ATTRIBUTE_UNIT_MSECONDS; - - return space; - } - - return NULL; } int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) { - if (!d->smart_data_valid) { - errno = ENOENT; - return -1; - } + if (!d->smart_data_valid) { + errno = ENOENT; + return -1; + } - switch (d->smart_data[362]) { - case 0x00: - case 0x80: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER; - break; + switch (d->smart_data[362]) { + case 0x00: + case 0x80: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER; + break; + + case 0x02: + case 0x82: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS; + break; + + case 0x03: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS; + break; + + case 0x04: + case 0x84: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED; + break; + + case 0x05: + case 0x85: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED; + break; + + case 0x06: + case 0x86: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL; + break; + + default: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN; + break; + } - case 0x02: - case 0x82: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS; - break; + d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF); + d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF; - case 0x03: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS; - break; + d->smart_parsed_data.total_offline_data_collection_seconds = (guint16) d->smart_data[364] | ((guint16) d->smart_data[365] << 8); - case 0x04: - case 0x84: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED; - break; + d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d); + d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d); + d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d); + d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d); - case 0x05: - case 0x85: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED; - break; + d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372]; + d->smart_parsed_data.extended_test_polling_minutes = d->smart_data[373] != 0xFF ? d->smart_data[373] : ((guint16) d->smart_data[376] << 8 | (guint16) d->smart_data[375]); + d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374]; - case 0x06: - case 0x86: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL; - break; + *spd = &d->smart_parsed_data; - default: - d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN; - break; - } + return 0; +} - d->smart_parsed_data.selftest_execution_percent_remaining = 10*(d->smart_data[363] & 0xF); +static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) { + guint8 *p; + unsigned n; - d->smart_parsed_data.total_offline_data_collection_seconds = (guint16) d->smart_data[364] | ((guint16) d->smart_data[365] << 8); + if (!d->smart_threshold_data_valid) { + a->threshold_valid = FALSE; + return; + } - d->smart_parsed_data.conveyance_test_available = !!(d->smart_data[367] & 32); - d->smart_parsed_data.short_and_extended_test_available = !!(d->smart_data[367] & 16); - d->smart_parsed_data.start_test_available = !!(d->smart_data[367] & 1); - d->smart_parsed_data.abort_test_available = !!(d->smart_data[367] & 41); + for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12) + if (p[0] == a->id) + break; - d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372]; - d->smart_parsed_data.extended_test_polling_minutes = d->smart_data[373] != 0xFF ? d->smart_data[373] : ((guint16) d->smart_data[376] << 8 | (guint16) d->smart_data[375]); - d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374]; + if (n >= 30) { + a->threshold_valid = FALSE; + return; + } - *spd = &d->smart_parsed_data; + a->threshold = p[1]; + a->threshold_valid = TRUE; - return 0; + a->bad = + a->worst_value <= a->threshold || + a->current_value <= a->threshold; } -static void find_threshold(SkDisk *d, SkSmartAttribute *a) { - guint8 *p; - unsigned n; - - if (!d->smart_threshold_data_valid) { - a->threshold_valid = FALSE; - return; - } - - for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12) - if (p[0] == a->id) - break; +int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, gpointer userdata) { + guint8 *p; + unsigned n; - if (n >= 30) { - a->threshold_valid = FALSE; - return; - } + if (!d->smart_data_valid) { + errno = ENOENT; + return -1; + } - a->threshold = p[1]; - a->threshold_valid = TRUE; + for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) { + SkSmartAttributeParsedData a; + const SkSmartAttributeInfo *i; + gchar *an = NULL; - a->bad = - a->worst_value <= a->threshold || - a->current_value <= a->threshold; -} + if (p[0] == 0) + continue; -int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeCallback cb, gpointer userdata) { - guint8 *p; - unsigned n; + memset(&a, 0, sizeof(a)); + a.id = p[0]; + a.current_value = p[3]; + a.worst_value = p[4]; - if (!d->smart_data_valid) { - errno = ENOENT; - return -1; - } + a.flags = ((guint16) p[2] << 8) | p[1]; + a.prefailure = !!(p[1] & 1); + a.online = !!(p[1] & 2); - for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) { - SkSmartAttribute a; - SkSmartAttributeInfo space; - const SkSmartAttributeInfo *i; + memcpy(a.raw, p+5, 6); - if (p[0] == 0) - continue; + if ((i = lookup_attribute(d, p[0]))) { + a.name = i->name; + a.pretty_unit = i->unit; + } else { + a.name = an = g_strdup_printf("attribute-%u", a.id); + a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN; + } - memset(&a, 0, sizeof(a)); - a.id = p[0]; - a.current_value = p[3]; - a.worst_value = p[4]; + make_pretty(&a); - a.flag = p[2]; - a.prefailure = !!(p[1] & 1); - a.online = !!(p[1] & 2); + find_threshold(d, &a); - memcpy(a.raw, p+5, 6); + cb(d, &a, userdata); - if ((i = lookup_attribute(d, p[0], &space))) { - a.name = i->name; - a.pretty_unit = i->unit; + g_free(an); } - make_pretty(&a); - - find_threshold(d, &a); - - if (cb) - cb(d, &a, userdata); - } - - return 0; + return 0; } static const char *yes_no(gboolean b) { - return b ? "yes" : "no"; + return b ? "yes" : "no"; } const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) { - const char * const map[] = { - [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL, - [SK_SMART_ATTRIBUTE_UNIT_NONE] = "", - [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms", - [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors", - [SK_SMART_ATTRIBUTE_UNIT_KELVIN] = "K" - }; + const char * const map[] = { + [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL, + [SK_SMART_ATTRIBUTE_UNIT_NONE] = "", + [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms", + [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors", + [SK_SMART_ATTRIBUTE_UNIT_KELVIN] = "K" + }; - if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX) - return NULL; + if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX) + return NULL; - return map[unit]; + return map[unit]; } static char* print_name(char *s, size_t len, guint8 id, const char *k) { - if (k) - g_strlcpy(s, k, len); - else - g_snprintf(s, len, "%u", id); + if (k) + g_strlcpy(s, k, len); + else + g_snprintf(s, len, "%u", id); - return s; + return s; } -static char *print_value(char *s, size_t len, const SkSmartAttribute *a) { +static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) { - switch (a->pretty_unit) { - case SK_SMART_ATTRIBUTE_UNIT_MSECONDS: + switch (a->pretty_unit) { + case SK_SMART_ATTRIBUTE_UNIT_MSECONDS: - if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU) - g_snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365)); - else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU) - g_snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30)); - else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU) - g_snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24)); - else if (a->pretty_value >= 1000LLU*60LLU*60LLU) - g_snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60)); - else if (a->pretty_value >= 1000LLU*60LLU) - g_snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60)); - else if (a->pretty_value >= 1000LLU) - g_snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0)); - else - g_snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value); + if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU) + g_snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365)); + else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU) + g_snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30)); + else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU) + g_snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24)); + else if (a->pretty_value >= 1000LLU*60LLU*60LLU) + g_snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60)); + else if (a->pretty_value >= 1000LLU*60LLU) + g_snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60)); + else if (a->pretty_value >= 1000LLU) + g_snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0)); + else + g_snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value); - break; + break; - case SK_SMART_ATTRIBUTE_UNIT_KELVIN: + case SK_SMART_ATTRIBUTE_UNIT_KELVIN: - g_snprintf(s, len, "%lli C", (long long) a->pretty_value - 273); - break; + g_snprintf(s, len, "%lli C", (long long) a->pretty_value - 273); + break; - case SK_SMART_ATTRIBUTE_UNIT_SECTORS: - g_snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value); - break; + case SK_SMART_ATTRIBUTE_UNIT_SECTORS: + g_snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value); + break; - case SK_SMART_ATTRIBUTE_UNIT_NONE: - g_snprintf(s, len, "%llu", (unsigned long long) a->pretty_value); - break; + case SK_SMART_ATTRIBUTE_UNIT_NONE: + g_snprintf(s, len, "%llu", (unsigned long long) a->pretty_value); + break; - case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN: - g_snprintf(s, len, "n/a"); - break; + case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN: + g_snprintf(s, len, "n/a"); + break; - case _SK_SMART_ATTRIBUTE_UNIT_MAX: - g_assert_not_reached(); - } + case _SK_SMART_ATTRIBUTE_UNIT_MAX: + g_assert_not_reached(); + } - return s; + return s; }; -static void disk_dump_attributes(SkDisk *d, const SkSmartAttribute *a, gpointer userdata) { - char name[32]; - char pretty[32]; - char t[32]; - - g_snprintf(t, sizeof(t), "%3u", a->threshold); - - g_print("%3u %-27s %3u %3u %-3s %-11s %-7s %-7s %-3s\n", - a->id, - print_name(name, sizeof(name), a->id, a->name), - a->current_value, - a->worst_value, - a->threshold_valid ? t : "n/a", - print_value(pretty, sizeof(pretty), a), - a->prefailure ? "prefail" : "old-age", - a->online ? "online" : "offline", - yes_no(!a->bad)); +#define HIGHLIGHT "\x1B[1m" +#define ENDHIGHLIGHT "\x1B[0m" + +static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, gpointer userdata) { + char name[32]; + char pretty[32]; + char t[32]; + + g_snprintf(t, sizeof(t), "%3u", a->threshold); + + if (a->bad && isatty(1)) + fprintf(stderr, HIGHLIGHT); + + g_print("%3u %-27s %3u %3u %-3s %-11s %-7s %-7s %-3s\n", + a->id, + print_name(name, sizeof(name), a->id, a->name), + a->current_value, + a->worst_value, + a->threshold_valid ? t : "n/a", + print_value(pretty, sizeof(pretty), a), + a->prefailure ? "prefail" : "old-age", + a->online ? "online" : "offline", + yes_no(!a->bad)); + + if (a->bad && isatty(1)) + fprintf(stderr, ENDHIGHLIGHT); } int sk_disk_dump(SkDisk *d) { - int ret; - gboolean powered = FALSE; - - g_print("Device: %s\n" - "Size: %lu MiB\n", - d->name, - (unsigned long) (d->size/1024/1024)); + int ret; + gboolean awake = FALSE; + + g_print("Device: %s\n" + "Size: %lu MiB\n", + d->name, + (unsigned long) (d->size/1024/1024)); + + if (d->identify_data_valid) { + const SkIdentifyParsedData *ipd; + + if ((ret = sk_disk_identify_parse(d, &ipd)) < 0) + return ret; + + g_print("Model: [%s]\n" + "Serial: [%s]\n" + "Firmware: [%s]\n" + "SMART Available: %s\n", + ipd->model, + ipd->serial, + ipd->firmware, + yes_no(disk_smart_is_available(d))); + } - if (d->identify_data_valid) { - const SkIdentifyParsedData *ipd; + ret = sk_disk_check_sleep_mode(d, &awake); + g_print("Awake: %s\n", + ret >= 0 ? yes_no(awake) : "unknown"); + + if (disk_smart_is_available(d)) { + const SkSmartParsedData *spd; + + if ((ret = sk_disk_smart_read_data(d)) < 0) + return ret; + + if ((ret = sk_disk_smart_parse(d, &spd)) < 0) + return ret; + + g_print("Off-line Data Collection Status: [%s]\n" + "Total Time To Complete Off-Line Data Collection: %u s\n" + "Self-Test Execution Status: [%s]\n" + "Percent Self-Test Remaining: %u%%\n" + "Conveyance Self-Test Available: %s\n" + "Short/Extended Self-Test Available: %s\n" + "Start Self-Test Available: %s\n" + "Abort Self-Test Available: %s\n" + "Short Self-Test Polling Time: %u min\n" + "Extended Self-Test Polling Time: %u min\n" + "Conveyance Self-Test Polling Time: %u min\n", + sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status), + spd->total_offline_data_collection_seconds, + sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status), + spd->self_test_execution_percent_remaining, + yes_no(spd->conveyance_test_available), + yes_no(spd->short_and_extended_test_available), + yes_no(spd->start_test_available), + yes_no(spd->abort_test_available), + spd->short_test_polling_minutes, + spd->extended_test_polling_minutes, + spd->conveyance_test_polling_minutes); + + g_print("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n", + "ID#", + "Name", + "Value", + "Worst", + "Thres", + "Pretty", + "Type", + "Updates", + "Good"); + + if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0) + return ret; + } - if ((ret = sk_disk_identify_parse(d, &ipd)) < 0) - return ret; - - g_print("Model: [%s]\n" - "Serial: [%s]\n" - "Firmware: [%s]\n" - "SMART Available: %s\n", - ipd->model, - ipd->serial, - ipd->firmware, - yes_no(disk_smart_is_available(d))); - } - - ret = sk_disk_check_power_mode(d, &powered); - g_print("Spin-up: %s\n", - ret >= 0 ? yes_no(powered) : "unknown"); - - if (disk_smart_is_available(d)) { - const SkSmartParsedData *spd; - - if ((ret = sk_disk_smart_read_data(d)) < 0) - return ret; - - if ((ret = sk_disk_smart_parse(d, &spd)) < 0) - return ret; - - g_print("Off-line Collection Status: %s\n" - "Percent Self-Test Remaining: %u%%\n" - "Total Time To Complete Off-Line Data Collection: %u s\n" - "Conveyance Self-Test Available: %s\n" - "Short/Extended Self-Test Available: %s\n" - "Start Self-Test Available: %s\n" - "Abort Self-Test Available: %s\n", - sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status), - spd->selftest_execution_percent_remaining, - spd->total_offline_data_collection_seconds, - yes_no(spd->conveyance_test_available), - yes_no(spd->short_and_extended_test_available), - yes_no(spd->start_test_available), - yes_no(spd->abort_test_available)); - - if (spd->short_and_extended_test_available) - g_print("Short Self-Test Polling Time: %u min\n" - "Extended Self-Test Polling Time: %u min\n", - spd->short_test_polling_minutes, - spd->extended_test_polling_minutes); - - if (spd->conveyance_test_available) - g_print("Conveyance Self-Test Polling Time: %u min\n", - spd->conveyance_test_polling_minutes); - - g_print("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n", - "ID#", - "Name", - "Value", - "Worst", - "Thres", - "Pretty", - "Type", - "Updates", - "Good"); - - if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0) - return ret; - } - - return 0; + return 0; } int sk_disk_get_size(SkDisk *d, guint64 *bytes) { - *bytes = d->size; - return 0; + *bytes = d->size; + return 0; } int sk_disk_open(const gchar *name, SkDisk **_d) { - SkDisk *d; - int ret = -1; + SkDisk *d; + int ret = -1; + struct stat st; - g_assert(name); - g_assert(_d); + g_assert(name); + g_assert(_d); - d = g_new0(SkDisk, 1); - d->name = g_strdup(name); + d = g_new0(SkDisk, 1); + d->name = g_strdup(name); - if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) { - ret = d->fd; - goto fail; - } - - if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0) - goto fail; - - if (d->size <= 0 || d->size == (guint64) -1) { - errno = EINVAL; - goto fail; - } - - /* Find a way to identify the device */ - for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++) - if (disk_identify_device(d) >= 0) - break; + if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) { + ret = d->fd; + goto fail; + } - /* Check if driver can do SMART, and enable if necessary */ - if (disk_smart_is_available(d)) { + if ((ret = fstat(d->fd, &st)) < 0) + goto fail; - if (!disk_smart_is_enabled(d)) { - if ((ret = disk_smart_enable(d, TRUE)) < 0) + if (!S_ISBLK(st.st_mode)) { + errno = ENODEV; goto fail; + } + + /* So, it's a block device. Let's make sure the ioctls work */ - if ((ret = disk_identify_device(d)) < 0) + if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0) goto fail; - if (!disk_smart_is_enabled(d)) { + if (d->size <= 0 || d->size == (guint64) -1) { errno = EIO; - ret = -1; goto fail; - } } - disk_smart_read_thresholds(d); - } + /* OK, it's a real block device with a size. Find a way to + * identify the device. */ + for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++) + if (disk_identify_device(d) >= 0) + break; - *_d = d; + /* Check if driver can do SMART, and enable if necessary */ + if (disk_smart_is_available(d)) { - return 0; + if (!disk_smart_is_enabled(d)) { + if ((ret = disk_smart_enable(d, TRUE)) < 0) + goto fail; + + if ((ret = disk_identify_device(d)) < 0) + goto fail; + + if (!disk_smart_is_enabled(d)) { + errno = EIO; + ret = -1; + goto fail; + } + } + + disk_smart_read_thresholds(d); + } + + *_d = d; + + return 0; fail: - if (d) - sk_disk_free(d); + if (d) + sk_disk_free(d); - return ret; + return ret; } void sk_disk_free(SkDisk *d) { - g_assert(d); + g_assert(d); - if (d->fd >= 0) - close(d->fd); + if (d->fd >= 0) + close(d->fd); - g_free(d->name); - g_free(d); + g_free(d->name); + g_free(d); } diff --git a/smart.h b/smart.h index cc96efb..efdc4be 100644 --- a/smart.h +++ b/smart.h @@ -1,99 +1,170 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + #ifndef foosmarthfoo #define foosmarthfoo +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + #include -typedef struct SkDisk SkDisk; +/* ATA SMART test type (ATA8 7.52.5.2) */ +typedef enum SkSmartSelfTest { + SK_SMART_SELF_TEST_SHORT = 1, + SK_SMART_SELF_TEST_EXTENDED = 2, + SK_SMART_SELF_TEST_CONVEYANCE = 3, + SK_SMART_SELF_TEST_ABORT = 127 +} SkSmartSelfTest; + +const char* sk_smart_self_test_to_string(SkSmartSelfTest test); typedef struct SkIdentifyParsedData { - gchar serial[21]; - gchar firmware[9]; - gchar model[41]; + gchar serial[21]; + gchar firmware[9]; + gchar model[41]; + + /* This structure may be extended at any time without this being + * considered an ABI change. So take care when you copy it. */ } SkIdentifyParsedData; typedef enum SkSmartOfflineDataCollectionStatus { - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER, - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS, - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS, - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED, - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED, - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL, - SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN, - _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER, + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS, + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS, + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED, + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED, + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL, + SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN, + _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX } SkSmartOfflineDataCollectionStatus; -typedef struct SkSmartParsedData { - SkSmartOfflineDataCollectionStatus offline_data_collection_status; - unsigned selftest_execution_percent_remaining; - unsigned total_offline_data_collection_seconds; - - gboolean conveyance_test_available:1; - gboolean short_and_extended_test_available:1; - gboolean start_test_available:1; - gboolean abort_test_available:1; +const char* sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status); - unsigned short_test_polling_minutes; - unsigned extended_test_polling_minutes; - unsigned conveyance_test_polling_minutes; +typedef enum SkSmartSelfTestExecutionStatus { + SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER = 0, + SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED = 1, + SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED = 2, + SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL = 3, + SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN = 4, + SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL = 5, + SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO = 6, + SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ = 7, + SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING = 8, + SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS = 15, + _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX +} SkSmartSelfTestExecutionStatus; + +const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status); - /* This structure may be extended at any time without being - * considered an ABI change. So take care when you copy it. */ +typedef struct SkSmartParsedData { + /* Volatile data */ + SkSmartOfflineDataCollectionStatus offline_data_collection_status; + unsigned total_offline_data_collection_seconds; + SkSmartSelfTestExecutionStatus self_test_execution_status; + unsigned self_test_execution_percent_remaining; + + /* Fixed data */ + gboolean short_and_extended_test_available:1; + gboolean conveyance_test_available:1; + gboolean start_test_available:1; + gboolean abort_test_available:1; + + unsigned short_test_polling_minutes; + unsigned extended_test_polling_minutes; + unsigned conveyance_test_polling_minutes; + + /* This structure may be extended at any time without this being + * considered an ABI change. So take care when you copy it. */ } SkSmartParsedData; +gboolean sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test); +unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test); + typedef enum SkSmartAttributeUnit { - SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, - SK_SMART_ATTRIBUTE_UNIT_NONE, - SK_SMART_ATTRIBUTE_UNIT_MSECONDS, - SK_SMART_ATTRIBUTE_UNIT_SECTORS, - SK_SMART_ATTRIBUTE_UNIT_KELVIN, - _SK_SMART_ATTRIBUTE_UNIT_MAX + SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, + SK_SMART_ATTRIBUTE_UNIT_NONE, + SK_SMART_ATTRIBUTE_UNIT_MSECONDS, + SK_SMART_ATTRIBUTE_UNIT_SECTORS, + SK_SMART_ATTRIBUTE_UNIT_KELVIN, + _SK_SMART_ATTRIBUTE_UNIT_MAX } SkSmartAttributeUnit; -typedef struct SkSmartAttribute { - /* Static data */ - guint8 id; - const char *name; - SkSmartAttributeUnit pretty_unit; /* for pretty value */ +const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit); + +typedef struct SkSmartAttributeParsedData { + /* Fixed data */ + guint8 id; + const char *name; + SkSmartAttributeUnit pretty_unit; /* for pretty_value */ - guint8 threshold; - gboolean threshold_valid:1; + guint16 flags; - gboolean online:1; - gboolean prefailure:1; + guint8 threshold; + gboolean threshold_valid:1; - guint8 flag; + gboolean online:1; + gboolean prefailure:1; - /* Volatile data */ - gboolean bad:1; - guint8 current_value, worst_value; - guint64 pretty_value; - guint8 raw[6]; + /* Volatile data */ + gboolean bad:1; + guint8 current_value, worst_value; + guint64 pretty_value; + guint8 raw[6]; - /* This structure may be extended at any time without being - * considered an ABI change. So take care when you copy it. */ -} SkSmartAttribute; + /* This structure may be extended at any time without this being + * considered an ABI change. So take care when you copy it. */ +} SkSmartAttributeParsedData; -typedef void (*SkSmartAttributeCallback)(SkDisk *d, const SkSmartAttribute *a, gpointer userdata); +typedef struct SkDisk SkDisk; int sk_disk_open(const gchar *name, SkDisk **d); -int sk_disk_check_power_mode(SkDisk *d, gboolean *mode); +int sk_disk_check_sleep_mode(SkDisk *d, gboolean *awake); int sk_disk_identify_is_available(SkDisk *d, gboolean *b); int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **data); int sk_disk_smart_is_available(SkDisk *d, gboolean *b); + +/* Reading SMART data might cause the disk to wake up from + * sleep. Hence from monitoring daemons make sure to call + * sk_disk_check_power_mode() to check wether the disk is sleeping and + * skip the read if so. */ int sk_disk_smart_read_data(SkDisk *d); + int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **data); -int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeCallback cb, gpointer userdata); + +typedef void (*SkSmartAttributeParseCallback)(SkDisk *d, const SkSmartAttributeParsedData *a, gpointer userdata); +int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, gpointer userdata); int sk_disk_get_size(SkDisk *d, guint64 *bytes); +int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test); + int sk_disk_dump(SkDisk *d); void sk_disk_free(SkDisk *d); -const char* sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status); -const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit); +/* TODO: + * + * Smart status + */ #endif diff --git a/smart.vapi b/smart.vapi new file mode 100644 index 0000000..e92215f --- /dev/null +++ b/smart.vapi @@ -0,0 +1,139 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + +using GLib; + +[CCode (cheader_filename="smart.h")] +namespace Smart { + + [CCode (cname="SkSmartSelfTest", cprefix="SK_SMART_SELF_TEST_")] + public enum SmartSelfTest { + SHORT, EXTENDED, CONVEYANCE, ABORT + } + + [Immutable] + [CCode (cname="SkIdentifyParsedData")] + public struct IdentifyParsedData { + public string serial; + public string firmware; + public string model; + } + + [CCode (cname="SkSmartOfflineDataCollectionStatus", cprefix="SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_")] + public enum SmartOfflineDataCollectionStatus { + NEVER, SUCCESS, INPROGRESS, SUSPENDED, ABORTED, FATAL, UNKNOWN + } + + [CCode (cname="sk_smart_offline_data_collection_status_to_string")] + public weak string smart_offline_data_collection_status_to_string(SmartOfflineDataCollectionStatus status); + + + [CCode (cname="SkSmartSelfTestExecutionStatus", cprefix="SK_SMART_SELF_TEST_EXECUTION_STATUS_")] + public enum SmartSelfTestExecutionStatus { + SUCCESS_OR_NEVER, ABORTED, INTERRUPTED, FATAL, ERROR_UNKNOWN, ERROR_ELECTRICAL, ERROR_SERVO, ERROR_READ, ERROR_HANDLING, INPROGRESS + } + + [CCode (cname="sk_smart_self_test_execution_status_to_string")] + public weak string smart_self_test_execution_status_to_string(SmartSelfTestExecutionStatus status); + + [Immutable] + [CCode (cname="SkSmartParsedData")] + public struct SmartParsedData { + public SmartOfflineDataCollectionStatus offline_data_collection_status; + public uint total_offline_data_collection_seconds; + public SmartSelfTestExecutionStatus self_test_execution_status; + public uint self_test_execution_percent_remaining; + + public bool conveyance_test_available; + public bool short_and_extended_test_available; + public bool start_test_available; + public bool abort_test_available; + + public uint short_test_polling_minutes; + public uint extended_test_polling_minutes; + public uint conveyance_test_polling_minutes; + + [CCode (cname="sk_smart_self_test_available")] + public bool self_test_available(SmartSelfTest test); + + [CCode (cname="sk_smart_self_test_polling_minutes")] + public uint self_test_polling_minutes(SmartSelfTest test); + } + + [CCode (cname="SkSmartAttributeUnit", cprefix="CK_SMART_ATTRIBUTE_UNIT")] + public enum SmartAttributeUnit { + UNKNOWN, NONE, MSECONDS, SECTORS, KELVIN + } + + [CCode (cname="sk_smart_attribute_unit_to_string")] + public weak string smart_attribute_unit_to_string(SmartAttributeUnit unit); + + [Immutable] + [CCode (cname="SkSmartAttribute")] + public struct SmartAttribute { + public uint8 id; + public char *name; + public SmartAttributeUnit pretty_unit; + public uint16 flags; + public uint8 threshold; + public bool threshold_valid; + public bool online; + public bool prefailure; + public bool bad; + public uint8 current_value; + public uint8 worst_value; + public uint64 pretty_value; + public uint8[6] raw; + } + + [Compact] + [CCode (free_function="sk_disk_free", cname="SkDisk", cprefix="sk_disk_")] + public class Disk { + + public delegate void SmartAttributeCallback(Disk d, SmartAttribute a, void* userdata); + + public static int open(string name, out Disk disk); + + public int check_sleep_mode(out bool awake); + + public int identify_is_available(out bool mode); + public int identify_parse(out weak IdentifyParsedData* data); + + public int smart_is_available(out bool mode); + public int smart_read_data(); + public int smart_parse_attributes(SmartAttributeCallback cb, void* userdata); + public int smart_parse(out weak SmartParsedData* data); + + public int get_size(out uint64 bytes); + + public int self_test(SmartSelfTest test); + + public int dump(); + } + + /* These two should move to an official vala package */ + [CCode (cname="errno", cheader_filename="errno.h")] + public int errno; + + [CCode (cname="g_strerror", cheader_filename="glib.h")] + public weak string strerror(int err); +} diff --git a/smartkitd.vala b/smartkitd.vala new file mode 100644 index 0000000..0ef72cf --- /dev/null +++ b/smartkitd.vala @@ -0,0 +1,390 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of SmartKit. + + Copyright 2008 Lennart Poettering + + libcanberra is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libcanberra is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libcanberra. If not, If not, see + . +***/ + +using GLib; +using DBus; +using Hal; +using Smart; + +errordomain Error { + SMART_NOT_AVAILABLE, + SYSTEM, + NOT_FOUND +} + +[DBus (name = "net.poettering.SmartKit.Manager")] +public interface ManagerAPI { + public abstract DBus.ObjectPath getDiskByUDI(string udi) throws Error; + public abstract DBus.ObjectPath getDiskByPath(string path) throws Error; +/* public abstract DBus.ObjectPath[] getDisks() throws Error; */ +} + +[DBus (name = "net.poettering.SmartKit.Disk")] +public interface DiskAPI { + +/* public abstract uint64 size { get; } */ + + public abstract string getPath() throws Error; + public abstract string getUDI() throws Error; + + public abstract uint64 getSize() throws Error; + + public abstract bool checkPowerMode() throws Error; + + public abstract bool isIdentifyAvailable() throws Error; + public abstract string getIdentifySerial() throws Error; + public abstract string getIdentifyFirmware() throws Error; + public abstract string getIdentifyModel() throws Error; + + public abstract bool isSmartAvailable() throws Error; + public abstract void readSmartData() throws Error; + + public abstract string getOfflineDataCollectionStatus() throws Error; + public abstract uint getSelfTestExecutionPercentRemaining() throws Error; + public abstract uint getTotalOfflineDataCollectionSeconds() throws Error; + public abstract bool getConveyanceTestAvailable() throws Error; + public abstract bool getShortAndExtendedTestAvailable() throws Error; + public abstract bool getStartTestAvailable() throws Error; + public abstract bool getAbortTestAvailable() throws Error; + + public abstract uint getShortTestPollingMinutes() throws Error; + public abstract uint getExtendedTestPollingMinutes() throws Error; + public abstract uint getConveyanceTestPollingMinutes() throws Error; +} + +public class Disk : GLib.Object, DiskAPI { + private Smart.Disk disk; + public string dbus_path; + + public string path { get; construct; } + public string udi { get; construct; } + public DBus.Connection connection { get; construct; } + + Disk(DBus.Connection connection, string path, string udi) { + this.connection = connection; + this.path = path; + this.udi = udi; + } + + private string clean_path(string s) { + var builder = new StringBuilder (); + string t; + + for (int i = 0; i < s.size(); i++) + if (s[i].isalnum() || s[i] == '_') + builder.append_unichar(s[i]); + else + builder.append_unichar('_'); + + return builder.str; + } + + public void open() throws Error { + if (Smart.Disk.open(this.path, out this.disk) < 0) + throw new Error.SYSTEM("open() failed"); + + weak Smart.IdentifyParsedData *d; + + if (this.disk.identify_parse(out d) >= 0) + this.dbus_path = "/disk/%s/%s".printf(clean_path(d->model), clean_path(d->serial)); + else + this.dbus_path = "/disk/%s".printf(clean_path(this.path)); + + stderr.printf("Registering D-Bus path %s\n", this.dbus_path); + this.connection.register_object(this.dbus_path, this); + + this.disk.smart_read_data(); + } + + public string getPath() throws Error { + return this.path; + } + + public string getUDI() throws Error { + return this.udi; + } + + public uint64 getSize() throws Error { + uint64 s; + if (this.disk.get_size(out s) < 0) + throw new Error.SYSTEM("get_size() failed: %s", Smart.strerror(Smart.errno)); + return s; + } + + public bool checkPowerMode() throws Error { + bool b; + if (this.disk.check_sleep_mode(out b) < 0) + throw new Error.SYSTEM("check_power_mode() failed: %s", Smart.strerror(Smart.errno)); + return b; + } + + public bool isIdentifyAvailable() throws Error { + bool b; + if (this.disk.identify_is_available(out b) < 0) + throw new Error.SYSTEM("identify_is_available() failed: %s", Smart.strerror(Smart.errno)); + return b; + } + + public string getIdentifySerial() throws Error { + weak Smart.IdentifyParsedData *d; + if (this.disk.identify_parse(out d) < 0) + throw new Error.SYSTEM("identify_parse() failed: %s", Smart.strerror(Smart.errno)); + return d->serial; + } + + public string getIdentifyFirmware() throws Error { + weak Smart.IdentifyParsedData *d; + if (this.disk.identify_parse(out d) < 0) + throw new Error.SYSTEM("identify_parse() failed: %s", Smart.strerror(Smart.errno)); + return d->firmware; + } + + public string getIdentifyModel() throws Error { + weak Smart.IdentifyParsedData *d; + if (this.disk.identify_parse(out d) < 0) + throw new Error.SYSTEM("identify_parse() failed: %s", Smart.strerror(Smart.errno)); + return d->model; + } + + public bool isSmartAvailable() throws Error { + bool b; + if (this.disk.smart_is_available(out b) < 0) + throw new Error.SYSTEM("smart_is_available() failed: %s", Smart.strerror(Smart.errno)); + return b; + } + + public void readSmartData() throws Error { + if (this.disk.smart_read_data() < 0) + throw new Error.SYSTEM("smart_read_data() failed: %s", Smart.strerror(Smart.errno)); + } + + public string getOfflineDataCollectionStatus() throws Error { + weak Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + switch (d->offline_data_collection_status) { + case SmartOfflineDataCollectionStatus.NEVER: + return "never"; + case SmartOfflineDataCollectionStatus.SUCCESS: + return "success"; + case SmartOfflineDataCollectionStatus.INPROGRESS: + return "inprogress"; + case SmartOfflineDataCollectionStatus.SUSPENDED: + return "suspended"; + case SmartOfflineDataCollectionStatus.ABORTED: + return "aborted"; + case SmartOfflineDataCollectionStatus.FATAL: + return "fatal"; + default: + return "unknown"; + } + } + + public uint getSelfTestExecutionPercentRemaining() throws Error { + weak Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->self_test_execution_percent_remaining; + } + + public uint getTotalOfflineDataCollectionSeconds() throws Error { + weak Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->total_offline_data_collection_seconds; + } + + public bool getConveyanceTestAvailable() throws Error { + weak Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->conveyance_test_available; + } + + public bool getShortAndExtendedTestAvailable() throws Error { + weak Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->short_and_extended_test_available; + } + + public bool getStartTestAvailable() throws Error { + Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->start_test_available; + } + + public bool getAbortTestAvailable() throws Error { + Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->abort_test_available; + } + + public uint getShortTestPollingMinutes() throws Error { + Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->short_test_polling_minutes; + } + + public uint getExtendedTestPollingMinutes() throws Error { + Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->extended_test_polling_minutes; + } + + public uint getConveyanceTestPollingMinutes() throws Error { + Smart.SmartParsedData *d; + + if (this.disk.smart_parse(out d) < 0) + throw new Error.SYSTEM("smart_parse() failed: %s", Smart.strerror(Smart.errno)); + + return d->conveyance_test_polling_minutes; + } + +/* public uint64 size { */ +/* get { */ +/* uint64 s; */ +/* this.disk.get_size(out s); */ +/* return s; */ + +/* } */ +/* } */ +} + +public class Manager : GLib.Object, ManagerAPI { + public DBus.Connection connection { get; construct; } + public List disks; + + public DBus.RawConnection raw_connection; + public Hal.Context hal_context; + + Manager(DBus.Connection connection) { + this.connection = connection; + } + + public void start() throws Error { + DBus.RawError err; + + this.connection.register_object("/", this); + + this.raw_connection = DBus.RawBus.get(DBus.BusType.SYSTEM, ref err); + + this.hal_context = new Hal.Context(); + this.hal_context.set_dbus_connection(this.raw_connection); + + string[] haldisks = this.hal_context.find_device_by_capability("storage", ref err); + + foreach (string udi in haldisks) { + string bdev = this.hal_context.device_get_property_string(udi, "block.device", ref err); + + stderr.printf("Found device %s\n", bdev); + + try { + Disk disk = new Disk(this.connection, bdev, udi); + disk.open(); + this.disks.append(#disk); + } catch (Error e) { + stderr.printf("Failed to open disk %s: %s\n", bdev, e.message); + } + } + } + + public DBus.ObjectPath getDiskByUDI(string udi) throws Error { + + foreach (Disk d in this.disks) + if (d.udi == udi) + return new DBus.ObjectPath(d.dbus_path); + + throw new Error.NOT_FOUND("Device not found"); + } + + public DBus.ObjectPath getDiskByPath(string path) throws Error { + foreach (Disk d in this.disks) + if (d.path == path) + return new DBus.ObjectPath(d.dbus_path); + + throw new Error.NOT_FOUND("Device not found"); + } + +/* public DBus.ObjectPath[] getDisks() throws Error { */ +/* DBus.ObjectPath[] o = new DBus.ObjectPath[this.disks.length()]; */ + +/* int i = 0; */ +/* foreach (Disk d in this.disks) */ +/* o[i++] = new DBus.ObjectPath(d.dbus_path); */ + +/* return o; */ +/* } */ + +} + + + +int main() { + + try { + var c = DBus.Bus.get(DBus.BusType.SYSTEM); + + dynamic DBus.Object bus = c.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus"); + + uint request_name_result = bus.RequestName("net.poettering.SmartKit", (uint) 0); + + if (request_name_result == DBus.RequestNameReply.PRIMARY_OWNER) { + + MainLoop loop = new MainLoop(null, false); + Manager manager = new Manager(c); + + manager.start(); + + stdout.printf("Started\n"); + loop.run(); + } + + } catch (Error e) { + stderr.printf("Error: %s\n", e.message); + return 1; + } + + return 0; +} -- cgit