mirror of https://github.com/vmware/tdnf.git
1613 lines
42 KiB
C
1613 lines
42 KiB
C
/*
|
|
* Copyright (C) 2022-2023 VMware, Inc. All Rights Reserved.
|
|
*
|
|
* Licensed under the GNU Lesser General Public License v2.1 (the "License");
|
|
* you may not use this file except in compliance with the License. The terms
|
|
* of the License are located in the COPYING file of this distribution.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
/* for O_RDONLY */
|
|
#include <fcntl.h>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include <rpm/rpmlib.h>
|
|
#include <rpm/rpmdb.h>
|
|
#include <rpm/rpmlog.h>
|
|
#include <rpm/rpmps.h>
|
|
#include <rpm/rpmts.h>
|
|
#include <rpm/rpmdb.h>
|
|
|
|
#include "history.h"
|
|
|
|
#define check_cond(COND) if(!(COND)) { \
|
|
fprintf(stderr, "check_cond failed in %s line %d\n", \
|
|
__FUNCTION__, __LINE__); \
|
|
rc = -1; \
|
|
((void)(rc)); /* suppress "set but not used" warning */ \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_ptr(ptr) if(!(ptr)) { \
|
|
fprintf(stderr, "check_ptr failed in %s line %d\n", \
|
|
__FUNCTION__, __LINE__); \
|
|
rc = -1; \
|
|
((void)(rc)); /* suppress "set but not used" warning */ \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_rc(rc) if((rc) != 0) { \
|
|
fprintf(stderr, "check_rc failed in %s line %d\n", \
|
|
__FUNCTION__, __LINE__); \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_db_rc(db, rc) if((rc) != SQLITE_OK) { \
|
|
fprintf(stderr, \
|
|
"check_db_rc failed with %s in %s line %d\n", \
|
|
sqlite3_errmsg(db), __FUNCTION__, __LINE__); \
|
|
((void)(rc)); /* suppress "set but not used" warning */ \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_db_step(db, step) \
|
|
if((step) != SQLITE_ROW && (step) != SQLITE_DONE) { \
|
|
fprintf(stderr, \
|
|
"check_db_rc failed with %s in %s line %d\n", \
|
|
sqlite3_errmsg(db), __FUNCTION__, __LINE__); \
|
|
rc = -1; \
|
|
((void)(rc)); /* suppress "set but not used" warning */ \
|
|
goto error; \
|
|
}
|
|
|
|
#define check_db_cmd(db, cmd) { \
|
|
rc = (cmd); \
|
|
check_db_rc(db, rc); \
|
|
}
|
|
|
|
#define safe_free(ptr) { if ((ptr) != NULL) { free(ptr); ptr = NULL; }}
|
|
|
|
#define map_isset(map, idx) (map[(idx)-1] != 0)
|
|
#define map_set(map, idx) { map[(idx)-1] = 1;}
|
|
#define map_unset(map, idx) { map[(idx)-1] = 0;}
|
|
|
|
|
|
/* SQL CREATE statements and column ids */
|
|
|
|
#define SQL_CREATE_TABLE_RPMS \
|
|
"CREATE TABLE IF NOT EXISTS " \
|
|
"rpms(" \
|
|
"Id INTEGER PRIMARY KEY AUTOINCREMENT," \
|
|
"nevra TEXT);"
|
|
|
|
#define COLUMN_RPMS_ID 0
|
|
#define COLUMN_RPMS_NEVRA 1
|
|
|
|
#define SQL_CREATE_TABLE_NAMES \
|
|
"CREATE TABLE IF NOT EXISTS " \
|
|
"names(" \
|
|
"Id INTEGER PRIMARY KEY AUTOINCREMENT," \
|
|
"name TEXT);"
|
|
|
|
#define COLUMN_NAMES_ID 0
|
|
#define COLUMN_NAMES_NAME 1
|
|
|
|
#define TABLE_CREATE_TABLE_FLAG_SET \
|
|
"CREATE TABLE IF NOT EXISTS " \
|
|
"flag_set(" \
|
|
"Id INTEGER PRIMARY KEY AUTOINCREMENT," \
|
|
"trans_id INTEGER," \
|
|
"name_id INTEGER," \
|
|
"value INTEGER);"
|
|
|
|
#define COLUMN_FLAG_SET_ID 0
|
|
#define COLUMN_FLAG_SET_TRANS_ID 1
|
|
#define COLUMN_FLAG_SET_NAME_ID 2
|
|
#define COLUMN_FLAG_SET_VALUE 3
|
|
|
|
#define SQL_CREATE_TABLE_TRANSACTIONS \
|
|
"CREATE TABLE IF NOT EXISTS " \
|
|
"transactions(" \
|
|
"Id INTEGER PRIMARY KEY AUTOINCREMENT," \
|
|
"cookie TEXT," \
|
|
"cmdline TEXT," \
|
|
"timestamp INTEGER, " \
|
|
"type INTEGER);"
|
|
|
|
#define COLUMN_TRANSACTIONS_ID 0
|
|
#define COLUMN_TRANSACTIONS_COOKIE 1
|
|
#define COLUMN_TRANSACTIONS_CMDLINE 2
|
|
#define COLUMN_TRANSACTIONS_TIMESTAMP 3
|
|
#define COLUMN_TRANSACTIONS_TYPE 4
|
|
|
|
#define SQL_CREATE_TABLE_TRANS_ITEMS \
|
|
"CREATE TABLE IF NOT EXISTS " \
|
|
"trans_items(" \
|
|
"Id INTEGER PRIMARY KEY AUTOINCREMENT," \
|
|
"trans_id INTEGER," \
|
|
"type INTEGER," \
|
|
"rpm_id INTEGER);"
|
|
|
|
#define COLUMN_TRANS_ITEMS_ID 0
|
|
#define COLUMN_TRANS_ITEMS_TRANS_ID 1
|
|
#define COLUMN_TRANS_ITEMS_TYPE 2
|
|
#define COLUMN_TRANS_ITEMS_RPM_ID 3
|
|
|
|
|
|
static
|
|
int _cmp_int(const void *p1, const void *p2)
|
|
{
|
|
return *(int *)p1 - *(int *)p2;
|
|
}
|
|
|
|
static
|
|
void sort_array(int *arr, int count)
|
|
{
|
|
qsort(arr, count, sizeof(int), _cmp_int);
|
|
}
|
|
|
|
/*
|
|
* Diff arr1 and arr2, with sizes count1 and count2. The arrays must be
|
|
* sorted. The differences will be stored in ponly1 and ponly2, which are
|
|
* dynamically allocated and should be free'd by the caller. Counts of the
|
|
* created arrays are stored in ponly1_count and ponly2_count.
|
|
*/
|
|
static
|
|
int diff_arrays(const int *arr1, int count1,
|
|
const int *arr2, int count2,
|
|
int **ponly1, int *ponly1_count,
|
|
int **ponly2, int *ponly2_count)
|
|
{
|
|
int rc = 0;
|
|
int i1 = 0, i2 = 0;
|
|
int *only1 = NULL, *only2 = NULL;
|
|
int only1_count = 0, only2_count = 0;
|
|
|
|
check_ptr(only1 = (int *)calloc(count1, sizeof(int)));
|
|
check_ptr(only2 = (int *)calloc(count2, sizeof(int)));
|
|
|
|
while(i1 < count1 && i2 < count2) {
|
|
if (arr1[i1] != arr2[i2]) {
|
|
if(arr1[i1] > arr2[i2]) {
|
|
only2[only2_count++] = arr2[i2++];
|
|
} else {
|
|
only1[only1_count++] = arr1[i1++];
|
|
}
|
|
} else {
|
|
i1++;
|
|
i2++;
|
|
}
|
|
}
|
|
|
|
if(i1 == count1) {
|
|
while (i2 < count2) {
|
|
only2[only2_count++] = arr2[i2++];
|
|
}
|
|
} else {
|
|
while (i1 < count1) {
|
|
only1[only1_count++] = arr1[i1++];
|
|
}
|
|
}
|
|
*ponly1 = only1;
|
|
*ponly1_count = only1_count;
|
|
|
|
*ponly2 = only2;
|
|
*ponly2_count = only2_count;
|
|
|
|
error:
|
|
if(rc) {
|
|
safe_free(only1);
|
|
safe_free(only2);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
sqlite3 *init_db(const char *filename)
|
|
{
|
|
sqlite3 *db = NULL;
|
|
|
|
int rc = sqlite3_open(filename, &db);
|
|
check_db_rc(db, rc);
|
|
|
|
error:
|
|
return db;
|
|
}
|
|
|
|
/* Check if table exists. Returns SQLITE_ROW if it exists, SQLITE_DONE if not,
|
|
or error code in case of error */
|
|
static
|
|
int db_table_exists(sqlite3 *db, const char *name)
|
|
{
|
|
int rc = 0;
|
|
sqlite3_stmt *res = NULL;
|
|
|
|
rc = sqlite3_prepare_v2(db,
|
|
"SELECT * FROM sqlite_master "
|
|
"WHERE type='table' AND name=?;",
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_text(res, 1, name, -1, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_step(res);
|
|
sqlite3_finalize(res); res = NULL;
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* count known rpms in db */
|
|
static
|
|
int db_rpms_count(sqlite3 *db, int *pcount)
|
|
{
|
|
int rc = 0, step;
|
|
sqlite3_stmt *res = NULL;
|
|
const char *sql = "SELECT count(*) FROM rpms;";
|
|
|
|
rc = sqlite3_prepare_v2(db,
|
|
sql,
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
if (step == SQLITE_ROW) {
|
|
*pcount = sqlite3_column_int(res, 0);
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* max id in a table (should be same as count unless there are gaps) */
|
|
static
|
|
int db_maxid(sqlite3 *db, const char *table_name, int *pmaxid)
|
|
{
|
|
int rc = 0, step;
|
|
sqlite3_stmt *res = NULL;
|
|
char sql[256];
|
|
|
|
snprintf(sql, sizeof(sql), "SELECT * FROM %s ORDER BY id DESC;", table_name);
|
|
rc = sqlite3_prepare_v2(db,
|
|
sql,
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
check_cond(step == SQLITE_ROW || step == SQLITE_DONE);
|
|
if (step == SQLITE_ROW) {
|
|
*pmaxid = sqlite3_column_int(res, COLUMN_RPMS_ID);
|
|
} else if (step == SQLITE_DONE) {
|
|
*pmaxid = 0;
|
|
}
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* max rpm id db (should be same as count unless there are gaps) */
|
|
static
|
|
int db_rpms_maxid(sqlite3 *db, int *pmaxid)
|
|
{
|
|
return db_maxid(db, "rpms", pmaxid);
|
|
}
|
|
|
|
static
|
|
void db_free_nevra_map(struct history_nevra_map *hnm)
|
|
{
|
|
if (hnm){
|
|
if (hnm->idmap) {
|
|
for (int i = 0; i < hnm->count; i++) {
|
|
safe_free(hnm->idmap[i]);
|
|
}
|
|
free(hnm->idmap);
|
|
}
|
|
free(hnm);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a map with all known NEVRAs.
|
|
*/
|
|
/* TODO: make this generic so it can be used for the 'names' table too */
|
|
static
|
|
struct history_nevra_map *db_string_map(sqlite3 *db, const char *table_name)
|
|
{
|
|
int rc = 0;
|
|
struct history_nevra_map *hnm = NULL;
|
|
sqlite3_stmt *res = NULL;
|
|
char sql_find[256];
|
|
int count = 0;
|
|
char *nevra = NULL;
|
|
|
|
snprintf(sql_find, sizeof(sql_find), "SELECT * FROM %s;", table_name);
|
|
|
|
rc = db_maxid(db, table_name, &count);
|
|
check_db_rc(db, rc);
|
|
|
|
hnm = (struct history_nevra_map *)calloc(1, sizeof(struct history_nevra_map));
|
|
check_ptr(hnm);
|
|
|
|
hnm->idmap = (char **)calloc(count, sizeof(char *));
|
|
check_ptr(hnm->idmap);
|
|
|
|
hnm->count = count;
|
|
|
|
rc = sqlite3_prepare_v2(db, sql_find, -1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
for(int step = sqlite3_step(res); step == SQLITE_ROW; step = sqlite3_step(res)) {
|
|
int id = sqlite3_column_int(res, COLUMN_RPMS_ID);
|
|
nevra = strdup((const char *)sqlite3_column_text(res, COLUMN_RPMS_NEVRA));
|
|
check_ptr(nevra);
|
|
check_cond((id > 0 && id <= count));
|
|
/* map is zero based, rpm ids start with 1 */
|
|
hnm->idmap[id - 1] = nevra;
|
|
}
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
if (rc) {
|
|
db_free_nevra_map(hnm); hnm = NULL;
|
|
safe_free(nevra);
|
|
}
|
|
return hnm;
|
|
}
|
|
|
|
static
|
|
struct history_nevra_map *db_nevra_map(sqlite3 *db)
|
|
{
|
|
return db_string_map(db, "rpms");
|
|
}
|
|
|
|
static
|
|
int db_get_dict_entry(sqlite3 *db,
|
|
const char *table_name, const char *field_name,
|
|
const char *entry, int *pid,
|
|
int create)
|
|
{
|
|
int rc = 0, step;
|
|
sqlite3_stmt *res = NULL;
|
|
char sql[256];
|
|
|
|
snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s = ?;", table_name, field_name);
|
|
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_text(res, 1, entry, -1, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
if (step == SQLITE_ROW) {
|
|
int id = sqlite3_column_int(res, COLUMN_RPMS_ID);
|
|
if (pid)
|
|
*pid = id;
|
|
sqlite3_finalize(res);
|
|
/* we found it, return */
|
|
return 0;
|
|
}
|
|
sqlite3_finalize(res); res = NULL;
|
|
|
|
if (!create) {
|
|
if (pid) *pid = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* add it to db */
|
|
snprintf(sql, sizeof(sql), "INSERT INTO %s(%s) VALUES (?);", table_name, field_name);
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_text(res, 1, entry, -1, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
if (step == SQLITE_DONE) {
|
|
int id = sqlite3_last_insert_rowid(db);
|
|
if (pid)
|
|
*pid = id;
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Add nevra to db if it's not there already. The id will be returned
|
|
* in *pid if that's not NULL.
|
|
*/
|
|
static
|
|
int db_add_nevra(sqlite3 *db, const char *nevra, int *pid)
|
|
{
|
|
return db_get_dict_entry(db, "rpms", "nevra", nevra, pid, 1);
|
|
}
|
|
|
|
/* read transaction items into ht */
|
|
static
|
|
int history_delta_read(sqlite3 *db, struct history_delta *hd, int id)
|
|
{
|
|
int rc = 0;
|
|
sqlite3_stmt *res = NULL;
|
|
const char *sql = "SELECT * FROM trans_items WHERE trans_id = ? ORDER BY rpm_id;";
|
|
int max_count = 0;
|
|
|
|
/* array sizes cannot be larger than number of known rpms */
|
|
rc = db_rpms_count(db, &max_count);
|
|
check_db_rc(db, rc);
|
|
|
|
hd->added_ids = (int *)calloc(max_count, sizeof(int));
|
|
check_ptr(hd->added_ids);
|
|
hd->removed_ids = (int *)calloc(max_count, sizeof(int));
|
|
check_ptr(hd->removed_ids);
|
|
|
|
hd->added_count = 0;
|
|
hd->removed_count = 0;
|
|
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 1, id);
|
|
check_db_rc(db, rc);
|
|
|
|
for(int step = sqlite3_step(res); step == SQLITE_ROW; step = sqlite3_step(res)) {
|
|
int type = sqlite3_column_int(res, COLUMN_TRANS_ITEMS_TYPE);
|
|
int rpm_id = sqlite3_column_int(res, COLUMN_TRANS_ITEMS_RPM_ID);
|
|
if (type == HISTORY_ITEM_TYPE_ADD || type == HISTORY_ITEM_TYPE_SET) {
|
|
check_cond(hd->added_count < max_count);
|
|
hd->added_ids[hd->added_count++] = rpm_id;
|
|
}
|
|
else if (type == HISTORY_ITEM_TYPE_REMOVE) {
|
|
check_cond(hd->removed_count < max_count);
|
|
hd->removed_ids[hd->removed_count++] = rpm_id;
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* play one delta transaction on installed_map */
|
|
static
|
|
int db_play_delta(sqlite3 *db, int trans_id,
|
|
char *installed_map, int map_size)
|
|
{
|
|
int rc = 0;
|
|
sqlite3_stmt *res = NULL;
|
|
const char *sql = "SELECT * FROM trans_items WHERE trans_id = ? ORDER BY rpm_id;";
|
|
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 1, trans_id);
|
|
check_db_rc(db, rc);
|
|
|
|
for(int step = sqlite3_step(res); step == SQLITE_ROW; step = sqlite3_step(res)) {
|
|
int type = sqlite3_column_int(res, COLUMN_TRANS_ITEMS_TYPE);
|
|
int rpm_id = sqlite3_column_int(res, COLUMN_TRANS_ITEMS_RPM_ID);
|
|
check_cond(rpm_id > 0 && rpm_id <= map_size);
|
|
check_cond(type == HISTORY_ITEM_TYPE_ADD || type == HISTORY_ITEM_TYPE_REMOVE);
|
|
if (type == HISTORY_ITEM_TYPE_ADD) {
|
|
map_set(installed_map, rpm_id);
|
|
}
|
|
else if (type == HISTORY_ITEM_TYPE_REMOVE) {
|
|
map_unset(installed_map, rpm_id);
|
|
}
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* play one set transaction on installed_map */
|
|
static
|
|
int db_play_set(sqlite3 *db, int trans_id,
|
|
char *installed_map, int map_size)
|
|
{
|
|
int rc = 0;
|
|
sqlite3_stmt *res = NULL;
|
|
const char *sql = "SELECT * FROM trans_items "
|
|
"WHERE trans_id = ? ORDER BY rpm_id;";
|
|
|
|
rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 1, trans_id);
|
|
check_db_rc(db, rc);
|
|
|
|
for(int step = sqlite3_step(res); step == SQLITE_ROW; step = sqlite3_step(res)) {
|
|
int type = sqlite3_column_int(res, COLUMN_TRANS_ITEMS_TYPE);
|
|
int rpm_id = sqlite3_column_int(res, COLUMN_TRANS_ITEMS_RPM_ID);
|
|
check_cond(rpm_id > 0 && rpm_id <= map_size);
|
|
check_cond(type == HISTORY_ITEM_TYPE_SET);
|
|
map_set(installed_map, rpm_id);
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* Replay all transactions on installed_map to reach trans_id by rewinding to
|
|
* the last baseline state and applying all deltas before and
|
|
* including trans_id.
|
|
* installed_map must have been allocated.
|
|
* Does not install RPMs or modify the db.
|
|
*/
|
|
static
|
|
int db_play_transaction(sqlite3 *db, int trans_id,
|
|
char *installed_map, int map_size)
|
|
{
|
|
sqlite3_stmt *res = NULL;
|
|
int step, rc = 0;
|
|
int base_id = 0;
|
|
|
|
/* find most recent HISTORY_TRANS_TYPE_BASE */
|
|
rc = sqlite3_prepare_v2(db,
|
|
"SELECT * FROM transactions "
|
|
"WHERE type = ? ORDER BY id DESC;",
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 1, HISTORY_TRANS_TYPE_BASE);
|
|
check_db_rc(db, rc);
|
|
|
|
for (step = sqlite3_step(res); step == SQLITE_ROW; step = sqlite3_step(res)) {
|
|
base_id = sqlite3_column_int(res, COLUMN_TRANSACTIONS_ID);
|
|
if (base_id <= trans_id)
|
|
break;
|
|
}
|
|
/* make sure we have found a base line */
|
|
check_cond(step == SQLITE_ROW);
|
|
|
|
/* we found the most recent base state before trans_id,
|
|
set to that state ... */
|
|
rc = db_play_set(db, base_id, installed_map, map_size);
|
|
check_rc(rc);
|
|
/* ... then replay all deltas until we reach the desired trans_id */
|
|
for (int id = base_id + 1; id <= trans_id; id++) {
|
|
rc = db_play_delta(db, id, installed_map, map_size);
|
|
check_rc(rc);
|
|
}
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* Update db with currently installed RPMs.
|
|
* All ids will be saved to array pointed to by pids
|
|
* (if not NULL), the array will be allocated.
|
|
*/
|
|
static
|
|
int db_update_rpms(rpmts ts, sqlite3 *db, int **pids, int *pcount)
|
|
{
|
|
int rc = 0;
|
|
int count = 0, i = 0;
|
|
int *ids = NULL;
|
|
Header h;
|
|
rpmdbMatchIterator mi = NULL;
|
|
char *nevra = NULL;
|
|
|
|
if (pids) {
|
|
/* count installed packages */
|
|
mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
|
|
while ((h = rpmdbNextIterator(mi))) {
|
|
count++;
|
|
}
|
|
rpmdbFreeIterator(mi);
|
|
ids = (int *)calloc(count, sizeof(int));
|
|
}
|
|
|
|
rc = sqlite3_exec(db, SQL_CREATE_TABLE_RPMS, 0, 0, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
|
|
while ((h = rpmdbNextIterator(mi))) {
|
|
nevra = headerGetAsString(h, RPMTAG_NEVRA);
|
|
rc = db_add_nevra(db, nevra, ids ? &ids[i++] : NULL);
|
|
check_db_rc(db, rc);
|
|
safe_free(nevra);
|
|
}
|
|
rpmdbFreeIterator(mi); mi = NULL;
|
|
|
|
if (pids && pcount) {
|
|
sort_array(ids, count);
|
|
|
|
*pids = ids;
|
|
*pcount = count;
|
|
}
|
|
|
|
error:
|
|
safe_free(nevra);
|
|
if (mi)
|
|
rpmdbFreeIterator(mi);
|
|
if (rc && ids)
|
|
free(ids);
|
|
return rc;
|
|
}
|
|
|
|
/* Helper function to add a transaction entry to the db.
|
|
Does not add transaction items */
|
|
static
|
|
int db_add_transaction(sqlite3 *db, int *ptrans_id,
|
|
const char *cmdline, time_t timestamp,
|
|
const char *cookie, int type)
|
|
{
|
|
int rc = 0, ret;
|
|
sqlite3_stmt *res = NULL;
|
|
|
|
rc = sqlite3_prepare_v2(db,
|
|
"INSERT INTO transactions(cmdline, cookie, timestamp, type) VALUES (?, ?, ?, ?);",
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_text(res, 1, cmdline, -1, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_text(res, 2, cookie, -1, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int64(res, 3, timestamp);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 4, type);
|
|
check_db_rc(db, rc);
|
|
|
|
ret = sqlite3_step(res);
|
|
check_cond(ret == SQLITE_DONE);
|
|
*ptrans_id = sqlite3_last_insert_rowid(db);
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
/* Helper function to add transaction items */
|
|
static
|
|
int db_add_trans_items(sqlite3 *db, int trans_id, int type,
|
|
int *rpm_ids, int rpm_count)
|
|
{
|
|
int rc = 0, ret;
|
|
int i;
|
|
sqlite3_stmt *res = NULL;
|
|
|
|
for (i = 0; i < rpm_count; i++) {
|
|
rc = sqlite3_prepare_v2(db,
|
|
"INSERT INTO trans_items(trans_id, type, rpm_id) VALUES (?, ?, ?);",
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 1, trans_id);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 2, type);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 3, rpm_ids[i]);
|
|
check_db_rc(db, rc);
|
|
|
|
ret = sqlite3_step(res);
|
|
check_cond(ret == SQLITE_DONE);
|
|
|
|
sqlite3_finalize(res); res = NULL;
|
|
}
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
int db_set_auto_flag_byid(sqlite3 *db, int trans_id, int name_id, int value)
|
|
{
|
|
int rc = 0, step;
|
|
sqlite3_stmt *res = NULL;
|
|
|
|
/* TODO: check if entry with trans_id and name_id already exists and update,
|
|
instead of creating a new entry */
|
|
rc = sqlite3_prepare_v2(db,
|
|
"INSERT INTO flag_set(trans_id, name_id, value) VALUES (?, ?, ?);",
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 1, trans_id);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 2, name_id);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 3, value);
|
|
check_db_rc(db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
check_cond(step == SQLITE_DONE);
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
int db_set_auto_flag(sqlite3 *db, int trans_id, const char *name, int value)
|
|
{
|
|
int rc = 0;
|
|
int name_id;
|
|
|
|
rc = sqlite3_exec(db, SQL_CREATE_TABLE_NAMES,
|
|
0, 0, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = db_get_dict_entry(db, "names", "name", name, &name_id, 1);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_exec(db, TABLE_CREATE_TABLE_FLAG_SET,
|
|
0, 0, NULL);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = db_set_auto_flag_byid(db, trans_id, name_id, value);
|
|
check_rc(rc);
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
int db_get_auto_flag_byid(sqlite3 *db, int trans_id, int name_id, int *pvalue)
|
|
{
|
|
int rc = 0, step;
|
|
sqlite3_stmt *res = NULL;
|
|
|
|
/* last entry will set the value */
|
|
/* shouldn't matter if we order by id or trans_id? */
|
|
rc = sqlite3_prepare_v2(db,
|
|
"SELECT * FROM flag_set "
|
|
"WHERE name_id = ? AND trans_id <= ? "
|
|
"ORDER BY id DESC;",
|
|
-1, &res, 0);
|
|
check_db_rc(db, rc);
|
|
rc = sqlite3_bind_int(res, 1, name_id);
|
|
check_db_rc(db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 2, trans_id);
|
|
check_db_rc(db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
|
|
if (step == SQLITE_ROW) { /* found */
|
|
*pvalue = sqlite3_column_int(res, COLUMN_FLAG_SET_VALUE);
|
|
} else {
|
|
/* Not found is valid and means value is 0 (unset).
|
|
This can happen although we found the name if trans_id is not the
|
|
latest and an entry was added later. */
|
|
*pvalue = 0;
|
|
}
|
|
sqlite3_finalize(res); res = NULL;
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
return rc;
|
|
}
|
|
|
|
static
|
|
int db_get_auto_flag(sqlite3 *db, int trans_id, const char *name, int *pvalue)
|
|
{
|
|
int rc = 0;
|
|
int name_id;
|
|
|
|
rc = db_table_exists(db, "flag_set");
|
|
if (rc == SQLITE_DONE) { /* no table */
|
|
*pvalue = 0;
|
|
return 0;
|
|
}
|
|
check_cond(rc == SQLITE_ROW);
|
|
|
|
rc = db_table_exists(db, "names");
|
|
if (rc == SQLITE_DONE) { /* no table */
|
|
*pvalue = 0;
|
|
return 0;
|
|
}
|
|
check_cond(rc == SQLITE_ROW);
|
|
|
|
rc = db_get_dict_entry(db, "names", "name", name, &name_id, 0);
|
|
check_db_rc(db, rc);
|
|
|
|
if (name_id == 0) {
|
|
/* not found is valid and means value is 0 (unset) */
|
|
*pvalue = 0;
|
|
return 0;
|
|
}
|
|
|
|
rc = db_get_auto_flag_byid(db, trans_id, name_id, pvalue);
|
|
check_db_rc(db, rc);
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
int history_set_auto_flag(struct history_ctx *ctx, const char *name, int value)
|
|
{
|
|
int rc = 0;
|
|
int oldval;
|
|
|
|
check_ptr(name);
|
|
check_cond(ctx->trans_id > 0);
|
|
|
|
/* setting only when needed avoids cluttering the db */
|
|
rc = db_get_auto_flag(ctx->db, ctx->trans_id, name, &oldval);
|
|
check_rc(rc);
|
|
|
|
if (oldval != value) {
|
|
rc = db_set_auto_flag(ctx->db, ctx->trans_id, name, value);
|
|
check_rc(rc);
|
|
}
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
int history_get_auto_flag(struct history_ctx *ctx, const char *name, int *pvalue)
|
|
{
|
|
int rc = 0;
|
|
|
|
check_ptr(name);
|
|
check_cond(ctx->trans_id > 0);
|
|
rc = db_get_auto_flag(ctx->db, ctx->trans_id, name, pvalue);
|
|
check_rc(rc);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
/* restore flags to values from trans_id */
|
|
int history_restore_auto_flags(struct history_ctx *ctx, int trans_id)
|
|
{
|
|
int rc = 0;
|
|
int i, count;
|
|
|
|
check_ptr(ctx);
|
|
|
|
rc = db_table_exists(ctx->db, "names");
|
|
if (rc == SQLITE_DONE) { /* no table => nothing to restore */
|
|
return 0;
|
|
}
|
|
check_cond(rc == SQLITE_ROW);
|
|
|
|
rc = db_maxid(ctx->db, "names", &count);
|
|
check_rc(rc);
|
|
|
|
for (i = 1; i <= count; i++) {
|
|
int value, oldval;
|
|
|
|
rc = db_get_auto_flag_byid(ctx->db, trans_id, i, &value);
|
|
check_rc(rc);
|
|
|
|
rc = db_get_auto_flag_byid(ctx->db, ctx->trans_id, i, &oldval);
|
|
check_rc(rc);
|
|
|
|
if (value != oldval) {
|
|
rc = db_set_auto_flag_byid(ctx->db, ctx->trans_id, i, value);
|
|
check_rc(rc);
|
|
}
|
|
}
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
/* range is exclusive for from */
|
|
int history_replay_auto_flags(struct history_ctx *ctx, int from, int to)
|
|
{
|
|
int rc = 0;
|
|
int i, count;
|
|
|
|
check_ptr(ctx);
|
|
|
|
rc = db_table_exists(ctx->db, "names");
|
|
if (rc == SQLITE_DONE) { /* no table => nothing to restore */
|
|
return 0;
|
|
}
|
|
check_cond(rc == SQLITE_ROW);
|
|
|
|
rc = db_maxid(ctx->db, "names", &count);
|
|
check_rc(rc);
|
|
|
|
for (i = 1; i <= count; i++) {
|
|
int val_from, val_to;
|
|
|
|
rc = db_get_auto_flag_byid(ctx->db, from, i, &val_from);
|
|
check_rc(rc);
|
|
rc = db_get_auto_flag_byid(ctx->db, to, i, &val_to);
|
|
check_rc(rc);
|
|
|
|
if (val_from != val_to)
|
|
{
|
|
int oldval;
|
|
|
|
rc = db_get_auto_flag_byid(ctx->db, ctx->trans_id, i, &oldval);
|
|
check_rc(rc);
|
|
|
|
if (val_to != oldval) {
|
|
rc = db_set_auto_flag_byid(ctx->db, ctx->trans_id, i, val_to);
|
|
check_rc(rc);
|
|
}
|
|
}
|
|
}
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
void history_free_flags_delta(struct history_flags_delta * hfd)
|
|
{
|
|
if (hfd){
|
|
if (hfd->changed_ids) free(hfd->changed_ids);
|
|
if (hfd->values) free(hfd->values);
|
|
free(hfd);
|
|
}
|
|
}
|
|
|
|
/* range is exclusive for from */
|
|
struct history_flags_delta *
|
|
history_get_flags_delta(struct history_ctx *ctx, int from, int to)
|
|
{
|
|
int rc = 0;
|
|
int i, count;
|
|
struct history_flags_delta *hfd = NULL;
|
|
|
|
check_ptr(ctx);
|
|
|
|
rc = db_table_exists(ctx->db, "names");
|
|
if (rc == SQLITE_DONE) { /* no table => nothing to restore */
|
|
return 0;
|
|
}
|
|
check_cond(rc == SQLITE_ROW);
|
|
rc = db_maxid(ctx->db, "names", &count);
|
|
check_rc(rc);
|
|
|
|
hfd = (struct history_flags_delta *)calloc(1, sizeof(struct history_flags_delta));
|
|
check_ptr(hfd);
|
|
|
|
hfd->changed_ids = calloc(count, sizeof(int));
|
|
check_ptr(hfd->changed_ids);
|
|
hfd->values = calloc(count, sizeof(int));
|
|
check_ptr(hfd->values);
|
|
|
|
for (i = 1; i <= count; i++) {
|
|
int val_from, val_to;
|
|
|
|
rc = db_get_auto_flag_byid(ctx->db, from, i, &val_from);
|
|
check_rc(rc);
|
|
rc = db_get_auto_flag_byid(ctx->db, to, i, &val_to);
|
|
check_rc(rc);
|
|
if (val_from != val_to)
|
|
{
|
|
int oldval;
|
|
|
|
rc = db_get_auto_flag_byid(ctx->db, ctx->trans_id, i, &oldval);
|
|
check_rc(rc);
|
|
|
|
if (val_to != oldval) {
|
|
hfd->changed_ids[hfd->count] = i;
|
|
hfd->values[hfd->count] = val_to;
|
|
hfd->count++;
|
|
}
|
|
}
|
|
}
|
|
error:
|
|
if (rc) {
|
|
history_free_flags_delta(hfd);
|
|
hfd = NULL;
|
|
}
|
|
return hfd;
|
|
}
|
|
|
|
/* Helper to set the ctx cookie, free'ing the old one if needed */
|
|
static
|
|
void history_set_cookie(struct history_ctx *ctx, const char *cookie)
|
|
{
|
|
safe_free(ctx->cookie);
|
|
ctx->cookie = strdup(cookie);
|
|
}
|
|
|
|
/* Helper to convert installed_map into list of ids.
|
|
The list will be sorted. */
|
|
static
|
|
int *get_ids_from_map(const char *map, int map_size, int *pcount)
|
|
{
|
|
int rc = 0;
|
|
int id;
|
|
int *ids = NULL;
|
|
int j = 0, count = 0;
|
|
|
|
for (id = 1; id <= map_size; id++) {
|
|
if (map_isset(map, id))
|
|
count++;
|
|
}
|
|
check_ptr(ids = calloc(count, sizeof(int)));
|
|
for (id = 1; id <= map_size; id++) {
|
|
if (map_isset(map, id))
|
|
ids[j++] = id;
|
|
}
|
|
*pcount = count;
|
|
|
|
error:
|
|
if (rc)
|
|
safe_free(ids);
|
|
return ids;
|
|
}
|
|
|
|
|
|
/* Helper to convert installed_map into list of ids and set it for ctx */
|
|
static
|
|
int history_set_ids_from_map(struct history_ctx *ctx,
|
|
const char *installed_map, int map_size)
|
|
{
|
|
int rc = 0;
|
|
|
|
check_ptr(ctx);
|
|
check_ptr(installed_map);
|
|
|
|
safe_free(ctx->installed_ids);
|
|
ctx->installed_ids =
|
|
get_ids_from_map(installed_map, map_size, &ctx->installed_count);
|
|
check_ptr(ctx->installed_ids);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
void history_free_delta(struct history_delta *hd)
|
|
{
|
|
if (hd) {
|
|
safe_free(hd->added_ids);
|
|
safe_free(hd->removed_ids);
|
|
free(hd);
|
|
}
|
|
}
|
|
|
|
/* Get the delta from trans_id to current state. trans_id points to the state
|
|
where that transaction has been completed. */
|
|
struct history_delta *history_get_delta(struct history_ctx *ctx, int trans_id)
|
|
{
|
|
int rc = 0;
|
|
char *installed_map = NULL;
|
|
int map_size = 0, installed_count;
|
|
struct history_delta *hd =
|
|
(struct history_delta *)calloc(1, sizeof(struct history_delta));
|
|
int *installed_ids = NULL;
|
|
|
|
check_ptr(ctx);
|
|
|
|
check_ptr(hd);
|
|
|
|
/* TODO: store size in ctx and use that */
|
|
rc = db_rpms_maxid(ctx->db, &map_size);
|
|
check_rc(rc);
|
|
|
|
installed_map = (char *)calloc(map_size, sizeof(char));
|
|
check_ptr(installed_map);
|
|
|
|
rc = db_play_transaction(ctx->db, trans_id, installed_map, map_size);
|
|
check_rc(rc);
|
|
|
|
installed_ids = get_ids_from_map(installed_map, map_size, &installed_count);
|
|
check_ptr(installed_ids);
|
|
|
|
rc = diff_arrays(ctx->installed_ids, ctx->installed_count,
|
|
installed_ids, installed_count,
|
|
&hd->removed_ids, &hd->removed_count,
|
|
&hd->added_ids, &hd->added_count);
|
|
check_rc(rc);
|
|
|
|
error:
|
|
safe_free(installed_ids);
|
|
safe_free(installed_map);
|
|
|
|
if (rc) {
|
|
history_free_delta(hd);
|
|
hd = NULL;
|
|
}
|
|
return hd;
|
|
}
|
|
|
|
/* Get the delta from trans_id0 to trans_id1. trans_id1 can be less than
|
|
trans_id0, in which case the delta is reversed. Both ids point to the
|
|
states where those transactions have been completed. */
|
|
struct history_delta *history_get_delta_range(struct history_ctx *ctx,
|
|
int trans_id0, int trans_id1)
|
|
{
|
|
int rc = 0;
|
|
char *installed_map = NULL;
|
|
int map_size = 0, installed_count0, installed_count1;
|
|
struct history_delta *hd =
|
|
(struct history_delta *)calloc(1, sizeof(struct history_delta));
|
|
int *installed_ids0 = NULL, *installed_ids1 = NULL;
|
|
|
|
check_ptr(ctx);
|
|
|
|
check_ptr(hd);
|
|
|
|
/* TODO: store size in ctx and use that */
|
|
rc = db_rpms_maxid(ctx->db, &map_size);
|
|
check_rc(rc);
|
|
|
|
installed_map = (char *)calloc(map_size, sizeof(char));
|
|
check_ptr(installed_map);
|
|
|
|
rc = db_play_transaction(ctx->db, trans_id0, installed_map, map_size);
|
|
check_rc(rc);
|
|
|
|
installed_ids0 = get_ids_from_map(installed_map, map_size, &installed_count0);
|
|
check_ptr(installed_ids0);
|
|
|
|
safe_free(installed_map);
|
|
|
|
installed_map = (char *)calloc(map_size, sizeof(char));
|
|
check_ptr(installed_map);
|
|
|
|
rc = db_play_transaction(ctx->db, trans_id1, installed_map, map_size);
|
|
check_rc(rc);
|
|
|
|
installed_ids1 = get_ids_from_map(installed_map, map_size, &installed_count1);
|
|
check_ptr(installed_ids1);
|
|
|
|
rc = diff_arrays(installed_ids1, installed_count1,
|
|
installed_ids0, installed_count0,
|
|
&hd->removed_ids, &hd->removed_count,
|
|
&hd->added_ids, &hd->added_count);
|
|
check_rc(rc);
|
|
|
|
error:
|
|
safe_free(installed_ids0);
|
|
safe_free(installed_ids1);
|
|
|
|
safe_free(installed_map);
|
|
|
|
if (rc) {
|
|
history_free_delta(hd);
|
|
hd = NULL;
|
|
}
|
|
return hd;
|
|
}
|
|
|
|
void history_free_nevra_map(struct history_nevra_map *hnm)
|
|
{
|
|
db_free_nevra_map(hnm);
|
|
}
|
|
|
|
struct history_nevra_map *history_nevra_map(struct history_ctx *ctx)
|
|
{
|
|
int rc = 0;
|
|
check_ptr(ctx);
|
|
return db_nevra_map(ctx->db);
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
char *history_get_nevra(struct history_nevra_map *hnm, int id)
|
|
{
|
|
int rc = 0;
|
|
|
|
check_ptr(hnm);
|
|
if (id > 0 && id <= hnm->count)
|
|
return hnm->idmap[id - 1];
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
/* set ctx to the state at trans_id */
|
|
int history_set_state(struct history_ctx *ctx, int trans_id)
|
|
{
|
|
int rc = 0;
|
|
char *installed_map = NULL;
|
|
int map_size = 0;
|
|
|
|
check_ptr(ctx);
|
|
|
|
rc = db_rpms_maxid(ctx->db, &map_size);
|
|
check_rc(rc);
|
|
|
|
installed_map = (char *)calloc(map_size, sizeof(char));
|
|
check_ptr(installed_map);
|
|
|
|
rc = db_play_transaction(ctx->db, trans_id, installed_map, map_size);
|
|
check_rc(rc);
|
|
|
|
history_set_ids_from_map(ctx, installed_map, map_size);
|
|
|
|
ctx->trans_id = trans_id;
|
|
error:
|
|
safe_free(installed_map);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Update state in ctx and db by setting a baseline state.
|
|
* This is used to initialize the db, but can be called later to set a new baseline.
|
|
*/
|
|
int history_record_state(struct history_ctx *ctx)
|
|
{
|
|
int rc = 0;
|
|
char *err_msg = NULL;
|
|
int trans_id = 0;
|
|
|
|
check_ptr(ctx);
|
|
|
|
/* avoid unfinished transaction record on failure or crash */
|
|
rc = sqlite3_exec(ctx->db, "BEGIN TRANSACTION;", 0, 0, NULL);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
rc = sqlite3_exec(ctx->db,
|
|
SQL_CREATE_TABLE_TRANSACTIONS,
|
|
0, 0, NULL);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
rc = db_add_transaction(ctx->db, &trans_id, "(set)", time(NULL),
|
|
ctx->cookie, HISTORY_TRANS_TYPE_BASE);
|
|
check_rc(rc);
|
|
|
|
rc = sqlite3_exec(ctx->db,
|
|
SQL_CREATE_TABLE_TRANS_ITEMS,
|
|
0, 0, &err_msg);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
rc = db_add_trans_items(ctx->db, trans_id, HISTORY_ITEM_TYPE_SET,
|
|
ctx->installed_ids, ctx->installed_count);
|
|
check_rc(rc);
|
|
|
|
ctx->trans_id = trans_id;
|
|
error:
|
|
if(err_msg)
|
|
sqlite3_free(err_msg);
|
|
if (rc)
|
|
sqlite3_exec(ctx->db, "ROLLBACK;", 0, 0, NULL);
|
|
else
|
|
sqlite3_exec(ctx->db, "COMMIT;", 0, 0, NULL);
|
|
return rc;
|
|
}
|
|
|
|
void history_free_transactions(struct history_transaction *tas, int count)
|
|
{
|
|
if (tas) {
|
|
for(int i = 0; i < count; i++) {
|
|
safe_free(tas[i].cookie);
|
|
safe_free(tas[i].cmdline);
|
|
safe_free(tas[i].delta.added_ids);
|
|
safe_free(tas[i].delta.removed_ids);
|
|
}
|
|
free(tas);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Get a list of transcactions and store them into buffer pointed to by ptas.
|
|
The number will be stored into pcount. Optionally a range can be set with
|
|
the from and to parameters. Either both have to be set to a range, or both
|
|
need to be 0 for no range (all transactions). The list will be in reverse
|
|
order if the reverse parameter is set.
|
|
*/
|
|
int history_get_transactions(struct history_ctx *ctx,
|
|
struct history_transaction **ptas,
|
|
int *pcount,
|
|
int reverse, int from, int to)
|
|
{
|
|
sqlite3_stmt *res = NULL;
|
|
int step, rc = 0;
|
|
int i, count = 0;
|
|
struct history_transaction *tas = NULL;
|
|
char sql[256];
|
|
|
|
check_ptr(ctx);
|
|
check_ptr(ptas);
|
|
check_ptr(pcount);
|
|
check_cond(to >= from);
|
|
|
|
if (from == 0 || to == 0) {
|
|
rc = sqlite3_prepare_v2(ctx->db,
|
|
"SELECT * FROM transactions ORDER BY id DESC;",
|
|
-1, &res, 0);
|
|
check_db_rc(ctx->db, rc);
|
|
step = sqlite3_step(res);
|
|
check_cond(step == SQLITE_ROW);
|
|
|
|
count = sqlite3_column_int(res, COLUMN_TRANSACTIONS_ID);
|
|
sqlite3_finalize(res); res = NULL;
|
|
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT * FROM transactions ORDER BY id%s;",
|
|
reverse ? " DESC" : "");
|
|
rc = sqlite3_prepare_v2(ctx->db,
|
|
sql,
|
|
-1, &res, 0);
|
|
check_db_rc(ctx->db, rc);
|
|
} else {
|
|
count = to - from + 1;
|
|
snprintf(sql, sizeof(sql),
|
|
"SELECT * FROM transactions WHERE id BETWEEN ? AND ? ORDER BY id%s;",
|
|
reverse ? " DESC" : "");
|
|
rc = sqlite3_prepare_v2(ctx->db,
|
|
sql,
|
|
-1, &res, 0);
|
|
check_db_rc(ctx->db, rc);
|
|
rc = sqlite3_bind_int(res, 1, from);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
rc = sqlite3_bind_int(res, 2, to);
|
|
check_db_rc(ctx->db, rc);
|
|
}
|
|
|
|
tas = (struct history_transaction *)calloc(count, sizeof(struct history_transaction));
|
|
check_ptr(tas);
|
|
|
|
for (i = 0, step = sqlite3_step(res);
|
|
step == SQLITE_ROW;
|
|
step = sqlite3_step(res), i++) {
|
|
check_cond(i >= 0 && i < count);
|
|
|
|
tas[i].id = sqlite3_column_int(res, COLUMN_TRANSACTIONS_ID);
|
|
|
|
const char *cookie = (char *)sqlite3_column_text(res, COLUMN_TRANSACTIONS_COOKIE);
|
|
tas[i].cookie = cookie ? strdup(cookie) : NULL;
|
|
|
|
const char *cmdline = (char *)sqlite3_column_text(res, COLUMN_TRANSACTIONS_CMDLINE);
|
|
tas[i].cmdline = cmdline ? strdup(cmdline) : NULL;
|
|
|
|
tas[i].timestamp = sqlite3_column_int(res, COLUMN_TRANSACTIONS_TIMESTAMP);
|
|
tas[i].type = sqlite3_column_int(res, COLUMN_TRANSACTIONS_TYPE);
|
|
}
|
|
count = i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
rc = history_delta_read(ctx->db, &tas[i].delta, tas[i].id);
|
|
check_rc(rc);
|
|
}
|
|
|
|
*ptas = tas;
|
|
*pcount = count;
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
if (rc)
|
|
history_free_transactions(tas, count);
|
|
return rc;
|
|
}
|
|
|
|
/* create an empty transaction - useful if we are just going to add flags */
|
|
int history_add_transaction(struct history_ctx *ctx, const char *cmdline)
|
|
{
|
|
int rc = 0;
|
|
int trans_id;
|
|
time_t now = time(NULL);
|
|
|
|
check_ptr(ctx);
|
|
check_ptr(cmdline);
|
|
|
|
rc = db_add_transaction(ctx->db, &trans_id, cmdline, now,
|
|
/* reuse cookie, we are not changing the rpmdb */
|
|
ctx->cookie,
|
|
HISTORY_TRANS_TYPE_DELTA);
|
|
check_rc(rc);
|
|
ctx->trans_id = trans_id;
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
Update state in ctx and db to actual RPM state on system if it has changed
|
|
by adding a delta transaction.
|
|
*/
|
|
int history_update_state(struct history_ctx *ctx, rpmts ts, const char *cmdline)
|
|
{
|
|
int rc = 0;
|
|
int trans_id;
|
|
int *current_ids = NULL, current_count;
|
|
int *added_ids = NULL, added_count;
|
|
int *removed_ids = NULL, removed_count;
|
|
char *cookie = NULL;
|
|
|
|
check_ptr(ctx);
|
|
check_ptr(ts);
|
|
|
|
cookie = rpmdbCookie(rpmtsGetRdb(ts));
|
|
check_ptr(cookie);
|
|
|
|
if (strcmp(ctx->cookie, cookie) == 0) {
|
|
/* nothing changed */
|
|
safe_free(cookie);
|
|
return 0;
|
|
}
|
|
|
|
rc = db_update_rpms(ts, ctx->db, ¤t_ids, ¤t_count);
|
|
check_rc(rc);
|
|
|
|
rc = diff_arrays(ctx->installed_ids, ctx->installed_count,
|
|
current_ids, current_count,
|
|
&removed_ids, &removed_count,
|
|
&added_ids, &added_count);
|
|
check_rc(rc);
|
|
|
|
/* avoid unfinished transaction record on failure or crash */
|
|
rc = sqlite3_exec(ctx->db, "BEGIN TRANSACTION;", 0, 0, NULL);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
rc = db_add_transaction(ctx->db, &trans_id, cmdline, time(NULL),
|
|
cookie, HISTORY_TRANS_TYPE_DELTA);
|
|
check_rc(rc);
|
|
|
|
/* both added_count and removed_count can be 0 even if the cookie differed
|
|
if there was a reinstall */
|
|
if (added_count > 0) {
|
|
rc = db_add_trans_items(ctx->db, trans_id, HISTORY_ITEM_TYPE_ADD,
|
|
added_ids, added_count);
|
|
check_rc(rc);
|
|
}
|
|
if (removed_count > 0) {
|
|
rc = db_add_trans_items(ctx->db, trans_id, HISTORY_ITEM_TYPE_REMOVE,
|
|
removed_ids, removed_count);
|
|
check_rc(rc);
|
|
}
|
|
|
|
/* replace ctx->installed_ids */
|
|
safe_free(ctx->installed_ids);
|
|
ctx->installed_ids = current_ids;
|
|
ctx->installed_count = current_count;
|
|
|
|
history_set_cookie(ctx, cookie);
|
|
ctx->trans_id = trans_id;
|
|
error:
|
|
if (rc) {
|
|
sqlite3_exec(ctx->db, "ROLLBACK;", 0, 0, NULL);
|
|
safe_free(current_ids);
|
|
} else
|
|
sqlite3_exec(ctx->db, "COMMIT;", 0, 0, NULL);
|
|
safe_free(added_ids);
|
|
safe_free(removed_ids);
|
|
safe_free(cookie);
|
|
return rc;
|
|
}
|
|
|
|
/* sync history context to current state from ts */
|
|
int history_sync(struct history_ctx *ctx, rpmts ts)
|
|
{
|
|
sqlite3_stmt *res = NULL;
|
|
int step, rc = 0;
|
|
char *cookie = NULL;
|
|
int db_isfresh = 1;
|
|
|
|
check_ptr(ctx);
|
|
check_ptr(ts);
|
|
|
|
rpmtsOpenDB(ts, O_RDONLY);
|
|
cookie = rpmdbCookie(rpmtsGetRdb(ts));
|
|
/* this fails if the rpm db isn't opened */
|
|
check_ptr(cookie);
|
|
|
|
step = db_table_exists(ctx->db, "transactions");
|
|
|
|
if (step == SQLITE_ROW) {
|
|
rc = sqlite3_prepare_v2(ctx->db,
|
|
"SELECT * FROM transactions ORDER BY id DESC;",
|
|
-1, &res, 0);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
if (step == SQLITE_ROW) {
|
|
int id = sqlite3_column_int(res, COLUMN_TRANSACTIONS_ID);
|
|
const char *cookie_db = (char *)sqlite3_column_text(res, 1);
|
|
|
|
if (strcmp(cookie, cookie_db) != 0) {
|
|
/*
|
|
* cookie has changed but not by us (probably rpm) -
|
|
* update state by adding a pseudo transaction.
|
|
*/
|
|
rc = history_set_state(ctx, id);
|
|
check_rc(rc);
|
|
history_set_cookie(ctx, cookie_db);
|
|
rc = history_update_state(ctx, ts, "(unknown)");
|
|
check_rc(rc);
|
|
} else {
|
|
/*
|
|
* No change, we can either update from db or read rpms.
|
|
* The former may need replaying history, the latter may be faster
|
|
*/
|
|
rc = db_update_rpms(ts, ctx->db,
|
|
&(ctx->installed_ids), &ctx->installed_count);
|
|
check_rc(rc);
|
|
history_set_cookie(ctx, cookie_db);
|
|
ctx->trans_id = id;
|
|
}
|
|
sqlite3_finalize(res); res = NULL;
|
|
db_isfresh = 0;
|
|
}
|
|
} else {
|
|
check_cond(step == SQLITE_DONE);
|
|
}
|
|
|
|
if (db_isfresh) {
|
|
/* we are starting from scratch */
|
|
history_set_cookie(ctx, cookie);
|
|
|
|
rc = db_update_rpms(ts, ctx->db,
|
|
&(ctx->installed_ids), &ctx->installed_count);
|
|
check_rc(rc);
|
|
|
|
rc = history_record_state(ctx);
|
|
check_rc(rc);
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
safe_free(cookie);
|
|
return rc;
|
|
}
|
|
|
|
struct history_ctx *create_history_ctx(const char *db_filename)
|
|
{
|
|
int rc = 0, step;
|
|
sqlite3 *db;
|
|
struct history_ctx *ctx = NULL;
|
|
sqlite3_stmt *res = NULL;
|
|
|
|
check_ptr(db_filename);
|
|
|
|
db = init_db(db_filename);
|
|
check_ptr(db);
|
|
|
|
ctx = (struct history_ctx *)calloc(1, sizeof(struct history_ctx));
|
|
check_ptr(ctx);
|
|
|
|
ctx->db = db;
|
|
|
|
step = db_table_exists(ctx->db, "transactions");
|
|
check_db_step(db, step);
|
|
|
|
if (step == SQLITE_ROW) {
|
|
rc = sqlite3_prepare_v2(ctx->db,
|
|
"SELECT * FROM transactions ORDER BY id DESC;",
|
|
-1, &res, 0);
|
|
check_db_rc(ctx->db, rc);
|
|
|
|
step = sqlite3_step(res);
|
|
if (step == SQLITE_ROW) {
|
|
const char *cookie = (char *)sqlite3_column_text(res, COLUMN_TRANSACTIONS_COOKIE);
|
|
if (cookie)
|
|
history_set_cookie(ctx, cookie);
|
|
ctx->trans_id = sqlite3_column_int(res, COLUMN_TRANSACTIONS_ID);
|
|
}
|
|
}
|
|
error:
|
|
if (res)
|
|
sqlite3_finalize(res);
|
|
if (rc) {
|
|
destroy_history_ctx(ctx);
|
|
return NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
void destroy_history_ctx(struct history_ctx *ctx)
|
|
{
|
|
if (ctx) {
|
|
if(ctx->db)
|
|
sqlite3_close(ctx->db);
|
|
safe_free(ctx->installed_ids);
|
|
safe_free(ctx->cookie);
|
|
free(ctx);
|
|
}
|
|
}
|