/* * Copyright (C) 2017 info-cppsp * Copyright (C) 2005-2009 Alex Murray * * 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 */ /* Udisks2 plugin for the mate-sensors-applet written using the structure and code of the previous version from Pramod Dematagoda dbus-glib documentation https://dbus.freedesktop.org/doc/dbus-glib/ GDBUS documentation https://developer.gnome.org/gio/stable/index.html */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include "udisks2-plugin.h" /* remove // from next line for syslog debug */ //#define UD2PD 1 #ifdef UD2PD #include #endif #define UDISKS2_BUS_NAME "org.freedesktop.UDisks2" #define UDISKS2_INTERFACE_NAME "org.freedesktop.DBus.ObjectManager" #define UDISKS2_DEVICE_INTERFACE_NAME "org.freedesktop.UDisks2.Drive" #define UDISKS2_DEVICE_INTERFACE2_NAME "org.freedesktop.UDisks2.Drive.Ata" #define UDISKS2_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" #define UDISKS2_OBJECT_PATH "/org/freedesktop/UDisks2" /* Info about a single sensor */ typedef struct _DevInfo { gchar *path; gchar *id; gdouble temp; GDBusProxy *sensor_proxy; /* dbus object */ GError *error; } DevInfo; const gchar *plugin_name = "udisks2"; /* a container for the devices found to have smart enabled */ GHashTable *devices = NULL; /* This is a global variable for convenience */ GDBusConnection *connection = NULL; static void update_device (DevInfo *info) { GError *error = NULL; GVariant *tempgvar = NULL; GVariant *tempgvar2 = NULL; gdouble temp; /* check valid input parameter */ g_return_if_fail (info != NULL); /* check connection too */ g_return_if_fail (connection != NULL); g_clear_error (&info->error); /* check for sensor_proxy, which should exist at this point, make one if necessary and save it into DevInfo * this is used to get the temp value the direct way */ if (NULL == info->sensor_proxy) { info->sensor_proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS2_BUS_NAME, info->path, UDISKS2_PROPERTIES_INTERFACE, NULL, &error); /* check, just to be sure */ if (NULL == info->sensor_proxy) { #ifdef UD2PD syslog(LOG_ERR, "Failed to get drive temperature 1"); #endif g_debug ("Failed to get drive temperature 1: %s", error->message); g_clear_error (&error); return; } } /* note on timing: * it seems to me that smart updates occur automatically every 10 minutes * mate-sensor-applet has a default refresh of 2 seconds... * it is possible to force a smart update with udisks2: SmartUpdate (IN a{sv} options); */ /* directly asking the device's DBus object for the temp */ tempgvar = g_dbus_proxy_call_sync (info->sensor_proxy, "Get", g_variant_new ("(ss)", UDISKS2_DEVICE_INTERFACE2_NAME, "SmartTemperature"), /* parameters */ G_DBUS_CALL_FLAGS_NONE, /* flags */ -1, /* timeout */ NULL, /* cancellable */ &error); if (NULL == tempgvar) { #ifdef UD2PD syslog(LOG_ERR, "Failed to get drive temperature 2"); #endif g_debug ("Failed to get drive temperature 2: %s", error->message); g_clear_error (&error); /* throw away proxy, maybe next time it will be better */ g_clear_object (&info->sensor_proxy); return; } else { #ifdef UD2PD syslog(LOG_ERR, "tempgvar value: %s", g_variant_print(tempgvar, TRUE)); /* leaks memory! */ //syslog(LOG_ERR, "tempgvar value: %s", g_variant_print(g_variant_get_variant(g_variant_get_child_value(tempgvar, 0)), TRUE)); #endif /* tempgvar comes back as something along the lines of array(gvariant(tempasdouble)) * hence unpacking * need to free up every param / return value, so can't do it like: * temp = g_variant_get_double(g_variant_get_variant(g_variant_get_child_value(tempgvar, 0))); */ tempgvar2 = g_variant_get_child_value (tempgvar, 0); g_variant_unref (tempgvar); tempgvar = g_variant_get_variant (tempgvar2); g_variant_unref (tempgvar2); temp = g_variant_get_double (tempgvar); g_variant_unref (tempgvar); /* temp in K */ info->temp = temp - 273.15; #ifdef UD2PD syslog(LOG_ERR, "Refresh udisks2 device temp: '%f'\n", info->temp); #endif } } /* in this function we would like to get a list of device (hdd/ssd) paths * then with each path we get the temperature * it is possible with udisks2 to get all the above information in one g_dbus_proxy_call_sync(), so that is how I did it * maybe a better version would be to use GDBusObjectManager Server + Client ?? */ static void udisks2_plugin_get_sensors (GList **sensors) { #ifdef UD2PD syslog(LOG_ERR, "fstart"); #endif GDBusProxy *proxy = NULL; GError *error = NULL; DevInfo *info; /* This connection will be used for everything, including the obtaining of sensor data */ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (NULL == connection) { #ifdef UD2PD syslog(LOG_ERR, "dbus conn fail"); #endif g_debug ("Failed to open connection to DBUS: %s", error->message); g_clear_error (&error); return; } #ifdef UD2PD syslog(LOG_ERR, "dbus conn success"); #endif /* I use this proxy to get all info of all devices at once */ proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS2_BUS_NAME, UDISKS2_OBJECT_PATH, UDISKS2_INTERFACE_NAME, NULL, &error); if (NULL == proxy) { #ifdef UD2PD syslog(LOG_ERR, "dbus conn proxy fail"); #endif g_debug ("dbus conn proxy fail: %s", error->message); g_clear_error (&error); g_clear_object (&connection); return; } #ifdef UD2PD syslog(LOG_ERR, "dbus conn proxy success"); #endif /* The object paths of the disks are enumerated and placed in an array of object paths * "GetManagedObjects" returns dict of (objectpath, (dict of (string [ie. if. name], dict of(string [ie. property name], variant [ie. prop. value])))) */ /* g_dbus_proxy_call_sync() returns NULL on error, GVariant * otherwise * need second variable to prevent memory leak */ GVariant *managed_objects = NULL; GVariant *managed_objects2 = NULL; managed_objects2 = g_dbus_proxy_call_sync (proxy, "GetManagedObjects", NULL, /* parameters */ G_DBUS_CALL_FLAGS_NONE, /* flags */ -1, /* timeout */ NULL, /* cancellable */ &error); if (NULL == managed_objects2) { #ifdef UD2PD syslog(LOG_ERR, "Failed to enumerate disk devices"); #endif g_debug ("Failed to enumerate disk devices: %s", error->message); g_clear_error (&error); g_clear_object (&proxy); g_clear_object (&connection); return; } /* the result dictionary is enclosed in an array, unpack */ managed_objects = g_variant_get_child_value (managed_objects2, 0); g_variant_unref (managed_objects2); #ifdef UD2PD //syslog(LOG_ERR, "managed_objects type: %s", g_variant_print(managed_objects, TRUE)); syslog(LOG_ERR, "success to enumerate disk devices"); #endif /* iterator for the result dictionary * iterator code is based on the g_variant_iter_next() documentation * iter is freed if the GVariant is, when using g_variant_iter_init() */ GVariantIter iter; gchar *key = NULL; /* object path (like '/org/freedesktop/UDisks2/drives/Samsung_SSD_840_EVO_250GB_*insert drive serial nr.*') */ GVariant *value = NULL; #ifdef UD2PD /* log collection size */ syslog(LOG_ERR, "iter init count: %d", (int) g_variant_iter_init (&iter, managed_objects)); #else g_variant_iter_init (&iter, managed_objects); #endif /* "{sv}" is a GVariant format string * {} dictionary of, s string, v GVariant * changed to "{oa{sa{sv}}}" on error message 'the GVariant format string '{sv}' has a type of '{sv}' but the given value has a type of 'a{oa{sa{sv}}}'' * a is array, o is object path * NOO!! the right format string is "{o@*}", which means get an object path into the 1st variable (key) * and get 'everything else' (as a GVariant) into the 2nd variable (value) * needs the & before the key and value params! */ while (g_variant_iter_next (&iter, "{o@*}", &key, &value)) { #ifdef UD2PD syslog(LOG_ERR, "in iter while loop"); syslog(LOG_ERR, "key value: %s", key); //syslog(LOG_ERR, "value type: %s", g_variant_print(value, TRUE)); #endif /* level 2 * create a dictionary of value * the two interface names that we are searching for are known and defined * can't use GVariantDict, it only supports '{sv}' but the given value has a type of '{sa{sv}}' * using general lookup */ GVariant *propdict = NULL; /* drive data */ GVariant *propdict2 = NULL; /* drive smart data */ /* make two dictionaries that contain the properties of the drive interfaces */ propdict = g_variant_lookup_value (value, UDISKS2_DEVICE_INTERFACE_NAME, G_VARIANT_TYPE_DICTIONARY); propdict2 = g_variant_lookup_value (value, UDISKS2_DEVICE_INTERFACE2_NAME, G_VARIANT_TYPE_DICTIONARY); /* do we have the right ifname keys? */ if ((NULL != propdict) && (NULL != propdict2)) { #ifdef UD2PD syslog(LOG_ERR, "propdict type: %s", g_variant_print(propdict, TRUE)); syslog(LOG_ERR, "propdict2 type: %s", g_variant_print(propdict2, TRUE)); #endif /* get data */ gchar *id = NULL; gchar *model = NULL; gboolean smartenabled; gdouble temp; /* NULL, bc we don't care about the length of the string * typecast bc g_variant_get_string() returns const char* */ id = (gchar *) g_variant_get_string (g_variant_lookup_value (propdict, "Id", G_VARIANT_TYPE_STRING), NULL); model = (gchar *) g_variant_get_string (g_variant_lookup_value (propdict, "Model", G_VARIANT_TYPE_STRING), NULL); smartenabled = g_variant_get_boolean (g_variant_lookup_value (propdict2, "SmartEnabled", G_VARIANT_TYPE_BOOLEAN)); temp = g_variant_get_double (g_variant_lookup_value (propdict2, "SmartTemperature", G_VARIANT_TYPE_DOUBLE)); #ifdef UD2PD syslog(LOG_ERR, "Found udisks2 device id: '%s'\n", id); syslog(LOG_ERR, "Found udisks2 device model: '%s'\n", model); syslog(LOG_ERR, "Found udisks2 device smartenabled: '%d'\n", smartenabled); syslog(LOG_ERR, "Found udisks2 device temp: '%f'\n", temp); #endif /* only go on if smart is enabled * save data */ if (smartenabled) { info = g_malloc0 (sizeof (DevInfo)); if (NULL == devices) { devices = g_hash_table_new (g_str_hash, g_str_equal); } info->id = g_strdup (id); info->path = g_strdup (key); /* temp in K * this could be left at 0.0, 2 seconds later it will be refreshed anyway */ info->temp = (gdouble) temp - 273.15; g_hash_table_insert (devices, info->id, info); /* Write the sensor data */ sensors_applet_plugin_add_sensor (sensors, id, "Disk Temperature", model, TEMP_SENSOR, FALSE, HDD_ICON, DEFAULT_GRAPH_COLOR); g_debug ("Added %s", id); #ifdef UD2PD syslog(LOG_ERR, "Added %s", id); #endif } else { #ifdef UD2PD syslog(LOG_ERR, "No temp data for device: %s\n", key); #endif g_debug ("No temp data for device: %s\n", key); } #ifdef UD2PD syslog(LOG_ERR, "b4 free1"); #endif g_free (id); g_free (model); } #ifdef UD2PD syslog(LOG_ERR, "b4 free2"); #endif /* free propdict, propdict2 * g_variant_dict_unref() may not work a few times, gives error * this one seems to do fine */ if (NULL != propdict) {g_variant_unref (propdict);} if (NULL != propdict2) {g_variant_unref (propdict2);} #ifdef UD2PD syslog(LOG_ERR, "b4 free3"); #endif g_free (key); g_variant_unref (value); } /* end of while loop */ g_variant_unref (managed_objects); g_clear_object (&proxy); if (NULL == devices) { g_clear_object (&connection); } } /* this function is called every refresh cycle */ static gdouble udisks2_plugin_get_sensor_value (const gchar *path, const gchar *id, SensorType type, GError **error) { DevInfo *info = NULL; /* get device stuct from data store */ info = (DevInfo *) g_hash_table_lookup (devices, path); if (NULL == info) { g_set_error (error, SENSORS_APPLET_PLUGIN_ERROR, 0, "Error finding disk with path %s", path); return 0.0; } if (info->error) { *error = info->error; info->error = NULL; return 0.0; } /* refresh device temp */ update_device (info); return info->temp; } /* API functions */ const gchar *sensors_applet_plugin_name (void) { return plugin_name; } static GList *udisks2_plugin_init (void) { GList *sensors = NULL; udisks2_plugin_get_sensors (&sensors); return sensors; } GList *sensors_applet_plugin_init (void) { return udisks2_plugin_init (); } gdouble sensors_applet_plugin_get_sensor_value (const gchar *path, const gchar *id, SensorType type, GError **error) { return udisks2_plugin_get_sensor_value (path, id, type, error); }