1 /*
2  *  callback.c
3  *
4  *  Copyright (c) 2006-2018 Pacman Development Team <pacman-dev@archlinux.org>
5  *  Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/time.h> /* gettimeofday */
25 #include <sys/types.h> /* off_t */
26 #include <stdint.h> /* int64_t */
27 #include <time.h>
28 #include <unistd.h>
29 #include <wchar.h>
30 #include <limits.h> /* UINT_MAX */
31 
32 #include <alpm.h>
33 
34 /* pacman */
35 #include "pacman.h"
36 #include "callback.h"
37 #include "util.h"
38 #include "conf.h"
39 
40 /* download progress bar */
41 static off_t list_xfered = 0.0;
42 static off_t list_total = 0.0;
43 
44 /* delayed output during progress bar */
45 static int on_progress = 0;
46 static alpm_list_t *output = NULL;
47 
48 /* update speed for the fill_progress based functions */
49 #define UPDATE_SPEED_MS 200
50 
51 #if !defined(CLOCK_MONOTONIC_COARSE) && defined(CLOCK_MONOTONIC)
52 #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC
53 #endif
54 
get_time_ms(void)55 static int64_t get_time_ms(void)
56 {
57 #if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) && defined(CLOCK_MONOTONIC_COARSE)
58 	struct timespec ts = {0, 0};
59 	clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
60 	return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
61 #else
62 	/* darwin doesn't support clock_gettime, fallback to gettimeofday */
63 	struct timeval tv = {0, 0};
64 	gettimeofday(&tv, NULL);
65 	return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
66 #endif
67 }
68 
69 /**
70  * Silly little helper function, determines if the caller needs a visual update
71  * since the last time this function was called.
72  * This is made for the two progress bar functions, to prevent flicker.
73  * @param first_call 1 on first call for initialization purposes, 0 otherwise
74  * @return number of milliseconds since last call
75  */
get_update_timediff(int first_call)76 static int64_t get_update_timediff(int first_call)
77 {
78 	int64_t retval = 0;
79 	static int64_t last_time = 0;
80 
81 	/* on first call, simply set the last time and return */
82 	if(first_call) {
83 		last_time = get_time_ms();
84 	} else {
85 		int64_t this_time = get_time_ms();
86 		retval = this_time - last_time;
87 
88 		/* do not update last_time if interval was too short */
89 		if(retval < 0 || retval >= UPDATE_SPEED_MS) {
90 			last_time = this_time;
91 		}
92 	}
93 
94 	return retval;
95 }
96 
97 /* refactored from cb_trans_progress */
fill_progress(const int bar_percent,const int disp_percent,const int proglen)98 static void fill_progress(const int bar_percent, const int disp_percent,
99 		const int proglen)
100 {
101 	/* 8 = 1 space + 1 [ + 1 ] + 5 for percent */
102 	const int hashlen = proglen > 8 ? proglen - 8 : 0;
103 	const int hash = bar_percent * hashlen / 100;
104 	static int lasthash = 0, mouth = 0;
105 	int i;
106 
107 	if(bar_percent == 0) {
108 		lasthash = 0;
109 		mouth = 0;
110 	}
111 
112 	if(hashlen > 0) {
113 		fputs(" [", stdout);
114 		for(i = hashlen; i > 0; --i) {
115 			/* if special progress bar enabled */
116 			if(config->chomp) {
117 				if(i > hashlen - hash) {
118 					putchar('-');
119 				} else if(i == hashlen - hash) {
120 					if(lasthash == hash) {
121 						if(mouth) {
122 							fputs("\033[1;33mC\033[m", stdout);
123 						} else {
124 							fputs("\033[1;33mc\033[m", stdout);
125 						}
126 					} else {
127 						lasthash = hash;
128 						mouth = mouth == 1 ? 0 : 1;
129 						if(mouth) {
130 							fputs("\033[1;33mC\033[m", stdout);
131 						} else {
132 							fputs("\033[1;33mc\033[m", stdout);
133 						}
134 					}
135 				} else if(i % 3 == 0) {
136 					fputs("\033[0;37mo\033[m", stdout);
137 				} else {
138 					fputs("\033[0;37m \033[m", stdout);
139 				}
140 			} /* else regular progress bar */
141 			else if(i > hashlen - hash) {
142 				putchar('#');
143 			} else {
144 				putchar('-');
145 			}
146 		}
147 		putchar(']');
148 	}
149 	/* print display percent after progress bar */
150 	/* 5 = 1 space + 3 digits + 1 % */
151 	if(proglen >= 5) {
152 		printf(" %3d%%", disp_percent);
153 	}
154 
155 	if(bar_percent == 100) {
156 		putchar('\n');
157 	} else {
158 		putchar('\r');
159 	}
160 	fflush(stdout);
161 }
162 
number_length(size_t n)163 static int number_length(size_t n)
164 {
165 	int digits = 1;
166 	while((n /= 10)) {
167 		++digits;
168 	}
169 
170 	return digits;
171 }
172 
173 /* callback to handle messages/notifications from libalpm transactions */
cb_event(alpm_event_t * event)174 void cb_event(alpm_event_t *event)
175 {
176 	if(config->print) {
177 		return;
178 	}
179 	switch(event->type) {
180 		case ALPM_EVENT_HOOK_START:
181 			if(event->hook.when == ALPM_HOOK_PRE_TRANSACTION) {
182 				colon_printf(_("Running pre-transaction hooks...\n"));
183 			} else {
184 				colon_printf(_("Running post-transaction hooks...\n"));
185 			}
186 			break;
187 		case ALPM_EVENT_HOOK_RUN_START:
188 			{
189 				alpm_event_hook_run_t *e = &event->hook_run;
190 				int digits = number_length(e->total);
191 				printf("(%*zu/%*zu) %s\n", digits, e->position,
192 						digits, e->total,
193 						e->desc ? e->desc : e->name);
194 			}
195 			break;
196 		case ALPM_EVENT_CHECKDEPS_START:
197 			printf(_("checking dependencies...\n"));
198 			break;
199 		case ALPM_EVENT_FILECONFLICTS_START:
200 			if(config->noprogressbar) {
201 				printf(_("checking for file conflicts...\n"));
202 			}
203 			break;
204 		case ALPM_EVENT_RESOLVEDEPS_START:
205 			printf(_("resolving dependencies...\n"));
206 			break;
207 		case ALPM_EVENT_INTERCONFLICTS_START:
208 			printf(_("looking for conflicting packages...\n"));
209 			break;
210 		case ALPM_EVENT_TRANSACTION_START:
211 			colon_printf(_("Processing package changes...\n"));
212 			break;
213 		case ALPM_EVENT_PACKAGE_OPERATION_START:
214 			if(config->noprogressbar) {
215 				alpm_event_package_operation_t *e = &event->package_operation;
216 				switch(e->operation) {
217 					case ALPM_PACKAGE_INSTALL:
218 						printf(_("installing %s...\n"), alpm_pkg_get_name(e->newpkg));
219 						break;
220 					case ALPM_PACKAGE_UPGRADE:
221 						printf(_("upgrading %s...\n"), alpm_pkg_get_name(e->newpkg));
222 						break;
223 					case ALPM_PACKAGE_REINSTALL:
224 						printf(_("reinstalling %s...\n"), alpm_pkg_get_name(e->newpkg));
225 						break;
226 					case ALPM_PACKAGE_DOWNGRADE:
227 						printf(_("downgrading %s...\n"), alpm_pkg_get_name(e->newpkg));
228 						break;
229 					case ALPM_PACKAGE_REMOVE:
230 						printf(_("removing %s...\n"), alpm_pkg_get_name(e->oldpkg));
231 						break;
232 				}
233 			}
234 			break;
235 		case ALPM_EVENT_PACKAGE_OPERATION_DONE:
236 			{
237 				alpm_event_package_operation_t *e = &event->package_operation;
238 				switch(e->operation) {
239 					case ALPM_PACKAGE_INSTALL:
240 						display_optdepends(e->newpkg);
241 						break;
242 					case ALPM_PACKAGE_UPGRADE:
243 					case ALPM_PACKAGE_DOWNGRADE:
244 						display_new_optdepends(e->oldpkg, e->newpkg);
245 						break;
246 					case ALPM_PACKAGE_REINSTALL:
247 					case ALPM_PACKAGE_REMOVE:
248 						break;
249 				}
250 			}
251 			break;
252 		case ALPM_EVENT_INTEGRITY_START:
253 			if(config->noprogressbar) {
254 				printf(_("checking package integrity...\n"));
255 			}
256 			break;
257 		case ALPM_EVENT_KEYRING_START:
258 			if(config->noprogressbar) {
259 				printf(_("checking keyring...\n"));
260 			}
261 			break;
262 		case ALPM_EVENT_KEY_DOWNLOAD_START:
263 			printf(_("downloading required keys...\n"));
264 			break;
265 		case ALPM_EVENT_LOAD_START:
266 			if(config->noprogressbar) {
267 				printf(_("loading package files...\n"));
268 			}
269 			break;
270 		case ALPM_EVENT_DELTA_INTEGRITY_START:
271 			printf(_("checking delta integrity...\n"));
272 			break;
273 		case ALPM_EVENT_DELTA_PATCHES_START:
274 			printf(_("applying deltas...\n"));
275 			break;
276 		case ALPM_EVENT_DELTA_PATCH_START:
277 			printf(_("generating %s with %s... "),
278 					event->delta_patch.delta->to,
279 					event->delta_patch.delta->delta);
280 			break;
281 		case ALPM_EVENT_DELTA_PATCH_DONE:
282 			printf(_("success!\n"));
283 			break;
284 		case ALPM_EVENT_DELTA_PATCH_FAILED:
285 			printf(_("failed.\n"));
286 			break;
287 		case ALPM_EVENT_SCRIPTLET_INFO:
288 			fputs(event->scriptlet_info.line, stdout);
289 			break;
290 		case ALPM_EVENT_RETRIEVE_START:
291 			colon_printf(_("Retrieving packages...\n"));
292 			break;
293 		case ALPM_EVENT_DISKSPACE_START:
294 			if(config->noprogressbar) {
295 				printf(_("checking available disk space...\n"));
296 			}
297 			break;
298 		case ALPM_EVENT_OPTDEP_REMOVAL:
299 			{
300 				alpm_event_optdep_removal_t *e = &event->optdep_removal;
301 				char *dep_string = alpm_dep_compute_string(e->optdep);
302 				colon_printf(_("%s optionally requires %s\n"),
303 						alpm_pkg_get_name(e->pkg),
304 						dep_string);
305 				free(dep_string);
306 			}
307 			break;
308 		case ALPM_EVENT_DATABASE_MISSING:
309 			if(!config->op_s_sync) {
310 				pm_printf(ALPM_LOG_WARNING,
311 						"database file for '%s' does not exist (use '%s' to download)\n",
312 						event->database_missing.dbname,
313 						config->op == PM_OP_FILES ? "-Fy": "-Sy");
314 			}
315 			break;
316 		case ALPM_EVENT_PACNEW_CREATED:
317 			{
318 				alpm_event_pacnew_created_t *e = &event->pacnew_created;
319 				if(on_progress) {
320 					char *string = NULL;
321 					pm_sprintf(&string, ALPM_LOG_WARNING, _("%s installed as %s.pacnew\n"),
322 							e->file, e->file);
323 					if(string != NULL) {
324 						output = alpm_list_add(output, string);
325 					}
326 				} else {
327 					pm_printf(ALPM_LOG_WARNING, _("%s installed as %s.pacnew\n"),
328 							e->file, e->file);
329 				}
330 			}
331 			break;
332 		case ALPM_EVENT_PACSAVE_CREATED:
333 			{
334 				alpm_event_pacsave_created_t *e = &event->pacsave_created;
335 				if(on_progress) {
336 					char *string = NULL;
337 					pm_sprintf(&string, ALPM_LOG_WARNING, _("%s saved as %s.pacsave\n"),
338 							e->file, e->file);
339 					if(string != NULL) {
340 						output = alpm_list_add(output, string);
341 					}
342 				} else {
343 					pm_printf(ALPM_LOG_WARNING, _("%s saved as %s.pacsave\n"),
344 							e->file, e->file);
345 				}
346 			}
347 			break;
348 		/* all the simple done events, with fallthrough for each */
349 		case ALPM_EVENT_FILECONFLICTS_DONE:
350 		case ALPM_EVENT_CHECKDEPS_DONE:
351 		case ALPM_EVENT_RESOLVEDEPS_DONE:
352 		case ALPM_EVENT_INTERCONFLICTS_DONE:
353 		case ALPM_EVENT_TRANSACTION_DONE:
354 		case ALPM_EVENT_INTEGRITY_DONE:
355 		case ALPM_EVENT_KEYRING_DONE:
356 		case ALPM_EVENT_KEY_DOWNLOAD_DONE:
357 		case ALPM_EVENT_LOAD_DONE:
358 		case ALPM_EVENT_DELTA_INTEGRITY_DONE:
359 		case ALPM_EVENT_DELTA_PATCHES_DONE:
360 		case ALPM_EVENT_DISKSPACE_DONE:
361 		case ALPM_EVENT_RETRIEVE_DONE:
362 		case ALPM_EVENT_RETRIEVE_FAILED:
363 		case ALPM_EVENT_HOOK_DONE:
364 		case ALPM_EVENT_HOOK_RUN_DONE:
365 		/* we can safely ignore those as well */
366 		case ALPM_EVENT_PKGDOWNLOAD_START:
367 		case ALPM_EVENT_PKGDOWNLOAD_DONE:
368 		case ALPM_EVENT_PKGDOWNLOAD_FAILED:
369 			/* nothing */
370 			break;
371 	}
372 	fflush(stdout);
373 }
374 
375 /* callback to handle questions from libalpm transactions (yes/no) */
cb_question(alpm_question_t * question)376 void cb_question(alpm_question_t *question)
377 {
378 	if(config->print) {
379 		switch(question->type) {
380 			case ALPM_QUESTION_INSTALL_IGNOREPKG:
381 			case ALPM_QUESTION_REPLACE_PKG:
382 				question->any.answer = 1;
383 				break;
384 			default:
385 				question->any.answer = 0;
386 				break;
387 		}
388 		return;
389 	}
390 	switch(question->type) {
391 		case ALPM_QUESTION_INSTALL_IGNOREPKG:
392 			{
393 				alpm_question_install_ignorepkg_t *q = &question->install_ignorepkg;
394 				if(!config->op_s_downloadonly) {
395 					q->install = yesno(_("%s is in IgnorePkg/IgnoreGroup. Install anyway?"),
396 							alpm_pkg_get_name(q->pkg));
397 				} else {
398 					q->install = 1;
399 				}
400 			}
401 			break;
402 		case ALPM_QUESTION_REPLACE_PKG:
403 			{
404 				alpm_question_replace_t *q = &question->replace;
405 				q->replace = yesno(_("Replace %s with %s/%s?"),
406 						alpm_pkg_get_name(q->oldpkg),
407 						alpm_db_get_name(q->newdb),
408 						alpm_pkg_get_name(q->newpkg));
409 			}
410 			break;
411 		case ALPM_QUESTION_CONFLICT_PKG:
412 			{
413 				alpm_question_conflict_t *q = &question->conflict;
414 				/* print conflict only if it contains new information */
415 				if(strcmp(q->conflict->package1, q->conflict->reason->name) == 0
416 						|| strcmp(q->conflict->package2, q->conflict->reason->name) == 0) {
417 					q->remove = noyes(_("%s and %s are in conflict. Remove %s?"),
418 							q->conflict->package1,
419 							q->conflict->package2,
420 							q->conflict->package2);
421 				} else {
422 					q->remove = noyes(_("%s and %s are in conflict (%s). Remove %s?"),
423 							q->conflict->package1,
424 							q->conflict->package2,
425 							q->conflict->reason->name,
426 							q->conflict->package2);
427 				}
428 			}
429 			break;
430 		case ALPM_QUESTION_REMOVE_PKGS:
431 			{
432 				alpm_question_remove_pkgs_t *q = &question->remove_pkgs;
433 				alpm_list_t *namelist = NULL, *i;
434 				size_t count = 0;
435 				for(i = q->packages; i; i = i->next) {
436 					namelist = alpm_list_add(namelist,
437 							(char *)alpm_pkg_get_name(i->data));
438 					count++;
439 				}
440 				colon_printf(_n(
441 							"The following package cannot be upgraded due to unresolvable dependencies:\n",
442 							"The following packages cannot be upgraded due to unresolvable dependencies:\n",
443 							count));
444 				list_display("     ", namelist, getcols());
445 				printf("\n");
446 				q->skip = noyes(_n(
447 							"Do you want to skip the above package for this upgrade?",
448 							"Do you want to skip the above packages for this upgrade?",
449 							count));
450 				alpm_list_free(namelist);
451 			}
452 			break;
453 		case ALPM_QUESTION_SELECT_PROVIDER:
454 			{
455 				alpm_question_select_provider_t *q = &question->select_provider;
456 				size_t count = alpm_list_count(q->providers);
457 				char *depstring = alpm_dep_compute_string(q->depend);
458 				colon_printf(_n("There is %zu provider available for %s\n",
459 						"There are %zu providers available for %s:\n", count),
460 						count, depstring);
461 				free(depstring);
462 				select_display(q->providers);
463 				q->use_index = select_question(count);
464 			}
465 			break;
466 		case ALPM_QUESTION_CORRUPTED_PKG:
467 			{
468 				alpm_question_corrupted_t *q = &question->corrupted;
469 				q->remove = yesno(_("File %s is corrupted (%s).\n"
470 							"Do you want to delete it?"),
471 						q->filepath,
472 						alpm_strerror(q->reason));
473 			}
474 			break;
475 		case ALPM_QUESTION_IMPORT_KEY:
476 			{
477 				alpm_question_import_key_t *q = &question->import_key;
478 				char created[12];
479 				time_t time = (time_t)q->key->created;
480 				strftime(created, 12, "%Y-%m-%d", localtime(&time));
481 
482 				if(q->key->revoked) {
483 					q->import = yesno(_("Import PGP key %u%c/%s, \"%s\", created: %s (revoked)?"),
484 							q->key->length, q->key->pubkey_algo, q->key->fingerprint, q->key->uid, created);
485 				} else {
486 					q->import = yesno(_("Import PGP key %u%c/%s, \"%s\", created: %s?"),
487 							q->key->length, q->key->pubkey_algo, q->key->fingerprint, q->key->uid, created);
488 				}
489 			}
490 			break;
491 	}
492 	if(config->noask) {
493 		if(config->ask & question->type) {
494 			/* inverse the default answer */
495 			question->any.answer = !question->any.answer;
496 		}
497 	}
498 }
499 
500 /* callback to handle display of transaction progress */
cb_progress(alpm_progress_t event,const char * pkgname,int percent,size_t howmany,size_t current)501 void cb_progress(alpm_progress_t event, const char *pkgname, int percent,
502                        size_t howmany, size_t current)
503 {
504 	static int prevpercent;
505 	static size_t prevcurrent;
506 	/* size of line to allocate for text printing (e.g. not progressbar) */
507 	int infolen;
508 	int digits, textlen;
509 	char *opr = NULL;
510 	/* used for wide character width determination and printing */
511 	int len, wclen, wcwid, padwid;
512 	wchar_t *wcstr;
513 
514 	const unsigned short cols = getcols();
515 
516 	if(config->noprogressbar || cols == 0) {
517 		return;
518 	}
519 
520 	if(percent == 0) {
521 		get_update_timediff(1);
522 	} else if(percent == 100) {
523 		/* no need for timediff update, but unconditionally continue unless we
524 		 * already completed on a previous call */
525 		if(prevpercent == 100) {
526 			return;
527 		}
528 	} else {
529 		if(current != prevcurrent) {
530 			/* update always */
531 		} else if(!pkgname || percent == prevpercent ||
532 				get_update_timediff(0) < UPDATE_SPEED_MS) {
533 			/* only update the progress bar when we have a package name, the
534 			 * percentage has changed, and it has been long enough. */
535 			return;
536 		}
537 	}
538 
539 	prevpercent = percent;
540 	prevcurrent = current;
541 
542 	/* set text of message to display */
543 	switch(event) {
544 		case ALPM_PROGRESS_ADD_START:
545 			opr = _("installing");
546 			break;
547 		case ALPM_PROGRESS_UPGRADE_START:
548 			opr = _("upgrading");
549 			break;
550 		case ALPM_PROGRESS_DOWNGRADE_START:
551 			opr = _("downgrading");
552 			break;
553 		case ALPM_PROGRESS_REINSTALL_START:
554 			opr = _("reinstalling");
555 			break;
556 		case ALPM_PROGRESS_REMOVE_START:
557 			opr = _("removing");
558 			break;
559 		case ALPM_PROGRESS_CONFLICTS_START:
560 			opr = _("checking for file conflicts");
561 			break;
562 		case ALPM_PROGRESS_DISKSPACE_START:
563 			opr = _("checking available disk space");
564 			break;
565 		case ALPM_PROGRESS_INTEGRITY_START:
566 			opr = _("checking package integrity");
567 			break;
568 		case ALPM_PROGRESS_KEYRING_START:
569 			opr = _("checking keys in keyring");
570 			break;
571 		case ALPM_PROGRESS_LOAD_START:
572 			opr = _("loading package files");
573 			break;
574 		default:
575 			return;
576 	}
577 
578 	infolen = cols * 6 / 10;
579 	if(infolen < 50) {
580 		infolen = 50;
581 	}
582 
583 	/* find # of digits in package counts to scale output */
584 	digits = number_length(howmany);
585 
586 	/* determine room left for non-digits text [not ( 1/12) part] */
587 	textlen = infolen - 3 /* (/) */ - (2 * digits) - 1 /* space */;
588 
589 	/* In order to deal with characters from all locales, we have to worry
590 	 * about wide characters and their column widths. A lot of stuff is
591 	 * done here to figure out the actual number of screen columns used
592 	 * by the output, and then pad it accordingly so we fill the terminal.
593 	 */
594 	/* len = opr len + pkgname len (if available) + space + null */
595 	len = strlen(opr) + ((pkgname) ? strlen(pkgname) : 0) + 2;
596 	wcstr = calloc(len, sizeof(wchar_t));
597 	/* print our strings to the alloc'ed memory */
598 #if defined(HAVE_SWPRINTF)
599 	wclen = swprintf(wcstr, len, L"%s %s", opr, pkgname);
600 #else
601 	/* because the format string was simple, we can easily do this without
602 	 * using swprintf, although it is probably not as safe/fast. The max
603 	 * chars we can copy is decremented each time by subtracting the length
604 	 * of the already printed/copied wide char string. */
605 	wclen = mbstowcs(wcstr, opr, len);
606 	wclen += mbstowcs(wcstr + wclen, " ", len - wclen);
607 	wclen += mbstowcs(wcstr + wclen, pkgname, len - wclen);
608 #endif
609 	wcwid = wcswidth(wcstr, wclen);
610 	padwid = textlen - wcwid;
611 	/* if padwid is < 0, we need to trim the string so padwid = 0 */
612 	if(padwid < 0) {
613 		int i = textlen - 3;
614 		wchar_t *p = wcstr;
615 		/* grab the max number of char columns we can fill */
616 		while(i - wcwidth(*p) > 0) {
617 			i -= wcwidth(*p);
618 			p++;
619 		}
620 		/* then add the ellipsis and fill out any extra padding */
621 		wcscpy(p, L"...");
622 		padwid = i;
623 
624 	}
625 
626 	printf("(%*zu/%*zu) %ls%-*s", digits, current,
627 			digits, howmany, wcstr, padwid, "");
628 
629 	free(wcstr);
630 
631 	/* call refactored fill progress function */
632 	fill_progress(percent, percent, cols - infolen);
633 
634 	if(percent == 100) {
635 		alpm_list_t *i = NULL;
636 		on_progress = 0;
637 		fflush(stdout);
638 		for(i = output; i; i = i->next) {
639 			fputs((const char *)i->data, stderr);
640 		}
641 		fflush(stderr);
642 		FREELIST(output);
643 	} else {
644 		on_progress = 1;
645 	}
646 }
647 
648 /* callback to handle receipt of total download value */
cb_dl_total(off_t total)649 void cb_dl_total(off_t total)
650 {
651 	list_total = total;
652 	/* if we get a 0 value, it means this list has finished downloading,
653 	 * so clear out our list_xfered as well */
654 	if(total == 0) {
655 		list_xfered = 0;
656 	}
657 }
658 
659 /* callback to handle display of download progress */
cb_dl_progress(const char * filename,off_t file_xfered,off_t file_total)660 void cb_dl_progress(const char *filename, off_t file_xfered, off_t file_total)
661 {
662 	static double rate_last;
663 	static off_t xfered_last;
664 	static int64_t initial_time = 0;
665 	int infolen;
666 	int filenamelen;
667 	char *fname, *p;
668 	/* used for wide character width determination and printing */
669 	int len, wclen, wcwid, padwid;
670 	wchar_t *wcfname;
671 
672 	int totaldownload = 0;
673 	off_t xfered, total;
674 	double rate = 0.0;
675 	unsigned int eta_h = 0, eta_m = 0, eta_s = 0;
676 	double rate_human, xfered_human;
677 	const char *rate_label, *xfered_label;
678 	int file_percent = 0, total_percent = 0;
679 
680 	const unsigned short cols = getcols();
681 
682 	/* Nothing has changed since last callback; stop here */
683 	if(file_xfered == 0 && file_total == 0) {
684 		return;
685 	}
686 
687 	if(config->noprogressbar || cols == 0) {
688 		if(file_xfered == 0 && file_total == -1) {
689 			printf(_("downloading %s...\n"), filename);
690 			fflush(stdout);
691 		}
692 		return;
693 	}
694 
695 	infolen = cols * 6 / 10;
696 	if(infolen < 50) {
697 		infolen = 50;
698 	}
699 	/* only use TotalDownload if enabled and we have a callback value */
700 	if(config->totaldownload && list_total) {
701 		/* sanity check */
702 		if(list_xfered + file_total <= list_total) {
703 			totaldownload = 1;
704 		} else {
705 			/* bogus values : don't enable totaldownload and reset */
706 			list_xfered = 0;
707 			list_total = 0;
708 		}
709 	}
710 
711 	if(totaldownload) {
712 		xfered = list_xfered + file_xfered;
713 		total = list_total;
714 	} else {
715 		xfered = file_xfered;
716 		total = file_total;
717 	}
718 
719 	/* this is basically a switch on xfered: 0, total, and
720 	 * anything else */
721 	if(file_xfered == 0 && file_total == -1) {
722 		/* set default starting values, ensure we only call this once
723 		 * if TotalDownload is enabled */
724 		if(!totaldownload || (totaldownload && list_xfered == 0)) {
725 			initial_time = get_time_ms();
726 			xfered_last = (off_t)0;
727 			rate_last = 0.0;
728 			get_update_timediff(1);
729 		}
730 	} else if(xfered > total || xfered < 0) {
731 		/* bogus values : stop here */
732 		return;
733 	} else if(file_xfered == file_total) {
734 		/* compute final values */
735 		int64_t timediff = get_time_ms() - initial_time;
736 		if(timediff > 0) {
737 			rate = (double)xfered / (timediff / 1000.0);
738 			/* round elapsed time (in ms) to the nearest second */
739 			eta_s = (unsigned int)(timediff + 500) / 1000;
740 		} else {
741 			eta_s = 0;
742 		}
743 	} else {
744 		/* compute current average values */
745 		int64_t timediff = get_update_timediff(0);
746 
747 		if(timediff < UPDATE_SPEED_MS) {
748 			/* return if the calling interval was too short */
749 			return;
750 		}
751 		rate = (double)(xfered - xfered_last) / (timediff / 1000.0);
752 		/* average rate to reduce jumpiness */
753 		rate = (rate + 2 * rate_last) / 3;
754 		if(rate > 0.0) {
755 			eta_s = (total - xfered) / rate;
756 		} else {
757 			eta_s = UINT_MAX;
758 		}
759 		rate_last = rate;
760 		xfered_last = xfered;
761 	}
762 
763 	if(file_total) {
764 		file_percent = (file_xfered * 100) / file_total;
765 	} else {
766 		file_percent = 100;
767 	}
768 
769 	if(totaldownload) {
770 		total_percent = ((list_xfered + file_xfered) * 100) /
771 			list_total;
772 
773 		/* if we are at the end, add the completed file to list_xfered */
774 		if(file_xfered == file_total) {
775 			list_xfered += file_total;
776 		}
777 	}
778 
779 	/* fix up time for display */
780 	eta_h = eta_s / 3600;
781 	eta_s -= eta_h * 3600;
782 	eta_m = eta_s / 60;
783 	eta_s -= eta_m * 60;
784 
785 	len = strlen(filename);
786 	fname = malloc(len + 1);
787 	memcpy(fname, filename, len);
788 	/* strip package or DB extension for cleaner look */
789 	if((p = strstr(fname, ".pkg")) || (p = strstr(fname, ".db")) || (p = strstr(fname, ".files"))) {
790 		/* tack on a .sig suffix for signatures */
791 		if(memcmp(&filename[len - 4], ".sig", 4) == 0) {
792 			memcpy(p, ".sig", 4);
793 
794 			/* adjust length for later calculations */
795 			len = p - fname + 4;
796 		} else {
797 			len = p - fname;
798 		}
799 	}
800 	fname[len] = '\0';
801 
802 	/* 1 space + filenamelen + 1 space + 6 for size + 1 space + 3 for label +
803 	 * + 2 spaces + 4 for rate + 1 for label + 2 for /s + 1 space +
804 	 * 8 for eta, gives us the magic 30 */
805 	filenamelen = infolen - 30;
806 	/* see printf() code, we omit 'HH:' in these conditions */
807 	if(eta_h == 0 || eta_h >= 100) {
808 		filenamelen += 3;
809 	}
810 
811 	/* In order to deal with characters from all locales, we have to worry
812 	 * about wide characters and their column widths. A lot of stuff is
813 	 * done here to figure out the actual number of screen columns used
814 	 * by the output, and then pad it accordingly so we fill the terminal.
815 	 */
816 	/* len = filename len + null */
817 	wcfname = calloc(len + 1, sizeof(wchar_t));
818 	wclen = mbstowcs(wcfname, fname, len);
819 	wcwid = wcswidth(wcfname, wclen);
820 	padwid = filenamelen - wcwid;
821 	/* if padwid is < 0, we need to trim the string so padwid = 0 */
822 	if(padwid < 0) {
823 		int i = filenamelen - 3;
824 		wchar_t *wcp = wcfname;
825 		/* grab the max number of char columns we can fill */
826 		while(i > 0 && wcwidth(*wcp) < i) {
827 			i -= wcwidth(*wcp);
828 			wcp++;
829 		}
830 		/* then add the ellipsis and fill out any extra padding */
831 		wcscpy(wcp, L"...");
832 		padwid = i;
833 
834 	}
835 
836 	rate_human = humanize_size((off_t)rate, '\0', -1, &rate_label);
837 	xfered_human = humanize_size(xfered, '\0', -1, &xfered_label);
838 
839 	printf(" %ls%-*s ", wcfname, padwid, "");
840 	/* We will show 1.62M/s, 11.6M/s, but 116K/s and 1116K/s */
841 	if(rate_human < 9.995) {
842 		printf("%6.1f %3s  %4.2f%c/s ",
843 				xfered_human, xfered_label, rate_human, rate_label[0]);
844 	} else if(rate_human < 99.95) {
845 		printf("%6.1f %3s  %4.1f%c/s ",
846 				xfered_human, xfered_label, rate_human, rate_label[0]);
847 	} else {
848 		printf("%6.1f %3s  %4.f%c/s ",
849 				xfered_human, xfered_label, rate_human, rate_label[0]);
850 	}
851 	if(eta_h == 0) {
852 		printf("%02u:%02u", eta_m, eta_s);
853 	} else if(eta_h < 100) {
854 		printf("%02u:%02u:%02u", eta_h, eta_m, eta_s);
855 	} else {
856 		fputs("--:--", stdout);
857 	}
858 
859 	free(fname);
860 	free(wcfname);
861 
862 	if(totaldownload) {
863 		fill_progress(file_percent, total_percent, cols - infolen);
864 	} else {
865 		fill_progress(file_percent, file_percent, cols - infolen);
866 	}
867 	return;
868 }
869 
870 /* Callback to handle notifications from the library */
cb_log(alpm_loglevel_t level,const char * fmt,va_list args)871 void cb_log(alpm_loglevel_t level, const char *fmt, va_list args)
872 {
873 	if(!fmt || strlen(fmt) == 0) {
874 		return;
875 	}
876 
877 	if(on_progress) {
878 		char *string = NULL;
879 		pm_vasprintf(&string, level, fmt, args);
880 		if(string != NULL) {
881 			output = alpm_list_add(output, string);
882 		}
883 	} else {
884 		pm_vfprintf(stderr, level, fmt, args);
885 	}
886 }
887