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