sw-collector: Added --installed/removed options
[strongswan.git] / src / sw-collector / sw_collector_history.c
1 /*
2 * Copyright (C) 2017 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #define _GNU_SOURCE
17 #include <stdio.h>
18 #include <time.h>
19
20 #include "sw_collector_history.h"
21 #include "sw_collector_dpkg.h"
22
23 #include "swima/swima_event.h"
24
25 typedef struct private_sw_collector_history_t private_sw_collector_history_t;
26
27 /**
28 * Private data of an sw_collector_history_t object.
29 */
30 struct private_sw_collector_history_t {
31
32 /**
33 * Public members of sw_collector_history_state_t
34 */
35 sw_collector_history_t public;
36
37 /**
38 * Software Event Source Number
39 */
40 uint8_t source;
41
42 /**
43 * Reference to OS info object
44 */
45 sw_collector_info_t *info;
46
47 /**
48 * Reference to collector database
49 */
50 sw_collector_db_t *db;
51
52 };
53
54 /**
55 * Define auxiliary package_t list item object
56 */
57 typedef struct package_t package_t;
58
59 struct package_t {
60 char *package;
61 char *version;
62 char *old_version;
63 char *sw_id;
64 char *old_sw_id;
65 };
66
67 /**
68 * Create package_t list item object
69 */
70 static package_t* create_package(sw_collector_info_t *info, chunk_t package,
71 chunk_t version, chunk_t old_version)
72 {
73 package_t *this;
74
75 INIT(this,
76 .package = strndup(package.ptr, package.len),
77 .version = strndup(version.ptr, version.len),
78 .old_version = strndup(old_version.ptr, old_version.len),
79 )
80
81 this->sw_id = info->create_sw_id(info, this->package, this->version);
82 if (old_version.len)
83 {
84 this->old_sw_id = info->create_sw_id(info, this->package,
85 this->old_version);
86 }
87
88 return this;
89 }
90
91 /**
92 * Free package_t list item object
93 */
94 static void free_package(package_t *this)
95 {
96 if (this)
97 {
98 free(this->package);
99 free(this->version);
100 free(this->old_version);
101 free(this->sw_id);
102 free(this->old_sw_id);
103 free(this);
104 }
105 }
106
107 /**
108 * Extract and parse a single package item
109 */
110 static package_t* extract_package(chunk_t item, sw_collector_info_t *info,
111 sw_collector_history_op_t op)
112 {
113 chunk_t package, package_stripped, version, old_version;
114 package_t *p;
115
116 /* extract package name */
117 if (!extract_token(&package, ' ', &item))
118 {
119 fprintf(stderr, "version not found.\n");
120 return NULL;
121 }
122 item = chunk_skip(item, 1);
123
124 /* strip architecture suffix if present */
125 if (extract_token(&package_stripped, ':', &package))
126 {
127 package = package_stripped;
128 }
129
130 /* extract versions */
131 version = old_version = chunk_empty;
132
133 if (item.len > 0)
134 {
135 if (extract_token(&version, ',', &item))
136 {
137 eat_whitespace(&item);
138 if (!match("automatic", &item))
139 {
140 old_version = version;
141 version = item;
142 }
143 }
144 else
145 {
146 version = item;
147 }
148 }
149 p = create_package(info, package, version, old_version);
150
151 /* generate log entry */
152 if (op == SW_OP_UPGRADE)
153 {
154 DBG2(DBG_IMC, " %s (%s, %s)", p->package, p->old_version, p->version);
155 DBG2(DBG_IMC, " +%s", p->sw_id);
156 DBG2(DBG_IMC, " -%s", p->old_sw_id);
157 }
158 else
159 {
160 DBG2(DBG_IMC, " %s (%s)", p->package, p->version);
161 DBG2(DBG_IMC, " %s%s", (op == SW_OP_INSTALL) ? "+" : "-", p->sw_id);
162 }
163
164 return p;
165 }
166
167 METHOD(sw_collector_history_t, extract_timestamp, bool,
168 private_sw_collector_history_t *this, chunk_t args, char *buf)
169 {
170 struct tm loc, utc;
171 chunk_t t1, t2;
172 time_t t;
173
174 /* Break down local time with format t1 = yyyy-mm-dd and t2 = hh:mm:ss */
175 if (!eat_whitespace(&args) || !extract_token(&t1, ' ', &args) ||
176 !eat_whitespace(&args) || t1.len != 10 || args.len != 8)
177 {
178 DBG1(DBG_IMC, "unable to parse start-date");
179 return FALSE;
180 }
181 t2 = args;
182
183 if (sscanf(t1.ptr, "%4d-%2d-%2d",
184 &loc.tm_year, &loc.tm_mon, &loc.tm_mday) != 3)
185 {
186 DBG1(DBG_IMC, "unable to parse date format yyyy-mm-dd");
187 return FALSE;
188 }
189 loc.tm_year -= 1900;
190 loc.tm_mon -= 1;
191 loc.tm_isdst = -1;
192
193 if (sscanf(t2.ptr, "%2d:%2d:%2d",
194 &loc.tm_hour, &loc.tm_min, &loc.tm_sec) != 3)
195 {
196 DBG1(DBG_IMC, "unable to parse time format hh:mm:ss");
197 return FALSE;
198 }
199
200 /* Convert from local time to UTC */
201 t = mktime(&loc);
202 gmtime_r(&t, &utc);
203 utc.tm_year += 1900;
204 utc.tm_mon += 1;
205
206 /* Form timestamp according to RFC 3339 (20 characters) */
207 snprintf(buf, 21, "%4d-%02d-%02dT%02d:%02d:%02dZ",
208 utc.tm_year, utc.tm_mon, utc.tm_mday,
209 utc.tm_hour, utc.tm_min, utc.tm_sec);
210
211 return TRUE;
212 }
213
214 METHOD(sw_collector_history_t, extract_packages, bool,
215 private_sw_collector_history_t *this, chunk_t args, uint32_t eid,
216 sw_collector_history_op_t op)
217 {
218 bool success = FALSE;
219 package_t *p = NULL;
220 chunk_t item;
221
222 eat_whitespace(&args);
223
224 while (extract_token(&item, ')', &args))
225 {
226 char *del_sw_id = NULL, *del_version = NULL;
227 char *nx, *px, *vx, *v1;
228 bool installed;
229 u_int sw_idx, ix;
230 uint32_t sw_id, sw_id_epoch_less = 0;
231 enumerator_t *e;
232
233 p = extract_package(item, this->info, op);
234 if (!p)
235 {
236 goto end;
237 }
238
239 /* packages without version information cannot be handled */
240 if (strlen(p->version) == 0)
241 {
242 free_package(p);
243 continue;
244 }
245
246 switch (op)
247 {
248 case SW_OP_REMOVE:
249 /* prepare subsequent deletion sw event */
250 del_sw_id = p->sw_id;
251 del_version = p->version;
252 break;
253 case SW_OP_UPGRADE:
254 /* prepare subsequent deletion sw event */
255 del_sw_id = p->old_sw_id;
256 del_version = p->old_version;
257 /* fall through to next case */
258 case SW_OP_INSTALL:
259 sw_id = this->db->get_sw_id(this->db, p->sw_id, NULL, NULL,
260 NULL, &installed);
261 if (sw_id)
262 {
263 /* sw identifier exists - update state to 'installed' */
264 if (installed)
265 {
266 /* this case should not occur */
267 DBG1(DBG_IMC, " warning: sw_id %d is already "
268 "installed", sw_id);
269 }
270 else if (!this->db->update_sw_id(this->db, sw_id, NULL,
271 NULL, TRUE))
272 {
273 goto end;
274 }
275 }
276 else
277 {
278 /* new sw identifier - create with state 'installed' */
279 sw_id = this->db->set_sw_id(this->db, p->sw_id, p->package,
280 p->version, this->source, TRUE);
281 if (!sw_id)
282 {
283 goto end;
284 }
285 }
286
287 /* add creation sw event with current eid */
288 if (!this->db->add_sw_event(this->db, eid, sw_id,
289 SWIMA_EVENT_ACTION_CREATION))
290 {
291 goto end;
292 }
293 break;
294 }
295
296 if (op != SW_OP_INSTALL)
297 {
298 sw_id = 0;
299
300 /* look for existing installed package versions */
301 e = this->db->create_sw_enumerator(this->db, SW_QUERY_INSTALLED,
302 p->package);
303 if (!e)
304 {
305 goto end;
306 }
307
308 while (e->enumerate(e, &sw_idx, &nx, &px, &vx, &ix))
309 {
310 if (streq(vx, del_version))
311 {
312 /* full match with epoch */
313 sw_id = sw_idx;
314 break;
315 }
316 v1 = strchr(vx, ':');
317 if (v1 && streq(++v1, del_version))
318 {
319 /* match with stripped epoch */
320 sw_id_epoch_less = sw_idx;
321 }
322 }
323 e->destroy(e);
324
325 if (!sw_id && sw_id_epoch_less)
326 {
327 /* no full match - fall back to epoch-less match */
328 sw_id = sw_id_epoch_less;
329 }
330 if (sw_id)
331 {
332 /* sw identifier exists - update state to 'removed' */
333 if (!this->db->update_sw_id(this->db, sw_id, NULL, NULL, FALSE))
334 {
335 goto end;
336 }
337 }
338 else
339 {
340 /* new sw identifier - create with state 'removed' */
341 sw_id = this->db->set_sw_id(this->db, del_sw_id, p->package,
342 del_version, this->source, FALSE);
343
344 /* add creation sw event with eid = 1 */
345 if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
346 SWIMA_EVENT_ACTION_CREATION))
347 {
348 goto end;
349 }
350 }
351
352 /* add creation sw event with current eid */
353 if (!this->db->add_sw_event(this->db, eid, sw_id,
354 SWIMA_EVENT_ACTION_DELETION))
355 {
356 goto end;
357 }
358 }
359 free_package(p);
360
361 if (args.len < 2)
362 {
363 break;
364 }
365 args = chunk_skip(args, 2);
366 }
367 p = NULL;
368 success = TRUE;
369
370 end:
371 free_package(p);
372
373 return success;
374 }
375
376 METHOD(sw_collector_history_t, merge_installed_packages, bool,
377 private_sw_collector_history_t *this)
378 {
379 uint32_t sw_id, count = 0;
380 char *package, *arch, *version, *v1, *name, *n1;
381 bool installed, success = FALSE;
382 sw_collector_dpkg_t *dpkg;
383 enumerator_t *enumerator;
384
385 DBG1(DBG_IMC, "Merging:");
386
387 dpkg = sw_collector_dpkg_create();
388 if (!dpkg)
389 {
390 return FALSE;
391 }
392
393 enumerator = dpkg->create_sw_enumerator(dpkg);
394 while (enumerator->enumerate(enumerator, &package, &arch, &version))
395 {
396 name = this->info->create_sw_id(this->info, package, version);
397 DBG3(DBG_IMC, " %s merged", name);
398
399 sw_id = this->db->get_sw_id(this->db, name, NULL, NULL, NULL,
400 &installed);
401 if (sw_id)
402 {
403 if (!installed)
404 {
405 DBG1(DBG_IMC, " warning: existing sw_id %u"
406 " is not installed", sw_id);
407
408 if (!this->db->update_sw_id(this->db, sw_id, name, version,
409 TRUE))
410 {
411 free(name);
412 goto end;
413 }
414 }
415 }
416 else
417 {
418 /* check for a Debian epoch number */
419 v1 = strchr(version, ':');
420 if (v1)
421 {
422 /* check for existing and installed epoch-less version */
423 n1 = this->info->create_sw_id(this->info, package, ++v1);
424 sw_id = this->db->get_sw_id(this->db, n1, NULL, NULL, NULL,
425 &installed);
426 free(n1);
427
428 if (sw_id && installed)
429 {
430 /* add epoch to existing version */
431 if (!this->db->update_sw_id(this->db, sw_id, name, version,
432 installed))
433 {
434 free(name);
435 goto end;
436 }
437 }
438 else
439 {
440 sw_id = 0;
441 }
442 }
443 }
444
445 if (!sw_id)
446 {
447 /* new sw identifier - create with state 'installed' */
448 sw_id = this->db->set_sw_id(this->db, name, package, version,
449 this->source, TRUE);
450
451 /* add creation sw event with eid = 1 */
452 if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
453 SWIMA_EVENT_ACTION_CREATION))
454 {
455 free(name);
456 goto end;
457 }
458
459 }
460 free(name);
461 count++;
462 }
463 success = TRUE;
464
465 DBG1(DBG_IMC, " merged %u installed packages, %u registered in database",
466 count, this->db->get_sw_id_count(this->db, SW_QUERY_INSTALLED));
467
468 end:
469 enumerator->destroy(enumerator);
470 dpkg->destroy(dpkg);
471
472 return success;
473 }
474
475 METHOD(sw_collector_history_t, destroy, void,
476 private_sw_collector_history_t *this)
477 {
478 free(this);
479 }
480
481 /**
482 * Described in header.
483 */
484 sw_collector_history_t *sw_collector_history_create(sw_collector_info_t *info,
485 sw_collector_db_t *db,
486 uint8_t source)
487 {
488 private_sw_collector_history_t *this;
489
490 INIT(this,
491 .public = {
492 .extract_timestamp = _extract_timestamp,
493 .extract_packages = _extract_packages,
494 .merge_installed_packages = _merge_installed_packages,
495 .destroy = _destroy,
496 },
497 .source = source,
498 .info = info,
499 .db = db,
500 );
501
502 return &this->public;
503 }