2 * Copyright (C) 2017 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
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>.
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
20 #include "sw_collector_history.h"
21 #include "sw_collector_dpkg.h"
23 #include <swima/swima_event.h>
24 #include <swid_gen/swid_gen_info.h>
26 typedef struct private_sw_collector_history_t private_sw_collector_history_t
;
29 * Private data of an sw_collector_history_t object.
31 struct private_sw_collector_history_t
{
34 * Public members of sw_collector_history_state_t
36 sw_collector_history_t
public;
39 * Software Event Source Number
44 * Reference to OS info object
46 swid_gen_info_t
*info
;
49 * Reference to collector database
51 sw_collector_db_t
*db
;
56 * Define auxiliary package_t list item object
58 typedef struct package_t package_t
;
69 * Create package_t list item object
71 static package_t
* create_package(swid_gen_info_t
*info
, chunk_t package
,
72 chunk_t version
, chunk_t old_version
)
77 .package
= strndup(package
.ptr
, package
.len
),
78 .version
= strndup(version
.ptr
, version
.len
),
79 .old_version
= strndup(old_version
.ptr
, old_version
.len
),
82 this->sw_id
= info
->create_sw_id(info
, this->package
, this->version
);
85 this->old_sw_id
= info
->create_sw_id(info
, this->package
,
93 * Free package_t list item object
95 static void free_package(package_t
*this)
101 free(this->old_version
);
103 free(this->old_sw_id
);
109 * Extract and parse a single package item
111 static package_t
* extract_package(chunk_t item
, swid_gen_info_t
*info
,
112 sw_collector_history_op_t op
)
114 chunk_t package
, package_stripped
, version
, old_version
;
117 /* extract package name */
118 if (!extract_token(&package
, ' ', &item
))
120 fprintf(stderr
, "version not found.\n");
123 item
= chunk_skip(item
, 1);
125 /* strip architecture suffix if present */
126 if (extract_token(&package_stripped
, ':', &package
))
128 package
= package_stripped
;
131 /* extract versions */
132 version
= old_version
= chunk_empty
;
136 if (extract_token(&version
, ',', &item
))
138 eat_whitespace(&item
);
139 if (!match("automatic", &item
))
141 old_version
= version
;
150 p
= create_package(info
, package
, version
, old_version
);
152 /* generate log entry */
153 if (op
== SW_OP_UPGRADE
)
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
);
161 DBG2(DBG_IMC
, " %s (%s)", p
->package
, p
->version
);
162 DBG2(DBG_IMC
, " %s%s", (op
== SW_OP_INSTALL
) ? "+" : "-", p
->sw_id
);
168 METHOD(sw_collector_history_t
, extract_timestamp
, bool,
169 private_sw_collector_history_t
*this, chunk_t args
, char *buf
)
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)
179 DBG1(DBG_IMC
, "unable to parse start-date");
184 if (sscanf(t1
.ptr
, "%4d-%2d-%2d",
185 &loc
.tm_year
, &loc
.tm_mon
, &loc
.tm_mday
) != 3)
187 DBG1(DBG_IMC
, "unable to parse date format yyyy-mm-dd");
194 if (sscanf(t2
.ptr
, "%2d:%2d:%2d",
195 &loc
.tm_hour
, &loc
.tm_min
, &loc
.tm_sec
) != 3)
197 DBG1(DBG_IMC
, "unable to parse time format hh:mm:ss");
201 /* Convert from local time to UTC */
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
);
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
)
219 bool success
= FALSE
;
223 eat_whitespace(&args
);
225 while (extract_token(&item
, ')', &args
))
227 char *del_sw_id
= NULL
, *del_version
= NULL
;
228 char *nx
, *px
, *vx
, *v1
;
231 uint32_t sw_id
, sw_id_epoch_less
= 0;
234 p
= extract_package(item
, this->info
, op
);
240 /* packages without version information cannot be handled */
241 if (strlen(p
->version
) == 0)
250 /* prepare subsequent deletion sw event */
251 del_sw_id
= p
->sw_id
;
252 del_version
= p
->version
;
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 */
260 sw_id
= this->db
->get_sw_id(this->db
, p
->sw_id
, NULL
, NULL
,
264 /* sw identifier exists - update state to 'installed' */
267 /* this case should not occur */
268 DBG1(DBG_IMC
, " warning: sw_id %d is already "
271 else if (!this->db
->update_sw_id(this->db
, sw_id
, NULL
,
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
);
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
))
297 if (op
!= SW_OP_INSTALL
)
301 /* look for existing installed package versions */
302 e
= this->db
->create_sw_enumerator(this->db
, SW_QUERY_INSTALLED
,
309 while (e
->enumerate(e
, &sw_idx
, &nx
, &px
, &vx
, &ix
))
311 if (streq(vx
, del_version
))
313 /* full match with epoch */
317 v1
= strchr(vx
, ':');
318 if (v1
&& streq(++v1
, del_version
))
320 /* match with stripped epoch */
321 sw_id_epoch_less
= sw_idx
;
326 if (!sw_id
&& sw_id_epoch_less
)
328 /* no full match - fall back to epoch-less match */
329 sw_id
= sw_id_epoch_less
;
333 /* sw identifier exists - update state to 'removed' */
334 if (!this->db
->update_sw_id(this->db
, sw_id
, NULL
, NULL
, FALSE
))
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
);
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
))
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
))
366 args
= chunk_skip(args
, 2);
377 METHOD(sw_collector_history_t
, merge_installed_packages
, bool,
378 private_sw_collector_history_t
*this)
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
;
386 DBG1(DBG_IMC
, "Merging:");
388 dpkg
= sw_collector_dpkg_create();
394 enumerator
= dpkg
->create_sw_enumerator(dpkg
);
395 while (enumerator
->enumerate(enumerator
, &package
, &arch
, &version
))
397 name
= this->info
->create_sw_id(this->info
, package
, version
);
398 DBG3(DBG_IMC
, " %s merged", name
);
400 sw_id
= this->db
->get_sw_id(this->db
, name
, NULL
, NULL
, NULL
,
406 DBG1(DBG_IMC
, " warning: existing sw_id %u"
407 " is not installed", sw_id
);
409 if (!this->db
->update_sw_id(this->db
, sw_id
, name
, version
,
419 /* check for a Debian epoch number */
420 v1
= strchr(version
, ':');
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
,
429 if (sw_id
&& installed
)
431 /* add epoch to existing version */
432 if (!this->db
->update_sw_id(this->db
, sw_id
, name
, version
,
448 /* new sw identifier - create with state 'installed' */
449 sw_id
= this->db
->set_sw_id(this->db
, name
, package
, version
,
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
))
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
));
470 enumerator
->destroy(enumerator
);
476 METHOD(sw_collector_history_t
, destroy
, void,
477 private_sw_collector_history_t
*this)
479 this->info
->destroy(this->info
);
484 * Described in header.
486 sw_collector_history_t
*sw_collector_history_create(sw_collector_db_t
*db
,
489 private_sw_collector_history_t
*this;
490 swid_gen_info_t
*info
;
494 info
= swid_gen_info_create();
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
)
501 DBG1(DBG_IMC
, "%.*s not supported", os
);
508 .extract_timestamp
= _extract_timestamp
,
509 .extract_packages
= _extract_packages
,
510 .merge_installed_packages
= _merge_installed_packages
,
518 return &this->public;