1 /*-
2  * Copyright (c) 2011-2014 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 #ifdef HAVE_CAPSICUM
45 #include <sys/capsicum.h>
46 #endif
47 
48 #include <err.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <errno.h>
52 #include <time.h>
53 #include <signal.h>
54 #ifdef HAVE_LIBUTIL_H
55 #include <libutil.h>
56 #endif
57 #include <kvec.h>
58 
59 #include <bsd_compat.h>
60 
61 #include "pkg.h"
62 #include "pkgcli.h"
63 
64 #define STALL_TIME 5
65 
66 xstring *messages = NULL;
67 xstring *conflicts = NULL;
68 
69 struct cleanup {
70 	void *data;
71 	void (*cb)(void *);
72 };
73 
74 static char *progress_message = NULL;
75 static xstring *msg_buf = NULL;
76 static int last_progress_percent = -1;
77 static bool progress_started = false;
78 static bool progress_interrupted = false;
79 static bool progress_debit = false;
80 static int64_t last_tick = 0;
81 static int64_t stalled;
82 static int64_t bytes_per_second;
83 static time_t last_update;
84 static time_t begin = 0;
85 static int add_deps_depth;
86 static kvec_t(struct cleanup *) cleanup_list;
87 static bool signal_handler_installed = false;
88 
89 /* units for format_size */
90 static const char *unit_SI[] = { " ", "k", "M", "G", "T", };
91 
92 static void draw_progressbar(int64_t current, int64_t total);
93 
94 static void
cleanup_handler(int dummy __unused)95 cleanup_handler(int dummy __unused)
96 {
97 	struct cleanup *ev;
98 	size_t i;
99 
100 	if (kv_size(cleanup_list) == 0)
101 		return;
102 	warnx("\nsignal received, cleaning up");
103 	for (i = 0; i < kv_size(cleanup_list); i++) {
104 		ev = kv_A(cleanup_list, i);
105 		ev->cb(ev->data);
106 	}
107 	exit(1);
108 }
109 
110 static void
format_rate_SI(char * buf,int size,off_t bytes)111 format_rate_SI(char *buf, int size, off_t bytes)
112 {
113 	int i;
114 
115 	bytes *= 100;
116 	for (i = 0; bytes >= 100*1000 && unit_SI[i][0] != 'T'; i++)
117 		bytes = (bytes + 500) / 1000;
118 	if (i == 0) {
119 		i++;
120 		bytes = (bytes + 500) / 1000;
121 	}
122 	snprintf(buf, size, "%3lld.%1lld%sB",
123 	    (long long) (bytes + 5) / 100,
124 	    (long long) (bytes + 5) / 10 % 10,
125 	    unit_SI[i]);
126 }
127 
128 void
job_status_end(xstring * msg)129 job_status_end(xstring *msg)
130 {
131 	fflush(msg->fp);
132 	printf("%s\n", msg->buf);
133 	xstring_reset(msg);
134 }
135 
136 void
job_status_begin(xstring * msg)137 job_status_begin(xstring *msg)
138 {
139 	int n;
140 
141 	xstring_reset(msg);
142 #ifdef HAVE_LIBJAIL
143 	static char hostname[MAXHOSTNAMELEN] = "";
144 	static int jailed = -1;
145 	size_t intlen;
146 
147 	if (jailed == -1) {
148 		intlen = sizeof(jailed);
149 		if (sysctlbyname("security.jail.jailed", &jailed, &intlen,
150 		    NULL, 0) == -1)
151 			jailed = 0;
152 	}
153 
154 	if (jailed == 1) {
155 		if (hostname[0] == '\0')
156 			gethostname(hostname, sizeof(hostname));
157 
158 		fprintf(msg->fp, "[%s] ", hostname);
159 	}
160 #endif
161 
162 	/* Only used for pkg-add right now. */
163 	if (add_deps_depth) {
164 		if (add_deps_depth > 1) {
165 			for (n = 0; n < (2 * add_deps_depth); ++n) {
166 				if (n % 4 == 0 && n < (2 * add_deps_depth))
167 					fprintf(msg->fp, "|");
168 				else
169 					fprintf(msg->fp, " ");
170 			}
171 		}
172 		fprintf(msg->fp, "`-- ");
173 	}
174 
175 	if ((nbtodl > 0 || nbactions > 0) && nbdone > 0)
176 		fprintf(msg->fp, "[%d/%d] ", nbdone, (nbtodl) ? nbtodl : nbactions);
177 	if (nbtodl > 0 && nbtodl == nbdone) {
178 		nbtodl = 0;
179 		nbdone = 0;
180 	}
181 }
182 
183 static int
event_sandboxed_call(pkg_sandbox_cb func,int fd,void * ud)184 event_sandboxed_call(pkg_sandbox_cb func, int fd, void *ud)
185 {
186 	pid_t pid;
187 	int status, ret;
188 	struct rlimit rl_zero;
189 
190 	ret = -1;
191 	pid = fork();
192 
193 	switch(pid) {
194 	case -1:
195 		warn("fork failed");
196 		return (EPKG_FATAL);
197 		break;
198 	case 0:
199 		break;
200 	default:
201 		/* Parent process */
202 		while (waitpid(pid, &status, 0) == -1) {
203 			if (errno != EINTR) {
204 				warn("Sandboxed process pid=%d", (int)pid);
205 				ret = -1;
206 				break;
207 			}
208 		}
209 
210 		if (WIFEXITED(status)) {
211 			ret = WEXITSTATUS(status);
212 		}
213 		if (WIFSIGNALED(status)) {
214 			/* Process got some terminating signal, hence stop the loop */
215 			fprintf(stderr, "Sandboxed process pid=%d terminated abnormally by signal: %d\n",
216 					(int)pid, WTERMSIG(status));
217 			ret = -1;
218 		}
219 		return (ret);
220 	}
221 
222 	rl_zero.rlim_cur = rl_zero.rlim_max = 0;
223 	if (setrlimit(RLIMIT_NPROC, &rl_zero) == -1)
224 		err(EXIT_FAILURE, "Unable to setrlimit(RLIMIT_NPROC)");
225 
226 	/* Here comes child process */
227 #ifdef HAVE_CAPSICUM
228 	if (cap_enter() < 0 && errno != ENOSYS) {
229 		warn("cap_enter() failed");
230 		_exit(EXIT_FAILURE);
231 	}
232 #endif
233 
234 	ret = func(fd, ud);
235 
236 	_exit(ret);
237 }
238 
239 static int
event_sandboxed_get_string(pkg_sandbox_cb func,char ** result,int64_t * len,void * ud)240 event_sandboxed_get_string(pkg_sandbox_cb func, char **result, int64_t *len,
241 		void *ud)
242 {
243 	pid_t pid;
244 	struct rlimit rl_zero;
245 	int	status, ret = EPKG_OK;
246 	int pair[2], r, allocated_len = 0, off = 0;
247 	char *buf = NULL;
248 
249 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
250 		warn("socketpair failed");
251 		return (EPKG_FATAL);
252 	}
253 
254 	pid = fork();
255 
256 	switch(pid) {
257 	case -1:
258 		warn("fork failed");
259 		return (EPKG_FATAL);
260 		break;
261 	case 0:
262 		break;
263 	default:
264 		/* Parent process */
265 		close(pair[0]);
266 		/*
267 		 * We use blocking IO here as if the child is terminated we would have
268 		 * EINTR here
269 		 */
270 		buf = malloc(BUFSIZ);
271 		if (buf == NULL) {
272 			warn("malloc failed");
273 			return (EPKG_FATAL);
274 		}
275 		allocated_len = BUFSIZ;
276 		do {
277 			if (off >= allocated_len) {
278 				allocated_len *= 2;
279 				buf = realloc(buf, allocated_len);
280 				if (buf == NULL) {
281 					warn("realloc failed");
282 					return (EPKG_FATAL);
283 				}
284 			}
285 
286 			r = read(pair[1], buf + off, allocated_len - off);
287 			if (r == -1 && errno != EINTR) {
288 				free(buf);
289 				warn("read failed");
290 				return (EPKG_FATAL);
291 			}
292 			else if (r > 0) {
293 				off += r;
294 			}
295 		} while (r > 0);
296 
297 		/* Fill the result buffer */
298 		*len = off;
299 		*result = buf;
300 		if (*result == NULL) {
301 			warn("malloc failed");
302 			kill(pid, SIGTERM);
303 			ret = EPKG_FATAL;
304 		}
305 		while (waitpid(pid, &status, 0) == -1) {
306 			if (errno != EINTR) {
307 				warn("Sandboxed process pid=%d", (int)pid);
308 				ret = -1;
309 				break;
310 			}
311 		}
312 
313 		if (WIFEXITED(status)) {
314 			ret = WEXITSTATUS(status);
315 		}
316 		if (WIFSIGNALED(status)) {
317 			/* Process got some terminating signal, hence stop the loop */
318 			fprintf(stderr, "Sandboxed process pid=%d terminated abnormally by signal: %d\n",
319 					(int)pid, WTERMSIG(status));
320 			ret = -1;
321 		}
322 		return (ret);
323 	}
324 
325 	/* Here comes child process */
326 	close(pair[1]);
327 
328 	drop_privileges();
329 
330 	rl_zero.rlim_cur = rl_zero.rlim_max = 0;
331 	if (setrlimit(RLIMIT_NPROC, &rl_zero) == -1)
332 		err(EXIT_FAILURE, "Unable to setrlimit(RLIMIT_NPROC)");
333 
334 #ifdef HAVE_CAPSICUM
335 	if (cap_enter() < 0 && errno != ENOSYS) {
336 		warn("cap_enter() failed");
337 		return (EPKG_FATAL);
338 	}
339 #endif
340 
341 	ret = func(pair[0], ud);
342 
343 	close(pair[0]);
344 
345 	_exit(ret);
346 }
347 
348 void
progressbar_start(const char * pmsg)349 progressbar_start(const char *pmsg)
350 {
351 	free(progress_message);
352 	progress_message = NULL;
353 
354 	if (quiet)
355 		return;
356 	if (pmsg != NULL)
357 		progress_message = strdup(pmsg);
358 	else {
359 		fflush(msg_buf->fp);
360 		progress_message = strdup(msg_buf->buf);
361 	}
362 	last_progress_percent = -1;
363 	last_tick = 0;
364 	begin = last_update = time(NULL);
365 	bytes_per_second = 0;
366 	stalled = 0;
367 
368 	progress_started = true;
369 	progress_interrupted = false;
370 	if (!isatty(STDOUT_FILENO))
371 		printf("%s: ", progress_message);
372 	else
373 		printf("%s:   0%%", progress_message);
374 }
375 
376 void
progressbar_tick(int64_t current,int64_t total)377 progressbar_tick(int64_t current, int64_t total)
378 {
379 	int percent;
380 
381 	if (!quiet && progress_started) {
382 		if (isatty(STDOUT_FILENO))
383 			draw_progressbar(current, total);
384 		else {
385 			if (progress_interrupted) {
386 				printf("%s...", progress_message);
387 			} else if (!getenv("NO_TICK")){
388 				percent = (total != 0) ? (current * 100. / total) : 100;
389 				if (last_progress_percent / 10 < percent / 10) {
390 					last_progress_percent = percent;
391 					printf(".");
392 					fflush(stdout);
393 				}
394 			}
395 			if (current >= total)
396 				progressbar_stop();
397 		}
398 	}
399 	progress_interrupted = false;
400 }
401 
402 void
progressbar_stop(void)403 progressbar_stop(void)
404 {
405 	if (progress_started) {
406 		if (!isatty(STDOUT_FILENO))
407 			printf(" done");
408 		putchar('\n');
409 	}
410 	last_progress_percent = -1;
411 	progress_started = false;
412 	progress_interrupted = false;
413 }
414 
415 static void
draw_progressbar(int64_t current,int64_t total)416 draw_progressbar(int64_t current, int64_t total)
417 {
418 	int percent;
419 	int64_t transferred;
420 	time_t elapsed = 0, now = 0;
421 	char buf[8];
422 	int64_t bytes_left;
423 	int cur_speed;
424 	int hours, minutes, seconds;
425 	float age_factor;
426 
427 	if (!progress_started) {
428 		progressbar_stop();
429 		return;
430 	}
431 
432 	if (progress_debit) {
433 		now = time(NULL);
434 		elapsed = (now >= last_update) ? now - last_update : 0;
435 	}
436 
437 	percent = (total != 0) ? (current * 100. / total) : 100;
438 
439 	/**
440 	 * Wait for interval for debit bars to keep calc per second.
441 	 * If not debit, show on every % change, or if ticking after
442 	 * an interruption (which removed our progressbar output).
443 	 */
444 	if (current >= total || (progress_debit && elapsed >= 1) ||
445 	    (!progress_debit &&
446 	    (percent != last_progress_percent || progress_interrupted))) {
447 		last_progress_percent = percent;
448 
449 		printf("\r%s: %3d%%", progress_message, percent);
450 		if (progress_debit) {
451 			transferred = current - last_tick;
452 			last_tick = current;
453 			bytes_left = total - current;
454 			if (bytes_left <= 0) {
455 				elapsed = now - begin;
456 				/* Always show at least 1 second at end. */
457 				if (elapsed == 0)
458 					elapsed = 1;
459 				/* Calculate true total speed when done */
460 				transferred = total;
461 				bytes_per_second = 0;
462 			}
463 
464 			if (elapsed != 0)
465 				cur_speed = (transferred / elapsed);
466 			else
467 				cur_speed = transferred;
468 
469 #define AGE_FACTOR_SLOW_START 3
470 			if (now - begin <= AGE_FACTOR_SLOW_START)
471 				age_factor = 0.4;
472 			else
473 				age_factor = 0.9;
474 
475 			if (bytes_per_second != 0) {
476 				bytes_per_second =
477 				    (bytes_per_second * age_factor) +
478 				    (cur_speed * (1.0 - age_factor));
479 			} else
480 				bytes_per_second = cur_speed;
481 
482 			humanize_number(buf, sizeof(buf),
483 			    current,"B", HN_AUTOSCALE, HN_IEC_PREFIXES);
484 			printf(" %*s", (int)sizeof(buf), buf);
485 
486 			if (bytes_left > 0)
487 				format_rate_SI(buf, sizeof(buf), transferred);
488 			else /* Show overall speed when done */
489 				format_rate_SI(buf, sizeof(buf),
490 				    bytes_per_second);
491 			printf(" %s/s ", buf);
492 
493 			if (!transferred)
494 				stalled += elapsed;
495 			else
496 				stalled = 0;
497 
498 			if (stalled >= STALL_TIME)
499 				printf(" - stalled -");
500 			else if (bytes_per_second == 0 && bytes_left > 0)
501 				printf("   --:-- ETA");
502 			else {
503 				if (bytes_left > 0)
504 					seconds = bytes_left / bytes_per_second;
505 				else
506 					seconds = elapsed;
507 
508 				hours = seconds / 3600;
509 				seconds -= hours * 3600;
510 				minutes = seconds / 60;
511 				seconds -= minutes * 60;
512 
513 				if (hours != 0)
514 					printf("%02d:%02d:%02d", hours,
515 					    minutes, seconds);
516 				else
517 					printf("   %02d:%02d", minutes, seconds);
518 
519 				if (bytes_left > 0)
520 					printf(" ETA");
521 				else
522 					printf("    ");
523 			}
524 			last_update = now;
525 		}
526 		fflush(stdout);
527 	}
528 	if (current >= total)
529 		progressbar_stop();
530 }
531 
532 int
event_callback(void * data,struct pkg_event * ev)533 event_callback(void *data, struct pkg_event *ev)
534 {
535 	struct pkg *pkg = NULL, *pkg_new, *pkg_old;
536 	struct cleanup *evtmp;
537 	int *debug = data;
538 	size_t i;
539 	struct pkg_event_conflict *cur_conflict;
540 	const char *filename, *reponame;
541 
542 	if (msg_buf == NULL) {
543 		msg_buf = xstring_new();
544 	}
545 
546 	/*
547 	 * If a progressbar has been interrupted by another event, then
548 	 * we need to add a newline to prevent bad formatting.
549 	 */
550 	if (progress_started && ev->type != PKG_EVENT_PROGRESS_TICK &&
551 	    !progress_interrupted) {
552 		putchar('\n');
553 		progress_interrupted = true;
554 	}
555 
556 	switch(ev->type) {
557 	case PKG_EVENT_ERRNO:
558 		warnx("%s(%s): %s", ev->e_errno.func, ev->e_errno.arg,
559 		    strerror(ev->e_errno.no));
560 		break;
561 	case PKG_EVENT_ERROR:
562 		warnx("%s", ev->e_pkg_error.msg);
563 		break;
564 	case PKG_EVENT_NOTICE:
565 		if (!quiet)
566 			printf("%s\n", ev->e_pkg_notice.msg);
567 		break;
568 	case PKG_EVENT_DEVELOPER_MODE:
569 		warnx("DEVELOPER_MODE: %s", ev->e_pkg_error.msg);
570 		break;
571 	case PKG_EVENT_UPDATE_ADD:
572 		if (quiet || !isatty(STDOUT_FILENO))
573 			break;
574 		printf("\rPushing new entries %d/%d", ev->e_upd_add.done, ev->e_upd_add.total);
575 		if (ev->e_upd_add.total == ev->e_upd_add.done)
576 			printf("\n");
577 		break;
578 	case PKG_EVENT_UPDATE_REMOVE:
579 		if (quiet || !isatty(STDOUT_FILENO))
580 			break;
581 		printf("\rRemoving entries %d/%d", ev->e_upd_remove.done, ev->e_upd_remove.total);
582 		if (ev->e_upd_remove.total == ev->e_upd_remove.done)
583 			printf("\n");
584 		break;
585 	case PKG_EVENT_FETCH_BEGIN:
586 		if (nbtodl > 0)
587 			nbdone++;
588 		if (quiet)
589 			break;
590 		filename = strrchr(ev->e_fetching.url, '/');
591 		if (filename != NULL) {
592 			filename++;
593 		} else {
594 			/*
595 			 * We failed at being smart, so display
596 			 * the entire url.
597 			 */
598 			filename = ev->e_fetching.url;
599 		}
600 		job_status_begin(msg_buf);
601 		progress_debit = true;
602 		fprintf(msg_buf->fp, "Fetching %s", filename);
603 		break;
604 	case PKG_EVENT_FETCH_FINISHED:
605 		progress_debit = false;
606 		break;
607 	case PKG_EVENT_INSTALL_BEGIN:
608 		if (quiet)
609 			break;
610 		job_status_begin(msg_buf);
611 
612 		pkg = ev->e_install_begin.pkg;
613 		pkg_fprintf(msg_buf->fp, "Installing %n-%v...\n", pkg,
614 		    pkg);
615 		fflush(msg_buf->fp);
616 		printf("%s", msg_buf->buf);
617 		break;
618 	case PKG_EVENT_INSTALL_FINISHED:
619 		if (quiet)
620 			break;
621 		break;
622 	case PKG_EVENT_EXTRACT_BEGIN:
623 		if (quiet)
624 			break;
625 		else {
626 			job_status_begin(msg_buf);
627 			pkg = ev->e_install_begin.pkg;
628 			pkg_fprintf(msg_buf->fp, "Extracting %n-%v", pkg, pkg);
629 			fflush(msg_buf->fp);
630 		}
631 		break;
632 	case PKG_EVENT_EXTRACT_FINISHED:
633 		break;
634 	case PKG_EVENT_ADD_DEPS_BEGIN:
635 		++add_deps_depth;
636 		break;
637 	case PKG_EVENT_ADD_DEPS_FINISHED:
638 		--add_deps_depth;
639 		break;
640 	case PKG_EVENT_INTEGRITYCHECK_BEGIN:
641 		if (quiet)
642 			break;
643 		printf("Checking integrity...");
644 		break;
645 	case PKG_EVENT_INTEGRITYCHECK_FINISHED:
646 		if (quiet)
647 			break;
648 		printf(" done (%d conflicting)\n", ev->e_integrity_finished.conflicting);
649 		if (conflicts != NULL) {
650 			fflush(conflicts->fp);
651 			printf("%s", conflicts->buf);
652 			xstring_free(conflicts);
653 			conflicts = NULL;
654 		}
655 		break;
656 	case PKG_EVENT_INTEGRITYCHECK_CONFLICT:
657 		if (*debug == 0)
658 			break;
659 		printf("\nConflict found on path %s between %s and ",
660 		    ev->e_integrity_conflict.pkg_path,
661 		    ev->e_integrity_conflict.pkg_uid);
662 		cur_conflict = ev->e_integrity_conflict.conflicts;
663 		while (cur_conflict) {
664 			if (cur_conflict->next)
665 				printf("%s, ", cur_conflict->uid);
666 			else
667 				printf("%s", cur_conflict->uid);
668 
669 			cur_conflict = cur_conflict->next;
670 		}
671 		printf("\n");
672 		break;
673 	case PKG_EVENT_DEINSTALL_BEGIN:
674 		if (quiet)
675 			break;
676 
677 		job_status_begin(msg_buf);
678 
679 		pkg = ev->e_install_begin.pkg;
680 		pkg_fprintf(msg_buf->fp, "Deinstalling %n-%v...\n", pkg, pkg);
681 		fflush(msg_buf->fp);
682 		printf("%s", msg_buf->buf);
683 		break;
684 	case PKG_EVENT_DEINSTALL_FINISHED:
685 		if (quiet)
686 			break;
687 		break;
688 	case PKG_EVENT_DELETE_FILES_BEGIN:
689 		if (quiet)
690 			break;
691 		else {
692 			job_status_begin(msg_buf);
693 			pkg = ev->e_install_begin.pkg;
694 			pkg_fprintf(msg_buf->fp, "Deleting files for %n-%v",
695 			    pkg, pkg);
696 		}
697 		break;
698 	case PKG_EVENT_DELETE_FILES_FINISHED:
699 		break;
700 	case PKG_EVENT_UPGRADE_BEGIN:
701 		if (quiet)
702 			break;
703 		pkg_new = ev->e_upgrade_begin.n;
704 		pkg_old = ev->e_upgrade_begin.o;
705 
706 		job_status_begin(msg_buf);
707 
708 		switch (pkg_version_change_between(pkg_new, pkg_old)) {
709 		case PKG_DOWNGRADE:
710 			pkg_fprintf(msg_buf->fp, "Downgrading %n from %v to %v...\n",
711 			    pkg_new, pkg_old, pkg_new);
712 			break;
713 		case PKG_REINSTALL:
714 			pkg_fprintf(msg_buf->fp, "Reinstalling %n-%v...\n",
715 		    pkg_old, pkg_old);
716 			break;
717 		case PKG_UPGRADE:
718 			pkg_fprintf(msg_buf->fp, "Upgrading %n from %v to %v...\n",
719 			    pkg_new, pkg_old, pkg_new);
720 			break;
721 		}
722 		fflush(msg_buf->fp);
723 		printf("%s", msg_buf->buf);
724 		break;
725 	case PKG_EVENT_UPGRADE_FINISHED:
726 		if (quiet)
727 			break;
728 		break;
729 	case PKG_EVENT_LOCKED:
730 		pkg = ev->e_locked.pkg;
731 		pkg_printf("\n%n-%v is locked and may not be modified\n", pkg, pkg);
732 		break;
733 	case PKG_EVENT_REQUIRED:
734 		pkg = ev->e_required.pkg;
735 		pkg_printf("\n%n-%v is required by: %r%{%rn-%rv%| %}", pkg, pkg, pkg);
736 		if (ev->e_required.force == 1)
737 			fprintf(stderr, ", deleting anyway\n");
738 		else
739 			fprintf(stderr, "\n");
740 		break;
741 	case PKG_EVENT_ALREADY_INSTALLED:
742 		if (quiet)
743 			break;
744 		pkg = ev->e_already_installed.pkg;
745 		pkg_printf("the most recent version of %n-%v is already installed\n",
746 				pkg, pkg);
747 		break;
748 	case PKG_EVENT_NOT_FOUND:
749 		printf("Package '%s' was not found in "
750 		    "the repositories\n", ev->e_not_found.pkg_name);
751 		break;
752 	case PKG_EVENT_MISSING_DEP:
753 		warnx("Missing dependency '%s'",
754 		    pkg_dep_name(ev->e_missing_dep.dep));
755 		break;
756 	case PKG_EVENT_NOREMOTEDB:
757 		fprintf(stderr, "Unable to open remote database \"%s\". "
758 		    "Try running '%s update' first.\n", ev->e_remotedb.repo,
759 		    getprogname());
760 		break;
761 	case PKG_EVENT_NOLOCALDB:
762 		fprintf(stderr, "Local package database nonexistent!\n");
763 		break;
764 	case PKG_EVENT_NEWPKGVERSION:
765 		newpkgversion = true;
766 		printf("New version of pkg detected; it needs to be "
767 		    "installed first.\n");
768 		break;
769 	case PKG_EVENT_FILE_MISMATCH:
770 		pkg = ev->e_file_mismatch.pkg;
771 		pkg_fprintf(stderr, "%n-%v: checksum mismatch for %Fn\n", pkg,
772 		    pkg, ev->e_file_mismatch.file);
773 		break;
774 	case PKG_EVENT_FILE_MISSING:
775 		pkg = ev->e_file_missing.pkg;
776 		pkg_fprintf(stderr, "%n-%v: missing file %Fn\n", pkg, pkg,
777 		    ev->e_file_missing.file);
778 		break;
779 	case PKG_EVENT_PLUGIN_ERRNO:
780 		warnx("%s: %s(%s): %s",
781 		    pkg_plugin_get(ev->e_plugin_errno.plugin, PKG_PLUGIN_NAME),
782 		    ev->e_plugin_errno.func, ev->e_plugin_errno.arg,
783 		    strerror(ev->e_plugin_errno.no));
784 		break;
785 	case PKG_EVENT_PLUGIN_ERROR:
786 		warnx("%s: %s",
787 		    pkg_plugin_get(ev->e_plugin_error.plugin, PKG_PLUGIN_NAME),
788 		    ev->e_plugin_error.msg);
789 		break;
790 	case PKG_EVENT_PLUGIN_INFO:
791 		if (quiet)
792 			break;
793 		printf("%s: %s\n",
794 		    pkg_plugin_get(ev->e_plugin_info.plugin, PKG_PLUGIN_NAME),
795 		    ev->e_plugin_info.msg);
796 		break;
797 	case PKG_EVENT_INCREMENTAL_UPDATE:
798 		if (!quiet)
799 			printf("%s repository update completed. %d packages processed.\n",
800 			    ev->e_incremental_update.reponame,
801 			    ev->e_incremental_update.processed);
802 		break;
803 	case PKG_EVENT_DEBUG:
804 		fprintf(stderr, "DBG(%d)[%d]> %s\n", ev->e_debug.level,
805 			(int)getpid(), ev->e_debug.msg);
806 		break;
807 	case PKG_EVENT_QUERY_YESNO:
808 		return ( ev->e_query_yesno.deft ?
809 			query_yesno(true, ev->e_query_yesno.msg, "[Y/n]") :
810 			query_yesno(false, ev->e_query_yesno.msg, "[y/N]") );
811 		break;
812 	case PKG_EVENT_QUERY_SELECT:
813 		return query_select(ev->e_query_select.msg, ev->e_query_select.items,
814 			ev->e_query_select.ncnt, ev->e_query_select.deft);
815 		break;
816 	case PKG_EVENT_SANDBOX_CALL:
817 		return ( event_sandboxed_call(ev->e_sandbox_call.call,
818 				ev->e_sandbox_call.fd,
819 				ev->e_sandbox_call.userdata) );
820 		break;
821 	case PKG_EVENT_SANDBOX_GET_STRING:
822 		return ( event_sandboxed_get_string(ev->e_sandbox_call_str.call,
823 				ev->e_sandbox_call_str.result,
824 				ev->e_sandbox_call_str.len,
825 				ev->e_sandbox_call_str.userdata) );
826 		break;
827 	case PKG_EVENT_PROGRESS_START:
828 		progressbar_start(ev->e_progress_start.msg);
829 		break;
830 	case PKG_EVENT_PROGRESS_TICK:
831 		progressbar_tick(ev->e_progress_tick.current,
832 		    ev->e_progress_tick.total);
833 		break;
834 	case PKG_EVENT_BACKUP:
835 		fprintf(msg_buf->fp, "Backing up");
836 		break;
837 	case PKG_EVENT_RESTORE:
838 		fprintf(msg_buf->fp, "Restoring");
839 		break;
840 	case PKG_EVENT_NEW_ACTION:
841 		nbdone++;
842 		break;
843 	case PKG_EVENT_MESSAGE:
844 		if (messages == NULL)
845 			messages = xstring_new();
846 		fprintf(messages->fp, "%s", ev->e_pkg_message.msg);
847 		break;
848 	case PKG_EVENT_CLEANUP_CALLBACK_REGISTER:
849 		if (!signal_handler_installed) {
850 			kv_init(cleanup_list);
851 			signal(SIGINT, cleanup_handler);
852 			signal_handler_installed = true;
853 		}
854 		evtmp = malloc(sizeof(struct cleanup));
855 		evtmp->cb = ev->e_cleanup_callback.cleanup_cb;
856 		evtmp->data = ev->e_cleanup_callback.data;
857 		kv_push(struct cleanup *, cleanup_list, evtmp);
858 		break;
859 	case PKG_EVENT_CLEANUP_CALLBACK_UNREGISTER:
860 		if (!signal_handler_installed)
861 			break;
862 		for (i = 0; i < kv_size(cleanup_list); i++) {
863 			evtmp = kv_A(cleanup_list, i);
864 			if (evtmp->cb == ev->e_cleanup_callback.cleanup_cb &&
865 			    evtmp->data == ev->e_cleanup_callback.data) {
866 				kv_del(struct cleanup *, cleanup_list, i);
867 				break;
868 			}
869 		}
870 		break;
871 	case PKG_EVENT_CONFLICTS:
872 		if (conflicts == NULL) {
873 			conflicts = xstring_new();
874 		}
875 		pkg_fprintf(conflicts->fp, "  - %n-%v",
876 		    ev->e_conflicts.p1, ev->e_conflicts.p1);
877 		if (pkg_repos_total_count() > 1) {
878 			pkg_get(ev->e_conflicts.p1, PKG_REPONAME, &reponame);
879 			fprintf(conflicts->fp, " [%s]",
880 			    reponame == NULL ? "installed" : reponame);
881 		}
882 		pkg_fprintf(conflicts->fp, " conflicts with %n-%v",
883 		    ev->e_conflicts.p2, ev->e_conflicts.p2);
884 		if (pkg_repos_total_count() > 1) {
885 			pkg_get(ev->e_conflicts.p2, PKG_REPONAME, &reponame);
886 			fprintf(conflicts->fp, " [%s]",
887 			    reponame == NULL ? "installed" : reponame);
888 		}
889 		fprintf(conflicts->fp, " on %s\n",
890 		    ev->e_conflicts.path);
891 		break;
892 	case PKG_EVENT_TRIGGER:
893 		if (!quiet) {
894 			if (ev->e_trigger.cleanup)
895 				printf("==> Cleaning up trigger: %s\n", ev->e_trigger.name);
896 			else
897 				printf("==> Running trigger: %s\n", ev->e_trigger.name);
898 		}
899 	default:
900 		break;
901 	}
902 
903 	return 0;
904 }
905