/ libpkg / pkg_cudf.c
pkg_cudf.c
  1  /*-
  2   * Copyright (c) 2013 Vsevolod Stakhov <vsevolod@FreeBSD.org>
  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 <stdio.h>
 28  #include <ctype.h>
 29  
 30  #include "pkg.h"
 31  #include "private/event.h"
 32  #include "private/pkg.h"
 33  #include "private/pkgdb.h"
 34  #include "private/pkg_jobs.h"
 35  
 36  /*
 37   * CUDF does not support packages with '_' in theirs names, therefore
 38   * use this ugly function to replace '_' to '@'
 39   */
 40  static inline int
 41  cudf_print_package_name(FILE *f, const char *name)
 42  {
 43  	const char *p, *c;
 44  	int r = 0;
 45  
 46  	p = c = name;
 47  	while (*p) {
 48  		if (*p == '_') {
 49  			r += fprintf(f, "%.*s", (int)(p - c), c);
 50  			fputc('@', f);
 51  			r ++;
 52  			c = p + 1;
 53  		}
 54  		p ++;
 55  	}
 56  	if (p > c) {
 57  		r += fprintf(f, "%.*s", (int)(p - c), c);
 58  	}
 59  
 60  	return r;
 61  }
 62  
 63  static inline int
 64  cudf_print_element(FILE *f, const char *line, bool has_next, size_t *column)
 65  {
 66  	int ret = 0;
 67  	if (*column > 80) {
 68  		*column = 0;
 69  		ret += fprintf(f, "\n ");
 70  	}
 71  
 72  	ret += cudf_print_package_name(f, line);
 73  
 74  	if (has_next)
 75  		ret += fprintf(f, ", ");
 76  	else
 77  		ret += fprintf(f, "\n");
 78  
 79  	if (ret > 0)
 80  		*column += ret;
 81  
 82  	return (ret);
 83  }
 84  
 85  static inline int
 86  cudf_print_conflict(FILE *f, const char *uid, int ver, bool has_next, size_t *column)
 87  {
 88  	int ret = 0;
 89  	if (*column > 80) {
 90  		*column = 0;
 91  		ret += fprintf(f, "\n ");
 92  	}
 93  
 94  	ret += cudf_print_package_name(f, uid);
 95  	ret += fprintf(f, "=%d", ver);
 96  
 97  	if (has_next)
 98  		ret += fprintf(f, ", ");
 99  	else
100  		ret += fprintf(f, "\n");
101  
102  	if (ret > 0)
103  		*column += ret;
104  
105  	return (ret);
106  }
107  
108  
109  static int
110  cudf_emit_pkg(struct pkg *pkg, int version, FILE *f,
111  		struct pkg_job_universe_item *conflicts_chain)
112  {
113  	struct pkg_dep *dep;
114  	struct pkg_conflict *conflict;
115  	struct pkg_job_universe_item *u;
116  	size_t column = 0;
117  	int ver;
118  
119  	if (fprintf(f, "package: ") < 0)
120  		return (EPKG_FATAL);
121  
122  	if (cudf_print_package_name(f, pkg->uid) < 0)
123  		return (EPKG_FATAL);
124  
125  	if (fprintf(f, "\nversion: %d\n", version) < 0)
126  		return (EPKG_FATAL);
127  
128  	if (pkghash_count(pkg->depshash) > 0) {
129  		if (fprintf(f, "depends: ") < 0)
130  			return (EPKG_FATAL);
131  		LL_FOREACH(pkg->depends, dep) {
132  			if (cudf_print_element(f, dep->name,
133  			    column + 1 == pkghash_count(pkg->depshash), &column) < 0) {
134  				return (EPKG_FATAL);
135  			}
136  		}
137  	}
138  
139  	column = 0;
140  	if (vec_len(&pkg->provides) > 0) {
141  		if (fprintf(f, "provides: ") < 0)
142  			return (EPKG_FATAL);
143  		vec_foreach(pkg->provides, i) {
144  			if (cudf_print_element(f, pkg->provides.d[i],
145  			    column + 1 == vec_len(&pkg->provides), &column) < 0) {
146  				return (EPKG_FATAL);
147  			}
148  		}
149  	}
150  
151  	column = 0;
152  	if (pkghash_count(pkg->conflictshash) > 0 ||
153  			(conflicts_chain->next != NULL &&
154  			!conflicts_chain->next->cudf_emit_skip)) {
155  		if (fprintf(f, "conflicts: ") < 0)
156  			return (EPKG_FATAL);
157  		LL_FOREACH(pkg->conflicts, conflict) {
158  			if (cudf_print_element(f, conflict->uid,
159  					(conflict->next != NULL), &column) < 0) {
160  				return (EPKG_FATAL);
161  			}
162  		}
163  		ver = 1;
164  		LL_FOREACH(conflicts_chain, u) {
165  			if (u->pkg != pkg && !u->cudf_emit_skip) {
166  				if (cudf_print_conflict(f, pkg->uid, ver,
167  				   (u->next != NULL && u->next->pkg != pkg), &column) < 0) {
168  					return (EPKG_FATAL);
169  				}
170  			}
171  			ver ++;
172  		}
173  	}
174  
175  	if (fprintf(f, "installed: %s\n\n", pkg->type == PKG_INSTALLED ?
176  			"true" : "false") < 0)
177  		return (EPKG_FATAL);
178  
179  	return (EPKG_OK);
180  }
181  
182  static int
183  cudf_emit_request_packages(const char *op, struct pkg_jobs *j, FILE *f)
184  {
185  	struct pkg_job_request *req;
186  	size_t column = 0, cnt = 0, max;
187  	bool printed = false;
188  	pkghash_it it;
189  
190  	max = pkghash_count(j->request_add);
191  	if (fprintf(f, "%s: ", op) < 0)
192  		return (EPKG_FATAL);
193  	it = pkghash_iterator(j->request_add);
194  	while (pkghash_next(&it)) {
195  		req = it.value;
196  		cnt++;
197  		if (req->skip)
198  			continue;
199  		if (cudf_print_element(f, req->item->pkg->uid,
200  		    (max > cnt), &column) < 0) {
201  			return (EPKG_FATAL);
202  		}
203  		printed = true;
204  	}
205  
206  	if (!printed)
207  		if (fputc('\n', f) < 0)
208  			return (EPKG_FATAL);
209  
210  	column = 0;
211  	printed = false;
212  	if (fprintf(f, "remove: ") < 0)
213  		return (EPKG_FATAL);
214  	max = pkghash_count(j->request_delete);
215  	it = pkghash_iterator(j->request_delete);
216  	while (pkghash_next(&it)) {
217  		req = it.value;
218  		cnt++;
219  		if (req->skip)
220  			continue;
221  		if (cudf_print_element(f, req->item->pkg->uid,
222  		    (max > cnt), &column) < 0) {
223  			return (EPKG_FATAL);
224  		}
225  		printed = true;
226  	}
227  
228  	if (!printed)
229  		if (fputc('\n', f) < 0)
230  			return (EPKG_FATAL);
231  
232  	return (EPKG_OK);
233  }
234  
235  static int
236  pkg_cudf_version_cmp(struct pkg_job_universe_item *a, struct pkg_job_universe_item *b)
237  {
238  	int ret;
239  
240  	ret = pkg_version_cmp(a->pkg->version, b->pkg->version);
241  	if (ret == 0) {
242  		/* Ignore remote packages whose versions are equal to ours */
243  		if (a->pkg->type != PKG_INSTALLED)
244  			a->cudf_emit_skip = true;
245  		else if (b->pkg->type != PKG_INSTALLED)
246  			b->cudf_emit_skip = true;
247  	}
248  
249  
250  	return (ret);
251  }
252  
253  int
254  pkg_jobs_cudf_emit_file(struct pkg_jobs *j, pkg_jobs_t t, FILE *f)
255  {
256  	struct pkg *pkg;
257  	struct pkg_job_universe_item *it, *icur;
258  	int version;
259  	pkghash_it hit;
260  
261  	if (fprintf(f, "preamble: \n\n") < 0)
262  		return (EPKG_FATAL);
263  
264  	hit = pkghash_iterator(j->universe->items);
265  	while (pkghash_next(&hit)) {
266  		it = (struct pkg_job_universe_item *)hit.value;
267  		/* XXX
268  		 * Here are dragons:
269  		 * after sorting it we actually modify the head of the list, but there is
270  		 * no simple way to update a pointer in uthash, therefore universe hash
271  		 * contains not a head of list but a random elt of the conflicts chain:
272  		 * before:
273  		 * head -> elt1 -> elt2 -> elt3
274  		 * after:
275  		 * elt1 -> elt3 -> head -> elt2
276  		 *
277  		 * But hash would still point to head whilst the real head is elt1.
278  		 * So after sorting we need to rotate conflicts chain back to find the new
279  		 * head.
280  		 */
281  		DL_SORT(it, pkg_cudf_version_cmp);
282  
283  		version = 1;
284  		LL_FOREACH(it, icur) {
285  			if (!icur->cudf_emit_skip) {
286  				pkg = icur->pkg;
287  
288  				if (cudf_emit_pkg(pkg, version ++, f, it) != EPKG_OK)
289  					return (EPKG_FATAL);
290  			}
291  		}
292  	}
293  
294  	if (fprintf(f, "request: \n") < 0)
295  			return (EPKG_FATAL);
296  
297  	switch (t) {
298  	case PKG_JOBS_FETCH:
299  	case PKG_JOBS_INSTALL:
300  	case PKG_JOBS_DEINSTALL:
301  	case PKG_JOBS_AUTOREMOVE:
302  		if (cudf_emit_request_packages("install", j, f) != EPKG_OK)
303  			return (EPKG_FATAL);
304  		break;
305  	case PKG_JOBS_UPGRADE:
306  		if (cudf_emit_request_packages("upgrade", j, f) != EPKG_OK)
307  			return (EPKG_FATAL);
308  		break;
309  	}
310  	return (EPKG_OK);
311  }
312  
313  /*
314   * Perform backward conversion of an uid replacing '@' to '_'
315   */
316  static char *
317  cudf_strdup(const char *in)
318  {
319  	size_t len = strlen(in);
320  	char *out, *d;
321  	const char *s;
322  
323  	out = xmalloc(len + 1);
324  
325  	s = in;
326  	d = out;
327  	while (isspace(*s))
328  		s++;
329  	while (*s) {
330  		if (!isspace(*s))
331  			*d++ = (*s == '@') ? '_' : *s;
332  		s++;
333  	}
334  
335  	*d = '\0';
336  	return (out);
337  }
338  
339  static void
340  pkg_jobs_cudf_insert_res_job (pkg_solved_list *target,
341  		struct pkg_job_universe_item *it_new,
342  		struct pkg_job_universe_item *it_old,
343  		int type)
344  {
345  	struct pkg_solved *res;
346  
347  	res = xcalloc(1, sizeof(struct pkg_solved));
348  
349  	res->items[0] = it_new;
350  	res->type = type;
351  	if (it_old != NULL)
352  		res->items[1] = it_old;
353  
354  	vec_push(target, res);
355  }
356  
357  struct pkg_cudf_entry {
358  	char *uid;
359  	bool was_installed;
360  	bool installed;
361  	char *version;
362  };
363  
364  static int
365  pkg_jobs_cudf_add_package(struct pkg_jobs *j, struct pkg_cudf_entry *entry)
366  {
367  	struct pkg_job_universe_item *it, *cur, *selected = NULL, *old = NULL, *head;
368  	int ver, n;
369  
370  	it = pkg_jobs_universe_find(j->universe, entry->uid);
371  	if (it == NULL) {
372  		pkg_emit_error("package %s is found in CUDF output but not in the universe",
373  				entry->uid);
374  		return (EPKG_FATAL);
375  	}
376  
377  	/*
378  	 * Now we need to select an appropriate version. We assume that
379  	 * the order of packages in list is the same as was passed to the
380  	 * cudf solver.
381  	 */
382  	ver = strtoul(entry->version, NULL, 10);
383  
384  	/* Find the old head, see the comment in `pkg_jobs_cudf_emit_file` */
385  	cur = it;
386  	do {
387  		head = cur;
388  		cur = cur->prev;
389  	} while (cur->next != NULL);
390  
391  	n = 1;
392  	LL_FOREACH(head, cur) {
393  		if (n == ver) {
394  			selected = cur;
395  			break;
396  		}
397  		n ++;
398  	}
399  
400  	if (selected == NULL) {
401  		pkg_emit_error("package %s-%d is found in CUDF output but the "
402  				"universe has no such version (only %d versions found)",
403  				entry->uid, ver, n);
404  		return (EPKG_FATAL);
405  	}
406  
407  	if (n == 1) {
408  		if (entry->installed && selected->pkg->type != PKG_INSTALLED) {
409  			pkg_debug(3, "pkg_cudf: schedule installation of %s(%d)",
410  					entry->uid, ver);
411  			pkg_jobs_cudf_insert_res_job (&j->jobs, selected, NULL, PKG_SOLVED_INSTALL);
412  		}
413  		else if (!entry->installed && selected->pkg->type == PKG_INSTALLED) {
414  			pkg_debug(3, "pkg_cudf: schedule removing of %s(%d)",
415  					entry->uid, ver);
416  			pkg_jobs_cudf_insert_res_job (&j->jobs, selected, NULL, PKG_SOLVED_DELETE);
417  		}
418  	}
419  	else {
420  		/* Define upgrade */
421  		LL_FOREACH(head, cur) {
422  			if (cur != selected) {
423  				old = cur;
424  				break;
425  			}
426  		}
427  		pkg_debug(3, "pkg_cudf: schedule upgrade of %s(to %d)",
428  				entry->uid, ver);
429  		assert(old != NULL);
430  		/* XXX: this is a hack due to iterators stupidity */
431  		selected->pkg->old_version = old->pkg->version;
432  		pkg_jobs_cudf_insert_res_job (&j->jobs, selected, old, PKG_SOLVED_UPGRADE);
433  	}
434  
435  	return (EPKG_OK);
436  }
437  
438  int
439  pkg_jobs_cudf_parse_output(struct pkg_jobs *j, FILE *f)
440  {
441  	char *line = NULL, *begin, *param, *value;
442  	size_t linecap = 0;
443  	struct pkg_cudf_entry cur_pkg;
444  
445  	memset(&cur_pkg, 0, sizeof(cur_pkg));
446  
447  	while (getline(&line, &linecap, f) > 0) {
448  		/* Split line, cut spaces */
449  		begin = line;
450  		param = strsep(&begin, ": \t");
451  		value = begin;
452  		while(begin != NULL)
453  			value = strsep(&begin, " \t");
454  
455  		if (STREQ(param, "package")) {
456  			if (cur_pkg.uid != NULL) {
457  				if (pkg_jobs_cudf_add_package(j, &cur_pkg) != EPKG_OK)  {
458  					free(line);
459  					return (EPKG_FATAL);
460  				}
461  			}
462  			cur_pkg.uid = cudf_strdup(value);
463  			cur_pkg.was_installed = false;
464  			cur_pkg.installed = false;
465  			cur_pkg.version = NULL;
466  		}
467  		else if (STREQ(param, "version")) {
468  			if (cur_pkg.uid == NULL) {
469  				pkg_emit_error("version line has no corresponding uid in CUDF output");
470  				free(line);
471  				return (EPKG_FATAL);
472  			}
473  			cur_pkg.version = cudf_strdup(value);
474  		}
475  		else if (STREQ(param, "installed")) {
476  			if (cur_pkg.uid == NULL) {
477  				pkg_emit_error("installed line has no corresponding uid in CUDF output");
478  				free(line);
479  				return (EPKG_FATAL);
480  			}
481  			if (strncmp(value, "true", 4) == 0)
482  				cur_pkg.installed = true;
483  		}
484  		else if (STREQ(param, "was-installed")) {
485  			if (cur_pkg.uid == NULL) {
486  				pkg_emit_error("was-installed line has no corresponding uid in CUDF output");
487  				free(line);
488  				return (EPKG_FATAL);
489  			}
490  			if (strncmp(value, "true", 4) == 0)
491  				cur_pkg.was_installed = true;
492  		}
493  	}
494  
495  	if (cur_pkg.uid != NULL) {
496  		if (pkg_jobs_cudf_add_package(j, &cur_pkg) != EPKG_OK)  {
497  			free(line);
498  			return (EPKG_FATAL);
499  		}
500  	}
501  
502  	free(line);
503  
504  	return (EPKG_OK);
505  }