moved common sensor functionality to a util lib
[oweals/gnunet.git] / src / sensor / sensor_util_lib.c
1 /*
2      This file is part of GNUnet.
3      (C)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file sensor/sensor_util_lib.c
23  * @brief senor utilities
24  * @author Omar Tarabai
25  */
26 #include <inttypes.h>
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_sensor_util_lib.h"
30
31 #define LOG(kind,...) GNUNET_log_from (kind, "sensor-util",__VA_ARGS__)
32
33 /**
34  * Minimum sensor execution interval (in seconds)
35  */
36 #define MIN_INTERVAL 30
37
38 /**
39  * Supported sources of sensor information
40  */
41 static const char *sources[] = { "gnunet-statistics", "process", NULL };
42
43 /**
44  * Supported datatypes of sensor information
45  */
46 static const char *datatypes[] = { "numeric", "string", NULL };
47
48 /**
49  * Parses a version number string into major and minor
50  *
51  * @param version full version string
52  * @param major pointer to parsed major value
53  * @param minor pointer to parsed minor value
54  * @return #GNUNET_OK if parsing went ok, #GNUNET_SYSERROR in case of error
55  */
56 static int
57 version_parse(char *version, uint16_t *major, uint16_t *minor)
58 {
59   int majorval = 0;
60   int minorval = 0;
61
62   for(; isdigit(*version); version++)
63   {
64     majorval *= 10;
65     majorval += *version - '0';
66   }
67   if(*version != '.')
68     return GNUNET_SYSERR;
69   version++;
70   for(; isdigit(*version); version++)
71   {
72     minorval *= 10;
73     minorval += *version - '0';
74   }
75   if(*version != 0)
76     return GNUNET_SYSERR;
77   *major = majorval;
78   *minor = minorval;
79
80   return GNUNET_OK;
81 }
82
83 /**
84  * Load sensor definition from configuration
85  *
86  * @param cfg configuration handle
87  * @param sectionname configuration section containing definition
88  */
89 static struct SensorInfo *
90 load_sensor_from_cfg(struct GNUNET_CONFIGURATION_Handle *cfg, const char *sectionname)
91 {
92   struct SensorInfo *sensor;
93   char *version_str;
94   char *starttime_str;
95   char *endtime_str;
96   unsigned long long time_sec;
97   char *dummy;
98   struct GNUNET_CRYPTO_EddsaPublicKey public_key;
99
100   sensor = GNUNET_new(struct SensorInfo);
101   //name
102   sensor->name = GNUNET_strdup(sectionname);
103   //version
104   if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "VERSION", &version_str))
105   {
106     LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor version\n"));
107     GNUNET_free(sensor);
108     return NULL;
109   }
110   if(GNUNET_OK != version_parse(version_str, &(sensor->version_major), &(sensor->version_minor)))
111   {
112     LOG (GNUNET_ERROR_TYPE_ERROR, _("Invalid sensor version number, format should be major.minor\n"));
113     GNUNET_free(sensor);
114     GNUNET_free(version_str);
115     return NULL;
116   }
117   GNUNET_free(version_str);
118   //description
119   GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "DESCRIPTION", &sensor->description);
120   //category
121   if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "CATEGORY", &sensor->category) ||
122         NULL == sensor->category)
123   {
124     LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor category\n"));
125     GNUNET_free(sensor);
126     return NULL;
127   }
128   //enabled
129   if(GNUNET_NO == GNUNET_CONFIGURATION_get_value_yesno(cfg, sectionname, "ENABLED"))
130     sensor->enabled = GNUNET_NO;
131   else
132     sensor->enabled = GNUNET_YES;
133   //start time
134   sensor->start_time = NULL;
135   if(GNUNET_OK == GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "START_TIME", &starttime_str))
136   {
137     GNUNET_STRINGS_fancy_time_to_absolute(starttime_str, sensor->start_time);
138     LOG (GNUNET_ERROR_TYPE_DEBUG, "Start time loaded: `%s'. Parsed: %d\n", starttime_str, (NULL != sensor->start_time));
139     GNUNET_free(starttime_str);
140   }
141   //end time
142   sensor->end_time = NULL;
143   if(GNUNET_OK == GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "END_TIME", &endtime_str))
144   {
145     GNUNET_STRINGS_fancy_time_to_absolute(endtime_str, sensor->end_time);
146     LOG (GNUNET_ERROR_TYPE_DEBUG, "End time loaded: `%s'. Parsed: %d\n", endtime_str, (NULL != sensor->end_time));
147     GNUNET_free(endtime_str);
148   }
149   //interval
150   if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_number(cfg, sectionname, "INTERVAL", &time_sec))
151   {
152     LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor run interval\n"));
153     GNUNET_free(sensor);
154     return NULL;
155   }
156   if(time_sec < MIN_INTERVAL)
157   {
158     LOG (GNUNET_ERROR_TYPE_ERROR, _("Sensor run interval too low (%" PRIu64 " < %d)\n"),
159         time_sec, MIN_INTERVAL);
160     GNUNET_free(sensor);
161     return NULL;
162   }
163   sensor->interval = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, time_sec);
164   //lifetime
165   if(GNUNET_OK == GNUNET_CONFIGURATION_get_value_number(cfg, sectionname, "LIFETIME", &time_sec))
166   {
167     sensor->lifetime = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, time_sec);
168     if (sensor->lifetime.rel_value_us < sensor->interval.rel_value_us)
169       LOG  (GNUNET_ERROR_TYPE_WARNING,
170           "Lifetime of sensor data is preferred to be higher than interval for sensor `%s'.\n",
171           sensor->name);
172   }
173   else
174     sensor->lifetime = sensor->interval;
175   //capabilities TODO
176   //source
177   if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_choice(cfg, sectionname, "SOURCE", sources, (const char **)&sensor->source))
178   {
179     LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor source\n"));
180     GNUNET_free(sensor);
181     return NULL;
182   }
183   if(sources[0] == sensor->source) //gnunet-statistics
184   {
185     if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "GNUNET_STAT_SERVICE", &sensor->gnunet_stat_service) ||
186         GNUNET_OK != GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "GNUNET_STAT_NAME", &sensor->gnunet_stat_name))
187     {
188       LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor gnunet-statistics source information\n"));
189       GNUNET_free(sensor);
190       return NULL;
191     }
192     sensor->gnunet_stat_get_handle = NULL;
193   }
194   else if(sources[1] == sensor->source) //process
195   {
196     if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "EXT_PROCESS", &sensor->ext_process))
197     {
198       LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor process name\n"));
199       GNUNET_free(sensor);
200       return NULL;
201     }
202     GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "EXT_ARGS", &sensor->ext_args);
203   }
204   //expected datatype
205   if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_choice(cfg, sectionname, "EXPECTED_DATATYPE", datatypes, (const char **)&sensor->expected_datatype))
206   {
207     LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor expected datatype\n"));
208     GNUNET_free(sensor);
209     return NULL;
210   }
211   if(sources[0] == sensor->source && datatypes[0] != sensor->expected_datatype)
212   {
213     LOG (GNUNET_ERROR_TYPE_ERROR, _("Invalid expected datatype, gnunet-statistics returns uint64 values\n"));
214     GNUNET_free(sensor);
215     return NULL;
216   }
217   //reporting mechanism
218   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string(cfg, sectionname, "COLLECTION_POINT", &dummy))
219   {
220     if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_number(cfg, sectionname, "COLLECTION_INTERVAL", &time_sec))
221     {
222       LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor collection interval\n"));
223     }
224     else
225     {
226       sensor->collection_interval = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, time_sec);
227       if (GNUNET_OK == GNUNET_CRYPTO_eddsa_public_key_from_string(dummy, strlen(dummy), &public_key))
228       {
229         sensor->collection_point = GNUNET_new(struct GNUNET_PeerIdentity);
230         sensor->collection_point->public_key = public_key;
231       }
232     }
233   }
234   sensor->p2p_report = GNUNET_NO;
235   if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno(cfg, sectionname, "P2P_REPORT"))
236   {
237     if(GNUNET_OK != GNUNET_CONFIGURATION_get_value_number(cfg, sectionname, "P2P_INTERVAL", &time_sec))
238     {
239       LOG (GNUNET_ERROR_TYPE_ERROR, _("Error reading sensor p2p reporting interval\n"));
240     }
241     else
242     {
243       sensor->p2p_interval = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, time_sec);
244       sensor->p2p_report = GNUNET_YES;
245     }
246   }
247   //execution task
248   sensor->execution_task = GNUNET_SCHEDULER_NO_TASK;
249   //running
250   sensor->running = GNUNET_NO;
251
252   return sensor;
253 }
254
255 /**
256  * Load sensor definition from file
257  *
258  * @param filename full path to file containing sensor definition
259  */
260 static struct SensorInfo *
261 load_sensor_from_file(const char *filename)
262 {
263   struct GNUNET_CONFIGURATION_Handle *sensorcfg;
264   const char *filebasename;
265   struct SensorInfo *sensor;
266
267   //test file
268   if(GNUNET_YES != GNUNET_DISK_file_test(filename))
269   {
270     LOG (GNUNET_ERROR_TYPE_ERROR, _("Failed to access sensor file: %s\n"), filename);
271     return NULL;
272   }
273   //load file as configuration
274   sensorcfg = GNUNET_CONFIGURATION_create();
275   if(GNUNET_SYSERR == GNUNET_CONFIGURATION_parse(sensorcfg, filename))
276   {
277     GNUNET_CONFIGURATION_destroy(sensorcfg);
278     LOG (GNUNET_ERROR_TYPE_ERROR, _("Failed to load sensor definition: %s\n"), filename);
279     return NULL;
280   }
281   //configuration section should be the same as filename
282   filebasename = GNUNET_STRINGS_get_short_name(filename);
283   sensor = load_sensor_from_cfg(sensorcfg, filebasename);
284   if(NULL == sensor)
285   {
286     GNUNET_CONFIGURATION_destroy(sensorcfg);
287     return NULL;
288   }
289   sensor->def_file = GNUNET_strdup(filename);
290   sensor->cfg = sensorcfg;
291
292   return sensor;
293 }
294
295 /**
296  * Compares version numbers of two sensors
297  *
298  * @param s1 first sensor
299  * @param s2 second sensor
300  * @return 1: s1 > s2, 0: s1 == s2, -1: s1 < s2
301  */
302 static int
303 sensor_version_compare(struct SensorInfo *s1, struct SensorInfo *s2)
304 {
305   if(s1->version_major == s2->version_major)
306     return (s1->version_minor < s2->version_minor) ? -1 : (s1->version_minor > s2->version_minor);
307   else
308     return (s1->version_major < s2->version_major) ? -1 : (s1->version_major > s2->version_major);
309 }
310
311 /**
312  * Adds a new sensor to given hashmap.
313  * If the same name exist, compares versions and update if old.
314  *
315  * @param sensor Sensor structure to add
316  * @param map Hashmap to add to
317  * @return #GNUNET_YES if added, #GNUNET_NO if not added which is not necessarily an error
318  */
319 static int
320 add_sensor_to_hashmap(struct SensorInfo *sensor, struct GNUNET_CONTAINER_MultiHashMap *map)
321 {
322   struct GNUNET_HashCode key;
323   struct SensorInfo *existing;
324
325   GNUNET_CRYPTO_hash(sensor->name, strlen(sensor->name), &key);
326   existing = GNUNET_CONTAINER_multihashmap_get(map, &key);
327   if(NULL != existing) //sensor with same name already exists
328   {
329     if(sensor_version_compare(existing, sensor) >= 0) //same or newer version already exist
330     {
331       LOG (GNUNET_ERROR_TYPE_INFO, _("Sensor `%s' already exists with same or newer version\n"), sensor->name);
332       return GNUNET_NO;
333     }
334     else
335     {
336       GNUNET_CONTAINER_multihashmap_remove(map, &key, existing); //remove the old version
337       GNUNET_free(existing);
338       LOG (GNUNET_ERROR_TYPE_INFO, "Upgrading sensor `%s' to a newer version\n", sensor->name);
339     }
340   }
341   if(GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put(map, &key, sensor, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
342   {
343     LOG (GNUNET_ERROR_TYPE_ERROR, _("Error adding new sensor `%s' to global hashmap, this should not happen\n"), sensor->name);
344     return GNUNET_NO;
345   }
346
347   return GNUNET_YES;
348 }
349
350 /**
351  * Iterating over files in sensors directory
352  *
353  * @param cls closure
354  * @param filename complete filename (absolute path)
355  * @return #GNUNET_OK to continue to iterate
356  */
357 static int
358 reload_sensors_dir_cb(void *cls, const char *filename)
359 {
360   struct GNUNET_CONTAINER_MultiHashMap *sensors = cls;
361   struct SensorInfo *sensor;
362
363   if(GNUNET_YES != GNUNET_DISK_file_test(filename))
364     return GNUNET_OK;
365   sensor = load_sensor_from_file(filename);
366   if(NULL == sensor)
367   {
368     LOG (GNUNET_ERROR_TYPE_ERROR,
369         _("Error loading sensor from file: %s\n"), filename);
370     return GNUNET_OK;
371   }
372   if(GNUNET_YES == add_sensor_to_hashmap(sensor, sensors))
373     LOG (GNUNET_ERROR_TYPE_DEBUG,
374         "Sensor `%s' added to global hashmap\n", sensor->name);
375   else
376     LOG (GNUNET_ERROR_TYPE_WARNING,
377         "Could not add sensor `%s' to global hashmap\n", sensor->name);
378
379   return GNUNET_OK;
380 }
381
382 /*
383  * Get path to the directory containing the sensor definition files
384  *
385  * @return sensor files directory
386  */
387 char *
388 GNUNET_SENSOR_get_sensor_dir ()
389 {
390   char* datadir;
391   char* sensordir;
392
393   datadir = GNUNET_OS_installation_get_path(GNUNET_OS_IPK_DATADIR);
394   GNUNET_asprintf(&sensordir, "%ssensors%s",
395       datadir, DIR_SEPARATOR_STR);
396   GNUNET_free(datadir);
397
398   return sensordir;
399 }
400
401 /**
402  * Reads sensor definitions from local data files
403  *
404  * @return a multihashmap of loaded sensors
405  */
406 struct GNUNET_CONTAINER_MultiHashMap *
407 GNUNET_SENSOR_load_all_sensors ()
408 {
409   char* sensordir;
410   struct GNUNET_CONTAINER_MultiHashMap *sensors;
411
412   sensors = GNUNET_CONTAINER_multihashmap_create(10, GNUNET_NO);
413   sensordir = GNUNET_SENSOR_get_sensor_dir ();
414   LOG (GNUNET_ERROR_TYPE_INFO,
415       "Loading sensor definitions from directory `%s'\n", sensordir);
416   GNUNET_assert(GNUNET_YES == GNUNET_DISK_directory_test(sensordir, GNUNET_YES));
417
418   //read all files in sensors directory
419   GNUNET_DISK_directory_scan(sensordir, &reload_sensors_dir_cb, sensors);
420   LOG (GNUNET_ERROR_TYPE_INFO, "Loaded %d sensors from directory `%s'\n",
421       GNUNET_CONTAINER_multihashmap_size(sensors), sensordir);
422   GNUNET_free(sensordir);
423   return sensors;
424 }