From: David Zeuthen Date: Mon, 6 Apr 2009 15:51:15 +0000 (-0400) Subject: rework some of the ATA SMART stuff, go back to using sqlite X-Git-Tag: upstream/2.1.2~887 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=daffd3414672a2344806e697137ec12a940c34cf;p=platform%2Fupstream%2Fudisks2.git rework some of the ATA SMART stuff, go back to using sqlite --- diff --git a/configure.ac b/configure.ac index 14d178f..5d3dd71 100644 --- a/configure.ac +++ b/configure.ac @@ -107,6 +107,20 @@ if test "x$GCC" = "xyes"; then changequote([,])dnl fi +have_zlib="false" +AC_CHECK_LIB([z], [compress2], [have_zlib="true"]) +if test x$have_zlib != "xtrue"; then + AC_MSG_ERROR([zlib is needed]) +fi +ZLIB_CFLAGS= +ZLIB_LIBS="-lz" +AC_SUBST(ZLIB_CFLAGS) +AC_SUBST(ZLIB_LIBS) + +PKG_CHECK_MODULES(SQLITE3, [sqlite3]) +AC_SUBST(SQLITE3_CFLAGS) +AC_SUBST(SQLITE3_LIBS) + PKG_CHECK_MODULES(DEVKIT, [devkit-gobject >= 002]) AC_SUBST(DEVKIT_GOBJECT_CFLAGS) AC_SUBST(DEVKIT_GOBJECT_LIBS) @@ -139,7 +153,7 @@ PKG_CHECK_MODULES(DEVMAPPER, [devmapper >= 1.02]) AC_SUBST(DEVMAPPER_CFLAGS) AC_SUBST(DEVMAPPER_LIBS) -PKG_CHECK_MODULES(LIBATASMART, [libatasmart >= 0.2]) +PKG_CHECK_MODULES(LIBATASMART, [libatasmart >= 0.5]) AC_SUBST(LIBATASMART_CFLAGS) AC_SUBST(LIBATASMART_LIBS) diff --git a/src/Makefile.am b/src/Makefile.am index a59cb0b..e2e40ed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -50,6 +50,7 @@ devkit_disks_daemon_SOURCES = \ devkit-disks-mount.h devkit-disks-mount.c \ devkit-disks-mount-monitor.h devkit-disks-mount-monitor.c \ devkit-disks-inhibitor.h devkit-disks-inhibitor.c \ + devkit-disks-ata-smart-db.h devkit-disks-ata-smart-db.c \ devkit-disks-poller.h devkit-disks-poller.c \ main.c \ $(BUILT_SOURCES) @@ -57,16 +58,25 @@ devkit_disks_daemon_SOURCES = \ devkit_disks_daemon_CPPFLAGS = \ -I$(top_srcdir)/src \ -DG_LOG_DOMAIN=\"devkit-disks-daemon\" \ - $(SQLITE3_CFLAGS) \ $(DISABLE_DEPRECATED) \ - $(AM_CPPFLAGS) + $(AM_CPPFLAGS) \ + $(NULL) + +devkit_disks_daemon_CFLAGS = \ + $(ZLIB_CFLAGS) \ + $(SQLITE3_CFLAGS) \ + $(LIBATASMART_CFLAGS) \ + $(NULL) devkit_disks_daemon_LDADD = \ $(GIO_LIBS) \ - $(SQLITE3_LIBS) \ $(DBUS_GLIB_LIBS) \ $(POLKIT_DBUS_LIBS) \ - $(DEVKIT_LIBS) + $(DEVKIT_LIBS) \ + $(ZLIB_LIBS) \ + $(SQLITE3_LIBS) \ + $(LIBATASMART_LIBS) \ + $(NULL) noinst_LTLIBRARIES = libpartutil.la libpartutil_la_SOURCES = partutil.h partutil.c @@ -178,4 +188,3 @@ clean-local : install-data-local: -$(mkdir_p) $(DESTDIR)$(localstatedir)/lib/DeviceKit-disks -chmod 0700 $(DESTDIR)$(localstatedir)/lib/DeviceKit-disks - -$(mkdir_p) $(DESTDIR)$(localstatedir)/lib/DeviceKit-disks/ata-smart diff --git a/src/devkit-disks-ata-smart-db.c b/src/devkit-disks-ata-smart-db.c new file mode 100644 index 0000000..e3410b9 --- /dev/null +++ b/src/devkit-disks-ata-smart-db.c @@ -0,0 +1,784 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 David Zeuthen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devkit-disks-daemon.h" +#include "devkit-disks-device.h" +#include "devkit-disks-device-private.h" +#include "devkit-disks-ata-smart-db.h" + +struct DevkitDisksAtaSmartDbPrivate +{ + sqlite3 *db; +}; + +G_DEFINE_TYPE (DevkitDisksAtaSmartDb, devkit_disks_ata_smart_db, G_TYPE_OBJECT) + +#define DEVKIT_DISKS_ATA_SMART_DB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DEVKIT_DISKS_TYPE_DEVICE, DevkitDisksDevicePrivate)) + + +static void +devkit_disks_ata_smart_db_finalize (GObject *object) +{ + DevkitDisksAtaSmartDb *db = DEVKIT_DISKS_ATA_SMART_DB (object); + + if (db->priv->db != NULL) + sqlite3_close (db->priv->db); + + if (G_OBJECT_CLASS (devkit_disks_ata_smart_db_parent_class)->finalize != NULL) + G_OBJECT_CLASS (devkit_disks_ata_smart_db_parent_class)->finalize (object); +} + +static void +devkit_disks_ata_smart_db_class_init (DevkitDisksAtaSmartDbClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + g_type_class_add_private (klass, sizeof (DevkitDisksAtaSmartDbPrivate)); + + object_class->finalize = devkit_disks_ata_smart_db_finalize; +} + +static void +devkit_disks_ata_smart_db_init (DevkitDisksAtaSmartDb *db) +{ + gint ret; + gchar *err_msg; + + db->priv = G_TYPE_INSTANCE_GET_PRIVATE (db, DEVKIT_DISKS_TYPE_ATA_SMART_DB, DevkitDisksAtaSmartDbPrivate); + + ret = sqlite3_open_v2 (PACKAGE_LOCALSTATE_DIR "/lib/DeviceKit-disks/ata-smart-db.sqlite3", + &db->priv->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL); + if (ret != SQLITE_OK) { + g_warning ("error opening sqlite3 database at " + PACKAGE_LOCALSTATE_DIR "/lib/DeviceKit-disks/ata-smart-db.sqlite3" + ": %s", sqlite3_errmsg (db->priv->db)); + sqlite3_close (db->priv->db); + g_object_unref (db); + goto out; + } + + /* create tables */ + ret = sqlite3_exec (db->priv->db, + "CREATE TABLE AtaSmartEntry (" + " disk_id TEXT, " + " time_collected INTEGER, " + " is_failing INTEGER," + " is_failing_valid INTEGER," + " has_bad_sectors INTEGER," + " has_bad_attributes INTEGER," + " temperature_kelvin REAL," + " power_on_secs INTEGER," + " compressed_data BLOB " + ");", + NULL, + NULL, + &err_msg); + if (ret != SQLITE_OK) { + g_warning ("SQL error creating tables: %s", err_msg); + sqlite3_free (err_msg); + } + + out: + ; +} + +DevkitDisksAtaSmartDb * +devkit_disks_ata_smart_db_new (void) +{ + return DEVKIT_DISKS_ATA_SMART_DB (g_object_new (DEVKIT_DISKS_TYPE_ATA_SMART_DB, NULL)); +} + +static gchar * +get_disk_id (DevkitDisksDevice *device) +{ + gchar *s; + gchar *result; + + result = NULL; + + if (device->priv->drive_vendor == NULL || strlen (device->priv->drive_vendor) == 0) + goto out; + if (device->priv->drive_model == NULL || strlen (device->priv->drive_model) == 0) + goto out; + if (device->priv->drive_revision == NULL || strlen (device->priv->drive_revision) == 0) + goto out; + if (device->priv->drive_serial == NULL || strlen (device->priv->drive_serial) == 0) + goto out; + + s = g_strdup_printf ("%s_%s_%s_%s", + device->priv->drive_vendor, + device->priv->drive_model, + device->priv->drive_revision, + device->priv->drive_serial); + result = g_uri_escape_string (s, NULL, FALSE); + g_free (s); + +out: + return result; +} + +void +devkit_disks_ata_smart_db_add_entry (DevkitDisksAtaSmartDb *db, + DevkitDisksDevice *device, + time_t time_collected, + gboolean is_failing, + gboolean is_failing_valid, + gboolean has_bad_sectors, + gboolean has_bad_attributes, + gdouble temperature_kelvin, + guint64 power_on_seconds, + const void *blob, + gsize blob_size) +{ + gint ret; + char *s; + gchar *disk_id; + sqlite3_stmt *stmt; + guchar *compressed_blob; + uLongf compressed_blob_size; + + s = NULL; + stmt = NULL; + disk_id = NULL; + compressed_blob = NULL; + + if (db->priv->db == NULL) { + g_warning ("No database"); + goto out; + } + + disk_id = get_disk_id (device); + if (disk_id == NULL) { + g_warning ("Error getting stable ID for device"); + goto out; + } + + /* compress the data */ + compressed_blob = g_new0 (guchar, blob_size * 3 / 2 + 32); + ret = compress2 (compressed_blob, &compressed_blob_size, + blob, blob_size, + 1); + if (ret != Z_OK) { + g_warning ("Error compressing blob: %d", ret); + goto out; + } + + //g_debug ("Compressed %ld bytes into %ld bytes", blob_size, compressed_blob_size); + + /* insert it into the database */ + s = sqlite3_mprintf ("INSERT INTO AtaSmartEntry " + "(disk_id, " + "time_collected, " + "is_failing, " + "is_failing_valid, " + "has_bad_sectors, " + "has_bad_attributes, " + "temperature_kelvin, " + "power_on_secs, " + "compressed_data) " + "VALUES ('%q', " + "%" G_GUINT64_FORMAT ", " + "%d, " + "%d, " + "%d, " + "%d, " + "%g, " + "%" G_GUINT64_FORMAT ", " + "?)", + disk_id, + (guint64) time_collected, + is_failing, + is_failing_valid, + has_bad_sectors, + has_bad_attributes, + temperature_kelvin, + power_on_seconds); + ret = sqlite3_prepare_v2 (db->priv->db, + s, + -1, + &stmt, + NULL); + if (ret != SQLITE_OK) { + g_warning ("SQL error preparing statement: %d", ret); + goto out; + } + ret = sqlite3_bind_blob (stmt, + 1, + compressed_blob, + compressed_blob_size, + SQLITE_STATIC); + if (ret != SQLITE_OK) { + g_warning ("SQL error binding BLOB: %d", ret); + goto out; + } + ret = sqlite3_step (stmt); + if (ret != SQLITE_DONE) { + g_warning ("SQL error executing statement: %d", ret); + goto out; + } + + out: + g_free (compressed_blob); + g_free (disk_id); + if (s != NULL) + sqlite3_free (s); + if (stmt != NULL) + sqlite3_finalize (stmt); +} + +void +devkit_disks_ata_smart_db_delete_entries (DevkitDisksAtaSmartDb *db, + time_t cut_off_point) +{ + char *s; + gint ret; + sqlite3_stmt *stmt; + + s = NULL; + stmt = NULL; + + s = sqlite3_mprintf ("DELETE FROM AtaSmartEntry WHERE time_collected < %"G_GUINT64_FORMAT ";", + (guint64) cut_off_point); + ret = sqlite3_prepare_v2 (db->priv->db, + s, + -1, + &stmt, + NULL); + if (ret != SQLITE_OK) { + g_warning ("SQL error preparing statement: %d", ret); + goto out; + } + ret = sqlite3_step (stmt); + if (ret != SQLITE_DONE) { + g_warning ("SQL error executing statement: %d", ret); + goto out; + } + + out: + if (s != NULL) + sqlite3_free (s); + if (stmt != NULL) + sqlite3_finalize (stmt); +} + +gboolean +devkit_disks_ata_smart_db_get_entries (DevkitDisksAtaSmartDb *db, + DevkitDisksDevice *device, + time_t since, + time_t until, + guint64 spacing, + DevkitDisksAtaSmartDbGetEntriesFunc callback, + gpointer user_data) +{ + gboolean ret; + char *s; + sqlite3_stmt *stmt; + gchar *disk_id; + guint64 last_time_collected; + + ret = FALSE; + stmt = NULL; + + if (db->priv->db == NULL) { + g_warning ("No database"); + goto out; + } + + disk_id = get_disk_id (device); + if (disk_id == NULL) { + g_warning ("Error getting stable ID for device"); + goto out; + } + + s = sqlite3_mprintf ("SELECT" + " AtaSmartEntry.time_collected," + " AtaSmartEntry.compressed_data, " + " AtaSmartEntry.is_failing, " + " AtaSmartEntry.is_failing_valid, " + " AtaSmartEntry.has_bad_sectors, " + " AtaSmartEntry.has_bad_attributes, " + " AtaSmartEntry.temperature_kelvin, " + " AtaSmartEntry.power_on_secs " + "FROM AtaSmartEntry " + "WHERE" + " AtaSmartEntry.disk_id='%q' AND" + " AtaSmartEntry.time_collected >= %" G_GUINT64_FORMAT " AND" + " AtaSmartEntry.time_collected <= %" G_GUINT64_FORMAT " " + "ORDER BY AtaSmartEntry.time_collected;", + disk_id, + since, + until); + ret = sqlite3_prepare_v2 (db->priv->db, + s, + -1, + &stmt, + NULL); + if (ret != SQLITE_OK) { + g_warning ("SQL error preparing statement: %d", ret); + goto out; + } + + last_time_collected = 0; + do { + guint64 time_collected; + const void *compressed_blob; + gsize compressed_blob_size; + static guchar blob[2048]; /* assume 2k is enough */ + uLongf blob_size; + gint rc; + gboolean is_failing; + gboolean is_failing_valid; + gboolean has_bad_sectors; + gboolean has_bad_attributes; + gdouble temperature_kelvin; + guint64 power_on_seconds; + + ret = sqlite3_step (stmt); + + if (ret == SQLITE_DONE) + break; + + if (ret != SQLITE_ROW) { + g_warning ("SQL error stepping: %d", ret); + goto out; + } + + time_collected = sqlite3_column_int64 (stmt, 0); + + if (time_collected < (guint64) since) + continue; + if (time_collected > (guint64) until) + continue; + if (time_collected - last_time_collected < spacing) + continue; + + last_time_collected = time_collected; + + + compressed_blob = sqlite3_column_blob (stmt, 1); + compressed_blob_size = sqlite3_column_bytes (stmt, 1); + + is_failing = sqlite3_column_int (stmt, 2); + is_failing_valid = sqlite3_column_int (stmt, 3); + has_bad_sectors = sqlite3_column_int (stmt, 4); + has_bad_attributes = sqlite3_column_int (stmt, 5); + temperature_kelvin = sqlite3_column_double (stmt, 6); + power_on_seconds = sqlite3_column_int64 (stmt, 7); + + blob_size = sizeof blob; + rc = uncompress (blob, &blob_size, compressed_blob, compressed_blob_size); + if (rc != Z_OK) { + g_warning ("Decompression of compressed blob of size %d from time %" G_GUINT64_FORMAT + " for device %s FAILED with return code %d. Ignoring.", + (gint) compressed_blob_size, + time_collected, + device->priv->device_file, + rc); + continue; + } + + //g_debug ("haz row %ld %ld %ld", time_collected, compressed_blob_size, blob_size); + + callback (time_collected, + is_failing, + is_failing_valid, + has_bad_sectors, + has_bad_attributes, + temperature_kelvin, + power_on_seconds, + blob, + blob_size, + user_data); + + } while (ret != SQLITE_DONE); + + + out: + g_free (disk_id); + if (s != NULL) + sqlite3_free (s); + if (stmt != NULL) + sqlite3_finalize (stmt); + return ret; +} + +#if 0 +void +devkit_disks_ata_smart_db_record_smart_values (DevkitDisksAtaSmartDb *ata_smart_db, + DevkitDisksDevice *device) +{ + int n; + int ret; + char *err_msg; + char *s; + char *disk_id; + sqlite3_int64 row_id; + GString *str; + + g_return_if_fail (device != NULL); + g_return_if_fail (ata_smart_db != NULL); + g_return_if_fail (ata_smart_db->priv->db != NULL); + + disk_id = NULL; + + disk_id = drive_get_safe_uuid (device); + if (disk_id == NULL) { + g_warning ("no drive uuid for %s", device->priv->native_path); + goto out; + } + + s = sqlite3_mprintf ( + "BEGIN TRANSACTION;" + "INSERT INTO SmartEntry " + "(disk_id, time_collected, temperature, time_powered_on, last_self_test_result, is_failing) " + "VALUES ('%q', %" G_GUINT64_FORMAT ", %d, %" G_GUINT64_FORMAT ", '%q', %d)", + disk_id, + device->priv->drive_smart_time_collected, + (int) device->priv->drive_smart_temperature, + device->priv->drive_smart_time_powered_on, + device->priv->drive_smart_last_self_test_result, + device->priv->drive_smart_is_failing ? 1 : 0); + ret = sqlite3_exec (ata_smart_db->priv->db, s, NULL, NULL, &err_msg); + sqlite3_free (s); + if (ret != SQLITE_OK) { + g_warning ("SQL error: %s", err_msg); + sqlite3_free (err_msg); + goto out; + } + + row_id = sqlite3_last_insert_rowid (ata_smart_db->priv->db); + + str = g_string_new (NULL); + for (n = 0; n < (int) device->priv->drive_smart_attributes->len; n++) { + GValue elem = {0}; + int id; + char *name; + int flags; + int value; + int worst; + int threshold; + char *raw_string; + + g_value_init (&elem, SMART_DATA_STRUCT_TYPE); + g_value_set_static_boxed (&elem, device->priv->drive_smart_attributes->pdata[n]); + dbus_g_type_struct_get (&elem, + 0, &id, + 1, &name, + 2, &flags, + 3, &value, + 4, &worst, + 5, &threshold, + 6, &raw_string, + G_MAXUINT); + + s = sqlite3_mprintf ( + "INSERT INTO SmartAttr " + "VALUES (%" G_GINT64_FORMAT ", '%q', %" G_GUINT64_FORMAT ", %d, '%q', %d, %d, %d, %d, '%q');\n", + row_id, + disk_id, + device->priv->drive_smart_time_collected, + id, + name, + flags, + value, + worst, + threshold, + raw_string); + g_string_append (str, s); + sqlite3_free (s); + } + + g_string_append_printf (str, "COMMIT;"); + + s = g_string_free (str, FALSE); + ret = sqlite3_exec (ata_smart_db->priv->db, s, NULL, NULL, &err_msg); + g_free (s); + if (ret != SQLITE_OK) { + g_warning ("SQL error: %s", err_msg); + sqlite3_free (err_msg); + } +out: + g_free (disk_id); +} + +static gboolean +throw_error (DBusGMethodInvocation *context, int error_code, const char *format, ...) +{ + GError *error; + va_list args; + char *message; + + if (context == NULL) + return TRUE; + + va_start (args, format); + message = g_strdup_vprintf (format, args); + va_end (args); + + error = g_error_new (DEVKIT_DISKS_ERROR, + error_code, + "%s", message); + dbus_g_method_return_error (context, error); + g_error_free (error); + g_free (message); + return TRUE; +} + +typedef struct { + GPtrArray *array; + + gint64 cur_rowid; + gboolean needs_draining; + + guint64 time_collected; + double temperature; + guint64 time_powered_on; + char last_self_test_result[256]; + gboolean is_failing; + GPtrArray *attrs; +} HistoricalData; + +static void +historical_data_drain (HistoricalData *data) +{ + GValue elem = {0}; + + if (!data->needs_draining) + return; + + g_value_init (&elem, HISTORICAL_SMART_DATA_STRUCT_TYPE); + g_value_take_boxed (&elem, dbus_g_type_specialized_construct (HISTORICAL_SMART_DATA_STRUCT_TYPE)); + dbus_g_type_struct_set (&elem, + 0, data->time_collected, + 1, data->temperature, + 2, data->time_powered_on, + 3, data->last_self_test_result, + 4, data->is_failing, + 5, data->attrs, + G_MAXUINT); + g_ptr_array_add (data->array, g_value_get_boxed (&elem)); + + g_ptr_array_foreach (data->attrs, (GFunc) g_value_array_free, NULL); + g_ptr_array_free (data->attrs, TRUE); + data->attrs = NULL; + data->needs_draining = FALSE; +} + +static int +historical_data_cb (void *user_data, int argc, char **argv, char **col_name) +{ + HistoricalData *data = (HistoricalData *) user_data; + gint64 rowid; + int id; + const char *name; + int flags; + int value; + int worst; + int threshold; + const char *raw; + + if (argc != 13) { + g_warning ("expected 13 columns, got %d instead", argc); + goto out; + } + + /* TODO: could add checks for the column types */ + + rowid = atoll (argv[0]); + if (rowid != data->cur_rowid) { + if (data->needs_draining) { + historical_data_drain (data); + } + + data->needs_draining = TRUE; + data->cur_rowid = rowid; + data->time_collected = atoll (argv[1]); + data->temperature = atof (argv[2]); + data->time_powered_on = atoll (argv[3]); + strncpy (data->last_self_test_result, argv[4], 256); + data->is_failing = (strcmp (argv[5], "0") != 0); + data->attrs = g_ptr_array_new (); + + /*g_warning ("got time_collected=%lld temperature=%g time_powered_on=%lld lstr='%s' is_failing=%d", + data->time_collected, + data->temperature, + data->time_powered_on, + data->last_self_test_result, + data->is_failing);*/ + } + + id = atoi (argv[6]); + name = argv[7]; + flags = atoi (argv[8]); + value = atoi (argv[9]); + worst = atoi (argv[10]); + threshold = atoi (argv[11]); + raw = argv[12]; + + /*g_warning ("got id=%d name='%s' flags=0x%04x value=%d worst=%d threshold=%d raw='%s'", + id, name, flags, value, worst, threshold, raw);*/ + + GValue elem = {0}; + g_value_init (&elem, SMART_DATA_STRUCT_TYPE); + g_value_take_boxed (&elem, dbus_g_type_specialized_construct (SMART_DATA_STRUCT_TYPE)); + dbus_g_type_struct_set (&elem, + 0, id, + 1, name, + 2, flags, + 3, value, + 4, worst, + 5, threshold, + 6, raw, + G_MAXUINT); + g_ptr_array_add (data->attrs, g_value_get_boxed (&elem)); + + + /* + int n; + for (n = 0; n < argc; n++) { + printf ("%s = %s\n", col_name[n], argv[n] ? argv[n] : "NULL"); + } + printf("\n"); + */ + +out: + return 0; +} + +gboolean +devkit_disks_device_drive_smart_get_historical_data (DevkitDisksDevice *device, + guint64 from, + guint64 to, + DBusGMethodInvocation *context) +{ + char *s; + char *disk_id; + GTimeVal now; + int ret; + char *err_msg; + DevkitDisksAtaSmartDb *ata_smart_db; + HistoricalData *data; + PolKitCaller *pk_caller; + + disk_id = NULL; + pk_caller = NULL; + + if (context != NULL) { + if ((pk_caller = devkit_disks_damon_local_get_caller_for_context (device->priv->daemon, + context)) == NULL) + goto out; + } + + if (context != NULL) { + if (!devkit_disks_damon_local_check_auth ( + device->priv->daemon, + pk_caller, + "org.freedesktop.devicekit.disks.drive-smart-retrieve-historical-data", + context)) { + goto out; + } + } + + ata_smart_db = devkit_disks_daemon_local_get_ata_smart_db (device->priv->daemon); + + disk_id = drive_get_safe_uuid (device); + if (disk_id == NULL) { + g_warning ("no drive uuid for %s", device->priv->native_path); + throw_error (context, DEVKIT_DISKS_ERROR_FAILED, "No unique disk id for device"); + goto out; + } + + if (from > to) { + throw_error (context, DEVKIT_DISKS_ERROR_FAILED, "Malformed time range (from > to)"); + goto out; + } + + if (to == 0) { + g_get_current_time (&now); + to = (guint64) now.tv_sec; + } + + data = g_new0 (HistoricalData, 1); + data->array = g_ptr_array_new (); + data->cur_rowid = -1; + + s = sqlite3_mprintf ("SELECT" + " SmartEntry.smart_entry_id," + " SmartEntry.time_collected," + " SmartEntry.temperature," + " SmartEntry.time_powered_on," + " SmartEntry.last_self_test_result," + " SmartEntry.is_failing," + " SmartAttr.id," + " SmartAttr.name," + " Smartattr.flags, " + " SmartAttr.value," + " SmartAttr.worst," + " SmartAttr.threshold," + " SmartAttr.raw " + "FROM SmartEntry, SmartAttr " + "WHERE" + " SmartEntry.disk_id='%q' AND" + " SmartEntry.smart_entry_id=SmartAttr.smart_entry_id AND" + " SmartEntry.time_collected >= %" G_GUINT64_FORMAT " AND" + " SmartEntry.time_collected <= %" G_GUINT64_FORMAT " " + "ORDER BY SmartEntry.smart_entry_id, SmartAttr.id;", + disk_id, from, to); + ret = sqlite3_exec (ata_smart_db->priv->db, + s, + historical_data_cb, + data, + &err_msg); + if (ret != SQLITE_OK) { + g_warning ("SQL error: %s", err_msg); + sqlite3_free (err_msg); + } + sqlite3_free (s); + + historical_data_drain (data); + dbus_g_method_return (context, data->array); + g_ptr_array_foreach (data->array, (GFunc) g_value_array_free, NULL); + g_ptr_array_free (data->array, TRUE); + g_free (data); + +out: + g_free (disk_id); + if (pk_caller != NULL) + polkit_caller_unref (pk_caller); + return TRUE; +} +#endif diff --git a/src/devkit-disks-ata-smart-db.h b/src/devkit-disks-ata-smart-db.h new file mode 100644 index 0000000..100d0ee --- /dev/null +++ b/src/devkit-disks-ata-smart-db.h @@ -0,0 +1,85 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 David Zeuthen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __DEVKIT_DISKS_ATA_SMART_DB_H__ +#define __DEVKIT_DISKS_ATA_SMART_DB_H__ + +#include "devkit-disks-types.h" + +G_BEGIN_DECLS + +#define DEVKIT_DISKS_TYPE_ATA_SMART_DB (devkit_disks_ata_smart_db_get_type ()) +#define DEVKIT_DISKS_ATA_SMART_DB(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DEVKIT_DISKS_TYPE_ATA_SMART_DB, DevkitDisksAtaSmartDb)) +#define DEVKIT_DISKS_ATA_SMART_DB_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), DEVKIT_DISKS_TYPE_ATA_SMART_DB, DevkitDisksAtaSmartDbClass)) +#define DEVKIT_DISKS_IS_ATA_SMART_DB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DEVKIT_DISKS_TYPE_ATA_SMART_DB)) +#define DEVKIT_DISKS_IS_ATA_SMART_DB_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DEVKIT_DISKS_TYPE_ATA_SMART_DB)) +#define DEVKIT_DISKS_ATA_SMART_DB_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DEVKIT_DISKS_TYPE_ATA_SMART_DB, DevkitDisksAtaSmartDbClass)) + +typedef struct DevkitDisksAtaSmartDbClass DevkitDisksAtaSmartDbClass; +typedef struct DevkitDisksAtaSmartDbPrivate DevkitDisksAtaSmartDbPrivate; + +struct DevkitDisksAtaSmartDb +{ + GObject parent; + DevkitDisksAtaSmartDbPrivate *priv; +}; + +struct DevkitDisksAtaSmartDbClass +{ + GObjectClass parent_class; +}; + +typedef gboolean (*DevkitDisksAtaSmartDbGetEntriesFunc) (time_t time_collected, + gboolean is_failing, + gboolean is_failing_valid, + gboolean has_bad_sectors, + gboolean has_bad_attributes, + gdouble temperature_kelvin, + guint64 power_on_seconds, + const void *blob, + gsize blob_size, + gpointer user_data); + +GType devkit_disks_ata_smart_db_get_type (void) G_GNUC_CONST; +DevkitDisksAtaSmartDb *devkit_disks_ata_smart_db_new (void); +void devkit_disks_ata_smart_db_add_entry (DevkitDisksAtaSmartDb *db, + DevkitDisksDevice *device, + time_t time_collected, + gboolean is_failing, + gboolean is_failing_valid, + gboolean has_bad_sectors, + gboolean has_bad_attributes, + gdouble temperature_kelvin, + guint64 power_on_seconds, + const void *blob, + gsize blob_size); +void devkit_disks_ata_smart_db_delete_entries (DevkitDisksAtaSmartDb *db, + time_t cut_off_point); +gboolean devkit_disks_ata_smart_db_get_entries (DevkitDisksAtaSmartDb *db, + DevkitDisksDevice *device, + time_t since, + time_t until, + guint64 spacing, + DevkitDisksAtaSmartDbGetEntriesFunc callback, + gpointer user_data); + +G_END_DECLS + +#endif /* __DEVKIT_DISKS_ATA_SMART_DB_H__ */ diff --git a/src/devkit-disks-daemon.c b/src/devkit-disks-daemon.c index 8cad73c..3eaf065 100644 --- a/src/devkit-disks-daemon.c +++ b/src/devkit-disks-daemon.c @@ -24,6 +24,20 @@ #define _GNU_SOURCE 1 +/* ---------------------------------------------------------------------------------------------------- */ +/* We might want these things to be configurable; for now they are hardcoded */ + +/* update ATA SMART every 30 minutes */ +#define ATA_SMART_REFRESH_INTERVAL_SECONDS (30*60) + +/* clean up old ATA SMART entries every 24 hours (and on startup) */ +#define ATA_SMART_CLEANUP_INTERVAL_SECONDS (24*60*60) + +/* delete entries older than five days */ +#define ATA_SMART_KEEP_ENTRIES_SECONDS (5*24*60*60) + +/* ---------------------------------------------------------------------------------------------------- */ + #include #include #include @@ -57,6 +71,7 @@ #include "devkit-disks-mount-monitor.h" #include "devkit-disks-poller.h" #include "devkit-disks-inhibitor.h" +#include "devkit-disks-ata-smart-db.h" #include "devkit-disks-daemon-glue.h" #include "devkit-disks-marshal.h" @@ -102,7 +117,10 @@ struct DevkitDisksDaemonPrivate DevkitDisksMountMonitor *mount_monitor; + DevkitDisksAtaSmartDb *ata_smart_db; + guint ata_smart_refresh_timer_id; + guint ata_smart_cleanup_timer_id; GList *polling_inhibitors; @@ -301,6 +319,12 @@ static const DevkitDisksFilesystem known_file_systems[] = { static const int num_known_file_systems = sizeof (known_file_systems) / sizeof (DevkitDisksFilesystem); +DevkitDisksAtaSmartDb * +devkit_disks_daemon_local_get_ata_smart_db (DevkitDisksDaemon *daemon) +{ + return daemon->priv->ata_smart_db; +} + const DevkitDisksFilesystem * devkit_disks_daemon_local_get_fs_details (DevkitDisksDaemon *daemon, const gchar *filesystem_id) @@ -547,10 +571,18 @@ devkit_disks_daemon_finalize (GObject *object) g_object_unref (daemon->priv->mount_monitor); } + if (daemon->priv->ata_smart_db != NULL) { + g_object_unref (daemon->priv->ata_smart_db); + } + if (daemon->priv->devkit_client != NULL) { g_object_unref (daemon->priv->devkit_client); } + if (daemon->priv->ata_smart_cleanup_timer_id > 0) { + g_source_remove (daemon->priv->ata_smart_cleanup_timer_id); + } + if (daemon->priv->ata_smart_refresh_timer_id > 0) { g_source_remove (daemon->priv->ata_smart_refresh_timer_id); } @@ -914,6 +946,28 @@ out: } static gboolean +cleanup_ata_smart_data (DevkitDisksDaemon *daemon) +{ + time_t now; + time_t cut_off_point; + + now = time (NULL); + cut_off_point = now - ATA_SMART_KEEP_ENTRIES_SECONDS; + + g_print ("**** Deleting all ATA SMART data older than %d seconds\n", ATA_SMART_KEEP_ENTRIES_SECONDS); + + devkit_disks_ata_smart_db_delete_entries (daemon->priv->ata_smart_db, + cut_off_point); + + /* cleanup in another N seconds */ + daemon->priv->ata_smart_cleanup_timer_id = g_timeout_add_seconds (ATA_SMART_CLEANUP_INTERVAL_SECONDS, + (GSourceFunc) cleanup_ata_smart_data, + daemon); + + return FALSE; +} + +static gboolean refresh_ata_smart_data (DevkitDisksDaemon *daemon) { DevkitDisksDevice *device; @@ -925,14 +979,15 @@ refresh_ata_smart_data (DevkitDisksDaemon *daemon) if (device->priv->drive_ata_smart_is_available) { char *options[] = {"nowakeup", NULL}; - g_debug ("refreshing ATA SMART data for %s", native_path); + g_print ("**** Refreshing ATA SMART data for %s\n", native_path); devkit_disks_device_drive_ata_smart_refresh_data (device, options, NULL); } } - /* update in another 30 minutes */ - daemon->priv->ata_smart_refresh_timer_id = g_timeout_add_seconds (30 * 60, + + /* update in another N seconds */ + daemon->priv->ata_smart_refresh_timer_id = g_timeout_add_seconds (ATA_SMART_REFRESH_INTERVAL_SECONDS, (GSourceFunc) refresh_ata_smart_data, daemon); @@ -1050,6 +1105,8 @@ register_disks_daemon (DevkitDisksDaemon *daemon) g_signal_connect (daemon->priv->mount_monitor, "mount-added", (GCallback) mount_added, daemon); g_signal_connect (daemon->priv->mount_monitor, "mount-removed", (GCallback) mount_removed, daemon); + daemon->priv->ata_smart_db = devkit_disks_ata_smart_db_new (); + return TRUE; error: return FALSE; @@ -1107,8 +1164,13 @@ devkit_disks_daemon_new (void) devkit_disks_mount_file_clean_stale (l); g_list_free (l); - /* set up timer for ATA smart data refresh */ - daemon->priv->ata_smart_refresh_timer_id = g_timeout_add_seconds (30 * 60, + /* clean up old ATA SMART data from the database */ + cleanup_ata_smart_data (daemon); + + /* set up timer for refreshing ATA SMART data - we don't want to refresh immediately because + * when adding a device we also do this... + */ + daemon->priv->ata_smart_refresh_timer_id = g_timeout_add_seconds (ATA_SMART_REFRESH_INTERVAL_SECONDS, (GSourceFunc) refresh_ata_smart_data, daemon); diff --git a/src/devkit-disks-daemon.h b/src/devkit-disks-daemon.h index e631532..8a037d0 100644 --- a/src/devkit-disks-daemon.h +++ b/src/devkit-disks-daemon.h @@ -87,6 +87,8 @@ DevkitDisksDevice *devkit_disks_daemon_local_find_by_device_file (DevkitDisksDae DevkitDisksDevice *devkit_disks_daemon_local_find_by_dev (DevkitDisksDaemon *daemon, dev_t dev); +DevkitDisksAtaSmartDb *devkit_disks_daemon_local_get_ata_smart_db (DevkitDisksDaemon *daemon); + PolKitCaller *devkit_disks_damon_local_get_caller_for_context (DevkitDisksDaemon *daemon, DBusGMethodInvocation *context); diff --git a/src/devkit-disks-device.c b/src/devkit-disks-device.c index 00f9060..87f877c 100644 --- a/src/devkit-disks-device.c +++ b/src/devkit-disks-device.c @@ -48,7 +48,7 @@ #include #include #include -#include +#include #include "devkit-disks-daemon.h" #include "devkit-disks-device.h" @@ -59,6 +59,7 @@ #include "devkit-disks-mount-file.h" #include "devkit-disks-inhibitor.h" #include "devkit-disks-poller.h" +#include "devkit-disks-ata-smart-db.h" /*--------------------------------------------------------------------------------------------------------------*/ #include "devkit-disks-device-glue.h" @@ -7008,108 +7009,6 @@ out: /*--------------------------------------------------------------------------------------------------------------*/ -static gchar * -get_ata_smart_filename (DevkitDisksDevice *device) -{ - gchar *filename; - - /* TODO: hmm... unique enough? Thinking serial number collisions.. but ata_id, scsi_id, usb_id in - * udev should take care of that... - */ - filename = g_strdup_printf (PACKAGE_LOCALSTATE_DIR "/lib/DeviceKit-disks/ata-smart/%s-%s-%s-%s", - device->priv->drive_vendor, - device->priv->drive_model, - device->priv->drive_revision, - device->priv->drive_serial); - - return filename; -} - -static gboolean -ata_smart_parse_attribute (const gchar *tokens, GValue *elem) -{ - gboolean ret; - guint id; - guint flags; - gboolean online, prefailure; - guint current; - gboolean current_valid; - guint worst; - gboolean worst_valid; - guint threshold; - gboolean threshold_valid; - gboolean good, good_valid; - guint pretty_unit; - guint64 pretty_value; - guint raw0, raw1, raw2, raw3, raw4, raw5; - gchar name[256]; - GArray *raw_data; - - ret = FALSE; - - if (sscanf (tokens, - "%d " /* id */ - "%s " /* name */ - "%d " /* flags */ - "%d %d " /* online, prefailure */ - "%d %d " /* current_value, current_value_valid */ - "%d %d " /* worst_value, worst_value_valid */ - "%d %d " /* threshold, threshold_valid */ - "%d %d " /* good, good_valid */ - "%d %" G_GUINT64_FORMAT " " /* pretty_unit, pretty_value */ - "%02x %02x %02x %02x %02x %02x", /* raw[6] */ - &id, - name, - &flags, - &online, &prefailure, - ¤t, ¤t_valid, - &worst, &worst_valid, - &threshold, &threshold_valid, - &good, &good_valid, - &pretty_unit, &pretty_value, - &raw0, &raw1, &raw2, &raw3, &raw4, &raw5) != 21) { - goto out; - } - - raw_data = g_array_new (FALSE, TRUE, sizeof (guchar)); - g_array_append_val (raw_data, raw0); - g_array_append_val (raw_data, raw1); - g_array_append_val (raw_data, raw2); - g_array_append_val (raw_data, raw3); - g_array_append_val (raw_data, raw4); - g_array_append_val (raw_data, raw5); - - g_value_init (elem, ATA_SMART_DATA_ATTRIBUTE_STRUCT_TYPE); - g_value_take_boxed (elem, dbus_g_type_specialized_construct (ATA_SMART_DATA_ATTRIBUTE_STRUCT_TYPE)); - dbus_g_type_struct_set (elem, - 0, id, - 1, name, - 2, flags, - 3, online, - 4, prefailure, - 5, current, - 6, current_valid, - 7, worst, - 8, worst_valid, - 9, threshold, - 10, threshold_valid, - 11, good, - 12, good_valid, - 13, pretty_unit, - 14, pretty_value, - 15, raw_data, - G_MAXUINT); - - g_array_free (raw_data, TRUE); - - ret = TRUE; - - out: - return ret; -} - -/*--------------------------------------------------------------------------------------------------------------*/ - typedef struct { gboolean simulation; } DriveRefreshAtaSmartDataData; @@ -7129,6 +7028,57 @@ drive_ata_smart_refresh_data_unref (DriveRefreshAtaSmartDataData *data) g_free (data); } +typedef struct +{ + GPtrArray *attributes; + gboolean has_bad_attributes; + +} AtaSmartCollectAttrsData; + +static void +ata_smart_collect_attrs (SkDisk *d, const SkSmartAttributeParsedData *a, void *user_data) +{ + AtaSmartCollectAttrsData *data = user_data; + GValue elem = {0}; + GArray *raw_data; + + raw_data = g_array_new (FALSE, TRUE, sizeof (guchar)); + g_array_append_val (raw_data, a->raw[0]); + g_array_append_val (raw_data, a->raw[1]); + g_array_append_val (raw_data, a->raw[2]); + g_array_append_val (raw_data, a->raw[3]); + g_array_append_val (raw_data, a->raw[4]); + g_array_append_val (raw_data, a->raw[5]); + + g_value_init (&elem, ATA_SMART_DATA_ATTRIBUTE_STRUCT_TYPE); + g_value_take_boxed (&elem, dbus_g_type_specialized_construct (ATA_SMART_DATA_ATTRIBUTE_STRUCT_TYPE)); + dbus_g_type_struct_set (&elem, + 0, a->id, + 1, a->name, + 2, a->flags, + 3, a->online, + 4, a->prefailure, + 5, a->current_value, + 6, a->current_value_valid, + 7, a->worst_value, + 8, a->worst_value_valid, + 9, a->threshold, + 10, a->threshold_valid, + 11, a->good, + 12, a->good_valid, + 13, a->pretty_unit, + 14, a->pretty_value, + 15, raw_data, + G_MAXUINT); + + if (!a->good) + data->has_bad_attributes = TRUE; + + g_ptr_array_add (data->attributes, g_value_get_boxed (&elem)); + + g_array_free (raw_data, TRUE); +} + /* may be called with context==NULL */ static void drive_ata_smart_refresh_data_completed_cb (DBusGMethodInvocation *context, @@ -7142,32 +7092,25 @@ drive_ata_smart_refresh_data_completed_cb (DBusGMethodInvocation *context, { DriveRefreshAtaSmartDataData *data; gint rc; - gchar **tokens; - gboolean ata_smart_is_failing; - gboolean ata_smart_is_failing_valid; - gboolean ata_smart_has_bad_sectors; - gboolean ata_smart_has_bad_attributes; - gdouble ata_smart_temperature_kelvin; - guint64 ata_smart_power_on_seconds; - guint64 ata_smart_time_collected; - guint ata_smart_offline_data_collection_status; - guint ata_smart_offline_data_collection_seconds; - guint ata_smart_self_test_execution_status; - guint ata_smart_self_test_execution_percent_remaining; - gboolean ata_smart_short_and_extended_self_test_available; - gboolean ata_smart_conveyance_self_test_available; - gboolean ata_smart_start_self_test_available; - gboolean ata_smart_abort_self_test_available; - guint ata_smart_short_self_test_polling_minutes; - guint ata_smart_extended_self_test_polling_minutes; - guint ata_smart_conveyance_self_test_polling_minutes; - GPtrArray *attributes; - guint n; - - tokens = NULL; + SkBool good; + uint64_t num_bad_sectors; + uint64_t temperature_mkelvin; + uint64_t power_on_mseconds; + const SkSmartParsedData *asd; + SkDisk *d; + guchar *blob; + gsize blob_size; + time_t time_collected; + gboolean is_failing; + gboolean is_failing_valid; + AtaSmartCollectAttrsData collect_attrs_data; + DevkitDisksAtaSmartDb *db; data = user_data; + d = NULL; + blob = NULL; + if (job_was_cancelled || stdout == NULL) { if (job_was_cancelled) { if (context != NULL) @@ -7188,219 +7131,147 @@ drive_ata_smart_refresh_data_completed_cb (DBusGMethodInvocation *context, if (rc != 0) { if (rc == 2) { - throw_error (context, - DEVKIT_DISKS_ERROR_ATA_SMART_WOULD_WAKEUP, - "Error retrieving S.M.A.R.T. data: %s", - stderr); + if (context != NULL) { + throw_error (context, + DEVKIT_DISKS_ERROR_ATA_SMART_WOULD_WAKEUP, + "Error retrieving S.M.A.R.T. data: %s", + stderr); + } } else { - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "Error retrieving S.M.A.R.T. data: helper failed with exit code %d: %s", - rc, stderr); + if (context != NULL) { + throw_error (context, + DEVKIT_DISKS_ERROR_FAILED, + "Error retrieving S.M.A.R.T. data: helper failed with exit code %d: %s", + rc, stderr); + } } goto out; } - g_print ("**************************************************\n"); - g_print ("TODO: %s parse '%s'\n", device->priv->device_file, stdout); - g_print ("**************************************************\n"); - - tokens = g_strsplit (stdout, "|", 0); + blob = g_base64_decode (stdout, &blob_size); - if (g_strv_length (tokens) < 4) { - if (context != NULL) + if (sk_disk_open (NULL, &d) != 0) { + if (context != NULL) { throw_error (context, DEVKIT_DISKS_ERROR_FAILED, - "malformed data '%s'", stdout); + "unable to open a SkDisk"); + } goto out; } - if (sscanf (tokens[0], - "%" G_GUINT64_FORMAT, - &ata_smart_time_collected) != 1) { - if (context != NULL) + if (sk_disk_set_blob (d, blob, blob_size) != 0) { + if (context != NULL) { throw_error (context, DEVKIT_DISKS_ERROR_FAILED, - "error parsing section 0 '%s'", tokens[0]); + "error parsing blob: %s", + strerror (errno)); + } goto out; } - if (g_strcmp0 (tokens[1], "atasmartv0") != 0) { - if (context != NULL) - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "unknown format '%s'", tokens[1]); - goto out; + if (sk_disk_smart_status (d, &good) != 0) { + is_failing = FALSE; + is_failing_valid = FALSE; + } else { + is_failing = !good; + is_failing_valid = TRUE; } + devkit_disks_device_set_drive_ata_smart_is_failing (device, is_failing); + devkit_disks_device_set_drive_ata_smart_is_failing_valid (device, is_failing_valid); - if (sscanf (tokens[2], - "%d %d %d %d %lg %" G_GUINT64_FORMAT, - &ata_smart_is_failing, - &ata_smart_is_failing_valid, - &ata_smart_has_bad_sectors, - &ata_smart_has_bad_attributes, - &ata_smart_temperature_kelvin, - &ata_smart_power_on_seconds) != 6) { - if (context != NULL) - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "error parsing section 2 '%s'", tokens[2]); - goto out; - } - - if (sscanf (tokens[3], - "%d %d %d %d %d %d %d %d %d %d %d", - &ata_smart_offline_data_collection_status, - &ata_smart_offline_data_collection_seconds, - &ata_smart_self_test_execution_status, - &ata_smart_self_test_execution_percent_remaining, - &ata_smart_short_and_extended_self_test_available, - &ata_smart_conveyance_self_test_available, - &ata_smart_start_self_test_available, - &ata_smart_abort_self_test_available, - &ata_smart_short_self_test_polling_minutes, - &ata_smart_extended_self_test_polling_minutes, - &ata_smart_conveyance_self_test_polling_minutes) != 11) { + if (sk_disk_smart_get_bad (d, &num_bad_sectors) != 0) { + num_bad_sectors = 0; + } + devkit_disks_device_set_drive_ata_smart_has_bad_sectors (device, (num_bad_sectors > 0)); + + time_collected = time (NULL); + devkit_disks_device_set_drive_ata_smart_time_collected (device, time_collected); + + if (sk_disk_smart_get_temperature (d, &temperature_mkelvin) != 0) { + temperature_mkelvin = 0; + } + devkit_disks_device_set_drive_ata_smart_temperature_kelvin (device, temperature_mkelvin / 1000.0); + + if (sk_disk_smart_get_power_on (d, &power_on_mseconds) != 0) { + power_on_mseconds = 0; + } + devkit_disks_device_set_drive_ata_smart_power_on_seconds (device, power_on_mseconds / 1000); + + if (sk_disk_smart_parse (d, &asd) != 0) { + devkit_disks_device_set_drive_ata_smart_offline_data_collection_status (device, 0); + devkit_disks_device_set_drive_ata_smart_offline_data_collection_seconds (device, 0); + devkit_disks_device_set_drive_ata_smart_self_test_execution_status (device, 0); + devkit_disks_device_set_drive_ata_smart_self_test_execution_percent_remaining (device, 0); + devkit_disks_device_set_drive_ata_smart_short_and_extended_self_test_available (device, FALSE); + devkit_disks_device_set_drive_ata_smart_conveyance_self_test_available (device, FALSE); + devkit_disks_device_set_drive_ata_smart_start_self_test_available (device, FALSE); + devkit_disks_device_set_drive_ata_smart_abort_self_test_available (device, FALSE); + devkit_disks_device_set_drive_ata_smart_short_self_test_polling_minutes (device, 0); + devkit_disks_device_set_drive_ata_smart_extended_self_test_polling_minutes (device, 0); + devkit_disks_device_set_drive_ata_smart_conveyance_self_test_polling_minutes (device, 0); + } else { + devkit_disks_device_set_drive_ata_smart_offline_data_collection_status + (device, asd->offline_data_collection_status); + devkit_disks_device_set_drive_ata_smart_offline_data_collection_seconds + (device, asd->total_offline_data_collection_seconds); + devkit_disks_device_set_drive_ata_smart_self_test_execution_status + (device, asd->self_test_execution_status); + devkit_disks_device_set_drive_ata_smart_self_test_execution_percent_remaining + (device, asd->self_test_execution_percent_remaining); + devkit_disks_device_set_drive_ata_smart_short_and_extended_self_test_available + (device, asd->short_and_extended_test_available); + devkit_disks_device_set_drive_ata_smart_conveyance_self_test_available + (device, asd->conveyance_test_available); + devkit_disks_device_set_drive_ata_smart_start_self_test_available + (device, asd->start_test_available); + devkit_disks_device_set_drive_ata_smart_abort_self_test_available + (device, asd->abort_test_available); + devkit_disks_device_set_drive_ata_smart_short_self_test_polling_minutes + (device, asd->short_test_polling_minutes); + devkit_disks_device_set_drive_ata_smart_extended_self_test_polling_minutes + (device, asd->extended_test_polling_minutes); + devkit_disks_device_set_drive_ata_smart_conveyance_self_test_polling_minutes + (device, asd->conveyance_test_polling_minutes); + } + + collect_attrs_data.attributes = g_ptr_array_new (); + collect_attrs_data.has_bad_attributes = FALSE; + if (sk_disk_smart_parse_attributes (d, ata_smart_collect_attrs, &collect_attrs_data) != 0) { if (context != NULL) throw_error (context, DEVKIT_DISKS_ERROR_FAILED, - "error parsing section 3 '%s'", tokens[3]); - goto out; - } - - devkit_disks_device_set_drive_ata_smart_is_failing (device, ata_smart_is_failing); - devkit_disks_device_set_drive_ata_smart_is_failing_valid (device, ata_smart_is_failing_valid); - devkit_disks_device_set_drive_ata_smart_has_bad_sectors (device, ata_smart_has_bad_sectors); - devkit_disks_device_set_drive_ata_smart_has_bad_attributes (device, ata_smart_has_bad_attributes); - devkit_disks_device_set_drive_ata_smart_time_collected (device, ata_smart_time_collected); - devkit_disks_device_set_drive_ata_smart_temperature_kelvin (device, ata_smart_temperature_kelvin); - devkit_disks_device_set_drive_ata_smart_power_on_seconds (device, ata_smart_power_on_seconds); - devkit_disks_device_set_drive_ata_smart_offline_data_collection_status (device, ata_smart_offline_data_collection_status); - devkit_disks_device_set_drive_ata_smart_offline_data_collection_seconds (device, ata_smart_offline_data_collection_seconds); - devkit_disks_device_set_drive_ata_smart_self_test_execution_status (device, ata_smart_self_test_execution_status); - devkit_disks_device_set_drive_ata_smart_self_test_execution_percent_remaining (device, ata_smart_self_test_execution_percent_remaining); - devkit_disks_device_set_drive_ata_smart_short_and_extended_self_test_available (device, ata_smart_short_and_extended_self_test_available); - devkit_disks_device_set_drive_ata_smart_conveyance_self_test_available (device, ata_smart_conveyance_self_test_available); - devkit_disks_device_set_drive_ata_smart_start_self_test_available (device, ata_smart_start_self_test_available); - devkit_disks_device_set_drive_ata_smart_abort_self_test_available (device, ata_smart_abort_self_test_available); - devkit_disks_device_set_drive_ata_smart_short_self_test_polling_minutes (device, ata_smart_short_self_test_polling_minutes); - devkit_disks_device_set_drive_ata_smart_extended_self_test_polling_minutes (device, ata_smart_extended_self_test_polling_minutes); - devkit_disks_device_set_drive_ata_smart_conveyance_self_test_polling_minutes (device, ata_smart_conveyance_self_test_polling_minutes); - - /* then all attributes */ - attributes = g_ptr_array_new (); - for (n = 4; tokens[n] != NULL; n++) { - GValue elem = {0}; - - if (!ata_smart_parse_attribute (tokens[n], &elem)) { - if (context != NULL) - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "error parsing section %d '%s'", n, tokens[n]); - g_ptr_array_foreach (attributes, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (attributes, TRUE); - goto out; - } - - g_ptr_array_add (attributes, g_value_get_boxed (&elem)); + "Error parsing ATA SMART attributes: %m"); + g_ptr_array_free (collect_attrs_data.attributes, TRUE); + goto out; } - devkit_disks_device_set_drive_ata_smart_attributes_steal (device, attributes); + devkit_disks_device_set_drive_ata_smart_has_bad_attributes (device, collect_attrs_data.has_bad_attributes); + devkit_disks_device_set_drive_ata_smart_attributes_steal (device, collect_attrs_data.attributes); /* emit change event since we've updated the smart data */ drain_pending_changes (device, FALSE); - /* if not simulating, store the retrieved data and do some house-keeping as well - * - * TODO: it's probably somewhat inefficient to do house-keeping like this (better - * to just append to the file and do housekeeping only on startup and every - * 24 hours or so) - * - * TODO: retrieving/storing the data should probably be async as well - */ - if (!data->simulation) { - gchar *filename; - gchar *contents; - gsize length; - GError *error; - GString *s; - time_t now; - - filename = get_ata_smart_filename (device); - if (filename == NULL) { - if (context != NULL) - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "Error computing smart data filename for device"); - goto out; - } - - error = NULL; - if (!g_file_get_contents (filename, - &contents, - &length, - &error)) { - if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) { - /* it's ok if the file doesn't exist */ - g_error_free (error); - } else { - if (context != NULL) - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "Error retrieving existing SMART data from %s: %s", filename, error->message); - g_error_free (error); - g_free (filename); - goto out; - } - } - - now = time (NULL); - - s = g_string_new (NULL); - if (contents != NULL) { - gchar **lines; - - lines = g_strsplit (contents, "\n", 0); - for (n = 0; lines[n] != NULL; n++) { - time_t time_collected; - - time_collected = (time_t) atoll (lines[n]); - - /* keep data around for 7 days */ - if (now - time_collected > 7 * 24 * 60 * 60) - continue; - - g_string_append (s, lines[n]); - g_string_append_c (s, '\n'); - } - g_strfreev (lines); - - g_free (contents); - } - - g_string_append (s, stdout); - - error = NULL; - if (!g_file_set_contents (filename, - s->str, - s->len, - &error)) { - if (context != NULL) - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "Error retrieving saving SMART data to %s: %s", filename, error->message); - g_error_free (error); - g_free (filename); - goto out; - } - - g_free (filename); - } - if (context != NULL) dbus_g_method_return (context); + + /* store the (time_collected, disk_id, blob) tupple in our database */ + db = devkit_disks_daemon_local_get_ata_smart_db (device->priv->daemon); + devkit_disks_ata_smart_db_add_entry (db, + device, + time_collected, + is_failing, + is_failing_valid, + (num_bad_sectors > 0), + collect_attrs_data.has_bad_attributes, + temperature_mkelvin / 1000.0, + power_on_mseconds / 1000, + blob, + blob_size); + out: - g_strfreev (tokens); + g_free (blob); + if (d != NULL) + sk_disk_free (d); } /* may be called with context==NULL */ @@ -7498,20 +7369,77 @@ out: /*--------------------------------------------------------------------------------------------------------------*/ +static gboolean +ata_smart_historical_data_cb (time_t time_collected, + gboolean is_failing, + gboolean is_failing_valid, + gboolean has_bad_sectors, + gboolean has_bad_attributes, + gdouble temperature_kelvin, + guint64 power_on_seconds, + const void *blob, + gsize blob_size, + gpointer user_data) +{ + GPtrArray *array = user_data; + GValue elem = {0}; + SkDisk *d; + AtaSmartCollectAttrsData collect_attrs_data; + + d = NULL; + + if (sk_disk_open (NULL, &d) != 0) { + g_warning ("Unable to open a SkDisk"); + goto out; + } + + if (sk_disk_set_blob (d, blob, blob_size) != 0) { + g_warning ("Error parsing blob: %s", strerror (errno)); + goto out; + } + + collect_attrs_data.attributes = g_ptr_array_new (); + collect_attrs_data.has_bad_attributes = FALSE; + if (sk_disk_smart_parse_attributes (d, ata_smart_collect_attrs, &collect_attrs_data) != 0) { + g_warning ("Error parsing ATA SMART attributes: %m"); + } + + g_value_init (&elem, ATA_SMART_HISTORICAL_SMART_DATA_STRUCT_TYPE); + g_value_take_boxed (&elem, dbus_g_type_specialized_construct (ATA_SMART_HISTORICAL_SMART_DATA_STRUCT_TYPE)); + dbus_g_type_struct_set (&elem, + 0, time_collected, + 1, is_failing, + 2, is_failing_valid, + 3, has_bad_sectors, + 4, has_bad_attributes, + 5, temperature_kelvin, + 6, power_on_seconds, + 7, collect_attrs_data.attributes, + G_MAXUINT); + + g_ptr_array_foreach (collect_attrs_data.attributes, (GFunc) g_value_array_free, NULL); + g_ptr_array_free (collect_attrs_data.attributes, TRUE); + + g_ptr_array_add (array, g_value_get_boxed (&elem)); + + out: + if (d != NULL) + sk_disk_free (d); + return FALSE; +} + gboolean devkit_disks_device_drive_ata_smart_get_historical_data (DevkitDisksDevice *device, - guint64 from, - guint64 to, + guint64 since, + guint64 until, + guint64 spacing, DBusGMethodInvocation *context) { PolKitCaller *pk_caller; GPtrArray *array; - gchar *filename; - gchar *contents; - GError *error; + DevkitDisksAtaSmartDb *db; pk_caller = NULL; - filename = NULL; if (context != NULL) { if ((pk_caller = devkit_disks_damon_local_get_caller_for_context (device->priv->daemon, @@ -7536,133 +7464,25 @@ devkit_disks_device_drive_ata_smart_get_historical_data (DevkitDisksDevice * } } - if (from > to) { - throw_error (context, DEVKIT_DISKS_ERROR_FAILED, "Malformed time range (from > to)"); - goto out; - } - - to = time (NULL); + if (until == 0) + until = time (NULL); - filename = get_ata_smart_filename (device); - if (filename == NULL) { - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "Error computing smart data filename for device"); + if (since > until) { + throw_error (context, DEVKIT_DISKS_ERROR_FAILED, "Malformed time range (since > until)"); goto out; } - error = NULL; - if (!g_file_get_contents (filename, - &contents, - NULL, - &error)) { - if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT) { - /* it's ok if the file doesn't exist */ - g_error_free (error); - } else { - throw_error (context, - DEVKIT_DISKS_ERROR_FAILED, - "Error retrieving existing SMART data from %s: %s", filename, error->message); - g_error_free (error); - g_free (filename); - goto out; - } - } + db = devkit_disks_daemon_local_get_ata_smart_db (device->priv->daemon); array = g_ptr_array_new (); - if (contents != NULL) { - gchar **lines; - guint n; - - lines = g_strsplit (contents, "\n", 0); - for (n = 0; lines[n] != NULL; n++) { - gboolean ata_smart_is_failing; - gboolean ata_smart_is_failing_valid; - gboolean ata_smart_has_bad_sectors; - gboolean ata_smart_has_bad_attributes; - gdouble ata_smart_temperature_kelvin; - guint64 ata_smart_power_on_seconds; - guint64 ata_smart_time_collected; - gchar **tokens; - GPtrArray *attributes; - guint m; - GValue sample_elem = {0}; - - if (strlen (lines[n]) == 0) - continue; - - tokens = g_strsplit (lines[n], "|", 0); - - if (sscanf (tokens[0], - "%" G_GUINT64_FORMAT, - &ata_smart_time_collected) != 1) { - g_warning ("error parsing section 0 '%s'", tokens[0]); - g_strfreev (tokens); - continue; - } - - if (! ((ata_smart_time_collected >= from) && (ata_smart_time_collected <= to))) { - g_strfreev (tokens); - continue; - } - - if (g_strcmp0 (tokens[1], "atasmartv0") != 0) { - g_warning ("unknown format '%s'", tokens[1]); - g_strfreev (tokens); - continue; - } - - if (sscanf (tokens[2], - "%d %d %d %d %lg %" G_GUINT64_FORMAT, - &ata_smart_is_failing, - &ata_smart_is_failing_valid, - &ata_smart_has_bad_sectors, - &ata_smart_has_bad_attributes, - &ata_smart_temperature_kelvin, - &ata_smart_power_on_seconds) != 6) { - g_warning ("error parsing section 2 '%s'", tokens[2]); - g_strfreev (tokens); - continue; - } - - attributes = g_ptr_array_new (); - for (m = 4; tokens[m] != NULL; m++) { - GValue elem = {0}; - - /* ignore errors... */ - if (!ata_smart_parse_attribute (tokens[m], &elem)) { - g_warning ("error parsing '%s' in historical data", tokens[m]); - continue; - } - - g_ptr_array_add (attributes, g_value_get_boxed (&elem)); - } - - g_value_init (&sample_elem, ATA_SMART_HISTORICAL_SMART_DATA_STRUCT_TYPE); - g_value_take_boxed (&sample_elem, dbus_g_type_specialized_construct (ATA_SMART_HISTORICAL_SMART_DATA_STRUCT_TYPE)); - dbus_g_type_struct_set (&sample_elem, - 0, ata_smart_time_collected, - 1, ata_smart_is_failing, - 2, ata_smart_is_failing_valid, - 3, ata_smart_has_bad_sectors, - 4, ata_smart_has_bad_attributes, - 5, ata_smart_temperature_kelvin, - 6, ata_smart_power_on_seconds, - 7, attributes, - G_MAXUINT); - g_ptr_array_add (array, g_value_get_boxed (&sample_elem)); - - g_ptr_array_foreach (attributes, (GFunc) g_value_array_free, NULL); - g_ptr_array_free (attributes, TRUE); - - g_strfreev (tokens); - } - g_strfreev (lines); - - g_free (contents); - } - + devkit_disks_ata_smart_db_get_entries (db, + device, + since, + until, + spacing, + ata_smart_historical_data_cb, + array); dbus_g_method_return (context, array); @@ -7670,7 +7490,6 @@ devkit_disks_device_drive_ata_smart_get_historical_data (DevkitDisksDevice * g_ptr_array_free (array, TRUE); out: - g_free (filename); if (pk_caller != NULL) polkit_caller_unref (pk_caller); return TRUE; diff --git a/src/devkit-disks-device.h b/src/devkit-disks-device.h index dcb8ab2..61bd6e9 100644 --- a/src/devkit-disks-device.h +++ b/src/devkit-disks-device.h @@ -148,8 +148,9 @@ gboolean devkit_disks_device_drive_ata_smart_refresh_data (DevkitDisksDevice DBusGMethodInvocation *context); gboolean devkit_disks_device_drive_ata_smart_get_historical_data (DevkitDisksDevice *device, - guint64 from, - guint64 to, + guint64 since, + guint64 until, + guint64 spacing, DBusGMethodInvocation *context); gboolean devkit_disks_device_drive_ata_smart_initiate_selftest (DevkitDisksDevice *device, diff --git a/src/devkit-disks-types.h b/src/devkit-disks-types.h index 1f29ea9..d472e04 100644 --- a/src/devkit-disks-types.h +++ b/src/devkit-disks-types.h @@ -30,6 +30,7 @@ typedef struct DevkitDisksDevice DevkitDisksDevice; typedef struct DevkitDisksMount DevkitDisksMount; typedef struct DevkitDisksMountMonitor DevkitDisksMountMonitor; typedef struct DevkitDisksInhibitor DevkitDisksInhibitor; +typedef struct DevkitDisksAtaSmartDb DevkitDisksAtaSmartDb; G_END_DECLS diff --git a/src/job-ata-smart-collect.c b/src/job-ata-smart-collect.c index 6a61e55..dd7a526 100644 --- a/src/job-ata-smart-collect.c +++ b/src/job-ata-smart-collect.c @@ -33,73 +33,6 @@ usage (void) fprintf (stderr, "incorrect usage\n"); } -static guint64 temperature_mkelvin; -static gboolean has_bad_sectors; -static gboolean has_bad_attributes; -static guint64 power_on_seconds; - - -static void -collect_attrs (SkDisk *d, const SkSmartAttributeParsedData *a, void *user_data) -{ - GList **list = user_data; - GString *s; - - s = g_string_new (NULL); - - if (strcmp (a->name, "temperature-centi-celsius") == 0 || - strcmp (a->name, "temperature-celsius") == 0 || - strcmp (a->name, "temperature-celsius-2") == 0 || - strcmp (a->name, "airflow-temperature-celsius") == 0) { - temperature_mkelvin = a->pretty_value; - } - - if (strcmp (a->name, "power-on-minutes") == 0 || - strcmp (a->name, "power-on-seconds") == 0 || - strcmp (a->name, "power-on-half-minutes") == 0 || - strcmp (a->name, "power-on-hours") == 0) { - power_on_seconds = a->pretty_value / 1000; - } - - if (strcmp (a->name, "reallocated-sector-count") ==0 || - strcmp (a->name, "current-pending-sector") == 0 || - strcmp (a->name, "reallocated-event-count") == 0) { - if (a->pretty_value > 0) - has_bad_sectors = TRUE; - } - - if (!a->good) - has_bad_attributes = TRUE; - - g_string_append_printf (s, - "%d " /* id */ - "%s " /* name */ - "%d " /* flags */ - "%d %d " /* online, prefailure */ - "%d %d " /* current_value, current_value_valid */ - "%d %d " /* worst_value, worst_value_valid */ - "%d %d " /* threshold, threshold_valid */ - "%d %d " /* good, good_valid */ - "%d %" G_GUINT64_FORMAT " " /* pretty_unit, pretty_value */ - "%02x %02x %02x %02x %02x %02x", /* raw[6] */ - - a->id, - a->name, - a->flags, - a->online, a->prefailure, - - a->current_value, a->current_value_valid, - a->worst_value, a->worst_value_valid, - a->threshold, a->threshold_valid, - a->good, a->good_valid, - - a->pretty_unit, a->pretty_value, - - a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5]); - - *list = g_list_prepend (*list, g_string_free (s, FALSE)); -} - int main (int argc, char *argv[]) { @@ -108,19 +41,14 @@ main (int argc, char *argv[]) SkDisk *d; SkBool smart_is_available; SkBool awake; - SkBool good; - const SkSmartParsedData *data; - GString *s; - GList *attrs; - GList *l; gboolean nowakeup; + const void *blob; + size_t blob_size; + gchar *encoded_blob; d = NULL; - attrs = NULL; ret = 1; - s = g_string_new (NULL); - if (argc != 3) { usage (); goto out; @@ -131,100 +59,45 @@ main (int argc, char *argv[]) nowakeup = atoi (argv[2]); if (sk_disk_open (device, &d) != 0) { - fprintf (stderr, "Failed to open disk %s: %s\n", device, strerror (errno)); + g_printerr ("Failed to open disk %s: %m\n", device); goto out; } if (sk_disk_check_sleep_mode (d, &awake) != 0) { - fprintf (stderr, "Failed to check if disk %s is awake: %s\n", device, strerror (errno)); + g_printerr ("Failed to check if disk %s is awake: %m\n", device); goto out; } /* don't wake up disk unless specically asked to */ if (nowakeup && !awake) { - fprintf (stderr, "Disk %s is asleep and nowakeup option was passed\n", device); + g_printerr ("Disk %s is asleep and nowakeup option was passed\n", device); ret = 2; goto out; } if (sk_disk_smart_is_available (d, &smart_is_available) != 0) { - fprintf (stderr, "Failed to determine if smart is available for %s: %s\n", device, strerror (errno)); + g_printerr ("Failed to determine if smart is available for %s: %m\n", device); goto out; } - /* time collected */ - g_string_append_printf (s, "%" G_GUINT64_FORMAT "|", (guint64) time (NULL)); - - /* version of data */ - g_string_append_printf (s, "atasmartv0"); - /* main smart data */ if (sk_disk_smart_read_data (d) != 0) { - fprintf (stderr, "Failed to read smart data for %s: %s\n", device, strerror (errno)); - goto out; - } - if (sk_disk_smart_parse (d, &data) != 0) { - fprintf (stderr, "Failed to parse smart data for %s: %s\n", device, strerror (errno)); + g_printerr ("Failed to read smart data for %s: %m\n", device); goto out; } - temperature_mkelvin = 0; - has_bad_sectors = FALSE; - has_bad_attributes = FALSE; - power_on_seconds = 0; - - if (sk_disk_smart_parse_attributes (d, collect_attrs, &attrs) != 0) { - fprintf (stderr, "Failed to parse smart attributes for %s: %s\n", device, strerror (errno)); + if (sk_disk_get_blob (d, &blob, &blob_size) != 0) { + g_printerr ("Failed to read smart data for %s: %m\n", device); goto out; } - attrs = g_list_reverse (attrs); - - /* health status - * - * note that this is allowed to fail; some USB disks don't report status - */ - if (sk_disk_smart_status (d, &good) != 0) { - fprintf (stderr, "Failed to read smart status for %s: %s\n", device, strerror (errno)); - g_string_append (s, "|1 0"); - } else { - g_string_append_printf (s, "|%d 1", good ? 0 : 1); - } - g_string_append_printf (s, " %d %d %lg %" G_GUINT64_FORMAT, - has_bad_sectors, - has_bad_attributes, - temperature_mkelvin / 1000.0, - power_on_seconds); - - g_string_append_printf (s, - "|%d %d %d %d " - "%d %d %d %d " - "%d %d %d", - data->offline_data_collection_status, - data->total_offline_data_collection_seconds, - data->self_test_execution_status, - data->self_test_execution_percent_remaining, - data->short_and_extended_test_available, - data->conveyance_test_available, - data->start_test_available, - data->abort_test_available, - data->short_test_polling_minutes, - data->extended_test_polling_minutes, - data->conveyance_test_polling_minutes); - - /* then each attribute */ - for (l = attrs; l != NULL; l = l->next) { - g_string_append (s, "|"); - g_string_append (s, l->data); - } - printf ("%s\n", s->str); + encoded_blob = g_base64_encode ((const guchar *) blob, (gsize) blob_size); + g_print ("%s\n", encoded_blob); + g_free (encoded_blob); ret = 0; out: - g_string_free (s, TRUE); - g_list_foreach (attrs, (GFunc) g_free, FALSE); - g_list_free (attrs); if (d != NULL) sk_disk_free (d); diff --git a/src/org.freedesktop.DeviceKit.Disks.Device.xml b/src/org.freedesktop.DeviceKit.Disks.Device.xml index 2f26114..0e70eaa 100644 --- a/src/org.freedesktop.DeviceKit.Disks.Device.xml +++ b/src/org.freedesktop.DeviceKit.Disks.Device.xml @@ -1086,9 +1086,15 @@ passed the current time will be used. + + + The minimum spacing (in seconds) between two data points or 0 + to get all data points between @since and @until. + + - An array of historical data. Each element contains + An array of historical data sorted by collection date. Each element contains the following members (TODO).