Fix FS#27924: don't display negative zeroes
[pacman-ng.git] / src / pacman / util.c
blob96284a3397d0744fab9b15dd995cc0f550946f61
1 /*
2 * util.c
4 * Copyright (c) 2006-2012 Pacman Development Team <pacman-dev@archlinux.org>
5 * Copyright (c) 2002-2006 by Judd Vinet <jvinet@zeroflux.org>
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.
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.
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/>.
21 #include "config.h"
23 #include <sys/types.h>
24 #include <sys/ioctl.h>
25 #include <sys/stat.h>
26 #include <sys/time.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 #include <stdint.h> /* intmax_t */
32 #include <string.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <dirent.h>
36 #include <unistd.h>
37 #include <limits.h>
38 #include <wchar.h>
39 #include <math.h> /* pow */
40 #ifdef HAVE_TERMIOS_H
41 #include <termios.h> /* tcflush */
42 #endif
44 #include <alpm.h>
45 #include <alpm_list.h>
47 /* pacman */
48 #include "util.h"
49 #include "conf.h"
50 #include "callback.h"
53 int trans_init(alpm_transflag_t flags, int check_valid)
55 int ret;
57 check_syncdbs(0, check_valid);
59 ret = alpm_trans_init(config->handle, flags);
60 if(ret == -1) {
61 trans_init_error();
62 return -1;
64 return 0;
67 void trans_init_error(void)
69 enum _alpm_errno_t err = alpm_errno(config->handle);
70 pm_printf(ALPM_LOG_ERROR, _("failed to init transaction (%s)\n"),
71 alpm_strerror(err));
72 if(err == ALPM_ERR_HANDLE_LOCK) {
73 fprintf(stderr, _(" if you're sure a package manager is not already\n"
74 " running, you can remove %s\n"),
75 alpm_option_get_lockfile(config->handle));
79 int trans_release(void)
81 if(alpm_trans_release(config->handle) == -1) {
82 pm_printf(ALPM_LOG_ERROR, _("failed to release transaction (%s)\n"),
83 alpm_strerror(alpm_errno(config->handle)));
84 return -1;
86 return 0;
89 int needs_root(void)
91 switch(config->op) {
92 case PM_OP_DATABASE:
93 return 1;
94 case PM_OP_UPGRADE:
95 case PM_OP_REMOVE:
96 return !config->print;
97 case PM_OP_SYNC:
98 return (config->op_s_clean || config->op_s_sync ||
99 (!config->group && !config->op_s_info && !config->op_q_list &&
100 !config->op_s_search && !config->print));
101 default:
102 return 0;
106 int check_syncdbs(size_t need_repos, int check_valid)
108 int ret = 0;
109 alpm_list_t *i;
110 alpm_list_t *sync_dbs = alpm_option_get_syncdbs(config->handle);
112 if(need_repos && sync_dbs == NULL) {
113 pm_printf(ALPM_LOG_ERROR, _("no usable package repositories configured.\n"));
114 return 1;
117 if(check_valid) {
118 /* ensure all known dbs are valid */
119 for(i = sync_dbs; i; i = alpm_list_next(i)) {
120 alpm_db_t *db = i->data;
121 if(alpm_db_get_valid(db)) {
122 pm_printf(ALPM_LOG_ERROR, _("database '%s' is not valid (%s)\n"),
123 alpm_db_get_name(db), alpm_strerror(alpm_errno(config->handle)));
124 ret = 1;
128 return ret;
131 /* discard unhandled input on the terminal's input buffer */
132 static int flush_term_input(void) {
133 #ifdef HAVE_TCFLUSH
134 if(isatty(fileno(stdin))) {
135 return tcflush(fileno(stdin), TCIFLUSH);
137 #endif
139 /* fail silently */
140 return 0;
143 /* gets the current screen column width */
144 unsigned short getcols(void)
146 const unsigned short default_tty = 80;
147 const unsigned short default_notty = 0;
148 unsigned short termwidth = 0;
150 if(!isatty(fileno(stdout))) {
151 return default_notty;
154 #ifdef TIOCGSIZE
155 struct ttysize win;
156 if(ioctl(1, TIOCGSIZE, &win) == 0) {
157 termwidth = win.ts_cols;
159 #elif defined(TIOCGWINSZ)
160 struct winsize win;
161 if(ioctl(1, TIOCGWINSZ, &win) == 0) {
162 termwidth = win.ws_col;
164 #endif
165 return termwidth == 0 ? default_tty : termwidth;
168 /* does the same thing as 'rm -rf' */
169 int rmrf(const char *path)
171 int errflag = 0;
172 struct dirent *dp;
173 DIR *dirp;
175 if(!unlink(path)) {
176 return 0;
177 } else {
178 if(errno == ENOENT) {
179 return 0;
180 } else if(errno == EPERM) {
181 /* fallthrough */
182 } else if(errno == EISDIR) {
183 /* fallthrough */
184 } else if(errno == ENOTDIR) {
185 return 1;
186 } else {
187 /* not a directory */
188 return 1;
191 dirp = opendir(path);
192 if(!dirp) {
193 return 1;
195 for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
196 if(dp->d_name) {
197 if(strcmp(dp->d_name, "..") != 0 && strcmp(dp->d_name, ".") != 0) {
198 char name[PATH_MAX];
199 snprintf(name, PATH_MAX, "%s/%s", path, dp->d_name);
200 errflag += rmrf(name);
204 closedir(dirp);
205 if(rmdir(path)) {
206 errflag++;
208 return errflag;
212 /** Parse the basename of a program from a path.
213 * @param path path to parse basename from
215 * @return everything following the final '/'
217 const char *mbasename(const char *path)
219 const char *last = strrchr(path, '/');
220 if(last) {
221 return last + 1;
223 return path;
226 /** Parse the dirname of a program from a path.
227 * The path returned should be freed.
228 * @param path path to parse dirname from
230 * @return everything preceding the final '/'
232 char *mdirname(const char *path)
234 char *ret, *last;
236 /* null or empty path */
237 if(path == NULL || path == '\0') {
238 return strdup(".");
241 ret = strdup(path);
242 last = strrchr(ret, '/');
244 if(last != NULL) {
245 /* we found a '/', so terminate our string */
246 *last = '\0';
247 return ret;
249 /* no slash found */
250 free(ret);
251 return strdup(".");
254 /* output a string, but wrap words properly with a specified indentation
256 void indentprint(const char *str, size_t indent)
258 wchar_t *wcstr;
259 const wchar_t *p;
260 int len, cidx;
261 const unsigned short cols = getcols();
263 if(!str) {
264 return;
267 /* if we're not a tty, or our tty is not wide enough that wrapping even makes
268 * sense, print without indenting */
269 if(cols == 0 || indent > cols) {
270 printf("%s", str);
271 return;
274 len = strlen(str) + 1;
275 wcstr = calloc(len, sizeof(wchar_t));
276 len = mbstowcs(wcstr, str, len);
277 p = wcstr;
278 cidx = indent;
280 if(!p || !len) {
281 return;
284 while(*p) {
285 if(*p == L' ') {
286 const wchar_t *q, *next;
287 p++;
288 if(p == NULL || *p == L' ') continue;
289 next = wcschr(p, L' ');
290 if(next == NULL) {
291 next = p + wcslen(p);
293 /* len captures # cols */
294 len = 0;
295 q = p;
296 while(q < next) {
297 len += wcwidth(*q++);
299 if(len > (cols - cidx - 1)) {
300 /* wrap to a newline and reindent */
301 printf("\n%-*s", (int)indent, "");
302 cidx = indent;
303 } else {
304 printf(" ");
305 cidx++;
307 continue;
309 printf("%lc", (wint_t)*p);
310 cidx += wcwidth(*p);
311 p++;
313 free(wcstr);
316 /* Trim whitespace and newlines from a string
318 char *strtrim(char *str)
320 char *pch = str;
322 if(str == NULL || *str == '\0') {
323 /* string is empty, so we're done. */
324 return str;
327 while(isspace((unsigned char)*pch)) {
328 pch++;
330 if(pch != str) {
331 size_t len = strlen(pch);
332 if(len) {
333 memmove(str, pch, len + 1);
334 } else {
335 *str = '\0';
339 /* check if there wasn't anything but whitespace in the string. */
340 if(*str == '\0') {
341 return str;
344 pch = (str + (strlen(str) - 1));
345 while(isspace((unsigned char)*pch)) {
346 pch--;
348 *++pch = '\0';
350 return str;
353 /* Replace all occurances of 'needle' with 'replace' in 'str', returning
354 * a new string (must be free'd) */
355 char *strreplace(const char *str, const char *needle, const char *replace)
357 const char *p = NULL, *q = NULL;
358 char *newstr = NULL, *newp = NULL;
359 alpm_list_t *i = NULL, *list = NULL;
360 size_t needlesz = strlen(needle), replacesz = strlen(replace);
361 size_t newsz;
363 if(!str) {
364 return NULL;
367 p = str;
368 q = strstr(p, needle);
369 while(q) {
370 list = alpm_list_add(list, (char *)q);
371 p = q + needlesz;
372 q = strstr(p, needle);
375 /* no occurences of needle found */
376 if(!list) {
377 return strdup(str);
379 /* size of new string = size of old string + "number of occurences of needle"
380 * x "size difference between replace and needle" */
381 newsz = strlen(str) + 1 +
382 alpm_list_count(list) * (replacesz - needlesz);
383 newstr = calloc(newsz, sizeof(char));
384 if(!newstr) {
385 return NULL;
388 p = str;
389 newp = newstr;
390 for(i = list; i; i = alpm_list_next(i)) {
391 q = alpm_list_getdata(i);
392 if(q > p) {
393 /* add chars between this occurence and last occurence, if any */
394 memcpy(newp, p, (size_t)(q - p));
395 newp += q - p;
397 memcpy(newp, replace, replacesz);
398 newp += replacesz;
399 p = q + needlesz;
401 alpm_list_free(list);
403 if(*p) {
404 /* add the rest of 'p' */
405 strcpy(newp, p);
408 return newstr;
411 /** Splits a string into a list of strings using the chosen character as
412 * a delimiter.
414 * @param str the string to split
415 * @param splitchar the character to split at
417 * @return a list containing the duplicated strings
419 alpm_list_t *strsplit(const char *str, const char splitchar)
421 alpm_list_t *list = NULL;
422 const char *prev = str;
423 char *dup = NULL;
425 while((str = strchr(str, splitchar))) {
426 dup = strndup(prev, (size_t)(str - prev));
427 if(dup == NULL) {
428 return NULL;
430 list = alpm_list_add(list, dup);
432 str++;
433 prev = str;
436 dup = strdup(prev);
437 if(dup == NULL) {
438 return NULL;
440 list = alpm_list_add(list, dup);
442 return list;
445 static size_t string_length(const char *s)
447 int len;
448 wchar_t *wcstr;
450 if(!s || s[0] == '\0') {
451 return 0;
453 /* len goes from # bytes -> # chars -> # cols */
454 len = strlen(s) + 1;
455 wcstr = calloc(len, sizeof(wchar_t));
456 len = mbstowcs(wcstr, s, len);
457 len = wcswidth(wcstr, len);
458 free(wcstr);
460 return len;
463 void string_display(const char *title, const char *string)
465 if(title) {
466 printf("%s ", title);
468 if(string == NULL || string[0] == '\0') {
469 printf(_("None"));
470 } else {
471 /* compute the length of title + a space */
472 size_t len = string_length(title) + 1;
473 indentprint(string, len);
475 printf("\n");
478 static void table_print_line(const alpm_list_t *line,
479 size_t colcount, size_t *widths)
481 size_t i;
482 const alpm_list_t *curcell;
484 for(i = 0, curcell = line; curcell && i < colcount;
485 i++, curcell = alpm_list_next(curcell)) {
486 const char *value = curcell->data;
487 size_t len = string_length(value);
488 /* silly printf requires padding size to be an int */
489 int padding = (int)widths[i] - (int)len;
490 if(padding < 0) {
491 padding = 0;
493 /* left-align all but the last column */
494 if(i + 1 < colcount) {
495 printf("%s%*s", value, padding, "");
496 } else {
497 printf("%*s%s", padding, "", value);
501 printf("\n");
504 /* find the max string width of each column */
505 static size_t table_calc_widths(const alpm_list_t *header,
506 const alpm_list_t *rows, size_t totalcols, size_t **widths)
508 const alpm_list_t *i;
509 const unsigned short padding = 2;
510 size_t curcol, totalwidth = 0;
511 size_t *colwidths;
513 if(totalcols <= 0) {
514 return 0;
517 colwidths = malloc(totalcols * sizeof(size_t));
518 if(!colwidths) {
519 return 0;
521 /* header determines column count and initial values of longest_strs */
522 for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) {
523 colwidths[curcol] = string_length(alpm_list_getdata(i));
526 /* now find the longest string in each column */
527 for(i = rows; i; i = alpm_list_next(i)) {
528 /* grab first column of each row and iterate through columns */
529 const alpm_list_t *j = alpm_list_getdata(i);
530 for(curcol = 0; j; j = alpm_list_next(j), curcol++) {
531 char *str = alpm_list_getdata(j);
532 size_t str_len = string_length(str);
534 if(str_len > colwidths[curcol]) {
535 colwidths[curcol] = str_len;
540 for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) {
541 /* pad everything but the last column */
542 if(curcol + 1 < totalcols) {
543 colwidths[curcol] += padding;
545 totalwidth += colwidths[curcol];
548 *widths = colwidths;
549 return totalwidth;
552 /** Displays the list in table format
554 * @param title the tables title
555 * @param header the column headers. column count is determined by the nr
556 * of headers
557 * @param rows the rows to display as a list of lists of strings. the outer
558 * list represents the rows, the inner list the cells (= columns)
560 * @return -1 if not enough terminal cols available, else 0
562 int table_display(const char *title, const alpm_list_t *header,
563 const alpm_list_t *rows)
565 const alpm_list_t *i;
566 size_t *widths = NULL, totalcols, totalwidth;
568 if(rows == NULL || header == NULL) {
569 return 0;
572 totalcols = alpm_list_count(header);
573 totalwidth = table_calc_widths(header, rows, totalcols, &widths);
574 /* return -1 if terminal is not wide enough */
575 if(totalwidth > getcols()) {
576 pm_printf(ALPM_LOG_WARNING,
577 _("insufficient columns available for table display\n"));
578 return -1;
580 if(!totalwidth || !widths) {
581 return -1;
584 if(title != NULL) {
585 printf("%s\n\n", title);
588 table_print_line(header, totalcols, widths);
589 printf("\n");
591 for(i = rows; i; i = alpm_list_next(i)) {
592 table_print_line(alpm_list_getdata(i), totalcols, widths);
595 free(widths);
596 return 0;
599 void list_display(const char *title, const alpm_list_t *list)
601 const alpm_list_t *i;
602 size_t len = 0;
604 if(title) {
605 len = string_length(title) + 1;
606 printf("%s ", title);
609 if(!list) {
610 printf("%s\n", _("None"));
611 } else {
612 const unsigned short maxcols = getcols();
613 size_t cols = len;
614 const char *str = alpm_list_getdata(list);
615 printf("%s", str);
616 cols += string_length(str);
617 for(i = alpm_list_next(list); i; i = alpm_list_next(i)) {
618 str = alpm_list_getdata(i);
619 size_t s = string_length(str);
620 /* wrap only if we have enough usable column space */
621 if(maxcols > len && cols + s + 2 >= maxcols) {
622 size_t j;
623 cols = len;
624 printf("\n");
625 for (j = 1; j <= len; j++) {
626 printf(" ");
628 } else if(cols != len) {
629 /* 2 spaces are added if this is not the first element on a line. */
630 printf(" ");
631 cols += 2;
633 printf("%s", str);
634 cols += s;
636 printf("\n");
640 void list_display_linebreak(const char *title, const alpm_list_t *list)
642 size_t len = 0;
644 if(title) {
645 len = string_length(title) + 1;
646 printf("%s ", title);
649 if(!list) {
650 printf("%s\n", _("None"));
651 } else {
652 const alpm_list_t *i;
653 /* Print the first element */
654 indentprint((const char *) alpm_list_getdata(list), len);
655 printf("\n");
656 /* Print the rest */
657 for(i = alpm_list_next(list); i; i = alpm_list_next(i)) {
658 size_t j;
659 for(j = 1; j <= len; j++) {
660 printf(" ");
662 indentprint((const char *) alpm_list_getdata(i), len);
663 printf("\n");
668 void signature_display(const char *title, alpm_siglist_t *siglist)
670 size_t len = 0;
672 if(title) {
673 len = string_length(title) + 1;
674 printf("%s ", title);
676 if(siglist->count == 0) {
677 printf(_("None"));
678 } else {
679 size_t i;
680 for(i = 0; i < siglist->count; i++) {
681 char *sigline;
682 const char *status, *validity, *name;
683 int ret;
684 alpm_sigresult_t *result = siglist->results + i;
685 /* Don't re-indent the first result */
686 if(i != 0) {
687 size_t j;
688 for(j = 1; j <= len; j++) {
689 printf(" ");
692 switch(result->status) {
693 case ALPM_SIGSTATUS_VALID:
694 status = _("Valid");
695 break;
696 case ALPM_SIGSTATUS_KEY_EXPIRED:
697 status = _("Key expired");
698 break;
699 case ALPM_SIGSTATUS_SIG_EXPIRED:
700 status = _("Expired");
701 break;
702 case ALPM_SIGSTATUS_INVALID:
703 status = _("Invalid");
704 break;
705 case ALPM_SIGSTATUS_KEY_UNKNOWN:
706 status = _("Key unknown");
707 break;
708 case ALPM_SIGSTATUS_KEY_DISABLED:
709 status = _("Key disabled");
710 break;
711 default:
712 status = _("Signature error");
713 break;
715 switch(result->validity) {
716 case ALPM_SIGVALIDITY_FULL:
717 validity = _("full trust");
718 break;
719 case ALPM_SIGVALIDITY_MARGINAL:
720 validity = _("marginal trust");
721 break;
722 case ALPM_SIGVALIDITY_NEVER:
723 validity = _("never trust");
724 break;
725 case ALPM_SIGVALIDITY_UNKNOWN:
726 default:
727 validity = _("unknown trust");
728 break;
730 name = result->key.uid ? result->key.uid : result->key.fingerprint;
731 ret = pm_asprintf(&sigline, _("%s, %s from \"%s\""),
732 status, validity, name);
733 if(ret == -1) {
734 pm_printf(ALPM_LOG_ERROR, _("failed to allocate string\n"));
735 continue;
737 indentprint(sigline, len);
738 printf("\n");
739 free(sigline);
744 /* creates a header row for use with table_display */
745 static alpm_list_t *create_verbose_header(int dl_size)
747 alpm_list_t *res = NULL;
748 char *str;
750 str = _("Name");
751 res = alpm_list_add(res, str);
752 str = _("Old Version");
753 res = alpm_list_add(res, str);
754 str = _("New Version");
755 res = alpm_list_add(res, str);
756 str = _("Net Change");
757 res = alpm_list_add(res, str);
758 if(dl_size) {
759 str = _("Download Size");
760 res = alpm_list_add(res, str);
763 return res;
766 /* returns package info as list of strings */
767 static alpm_list_t *create_verbose_row(pm_target_t *target, int dl_size)
769 char *str;
770 off_t size = 0;
771 double human_size;
772 const char *label;
773 alpm_list_t *ret = NULL;
775 /* a row consists of the package name, */
776 if(target->install) {
777 pm_asprintf(&str, "%s", alpm_pkg_get_name(target->install));
778 } else {
779 pm_asprintf(&str, "%s", alpm_pkg_get_name(target->remove));
781 ret = alpm_list_add(ret, str);
783 /* old and new versions */
784 pm_asprintf(&str, "%s",
785 target->remove != NULL ? alpm_pkg_get_version(target->remove) : "");
786 ret = alpm_list_add(ret, str);
788 pm_asprintf(&str, "%s",
789 target->install != NULL ? alpm_pkg_get_version(target->install) : "");
790 ret = alpm_list_add(ret, str);
792 /* and size */
793 size -= target->remove ? alpm_pkg_get_isize(target->remove) : 0;
794 size += target->install ? alpm_pkg_get_isize(target->install) : 0;
795 human_size = humanize_size(size, 'M', 2, &label);
796 pm_asprintf(&str, "%.2f %s", human_size, label);
797 ret = alpm_list_add(ret, str);
799 if(dl_size) {
800 size = target->install ? alpm_pkg_download_size(target->install) : 0;
801 human_size = humanize_size(size, 'M', 2, &label);
802 if(size != 0) {
803 pm_asprintf(&str, "%.2f %s", human_size, label);
804 } else {
805 str = strdup("");
807 ret = alpm_list_add(ret, str);
810 return ret;
813 /* prepare a list of pkgs to display */
814 static void _display_targets(alpm_list_t *targets, int verbose)
816 char *str;
817 const char *label;
818 double size;
819 off_t isize = 0, rsize = 0, dlsize = 0;
820 alpm_list_t *i, *rows = NULL, *names = NULL;
821 int show_dl_size = config->op == PM_OP_SYNC;
823 if(!targets) {
824 return;
827 /* gather package info */
828 for(i = targets; i; i = alpm_list_next(i)) {
829 pm_target_t *target = alpm_list_getdata(i);
831 if(target->install) {
832 dlsize += alpm_pkg_download_size(target->install);
833 isize += alpm_pkg_get_isize(target->install);
835 if(target->remove) {
836 /* add up size of all removed packages */
837 rsize += alpm_pkg_get_isize(target->remove);
841 /* form data for both verbose and non-verbose display */
842 for(i = targets; i; i = alpm_list_next(i)) {
843 pm_target_t *target = i->data;
845 rows = alpm_list_add(rows, create_verbose_row(target, show_dl_size));
846 if(target->install) {
847 pm_asprintf(&str, "%s-%s", alpm_pkg_get_name(target->install),
848 alpm_pkg_get_version(target->install));
849 } else if(isize == 0) {
850 pm_asprintf(&str, "%s-%s", alpm_pkg_get_name(target->remove),
851 alpm_pkg_get_version(target->remove));
852 } else {
853 pm_asprintf(&str, "%s-%s [removal]", alpm_pkg_get_name(target->remove),
854 alpm_pkg_get_version(target->remove));
856 names = alpm_list_add(names, str);
859 /* print to screen */
860 pm_asprintf(&str, _("Targets (%d):"), alpm_list_count(targets));
862 printf("\n");
863 if(verbose) {
864 alpm_list_t *header = create_verbose_header(show_dl_size);
865 if(table_display(str, header, rows) != 0) {
866 /* fallback to list display if table wouldn't fit */
867 list_display(str, names);
869 alpm_list_free(header);
870 } else {
871 list_display(str, names);
873 printf("\n");
875 /* rows is a list of lists of strings, free inner lists here */
876 for(i = rows; i; i = alpm_list_next(i)) {
877 alpm_list_t *lp = alpm_list_getdata(i);
878 FREELIST(lp);
880 alpm_list_free(rows);
881 FREELIST(names);
882 free(str);
884 if(dlsize > 0 || config->op_s_downloadonly) {
885 size = humanize_size(dlsize, 'M', 2, &label);
886 printf(_("Total Download Size: %.2f %s\n"), size, label);
888 if(!config->op_s_downloadonly) {
889 if(isize > 0) {
890 size = humanize_size(isize, 'M', 2, &label);
891 printf(_("Total Installed Size: %.2f %s\n"), size, label);
893 if(rsize > 0 && isize == 0) {
894 size = humanize_size(rsize, 'M', 2, &label);
895 printf(_("Total Removed Size: %.2f %s\n"), size, label);
897 /* only show this net value if different from raw installed size */
898 if(isize > 0 && rsize > 0) {
899 size = humanize_size(isize - rsize, 'M', 2, &label);
900 printf(_("Net Upgrade Size: %.2f %s\n"), size, label);
905 static int target_cmp(const void *p1, const void *p2)
907 const pm_target_t *targ1 = p1;
908 const pm_target_t *targ2 = p2;
909 /* explicit are always sorted after implicit (e.g. deps, pulled targets) */
910 if(targ1->is_explicit != targ2->is_explicit) {
911 return targ1->is_explicit > targ2->is_explicit;
913 const char *name1 = targ1->install ?
914 alpm_pkg_get_name(targ1->install) : alpm_pkg_get_name(targ1->remove);
915 const char *name2 = targ2->install ?
916 alpm_pkg_get_name(targ2->install) : alpm_pkg_get_name(targ2->remove);
917 return strcmp(name1, name2);
920 static int pkg_cmp(const void *p1, const void *p2)
922 /* explicit cast due to (un)necessary removal of const */
923 alpm_pkg_t *pkg1 = (alpm_pkg_t *)p1;
924 alpm_pkg_t *pkg2 = (alpm_pkg_t *)p2;
925 return strcmp(alpm_pkg_get_name(pkg1), alpm_pkg_get_name(pkg2));
928 void display_targets(void)
930 alpm_list_t *i, *targets = NULL;
931 alpm_db_t *db_local = alpm_option_get_localdb(config->handle);
933 for(i = alpm_trans_get_add(config->handle); i; i = alpm_list_next(i)) {
934 alpm_pkg_t *pkg = alpm_list_getdata(i);
935 pm_target_t *targ = calloc(1, sizeof(pm_target_t));
936 if(!targ) return;
937 targ->install = pkg;
938 targ->remove = alpm_db_get_pkg(db_local, alpm_pkg_get_name(pkg));
939 if(alpm_list_find(config->explicit_adds, pkg, pkg_cmp)) {
940 targ->is_explicit = 1;
942 targets = alpm_list_add(targets, targ);
944 for(i = alpm_trans_get_remove(config->handle); i; i = alpm_list_next(i)) {
945 alpm_pkg_t *pkg = alpm_list_getdata(i);
946 pm_target_t *targ = calloc(1, sizeof(pm_target_t));
947 if(!targ) return;
948 targ->remove = pkg;
949 if(alpm_list_find(config->explicit_removes, pkg, pkg_cmp)) {
950 targ->is_explicit = 1;
952 targets = alpm_list_add(targets, targ);
955 targets = alpm_list_msort(targets, alpm_list_count(targets), target_cmp);
956 _display_targets(targets, config->verbosepkglists);
957 FREELIST(targets);
960 static off_t pkg_get_size(alpm_pkg_t *pkg)
962 switch(config->op) {
963 case PM_OP_SYNC:
964 return alpm_pkg_download_size(pkg);
965 case PM_OP_UPGRADE:
966 return alpm_pkg_get_size(pkg);
967 default:
968 return alpm_pkg_get_isize(pkg);
972 static char *pkg_get_location(alpm_pkg_t *pkg)
974 alpm_list_t *servers;
975 char *string = NULL;
976 switch(config->op) {
977 case PM_OP_SYNC:
978 servers = alpm_db_get_servers(alpm_pkg_get_db(pkg));
979 if(servers) {
980 pm_asprintf(&string, "%s/%s", alpm_list_getdata(servers),
981 alpm_pkg_get_filename(pkg));
982 return string;
984 case PM_OP_UPGRADE:
985 return strdup(alpm_pkg_get_filename(pkg));
986 default:
987 pm_asprintf(&string, "%s-%s", alpm_pkg_get_name(pkg), alpm_pkg_get_version(pkg));
988 return string;
992 /** Converts sizes in bytes into human readable units.
994 * @param bytes the size in bytes
995 * @param target_unit '\0' or a short label. If equal to one of the short unit
996 * labels ('B', 'K', ...) bytes is converted to target_unit; if '\0', the first
997 * unit which will bring the value to below a threshold of 2048 will be chosen.
998 * @param precision number of decimal places, ensures -0.0.0 gets rounded to
999 * 0.00; -1 if no rounding desired
1000 * @param label will be set to the appropriate unit label
1002 * @return the size in the appropriate unit
1004 double humanize_size(off_t bytes, const char target_unit, int precision,
1005 const char **label)
1007 static const char *labels[] = {"B", "KiB", "MiB", "GiB",
1008 "TiB", "PiB", "EiB", "ZiB", "YiB"};
1009 static const int unitcount = sizeof(labels) / sizeof(labels[0]);
1011 double val = (double)bytes;
1012 int index;
1014 for(index = 0; index < unitcount - 1; index++) {
1015 if(target_unit != '\0' && labels[index][0] == target_unit) {
1016 break;
1017 } else if(target_unit == '\0' && val <= 2048.0 && val >= -2048.0) {
1018 break;
1020 val /= 1024.0;
1023 if(label) {
1024 *label = labels[index];
1027 /* fix FS#27924 so that it doesn't display negative zeroes */
1028 if(precision >= 0 && val < 0.0 && val > (-0.5 / pow(10, precision))) {
1029 val = 0.0;
1032 return val;
1035 void print_packages(const alpm_list_t *packages)
1037 const alpm_list_t *i;
1038 if(!config->print_format) {
1039 config->print_format = strdup("%l");
1041 for(i = packages; i; i = alpm_list_next(i)) {
1042 alpm_pkg_t *pkg = alpm_list_getdata(i);
1043 char *string = strdup(config->print_format);
1044 char *temp = string;
1045 /* %n : pkgname */
1046 if(strstr(temp, "%n")) {
1047 string = strreplace(temp, "%n", alpm_pkg_get_name(pkg));
1048 free(temp);
1049 temp = string;
1051 /* %v : pkgver */
1052 if(strstr(temp, "%v")) {
1053 string = strreplace(temp, "%v", alpm_pkg_get_version(pkg));
1054 free(temp);
1055 temp = string;
1057 /* %l : location */
1058 if(strstr(temp, "%l")) {
1059 char *pkgloc = pkg_get_location(pkg);
1060 string = strreplace(temp, "%l", pkgloc);
1061 free(pkgloc);
1062 free(temp);
1063 temp = string;
1065 /* %r : repo */
1066 if(strstr(temp, "%r")) {
1067 const char *repo = "local";
1068 alpm_db_t *db = alpm_pkg_get_db(pkg);
1069 if(db) {
1070 repo = alpm_db_get_name(db);
1072 string = strreplace(temp, "%r", repo);
1073 free(temp);
1074 temp = string;
1076 /* %s : size */
1077 if(strstr(temp, "%s")) {
1078 char *size;
1079 pm_asprintf(&size, "%jd", (intmax_t)pkg_get_size(pkg));
1080 string = strreplace(temp, "%s", size);
1081 free(size);
1082 free(temp);
1084 printf("%s\n",string);
1085 free(string);
1089 /* Helper function for comparing strings using the
1090 * alpm "compare func" signature */
1091 int str_cmp(const void *s1, const void *s2)
1093 return strcmp(s1, s2);
1096 void display_new_optdepends(alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg)
1098 alpm_list_t *old = alpm_pkg_get_optdepends(oldpkg);
1099 alpm_list_t *new = alpm_pkg_get_optdepends(newpkg);
1100 alpm_list_t *optdeps = alpm_list_diff(new,old,str_cmp);
1101 if(optdeps) {
1102 printf(_("New optional dependencies for %s\n"), alpm_pkg_get_name(newpkg));
1103 list_display_linebreak(" ", optdeps);
1105 alpm_list_free(optdeps);
1108 void display_optdepends(alpm_pkg_t *pkg)
1110 alpm_list_t *optdeps = alpm_pkg_get_optdepends(pkg);
1111 if(optdeps) {
1112 printf(_("Optional dependencies for %s\n"), alpm_pkg_get_name(pkg));
1113 list_display_linebreak(" ", optdeps);
1117 static void display_repo_list(const char *dbname, alpm_list_t *list)
1119 const char *prefix= " ";
1121 printf(":: ");
1122 printf(_("Repository %s\n"), dbname);
1123 list_display(prefix, list);
1126 void select_display(const alpm_list_t *pkglist)
1128 const alpm_list_t *i;
1129 int nth = 1;
1130 alpm_list_t *list = NULL;
1131 char *string = NULL;
1132 const char *dbname = NULL;
1134 for (i = pkglist; i; i = i->next) {
1135 alpm_pkg_t *pkg = alpm_list_getdata(i);
1136 alpm_db_t *db = alpm_pkg_get_db(pkg);
1138 if(!dbname)
1139 dbname = alpm_db_get_name(db);
1140 if(strcmp(alpm_db_get_name(db), dbname) != 0) {
1141 display_repo_list(dbname, list);
1142 FREELIST(list);
1143 dbname = alpm_db_get_name(db);
1145 string = NULL;
1146 pm_asprintf(&string, "%d) %s", nth, alpm_pkg_get_name(pkg));
1147 list = alpm_list_add(list, string);
1148 nth++;
1150 display_repo_list(dbname, list);
1151 FREELIST(list);
1154 static int parseindex(char *s, int *val, int min, int max)
1156 char *endptr = NULL;
1157 int n = strtol(s, &endptr, 10);
1158 if(*endptr == '\0') {
1159 if(n < min || n > max) {
1160 pm_printf(ALPM_LOG_ERROR,
1161 _("invalid value: %d is not between %d and %d\n"),
1162 n, min, max);
1163 return -1;
1165 *val = n;
1166 return 0;
1167 } else {
1168 pm_printf(ALPM_LOG_ERROR, _("invalid number: %s\n"), s);
1169 return -1;
1173 static int multiselect_parse(char *array, int count, char *response)
1175 char *str, *saveptr;
1177 for (str = response; ; str = NULL) {
1178 int include = 1;
1179 int start, end;
1180 char *ends = NULL;
1181 char *starts = strtok_r(str, " ", &saveptr);
1183 if(starts == NULL) {
1184 break;
1186 strtrim(starts);
1187 int len = strlen(starts);
1188 if(len == 0)
1189 continue;
1191 if(*starts == '^') {
1192 starts++;
1193 len--;
1194 include = 0;
1195 } else if(str) {
1196 /* if first token is including, we unselect all targets */
1197 memset(array, 0, count);
1200 if(len > 1) {
1201 /* check for range */
1202 char *p;
1203 if((p = strchr(starts + 1, '-'))) {
1204 *p = 0;
1205 ends = p + 1;
1209 if(parseindex(starts, &start, 1, count) != 0)
1210 return -1;
1212 if(!ends) {
1213 array[start-1] = include;
1214 } else {
1215 int d;
1216 if(parseindex(ends, &end, start, count) != 0) {
1217 return -1;
1219 for(d = start; d <= end; d++) {
1220 array[d-1] = include;
1225 return 0;
1228 int multiselect_question(char *array, int count)
1230 char *response, *lastchar;
1231 FILE *stream;
1232 size_t response_len = 64;
1234 if(config->noconfirm) {
1235 stream = stdout;
1236 } else {
1237 /* Use stderr so questions are always displayed when redirecting output */
1238 stream = stderr;
1241 response = malloc(response_len);
1242 if(!response) {
1243 return -1;
1245 lastchar = response + response_len - 1;
1246 /* sentinel byte to later see if we filled up the entire string */
1247 *lastchar = 1;
1249 while(1) {
1250 memset(array, 1, count);
1252 fprintf(stream, "\n");
1253 fprintf(stream, _("Enter a selection (default=all)"));
1254 fprintf(stream, ": ");
1255 fflush(stream);
1257 if(config->noconfirm) {
1258 fprintf(stream, "\n");
1259 break;
1262 flush_term_input();
1264 if(fgets(response, response_len, stdin)) {
1265 const size_t response_incr = 64;
1266 /* handle buffer not being large enough to read full line case */
1267 while(*lastchar == '\0' && lastchar[-1] != '\n') {
1268 response_len += response_incr;
1269 response = realloc(response, response_len);
1270 if(!response) {
1271 return -1;
1273 lastchar = response + response_len - 1;
1274 /* sentinel byte */
1275 *lastchar = 1;
1276 if(fgets(response + response_len - response_incr - 1,
1277 response_incr + 1, stdin) == 0) {
1278 free(response);
1279 return -1;
1282 strtrim(response);
1283 if(strlen(response) > 0) {
1284 if(multiselect_parse(array, count, response) == -1) {
1285 /* only loop if user gave an invalid answer */
1286 continue;
1289 break;
1290 } else {
1291 free(response);
1292 return -1;
1296 free(response);
1297 return 0;
1300 int select_question(int count)
1302 char response[32];
1303 FILE *stream;
1304 int preset = 1;
1306 if(config->noconfirm) {
1307 stream = stdout;
1308 } else {
1309 /* Use stderr so questions are always displayed when redirecting output */
1310 stream = stderr;
1313 while(1) {
1314 fprintf(stream, "\n");
1315 fprintf(stream, _("Enter a number (default=%d)"), preset);
1316 fprintf(stream, ": ");
1318 if(config->noconfirm) {
1319 fprintf(stream, "\n");
1320 break;
1323 flush_term_input();
1325 if(fgets(response, sizeof(response), stdin)) {
1326 strtrim(response);
1327 if(strlen(response) > 0) {
1328 int n;
1329 if(parseindex(response, &n, 1, count) != 0)
1330 continue;
1331 return (n - 1);
1334 break;
1337 return (preset - 1);
1341 /* presents a prompt and gets a Y/N answer */
1342 static int question(short preset, char *fmt, va_list args)
1344 char response[32];
1345 FILE *stream;
1347 if(config->noconfirm) {
1348 stream = stdout;
1349 } else {
1350 /* Use stderr so questions are always displayed when redirecting output */
1351 stream = stderr;
1354 /* ensure all text makes it to the screen before we prompt the user */
1355 fflush(stdout);
1356 fflush(stderr);
1358 vfprintf(stream, fmt, args);
1360 if(preset) {
1361 fprintf(stream, " %s ", _("[Y/n]"));
1362 } else {
1363 fprintf(stream, " %s ", _("[y/N]"));
1366 if(config->noconfirm) {
1367 fprintf(stream, "\n");
1368 return preset;
1371 fflush(stream);
1372 flush_term_input();
1374 if(fgets(response, sizeof(response), stdin)) {
1375 strtrim(response);
1376 if(strlen(response) == 0) {
1377 return preset;
1380 /* if stdin is piped, response does not get printed out, and as a result
1381 * a \n is missing, resulting in broken output (FS#27909) */
1382 if(!isatty(fileno(stdin))) {
1383 fprintf(stream, "%s\n", response);
1386 if(strcasecmp(response, _("Y")) == 0 || strcasecmp(response, _("YES")) == 0) {
1387 return 1;
1388 } else if(strcasecmp(response, _("N")) == 0 || strcasecmp(response, _("NO")) == 0) {
1389 return 0;
1392 return 0;
1395 int yesno(char *fmt, ...)
1397 int ret;
1398 va_list args;
1400 va_start(args, fmt);
1401 ret = question(1, fmt, args);
1402 va_end(args);
1404 return ret;
1407 int noyes(char *fmt, ...)
1409 int ret;
1410 va_list args;
1412 va_start(args, fmt);
1413 ret = question(0, fmt, args);
1414 va_end(args);
1416 return ret;
1419 int pm_printf(alpm_loglevel_t level, const char *format, ...)
1421 int ret;
1422 va_list args;
1424 /* print the message using va_arg list */
1425 va_start(args, format);
1426 ret = pm_vfprintf(stderr, level, format, args);
1427 va_end(args);
1429 return ret;
1432 int pm_asprintf(char **string, const char *format, ...)
1434 int ret = 0;
1435 va_list args;
1437 /* print the message using va_arg list */
1438 va_start(args, format);
1439 if(vasprintf(string, format, args) == -1) {
1440 pm_printf(ALPM_LOG_ERROR, _("failed to allocate string\n"));
1441 ret = -1;
1443 va_end(args);
1445 return ret;
1448 int pm_vasprintf(char **string, alpm_loglevel_t level, const char *format, va_list args)
1450 int ret = 0;
1451 char *msg = NULL;
1453 /* if current logmask does not overlap with level, do not print msg */
1454 if(!(config->logmask & level)) {
1455 return ret;
1458 /* print the message using va_arg list */
1459 ret = vasprintf(&msg, format, args);
1461 /* print a prefix to the message */
1462 switch(level) {
1463 case ALPM_LOG_ERROR:
1464 pm_asprintf(string, _("error: %s"), msg);
1465 break;
1466 case ALPM_LOG_WARNING:
1467 pm_asprintf(string, _("warning: %s"), msg);
1468 break;
1469 case ALPM_LOG_DEBUG:
1470 pm_asprintf(string, "debug: %s", msg);
1471 break;
1472 case ALPM_LOG_FUNCTION:
1473 pm_asprintf(string, "function: %s", msg);
1474 break;
1475 default:
1476 pm_asprintf(string, "%s", msg);
1477 break;
1479 free(msg);
1481 return ret;
1484 int pm_vfprintf(FILE *stream, alpm_loglevel_t level, const char *format, va_list args)
1486 int ret = 0;
1488 /* if current logmask does not overlap with level, do not print msg */
1489 if(!(config->logmask & level)) {
1490 return ret;
1493 #if defined(PACMAN_DEBUG)
1494 /* If debug is on, we'll timestamp the output */
1495 if(config->logmask & ALPM_LOG_DEBUG) {
1496 time_t t;
1497 struct tm *tmp;
1498 char timestr[10] = {0};
1500 t = time(NULL);
1501 tmp = localtime(&t);
1502 strftime(timestr, 9, "%H:%M:%S", tmp);
1503 timestr[8] = '\0';
1505 fprintf(stream, "[%s] ", timestr);
1507 #endif
1509 /* print a prefix to the message */
1510 switch(level) {
1511 case ALPM_LOG_ERROR:
1512 fprintf(stream, _("error: "));
1513 break;
1514 case ALPM_LOG_WARNING:
1515 fprintf(stream, _("warning: "));
1516 break;
1517 case ALPM_LOG_DEBUG:
1518 fprintf(stream, "debug: ");
1519 break;
1520 case ALPM_LOG_FUNCTION:
1521 fprintf(stream, "function: ");
1522 break;
1523 default:
1524 break;
1527 /* print the message using va_arg list */
1528 ret = vfprintf(stream, format, args);
1529 return ret;
1532 #ifndef HAVE_STRNDUP
1533 /* A quick and dirty implementation derived from glibc */
1534 static size_t strnlen(const char *s, size_t max)
1536 register const char *p;
1537 for(p = s; *p && max--; ++p);
1538 return (p - s);
1541 char *strndup(const char *s, size_t n)
1543 size_t len = strnlen(s, n);
1544 char *new = (char *) malloc(len + 1);
1546 if(new == NULL)
1547 return NULL;
1549 new[len] = '\0';
1550 return (char *)memcpy(new, s, len);
1552 #endif
1554 /* vim: set ts=2 sw=2 noet: */