Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/pkg_jobs_conflicts.c
2065 views
1
/*-
2
* Copyright (c) 2014 Vsevolod Stakhov <[email protected]>
3
* All rights reserved.
4
*
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions
7
* are met:
8
* 1. Redistributions of source code must retain the above copyright
9
* notice, this list of conditions and the following disclaimer
10
* in this position and unchanged.
11
* 2. Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
*
15
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
19
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
*/
26
27
#include <sys/param.h>
28
#include <sys/types.h>
29
#include <stdbool.h>
30
#include <stdlib.h>
31
#include <string.h>
32
33
#include "pkg.h"
34
#include "private/event.h"
35
#include "private/pkg.h"
36
#include "private/pkgdb.h"
37
#include "private/pkg_jobs.h"
38
#include "siphash.h"
39
40
typedef vec_t(struct pkg_job_request *) conflict_chain_t;
41
42
TREE_DEFINE(pkg_jobs_conflict_item, entry);
43
44
static struct sipkey *
45
pkg_conflicts_sipkey_init(void)
46
{
47
static struct sipkey *kinit;
48
49
if (kinit == NULL) {
50
kinit = xmalloc(sizeof(*kinit));
51
arc4random_buf((unsigned char*)kinit, sizeof(*kinit));
52
}
53
54
return (kinit);
55
}
56
57
static int
58
pkg_conflicts_chain_cmp(const void *jra, const void *jrb)
59
{
60
const struct pkg_job_request *a = *(const struct pkg_job_request **)jra;
61
const struct pkg_job_request *b = *(const struct pkg_job_request **)jrb;
62
const char *vera, *verb;
63
64
if (a->skip || b->skip) {
65
return (a->skip - b->skip);
66
}
67
68
vera = a->item->pkg->version;
69
verb = b->item->pkg->version;
70
71
/* Inverse sort to get the maximum version as the first element */
72
return (pkg_version_cmp(vera, verb));
73
}
74
75
static int
76
pkg_conflicts_request_resolve_chain(struct pkg *req, conflict_chain_t *chain)
77
{
78
struct pkg_job_request *selected = NULL;
79
const char *slash_pos;
80
81
/*
82
* First of all prefer pure origins, where the last element of
83
* an origin is pkg name
84
*/
85
vec_foreach(*chain, i) {
86
slash_pos = strrchr(chain->d[i]->item->pkg->origin, '/');
87
if (slash_pos != NULL) {
88
if (STREQ(slash_pos + 1, req->name)) {
89
selected = chain->d[i];
90
break;
91
}
92
}
93
}
94
95
if (selected == NULL) {
96
/* XXX: add manual selection here */
97
/* Sort list by version of package */
98
qsort(chain->d, chain->len, sizeof(chain->d[0]), pkg_conflicts_chain_cmp);
99
selected = chain->d[0];
100
}
101
102
pkg_debug(2, "select %s in the chain of conflicts for %s",
103
selected->item->pkg->name, req->name);
104
/* Disable conflicts from a request */
105
vec_foreach(*chain, i) {
106
if (chain->d[i] != selected)
107
chain->d[i]->skip = true;
108
}
109
110
return (EPKG_OK);
111
}
112
113
int
114
pkg_conflicts_request_resolve(struct pkg_jobs *j)
115
{
116
struct pkg_job_request *req, *found;
117
struct pkg_conflict *c;
118
struct pkg_job_universe_item *unit;
119
pkghash_it it;
120
121
it = pkghash_iterator(j->request_add);
122
while (pkghash_next(&it)) {
123
req = it.value;
124
conflict_chain_t chain = vec_init();
125
if (req->skip)
126
continue;
127
128
LL_FOREACH(req->item->pkg->conflicts, c) {
129
unit = pkg_jobs_universe_find(j->universe, c->uid);
130
if (unit != NULL) {
131
found = pkghash_get_value(j->request_add, unit->pkg->uid);
132
if (found != NULL && !found->skip) {
133
vec_push(&chain, found);
134
}
135
}
136
}
137
if (chain.len > 0) {
138
/* Add package itself */
139
vec_push(&chain, req);
140
141
if (pkg_conflicts_request_resolve_chain(req->item->pkg, &chain) != EPKG_OK) {
142
vec_free(&chain);
143
return (EPKG_FATAL);
144
}
145
}
146
vec_free(&chain);
147
}
148
149
return (EPKG_OK);
150
}
151
152
static int
153
pkg_conflicts_item_cmp(struct pkg_jobs_conflict_item *a,
154
struct pkg_jobs_conflict_item *b)
155
{
156
return (b->hash - a->hash);
157
}
158
159
/*
160
* Checks whether we need to add a conflict between two packages
161
*/
162
static bool
163
pkg_conflicts_need_conflict(struct pkg_jobs *j, struct pkg *p1, struct pkg *p2)
164
{
165
struct pkg_file *fcur;
166
167
if (pkgdb_ensure_loaded(j->db, p1, PKG_LOAD_FILES|PKG_LOAD_DIRS) != EPKG_OK ||
168
pkgdb_ensure_loaded(j->db, p2, PKG_LOAD_FILES|PKG_LOAD_DIRS)
169
!= EPKG_OK) {
170
/*
171
* If some of packages are not loaded we could silently and safely
172
* ignore them
173
*/
174
pkg_debug(1, "cannot load files from %s and %s to check conflicts",
175
p1->name, p2->name);
176
177
return (false);
178
}
179
180
/*
181
* Check if we already have this conflict registered
182
*/
183
if (pkghash_get(p1->conflictshash, p2->uid) != NULL &&
184
pkghash_get(p2->conflictshash, p1->uid) != NULL)
185
return false;
186
187
/*
188
* We need to check all files and dirs and find the similar ones
189
*/
190
LL_FOREACH(p1->files, fcur) {
191
if (pkg_has_file(p2, fcur->path))
192
return (true);
193
if (pkg_has_dir(p2, fcur->path))
194
return (true);
195
}
196
/* XXX pkg dirs are terribly broken */
197
198
/* No common paths are found in p1 and p2 */
199
return (false);
200
}
201
202
static void
203
pkg_conflicts_register_one(struct pkg *p, struct pkg *op,
204
enum pkg_conflict_type type)
205
{
206
struct pkg_conflict *conflict;
207
208
conflict = pkghash_get_value(p->conflictshash, op->uid);
209
if (conflict != NULL)
210
return;
211
212
conflict = xcalloc(1, sizeof(*conflict));
213
conflict->type = type;
214
conflict->uid = xstrdup(op->uid);
215
conflict->digest = xstrdup(op->digest);
216
217
pkghash_safe_add(p->conflictshash, op->uid, conflict, NULL);
218
DL_APPEND(p->conflicts, conflict);
219
}
220
221
/*
222
* Record the existence of a file conflict between a pair of packages.
223
*/
224
static void
225
pkg_conflicts_register(struct pkg *p1, struct pkg *p2, const char *path,
226
enum pkg_conflict_type type)
227
{
228
pkg_conflicts_register_one(p1, p2, type);
229
pkg_conflicts_register_one(p2, p1, type);
230
231
pkg_debug(2, "registering conflict between %s(%s) and %s(%s) on path %s",
232
p1->uid, p1->type == PKG_INSTALLED ? "local" : "remote",
233
p2->uid, p2->type == PKG_INSTALLED ? "local" : "remote", path);
234
}
235
236
/*
237
* Register conflicts between packages in the universe chains
238
*/
239
static bool
240
pkg_conflicts_register_chain(struct pkg_jobs *j, struct pkg_job_universe_item *u1,
241
struct pkg_job_universe_item *u2, const char *path)
242
{
243
struct pkg_job_universe_item *cur1, *cur2;
244
enum pkg_conflict_type type;
245
bool ret = false;
246
247
cur1 = u1;
248
do {
249
cur2 = u2;
250
do {
251
struct pkg *p1 = cur1->pkg, *p2 = cur2->pkg;
252
253
if (p1->type == PKG_INSTALLED && p2->type == PKG_INSTALLED)
254
type = PKG_CONFLICT_LOCAL_LOCAL;
255
else if (p1->type == PKG_INSTALLED || p2->type == PKG_INSTALLED)
256
type = PKG_CONFLICT_REMOTE_LOCAL;
257
else
258
type = PKG_CONFLICT_REMOTE_REMOTE;
259
260
/* A pair of installed packages cannot conflict. */
261
if (type != PKG_CONFLICT_LOCAL_LOCAL &&
262
pkg_conflicts_need_conflict(j, p1, p2)) {
263
pkg_emit_conflicts(p1, p2, path);
264
pkg_conflicts_register(p1, p2, path, type);
265
j->conflicts_registered++;
266
ret = true;
267
}
268
cur2 = cur2->prev;
269
} while (cur2 != u2);
270
cur1 = cur1->prev;
271
} while (cur1 != u1);
272
273
return (ret);
274
}
275
276
/*
277
* Check whether the specified path is registered locally and returns
278
* the package that contains that path or NULL if no conflict was found
279
*/
280
static struct pkg *
281
pkg_conflicts_check_local_path(const char *path, const char *uid,
282
struct pkg_jobs *j)
283
{
284
const char sql_local_conflict[] = ""
285
"SELECT p.name as uniqueid FROM packages AS p "
286
"INNER JOIN files AS f "
287
"ON p.id = f.package_id "
288
"WHERE f.path = ?1;";
289
sqlite3_stmt *stmt;
290
int ret;
291
struct pkg *p = NULL;
292
293
ret = sqlite3_prepare_v2(j->db->sqlite, sql_local_conflict, -1,
294
&stmt, NULL);
295
if (ret != SQLITE_OK) {
296
ERROR_SQLITE(j->db->sqlite, sql_local_conflict);
297
return (NULL);
298
}
299
300
sqlite3_bind_text(stmt, 1,
301
path, -1, SQLITE_STATIC);
302
sqlite3_bind_text(stmt, 2,
303
uid, -1, SQLITE_STATIC);
304
pkgdb_debug(4, stmt);
305
306
if (sqlite3_step(stmt) == SQLITE_ROW) {
307
/*
308
* We have found the conflict with some other chain, so find that chain
309
* or update the universe
310
*/
311
const char *uid_local = sqlite3_column_text(stmt, 0);
312
313
p = pkg_jobs_universe_get_local(j->universe,
314
uid_local, 0);
315
assert(p != NULL);
316
317
assert(!STREQ(uid, p->uid));
318
319
if (pkghash_get(p->conflictshash, uid) == NULL) {
320
/* We need to register the conflict between two universe chains */
321
sqlite3_finalize(stmt);
322
return (p);
323
}
324
}
325
326
sqlite3_finalize(stmt);
327
return (NULL);
328
}
329
330
static struct pkg_job_universe_item *
331
pkg_conflicts_check_all_paths(struct pkg_jobs *j, const char *path,
332
struct pkg_job_universe_item *it, struct sipkey *k)
333
{
334
const char *uid1, *uid2;
335
struct pkg_jobs_conflict_item *cit, test;
336
struct pkg_conflict *c;
337
uint64_t hv;
338
339
hv = siphash24(path, strlen(path), k);
340
test.hash = hv;
341
cit = TREE_FIND(j->conflict_items, pkg_jobs_conflict_item, entry, &test);
342
343
if (cit == NULL) {
344
/* New entry */
345
cit = xcalloc(1, sizeof(*cit));
346
cit->hash = hv;
347
cit->item = it;
348
TREE_INSERT(j->conflict_items, pkg_jobs_conflict_item, entry, cit);
349
}
350
else {
351
/* Check the same package */
352
if (cit->item == it)
353
return (NULL);
354
355
uid1 = it->pkg->uid;
356
uid2 = cit->item->pkg->uid;
357
if (STREQ(uid1, uid2)) {
358
/* The same upgrade chain, just upgrade item for speed */
359
cit->item = it;
360
return (NULL);
361
}
362
363
/* Here we can have either collision or a real conflict */
364
c = pkghash_get_value(it->pkg->conflictshash, uid2);
365
if (c != NULL || !pkg_conflicts_register_chain(j, it, cit->item, path)) {
366
/*
367
* Collision found, change the key following the
368
* Cuckoo principle
369
*/
370
struct sipkey nk;
371
372
pkg_debug(2, "found a collision on path %s between %s and %s, key: %lu",
373
path, uid1, uid2, (unsigned long)k->k[0]);
374
375
nk = *k;
376
nk.k[0] ++;
377
return (pkg_conflicts_check_all_paths(j, path, it, &nk));
378
}
379
380
return (cit->item);
381
}
382
383
return (NULL);
384
}
385
386
static void
387
pkg_conflicts_check_chain_conflict(struct pkg_job_universe_item *it,
388
struct pkg_job_universe_item *local, struct pkg_jobs *j)
389
{
390
struct pkg_file *fcur;
391
struct pkg *p;
392
struct pkg_job_universe_item *cun;
393
struct sipkey *k;
394
395
LL_FOREACH(it->pkg->files, fcur) {
396
k = pkg_conflicts_sipkey_init();
397
/* Check in hash tree */
398
cun = pkg_conflicts_check_all_paths(j, fcur->path, it, k);
399
400
if (local != NULL) {
401
/* Filter only new files for remote packages */
402
if (pkg_has_file(local->pkg, fcur->path))
403
continue;
404
}
405
/* Check for local conflict in db */
406
p = pkg_conflicts_check_local_path(fcur->path, it->pkg->uid, j);
407
pkg_debug(4, "integrity: check path %s of package %s", fcur->path,
408
it->pkg->uid);
409
410
if (p != NULL) {
411
if (pkg_jobs_universe_process_item(j->universe, p, &cun))
412
continue;
413
assert(cun != NULL);
414
pkg_conflicts_register_chain(j, it, cun, fcur->path);
415
}
416
}
417
/* XXX: dirs are currently broken terribly */
418
#if 0
419
struct pkg_dir *dcur, *dtmp, *df;
420
HASH_ITER(hh, it->pkg->dirs, dcur, dtmp) {
421
memset(&k, 0, sizeof(k));
422
cun = pkg_conflicts_check_all_paths(j, dcur->path, it, &k);
423
424
if (local != NULL) {
425
HASH_FIND_STR(local->pkg->dirs, dcur->path, df);
426
if (df != NULL)
427
continue;
428
}
429
/* Check for local conflict in db */
430
p = pkg_conflicts_check_local_path(dcur->path, uid, j);
431
if (p != NULL) {
432
pkg_jobs_universe_process_item(j->universe, p, &cun);
433
assert(cun != NULL);
434
pkg_conflicts_register_chain(j, it, cun, dcur->path);
435
}
436
}
437
#endif
438
}
439
440
int
441
pkg_conflicts_append_chain(struct pkg_job_universe_item *it,
442
struct pkg_jobs *j)
443
{
444
struct pkg_job_universe_item *lp = NULL, *cur;
445
446
/* Ensure that we have a tree initialized */
447
if (j->conflict_items == NULL) {
448
j->conflict_items = xmalloc(sizeof(*j->conflict_items));
449
TREE_INIT(j->conflict_items, pkg_conflicts_item_cmp);
450
}
451
452
/* Find local package */
453
cur = it->prev;
454
while (cur != it) {
455
if (cur->pkg->type == PKG_INSTALLED) {
456
lp = cur;
457
if (pkgdb_ensure_loaded(j->db, cur->pkg, PKG_LOAD_FILES|PKG_LOAD_DIRS)
458
!= EPKG_OK)
459
return (EPKG_FATAL);
460
461
/* Local package is found */
462
break;
463
}
464
cur = cur->prev;
465
}
466
467
/*
468
* Now we go through the all packages in the chain and check them against
469
* conflicts with the locally installed files
470
*/
471
cur = it;
472
do {
473
if (cur != lp) {
474
if (pkgdb_ensure_loaded(j->db, cur->pkg, PKG_LOAD_FILES|PKG_LOAD_DIRS)
475
!= EPKG_OK) {
476
/*
477
* This means that a package was not downloaded, so we can safely
478
* ignore this conflict, since we are not going to install it
479
* anyway
480
*/
481
pkg_debug (3, "cannot load files from %s to check integrity",
482
cur->pkg->name);
483
}
484
else {
485
pkg_conflicts_check_chain_conflict(cur, lp, j);
486
}
487
}
488
489
cur = cur->prev;
490
} while (cur != it);
491
492
return (EPKG_OK);
493
}
494
495