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