/ src / event.c
event.c
  1  /*-
  2   * Copyright (c) 2011-2025 Baptiste Daroussin <bapt@FreeBSD.org>
  3   * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
  4   * Copyright (c) 2011 Will Andrews <will@FreeBSD.org>
  5   * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
  6   * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
  7   * Copyright (c) 2015 Matthew Seaman <matthew@FreeBSD.org>
  8   * All rights reserved.
  9   *
 10   * Redistribution and use in source and binary forms, with or without
 11   * modification, are permitted provided that the following conditions
 12   * are met:
 13   * 1. Redistributions of source code must retain the above copyright
 14   *    notice, this list of conditions and the following disclaimer
 15   *    in this position and unchanged.
 16   * 2. Redistributions in binary form must reproduce the above copyright
 17   *    notice, this list of conditions and the following disclaimer in the
 18   *    documentation and/or other materials provided with the distribution.
 19   *
 20   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 21   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 22   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 23   * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 24   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 25   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 26   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 27   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 28   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 29   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 30   */
 31  
 32  #ifdef HAVE_CONFIG_H
 33  #include "pkg_config.h"
 34  #endif
 35  
 36  #include <sys/resource.h>
 37  #include <sys/types.h>
 38  #ifdef HAVE_LIBJAIL
 39  #include <sys/sysctl.h>
 40  #endif
 41  #include <sys/wait.h>
 42  #include <sys/socket.h>
 43  
 44  #include <err.h>
 45  #include <string.h>
 46  #include <unistd.h>
 47  #include <errno.h>
 48  #include <time.h>
 49  #include <signal.h>
 50  #if __has_include(<libutil.h>)
 51  #include <libutil.h>
 52  #endif
 53  
 54  #include <bsd_compat.h>
 55  
 56  #include "pkg.h"
 57  #include "pkgcli.h"
 58  #include "xmalloc.h"
 59  
 60  #define STALL_TIME 5
 61  
 62  xstring *messages = NULL;
 63  xstring *conflicts = NULL;
 64  
 65  struct cleanup {
 66  	void *data;
 67  	void (*cb)(void *);
 68  };
 69  
 70  static char *progress_message = NULL;
 71  static xstring *msg_buf = NULL;
 72  static int last_progress_percent = -1;
 73  static bool progress_started = false;
 74  static bool progress_interrupted = false;
 75  static bool progress_debit = false;
 76  static int64_t last_tick = 0;
 77  static int64_t stalled;
 78  static int64_t bytes_per_second;
 79  static time_t last_update;
 80  static time_t begin = 0;
 81  static int add_deps_depth;
 82  static vec_t(struct cleanup *) cleanup_list = vec_init();
 83  static bool signal_handler_installed = false;
 84  static size_t nbactions = 0;
 85  static size_t nbdigits = 0;
 86  static size_t nbdone = 0;
 87  
 88  /* units for format_size */
 89  static const char *unit_SI[] = { " ", "k", "M", "G", "T", };
 90  
 91  static void draw_progressbar(int64_t current, int64_t total);
 92  
 93  static void
 94  cleanup_handler(int sig)
 95  {
 96  	static const char msg[] = "\nsignal received, cleaning up\n";
 97  	struct cleanup *ev;
 98  
 99  	if (cleanup_list.len == 0) {
100  		signal(sig, SIG_DFL);
101  		kill(getpid(), sig);
102  		_exit(128 + sig);
103  	}
104  	write(STDERR_FILENO, msg, sizeof(msg) - 1);
105  	vec_foreach(cleanup_list, i) {
106  		ev = cleanup_list.d[i];
107  		ev->cb(ev->data);
108  	}
109  	signal(sig, SIG_DFL);
110  	kill(getpid(), sig);
111  	_exit(128 + sig);
112  }
113  
114  static void
115  format_rate_SI(char *buf, int size, off_t bytes)
116  {
117  	int i;
118  
119  	bytes *= 100;
120  	for (i = 0; bytes >= 100*1000 && unit_SI[i][0] != 'T'; i++)
121  		bytes = (bytes + 500) / 1000;
122  	if (i == 0) {
123  		i++;
124  		bytes = (bytes + 500) / 1000;
125  	}
126  	snprintf(buf, size, "%3lld.%1lld %sB",
127  	    (long long) (bytes + 5) / 100,
128  	    (long long) (bytes + 5) / 10 % 10,
129  	    unit_SI[i]);
130  }
131  
132  void
133  job_status_end(xstring *msg)
134  {
135  	fflush(msg->fp);
136  	printf("%s\n", msg->buf);
137  	xstring_reset(msg);
138  }
139  
140  static int
141  count_digits(int n){
142  	if (n == 0)
143  		return (1);
144  	if (n < 0)
145  		n = -n;
146  	int c = 0;
147  	while (n > 0) {
148  		n /= 10; c++;
149  	}
150  	return (c);
151  }
152  
153  
154  void
155  job_status_begin(xstring *msg)
156  {
157  	int n;
158  
159  	xstring_reset(msg);
160  #ifdef HAVE_LIBJAIL
161  	static char hostname[MAXHOSTNAMELEN] = "";
162  	static int jailed = -1;
163  	size_t intlen;
164  
165  	if (jailed == -1) {
166  		intlen = sizeof(jailed);
167  		if (sysctlbyname("security.jail.jailed", &jailed, &intlen,
168  		    NULL, 0) == -1)
169  			jailed = 0;
170  	}
171  
172  	if (jailed == 1) {
173  		if (hostname[0] == '\0')
174  			gethostname(hostname, sizeof(hostname));
175  
176  		fprintf(msg->fp, "[%s] ", hostname);
177  	}
178  #endif
179  
180  	/* Only used for pkg-add right now. */
181  	if (add_deps_depth) {
182  		if (add_deps_depth > 1) {
183  			for (n = 0; n < (2 * add_deps_depth); ++n) {
184  				if (n % 4 == 0 && n < (2 * add_deps_depth))
185  					fprintf(msg->fp, "|");
186  				else
187  					fprintf(msg->fp, " ");
188  			}
189  		}
190  		fprintf(msg->fp, "`-- ");
191  	}
192  
193  	if ((nbtodl > 0 || nbactions > 0) && nbdone > 0) {
194  		if (nbdigits == 0)
195  			nbdigits = count_digits(nbtodl ? nbtodl : nbactions);
196  		fprintf(msg->fp, "[%*zu/%zu] ", nbdigits, nbdone, (nbtodl) ? nbtodl : nbactions);
197  	}
198  	if (nbtodl > 0 && nbtodl == nbdone) {
199  		nbtodl = 0;
200  		nbdone = 0;
201  	}
202  }
203  
204  void
205  progressbar_start(const char *pmsg)
206  {
207  	if (progress_message) {
208  		free(progress_message);
209  		progress_message = NULL;
210  	}
211  
212  	if (quiet)
213  		return;
214  	if (pmsg != NULL)
215  		progress_message = xstrdup(pmsg);
216  	else {
217  		fflush(msg_buf->fp);
218  		progress_message = xstrdup(msg_buf->buf);
219  	}
220  	last_progress_percent = -1;
221  	last_tick = 0;
222  	begin = last_update = time(NULL);
223  	bytes_per_second = 0;
224  	stalled = 0;
225  
226  	progress_started = true;
227  	progress_interrupted = false;
228  	if (!isatty(STDOUT_FILENO))
229  		printf("%s: ", progress_message);
230  	else
231  		printf("%s:   0%%", progress_message);
232  }
233  
234  void
235  progressbar_tick(int64_t current, int64_t total)
236  {
237  	int percent;
238  
239  	if (!quiet && progress_started) {
240  		if (isatty(STDOUT_FILENO))
241  			draw_progressbar(current, total);
242  		else {
243  			if (progress_interrupted) {
244  				printf("%s...", progress_message);
245  			} else if (!getenv("NO_TICK")){
246  				percent = (total != 0) ? (current * 100. / total) : 100;
247  				if (last_progress_percent / 10 < percent / 10) {
248  					last_progress_percent = percent;
249  					putchar('.');
250  					fflush(stdout);
251  				}
252  			}
253  			if (current >= total)
254  				progressbar_stop();
255  		}
256  	}
257  	progress_interrupted = false;
258  }
259  
260  void
261  progressbar_stop(void)
262  {
263  	if (progress_started) {
264  		if (!isatty(STDOUT_FILENO))
265  			printf(" done");
266  		putchar('\n');
267  	}
268  	last_progress_percent = -1;
269  	progress_started = false;
270  	progress_interrupted = false;
271  }
272  
273  static void
274  draw_progressbar(int64_t current, int64_t total)
275  {
276  	int percent;
277  	int64_t transferred;
278  	time_t elapsed = 0, now = 0;
279  	char buf[9];
280  	int64_t bytes_left;
281  	int cur_speed;
282  	int hours, minutes, seconds;
283  	float age_factor;
284  
285  	if (!progress_started) {
286  		progressbar_stop();
287  		return;
288  	}
289  
290  	if (progress_debit) {
291  		now = time(NULL);
292  		elapsed = (now >= last_update) ? now - last_update : 0;
293  	}
294  
295  	percent = (total != 0) ? (current * 100. / total) : 100;
296  
297  	/**
298  	 * Wait for interval for debit bars to keep calc per second.
299  	 * If not debit, show on every % change, or if ticking after
300  	 * an interruption (which removed our progressbar output).
301  	 */
302  	if (current >= total || (progress_debit && elapsed >= 1) ||
303  	    (!progress_debit &&
304  	    (percent != last_progress_percent || progress_interrupted))) {
305  		last_progress_percent = percent;
306  
307  		printf("\r%s: %3d%%", progress_message, percent);
308  		if (progress_debit) {
309  			transferred = current - last_tick;
310  			last_tick = current;
311  			bytes_left = total - current;
312  			if (bytes_left <= 0) {
313  				elapsed = now - begin;
314  				/* Always show at least 1 second at end. */
315  				if (elapsed == 0)
316  					elapsed = 1;
317  				/* Calculate true total speed when done */
318  				transferred = total;
319  				bytes_per_second = 0;
320  			}
321  
322  			if (elapsed != 0)
323  				cur_speed = (transferred / elapsed);
324  			else
325  				cur_speed = transferred;
326  
327  #define AGE_FACTOR_SLOW_START 3
328  			if (now - begin <= AGE_FACTOR_SLOW_START)
329  				age_factor = 0.4;
330  			else
331  				age_factor = 0.9;
332  
333  			if (bytes_per_second != 0) {
334  				bytes_per_second =
335  				    (bytes_per_second * age_factor) +
336  				    (cur_speed * (1.0 - age_factor));
337  			} else
338  				bytes_per_second = cur_speed;
339  
340  			humanize_number(buf, sizeof(buf),
341  			    current,"B", HN_AUTOSCALE, HN_IEC_PREFIXES);
342  			printf(" %*s", (int)sizeof(buf), buf);
343  
344  			if (bytes_left > 0)
345  				format_rate_SI(buf, sizeof(buf), transferred);
346  			else /* Show overall speed when done */
347  				format_rate_SI(buf, sizeof(buf),
348  				    bytes_per_second);
349  			printf(" %s/s ", buf);
350  
351  			if (!transferred)
352  				stalled += elapsed;
353  			else
354  				stalled = 0;
355  
356  			if (stalled >= STALL_TIME)
357  				printf(" - stalled -");
358  			else if (bytes_per_second == 0 && bytes_left > 0)
359  				printf("   --:-- ETA");
360  			else {
361  				if (bytes_left > 0)
362  					seconds = bytes_left / bytes_per_second;
363  				else
364  					seconds = elapsed;
365  
366  				hours = seconds / 3600;
367  				seconds -= hours * 3600;
368  				minutes = seconds / 60;
369  				seconds -= minutes * 60;
370  
371  				if (hours != 0)
372  					printf("%02d:%02d:%02d", hours,
373  					    minutes, seconds);
374  				else
375  					printf("   %02d:%02d", minutes, seconds);
376  
377  				if (bytes_left > 0)
378  					printf(" ETA");
379  				else
380  					printf("    ");
381  			}
382  			last_update = now;
383  		}
384  		fflush(stdout);
385  	}
386  	if (current >= total)
387  		progressbar_stop();
388  }
389  
390  static const char *
391  str_or_unknown(const char *str)
392  {
393  	if (str == NULL || str[0] == '\0')
394  		return "???";
395  	return str;
396  }
397  
398  int
399  event_callback(void *data, struct pkg_event *ev)
400  {
401  	struct pkg *pkg = NULL, *pkg_new, *pkg_old;
402  	struct pkg_file *file;
403  	struct pkg_dir *dir;
404  	struct cleanup *evtmp;
405  	int *debug = data;
406  	struct pkg_event_conflict *cur_conflict;
407  	const char *filename, *reponame = NULL;
408  
409  	if (msg_buf == NULL) {
410  		msg_buf = xstring_new();
411  	}
412  
413  	/*
414  	 * If a progressbar has been interrupted by another event, then
415  	 * we need to add a newline to prevent bad formatting.
416  	 */
417  	if (progress_started && ev->type != PKG_EVENT_PROGRESS_TICK &&
418  	    !progress_interrupted) {
419  		putchar('\n');
420  		progress_interrupted = true;
421  	}
422  
423  	switch(ev->type) {
424  	case PKG_EVENT_ERRNO:
425  		warnx("%s(%s): %s", ev->e_errno.func, ev->e_errno.arg,
426  		    strerror(ev->e_errno.no));
427  		break;
428  	case PKG_EVENT_ERROR:
429  		warnx("%s", ev->e_pkg_error.msg);
430  		break;
431  	case PKG_EVENT_NOTICE:
432  		if (!quiet)
433  			printf("%s\n", ev->e_pkg_notice.msg);
434  		break;
435  	case PKG_EVENT_DEVELOPER_MODE:
436  		warnx("DEVELOPER_MODE: %s", ev->e_pkg_error.msg);
437  		break;
438  	case PKG_EVENT_UPDATE_ADD:
439  		if (quiet || !isatty(STDOUT_FILENO))
440  			break;
441  		printf("\rPushing new entries %d/%d", ev->e_upd_add.done, ev->e_upd_add.total);
442  		if (ev->e_upd_add.total == ev->e_upd_add.done)
443  		        putchar('\n');
444  		break;
445  	case PKG_EVENT_UPDATE_REMOVE:
446  		if (quiet || !isatty(STDOUT_FILENO))
447  			break;
448  		printf("\rRemoving entries %d/%d", ev->e_upd_remove.done, ev->e_upd_remove.total);
449  		if (ev->e_upd_remove.total == ev->e_upd_remove.done)
450  			putchar('\n');
451  		break;
452  	case PKG_EVENT_FETCH_BEGIN:
453  		if (nbtodl > 0)
454  			nbdone++;
455  		if (quiet)
456  			break;
457  		filename = strrchr(ev->e_fetching.url, '/');
458  		if (filename != NULL) {
459  			filename++;
460  			char *tmp = strrchr(filename, '~');
461  			if (tmp != NULL) {
462  				*tmp = '\0';
463  			} else {
464  				tmp = strrchr(filename, '.');
465  				if (tmp != NULL && strcmp(tmp, ".pkg") == 0)
466  					*tmp = '\0';
467  			}
468  		} else {
469  			/*
470  			 * We failed at being smart, so display
471  			 * the entire url.
472  			 */
473  			filename = ev->e_fetching.url;
474  		}
475  		job_status_begin(msg_buf);
476  		progress_debit = true;
477  		fprintf(msg_buf->fp, "Fetching %s", filename);
478  		break;
479  	case PKG_EVENT_FETCH_FINISHED:
480  		progress_debit = false;
481  		break;
482  	case PKG_EVENT_INSTALL_BEGIN:
483  		if (quiet)
484  			break;
485  		job_status_begin(msg_buf);
486  
487  		pkg = ev->e_install_begin.pkg;
488  		pkg_fprintf(msg_buf->fp, "Installing %n-%v...\n", pkg,
489  		    pkg);
490  		fflush(msg_buf->fp);
491  		printf("%s", msg_buf->buf);
492  		break;
493  	case PKG_EVENT_INSTALL_FINISHED:
494  		break;
495  	case PKG_EVENT_EXTRACT_BEGIN:
496  		if (quiet)
497  			break;
498  		job_status_begin(msg_buf);
499  		pkg = ev->e_install_begin.pkg;
500  		pkg_fprintf(msg_buf->fp, "Extracting %n-%v", pkg, pkg);
501  		fflush(msg_buf->fp);
502  		break;
503  	case PKG_EVENT_EXTRACT_FINISHED:
504  		break;
505  	case PKG_EVENT_ADD_DEPS_BEGIN:
506  		++add_deps_depth;
507  		break;
508  	case PKG_EVENT_ADD_DEPS_FINISHED:
509  		--add_deps_depth;
510  		break;
511  	case PKG_EVENT_INTEGRITYCHECK_BEGIN:
512  		if (quiet)
513  			break;
514  		printf("Checking integrity...");
515  		break;
516  	case PKG_EVENT_INTEGRITYCHECK_FINISHED:
517  		if (quiet)
518  			break;
519  		printf(" done (%d conflicting)\n", ev->e_integrity_finished.conflicting);
520  		if (conflicts != NULL) {
521  			fflush(conflicts->fp);
522  			printf("%s", conflicts->buf);
523  			xstring_free(conflicts);
524  			conflicts = NULL;
525  		}
526  		break;
527  	case PKG_EVENT_INTEGRITYCHECK_CONFLICT:
528  		if (*debug == 0)
529  			break;
530  		printf("\nConflict found on path %s between %s and ",
531  		    ev->e_integrity_conflict.pkg_path,
532  		    ev->e_integrity_conflict.pkg_uid);
533  		cur_conflict = ev->e_integrity_conflict.conflicts;
534  		while (cur_conflict) {
535  			if (cur_conflict->next)
536  				printf("%s, ", cur_conflict->uid);
537  			else
538  				printf("%s", cur_conflict->uid);
539  
540  			cur_conflict = cur_conflict->next;
541  		}
542  		printf("\n");
543  		break;
544  	case PKG_EVENT_DEINSTALL_BEGIN:
545  		if (quiet)
546  			break;
547  
548  		job_status_begin(msg_buf);
549  
550  		pkg = ev->e_install_begin.pkg;
551  		pkg_fprintf(msg_buf->fp, "Deinstalling %n-%v...\n", pkg, pkg);
552  		fflush(msg_buf->fp);
553  		printf("%s", msg_buf->buf);
554  		break;
555  	case PKG_EVENT_DEINSTALL_FINISHED:
556  		break;
557  	case PKG_EVENT_DELETE_FILES_BEGIN:
558  		if (quiet)
559  			break;
560  		job_status_begin(msg_buf);
561  		pkg = ev->e_install_begin.pkg;
562  		pkg_fprintf(msg_buf->fp, "Deleting files for %n-%v",
563  		    pkg, pkg);
564  		break;
565  	case PKG_EVENT_DELETE_FILES_FINISHED:
566  		break;
567  	case PKG_EVENT_UPGRADE_BEGIN:
568  		if (quiet)
569  			break;
570  		pkg_new = ev->e_upgrade_begin.n;
571  		pkg_old = ev->e_upgrade_begin.o;
572  
573  		job_status_begin(msg_buf);
574  
575  		switch (pkg_version_change_between(pkg_new, pkg_old)) {
576  		case PKG_DOWNGRADE:
577  			pkg_fprintf(msg_buf->fp, "Downgrading %n from %v to %v...\n",
578  			    pkg_new, pkg_old, pkg_new);
579  			break;
580  		case PKG_REINSTALL:
581  			pkg_fprintf(msg_buf->fp, "Reinstalling %n-%v...\n",
582  		    pkg_old, pkg_old);
583  			break;
584  		case PKG_UPGRADE:
585  			pkg_fprintf(msg_buf->fp, "Upgrading %n from %v to %v...\n",
586  			    pkg_new, pkg_old, pkg_new);
587  			break;
588  		}
589  		fflush(msg_buf->fp);
590  		printf("%s", msg_buf->buf);
591  		break;
592  	case PKG_EVENT_UPGRADE_FINISHED:
593  		break;
594  	case PKG_EVENT_LOCKED:
595  		pkg = ev->e_locked.pkg;
596  		pkg_printf("\n%n-%v is locked and may not be modified\n", pkg, pkg);
597  		break;
598  	case PKG_EVENT_REQUIRED:
599  		pkg = ev->e_required.pkg;
600  		pkg_printf("\n%n-%v is required by: %r%{%rn-%rv%| %}", pkg, pkg, pkg);
601  		if (ev->e_required.force == 1)
602  			fprintf(stderr, ", deleting anyway\n");
603  		else
604  			fprintf(stderr, "\n");
605  		break;
606  	case PKG_EVENT_ALREADY_INSTALLED:
607  		if (quiet)
608  			break;
609  		pkg = ev->e_already_installed.pkg;
610  		pkg_printf("the most recent version of %n-%v is already installed\n",
611  				pkg, pkg);
612  		break;
613  	case PKG_EVENT_NOT_FOUND:
614  		printf("Package '%s' was not found in "
615  		    "the repositories\n", ev->e_not_found.pkg_name);
616  		break;
617  	case PKG_EVENT_MISSING_DEP:
618  		warnx("Missing dependency '%s'",
619  		    pkg_dep_name(ev->e_missing_dep.dep));
620  		break;
621  	case PKG_EVENT_NOREMOTEDB:
622  		fprintf(stderr, "Unable to open remote database \"%s\". "
623  		    "Try running '%s update' first.\n", ev->e_remotedb.repo,
624  		    getprogname());
625  		break;
626  	case PKG_EVENT_NOLOCALDB:
627  		fprintf(stderr, "Local package database nonexistent!\n");
628  		break;
629  	case PKG_EVENT_NEWPKGVERSION:
630  		newpkgversion = true;
631  		printf("New version of pkg detected; it needs to be "
632  		    "installed first.\n");
633  		break;
634  	case PKG_EVENT_FILE_MISMATCH:
635  		pkg = ev->e_file_mismatch.pkg;
636  		pkg_fprintf(stderr, "%n-%v: checksum mismatch for %Fn\n", pkg,
637  		    pkg, ev->e_file_mismatch.file);
638  		break;
639  	case PKG_EVENT_FILE_MISSING:
640  		pkg = ev->e_file_missing.pkg;
641  		pkg_fprintf(stderr, "%n-%v: missing file %Fn\n", pkg, pkg,
642  		    ev->e_file_missing.file);
643  		break;
644  	case PKG_EVENT_DIR_META_MISMATCH:
645  		pkg = ev->e_file_meta_mismatch.pkg;
646  		dir = ev->e_dir_meta_mismatch.dir;
647  		pkg_fprintf(stderr, "%n-%v: %Dn [%S] %S -> %S\n", pkg, pkg, dir,
648  			    pkg_meta_attribute_tostring(ev->e_dir_meta_mismatch.attrib),
649  			    str_or_unknown(ev->e_dir_meta_mismatch.db_val),
650  			    str_or_unknown(ev->e_dir_meta_mismatch.fs_val));
651  		break;
652  	case PKG_EVENT_FILE_META_MISMATCH:
653  		pkg = ev->e_file_meta_mismatch.pkg;
654  		file = ev->e_file_meta_mismatch.file;
655  		pkg_fprintf(stderr, "%n-%v: %Fn [%S] %S -> %S\n", pkg, pkg, file,
656  			    pkg_meta_attribute_tostring(ev->e_file_meta_mismatch.attrib),
657  			    str_or_unknown(ev->e_file_meta_mismatch.db_val),
658  			    str_or_unknown(ev->e_file_meta_mismatch.fs_val));
659  		break;
660  	case PKG_EVENT_PLUGIN_ERRNO:
661  		warnx("%s: %s(%s): %s",
662  		    pkg_plugin_get(ev->e_plugin_errno.plugin, PKG_PLUGIN_NAME),
663  		    ev->e_plugin_errno.func, ev->e_plugin_errno.arg,
664  		    strerror(ev->e_plugin_errno.no));
665  		break;
666  	case PKG_EVENT_PLUGIN_ERROR:
667  		warnx("%s: %s",
668  		    pkg_plugin_get(ev->e_plugin_error.plugin, PKG_PLUGIN_NAME),
669  		    ev->e_plugin_error.msg);
670  		break;
671  	case PKG_EVENT_PLUGIN_INFO:
672  		if (quiet)
673  			break;
674  		printf("%s: %s\n",
675  		    pkg_plugin_get(ev->e_plugin_info.plugin, PKG_PLUGIN_NAME),
676  		    ev->e_plugin_info.msg);
677  		break;
678  	case PKG_EVENT_INCREMENTAL_UPDATE:
679  		if (!quiet)
680  			printf("%s repository update completed. %d packages processed.\n",
681  			    ev->e_incremental_update.reponame,
682  			    ev->e_incremental_update.processed);
683  		break;
684  	case PKG_EVENT_DEBUG:
685  		fprintf(stderr, "DBG(%d)[%d]> %s\n", ev->e_debug.level,
686  			(int)getpid(), ev->e_debug.msg);
687  		break;
688  	case PKG_EVENT_QUERY_YESNO:
689  		return ( ev->e_query_yesno.deft ?
690  			query_yesno(true, ev->e_query_yesno.msg, "[Y/n]") :
691  			query_yesno(false, ev->e_query_yesno.msg, "[y/N]") );
692  	case PKG_EVENT_QUERY_SELECT:
693  		return query_select(ev->e_query_select.msg, ev->e_query_select.items,
694  			ev->e_query_select.ncnt, ev->e_query_select.deft);
695  	case PKG_EVENT_SANDBOX_CALL:
696  		return ( pkg_handle_sandboxed_call(ev->e_sandbox_call.call,
697  				ev->e_sandbox_call.fd,
698  				ev->e_sandbox_call.userdata) );
699  	case PKG_EVENT_SANDBOX_GET_STRING:
700  		return ( pkg_handle_sandboxed_get_string(ev->e_sandbox_call_str.call,
701  				ev->e_sandbox_call_str.result,
702  				ev->e_sandbox_call_str.len,
703  				ev->e_sandbox_call_str.userdata) );
704  	case PKG_EVENT_PROGRESS_START:
705  		progressbar_start(ev->e_progress_start.msg);
706  		break;
707  	case PKG_EVENT_PROGRESS_TICK:
708  		progressbar_tick(ev->e_progress_tick.current,
709  		    ev->e_progress_tick.total);
710  		break;
711  	case PKG_EVENT_BACKUP:
712  		fprintf(msg_buf->fp, "Backing up");
713  		break;
714  	case PKG_EVENT_RESTORE:
715  		fprintf(msg_buf->fp, "Restoring");
716  		break;
717  	case PKG_EVENT_NEW_ACTION:
718  		nbactions = ev->e_action.total;
719  		nbdigits = 0;
720  		nbdone = ev->e_action.current;
721  		break;
722  	case PKG_EVENT_MESSAGE:
723  		if (messages == NULL)
724  			messages = xstring_new();
725  		fprintf(messages->fp, "%s", ev->e_pkg_message.msg);
726  		break;
727  	case PKG_EVENT_CLEANUP_CALLBACK_REGISTER:
728  		if (!signal_handler_installed) {
729  			signal(SIGINT, cleanup_handler);
730  			signal_handler_installed = true;
731  		}
732  		evtmp = xmalloc(sizeof(struct cleanup));
733  		evtmp->cb = ev->e_cleanup_callback.cleanup_cb;
734  		evtmp->data = ev->e_cleanup_callback.data;
735  		vec_push(&cleanup_list, evtmp);
736  		break;
737  	case PKG_EVENT_CLEANUP_CALLBACK_UNREGISTER:
738  		if (!signal_handler_installed)
739  			break;
740  		vec_foreach(cleanup_list, i) {
741  			evtmp = cleanup_list.d[i];
742  			if (evtmp->cb == ev->e_cleanup_callback.cleanup_cb &&
743  			    evtmp->data == ev->e_cleanup_callback.data) {
744  				vec_remove_and_free(&cleanup_list, i, free);
745  				break;
746  			}
747  		}
748  		break;
749  	case PKG_EVENT_CONFLICTS:
750  		if (conflicts == NULL) {
751  			conflicts = xstring_new();
752  		}
753  		pkg_fprintf(conflicts->fp, "  - %n-%v",
754  		    ev->e_conflicts.p1, ev->e_conflicts.p1);
755  		if (pkg_repos_total_count() > 1) {
756  			pkg_get(ev->e_conflicts.p1, PKG_ATTR_REPONAME, &reponame);
757  			fprintf(conflicts->fp, " [%s]",
758  			    reponame == NULL ? "installed" : reponame);
759  		}
760  		pkg_fprintf(conflicts->fp, " conflicts with %n-%v",
761  		    ev->e_conflicts.p2, ev->e_conflicts.p2);
762  		if (pkg_repos_total_count() > 1) {
763  			pkg_get(ev->e_conflicts.p2, PKG_ATTR_REPONAME, &reponame);
764  			fprintf(conflicts->fp, " [%s]",
765  			    reponame == NULL ? "installed" : reponame);
766  		}
767  		fprintf(conflicts->fp, " on %s\n",
768  		    ev->e_conflicts.path);
769  		break;
770  	case PKG_EVENT_TRIGGER:
771  		if (!quiet) {
772  			if (ev->e_trigger.cleanup)
773  				printf("==> Cleaning up trigger: %s\n", ev->e_trigger.name);
774  			else
775  				printf("==> Running trigger: %s\n", ev->e_trigger.name);
776  		}
777  		break;
778  	default:
779  		break;
780  	}
781  
782  	return 0;
783  }