520 lines
11 KiB
C
520 lines
11 KiB
C
/*
|
|
* Copyright (C) 2017 Andreas Steffen
|
|
* HSR Hochschule fuer Technik Rapperswil
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#include "sw_collector_history.h"
|
|
#include "sw_collector_dpkg.h"
|
|
|
|
#include <swima/swima_event.h>
|
|
#include <swid_gen/swid_gen_info.h>
|
|
|
|
typedef struct private_sw_collector_history_t private_sw_collector_history_t;
|
|
|
|
/**
|
|
* Private data of an sw_collector_history_t object.
|
|
*/
|
|
struct private_sw_collector_history_t {
|
|
|
|
/**
|
|
* Public members of sw_collector_history_state_t
|
|
*/
|
|
sw_collector_history_t public;
|
|
|
|
/**
|
|
* Software Event Source Number
|
|
*/
|
|
uint8_t source;
|
|
|
|
/**
|
|
* Reference to OS info object
|
|
*/
|
|
swid_gen_info_t *info;
|
|
|
|
/**
|
|
* Reference to collector database
|
|
*/
|
|
sw_collector_db_t *db;
|
|
|
|
};
|
|
|
|
/**
|
|
* Define auxiliary package_t list item object
|
|
*/
|
|
typedef struct package_t package_t;
|
|
|
|
struct package_t {
|
|
char *package;
|
|
char *version;
|
|
char *old_version;
|
|
char *sw_id;
|
|
char *old_sw_id;
|
|
};
|
|
|
|
/**
|
|
* Create package_t list item object
|
|
*/
|
|
static package_t* create_package(swid_gen_info_t *info, chunk_t package,
|
|
chunk_t version, chunk_t old_version)
|
|
{
|
|
package_t *this;
|
|
|
|
INIT(this,
|
|
.package = strndup(package.ptr, package.len),
|
|
.version = strndup(version.ptr, version.len),
|
|
.old_version = strndup(old_version.ptr, old_version.len),
|
|
)
|
|
|
|
this->sw_id = info->create_sw_id(info, this->package, this->version);
|
|
if (old_version.len)
|
|
{
|
|
this->old_sw_id = info->create_sw_id(info, this->package,
|
|
this->old_version);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Free package_t list item object
|
|
*/
|
|
static void free_package(package_t *this)
|
|
{
|
|
if (this)
|
|
{
|
|
free(this->package);
|
|
free(this->version);
|
|
free(this->old_version);
|
|
free(this->sw_id);
|
|
free(this->old_sw_id);
|
|
free(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract and parse a single package item
|
|
*/
|
|
static package_t* extract_package(chunk_t item, swid_gen_info_t *info,
|
|
sw_collector_history_op_t op)
|
|
{
|
|
chunk_t package, package_stripped, version, old_version;
|
|
package_t *p;
|
|
|
|
/* extract package name */
|
|
if (!extract_token(&package, ' ', &item))
|
|
{
|
|
fprintf(stderr, "version not found.\n");
|
|
return NULL;
|
|
}
|
|
item = chunk_skip(item, 1);
|
|
|
|
/* strip architecture suffix if present */
|
|
if (extract_token(&package_stripped, ':', &package))
|
|
{
|
|
package = package_stripped;
|
|
}
|
|
|
|
/* extract versions */
|
|
version = old_version = chunk_empty;
|
|
|
|
if (item.len > 0)
|
|
{
|
|
if (extract_token(&version, ',', &item))
|
|
{
|
|
eat_whitespace(&item);
|
|
if (!match("automatic", &item))
|
|
{
|
|
old_version = version;
|
|
version = item;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
version = item;
|
|
}
|
|
}
|
|
p = create_package(info, package, version, old_version);
|
|
|
|
/* generate log entry */
|
|
if (op == SW_OP_UPGRADE)
|
|
{
|
|
DBG2(DBG_IMC, " %s (%s, %s)", p->package, p->old_version, p->version);
|
|
DBG2(DBG_IMC, " +%s", p->sw_id);
|
|
DBG2(DBG_IMC, " -%s", p->old_sw_id);
|
|
}
|
|
else
|
|
{
|
|
DBG2(DBG_IMC, " %s (%s)", p->package, p->version);
|
|
DBG2(DBG_IMC, " %s%s", (op == SW_OP_INSTALL) ? "+" : "-", p->sw_id);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
METHOD(sw_collector_history_t, extract_timestamp, bool,
|
|
private_sw_collector_history_t *this, chunk_t args, char *buf)
|
|
{
|
|
struct tm loc, utc;
|
|
chunk_t t1, t2;
|
|
time_t t;
|
|
|
|
/* Break down local time with format t1 = yyyy-mm-dd and t2 = hh:mm:ss */
|
|
if (!eat_whitespace(&args) || !extract_token(&t1, ' ', &args) ||
|
|
!eat_whitespace(&args) || t1.len != 10 || args.len != 8)
|
|
{
|
|
DBG1(DBG_IMC, "unable to parse start-date");
|
|
return FALSE;
|
|
}
|
|
t2 = args;
|
|
|
|
if (sscanf(t1.ptr, "%4d-%2d-%2d",
|
|
&loc.tm_year, &loc.tm_mon, &loc.tm_mday) != 3)
|
|
{
|
|
DBG1(DBG_IMC, "unable to parse date format yyyy-mm-dd");
|
|
return FALSE;
|
|
}
|
|
loc.tm_year -= 1900;
|
|
loc.tm_mon -= 1;
|
|
loc.tm_isdst = -1;
|
|
|
|
if (sscanf(t2.ptr, "%2d:%2d:%2d",
|
|
&loc.tm_hour, &loc.tm_min, &loc.tm_sec) != 3)
|
|
{
|
|
DBG1(DBG_IMC, "unable to parse time format hh:mm:ss");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Convert from local time to UTC */
|
|
t = mktime(&loc);
|
|
gmtime_r(&t, &utc);
|
|
utc.tm_year += 1900;
|
|
utc.tm_mon += 1;
|
|
|
|
/* Form timestamp according to RFC 3339 (20 characters) */
|
|
snprintf(buf, 21, "%4d-%02d-%02dT%02d:%02d:%02dZ",
|
|
utc.tm_year, utc.tm_mon, utc.tm_mday,
|
|
utc.tm_hour, utc.tm_min, utc.tm_sec);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(sw_collector_history_t, extract_packages, bool,
|
|
private_sw_collector_history_t *this, chunk_t args, uint32_t eid,
|
|
sw_collector_history_op_t op)
|
|
{
|
|
bool success = FALSE;
|
|
package_t *p = NULL;
|
|
chunk_t item;
|
|
|
|
eat_whitespace(&args);
|
|
|
|
while (extract_token(&item, ')', &args))
|
|
{
|
|
char *del_sw_id = NULL, *del_version = NULL;
|
|
char *nx, *px, *vx, *v1;
|
|
bool installed;
|
|
u_int sw_idx, ix;
|
|
uint32_t sw_id, sw_id_epoch_less = 0;
|
|
enumerator_t *e;
|
|
|
|
p = extract_package(item, this->info, op);
|
|
if (!p)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
/* packages without version information cannot be handled */
|
|
if (strlen(p->version) == 0)
|
|
{
|
|
free_package(p);
|
|
continue;
|
|
}
|
|
|
|
switch (op)
|
|
{
|
|
case SW_OP_REMOVE:
|
|
/* prepare subsequent deletion sw event */
|
|
del_sw_id = p->sw_id;
|
|
del_version = p->version;
|
|
break;
|
|
case SW_OP_UPGRADE:
|
|
/* prepare subsequent deletion sw event */
|
|
del_sw_id = p->old_sw_id;
|
|
del_version = p->old_version;
|
|
/* fall through to next case */
|
|
case SW_OP_INSTALL:
|
|
sw_id = this->db->get_sw_id(this->db, p->sw_id, NULL, NULL,
|
|
NULL, &installed);
|
|
if (sw_id)
|
|
{
|
|
/* sw identifier exists - update state to 'installed' */
|
|
if (installed)
|
|
{
|
|
/* this case should not occur */
|
|
DBG1(DBG_IMC, " warning: sw_id %d is already "
|
|
"installed", sw_id);
|
|
}
|
|
else if (!this->db->update_sw_id(this->db, sw_id, NULL,
|
|
NULL, TRUE))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* new sw identifier - create with state 'installed' */
|
|
sw_id = this->db->set_sw_id(this->db, p->sw_id, p->package,
|
|
p->version, this->source, TRUE);
|
|
if (!sw_id)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* add creation sw event with current eid */
|
|
if (!this->db->add_sw_event(this->db, eid, sw_id,
|
|
SWIMA_EVENT_ACTION_CREATION))
|
|
{
|
|
goto end;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (op != SW_OP_INSTALL)
|
|
{
|
|
sw_id = 0;
|
|
|
|
/* look for existing installed package versions */
|
|
e = this->db->create_sw_enumerator(this->db, SW_QUERY_INSTALLED,
|
|
p->package);
|
|
if (!e)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
while (e->enumerate(e, &sw_idx, &nx, &px, &vx, &ix))
|
|
{
|
|
if (streq(vx, del_version))
|
|
{
|
|
/* full match with epoch */
|
|
sw_id = sw_idx;
|
|
break;
|
|
}
|
|
v1 = strchr(vx, ':');
|
|
if (v1 && streq(++v1, del_version))
|
|
{
|
|
/* match with stripped epoch */
|
|
sw_id_epoch_less = sw_idx;
|
|
}
|
|
}
|
|
e->destroy(e);
|
|
|
|
if (!sw_id && sw_id_epoch_less)
|
|
{
|
|
/* no full match - fall back to epoch-less match */
|
|
sw_id = sw_id_epoch_less;
|
|
}
|
|
if (sw_id)
|
|
{
|
|
/* sw identifier exists - update state to 'removed' */
|
|
if (!this->db->update_sw_id(this->db, sw_id, NULL, NULL, FALSE))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* new sw identifier - create with state 'removed' */
|
|
sw_id = this->db->set_sw_id(this->db, del_sw_id, p->package,
|
|
del_version, this->source, FALSE);
|
|
|
|
/* add creation sw event with eid = 1 */
|
|
if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
|
|
SWIMA_EVENT_ACTION_CREATION))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* add creation sw event with current eid */
|
|
if (!this->db->add_sw_event(this->db, eid, sw_id,
|
|
SWIMA_EVENT_ACTION_DELETION))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
free_package(p);
|
|
|
|
if (args.len < 2)
|
|
{
|
|
break;
|
|
}
|
|
args = chunk_skip(args, 2);
|
|
}
|
|
p = NULL;
|
|
success = TRUE;
|
|
|
|
end:
|
|
free_package(p);
|
|
|
|
return success;
|
|
}
|
|
|
|
METHOD(sw_collector_history_t, merge_installed_packages, bool,
|
|
private_sw_collector_history_t *this)
|
|
{
|
|
uint32_t sw_id, count = 0;
|
|
char *package, *arch, *version, *v1, *name, *n1;
|
|
bool installed, success = FALSE;
|
|
sw_collector_dpkg_t *dpkg;
|
|
enumerator_t *enumerator;
|
|
|
|
DBG1(DBG_IMC, "Merging:");
|
|
|
|
dpkg = sw_collector_dpkg_create();
|
|
if (!dpkg)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
enumerator = dpkg->create_sw_enumerator(dpkg);
|
|
while (enumerator->enumerate(enumerator, &package, &arch, &version))
|
|
{
|
|
name = this->info->create_sw_id(this->info, package, version);
|
|
DBG3(DBG_IMC, " %s merged", name);
|
|
|
|
sw_id = this->db->get_sw_id(this->db, name, NULL, NULL, NULL,
|
|
&installed);
|
|
if (sw_id)
|
|
{
|
|
if (!installed)
|
|
{
|
|
DBG1(DBG_IMC, " warning: existing sw_id %u"
|
|
" is not installed", sw_id);
|
|
|
|
if (!this->db->update_sw_id(this->db, sw_id, name, version,
|
|
TRUE))
|
|
{
|
|
free(name);
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* check for a Debian epoch number */
|
|
v1 = strchr(version, ':');
|
|
if (v1)
|
|
{
|
|
/* check for existing and installed epoch-less version */
|
|
n1 = this->info->create_sw_id(this->info, package, ++v1);
|
|
sw_id = this->db->get_sw_id(this->db, n1, NULL, NULL, NULL,
|
|
&installed);
|
|
free(n1);
|
|
|
|
if (sw_id && installed)
|
|
{
|
|
/* add epoch to existing version */
|
|
if (!this->db->update_sw_id(this->db, sw_id, name, version,
|
|
installed))
|
|
{
|
|
free(name);
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sw_id = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!sw_id)
|
|
{
|
|
/* new sw identifier - create with state 'installed' */
|
|
sw_id = this->db->set_sw_id(this->db, name, package, version,
|
|
this->source, TRUE);
|
|
|
|
/* add creation sw event with eid = 1 */
|
|
if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
|
|
SWIMA_EVENT_ACTION_CREATION))
|
|
{
|
|
free(name);
|
|
goto end;
|
|
}
|
|
|
|
}
|
|
free(name);
|
|
count++;
|
|
}
|
|
success = TRUE;
|
|
|
|
DBG1(DBG_IMC, " merged %u installed packages, %u registered in database",
|
|
count, this->db->get_sw_id_count(this->db, SW_QUERY_INSTALLED));
|
|
|
|
end:
|
|
enumerator->destroy(enumerator);
|
|
dpkg->destroy(dpkg);
|
|
|
|
return success;
|
|
}
|
|
|
|
METHOD(sw_collector_history_t, destroy, void,
|
|
private_sw_collector_history_t *this)
|
|
{
|
|
this->info->destroy(this->info);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* Described in header.
|
|
*/
|
|
sw_collector_history_t *sw_collector_history_create(sw_collector_db_t *db,
|
|
uint8_t source)
|
|
{
|
|
private_sw_collector_history_t *this;
|
|
swid_gen_info_t *info;
|
|
os_type_t os_type;
|
|
char *os;
|
|
|
|
info = swid_gen_info_create();
|
|
|
|
/* check if OS supports apg/dpkg history logs */
|
|
info->get_os(info, &os);
|
|
os_type = info->get_os_type(info);
|
|
if (os_type != OS_TYPE_DEBIAN && os_type != OS_TYPE_UBUNTU)
|
|
{
|
|
DBG1(DBG_IMC, "%.*s not supported", os);
|
|
info->destroy(info);
|
|
return NULL;
|
|
}
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.extract_timestamp = _extract_timestamp,
|
|
.extract_packages = _extract_packages,
|
|
.merge_installed_packages = _merge_installed_packages,
|
|
.destroy = _destroy,
|
|
},
|
|
.source = source,
|
|
.info = info,
|
|
.db = db,
|
|
);
|
|
|
|
return &this->public;
|
|
}
|