1 /*
2  *  util.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 <sys/types.h>
22 #include <sys/ioctl.h>
23 #include <sys/stat.h>
24 #include <time.h>
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <stdint.h> /* intmax_t */
30 #include <string.h>
31 #include <errno.h>
32 #include <dirent.h>
33 #include <unistd.h>
34 #include <limits.h>
35 #include <wchar.h>
36 #include <wctype.h>
37 #ifdef HAVE_TERMIOS_H
38 #include <termios.h> /* tcflush */
39 #endif
40 
41 #include <alpm.h>
42 #include <alpm_list.h>
43 
44 /* pacman */
45 #include "util.h"
46 #include "conf.h"
47 #include "callback.h"
48 
49 static int cached_columns = -1;
50 
51 struct table_cell_t {
52 	char *label;
53 	size_t len;
54 	int mode;
55 };
56 
57 enum {
58 	CELL_NORMAL = 0,
59 	CELL_TITLE = (1 << 0),
60 	CELL_RIGHT_ALIGN = (1 << 1),
61 	CELL_FREE = (1 << 3)
62 };
63 
trans_init(int flags,int check_valid)64 int trans_init(int flags, int check_valid)
65 {
66 	int ret;
67 
68 	check_syncdbs(0, check_valid);
69 
70 	ret = alpm_trans_init(config->handle, flags);
71 	if(ret == -1) {
72 		trans_init_error();
73 		return -1;
74 	}
75 	return 0;
76 }
77 
trans_init_error(void)78 void trans_init_error(void)
79 {
80 	alpm_errno_t err = alpm_errno(config->handle);
81 	pm_printf(ALPM_LOG_ERROR, _("failed to init transaction (%s)\n"),
82 			alpm_strerror(err));
83 	if(err == ALPM_ERR_HANDLE_LOCK) {
84 		const char *lockfile = alpm_option_get_lockfile(config->handle);
85 		pm_printf(ALPM_LOG_ERROR, _("could not lock database: %s\n"),
86 					strerror(errno));
87 		if(access(lockfile, F_OK) == 0) {
88 			fprintf(stderr, _("  if you're sure a package manager is not already\n"
89 						"  running, you can remove %s\n"), lockfile);
90 		}
91 	}
92 }
93 
trans_release(void)94 int trans_release(void)
95 {
96 	if(alpm_trans_release(config->handle) == -1) {
97 		pm_printf(ALPM_LOG_ERROR, _("failed to release transaction (%s)\n"),
98 				alpm_strerror(alpm_errno(config->handle)));
99 		return -1;
100 	}
101 	return 0;
102 }
103 
needs_root(void)104 int needs_root(void)
105 {
106 	if(config->sysroot) {
107 		return 1;
108 	}
109 	switch(config->op) {
110 		case PM_OP_DATABASE:
111 			return !config->op_q_check;
112 		case PM_OP_UPGRADE:
113 		case PM_OP_REMOVE:
114 			return !config->print;
115 		case PM_OP_SYNC:
116 			return (config->op_s_clean || config->op_s_sync ||
117 					(!config->group && !config->op_s_info && !config->op_q_list &&
118 					 !config->op_s_search && !config->print));
119 		case PM_OP_FILES:
120 			return config->op_s_sync;
121 		default:
122 			return 0;
123 	}
124 }
125 
check_syncdbs(size_t need_repos,int check_valid)126 int check_syncdbs(size_t need_repos, int check_valid)
127 {
128 	int ret = 0;
129 	alpm_list_t *i;
130 	alpm_list_t *sync_dbs = alpm_get_syncdbs(config->handle);
131 
132 	if(need_repos && sync_dbs == NULL) {
133 		pm_printf(ALPM_LOG_ERROR, _("no usable package repositories configured.\n"));
134 		return 1;
135 	}
136 
137 	if(check_valid) {
138 		/* ensure all known dbs are valid */
139 		for(i = sync_dbs; i; i = alpm_list_next(i)) {
140 			alpm_db_t *db = i->data;
141 			if(alpm_db_get_valid(db)) {
142 				pm_printf(ALPM_LOG_ERROR, _("database '%s' is not valid (%s)\n"),
143 						alpm_db_get_name(db), alpm_strerror(alpm_errno(config->handle)));
144 				ret = 1;
145 			}
146 		}
147 	}
148 	return ret;
149 }
150 
sync_syncdbs(int level,alpm_list_t * syncs)151 int sync_syncdbs(int level, alpm_list_t *syncs)
152 {
153 	alpm_list_t *i;
154 	unsigned int success = 1;
155 
156 	for(i = syncs; i; i = alpm_list_next(i)) {
157 		alpm_db_t *db = i->data;
158 
159 		int ret = alpm_db_update((level < 2 ? 0 : 1), db);
160 		if(ret < 0) {
161 			pm_printf(ALPM_LOG_ERROR, _("failed to update %s (%s)\n"),
162 					alpm_db_get_name(db), alpm_strerror(alpm_errno(config->handle)));
163 			success = 0;
164 		} else if(ret == 1) {
165 			printf(_(" %s is up to date\n"), alpm_db_get_name(db));
166 		}
167 	}
168 
169 	if(!success) {
170 		pm_printf(ALPM_LOG_ERROR, _("failed to synchronize all databases\n"));
171 	}
172 	return (success > 0);
173 }
174 
175 /* discard unhandled input on the terminal's input buffer */
flush_term_input(int fd)176 static int flush_term_input(int fd)
177 {
178 #ifdef HAVE_TCFLUSH
179 	if(isatty(fd)) {
180 		return tcflush(fd, TCIFLUSH);
181 	}
182 #endif
183 
184 	/* fail silently */
185 	return 0;
186 }
187 
columns_cache_reset(void)188 void columns_cache_reset(void)
189 {
190 	cached_columns = -1;
191 }
192 
getcols_fd(int fd)193 static int getcols_fd(int fd)
194 {
195 	int width = -1;
196 
197 	if(!isatty(fd)) {
198 		return 0;
199 	}
200 
201 #if defined(TIOCGSIZE)
202 	struct ttysize win;
203 	if(ioctl(fd, TIOCGSIZE, &win) == 0) {
204 		width = win.ts_cols;
205 	}
206 #elif defined(TIOCGWINSZ)
207 	struct winsize win;
208 	if(ioctl(fd, TIOCGWINSZ, &win) == 0) {
209 		width = win.ws_col;
210 	}
211 #endif
212 
213 	if(width <= 0) {
214 		return -EIO;
215 	}
216 
217 	return width;
218 }
219 
getcols(void)220 unsigned short getcols(void)
221 {
222 	const char *e;
223 	int c = -1;
224 
225 	if(cached_columns >= 0) {
226 		return cached_columns;
227 	}
228 
229 	e = getenv("COLUMNS");
230 	if(e && *e) {
231 		char *p = NULL;
232 		c = strtol(e, &p, 10);
233 		if(*p != '\0') {
234 			c= -1;
235 		}
236 	}
237 
238 	if(c < 0) {
239 		c = getcols_fd(STDOUT_FILENO);
240 	}
241 
242 	if(c < 0) {
243 		c = 80;
244 	}
245 
246 	cached_columns = c;
247 	return c;
248 }
249 
250 /* does the same thing as 'rm -rf' */
rmrf(const char * path)251 int rmrf(const char *path)
252 {
253 	int errflag = 0;
254 	struct dirent *dp;
255 	DIR *dirp;
256 
257 	if(!unlink(path)) {
258 		return 0;
259 	} else {
260 		switch(errno) {
261 		case ENOENT:
262 			return 0;
263 		case EPERM:
264 		case EISDIR:
265 			break;
266 		default:
267 			/* not a directory */
268 			return 1;
269 		}
270 
271 		dirp = opendir(path);
272 		if(!dirp) {
273 			return 1;
274 		}
275 		for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
276 			if(strcmp(dp->d_name, "..") != 0 && strcmp(dp->d_name, ".") != 0) {
277 				char name[PATH_MAX];
278 				snprintf(name, PATH_MAX, "%s/%s", path, dp->d_name);
279 				errflag += rmrf(name);
280 			}
281 		}
282 		closedir(dirp);
283 		if(rmdir(path)) {
284 			errflag++;
285 		}
286 		return errflag;
287 	}
288 }
289 
290 /* output a string, but wrap words properly with a specified indentation
291  */
indentprint(const char * str,unsigned short indent,unsigned short cols)292 void indentprint(const char *str, unsigned short indent, unsigned short cols)
293 {
294 	wchar_t *wcstr;
295 	const wchar_t *p;
296 	size_t len, cidx;
297 
298 	if(!str) {
299 		return;
300 	}
301 
302 	/* if we're not a tty, or our tty is not wide enough that wrapping even makes
303 	 * sense, print without indenting */
304 	if(cols == 0 || indent > cols) {
305 		fputs(str, stdout);
306 		return;
307 	}
308 
309 	len = strlen(str) + 1;
310 	wcstr = calloc(len, sizeof(wchar_t));
311 	len = mbstowcs(wcstr, str, len);
312 	p = wcstr;
313 	cidx = indent;
314 
315 	if(!p || !len) {
316 		free(wcstr);
317 		return;
318 	}
319 
320 	while(*p) {
321 		if(*p == L' ') {
322 			const wchar_t *q, *next;
323 			p++;
324 			if(p == NULL || *p == L' ') continue;
325 			next = wcschr(p, L' ');
326 			if(next == NULL) {
327 				next = p + wcslen(p);
328 			}
329 			/* len captures # cols */
330 			len = 0;
331 			q = p;
332 			while(q < next) {
333 				len += wcwidth(*q++);
334 			}
335 			if((len + 1) > (cols - cidx)) {
336 				/* wrap to a newline and reindent */
337 				printf("\n%-*s", (int)indent, "");
338 				cidx = indent;
339 			} else {
340 				printf(" ");
341 				cidx++;
342 			}
343 			continue;
344 		}
345 		printf("%lc", (wint_t)*p);
346 		cidx += wcwidth(*p);
347 		p++;
348 	}
349 	free(wcstr);
350 }
351 
352 /* Replace all occurrences of 'needle' with 'replace' in 'str', returning
353  * a new string (must be free'd) */
strreplace(const char * str,const char * needle,const char * replace)354 char *strreplace(const char *str, const char *needle, const char *replace)
355 {
356 	const char *p = NULL, *q = NULL;
357 	char *newstr = NULL, *newp = NULL;
358 	alpm_list_t *i = NULL, *list = NULL;
359 	size_t needlesz = strlen(needle), replacesz = strlen(replace);
360 	size_t newsz;
361 
362 	if(!str) {
363 		return NULL;
364 	}
365 
366 	p = str;
367 	q = strstr(p, needle);
368 	while(q) {
369 		list = alpm_list_add(list, (char *)q);
370 		p = q + needlesz;
371 		q = strstr(p, needle);
372 	}
373 
374 	/* no occurrences of needle found */
375 	if(!list) {
376 		return strdup(str);
377 	}
378 	/* size of new string = size of old string + "number of occurrences of needle"
379 	 * x "size difference between replace and needle" */
380 	newsz = strlen(str) + 1 +
381 		alpm_list_count(list) * (replacesz - needlesz);
382 	newstr = calloc(newsz, sizeof(char));
383 	if(!newstr) {
384 		return NULL;
385 	}
386 
387 	p = str;
388 	newp = newstr;
389 	for(i = list; i; i = alpm_list_next(i)) {
390 		q = i->data;
391 		if(q > p) {
392 			/* add chars between this occurrence and last occurrence, if any */
393 			memcpy(newp, p, (size_t)(q - p));
394 			newp += q - p;
395 		}
396 		memcpy(newp, replace, replacesz);
397 		newp += replacesz;
398 		p = q + needlesz;
399 	}
400 	alpm_list_free(list);
401 
402 	if(*p) {
403 		/* add the rest of 'p' */
404 		strcpy(newp, p);
405 	}
406 
407 	return newstr;
408 }
409 
string_length(const char * s)410 static size_t string_length(const char *s)
411 {
412 	int len;
413 	wchar_t *wcstr;
414 
415 	if(!s || s[0] == '\0') {
416 		return 0;
417 	}
418 	/* len goes from # bytes -> # chars -> # cols */
419 	len = strlen(s) + 1;
420 	wcstr = calloc(len, sizeof(wchar_t));
421 	len = mbstowcs(wcstr, s, len);
422 	len = wcswidth(wcstr, len);
423 	free(wcstr);
424 
425 	return len;
426 }
427 
add_table_cell(alpm_list_t ** row,char * label,int mode)428 static void add_table_cell(alpm_list_t **row, char *label, int mode)
429 {
430 	struct table_cell_t *cell = malloc(sizeof(struct table_cell_t));
431 
432 	cell->label = label;
433 	cell->mode = mode;
434 	cell->len = string_length(label);
435 
436 	*row = alpm_list_add(*row, cell);
437 }
438 
table_free_cell(void * ptr)439 static void table_free_cell(void *ptr)
440 {
441 	struct table_cell_t *cell = ptr;
442 
443 	if(cell) {
444 		if(cell->mode & CELL_FREE) {
445 			free(cell->label);
446 		}
447 		free(cell);
448 	}
449 }
450 
table_free(alpm_list_t * headers,alpm_list_t * rows)451 static void table_free(alpm_list_t *headers, alpm_list_t *rows)
452 {
453 	alpm_list_t *i;
454 
455 	alpm_list_free_inner(headers, table_free_cell);
456 
457 	for(i = rows; i; i = alpm_list_next(i)) {
458 		alpm_list_free_inner(i->data, table_free_cell);
459 		alpm_list_free(i->data);
460 	}
461 
462 	alpm_list_free(headers);
463 	alpm_list_free(rows);
464 }
465 
add_transaction_sizes_row(alpm_list_t ** rows,char * label,off_t size)466 static void add_transaction_sizes_row(alpm_list_t **rows, char *label, off_t size)
467 {
468 	alpm_list_t *row = NULL;
469 	char *str;
470 	const char *units;
471 	double s = humanize_size(size, 'M', 2, &units);
472 	pm_asprintf(&str, "%.2f %s", s, units);
473 
474 	add_table_cell(&row, label, CELL_TITLE);
475 	add_table_cell(&row, str, CELL_RIGHT_ALIGN | CELL_FREE);
476 
477 	*rows = alpm_list_add(*rows, row);
478 }
479 
string_display(const char * title,const char * string,unsigned short cols)480 void string_display(const char *title, const char *string, unsigned short cols)
481 {
482 	if(title) {
483 		printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor);
484 	}
485 	if(string == NULL || string[0] == '\0') {
486 		printf(_("None"));
487 	} else {
488 		/* compute the length of title + a space */
489 		size_t len = string_length(title) + 1;
490 		indentprint(string, (unsigned short)len, cols);
491 	}
492 	printf("\n");
493 }
494 
table_print_line(const alpm_list_t * line,short col_padding,size_t colcount,size_t * widths,int * has_data)495 static void table_print_line(const alpm_list_t *line, short col_padding,
496 		size_t colcount, size_t *widths, int *has_data)
497 {
498 	size_t i;
499 	int need_padding = 0;
500 	const alpm_list_t *curcell;
501 
502 	for(i = 0, curcell = line; curcell && i < colcount;
503 			i++, curcell = alpm_list_next(curcell)) {
504 		const struct table_cell_t *cell = curcell->data;
505 		const char *str = (cell->label ? cell->label : "");
506 		int cell_width;
507 
508 		if(!has_data[i]) {
509 			continue;
510 		}
511 
512 		cell_width = (cell->mode & CELL_RIGHT_ALIGN ? (int)widths[i] : -(int)widths[i]);
513 
514 		if(need_padding) {
515 			printf("%*s", col_padding, "");
516 		}
517 
518 		if(cell->mode & CELL_TITLE) {
519 			printf("%s%*s%s", config->colstr.title, cell_width, str, config->colstr.nocolor);
520 		} else {
521 			printf("%*s", cell_width, str);
522 		}
523 		need_padding = 1;
524 	}
525 
526 	printf("\n");
527 }
528 
529 
530 /**
531  * Find the max string width of each column. Also determines whether values
532  * exist in the column and sets the value in has_data accordingly.
533  * @param header a list of header strings
534  * @param rows a list of lists of rows as strings
535  * @param padding the amount of padding between columns
536  * @param totalcols the total number of columns in the header and each row
537  * @param widths a pointer to store width data
538  * @param has_data a pointer to store whether column has data
539  *
540  * @return the total width of the table; 0 on failure
541  */
table_calc_widths(const alpm_list_t * header,const alpm_list_t * rows,short padding,size_t totalcols,size_t ** widths,int ** has_data)542 static size_t table_calc_widths(const alpm_list_t *header,
543 		const alpm_list_t *rows, short padding, size_t totalcols,
544 		size_t **widths, int **has_data)
545 {
546 	const alpm_list_t *i;
547 	size_t curcol, totalwidth = 0, usefulcols = 0;
548 	size_t *colwidths;
549 	int *coldata;
550 
551 	if(totalcols <= 0) {
552 		return 0;
553 	}
554 
555 	colwidths = malloc(totalcols * sizeof(size_t));
556 	coldata = calloc(totalcols, sizeof(int));
557 	if(!colwidths || !coldata) {
558 		free(colwidths);
559 		free(coldata);
560 		return 0;
561 	}
562 	/* header determines column count and initial values of longest_strs */
563 	for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) {
564 		const struct table_cell_t *row = i->data;
565 		colwidths[curcol] = row->len;
566 		/* note: header does not determine whether column has data */
567 	}
568 
569 	/* now find the longest string in each column */
570 	for(i = rows; i; i = alpm_list_next(i)) {
571 		/* grab first column of each row and iterate through columns */
572 		const alpm_list_t *j = i->data;
573 		for(curcol = 0; j; j = alpm_list_next(j), curcol++) {
574 			const struct table_cell_t *cell = j->data;
575 			size_t str_len = cell ? cell->len : 0;
576 
577 			if(str_len > colwidths[curcol]) {
578 				colwidths[curcol] = str_len;
579 			}
580 			if(str_len > 0) {
581 				coldata[curcol] = 1;
582 			}
583 		}
584 	}
585 
586 	for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) {
587 		/* only include columns that have data */
588 		if(coldata[curcol]) {
589 			usefulcols++;
590 			totalwidth += colwidths[curcol];
591 		}
592 	}
593 
594 	/* add padding between columns */
595 	if(usefulcols > 0) {
596 		totalwidth += padding * (usefulcols - 1);
597 	}
598 
599 	*widths = colwidths;
600 	*has_data = coldata;
601 	return totalwidth;
602 }
603 
604 /** Displays the list in table format
605  *
606  * @param header the column headers. column count is determined by the nr
607  *               of headers
608  * @param rows the rows to display as a list of lists of strings. the outer
609  *             list represents the rows, the inner list the cells (= columns)
610  * @param cols the number of columns available in the terminal
611  * @return -1 if not enough terminal cols available, else 0
612  */
table_display(const alpm_list_t * header,const alpm_list_t * rows,unsigned short cols)613 static int table_display(const alpm_list_t *header,
614 		const alpm_list_t *rows, unsigned short cols)
615 {
616 	const unsigned short padding = 2;
617 	const alpm_list_t *i, *first;
618 	size_t *widths = NULL, totalcols, totalwidth;
619 	int *has_data = NULL;
620 	int ret = 0;
621 
622 	if(rows == NULL) {
623 		return ret;
624 	}
625 
626 	/* we want the first row. if no headers are provided, use the first
627 	 * entry of the rows array. */
628 	first = header ? header : rows->data;
629 
630 	totalcols = alpm_list_count(first);
631 	totalwidth = table_calc_widths(first, rows, padding, totalcols,
632 			&widths, &has_data);
633 	/* return -1 if terminal is not wide enough */
634 	if(cols && totalwidth > cols) {
635 		pm_printf(ALPM_LOG_WARNING,
636 				_("insufficient columns available for table display\n"));
637 		ret = -1;
638 		goto cleanup;
639 	}
640 	if(!totalwidth || !widths || !has_data) {
641 		ret = -1;
642 		goto cleanup;
643 	}
644 
645 	if(header) {
646 		table_print_line(header, padding, totalcols, widths, has_data);
647 		printf("\n");
648 	}
649 
650 	for(i = rows; i; i = alpm_list_next(i)) {
651 		table_print_line(i->data, padding, totalcols, widths, has_data);
652 	}
653 
654 cleanup:
655 	free(widths);
656 	free(has_data);
657 	return ret;
658 }
659 
list_display(const char * title,const alpm_list_t * list,unsigned short maxcols)660 void list_display(const char *title, const alpm_list_t *list,
661 		unsigned short maxcols)
662 {
663 	const alpm_list_t *i;
664 	size_t len = 0;
665 
666 	if(title) {
667 		len = string_length(title) + 1;
668 		printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor);
669 	}
670 
671 	if(!list) {
672 		printf("%s\n", _("None"));
673 	} else {
674 		size_t cols = len;
675 		const char *str = list->data;
676 		fputs(str, stdout);
677 		cols += string_length(str);
678 		for(i = alpm_list_next(list); i; i = alpm_list_next(i)) {
679 			str = i->data;
680 			size_t s = string_length(str);
681 			/* wrap only if we have enough usable column space */
682 			if(maxcols > len && cols + s + 2 >= maxcols) {
683 				size_t j;
684 				cols = len;
685 				printf("\n");
686 				for(j = 1; j <= len; j++) {
687 					printf(" ");
688 				}
689 			} else if(cols != len) {
690 				/* 2 spaces are added if this is not the first element on a line. */
691 				printf("  ");
692 				cols += 2;
693 			}
694 			fputs(str, stdout);
695 			cols += s;
696 		}
697 		putchar('\n');
698 	}
699 }
700 
list_display_linebreak(const char * title,const alpm_list_t * list,unsigned short maxcols)701 void list_display_linebreak(const char *title, const alpm_list_t *list,
702 		unsigned short maxcols)
703 {
704 	unsigned short len = 0;
705 
706 	if(title) {
707 		len = (unsigned short)string_length(title) + 1;
708 		printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor);
709 	}
710 
711 	if(!list) {
712 		printf("%s\n", _("None"));
713 	} else {
714 		const alpm_list_t *i;
715 		/* Print the first element */
716 		indentprint((const char *)list->data, len, maxcols);
717 		printf("\n");
718 		/* Print the rest */
719 		for(i = alpm_list_next(list); i; i = alpm_list_next(i)) {
720 			size_t j;
721 			for(j = 1; j <= len; j++) {
722 				printf(" ");
723 			}
724 			indentprint((const char *)i->data, len, maxcols);
725 			printf("\n");
726 		}
727 	}
728 }
729 
signature_display(const char * title,alpm_siglist_t * siglist,unsigned short maxcols)730 void signature_display(const char *title, alpm_siglist_t *siglist,
731 		unsigned short maxcols)
732 {
733 	unsigned short len = 0;
734 
735 	if(title) {
736 		len = (unsigned short)string_length(title) + 1;
737 		printf("%s%s%s ", config->colstr.title, title, config->colstr.nocolor);
738 	}
739 	if(siglist->count == 0) {
740 		printf(_("None"));
741 	} else {
742 		size_t i;
743 		for(i = 0; i < siglist->count; i++) {
744 			char *sigline;
745 			const char *status, *validity, *name;
746 			int ret;
747 			alpm_sigresult_t *result = siglist->results + i;
748 			/* Don't re-indent the first result */
749 			if(i != 0) {
750 				size_t j;
751 				for(j = 1; j <= len; j++) {
752 					printf(" ");
753 				}
754 			}
755 			switch(result->status) {
756 				case ALPM_SIGSTATUS_VALID:
757 					status = _("Valid");
758 					break;
759 				case ALPM_SIGSTATUS_KEY_EXPIRED:
760 					status = _("Key expired");
761 					break;
762 				case ALPM_SIGSTATUS_SIG_EXPIRED:
763 					status = _("Expired");
764 					break;
765 				case ALPM_SIGSTATUS_INVALID:
766 					status = _("Invalid");
767 					break;
768 				case ALPM_SIGSTATUS_KEY_UNKNOWN:
769 					status = _("Key unknown");
770 					break;
771 				case ALPM_SIGSTATUS_KEY_DISABLED:
772 					status = _("Key disabled");
773 					break;
774 				default:
775 					status = _("Signature error");
776 					break;
777 			}
778 			switch(result->validity) {
779 				case ALPM_SIGVALIDITY_FULL:
780 					validity = _("full trust");
781 					break;
782 				case ALPM_SIGVALIDITY_MARGINAL:
783 					validity = _("marginal trust");
784 					break;
785 				case ALPM_SIGVALIDITY_NEVER:
786 					validity = _("never trust");
787 					break;
788 				case ALPM_SIGVALIDITY_UNKNOWN:
789 				default:
790 					validity = _("unknown trust");
791 					break;
792 			}
793 			name = result->key.uid ? result->key.uid : result->key.fingerprint;
794 			ret = pm_asprintf(&sigline, _("%s, %s from \"%s\""),
795 					status, validity, name);
796 			if(ret == -1) {
797 				continue;
798 			}
799 			indentprint(sigline, len, maxcols);
800 			printf("\n");
801 			free(sigline);
802 		}
803 	}
804 }
805 
806 /* creates a header row for use with table_display */
create_verbose_header(size_t count)807 static alpm_list_t *create_verbose_header(size_t count)
808 {
809 	alpm_list_t *ret = NULL;
810 
811 	char *header;
812 	pm_asprintf(&header, "%s (%zu)", _("Package"), count);
813 
814 	add_table_cell(&ret, header, CELL_TITLE | CELL_FREE);
815 	add_table_cell(&ret, _("Old Version"), CELL_TITLE);
816 	add_table_cell(&ret, _("New Version"), CELL_TITLE);
817 	add_table_cell(&ret, _("Net Change"), CELL_TITLE);
818 	add_table_cell(&ret, _("Download Size"), CELL_TITLE);
819 
820 	return ret;
821 }
822 
823 /* returns package info as list of strings */
create_verbose_row(pm_target_t * target)824 static alpm_list_t *create_verbose_row(pm_target_t *target)
825 {
826 	char *str;
827 	off_t size = 0;
828 	double human_size;
829 	const char *label;
830 	alpm_list_t *ret = NULL;
831 
832 	/* a row consists of the package name, */
833 	if(target->install) {
834 		const alpm_db_t *db = alpm_pkg_get_db(target->install);
835 		if(db) {
836 			pm_asprintf(&str, "%s/%s", alpm_db_get_name(db), alpm_pkg_get_name(target->install));
837 		} else {
838 			pm_asprintf(&str, "%s", alpm_pkg_get_name(target->install));
839 		}
840 	} else {
841 		pm_asprintf(&str, "%s", alpm_pkg_get_name(target->remove));
842 	}
843 	add_table_cell(&ret, str, CELL_NORMAL | CELL_FREE);
844 
845 	/* old and new versions */
846 	pm_asprintf(&str, "%s",
847 			target->remove != NULL ? alpm_pkg_get_version(target->remove) : "");
848 	add_table_cell(&ret, str, CELL_NORMAL | CELL_FREE);
849 
850 	pm_asprintf(&str, "%s",
851 			target->install != NULL ? alpm_pkg_get_version(target->install) : "");
852 	add_table_cell(&ret, str, CELL_NORMAL | CELL_FREE);
853 
854 	/* and size */
855 	size -= target->remove ? alpm_pkg_get_isize(target->remove) : 0;
856 	size += target->install ? alpm_pkg_get_isize(target->install) : 0;
857 	human_size = humanize_size(size, 'M', 2, &label);
858 	pm_asprintf(&str, "%.2f %s", human_size, label);
859 	add_table_cell(&ret, str, CELL_RIGHT_ALIGN | CELL_FREE);
860 
861 	size = target->install ? alpm_pkg_download_size(target->install) : 0;
862 	if(size != 0) {
863 		human_size = humanize_size(size, 'M', 2, &label);
864 		pm_asprintf(&str, "%.2f %s", human_size, label);
865 	} else {
866 		str = NULL;
867 	}
868 	add_table_cell(&ret, str, CELL_RIGHT_ALIGN | CELL_FREE);
869 
870 	return ret;
871 }
872 
873 /* prepare a list of pkgs to display */
_display_targets(alpm_list_t * targets,int verbose)874 static void _display_targets(alpm_list_t *targets, int verbose)
875 {
876 	char *str;
877 	off_t isize = 0, rsize = 0, dlsize = 0;
878 	unsigned short cols;
879 	alpm_list_t *i, *names = NULL, *header = NULL, *rows = NULL;
880 
881 	if(!targets) {
882 		return;
883 	}
884 
885 	/* gather package info */
886 	for(i = targets; i; i = alpm_list_next(i)) {
887 		pm_target_t *target = i->data;
888 
889 		if(target->install) {
890 			dlsize += alpm_pkg_download_size(target->install);
891 			isize += alpm_pkg_get_isize(target->install);
892 		}
893 		if(target->remove) {
894 			/* add up size of all removed packages */
895 			rsize += alpm_pkg_get_isize(target->remove);
896 		}
897 	}
898 
899 	/* form data for both verbose and non-verbose display */
900 	for(i = targets; i; i = alpm_list_next(i)) {
901 		pm_target_t *target = i->data;
902 
903 		if(verbose) {
904 			rows = alpm_list_add(rows, create_verbose_row(target));
905 		}
906 
907 		if(target->install) {
908 			pm_asprintf(&str, "%s-%s", alpm_pkg_get_name(target->install),
909 					alpm_pkg_get_version(target->install));
910 		} else if(isize == 0) {
911 			pm_asprintf(&str, "%s-%s", alpm_pkg_get_name(target->remove),
912 					alpm_pkg_get_version(target->remove));
913 		} else {
914 			pm_asprintf(&str, "%s-%s [%s]", alpm_pkg_get_name(target->remove),
915 					alpm_pkg_get_version(target->remove), _("removal"));
916 		}
917 		names = alpm_list_add(names, str);
918 	}
919 
920 	/* print to screen */
921 	pm_asprintf(&str, "%s (%zu)", _("Packages"), alpm_list_count(targets));
922 	printf("\n");
923 
924 	cols = getcols();
925 	if(verbose) {
926 		header = create_verbose_header(alpm_list_count(targets));
927 		if(table_display(header, rows, cols) != 0) {
928 			/* fallback to list display if table wouldn't fit */
929 			list_display(str, names, cols);
930 		}
931 	} else {
932 		list_display(str, names, cols);
933 	}
934 	printf("\n");
935 
936 	table_free(header, rows);
937 	FREELIST(names);
938 	free(str);
939 	rows = NULL;
940 
941 	if(dlsize > 0 || config->op_s_downloadonly) {
942 		add_transaction_sizes_row(&rows, _("Total Download Size:"), dlsize);
943 	}
944 	if(!config->op_s_downloadonly) {
945 		if(isize > 0) {
946 			add_transaction_sizes_row(&rows, _("Total Installed Size:"), isize);
947 		}
948 		if(rsize > 0 && isize == 0) {
949 			add_transaction_sizes_row(&rows, _("Total Removed Size:"), rsize);
950 		}
951 		/* only show this net value if different from raw installed size */
952 		if(isize > 0 && rsize > 0) {
953 			add_transaction_sizes_row(&rows, _("Net Upgrade Size:"), isize - rsize);
954 		}
955 	}
956 	table_display(NULL, rows, cols);
957 	table_free(NULL, rows);
958 }
959 
target_cmp(const void * p1,const void * p2)960 static int target_cmp(const void *p1, const void *p2)
961 {
962 	const pm_target_t *targ1 = p1;
963 	const pm_target_t *targ2 = p2;
964 	/* explicit are always sorted after implicit (e.g. deps, pulled targets) */
965 	if(targ1->is_explicit != targ2->is_explicit) {
966 		return targ1->is_explicit > targ2->is_explicit;
967 	}
968 	const char *name1 = targ1->install ?
969 		alpm_pkg_get_name(targ1->install) : alpm_pkg_get_name(targ1->remove);
970 	const char *name2 = targ2->install ?
971 		alpm_pkg_get_name(targ2->install) : alpm_pkg_get_name(targ2->remove);
972 	return strcmp(name1, name2);
973 }
974 
pkg_cmp(const void * p1,const void * p2)975 static int pkg_cmp(const void *p1, const void *p2)
976 {
977 	/* explicit cast due to (un)necessary removal of const */
978 	alpm_pkg_t *pkg1 = (alpm_pkg_t *)p1;
979 	alpm_pkg_t *pkg2 = (alpm_pkg_t *)p2;
980 	return strcmp(alpm_pkg_get_name(pkg1), alpm_pkg_get_name(pkg2));
981 }
982 
display_targets(void)983 void display_targets(void)
984 {
985 	alpm_list_t *i, *targets = NULL;
986 	alpm_db_t *db_local = alpm_get_localdb(config->handle);
987 
988 	for(i = alpm_trans_get_add(config->handle); i; i = alpm_list_next(i)) {
989 		alpm_pkg_t *pkg = i->data;
990 		pm_target_t *targ = calloc(1, sizeof(pm_target_t));
991 		if(!targ) return;
992 		targ->install = pkg;
993 		targ->remove = alpm_db_get_pkg(db_local, alpm_pkg_get_name(pkg));
994 		if(alpm_list_find(config->explicit_adds, pkg, pkg_cmp)) {
995 			targ->is_explicit = 1;
996 		}
997 		targets = alpm_list_add(targets, targ);
998 	}
999 	for(i = alpm_trans_get_remove(config->handle); i; i = alpm_list_next(i)) {
1000 		alpm_pkg_t *pkg = i->data;
1001 		pm_target_t *targ = calloc(1, sizeof(pm_target_t));
1002 		if(!targ) return;
1003 		targ->remove = pkg;
1004 		if(alpm_list_find(config->explicit_removes, pkg, pkg_cmp)) {
1005 			targ->is_explicit = 1;
1006 		}
1007 		targets = alpm_list_add(targets, targ);
1008 	}
1009 
1010 	targets = alpm_list_msort(targets, alpm_list_count(targets), target_cmp);
1011 	_display_targets(targets, config->verbosepkglists);
1012 	FREELIST(targets);
1013 }
1014 
pkg_get_size(alpm_pkg_t * pkg)1015 static off_t pkg_get_size(alpm_pkg_t *pkg)
1016 {
1017 	switch(config->op) {
1018 		case PM_OP_SYNC:
1019 			return alpm_pkg_download_size(pkg);
1020 		case PM_OP_UPGRADE:
1021 			return alpm_pkg_get_size(pkg);
1022 		default:
1023 			return alpm_pkg_get_isize(pkg);
1024 	}
1025 }
1026 
pkg_get_location(alpm_pkg_t * pkg)1027 static char *pkg_get_location(alpm_pkg_t *pkg)
1028 {
1029 	alpm_list_t *servers;
1030 	char *string = NULL;
1031 	switch(alpm_pkg_get_origin(pkg)) {
1032 		case ALPM_PKG_FROM_SYNCDB:
1033 			if(alpm_pkg_download_size(pkg) == 0) {
1034 				/* file is already in the package cache */
1035 				alpm_list_t *i;
1036 				const char *pkgfile = alpm_pkg_get_filename(pkg);
1037 				char path[PATH_MAX];
1038 				struct stat buf;
1039 
1040 				for(i = alpm_option_get_cachedirs(config->handle); i; i = i->next) {
1041 					snprintf(path, PATH_MAX, "%s%s", (char *)i->data, pkgfile);
1042 					if(stat(path, &buf) == 0 && S_ISREG(buf.st_mode)) {
1043 						pm_asprintf(&string, "file://%s", path);
1044 						return string;
1045 					}
1046 				}
1047 			}
1048 
1049 			servers = alpm_db_get_servers(alpm_pkg_get_db(pkg));
1050 			if(servers) {
1051 				pm_asprintf(&string, "%s/%s", (char *)(servers->data),
1052 						alpm_pkg_get_filename(pkg));
1053 				return string;
1054 			}
1055 
1056 			/* fallthrough - for theoretical serverless repos */
1057 
1058 		case ALPM_PKG_FROM_FILE:
1059 			return strdup(alpm_pkg_get_filename(pkg));
1060 
1061 		case ALPM_PKG_FROM_LOCALDB:
1062 		default:
1063 			pm_asprintf(&string, "%s-%s", alpm_pkg_get_name(pkg), alpm_pkg_get_version(pkg));
1064 			return string;
1065 	}
1066 }
1067 
1068 /* a pow() implementation that is specialized for an integer base and small,
1069  * positive-only integer exponents. */
simple_pow(int base,int exp)1070 static double simple_pow(int base, int exp)
1071 {
1072 	double result = 1.0;
1073 	for(; exp > 0; exp--) {
1074 		result *= base;
1075 	}
1076 	return result;
1077 }
1078 
1079 /** Converts sizes in bytes into human readable units.
1080  *
1081  * @param bytes the size in bytes
1082  * @param target_unit '\0' or a short label. If equal to one of the short unit
1083  * labels ('B', 'K', ...) bytes is converted to target_unit; if '\0', the first
1084  * unit which will bring the value to below a threshold of 2048 will be chosen.
1085  * @param precision number of decimal places, ensures -0.00 gets rounded to
1086  * 0.00; -1 if no rounding desired
1087  * @param label will be set to the appropriate unit label
1088  *
1089  * @return the size in the appropriate unit
1090  */
humanize_size(off_t bytes,const char target_unit,int precision,const char ** label)1091 double humanize_size(off_t bytes, const char target_unit, int precision,
1092 		const char **label)
1093 {
1094 	static const char *labels[] = {"B", "KiB", "MiB", "GiB",
1095 		"TiB", "PiB", "EiB", "ZiB", "YiB"};
1096 	static const int unitcount = ARRAYSIZE(labels);
1097 
1098 	double val = (double)bytes;
1099 	int index;
1100 
1101 	for(index = 0; index < unitcount - 1; index++) {
1102 		if(target_unit != '\0' && labels[index][0] == target_unit) {
1103 			break;
1104 		} else if(target_unit == '\0' && val <= 2048.0 && val >= -2048.0) {
1105 			break;
1106 		}
1107 		val /= 1024.0;
1108 	}
1109 
1110 	if(label) {
1111 		*label = labels[index];
1112 	}
1113 
1114 	/* do not display negative zeroes */
1115 	if(precision >= 0 && val < 0.0 &&
1116 			val > (-0.5 / simple_pow(10, precision))) {
1117 		val = 0.0;
1118 	}
1119 
1120 	return val;
1121 }
1122 
print_packages(const alpm_list_t * packages)1123 void print_packages(const alpm_list_t *packages)
1124 {
1125 	const alpm_list_t *i;
1126 	if(!config->print_format) {
1127 		config->print_format = strdup("%l");
1128 	}
1129 	for(i = packages; i; i = alpm_list_next(i)) {
1130 		alpm_pkg_t *pkg = i->data;
1131 		char *string = strdup(config->print_format);
1132 		char *temp = string;
1133 		/* %n : pkgname */
1134 		if(strstr(temp, "%n")) {
1135 			string = strreplace(temp, "%n", alpm_pkg_get_name(pkg));
1136 			free(temp);
1137 			temp = string;
1138 		}
1139 		/* %v : pkgver */
1140 		if(strstr(temp, "%v")) {
1141 			string = strreplace(temp, "%v", alpm_pkg_get_version(pkg));
1142 			free(temp);
1143 			temp = string;
1144 		}
1145 		/* %l : location */
1146 		if(strstr(temp, "%l")) {
1147 			char *pkgloc = pkg_get_location(pkg);
1148 			string = strreplace(temp, "%l", pkgloc);
1149 			free(pkgloc);
1150 			free(temp);
1151 			temp = string;
1152 		}
1153 		/* %r : repo */
1154 		if(strstr(temp, "%r")) {
1155 			const char *repo = "local";
1156 			alpm_db_t *db = alpm_pkg_get_db(pkg);
1157 			if(db) {
1158 				repo = alpm_db_get_name(db);
1159 			}
1160 			string = strreplace(temp, "%r", repo);
1161 			free(temp);
1162 			temp = string;
1163 		}
1164 		/* %s : size */
1165 		if(strstr(temp, "%s")) {
1166 			char *size;
1167 			pm_asprintf(&size, "%jd", (intmax_t)pkg_get_size(pkg));
1168 			string = strreplace(temp, "%s", size);
1169 			free(size);
1170 			free(temp);
1171 		}
1172 		printf("%s\n", string);
1173 		free(string);
1174 	}
1175 }
1176 
1177 /**
1178  * Helper function for comparing depends using the alpm "compare func"
1179  * signature. The function descends through the structure in the following
1180  * comparison order: name, modifier (e.g., '>', '='), version, description.
1181  * @param d1 the first depend structure
1182  * @param d2 the second depend structure
1183  * @return -1, 0, or 1 if first is <, ==, or > second
1184  */
depend_cmp(const void * d1,const void * d2)1185 static int depend_cmp(const void *d1, const void *d2)
1186 {
1187 	const alpm_depend_t *dep1 = d1;
1188 	const alpm_depend_t *dep2 = d2;
1189 	int ret;
1190 
1191 	ret = strcmp(dep1->name, dep2->name);
1192 	if(ret == 0) {
1193 		ret = dep1->mod - dep2->mod;
1194 	}
1195 	if(ret == 0) {
1196 		if(dep1->version && dep2->version) {
1197 			ret = strcmp(dep1->version, dep2->version);
1198 		} else if(!dep1->version && dep2->version) {
1199 			ret = -1;
1200 		} else if(dep1->version && !dep2->version) {
1201 			ret = 1;
1202 		}
1203 	}
1204 	if(ret == 0) {
1205 		if(dep1->desc && dep2->desc) {
1206 			ret = strcmp(dep1->desc, dep2->desc);
1207 		} else if(!dep1->desc && dep2->desc) {
1208 			ret = -1;
1209 		} else if(dep1->desc && !dep2->desc) {
1210 			ret = 1;
1211 		}
1212 	}
1213 
1214 	return ret;
1215 }
1216 
make_optstring(alpm_depend_t * optdep)1217 static char *make_optstring(alpm_depend_t *optdep)
1218 {
1219 	alpm_db_t *localdb = alpm_get_localdb(config->handle);
1220 	char *optstring = alpm_dep_compute_string(optdep);
1221 	char *status = NULL;
1222 	if(alpm_find_satisfier(alpm_db_get_pkgcache(localdb), optstring)) {
1223 		status = _(" [installed]");
1224 	} else if(alpm_find_satisfier(alpm_trans_get_add(config->handle), optstring)) {
1225 		status = _(" [pending]");
1226 	}
1227 	if(status) {
1228 		optstring = realloc(optstring, strlen(optstring) + strlen(status) + 1);
1229 		strcpy(optstring + strlen(optstring), status);
1230 	}
1231 	return optstring;
1232 }
1233 
display_new_optdepends(alpm_pkg_t * oldpkg,alpm_pkg_t * newpkg)1234 void display_new_optdepends(alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg)
1235 {
1236 	alpm_list_t *i, *old, *new, *optdeps, *optstrings = NULL;
1237 
1238 	old = alpm_pkg_get_optdepends(oldpkg);
1239 	new = alpm_pkg_get_optdepends(newpkg);
1240 	optdeps = alpm_list_diff(new, old, depend_cmp);
1241 
1242 	/* turn optdepends list into a text list */
1243 	for(i = optdeps; i; i = alpm_list_next(i)) {
1244 		alpm_depend_t *optdep = i->data;
1245 		optstrings = alpm_list_add(optstrings, make_optstring(optdep));
1246 	}
1247 
1248 	if(optstrings) {
1249 		printf(_("New optional dependencies for %s\n"), alpm_pkg_get_name(newpkg));
1250 		unsigned short cols = getcols();
1251 		list_display_linebreak("   ", optstrings, cols);
1252 	}
1253 
1254 	alpm_list_free(optdeps);
1255 	FREELIST(optstrings);
1256 }
1257 
display_optdepends(alpm_pkg_t * pkg)1258 void display_optdepends(alpm_pkg_t *pkg)
1259 {
1260 	alpm_list_t *i, *optdeps, *optstrings = NULL;
1261 
1262 	optdeps = alpm_pkg_get_optdepends(pkg);
1263 
1264 	/* turn optdepends list into a text list */
1265 	for(i = optdeps; i; i = alpm_list_next(i)) {
1266 		alpm_depend_t *optdep = i->data;
1267 		optstrings = alpm_list_add(optstrings, make_optstring(optdep));
1268 	}
1269 
1270 	if(optstrings) {
1271 		printf(_("Optional dependencies for %s\n"), alpm_pkg_get_name(pkg));
1272 		unsigned short cols = getcols();
1273 		list_display_linebreak("   ", optstrings, cols);
1274 	}
1275 
1276 	FREELIST(optstrings);
1277 }
1278 
display_repo_list(const char * dbname,alpm_list_t * list,unsigned short cols)1279 static void display_repo_list(const char *dbname, alpm_list_t *list,
1280 		unsigned short cols)
1281 {
1282 	const char *prefix = "  ";
1283 	const colstr_t *colstr = &config->colstr;
1284 
1285 	colon_printf(_("Repository %s%s\n"), colstr->repo, dbname);
1286 	list_display(prefix, list, cols);
1287 }
1288 
select_display(const alpm_list_t * pkglist)1289 void select_display(const alpm_list_t *pkglist)
1290 {
1291 	const alpm_list_t *i;
1292 	int nth = 1;
1293 	alpm_list_t *list = NULL;
1294 	char *string = NULL;
1295 	const char *dbname = NULL;
1296 	unsigned short cols = getcols();
1297 
1298 	for(i = pkglist; i; i = i->next) {
1299 		alpm_pkg_t *pkg = i->data;
1300 		alpm_db_t *db = alpm_pkg_get_db(pkg);
1301 
1302 		if(!dbname) {
1303 			dbname = alpm_db_get_name(db);
1304 		}
1305 		if(strcmp(alpm_db_get_name(db), dbname) != 0) {
1306 			display_repo_list(dbname, list, cols);
1307 			FREELIST(list);
1308 			dbname = alpm_db_get_name(db);
1309 		}
1310 		string = NULL;
1311 		pm_asprintf(&string, "%d) %s", nth, alpm_pkg_get_name(pkg));
1312 		list = alpm_list_add(list, string);
1313 		nth++;
1314 	}
1315 	display_repo_list(dbname, list, cols);
1316 	FREELIST(list);
1317 }
1318 
parseindex(char * s,int * val,int min,int max)1319 static int parseindex(char *s, int *val, int min, int max)
1320 {
1321 	char *endptr = NULL;
1322 	int n = strtol(s, &endptr, 10);
1323 	if(*endptr == '\0') {
1324 		if(n < min || n > max) {
1325 			pm_printf(ALPM_LOG_ERROR,
1326 					_("invalid value: %d is not between %d and %d\n"),
1327 					n, min, max);
1328 			return -1;
1329 		}
1330 		*val = n;
1331 		return 0;
1332 	} else {
1333 		pm_printf(ALPM_LOG_ERROR, _("invalid number: %s\n"), s);
1334 		return -1;
1335 	}
1336 }
1337 
multiselect_parse(char * array,int count,char * response)1338 static int multiselect_parse(char *array, int count, char *response)
1339 {
1340 	char *str, *saveptr;
1341 
1342 	for(str = response; ; str = NULL) {
1343 		int include = 1;
1344 		int start, end;
1345 		size_t len;
1346 		char *ends = NULL;
1347 		char *starts = strtok_r(str, " ,", &saveptr);
1348 
1349 		if(starts == NULL) {
1350 			break;
1351 		}
1352 		len = strtrim(starts);
1353 		if(len == 0) {
1354 			continue;
1355 		}
1356 
1357 		if(*starts == '^') {
1358 			starts++;
1359 			len--;
1360 			include = 0;
1361 		} else if(str) {
1362 			/* if first token is including, we unselect all targets */
1363 			memset(array, 0, count);
1364 		}
1365 
1366 		if(len > 1) {
1367 			/* check for range */
1368 			char *p;
1369 			if((p = strchr(starts + 1, '-'))) {
1370 				*p = 0;
1371 				ends = p + 1;
1372 			}
1373 		}
1374 
1375 		if(parseindex(starts, &start, 1, count) != 0) {
1376 			return -1;
1377 		}
1378 
1379 		if(!ends) {
1380 			array[start - 1] = include;
1381 		} else {
1382 			if(parseindex(ends, &end, start, count) != 0) {
1383 				return -1;
1384 			}
1385 			do {
1386 				array[start - 1] = include;
1387 			} while(start++ < end);
1388 		}
1389 	}
1390 
1391 	return 0;
1392 }
1393 
multiselect_question(char * array,int count)1394 int multiselect_question(char *array, int count)
1395 {
1396 	char *response, *lastchar;
1397 	FILE *stream;
1398 	size_t response_len = 64;
1399 
1400 	if(config->noconfirm) {
1401 		stream = stdout;
1402 	} else {
1403 		/* Use stderr so questions are always displayed when redirecting output */
1404 		stream = stderr;
1405 	}
1406 
1407 	response = malloc(response_len);
1408 	if(!response) {
1409 		return -1;
1410 	}
1411 	lastchar = response + response_len - 1;
1412 	/* sentinel byte to later see if we filled up the entire string */
1413 	*lastchar = 1;
1414 
1415 	while(1) {
1416 		memset(array, 1, count);
1417 
1418 		fprintf(stream, "\n");
1419 		fprintf(stream, _("Enter a selection (default=all)"));
1420 		fprintf(stream, ": ");
1421 		fflush(stream);
1422 
1423 		if(config->noconfirm) {
1424 			fprintf(stream, "\n");
1425 			break;
1426 		}
1427 
1428 		flush_term_input(fileno(stdin));
1429 
1430 		if(safe_fgets(response, response_len, stdin)) {
1431 			const size_t response_incr = 64;
1432 			size_t len;
1433 			/* handle buffer not being large enough to read full line case */
1434 			while(*lastchar == '\0' && lastchar[-1] != '\n') {
1435 				char *new_response;
1436 				response_len += response_incr;
1437 				new_response = realloc(response, response_len);
1438 				if(!new_response) {
1439 					free(response);
1440 					return -1;
1441 				}
1442 				response = new_response;
1443 				lastchar = response + response_len - 1;
1444 				/* sentinel byte */
1445 				*lastchar = 1;
1446 				if(safe_fgets(response + response_len - response_incr - 1,
1447 							response_incr + 1, stdin) == 0) {
1448 					free(response);
1449 					return -1;
1450 				}
1451 			}
1452 
1453 			len = strtrim(response);
1454 			if(len > 0) {
1455 				if(multiselect_parse(array, count, response) == -1) {
1456 					/* only loop if user gave an invalid answer */
1457 					continue;
1458 				}
1459 			}
1460 			break;
1461 		} else {
1462 			free(response);
1463 			return -1;
1464 		}
1465 	}
1466 
1467 	free(response);
1468 	return 0;
1469 }
1470 
select_question(int count)1471 int select_question(int count)
1472 {
1473 	char response[32];
1474 	FILE *stream;
1475 	int preset = 1;
1476 
1477 	if(config->noconfirm) {
1478 		stream = stdout;
1479 	} else {
1480 		/* Use stderr so questions are always displayed when redirecting output */
1481 		stream = stderr;
1482 	}
1483 
1484 	while(1) {
1485 		fprintf(stream, "\n");
1486 		fprintf(stream, _("Enter a number (default=%d)"), preset);
1487 		fprintf(stream, ": ");
1488 		fflush(stream);
1489 
1490 		if(config->noconfirm) {
1491 			fprintf(stream, "\n");
1492 			break;
1493 		}
1494 
1495 		flush_term_input(fileno(stdin));
1496 
1497 		if(safe_fgets(response, sizeof(response), stdin)) {
1498 			size_t len = strtrim(response);
1499 			if(len > 0) {
1500 				int n;
1501 				if(parseindex(response, &n, 1, count) != 0) {
1502 					continue;
1503 				}
1504 				return (n - 1);
1505 			}
1506 		}
1507 		break;
1508 	}
1509 
1510 	return (preset - 1);
1511 }
1512 
1513 #define CMP(x, y) ((x) < (y) ? -1 : ((x) > (y) ? 1 : 0))
1514 
mbscasecmp(const char * s1,const char * s2)1515 static int mbscasecmp(const char *s1, const char *s2)
1516 {
1517 	size_t len1 = strlen(s1), len2 = strlen(s2);
1518 	wchar_t c1, c2;
1519 	const char *p1 = s1, *p2 = s2;
1520 	mbstate_t ps1, ps2;
1521 	memset(&ps1, 0, sizeof(mbstate_t));
1522 	memset(&ps2, 0, sizeof(mbstate_t));
1523 	while(*p1 && *p2) {
1524 		size_t b1 = mbrtowc(&c1, p1, len1, &ps1);
1525 		size_t b2 = mbrtowc(&c2, p2, len2, &ps2);
1526 		if(b1 == (size_t) -2 || b1 == (size_t) -1
1527 				|| b2 == (size_t) -2 || b2 == (size_t) -1) {
1528 			/* invalid multi-byte string, fall back to strcasecmp */
1529 			return strcasecmp(p1, p2);
1530 		}
1531 		if(b1 == 0 || b2 == 0) {
1532 			return CMP(c1, c2);
1533 		}
1534 		c1 = towlower(c1);
1535 		c2 = towlower(c2);
1536 		if(c1 != c2) {
1537 			return CMP(c1, c2);
1538 		}
1539 		p1 += b1;
1540 		p2 += b2;
1541 		len1 -= b1;
1542 		len2 -= b2;
1543 	}
1544 	return CMP(*p1, *p2);
1545 }
1546 
1547 /* presents a prompt and gets a Y/N answer */
1548 __attribute__((format(printf, 2, 0)))
question(short preset,const char * format,va_list args)1549 static int question(short preset, const char *format, va_list args)
1550 {
1551 	char response[32];
1552 	FILE *stream;
1553 	int fd_in = fileno(stdin);
1554 
1555 	if(config->noconfirm) {
1556 		stream = stdout;
1557 	} else {
1558 		/* Use stderr so questions are always displayed when redirecting output */
1559 		stream = stderr;
1560 	}
1561 
1562 	/* ensure all text makes it to the screen before we prompt the user */
1563 	fflush(stdout);
1564 	fflush(stderr);
1565 
1566 	fputs(config->colstr.colon, stream);
1567 	vfprintf(stream, format, args);
1568 
1569 	if(preset) {
1570 		fprintf(stream, " %s ", _("[Y/n]"));
1571 	} else {
1572 		fprintf(stream, " %s ", _("[y/N]"));
1573 	}
1574 
1575 	fputs(config->colstr.nocolor, stream);
1576 	fflush(stream);
1577 
1578 	if(config->noconfirm) {
1579 		fprintf(stream, "\n");
1580 		return preset;
1581 	}
1582 
1583 	flush_term_input(fd_in);
1584 
1585 	if(safe_fgets(response, sizeof(response), stdin)) {
1586 		size_t len = strtrim(response);
1587 		if(len == 0) {
1588 			return preset;
1589 		}
1590 
1591 		/* if stdin is piped, response does not get printed out, and as a result
1592 		 * a \n is missing, resulting in broken output */
1593 		if(!isatty(fd_in)) {
1594 			fprintf(stream, "%s\n", response);
1595 		}
1596 
1597 		if(mbscasecmp(response, _("Y")) == 0 || mbscasecmp(response, _("YES")) == 0) {
1598 			return 1;
1599 		} else if(mbscasecmp(response, _("N")) == 0 || mbscasecmp(response, _("NO")) == 0) {
1600 			return 0;
1601 		}
1602 	}
1603 	return 0;
1604 }
1605 
yesno(const char * format,...)1606 int yesno(const char *format, ...)
1607 {
1608 	int ret;
1609 	va_list args;
1610 
1611 	va_start(args, format);
1612 	ret = question(1, format, args);
1613 	va_end(args);
1614 
1615 	return ret;
1616 }
1617 
noyes(const char * format,...)1618 int noyes(const char *format, ...)
1619 {
1620 	int ret;
1621 	va_list args;
1622 
1623 	va_start(args, format);
1624 	ret = question(0, format, args);
1625 	va_end(args);
1626 
1627 	return ret;
1628 }
1629 
colon_printf(const char * fmt,...)1630 int colon_printf(const char *fmt, ...)
1631 {
1632 	int ret;
1633 	va_list args;
1634 
1635 	va_start(args, fmt);
1636 	fputs(config->colstr.colon, stdout);
1637 	ret = vprintf(fmt, args);
1638 	fputs(config->colstr.nocolor, stdout);
1639 	va_end(args);
1640 
1641 	fflush(stdout);
1642 	return ret;
1643 }
1644 
pm_printf(alpm_loglevel_t level,const char * format,...)1645 int pm_printf(alpm_loglevel_t level, const char *format, ...)
1646 {
1647 	int ret;
1648 	va_list args;
1649 
1650 	/* print the message using va_arg list */
1651 	va_start(args, format);
1652 	ret = pm_vfprintf(stderr, level, format, args);
1653 	va_end(args);
1654 
1655 	return ret;
1656 }
1657 
pm_asprintf(char ** string,const char * format,...)1658 int pm_asprintf(char **string, const char *format, ...)
1659 {
1660 	int ret = 0;
1661 	va_list args;
1662 
1663 	/* print the message using va_arg list */
1664 	va_start(args, format);
1665 	if(vasprintf(string, format, args) == -1) {
1666 		pm_printf(ALPM_LOG_ERROR, _("failed to allocate string\n"));
1667 		ret = -1;
1668 	}
1669 	va_end(args);
1670 
1671 	return ret;
1672 }
1673 
pm_sprintf(char ** string,alpm_loglevel_t level,const char * format,...)1674 int pm_sprintf(char **string, alpm_loglevel_t level, const char *format, ...)
1675 {
1676 	int ret = 0;
1677 	va_list args;
1678 
1679 	/* print the message using va_arg list */
1680 	va_start(args, format);
1681 	ret = pm_vasprintf(string, level, format, args);
1682 	va_end(args);
1683 
1684 	return ret;
1685 }
1686 
pm_vasprintf(char ** string,alpm_loglevel_t level,const char * format,va_list args)1687 int pm_vasprintf(char **string, alpm_loglevel_t level, const char *format, va_list args)
1688 {
1689 	int ret = 0;
1690 	char *msg = NULL;
1691 
1692 	/* if current logmask does not overlap with level, do not print msg */
1693 	if(!(config->logmask & level)) {
1694 		return ret;
1695 	}
1696 
1697 	/* print the message using va_arg list */
1698 	ret = vasprintf(&msg, format, args);
1699 
1700 	/* print a prefix to the message */
1701 	switch(level) {
1702 		case ALPM_LOG_ERROR:
1703 			pm_asprintf(string, "%s%s%s%s", config->colstr.err, _("error: "),
1704 								config->colstr.nocolor, msg);
1705 			break;
1706 		case ALPM_LOG_WARNING:
1707 			pm_asprintf(string, "%s%s%s%s", config->colstr.warn, _("warning: "),
1708 								config->colstr.nocolor, msg);
1709 			break;
1710 		case ALPM_LOG_DEBUG:
1711 			pm_asprintf(string, "debug: %s", msg);
1712 			break;
1713 		case ALPM_LOG_FUNCTION:
1714 			pm_asprintf(string, "function: %s", msg);
1715 			break;
1716 		default:
1717 			pm_asprintf(string, "%s", msg);
1718 			break;
1719 	}
1720 	free(msg);
1721 
1722 	return ret;
1723 }
1724 
pm_vfprintf(FILE * stream,alpm_loglevel_t level,const char * format,va_list args)1725 int pm_vfprintf(FILE *stream, alpm_loglevel_t level, const char *format, va_list args)
1726 {
1727 	int ret = 0;
1728 
1729 	/* if current logmask does not overlap with level, do not print msg */
1730 	if(!(config->logmask & level)) {
1731 		return ret;
1732 	}
1733 
1734 #if defined(PACMAN_DEBUG)
1735 	/* If debug is on, we'll timestamp the output */
1736 	if(config->logmask & ALPM_LOG_DEBUG) {
1737 		time_t t;
1738 		struct tm *tmp;
1739 		char timestr[10] = {0};
1740 
1741 		t = time(NULL);
1742 		tmp = localtime(&t);
1743 		strftime(timestr, 9, "%H:%M:%S", tmp);
1744 		timestr[8] = '\0';
1745 
1746 		fprintf(stream, "[%s] ", timestr);
1747 	}
1748 #endif
1749 
1750 	/* print a prefix to the message */
1751 	switch(level) {
1752 		case ALPM_LOG_ERROR:
1753 			fprintf(stream, "%s%s%s", config->colstr.err, _("error: "),
1754 					config->colstr.nocolor);
1755 			break;
1756 		case ALPM_LOG_WARNING:
1757 			fprintf(stream, "%s%s%s", config->colstr.warn, _("warning: "),
1758 					config->colstr.nocolor);
1759 			break;
1760 		case ALPM_LOG_DEBUG:
1761 			fprintf(stream, "debug: ");
1762 			break;
1763 		case ALPM_LOG_FUNCTION:
1764 			fprintf(stream, "function: ");
1765 			break;
1766 		default:
1767 			break;
1768 	}
1769 
1770 	/* print the message using va_arg list */
1771 	ret = vfprintf(stream, format, args);
1772 	return ret;
1773 }
1774