Back to index

unity  6.0.0
DebugDBusInterface.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright (C) 2010 Canonical Ltd
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License version 3 as
00007  * published by the Free Software Foundation.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Authored by: Alex Launi <alex.launi@canonical.com>
00018  */
00019 
00020 #include <queue>
00021 #include <fstream>
00022 #include <sstream>
00023 #include <boost/algorithm/string.hpp>
00024 #include <boost/algorithm/string/split.hpp>
00025 #include <boost/algorithm/string/classification.hpp>
00026 #include <boost/bind.hpp>
00027 #include <core/core.h>
00028 #include <NuxCore/Logger.h>
00029 #include <NuxCore/LoggingWriter.h>
00030 
00031 #include "DebugDBusInterface.h"
00032 #include "Introspectable.h"
00033 #include "XPathQueryPart.h"
00034 
00035 namespace unity
00036 {
00037 const std::string DBUS_BUS_NAME = "com.canonical.Unity";
00038 
00039 namespace debug
00040 {
00041 namespace
00042 {
00043 nux::logging::Logger logger("unity.debug.DebugDBusInterface");
00044 
00045 namespace local
00046 {
00047   std::ofstream output_file;
00048 }
00049 }
00050 
00051 GVariant* GetState(std::string const& query);
00052 void StartLogToFile(std::string const& file_path);
00053 void ResetLogging();
00054 void SetLogSeverity(std::string const& log_component,
00055   std::string const& severity);
00056 void LogMessage(std::string const& severity,
00057   std::string const& message);
00058 
00059 const char* DebugDBusInterface::DBUS_DEBUG_OBJECT_PATH = "/com/canonical/Unity/Debug";
00060 
00061 const gchar DebugDBusInterface::introspection_xml[] =
00062   " <node>"
00063   "   <interface name='com.canonical.Autopilot.Introspection'>"
00064   ""
00065   "     <method name='GetState'>"
00066   "       <arg type='s' name='piece' direction='in' />"
00067   "       <arg type='a(sv)' name='state' direction='out' />"
00068   "     </method>"
00069   ""
00070   "   </interface>"
00071   ""
00072   "   <interface name='com.canonical.Unity.Debug.Logging'>"
00073   ""
00074   "     <method name='StartLogToFile'>"
00075   "       <arg type='s' name='file_path' direction='in' />"
00076   "     </method>"
00077   ""
00078   "     <method name='ResetLogging'>"
00079   "     </method>"
00080   ""
00081   "     <method name='SetLogSeverity'>"
00082   "       <arg type='s' name='log_component' direction='in' />"
00083   "       <arg type='s' name='severity' direction='in' />"
00084   "     </method>"
00085   ""
00086   "     <method name='LogMessage'>"
00087   "       <arg type='s' name='severity' direction='in' />"
00088   "       <arg type='s' name='message' direction='in' />"
00089   "     </method>"
00090   ""
00091   "   </interface>"
00092   " </node>";
00093 
00094 GDBusInterfaceVTable DebugDBusInterface::interface_vtable =
00095 {
00096   DebugDBusInterface::HandleDBusMethodCall,
00097   NULL,
00098   NULL
00099 };
00100 
00101 static Introspectable* _parent_introspectable;
00102 
00103 DebugDBusInterface::DebugDBusInterface(Introspectable* parent)
00104 {
00105   _parent_introspectable = parent;
00106   _owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
00107                              unity::DBUS_BUS_NAME.c_str(),
00108                              G_BUS_NAME_OWNER_FLAGS_NONE,
00109                              &DebugDBusInterface::OnBusAcquired,
00110                              &DebugDBusInterface::OnNameAcquired,
00111                              &DebugDBusInterface::OnNameLost,
00112                              this,
00113                              NULL);
00114 }
00115 
00116 DebugDBusInterface::~DebugDBusInterface()
00117 {
00118   g_bus_unown_name(_owner_id);
00119 }
00120 
00121 void
00122 DebugDBusInterface::OnBusAcquired(GDBusConnection* connection, const gchar* name, gpointer data)
00123 {
00124   int i = 0;
00125   GError* error;
00126 
00127   GDBusNodeInfo* introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
00128   if (!introspection_data)
00129   {
00130     LOG_WARNING(logger) << "No dbus introspection data could be loaded. State introspection will not work";
00131     return;
00132   }
00133 
00134   while (introspection_data->interfaces[i] != NULL)
00135   {
00136     error = NULL;
00137     g_dbus_connection_register_object(connection,
00138                                       DebugDBusInterface::DBUS_DEBUG_OBJECT_PATH,
00139                                       introspection_data->interfaces[i],
00140                                       &interface_vtable,
00141                                       data,
00142                                       NULL,
00143                                       &error);
00144     if (error != NULL)
00145     {
00146       g_warning("Could not register debug interface onto d-bus");
00147       g_error_free(error);
00148     }
00149     i++;
00150   }
00151   g_dbus_node_info_unref(introspection_data);
00152 }
00153 
00154 void
00155 DebugDBusInterface::OnNameAcquired(GDBusConnection* connection, const gchar* name, gpointer data)
00156 {
00157 }
00158 
00159 void
00160 DebugDBusInterface::OnNameLost(GDBusConnection* connection, const gchar* name, gpointer data)
00161 {
00162 }
00163 
00164 void
00165 DebugDBusInterface::HandleDBusMethodCall(GDBusConnection* connection,
00166                                          const gchar* sender,
00167                                          const gchar* object_path,
00168                                          const gchar* interface_name,
00169                                          const gchar* method_name,
00170                                          GVariant* parameters,
00171                                          GDBusMethodInvocation* invocation,
00172                                          gpointer user_data)
00173 {
00174   if (g_strcmp0(method_name, "GetState") == 0)
00175   {
00176     GVariant* ret;
00177     const gchar* input;
00178     g_variant_get(parameters, "(&s)", &input);
00179 
00180     ret = GetState(input);
00181     // GetState returns a floating variant and
00182     // g_dbus_method_invocation_return_value ref sinks it
00183     g_dbus_method_invocation_return_value(invocation, ret);
00184   }
00185   else if (g_strcmp0(method_name, "StartLogToFile") == 0)
00186   {
00187     const gchar* log_path;
00188     g_variant_get(parameters, "(&s)", &log_path);
00189 
00190     StartLogToFile(log_path);
00191     g_dbus_method_invocation_return_value(invocation, NULL);
00192   }
00193   else if (g_strcmp0(method_name, "ResetLogging") == 0)
00194   {
00195     ResetLogging();
00196     g_dbus_method_invocation_return_value(invocation, NULL);
00197   }
00198   else if (g_strcmp0(method_name, "SetLogSeverity") == 0)
00199   {
00200     const gchar* component;
00201     const gchar* severity;
00202     g_variant_get(parameters, "(&s&s)", &component, &severity);
00203 
00204     SetLogSeverity(component, severity);
00205     g_dbus_method_invocation_return_value(invocation, NULL);
00206   }
00207   else if (g_strcmp0(method_name, "LogMessage") == 0)
00208   {
00209     const gchar* severity;
00210     const gchar* message;
00211     g_variant_get(parameters, "(&s&s)", &severity, &message);
00212 
00213     LogMessage(severity, message);
00214     g_dbus_method_invocation_return_value(invocation, NULL);
00215   }
00216   else
00217   {
00218     g_dbus_method_invocation_return_dbus_error(invocation,
00219                                                unity::DBUS_BUS_NAME.c_str(),
00220                                                "Failed to find method");
00221   }
00222 }
00223 
00224 
00225 GVariant* GetState(std::string const& query)
00226 {
00227   // process the XPath query:
00228   std::list<Introspectable*> parts = GetIntrospectableNodesFromQuery(query, _parent_introspectable);
00229   GVariantBuilder  builder;
00230   g_variant_builder_init(&builder, G_VARIANT_TYPE("a(sv)"));
00231   if (parts.empty())
00232   {
00233     LOG_WARNING(logger) << "Query '" << query << "' Did not match anything.";
00234   }
00235   for (Introspectable *node : parts)
00236   {
00237     g_variant_builder_add(&builder, "(sv)", node->GetName().c_str(), node->Introspect());
00238   }
00239 
00240   return g_variant_new("(a(sv))", &builder);
00241 }
00242 
00243 void StartLogToFile(std::string const& file_path)
00244 {
00245   if (local::output_file.is_open())
00246     local::output_file.close();
00247   local::output_file.open(file_path);
00248   nux::logging::Writer::Instance().SetOutputStream(local::output_file);
00249 }
00250 
00251 void ResetLogging()
00252 {
00253   if (local::output_file.is_open())
00254     local::output_file.close();
00255   nux::logging::Writer::Instance().SetOutputStream(std::cout);
00256   nux::logging::reset_logging();
00257 }
00258 
00259 void SetLogSeverity(std::string const& log_component,
00260                     std::string const& severity)
00261 {
00262   nux::logging::Logger(log_component).SetLogLevel(nux::logging::get_logging_level(severity));
00263 }
00264 
00265 void LogMessage(std::string const& severity,
00266                 std::string const& message)
00267 {
00268   nux::logging::Level level = nux::logging::get_logging_level(severity);
00269   if (logger.GetEffectiveLogLevel() <= level)
00270   {
00271    nux::logging::LogStream(level, logger.module(), __FILE__, __LINE__).stream()
00272       << message;
00273   }
00274 }
00275 
00276 /*
00277  * Do a breadth-first search of the introspection tree and find all nodes that match the
00278  * query.
00279  */
00280 std::list<Introspectable*> GetIntrospectableNodesFromQuery(std::string const& query, Introspectable* tree_root)
00281 {
00282   std::list<Introspectable*> start_points;
00283   std::string sanitised_query;
00284   // Allow user to be lazy when specifying root node.
00285   if (query == "" || query == "/")
00286   {
00287     sanitised_query = "/" + tree_root->GetName();
00288   }
00289   else
00290   {
00291     sanitised_query = query;
00292   }
00293   // split query into parts
00294   std::list<XPathQueryPart> query_parts;
00295 
00296   {
00297     std::list<std::string> query_strings;
00298     boost::algorithm::split(query_strings, sanitised_query, boost::algorithm::is_any_of("/"));
00299     // Boost's split() implementation does not match it's documentation! According to the
00300     // docs, it's not supposed to add empty strings, but it does, which is a PITA. This
00301     // next line removes them:
00302     query_strings.erase( std::remove_if( query_strings.begin(),
00303                                         query_strings.end(),
00304                                         boost::bind( &std::string::empty, _1 ) ),
00305                       query_strings.end());
00306     foreach(std::string part, query_strings)
00307     {
00308       query_parts.push_back(XPathQueryPart(part));
00309     }
00310   }
00311 
00312   // absolute or relative query string?
00313   if (sanitised_query.at(0) == '/' && sanitised_query.at(1) != '/')
00314   {
00315     // absolute query - start point is tree root node.
00316     if (query_parts.front().Matches(tree_root))
00317     {
00318       start_points.push_back(tree_root);
00319     }
00320   }
00321   else
00322   {
00323     // relative - need to do a depth first tree search for all nodes that match the
00324     // first node in the query.
00325 
00326     // warn about malformed queries (all queries must start with '/')
00327     if (sanitised_query.at(0) != '/')
00328     {
00329       LOG_WARNING(logger) << "Malformed relative introspection query: '" << query << "'.";
00330     }
00331 
00332     // non-recursive BFS traversal to find starting points:
00333     std::queue<Introspectable*> queue;
00334     queue.push(tree_root);
00335     while (!queue.empty())
00336     {
00337       Introspectable *node = queue.front();
00338       queue.pop();
00339       if (query_parts.front().Matches(node))
00340       {
00341         // found one. We keep going deeper, as there may be another node beneath this one
00342         // with the same node name.
00343         start_points.push_back(node);
00344       }
00345       // Add all children of current node to queue.
00346       foreach(Introspectable* child, node->GetIntrospectableChildren())
00347       {
00348         queue.push(child);
00349       }
00350     }
00351   }
00352 
00353   // now we have the tree start points, process them:
00354   query_parts.pop_front();
00355   typedef std::pair<Introspectable*, std::list<XPathQueryPart>::iterator> node_match_pair;
00356 
00357   std::queue<node_match_pair> traverse_queue;
00358   foreach(Introspectable *node, start_points)
00359   {
00360     traverse_queue.push(node_match_pair(node, query_parts.begin()));
00361   }
00362   start_points.clear();
00363 
00364   while (!traverse_queue.empty())
00365   {
00366     node_match_pair p = traverse_queue.front();
00367     traverse_queue.pop();
00368 
00369     Introspectable *node = p.first;
00370     auto query_it = p.second;
00371 
00372     if (query_it == query_parts.end())
00373     {
00374       // found a match:
00375       start_points.push_back(node);
00376     }
00377     else
00378     {
00379       // push all children of current node to start of queue, advance search iterator, and loop again.
00380       foreach (Introspectable *child, node->GetIntrospectableChildren())
00381       {
00382         if (query_it->Matches(child))
00383         {
00384           auto it_copy(query_it);
00385           ++it_copy;
00386           traverse_queue.push(node_match_pair(child, it_copy));
00387         }
00388       }
00389     }
00390   }
00391   return start_points;
00392 }
00393 }
00394 }