Update copyright on changed files since beginning of year
[pacman-ng.git] / src / pacman / util.c
blobfacdc1c99abda18b8a3e0c6747e20f863e7c17dc
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 #ifdef HAVE_TERMIOS_H
40 #include <termios.h> /* tcflush */
41 #endif
43 #include <alpm.h>
44 #include <alpm_list.h>
46 /* pacman */
47 #include "util.h"
48 #include "conf.h"
49 #include "callback.h"
52 int trans_init(alpm_transflag_t flags, int check_valid)
54 int ret;
56 check_syncdbs(0, check_valid);
58 ret = alpm_trans_init(config->handle, flags);
59 if(ret == -1) {
60 trans_init_error();
61 return -1;
63 return 0;
66 void trans_init_error(void)
68 enum _alpm_errno_t err = alpm_errno(config->handle);
69 pm_printf(ALPM_LOG_ERROR, _("failed to init transaction (%s)\n"),
70 alpm_strerror(err));
71 if(err == ALPM_ERR_HANDLE_LOCK) {
72 fprintf(stderr, _(" if you're sure a package manager is not already\n"
73 " running, you can remove %s\n"),
74 alpm_option_get_lockfile(config->handle));
78 int trans_release(void)
80 if(alpm_trans_release(config->handle) == -1) {
81 pm_printf(ALPM_LOG_ERROR, _("failed to release transaction (%s)\n"),
82 alpm_strerror(alpm_errno(config->handle)));
83 return -1;
85 return 0;
88 int needs_root(void)
90 switch(config->op) {
91 case PM_OP_DATABASE:
92 return 1;
93 case PM_OP_UPGRADE:
94 case PM_OP_REMOVE:
95 return !config->print;
96 case PM_OP_SYNC:
97 return (config->op_s_clean || config->op_s_sync ||
98 (!config->group && !config->op_s_info && !config->op_q_list &&
99 !config->op_s_search && !config->print));
100 default:
101 return 0;
105 int check_syncdbs(size_t need_repos, int check_valid)
107 int ret = 0;
108 alpm_list_t *i;
109 alpm_list_t *sync_dbs = alpm_option_get_syncdbs(config->handle);
111 if(need_repos && sync_dbs == NULL) {
112 pm_printf(ALPM_LOG_ERROR, _("no usable package repositories configured.\n"));
113 return 1;
116 if(check_valid) {
117 /* ensure all known dbs are valid */
118 for(i = sync_dbs; i; i = alpm_list_next(i)) {
119 alpm_db_t *db = i->data;
120 if(alpm_db_get_valid(db)) {
121 pm_printf(ALPM_LOG_ERROR, _("database '%s' is not valid (%s)\n"),
122 alpm_db_get_name(db), alpm_strerror(alpm_errno(config->handle)));
123 ret = 1;
127 return ret;
130 /* discard unhandled input on the terminal's input buffer */
131 static int flush_term_input(void) {
132 #ifdef HAVE_TCFLUSH
133 if(isatty(fileno(stdin))) {
134 return tcflush(fileno(stdin), TCIFLUSH);
136 #endif
138 /* fail silently */
139 return 0;
142 /* gets the current screen column width */
143 unsigned short getcols(void)
145 const unsigned short default_tty = 80;
146 const unsigned short default_notty = 0;
147 unsigned short termwidth = 0;
149 if(!isatty(fileno(stdout))) {
150 return default_notty;
153 #ifdef TIOCGSIZE
154 struct ttysize win;
155 if(ioctl(1, TIOCGSIZE, &win) == 0) {
156 termwidth = win.ts_cols;
158 #elif defined(TIOCGWINSZ)
159 struct winsize win;
160 if(ioctl(1, TIOCGWINSZ, &win) == 0) {
161 termwidth = win.ws_col;
163 #endif
164 return termwidth == 0 ? default_tty : termwidth;
167 /* does the same thing as 'rm -rf' */
168 int rmrf(const char *path)
170 int errflag = 0;
171 struct dirent *dp;
172 DIR *dirp;
174 if(!unlink(path)) {
175 return 0;
176 } else {
177 if(errno == ENOENT) {
178 return 0;
179 } else if(errno == EPERM) {
180 /* fallthrough */
181 } else if(errno == EISDIR) {
182 /* fallthrough */
183 } else if(errno == ENOTDIR) {
184 return 1;
185 } else {
186 /* not a directory */
187 return 1;
190 dirp = opendir(path);
191 if(!dirp) {
192 return 1;
194 for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
195 if(dp->d_name) {
196 if(strcmp(dp->d_name, "..") != 0 && strcmp(dp->d_name, ".") != 0) {
197 char name[PATH_MAX];
198 snprintf(name, PATH_MAX, "%s/%s", path, dp->d_name);
199 errflag += rmrf(name);
203 closedir(dirp);
204 if(rmdir(path)) {
205 errflag++;
207 return errflag;
211 /** Parse the basename of a program from a path.
212 * @param path path to parse basename from
214 * @return everything following the final '/'
216 const char *mbasename(const char *path)
218 const char *last = strrchr(path, '/');
219 if(last) {
220 return last + 1;
222 return path;
225 /** Parse the dirname of a program from a path.
226 * The path returned should be freed.
227 * @param path path to parse dirname from
229 * @return everything preceding the final '/'
231 char *mdirname(const char *path)
233 char *ret, *last;
235 /* null or empty path */
236 if(path == NULL || path == '\0') {
237 return strdup(".");
240 ret = strdup(path);
241 last = strrchr(ret, '/');
243 if(last != NULL) {
244 /* we found a '/', so terminate our string */
245 *last = '\0';
246 return ret;
248 /* no slash found */
249 free(ret);
250 return strdup(".");
253 /* output a string, but wrap words properly with a specified indentation
255 void indentprint(const char *str, size_t indent)
257 wchar_t *wcstr;
258 const wchar_t *p;
259 int len, cidx;
260 const unsigned short cols = getcols();
262 if(!str) {
263 return;
266 /* if we're not a tty, or our tty is not wide enough that wrapping even makes
267 * sense, print without indenting */
268 if(cols == 0 || indent > cols) {
269 printf("%s", str);
270 return;
273 len = strlen(str) + 1;
274 wcstr = calloc(len, sizeof(wchar_t));
275 len = mbstowcs(wcstr, str, len);
276 p = wcstr;
277 cidx = indent;
279 if(!p || !len) {
280 return;
283 while(*p) {
284 if(*p == L' ') {
285 const wchar_t *q, *next;
286 p++;
287 if(p == NULL || *p == L' ') continue;
288 next = wcschr(p, L' ');
289 if(next == NULL) {
290 next = p + wcslen(p);
292 /* len captures # cols */
293 len = 0;
294 q = p;
295 while(q < next) {
296 len += wcwidth(*q++);
298 if(len > (cols - cidx - 1)) {
299 /* wrap to a newline and reindent */
300 printf("\n%-*s", (int)indent, "");
301 cidx = indent;
302 } else {
303 printf(" ");
304 cidx++;
306 continue;
308 printf("%lc", (wint_t)*p);
309 cidx += wcwidth(*p);
310 p++;
312 free(wcstr);
315 /* Trim whitespace and newlines from a string
317 char *strtrim(char *str)
319 char *pch = str;
321 if(str == NULL || *str == '\0') {
322 /* string is empty, so we're done. */
323 return str;
326 while(isspace((unsigned char)*pch)) {
327 pch++;
329 if(pch != str) {
330 size_t len = strlen(pch);
331 if(len) {
332 memmove(str, pch, len + 1);
333 } else {
334 *str = '\0';
338 /* check if there wasn't anything but whitespace in the string. */
339 if(*str == '\0') {
340 return str;
343 pch = (str + (strlen(str) - 1));
344 while(isspace((unsigned char)*pch)) {
345 pch--;
347 *++pch = '\0';
349 return str;
352 /* Replace all occurances of 'needle' with 'replace' in 'str', returning
353 * a new string (must be free'd) */
354 char *strreplace(const char *str, const char *needle, const char *replace)
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;
362 if(!str) {
363 return NULL;
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);
374 /* no occurences of needle found */
375 if(!list) {
376 return strdup(str);
378 /* size of new string = size of old string + "number of occurences 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;
387 p = str;
388 newp = newstr;
389 for(i = list; i; i = alpm_list_next(i)) {
390 q = alpm_list_getdata(i);
391 if(q > p) {
392 /* add chars between this occurence and last occurence, if any */
393 memcpy(newp, p, (size_t)(q - p));
394 newp += q - p;
396 memcpy(newp, replace, replacesz);
397 newp += replacesz;
398 p = q + needlesz;
400 alpm_list_free(list);
402 if(*p) {
403 /* add the rest of 'p' */
404 strcpy(newp, p);
407 return newstr;
410 /** Splits a string into a list of strings using the chosen character as
411 * a delimiter.
413 * @param str the string to split
414 * @param splitchar the character to split at
416 * @return a list containing the duplicated strings
418 alpm_list_t *strsplit(const char *str, const char splitchar)
420 alpm_list_t *list = NULL;
421 const char *prev = str;
422 char *dup = NULL;
424 while((str = strchr(str, splitchar))) {
425 dup = strndup(prev, (size_t)(str - prev));
426 if(dup == NULL) {
427 return NULL;
429 list = alpm_list_add(list, dup);
431 str++;
432 prev = str;
435 dup = strdup(prev);
436 if(dup == NULL) {
437 return NULL;
439 list = alpm_list_add(list, dup);
441 return list;
444 static size_t string_length(const char *s)
446 int len;
447 wchar_t *wcstr;
449 if(!s || s[0] == '\0') {
450 return 0;
452 /* len goes from # bytes -> # chars -> # cols */
453 len = strlen(s) + 1;
454 wcstr = calloc(len, sizeof(wchar_t));
455 len = mbstowcs(wcstr, s, len);
456 len = wcswidth(wcstr, len);
457 free(wcstr);
459 return len;
462 void string_display(const char *title, const char *string)
464 if(title) {
465 printf("%s ", title);
467 if(string == NULL || string[0] == '\0') {
468 printf(_("None"));
469 } else {
470 /* compute the length of title + a space */
471 size_t len = string_length(title) + 1;
472 indentprint(string, len);
474 printf("\n");
477 static void table_print_line(const alpm_list_t *line,
478 size_t colcount, size_t *widths)
480 size_t i;
481 const alpm_list_t *curcell;
483 for(i = 0, curcell = line; curcell && i < colcount;
484 i++, curcell = alpm_list_next(curcell)) {
485 const char *value = curcell->data;
486 size_t len = string_length(value);
487 /* silly printf requires padding size to be an int */
488 int padding = (int)widths[i] - (int)len;
489 if(padding < 0) {
490 padding = 0;
492 /* left-align all but the last column */
493 if(i + 1 < colcount) {
494 printf("%s%*s", value, padding, "");
495 } else {
496 printf("%*s%s", padding, "", value);
500 printf("\n");
503 /* find the max string width of each column */
504 static size_t table_calc_widths(const alpm_list_t *header,
505 const alpm_list_t *rows, size_t totalcols, size_t **widths)
507 const alpm_list_t *i;
508 const unsigned short padding = 2;
509 size_t curcol, totalwidth = 0;
510 size_t *colwidths;
512 if(totalcols <= 0) {
513 return 0;
516 colwidths = malloc(totalcols * sizeof(size_t));
517 if(!colwidths) {
518 return 0;
520 /* header determines column count and initial values of longest_strs */
521 for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) {
522 colwidths[curcol] = string_length(alpm_list_getdata(i));
525 /* now find the longest string in each column */
526 for(i = rows; i; i = alpm_list_next(i)) {
527 /* grab first column of each row and iterate through columns */
528 const alpm_list_t *j = alpm_list_getdata(i);
529 for(curcol = 0; j; j = alpm_list_next(j), curcol++) {
530 char *str = alpm_list_getdata(j);
531 size_t str_len = string_length(str);
533 if(str_len > colwidths[curcol]) {
534 colwidths[curcol] = str_len;
539 for(i = header, curcol = 0; i; i = alpm_list_next(i), curcol++) {
540 /* pad everything but the last column */
541 if(curcol + 1 < totalcols) {
542 colwidths[curcol] += padding;
544 totalwidth += colwidths[curcol];
547 *widths = colwidths;
548 return totalwidth;
551 /** Displays the list in table format
553 * @param title the tables title
554 * @param header the column headers. column count is determined by the nr
555 * of headers
556 * @param rows the rows to display as a list of lists of strings. the outer
557 * list represents the rows, the inner list the cells (= columns)
559 * @return -1 if not enough terminal cols available, else 0
561 int table_display(const char *title, const alpm_list_t *header,
562 const alpm_list_t *rows)
564 const alpm_list_t *i;
565 size_t *widths = NULL, totalcols, totalwidth;
567 if(rows == NULL || header == NULL) {
568 return 0;
571 totalcols = alpm_list_count(header);
572 totalwidth = table_calc_widths(header, rows, totalcols, &widths);
573 /* return -1 if terminal is not wide enough */
574 if(totalwidth > getcols()) {
575 pm_printf(ALPM_LOG_WARNING,
576 _("insufficient columns available for table display\n"));
577 return -1;
579 if(!totalwidth || !widths) {
580 return -1;
583 if(title != NULL) {
584 printf("%s\n\n", title);
587 table_print_line(header, totalcols, widths);
588 printf("\n");
590 for(i = rows; i; i = alpm_list_next(i)) {
591 table_print_line(alpm_list_getdata(i), totalcols, widths);
594 free(widths);
595 return 0;
598 void list_display(const char *title, const alpm_list_t *list)
600 const alpm_list_t *i;
601 size_t len = 0;
603 if(title) {
604 len = string_length(title) + 1;
605 printf("%s ", title);
608 if(!list) {
609 printf("%s\n", _("None"));
610 } else {
611 const unsigned short maxcols = getcols();
612 size_t cols = len;
613 const char *str = alpm_list_getdata(list);
614 printf("%s", str);
615 cols += string_length(str);
616 for(i = alpm_list_next(list); i; i = alpm_list_next(i)) {
617 str = alpm_list_getdata(i);
618 size_t s = string_length(str);
619 /* wrap only if we have enough usable column space */
620 if(maxcols > len && cols + s + 2 >= maxcols) {
621 size_t j;
622 cols = len;
623 printf("\n");
624 for (j = 1; j <= len; j++) {
625 printf(" ");
627 } else if(cols != len) {
628 /* 2 spaces are added if this is not the first element on a line. */
629 printf(" ");
630 cols += 2;
632 printf("%s", str);
633 cols += s;
635 printf("\n");
639 void list_display_linebreak(const char *title, const alpm_list_t *list)
641 size_t len = 0;
643 if(title) {
644 len = string_length(title) + 1;
645 printf("%s ", title);
648 if(!list) {
649 printf("%s\n", _("None"));
650 } else {
651 const alpm_list_t *i;
652 /* Print the first element */
653 indentprint((const char *) alpm_list_getdata(list), len);
654 printf("\n");
655 /* Print the rest */
656 for(i = alpm_list_next(list); i; i = alpm_list_next(i)) {
657 size_t j;
658 for(j = 1; j <= len; j++) {
659 printf(" ");
661 indentprint((const char *) alpm_list_getdata(i), len);
662 printf("\n");
667 void signature_display(const char *title, alpm_siglist_t *siglist)
669 size_t len = 0;
671 if(title) {
672 len = string_length(title) + 1;
673 printf("%s ", title);
675 if(siglist->count == 0) {
676 printf(_("None"));
677 } else {
678 size_t i;
679 for(i = 0; i < siglist->count; i++) {
680 char *sigline;
681 const char *status, *validity, *name;
682 int ret;
683 alpm_sigresult_t *result = siglist->results + i;
684 /* Don't re-indent the first result */
685 if(i != 0) {
686 size_t j;
687 for(j = 1; j <= len; j++) {
688 printf(" ");
691 switch(result->status) {
692 case ALPM_SIGSTATUS_VALID:
693 status = _("Valid");
694 break;
695 case ALPM_SIGSTATUS_KEY_EXPIRED:
696 status = _("Key expired");
697 break;
698 case ALPM_SIGSTATUS_SIG_EXPIRED:
699 status = _("Expired");
700 break;
701 case ALPM_SIGSTATUS_INVALID:
702 status = _("Invalid");
703 break;
704 case ALPM_SIGSTATUS_KEY_UNKNOWN:
705 status = _("Key unknown");
706 break;
707 case ALPM_SIGSTATUS_KEY_DISABLED:
708 status = _("Key disabled");
709 break;
710 default:
711 status = _("Signature error");
712 break;
714 switch(result->validity) {
715 case ALPM_SIGVALIDITY_FULL:
716 validity = _("full trust");
717 break;
718 case ALPM_SIGVALIDITY_MARGINAL:
719 validity = _("marginal trust");
720 break;
721 case ALPM_SIGVALIDITY_NEVER:
722 validity = _("never trust");
723 break;
724 case ALPM_SIGVALIDITY_UNKNOWN:
725 default:
726 validity = _("unknown trust");
727 break;
729 name = result->key.uid ? result->key.uid : result->key.fingerprint;
730 ret = pm_asprintf(&sigline, _("%s, %s from \"%s\""),
731 status, validity, name);
732 if(ret == -1) {
733 pm_printf(ALPM_LOG_ERROR, _("failed to allocate string\n"));
734 continue;
736 indentprint(sigline, len);
737 printf("\n");
738 free(sigline);
743 /* creates a header row for use with table_display */
744 static alpm_list_t *create_verbose_header(int dl_size)
746 alpm_list_t *res = NULL;
747 char *str;
749 str = _("Name");
750 res = alpm_list_add(res, str);
751 str = _("Old Version");
752 res = alpm_list_add(res, str);
753 str = _("New Version");
754 res = alpm_list_add(res, str);
755 str = _("Net Change");
756 res = alpm_list_add(res, str);
757 if(dl_size) {
758 str = _("Download Size");
759 res = alpm_list_add(res, str);
762 return res;
765 /* returns package info as list of strings */
766 static alpm_list_t *create_verbose_row(pm_target_t *target, int dl_size)
768 char *str;
769 off_t size = 0;
770 double human_size;
771 const char *label;
772 alpm_list_t *ret = NULL;
774 /* a row consists of the package name, */
775 if(target->install) {
776 pm_asprintf(&str, "%s", alpm_pkg_get_name(target->install));
777 } else {
778 pm_asprintf(&str, "%s", alpm_pkg_get_name(target->remove));
780 ret = alpm_list_add(ret, str);
782 /* old and new versions */
783 pm_asprintf(&str, "%s",
784 target->remove != NULL ? alpm_pkg_get_version(target->remove) : "");
785 ret = alpm_list_add(ret, str);
787 pm_asprintf(&str, "%s",
788 target->install != NULL ? alpm_pkg_get_version(target->install) : "");
789 ret = alpm_list_add(ret, str);
791 /* and size */
792 size -= target->remove ? alpm_pkg_get_isize(target->remove) : 0;
793 size += target->install ? alpm_pkg_get_isize(target->install) : 0;
794 human_size = humanize_size(size, 'M', &label);
795 pm_asprintf(&str, "%.2f %s", human_size, label);
796 ret = alpm_list_add(ret, str);
798 if(dl_size) {
799 size = target->install ? alpm_pkg_download_size(target->install) : 0;
800 human_size = humanize_size(size, 'M', &label);
801 if(size != 0) {
802 pm_asprintf(&str, "%.2f %s", human_size, label);
803 } else {
804 str = strdup("");
806 ret = alpm_list_add(ret, str);
809 return ret;
812 /* prepare a list of pkgs to display */
813 static void _display_targets(alpm_list_t *targets, int verbose)
815 char *str;
816 const char *label;
817 double size;
818 off_t isize = 0, rsize = 0, dlsize = 0;
819 alpm_list_t *i, *rows = NULL, *names = NULL;
820 int show_dl_size = config->op == PM_OP_SYNC;
822 if(!targets) {
823 return;
826 /* gather package info */
827 for(i = targets; i; i = alpm_list_next(i)) {
828 pm_target_t *target = alpm_list_getdata(i);
830 if(target->install) {
831 dlsize += alpm_pkg_download_size(target->install);
832 isize += alpm_pkg_get_isize(target->install);
834 if(target->remove) {
835 /* add up size of all removed packages */
836 rsize += alpm_pkg_get_isize(target->remove);
840 /* form data for both verbose and non-verbose display */
841 for(i = targets; i; i = alpm_list_next(i)) {
842 pm_target_t *target = i->data;
844 rows = alpm_list_add(rows, create_verbose_row(target, show_dl_size));
845 if(target->install) {
846 pm_asprintf(&str, "%s-%s", alpm_pkg_get_name(target->install),
847 alpm_pkg_get_version(target->install));
848 } else if(isize == 0) {
849 pm_asprintf(&str, "%s-%s", alpm_pkg_get_name(target->remove),
850 alpm_pkg_get_version(target->remove));
851 } else {
852 pm_asprintf(&str, "%s-%s [removal]", alpm_pkg_get_name(target->remove),
853 alpm_pkg_get_version(target->remove));
855 names = alpm_list_add(names, str);
858 /* print to screen */
859 pm_asprintf(&str, _("Targets (%d):"), alpm_list_count(targets));
861 printf("\n");
862 if(verbose) {
863 alpm_list_t *header = create_verbose_header(show_dl_size);
864 if(table_display(str, header, rows) != 0) {
865 /* fallback to list display if table wouldn't fit */
866 list_display(str, names);
868 alpm_list_free(header);
869 } else {
870 list_display(str, names);
872 printf("\n");
874 /* rows is a list of lists of strings, free inner lists here */
875 for(i = rows; i; i = alpm_list_next(i)) {
876 alpm_list_t *lp = alpm_list_getdata(i);
877 FREELIST(lp);
879 alpm_list_free(rows);
880 FREELIST(names);
881 free(str);
883 if(dlsize > 0 || config->op_s_downloadonly) {
884 size = humanize_size(dlsize, 'M', &label);
885 printf(_("Total Download Size: %.2f %s\n"), size, label);
887 if(!config->op_s_downloadonly) {
888 if(isize > 0) {
889 size = humanize_size(isize, 'M', &label);
890 printf(_("Total Installed Size: %.2f %s\n"), size, label);
892 if(rsize > 0 && isize == 0) {
893 size = humanize_size(rsize, 'M', &label);
894 printf(_("Total Removed Size: %.2f %s\n"), size, label);
896 /* only show this net value if different from raw installed size */
897 if(isize > 0 && rsize > 0) {
898 size = humanize_size(isize - rsize, 'M', &label);
899 printf(_("Net Upgrade Size: %.2f %s\n"), size, label);
904 static int target_cmp(const void *p1, const void *p2)
906 const pm_target_t *targ1 = p1;
907 const pm_target_t *targ2 = p2;
908 /* explicit are always sorted after implicit (e.g. deps, pulled targets) */
909 if(targ1->is_explicit != targ2->is_explicit) {
910 return targ1->is_explicit > targ2->is_explicit;
912 const char *name1 = targ1->install ?
913 alpm_pkg_get_name(targ1->install) : alpm_pkg_get_name(targ1->remove);
914 const char *name2 = targ2->install ?
915 alpm_pkg_get_name(targ2->install) : alpm_pkg_get_name(targ2->remove);
916 return strcmp(name1, name2);
919 static int pkg_cmp(const void *p1, const void *p2)
921 /* explicit cast due to (un)necessary removal of const */
922 alpm_pkg_t *pkg1 = (alpm_pkg_t *)p1;
923 alpm_pkg_t *pkg2 = (alpm_pkg_t *)p2;
924 return strcmp(alpm_pkg_get_name(pkg1), alpm_pkg_get_name(pkg2));
927 void display_targets(void)
929 alpm_list_t *i, *targets = NULL;
930 alpm_db_t *db_local = alpm_option_get_localdb(config->handle);
932 for(i = alpm_trans_get_add(config->handle); i; i = alpm_list_next(i)) {
933 alpm_pkg_t *pkg = alpm_list_getdata(i);
934 pm_target_t *targ = calloc(1, sizeof(pm_target_t));
935 if(!targ) return;
936 targ->install = pkg;
937 targ->remove = alpm_db_get_pkg(db_local, alpm_pkg_get_name(pkg));
938 if(alpm_list_find(config->explicit_adds, pkg, pkg_cmp)) {
939 targ->is_explicit = 1;
941 targets = alpm_list_add(targets, targ);
943 for(i = alpm_trans_get_remove(config->handle); i; i = alpm_list_next(i)) {
944 alpm_pkg_t *pkg = alpm_list_getdata(i);
945 pm_target_t *targ = calloc(1, sizeof(pm_target_t));
946 if(!targ) return;
947 targ->remove = pkg;
948 if(alpm_list_find(config->explicit_removes, pkg, pkg_cmp)) {
949 targ->is_explicit = 1;
951 targets = alpm_list_add(targets, targ);
954 targets = alpm_list_msort(targets, alpm_list_count(targets), target_cmp);
955 _display_targets(targets, config->verbosepkglists);
956 FREELIST(targets);
959 static off_t pkg_get_size(alpm_pkg_t *pkg)
961 switch(config->op) {
962 case PM_OP_SYNC:
963 return alpm_pkg_download_size(pkg);
964 case PM_OP_UPGRADE:
965 return alpm_pkg_get_size(pkg);
966 default:
967 return alpm_pkg_get_isize(pkg);
971 static char *pkg_get_location(alpm_pkg_t *pkg)
973 alpm_list_t *servers;
974 char *string = NULL;
975 switch(config->op) {
976 case PM_OP_SYNC:
977 servers = alpm_db_get_servers(alpm_pkg_get_db(pkg));
978 if(servers) {
979 pm_asprintf(&string, "%s/%s", alpm_list_getdata(servers),
980 alpm_pkg_get_filename(pkg));
981 return string;
983 case PM_OP_UPGRADE:
984 return strdup(alpm_pkg_get_filename(pkg));
985 default:
986 pm_asprintf(&string, "%s-%s", alpm_pkg_get_name(pkg), alpm_pkg_get_version(pkg));
987 return string;
991 /** Converts sizes in bytes into human readable units.
993 * @param bytes the size in bytes
994 * @param target_unit '\0' or a short label. If equal to one of the short unit
995 * labels ('B', 'K', ...) bytes is converted to target_unit; if '\0', the first
996 * unit which will bring the value to below a threshold of 2048 will be chosen.
997 * @param long_labels whether to use short ("K") or long ("KiB") unit labels
998 * @param label will be set to the appropriate unit label
1000 * @return the size in the appropriate unit
1002 double humanize_size(off_t bytes, const char target_unit, const char **label)
1004 static const char *labels[] = {"B", "KiB", "MiB", "GiB",
1005 "TiB", "PiB", "EiB", "ZiB", "YiB"};
1006 static const int unitcount = sizeof(labels) / sizeof(labels[0]);
1008 double val = (double)bytes;
1009 int index;
1011 for(index = 0; index < unitcount - 1; index++) {
1012 if(target_unit != '\0' && labels[index][0] == target_unit) {
1013 break;
1014 } else if(target_unit == '\0' && val <= 2048.0 && val >= -2048.0) {
1015 break;
1017 val /= 1024.0;
1020 if(label) {
1021 *label = labels[index];
1024 return val;
1027 void print_packages(const alpm_list_t *packages)
1029 const alpm_list_t *i;
1030 if(!config->print_format) {
1031 config->print_format = strdup("%l");
1033 for(i = packages; i; i = alpm_list_next(i)) {
1034 alpm_pkg_t *pkg = alpm_list_getdata(i);
1035 char *string = strdup(config->print_format);
1036 char *temp = string;
1037 /* %n : pkgname */
1038 if(strstr(temp, "%n")) {
1039 string = strreplace(temp, "%n", alpm_pkg_get_name(pkg));
1040 free(temp);
1041 temp = string;
1043 /* %v : pkgver */
1044 if(strstr(temp, "%v")) {
1045 string = strreplace(temp, "%v", alpm_pkg_get_version(pkg));
1046 free(temp);
1047 temp = string;
1049 /* %l : location */
1050 if(strstr(temp, "%l")) {
1051 char *pkgloc = pkg_get_location(pkg);
1052 string = strreplace(temp, "%l", pkgloc);
1053 free(pkgloc);
1054 free(temp);
1055 temp = string;
1057 /* %r : repo */
1058 if(strstr(temp, "%r")) {
1059 const char *repo = "local";
1060 alpm_db_t *db = alpm_pkg_get_db(pkg);
1061 if(db) {
1062 repo = alpm_db_get_name(db);
1064 string = strreplace(temp, "%r", repo);
1065 free(temp);
1066 temp = string;
1068 /* %s : size */
1069 if(strstr(temp, "%s")) {
1070 char *size;
1071 pm_asprintf(&size, "%jd", (intmax_t)pkg_get_size(pkg));
1072 string = strreplace(temp, "%s", size);
1073 free(size);
1074 free(temp);
1076 printf("%s\n",string);
1077 free(string);
1081 /* Helper function for comparing strings using the
1082 * alpm "compare func" signature */
1083 int str_cmp(const void *s1, const void *s2)
1085 return strcmp(s1, s2);
1088 void display_new_optdepends(alpm_pkg_t *oldpkg, alpm_pkg_t *newpkg)
1090 alpm_list_t *old = alpm_pkg_get_optdepends(oldpkg);
1091 alpm_list_t *new = alpm_pkg_get_optdepends(newpkg);
1092 alpm_list_t *optdeps = alpm_list_diff(new,old,str_cmp);
1093 if(optdeps) {
1094 printf(_("New optional dependencies for %s\n"), alpm_pkg_get_name(newpkg));
1095 list_display_linebreak(" ", optdeps);
1097 alpm_list_free(optdeps);
1100 void display_optdepends(alpm_pkg_t *pkg)
1102 alpm_list_t *optdeps = alpm_pkg_get_optdepends(pkg);
1103 if(optdeps) {
1104 printf(_("Optional dependencies for %s\n"), alpm_pkg_get_name(pkg));
1105 list_display_linebreak(" ", optdeps);
1109 static void display_repo_list(const char *dbname, alpm_list_t *list)
1111 const char *prefix= " ";
1113 printf(":: ");
1114 printf(_("Repository %s\n"), dbname);
1115 list_display(prefix, list);
1118 void select_display(const alpm_list_t *pkglist)
1120 const alpm_list_t *i;
1121 int nth = 1;
1122 alpm_list_t *list = NULL;
1123 char *string = NULL;
1124 const char *dbname = NULL;
1126 for (i = pkglist; i; i = i->next) {
1127 alpm_pkg_t *pkg = alpm_list_getdata(i);
1128 alpm_db_t *db = alpm_pkg_get_db(pkg);
1130 if(!dbname)
1131 dbname = alpm_db_get_name(db);
1132 if(strcmp(alpm_db_get_name(db), dbname) != 0) {
1133 display_repo_list(dbname, list);
1134 FREELIST(list);
1135 dbname = alpm_db_get_name(db);
1137 string = NULL;
1138 pm_asprintf(&string, "%d) %s", nth, alpm_pkg_get_name(pkg));
1139 list = alpm_list_add(list, string);
1140 nth++;
1142 display_repo_list(dbname, list);
1143 FREELIST(list);
1146 static int parseindex(char *s, int *val, int min, int max)
1148 char *endptr = NULL;
1149 int n = strtol(s, &endptr, 10);
1150 if(*endptr == '\0') {
1151 if(n < min || n > max) {
1152 pm_printf(ALPM_LOG_ERROR,
1153 _("invalid value: %d is not between %d and %d\n"),
1154 n, min, max);
1155 return -1;
1157 *val = n;
1158 return 0;
1159 } else {
1160 pm_printf(ALPM_LOG_ERROR, _("invalid number: %s\n"), s);
1161 return -1;
1165 static int multiselect_parse(char *array, int count, char *response)
1167 char *str, *saveptr;
1169 for (str = response; ; str = NULL) {
1170 int include = 1;
1171 int start, end;
1172 char *ends = NULL;
1173 char *starts = strtok_r(str, " ", &saveptr);
1175 if(starts == NULL) {
1176 break;
1178 strtrim(starts);
1179 int len = strlen(starts);
1180 if(len == 0)
1181 continue;
1183 if(*starts == '^') {
1184 starts++;
1185 len--;
1186 include = 0;
1187 } else if(str) {
1188 /* if first token is including, we unselect all targets */
1189 memset(array, 0, count);
1192 if(len > 1) {
1193 /* check for range */
1194 char *p;
1195 if((p = strchr(starts + 1, '-'))) {
1196 *p = 0;
1197 ends = p + 1;
1201 if(parseindex(starts, &start, 1, count) != 0)
1202 return -1;
1204 if(!ends) {
1205 array[start-1] = include;
1206 } else {
1207 int d;
1208 if(parseindex(ends, &end, start, count) != 0) {
1209 return -1;
1211 for(d = start; d <= end; d++) {
1212 array[d-1] = include;
1217 return 0;
1220 int multiselect_question(char *array, int count)
1222 char *response, *lastchar;
1223 FILE *stream;
1224 size_t response_len = 64;
1226 if(config->noconfirm) {
1227 stream = stdout;
1228 } else {
1229 /* Use stderr so questions are always displayed when redirecting output */
1230 stream = stderr;
1233 response = malloc(response_len);
1234 if(!response) {
1235 return -1;
1237 lastchar = response + response_len - 1;
1238 /* sentinel byte to later see if we filled up the entire string */
1239 *lastchar = 1;
1241 while(1) {
1242 memset(array, 1, count);
1244 fprintf(stream, "\n");
1245 fprintf(stream, _("Enter a selection (default=all)"));
1246 fprintf(stream, ": ");
1247 fflush(stream);
1249 if(config->noconfirm) {
1250 fprintf(stream, "\n");
1251 break;
1254 flush_term_input();
1256 if(fgets(response, response_len, stdin)) {
1257 const size_t response_incr = 64;
1258 /* handle buffer not being large enough to read full line case */
1259 while(*lastchar == '\0' && lastchar[-1] != '\n') {
1260 response_len += response_incr;
1261 response = realloc(response, response_len);
1262 if(!response) {
1263 return -1;
1265 lastchar = response + response_len - 1;
1266 /* sentinel byte */
1267 *lastchar = 1;
1268 if(fgets(response + response_len - response_incr - 1,
1269 response_incr + 1, stdin) == 0) {
1270 free(response);
1271 return -1;
1274 strtrim(response);
1275 if(strlen(response) > 0) {
1276 if(multiselect_parse(array, count, response) == -1) {
1277 /* only loop if user gave an invalid answer */
1278 continue;
1281 break;
1282 } else {
1283 free(response);
1284 return -1;
1288 free(response);
1289 return 0;
1292 int select_question(int count)
1294 char response[32];
1295 FILE *stream;
1296 int preset = 1;
1298 if(config->noconfirm) {
1299 stream = stdout;
1300 } else {
1301 /* Use stderr so questions are always displayed when redirecting output */
1302 stream = stderr;
1305 while(1) {
1306 fprintf(stream, "\n");
1307 fprintf(stream, _("Enter a number (default=%d)"), preset);
1308 fprintf(stream, ": ");
1310 if(config->noconfirm) {
1311 fprintf(stream, "\n");
1312 break;
1315 flush_term_input();
1317 if(fgets(response, sizeof(response), stdin)) {
1318 strtrim(response);
1319 if(strlen(response) > 0) {
1320 int n;
1321 if(parseindex(response, &n, 1, count) != 0)
1322 continue;
1323 return (n - 1);
1326 break;
1329 return (preset - 1);
1333 /* presents a prompt and gets a Y/N answer */
1334 static int question(short preset, char *fmt, va_list args)
1336 char response[32];
1337 FILE *stream;
1339 if(config->noconfirm) {
1340 stream = stdout;
1341 } else {
1342 /* Use stderr so questions are always displayed when redirecting output */
1343 stream = stderr;
1346 /* ensure all text makes it to the screen before we prompt the user */
1347 fflush(stdout);
1348 fflush(stderr);
1350 vfprintf(stream, fmt, args);
1352 if(preset) {
1353 fprintf(stream, " %s ", _("[Y/n]"));
1354 } else {
1355 fprintf(stream, " %s ", _("[y/N]"));
1358 if(config->noconfirm) {
1359 fprintf(stream, "\n");
1360 return preset;
1363 fflush(stream);
1364 flush_term_input();
1366 if(fgets(response, sizeof(response), stdin)) {
1367 strtrim(response);
1368 if(strlen(response) == 0) {
1369 return preset;
1372 /* if stdin is piped, response does not get printed out, and as a result
1373 * a \n is missing, resulting in broken output (FS#27909) */
1374 if(!isatty(fileno(stdin))) {
1375 fprintf(stream, "%s\n", response);
1378 if(strcasecmp(response, _("Y")) == 0 || strcasecmp(response, _("YES")) == 0) {
1379 return 1;
1380 } else if(strcasecmp(response, _("N")) == 0 || strcasecmp(response, _("NO")) == 0) {
1381 return 0;
1384 return 0;
1387 int yesno(char *fmt, ...)
1389 int ret;
1390 va_list args;
1392 va_start(args, fmt);
1393 ret = question(1, fmt, args);
1394 va_end(args);
1396 return ret;
1399 int noyes(char *fmt, ...)
1401 int ret;
1402 va_list args;
1404 va_start(args, fmt);
1405 ret = question(0, fmt, args);
1406 va_end(args);
1408 return ret;
1411 int pm_printf(alpm_loglevel_t level, const char *format, ...)
1413 int ret;
1414 va_list args;
1416 /* print the message using va_arg list */
1417 va_start(args, format);
1418 ret = pm_vfprintf(stderr, level, format, args);
1419 va_end(args);
1421 return ret;
1424 int pm_asprintf(char **string, const char *format, ...)
1426 int ret = 0;
1427 va_list args;
1429 /* print the message using va_arg list */
1430 va_start(args, format);
1431 if(vasprintf(string, format, args) == -1) {
1432 pm_printf(ALPM_LOG_ERROR, _("failed to allocate string\n"));
1433 ret = -1;
1435 va_end(args);
1437 return ret;
1440 int pm_vasprintf(char **string, alpm_loglevel_t level, const char *format, va_list args)
1442 int ret = 0;
1443 char *msg = NULL;
1445 /* if current logmask does not overlap with level, do not print msg */
1446 if(!(config->logmask & level)) {
1447 return ret;
1450 /* print the message using va_arg list */
1451 ret = vasprintf(&msg, format, args);
1453 /* print a prefix to the message */
1454 switch(level) {
1455 case ALPM_LOG_ERROR:
1456 pm_asprintf(string, _("error: %s"), msg);
1457 break;
1458 case ALPM_LOG_WARNING:
1459 pm_asprintf(string, _("warning: %s"), msg);
1460 break;
1461 case ALPM_LOG_DEBUG:
1462 pm_asprintf(string, "debug: %s", msg);
1463 break;
1464 case ALPM_LOG_FUNCTION:
1465 pm_asprintf(string, "function: %s", msg);
1466 break;
1467 default:
1468 pm_asprintf(string, "%s", msg);
1469 break;
1471 free(msg);
1473 return ret;
1476 int pm_vfprintf(FILE *stream, alpm_loglevel_t level, const char *format, va_list args)
1478 int ret = 0;
1480 /* if current logmask does not overlap with level, do not print msg */
1481 if(!(config->logmask & level)) {
1482 return ret;
1485 #if defined(PACMAN_DEBUG)
1486 /* If debug is on, we'll timestamp the output */
1487 if(config->logmask & ALPM_LOG_DEBUG) {
1488 time_t t;
1489 struct tm *tmp;
1490 char timestr[10] = {0};
1492 t = time(NULL);
1493 tmp = localtime(&t);
1494 strftime(timestr, 9, "%H:%M:%S", tmp);
1495 timestr[8] = '\0';
1497 fprintf(stream, "[%s] ", timestr);
1499 #endif
1501 /* print a prefix to the message */
1502 switch(level) {
1503 case ALPM_LOG_ERROR:
1504 fprintf(stream, _("error: "));
1505 break;
1506 case ALPM_LOG_WARNING:
1507 fprintf(stream, _("warning: "));
1508 break;
1509 case ALPM_LOG_DEBUG:
1510 fprintf(stream, "debug: ");
1511 break;
1512 case ALPM_LOG_FUNCTION:
1513 fprintf(stream, "function: ");
1514 break;
1515 default:
1516 break;
1519 /* print the message using va_arg list */
1520 ret = vfprintf(stream, format, args);
1521 return ret;
1524 #ifndef HAVE_STRNDUP
1525 /* A quick and dirty implementation derived from glibc */
1526 static size_t strnlen(const char *s, size_t max)
1528 register const char *p;
1529 for(p = s; *p && max--; ++p);
1530 return (p - s);
1533 char *strndup(const char *s, size_t n)
1535 size_t len = strnlen(s, n);
1536 char *new = (char *) malloc(len + 1);
1538 if(new == NULL)
1539 return NULL;
1541 new[len] = '\0';
1542 return (char *)memcpy(new, s, len);
1544 #endif
1546 /* vim: set ts=2 sw=2 noet: */