/* * Copyright 2005-2020 Gentoo Foundation * Distributed under the terms of the GNU General Public License v2 * * Copyright 2005-2010 Ned Ludd - * Copyright 2005-2014 Mike Frysinger - * Copyright 2018- Fabian Groffen - */ #include "main.h" #include "applets.h" #include #include #include #include #include #include #include #include "atom.h" #include "eat_file.h" #include "scandirat.h" #include "set.h" #include "xarray.h" #include "xasprintf.h" #define QLOP_DEFAULT_LOGFILE "emerge.log" #define QLOP_FLAGS "ctaHMmuUsElerd:f:w:F:" COMMON_FLAGS static struct option const qlop_long_opts[] = { {"summary", no_argument, NULL, 'c'}, {"time", no_argument, NULL, 't'}, {"average", no_argument, NULL, 'a'}, {"human", no_argument, NULL, 'H'}, {"machine", no_argument, NULL, 'M'}, {"merge", no_argument, NULL, 'm'}, {"unmerge", no_argument, NULL, 'u'}, {"autoclean", no_argument, NULL, 'U'}, {"sync", no_argument, NULL, 's'}, {"emerge", no_argument, NULL, 'E'}, {"endtime", no_argument, NULL, 'e'}, {"running", no_argument, NULL, 'r'}, {"date", a_argument, NULL, 'd'}, {"lastmerge", no_argument, NULL, 'l'}, {"logfile", a_argument, NULL, 'f'}, {"atoms", a_argument, NULL, 'w'}, {"format", a_argument, NULL, 'F'}, COMMON_LONG_OPTS }; static const char * const qlop_opts_help[] = { "Print summary of average merges (implies -a)", "Print time taken to complete action", "Print average time taken to complete action", "Print elapsed time in human readable format (use with -t or -a)", "Print start/elapsed time as seconds with no formatting", "Show merge history", "Show unmerge history", "Show autoclean unmerge history", "Show sync history", "Show last merge similar to how emerge(1) -v would show it", "Report time at which the operation finished (iso started)", "Show current emerging packages", "Limit selection to this time (1st -d is start, 2nd -d is end)", "Limit selection to last Portage emerge action", "Read emerge logfile instead of $EMERGE_LOG_DIR/" QLOP_DEFAULT_LOGFILE, "Read package atoms to report from file", "Print matched atom using given format string", COMMON_OPTS_HELP }; static const char qlop_desc[] = "The --date option can take a few forms:\n" " -d '# [s] [ago]' (e.g. '3 days ago')\n" "Or using strptime(3) formats:\n" " -d '2015-12-25' (detected as %Y-%m-%d)\n" " -d '1459101740' (detected as %s)\n" " -d '%d.%m.%Y|25.12.2015' (format is specified)"; #define qlop_usage(ret) usage(ret, QLOP_FLAGS, qlop_long_opts, qlop_opts_help, qlop_desc, lookup_applet_idx("qlop")) struct qlop_mode { char do_time:1; char do_merge:1; char do_unmerge:1; char do_autoclean:1; char do_sync:1; char do_running:1; char do_average:1; char do_summary:1; char do_human:1; char do_machine:1; char do_endtime:1; char show_lastmerge:1; char show_emerge:1; const char *fmt; }; static bool parse_date(const char *sdate, time_t *t) { struct tm tm; const char *s; memset(&tm, 0, sizeof(tm)); s = strchr(sdate, '|'); if (s) { /* Handle custom format like "%Y|2012". */ size_t fmtlen = s - sdate; char fmt[fmtlen + 1]; memcpy(fmt, sdate, fmtlen); fmt[fmtlen] = '\0'; sdate = s + 1; s = strptime(sdate, fmt, &tm); if (s == NULL || s[0] != '\0') return false; } else { /* Handle automatic formats: * - "12315128" -> %s * - "2015-12-24" -> %Y-%m-%d * - "2019-03-28T13:52:31" -> %Y-%m-%dT%H:%M:%s" * - human readable format (see below) */ size_t len = strspn(sdate, "0123456789-:T"); if (sdate[len] == '\0') { const char *fmt; if (strchr(sdate, '-') == NULL) { fmt = "%s"; } else if ((s = strchr(sdate, 'T')) == NULL) { fmt = "%Y-%m-%d"; } else { fmt = "%Y-%m-%dT%H:%M:%S"; } s = strptime(sdate, fmt, &tm); if (s == NULL || s[0] != '\0') return false; } else { /* Handle the formats: * <#> [s] [ago] */ len = strlen(sdate) + 1; unsigned long num; char dur[len]; char ago[len]; int ret = sscanf(sdate, "%lu %s %s", &num, dur, ago); if (ret < 2) { if (strcmp(sdate, "today") == 0) { num = 1; snprintf(dur, len, "%s", "day"); } else if (strcmp(sdate, "yesterday") == 0) { num = 2; snprintf(dur, len, "%s", "day"); } else { return false; } } if (ret == 3 && strcmp(ago, "ago") != 0) return false; if (time(t) == -1) return false; if (localtime_r(t, &tm) == NULL) return false; /* Chop and trailing "s" sizes. */ len = strlen(dur); if (dur[len - 1] == 's') dur[len - 1] = '\0'; /* Step down the current time. */ if (!strcmp(dur, "year")) { tm.tm_year -= num; } else if (!strcmp(dur, "month")) { if (num >= 12) { tm.tm_year -= (num / 12); num %= 12; } tm.tm_mon -= num; if (tm.tm_mon < 0) { tm.tm_mon += 12; tm.tm_year -= 1; } } else if (!strcmp(dur, "week")) { num *= 7; goto days; } else if (!strcmp(dur, "day")) { days: /* This is in seconds, so scale w/that. */ *t -= (num * 24 * 60 * 60); if (localtime_r(t, &tm) == NULL) return false; } else return false; } } *t = mktime(&tm); return (*t == -1) ? false : true; } static char _date_buf[48]; static char *fmt_date(struct qlop_mode *flags, time_t ts, time_t te) { time_t t = flags->do_endtime ? te : ts; struct tm lt; if (flags->do_machine || localtime_r(&t, <) == NULL) snprintf(_date_buf, sizeof(_date_buf), "%zd", (size_t)t); else strftime(_date_buf, sizeof(_date_buf), "%Y-%m-%dT%H:%M:%S", <); return _date_buf; } static char _elapsed_buf[256]; static char *fmt_elapsedtime(struct qlop_mode *flags, time_t e) { time_t dd; time_t hh; time_t mm; time_t ss; size_t bufpos = 0; if (flags->do_machine || e < 0) { snprintf(_elapsed_buf, sizeof(_elapsed_buf), "%s%zd%s", GREEN, (size_t)e, NORM); return _elapsed_buf; } ss = e % 60; e /= 60; mm = e % 60; e /= 60; hh = e % 24; e /= 24; dd = e; if (flags->do_human) { if (dd > 0) bufpos += snprintf(_elapsed_buf + bufpos, sizeof(_elapsed_buf) - bufpos, "%s%zd%s day%s", GREEN, (size_t)dd, NORM, dd == 1 ? "" : "s"); if (hh > 0) bufpos += snprintf(_elapsed_buf + bufpos, sizeof(_elapsed_buf) - bufpos, "%s%s%zd%s hour%s", bufpos == 0 ? "" : ", ", GREEN, (size_t)hh, NORM, hh == 1 ? "" : "s"); if (mm > 0) bufpos += snprintf(_elapsed_buf + bufpos, sizeof(_elapsed_buf) - bufpos, "%s%s%zd%s minute%s", bufpos == 0 ? "" : ", ", GREEN, (size_t)mm, NORM, mm == 1 ? "" : "s"); if (ss > 0 || (mm + hh + dd) == 0) bufpos += snprintf(_elapsed_buf + bufpos, sizeof(_elapsed_buf) - bufpos, "%s%s%zd%s second%s", bufpos == 0 ? "" : ", ", GREEN, (size_t)ss, NORM, ss == 1 ? "" : "s"); } else { hh += 24 * dd; if (hh > 0) { snprintf(_elapsed_buf, sizeof(_elapsed_buf), "%s%zd%s:%s%02zd%s:%s%02zd%s", GREEN, (size_t)hh, NORM, GREEN, (size_t)mm, NORM, GREEN, (size_t)ss, NORM); } else if (mm > 0) { snprintf(_elapsed_buf, sizeof(_elapsed_buf), "%s%zd%s′%s%02zd%s″", GREEN, (size_t)mm, NORM, GREEN, (size_t)ss, NORM); } else { snprintf(_elapsed_buf, sizeof(_elapsed_buf), "%s%zd%ss", GREEN, (size_t)ss, NORM); } } return _elapsed_buf; } struct pkg_match { char id[BUFSIZ]; depend_atom *atom; time_t tbegin; time_t time; size_t cnt; }; static int pkg_sort_cb(const void *l, const void *r) { struct pkg_match *pl = *(struct pkg_match **)l; struct pkg_match *pr = *(struct pkg_match **)r; depend_atom *al = pl->atom; depend_atom *ar = pr->atom; return atom_compar_cb(al, ar); } /* The format of the sync log has changed over time. Old format: 1106804103: Started emerge on: Jan 27, 2005 05:35:03 1106804103: *** emerge sync 1106804103: === sync 1106804103: >>> starting rsync with rsync://192.168.0.5/gentoo-portage 1106804537: === Sync completed with rsync://192.168.0.5/gentoo-portage 1106804538: *** terminating. New format: 1431764402: Started emerge on: May 16, 2015 04:20:01 1431764402: *** emerge --quiet --keep-going --verbose --nospinner --oneshot --quiet-build=n --sync 1431764402: === sync 1431764402: >>> Syncing repository 'gentoo' into '/usr/portage'... 1431764402: >>> Starting rsync with rsync://[2a01:90:200:10::1a]/gentoo-portage 1431764460: === Sync completed for gentoo 1431764493: *** terminating. *** packages 1547475773: >>> emerge (53 of 74) app-shells/bash-5.0 to /gentoo/prefix64/ 1547475774: === (53 of 74) Cleaning (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild) 1547475774: === (53 of 74) Compiling/Merging (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild) 1547475913: === (53 of 74) Merging (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild) 1547475916: >>> AUTOCLEAN: app-shells/bash:0 1547475916: === Unmerging... (app-shells/bash-4.4_p23) 1547475918: >>> unmerge success: app-shells/bash-4.4_p23 1547475921: === (53 of 74) Post-Build Cleaning (app-shells/bash-5.0::/path/to/app-shells/bash/bash-5.0.ebuild) 1547475921: ::: completed emerge (53 of 74) app-shells/bash-5.0 to /gentoo/prefix64/ 1550953093: Started emerge on: Feb 23, 2019 21:18:12 1550953093: *** emerge --ask --verbose --depclean pwgen 1550953093: >>> depclean 1550953118: === Unmerging... (app-admin/pwgen-2.08) 1550953125: >>> unmerge success: app-admin/pwgen-2.08 1550953125: *** exiting successfully. 1550953125: *** terminating. Currently running merges can be found in the /proc filesystem: - Linux: readlink(/proc//fd/X) - Solaris: readlink(/proc//path/X) from here a file should be there that points to the build.log file $CAT/$P/work/build.log. If so, it's running for $CAT/$P. This requires being the portage user though, or root. Should there be no /proc, we can deduce from the log whether a package is being emerged, if and only if, there are no parallel merges, and portage never got interrupted in a way where it could not write its interruption to the log. Unfortunately these scenarios happen a lot. As such, we can try to remedy this somewhat by using a rule of thumb that currently merging packages need to be withinin the last 10 days. */ static int do_emerge_log( const char *log, struct qlop_mode *flags, array_t *atoms, time_t tbegin, time_t tend) { FILE *fp; char buf[BUFSIZ]; char *p; char *q; time_t tstart = LONG_MAX; time_t tlast = tbegin; time_t tstart_emerge = 0; time_t last_merge = 0; time_t sync_start = 0; time_t sync_time = 0; size_t sync_cnt = 0; time_t elapsed; depend_atom *atom; depend_atom *atomw; depend_atom *upgrade_atom = NULL; DECLARE_ARRAY(merge_matches); set *merge_averages = create_set(); DECLARE_ARRAY(unmerge_matches); set *unmerge_averages = create_set(); set *atomset = NULL; size_t i; size_t parallel_emerge = 0; bool all_atoms = false; char afmt[BUFSIZ]; struct pkg_match *pkg; struct pkg_match *pkgw; /* support relative path in here and now, when using ROOT, stick to * it, turning relative into a moot point */ if (portroot[1] == '\0') snprintf(buf, sizeof(buf), "%s", log); else snprintf(buf, sizeof(buf), "%s%s", portroot, log); if ((fp = fopen(buf, "r")) == NULL) { warnp("Could not open logfile '%s'", log); return 1; } all_atoms = array_cnt(atoms) == 0; if (all_atoms || flags->show_lastmerge) { atomset = create_set(); /* assemble list of atoms */ while (fgets(buf, sizeof(buf), fp) != NULL) { if ((p = strchr(buf, ':')) == NULL) continue; *p++ = '\0'; tstart = atol(buf); if (tstart < tbegin || tstart > tend) continue; if (flags->show_lastmerge) { if (strncmp(p, " *** emerge ", 13) == 0) tstart_emerge = tstart; if (!all_atoms) continue; } atom = NULL; if (strncmp(p, " >>> emerge ", 13) == 0 && (q = strchr(p + 13, ')')) != NULL) { p = q + 2; q = strchr(p, ' '); if (q != NULL) { *q = '\0'; atom = atom_explode(p); } } else if (strncmp(p, " === Unmerging... (", 20) == 0 || strncmp(p, " === Unmerging... (", 19) == 0) { if (p[1] == ' ') p++; p += 19; q = strchr(p, ')'); if (q != NULL) { *q = '\0'; atom = atom_explode(p); } } if (atom != NULL) { /* strip off version info, if we generate a list * ourselves, we will always print everything, so as * well can keep memory footprint a bit lower by only * having package matches */ atom->PV = NULL; atom->PVR = NULL; atom->PR_int = 0; snprintf(afmt, sizeof(afmt), "%s/%s", atom->CATEGORY, atom->PN); /* now we found a package, register this merge as a * "valid" one, such that dummy emerge calls (e.g. * emerge -pv foo) are ignored */ if (last_merge != tstart_emerge) { array_t vals; values_set(atomset, &vals); array_for_each(&vals, i, atomw) atom_implode(atomw); xarrayfree_int(&vals); clear_set(atomset); last_merge = tstart_emerge; } atomw = add_set_value(afmt, atom, atomset); if (atomw != NULL) atom_implode(atom); } } rewind(fp); } if (flags->show_lastmerge) { tbegin = last_merge; tend = tstart; } /* loop over lines searching for atoms */ while (fgets(buf, sizeof(buf), fp) != NULL) { if ((p = strchr(buf, ':')) == NULL) continue; *p++ = '\0'; /* keeping track of parallel merges needs to be done before * applying dates, for a subset of the log might show emerge * finished without knowledge of another instance */ if (flags->do_running && (strncmp(p, " *** emerge ", 13) == 0 || strncmp(p, " *** terminating.", 18) == 0)) { if (p[7] == 'm') { parallel_emerge++; } else if (parallel_emerge > 0) { parallel_emerge--; } /* for bug #687508, this cannot be in the else if case * above, if the log is truncated somehow, the leading * *** emerge might be missing, but a termination in that * case better means we forget about everything that was * unfinished not to keep reporting some packages forever */ if (parallel_emerge == 0) { /* we just finished the only emerge we found to be * running, so if there were "running" (unfinished) * merges, they must have been terminated */ sync_start = 0; while ((i = array_cnt(merge_matches)) > 0) { i--; pkgw = xarrayget(merge_matches, i); atom_implode(pkgw->atom); xarraydelete(merge_matches, i); } while ((i = array_cnt(unmerge_matches)) > 0) { i--; pkgw = xarrayget(unmerge_matches, i); atom_implode(pkgw->atom); xarraydelete(unmerge_matches, i); } } } tstart = atol(buf); if (tstart < tlast) continue; tlast = tstart; if (tstart < tbegin || tstart > tend) continue; /* are we interested in this line? */ if (flags->show_emerge && verbose && ( strncmp(p, " *** emerge ", 13) == 0)) { char shortopts[8]; /* must hold as many opts converted below */ int numopts = 0; printf("emerge"); for (p += 13; (q = strtok(p, " \n")) != NULL; p = NULL) { if (strncmp(q, "--", 2) == 0) { /* portage seems to normalise options given into * their long forms always; I don't want to keep a * mapping table to short forms here, but it's * tempting, so I just do a few of the often used * ones */ q += 2; if (strcmp(q, "ask") == 0) { shortopts[numopts++] = 'a'; } else if (strcmp(q, "verbose") == 0) { shortopts[numopts++] = 'v'; } else if (strcmp(q, "oneshot") == 0) { shortopts[numopts++] = '1'; } else if (strcmp(q, "deep") == 0) { shortopts[numopts++] = 'D'; } else if (strcmp(q, "update") == 0) { shortopts[numopts++] = 'u'; } else if (strcmp(q, "depclean") == 0) { shortopts[numopts++] = 'c'; } else if (strcmp(q, "unmerge") == 0) { shortopts[numopts++] = 'C'; } else { q = NULL; } /* process next token */ if (q != NULL) continue; } /* if we're here, we've assembled opts whatever we could */ if (numopts > 0) { printf(" %s-%.*s%s", GREEN, numopts, shortopts, NORM); numopts = 0; } if (*q == '\0') { /* skip empty token, likely the trailing \n */ continue; } if (strncmp(q, "--", 2) == 0) { printf(" %s--%s%s", GREEN, q, NORM); } else if (strcmp(q, "@world") == 0 || strcmp(q, "@system") == 0) { printf(" %s%s%s", YELLOW, q, NORM); } else if (strcmp(q, "world") == 0 || strcmp(q, "system") == 0) { printf(" %s@%s%s", YELLOW, q, NORM); } else { /* this should be an atom */ atom = atom_explode(q); if (atom == NULL) { /* or not ... just print it */ printf(" %s", q); } else { printf(" %s", atom_format( "%[pfx]%[CAT]%[PF]%[sfx]" "%[SLOT]%[SUBSLOT]%[REPO]", atom)); atom_implode(atom); } } } printf("\n"); } else if (flags->do_sync && ( strncmp(p, " === Sync completed ", 20) == 0 || strcmp(p, " === sync\n") == 0)) { /* sync start or stop, we have nothing to detect parallel * syncs with, so don't bother and assume this doesn't * happen */ if (p[6] == 's') { sync_start = tstart; } else { if (sync_start == 0) continue; /* sync without start, exclude */ elapsed = tstart - sync_start; p += 20; if (strncmp(p, "for ", 4) == 0) { p += 4; } else { /* "with " */ p += 5; } if ((q = strchr(p, '\n')) != NULL) *q = '\0'; if (flags->do_average || flags->do_running) { sync_cnt++; sync_time += elapsed; sync_start = 0; /* reset */ continue; } if (quiet) { printf("%s%s%s%s%s\n", GREEN, p, NORM, flags->do_time ? ": " : "", flags->do_time ? fmt_elapsedtime(flags, elapsed) : ""); } else if (flags->do_time) { printf("%s *** %s%s%s: %s\n", fmt_date(flags, sync_start, tstart), GREEN, p, NORM, fmt_elapsedtime(flags, elapsed)); } else { printf("%s *** %s%s%s\n", fmt_date(flags, sync_start, tstart), GREEN, p, NORM); } sync_start = 0; /* reset */ } } else if (flags->do_merge && ( strncmp(p, " >>> emerge (", 14) == 0 || strncmp(p, " ::: completed emerge (", 24) == 0)) { /* merge start/stop (including potential unmerge of old pkg) */ if (p[3] == '>') { /* >>> emerge */ char *id; q = strchr(p + 14, ')'); if (q == NULL) continue; /* keep a copy of the relevant string in case we need to * match this */ id = p + 6; q += 2; /* ") " */ p = strchr(q, ' '); if (p == NULL) continue; *p = '\0'; atom = atom_explode(q); *p = ' '; if (atom == NULL) continue; /* see if we need this atom */ atomw = NULL; if (atomset == NULL) { array_for_each(atoms, i, atomw) { if (atom_compare(atom, atomw) == EQUAL) break; atomw = NULL; } } else { snprintf(afmt, sizeof(afmt), "%s/%s", atom->CATEGORY, atom->PN); atomw = get_set(afmt, atomset); } if (atomw == NULL) { atom_implode(atom); continue; } pkg = xmalloc(sizeof(struct pkg_match)); snprintf(pkg->id, sizeof(pkg->id), "%s", id); pkg->atom = atom; pkg->tbegin = tstart; pkg->time = (time_t)0; pkg->cnt = 0; xarraypush_ptr(merge_matches, pkg); } else { /* ::: completed */ array_for_each_rev(merge_matches, i, pkgw) { if (strcmp(p + 16, pkgw->id) != 0) continue; /* found, do report */ elapsed = tstart - pkgw->tbegin; if (flags->do_average || flags->do_running) { /* find in list of averages */ if (!verbose || flags->do_running) { snprintf(afmt, sizeof(afmt), "%s/%s", pkgw->atom->CATEGORY, pkgw->atom->PN); } else { snprintf(afmt, sizeof(afmt), "%s/%s", pkgw->atom->CATEGORY, pkgw->atom->PF); } pkg = add_set_value(afmt, pkgw, merge_averages); if (pkg != NULL) { pkg->cnt++; pkg->time += elapsed; /* store max time for do_running */ if (elapsed > pkg->tbegin) pkg->tbegin = elapsed; atom_implode(pkgw->atom); xarraydelete(merge_matches, i); } else { /* new entry */ pkgw->id[0] = '\0'; pkgw->cnt = 1; pkgw->time = elapsed; pkgw->tbegin = elapsed; xarraydelete_ptr(merge_matches, i); } break; } if (quiet && !flags->do_average) { printf("%s%s%s\n", atom_format(flags->fmt, pkgw->atom), flags->do_time ? ": " : "", flags->do_time ? fmt_elapsedtime(flags, elapsed) : ""); } else if (flags->show_emerge) { int state = NOT_EQUAL; if (upgrade_atom != NULL) state = atom_compare(pkgw->atom, upgrade_atom); switch (state) { /* "NRUD " */ case EQUAL: printf(" %sR%s ", YELLOW, NORM); break; case NOT_EQUAL: printf("%sN%s ", GREEN, NORM); break; case NEWER: printf(" %sU%s ", BLUE, NORM); break; case OLDER: printf(" %sU%sD%s ", BLUE, DKBLUE, NORM); break; } printf("%s", atom_format("%[CAT]%[PF]", pkgw->atom)); if (state == NEWER || state == OLDER) printf(" %s[%s%s%s]%s", DKBLUE, NORM, atom_format("%[PVR]", upgrade_atom), DKBLUE, NORM); if (flags->do_time) printf(": %s\n", fmt_elapsedtime(flags, elapsed)); else printf("\n"); } else if (flags->do_time) { printf("%s >>> %s: %s\n", fmt_date(flags, pkgw->tbegin, tstart), atom_format(flags->fmt, pkgw->atom), fmt_elapsedtime(flags, elapsed)); } else if (!flags->do_average) { printf("%s >>> %s\n", fmt_date(flags, pkgw->tbegin, tstart), atom_format(flags->fmt, pkgw->atom)); } atom_implode(pkgw->atom); xarraydelete(merge_matches, i); break; } } } else if ( (flags->do_unmerge && strncmp(p, " === Unmerging... (", 19) == 0) || (flags->do_autoclean && strncmp(p, " === Unmerging... (", 20) == 0) || ((flags->do_unmerge || flags->do_autoclean) && strncmp(p, " >>> unmerge success: ", 23) == 0)) { /* unmerge action */ if (p[2] == '=') { if (p[1] == ' ') p++; p += 19; if ((q = strchr(p, ')')) == NULL) continue; *q = '\0'; atom = atom_explode(p); if (atom == NULL) continue; if (p[-20] == ' ' && flags->show_emerge) { if (upgrade_atom != NULL) atom_implode(upgrade_atom); upgrade_atom = atom; continue; } /* see if we need this atom */ atomw = NULL; if (atomset == NULL) { array_for_each(atoms, i, atomw) { if (atom_compare(atom, atomw) == EQUAL) break; atomw = NULL; } } else { snprintf(afmt, sizeof(afmt), "%s/%s", atom->CATEGORY, atom->PN); atomw = get_set(afmt, atomset); } if (atomw == NULL) { atom_implode(atom); continue; } pkg = xmalloc(sizeof(struct pkg_match)); snprintf(pkg->id, sizeof(pkg->id), "%s\n", p); /* \n !!! */ pkg->atom = atom; pkg->tbegin = tstart; pkg->time = (time_t)0; pkg->cnt = 0; xarraypush_ptr(unmerge_matches, pkg); } else { array_for_each_rev(unmerge_matches, i, pkgw) { if (strcmp(p + 23, pkgw->id) != 0) continue; /* found, do report */ elapsed = tstart - pkgw->tbegin; if (flags->do_average || flags->do_running) { /* find in list of averages */ if (!verbose || flags->do_running) { snprintf(afmt, sizeof(afmt), "%s/%s", pkgw->atom->CATEGORY, pkgw->atom->PN); } else { snprintf(afmt, sizeof(afmt), "%s/%s", pkgw->atom->CATEGORY, pkgw->atom->PF); } pkg = add_set_value(afmt, pkgw, unmerge_averages); if (pkg != NULL) { pkg->cnt++; pkg->time += elapsed; /* store max time for do_running */ if (elapsed > pkg->tbegin) pkg->tbegin = elapsed; atom_implode(pkgw->atom); xarraydelete(unmerge_matches, i); } else { /* new entry */ pkgw->id[0] = '\0'; pkgw->cnt = 1; pkgw->time = elapsed; pkgw->tbegin = elapsed; xarraydelete_ptr(unmerge_matches, i); } break; } if (quiet && !flags->do_average) { printf("%s%s%s\n", atom_format(flags->fmt, pkgw->atom), flags->do_time ? ": " : "", flags->do_time ? fmt_elapsedtime(flags, elapsed) : ""); } else if (flags->show_emerge) { /* emerge never lists packages it unmerges, only * when part of installation of another package, * so there is nothing to follow here. We could * use D(elete), R(emove) or (U)nmerge here, * which all three are already in use. I've * chosen to use D in the first column here, * because D is only used together with U, so it * is the only distinquishable choice, appearing * in the place of N(ew). */ printf("%sD%s %s", RED, NORM, atom_format("%[CAT]%[PF]", pkgw->atom)); if (flags->do_time) printf(": %s\n", fmt_elapsedtime(flags, elapsed)); else printf("\n"); } else if (flags->do_time) { printf("%s <<< %s: %s\n", fmt_date(flags, pkgw->tbegin, tstart), atom_format(flags->fmt, pkgw->atom), fmt_elapsedtime(flags, elapsed)); } else if (!flags->do_average) { printf("%s <<< %s\n", fmt_date(flags, pkgw->tbegin, tstart), atom_format(flags->fmt, pkgw->atom)); } atom_implode(pkgw->atom); xarraydelete(unmerge_matches, i); break; } } } } fclose(fp); if (flags->do_running) { time_t cutofftime; set *pkgs_seen = create_set(); tstart = time(NULL); /* emerge.log can be interrupted, incorrect and hopelessly lost, * so to eliminate some unfinished crap from there, we just * ignore anything that's > cutofftime, 10 days for now. */ cutofftime = 10 * 24 * 60 * 60; /* when we consider entries stale */ cutofftime = (tbegin > 0 ? tbegin : tstart) - cutofftime; /* can't report endtime for non-finished operations */ flags->do_endtime = 0; if (sync_time > 0) sync_time /= sync_cnt; if (sync_start >= cutofftime) { elapsed = tstart - sync_start; if (elapsed >= sync_time) sync_time = 0; if (flags->do_time) { elapsed = tstart - sync_start; printf("%s *** %s%s%s: %s... ETA: %s\n", fmt_date(flags, sync_start, 0), YELLOW, "sync", NORM, fmt_elapsedtime(flags, elapsed), sync_time == 0 ? "unknown" : fmt_elapsedtime(flags, sync_time - elapsed)); } else { printf("%s *** %s%s%s... ETA: %s\n", fmt_date(flags, sync_start, 0), YELLOW, "sync", NORM, sync_time == 0 ? "unknown" : fmt_elapsedtime(flags, sync_time - elapsed)); } } array_for_each_rev(merge_matches, i, pkgw) { time_t maxtime = 0; bool isMax = false; bool notseen; if (pkgw->tbegin < cutofftime) continue; snprintf(afmt, sizeof(afmt), "%s/%s", pkgw->atom->CATEGORY, pkgw->atom->PN); /* eliminate dups, bug #701392 */ add_set_unique(afmt, pkgs_seen, ¬seen); if (!notseen) continue; elapsed = tstart - pkgw->tbegin; pkg = get_set(afmt, merge_averages); if (pkg != NULL) { maxtime = pkg->time / pkg->cnt; if (elapsed >= maxtime) { maxtime = pkg->tbegin; if (elapsed >= maxtime) maxtime -= elapsed; isMax = true; } } /* extract (X of Y) from id, bug #442406 */ if ((p = strchr(pkgw->id, '(')) != NULL) { if ((q = strchr(p, ')')) != NULL) { q[1] = '\0'; p--; } else { p = NULL; } } if (flags->do_time) { printf("%s >>> %s: %s", fmt_date(flags, pkgw->tbegin, 0), atom_format(flags->fmt, pkgw->atom), fmt_elapsedtime(flags, elapsed)); } else { printf("%s >>> %s", fmt_date(flags, pkgw->tbegin, 0), atom_format(flags->fmt, pkgw->atom)); } if (maxtime < 0) printf("... +%s\n", fmt_elapsedtime(flags, -maxtime)); else printf("...%s ETA: %s%s\n", p == NULL ? "" : p, maxtime == 0 ? "unknown" : fmt_elapsedtime(flags, maxtime - elapsed), maxtime > 0 && verbose ? isMax ? " (longest run)" : " (average run)" : ""); } clear_set(pkgs_seen); array_for_each(unmerge_matches, i, pkgw) { time_t maxtime = 0; bool isMax = false; bool notseen; if (pkgw->tbegin < cutofftime) continue; snprintf(afmt, sizeof(afmt), "%s/%s", pkgw->atom->CATEGORY, pkgw->atom->PN); /* eliminate dups, bug #701392 */ add_set_unique(afmt, pkgs_seen, ¬seen); if (!notseen) continue; elapsed = tstart - pkgw->tbegin; pkg = get_set(afmt, unmerge_averages); if (pkg != NULL) { maxtime = pkg->time / pkg->cnt; if (elapsed >= maxtime) { maxtime = pkg->tbegin; if (elapsed >= maxtime) maxtime -= elapsed; isMax = true; } } if (flags->do_time) { printf("%s <<< %s: %s", fmt_date(flags, pkgw->tbegin, 0), atom_format(flags->fmt, pkgw->atom), fmt_elapsedtime(flags, elapsed)); } else { printf("%s <<< %s", fmt_date(flags, pkgw->tbegin, 0), atom_format(flags->fmt, pkgw->atom)); } if (maxtime < 0) printf("... +%s\n", fmt_elapsedtime(flags, -maxtime)); else printf("... ETA: %s%s\n", maxtime == 0 ? "unknown" : fmt_elapsedtime(flags, maxtime - elapsed), maxtime > 0 && verbose ? isMax ? " (longest run)" : " (average run)" : ""); } free_set(pkgs_seen); } else if (flags->do_average) { size_t total_merges = 0; size_t total_unmerges = 0; time_t total_time = (time_t)0; DECLARE_ARRAY(avgs); values_set(merge_averages, avgs); xarraysort(avgs, pkg_sort_cb); array_for_each(avgs, i, pkg) { printf("%s: %s average for %s%zd%s merge%s\n", atom_format(flags->fmt, pkg->atom), fmt_elapsedtime(flags, pkg->time / pkg->cnt), GREEN, pkg->cnt, NORM, pkg->cnt == 1 ? "" : "s"); total_merges += pkg->cnt; total_time += pkg->time; } xarrayfree_int(avgs); values_set(unmerge_averages, avgs); xarraysort(avgs, pkg_sort_cb); array_for_each(avgs, i, pkg) { printf("%s: %s average for %s%zd%s unmerge%s\n", atom_format(flags->fmt, pkg->atom), fmt_elapsedtime(flags, pkg->time / pkg->cnt), GREEN, pkg->cnt, NORM, pkg->cnt == 1 ? "" : "s"); total_unmerges += pkg->cnt; total_time += pkg->time; } xarrayfree_int(avgs); if (sync_cnt > 0) { printf("%ssync%s: %s average for %s%zd%s sync%s\n", BLUE, NORM, fmt_elapsedtime(flags, sync_time / sync_cnt), GREEN, sync_cnt, NORM, sync_cnt == 1 ? "" : "s"); total_time += sync_time; } if (flags->do_summary) { /* 123 seconds for 5 merges, 3 unmerges, 1 sync */ printf("%stotal%s: %s for ", BLUE, NORM, fmt_elapsedtime(flags, total_time)); if (total_merges > 0) printf("%s%zd%s merge%s", GREEN, total_merges, NORM, total_merges == 1 ? "" : "s"); if (total_unmerges > 0) printf("%s%s%zd%s unmerge%s", total_merges == 0 ? "" : ", ", GREEN, total_unmerges, NORM, total_unmerges == 1 ? "" : "s"); if (sync_cnt > 0) printf("%s%s%zd%s sync%s", total_merges + total_unmerges == 0 ? "" : ", ", GREEN, sync_cnt, NORM, sync_cnt == 1 ? "" : "s"); printf("\n"); } } { DECLARE_ARRAY(t); values_set(merge_averages, t); array_for_each(t, i, pkgw) { atom_implode(pkgw->atom); free(pkgw); } xarrayfree_int(t); values_set(unmerge_averages, t); array_for_each(t, i, pkgw) { atom_implode(pkgw->atom); free(pkgw); } xarrayfree_int(t); } free_set(merge_averages); free_set(unmerge_averages); array_for_each_rev(merge_matches, i, pkgw) { atom_implode(pkgw->atom); xarraydelete(merge_matches, i); } xarrayfree(merge_matches); array_for_each_rev(unmerge_matches, i, pkgw) { atom_implode(pkgw->atom); xarraydelete(unmerge_matches, i); } xarrayfree(unmerge_matches); if (atomset != NULL) { DECLARE_ARRAY(t); values_set(atomset, t); array_for_each(t, i, atom) atom_implode(atom); xarrayfree_int(t); free_set(atomset); } return 0; } /* scan through /proc for running merges, this requires portage user * or root */ static array_t *probe_proc(array_t *atoms) { struct dirent **procs; int procslen; int pi; struct dirent **links; int linkslen; int li; struct dirent *d; char npath[_Q_PATH_MAX * 2]; char rpath[_Q_PATH_MAX]; const char *subdir = NULL; const char *pid; ssize_t rpathlen; char *p; depend_atom *atom; DECLARE_ARRAY(ret_atoms); size_t i; /* /proc//path/<[0-9]+link> * /proc//fd/<[0-9]+link> */ if ((procslen = scandir("/proc", &procs, NULL, NULL)) > 0) { for (pi = 0; pi < procslen; pi++) { d = procs[pi]; /* must be [0-9]+ */ if (d->d_name[0] < '0' || d->d_name[0] > '9') continue; if (subdir == NULL) { struct stat st; snprintf(npath, sizeof(npath), "/proc/%s/path", d->d_name); if (stat(npath, &st) < 0) subdir = "fd"; else subdir = "path"; } pid = d->d_name; snprintf(npath, sizeof(npath), "/proc/%s/%s", pid, subdir); if ((linkslen = scandir(npath, &links, NULL, NULL)) > 0) { for (li = 0; li < linkslen; li++) { d = links[li]; /* must be [0-9]+ */ if (d->d_name[0] < '0' || d->d_name[0] > '9') continue; snprintf(npath, sizeof(npath), "/proc/%s/%s/%s", pid, subdir, d->d_name); rpathlen = readlink(npath, rpath, sizeof(rpath) - 1); if (rpathlen <= 0) continue; rpath[rpathlen] = '\0'; /* check if this points to a portage build: * /portage///temp/build.log */ if ((size_t)rpathlen > sizeof("/temp/build.log") && strcmp(rpath + rpathlen - (sizeof("/temp/build.log") - 1), "/temp/build.log") == 0 && (p = strstr(rpath, "/portage/")) != NULL) { p += sizeof("/portage/") - 1; rpath[rpathlen - (sizeof("/temp/build.log") - 1)] = '\0'; atom = atom_explode(p); if (atom == NULL || atom->CATEGORY == NULL || atom->P == NULL) { if (atom != NULL) atom_implode(atom); continue; } xarraypush_ptr(ret_atoms, atom); } } scandir_free(links, linkslen); } } scandir_free(procs, procslen); } else { /* flag /proc doesn't exist */ warn("/proc doesn't exist, running merges are based on heuristics"); return NULL; } if (array_cnt(ret_atoms) == 0) { /* if we didn't find anything, this is either because nothing is * running, or because we didn't have appropriate permissions -- * try to figure out which of the two is it (there is no good * way) */ if (geteuid() != 0) { warn("insufficient privileges for full /proc access, " "running merges are based on heuristics"); return NULL; } } if (array_cnt(atoms) > 0) { size_t j; depend_atom *atomr; /* calculate intersection */ array_for_each(atoms, i, atom) { array_for_each(ret_atoms, j, atomr) { if (atom_compare(atomr, atom) != EQUAL) { xarraydelete_ptr(ret_atoms, j); atom_implode(atomr); break; } } atom_implode(atom); } xarrayfree_int(atoms); } /* ret_atoms is allocated on the stack, so copy into atoms which is * empty at this point */ array_for_each(ret_atoms, i, atom) xarraypush_ptr(atoms, atom); xarrayfree_int(ret_atoms); return atoms; } int qlop_main(int argc, char **argv) { size_t i; int ret; time_t start_time; time_t end_time; struct qlop_mode m; char *logfile = NULL; char *atomfile = NULL; char *p; char *q; depend_atom *atom; DECLARE_ARRAY(atoms); int runningmode = 0; start_time = 0; end_time = LONG_MAX; m.do_time = 0; m.do_merge = 0; m.do_unmerge = 0; m.do_autoclean = 0; m.do_sync = 0; m.do_running = 0; m.do_average = 0; m.do_summary = 0; m.do_human = 0; m.do_machine = 0; m.do_endtime = 0; m.show_lastmerge = 0; m.show_emerge = 0; m.fmt = NULL; while ((ret = GETOPT_LONG(QLOP, qlop, "")) != -1) { switch (ret) { COMMON_GETOPTS_CASES(qlop) case 't': m.do_time = 1; break; case 'm': m.do_merge = 1; break; case 'u': m.do_unmerge = 1; break; case 'U': m.do_autoclean = 1; break; case 's': m.do_sync = 1; break; case 'E': m.do_merge = 1; m.do_unmerge = 1; m.do_autoclean = 1; m.show_lastmerge = 1; m.show_emerge = 1; break; case 'r': m.do_running = 1; runningmode++; break; case 'a': m.do_average = 1; break; case 'c': m.do_summary = 1; break; case 'H': m.do_human = 1; break; case 'M': m.do_machine = 1; break; case 'e': m.do_endtime = 1; break; case 'l': m.show_lastmerge = 1; break; case 'F': m.fmt = optarg; break; case 'd': if (start_time == 0) { if (!parse_date(optarg, &start_time)) err("invalid date: %s", optarg); } else if (end_time == LONG_MAX) { if (!parse_date(optarg, &end_time)) err("invalid date: %s", optarg); } else err("too many -d options"); break; case 'f': if (logfile != NULL) err("Only use -f once"); logfile = xstrdup(optarg); break; case 'w': if (atomfile != NULL) err("Only use -w once"); if (!eat_file(optarg, &atomfile, &i)) err("failed to open file %s", optarg); break; } } if (logfile == NULL) xasprintf(&logfile, "%s/%s", portlogdir, QLOP_DEFAULT_LOGFILE); argc -= optind; argv += optind; for (i = 0; i < (size_t)argc; ++i) { atom = atom_explode(argv[i]); if (!atom) warn("invalid atom: %s", argv[i]); else xarraypush_ptr(atoms, atom); } for (p = atomfile; p != NULL && *p != '\0'; p = q) { while (isspace((int)(*p))) p++; q = strchr(p, '\n'); if (q != NULL) { *q = '\0'; q++; } atom = atom_explode(p); if (!atom) warn("invalid atom: %s", p); else xarraypush_ptr(atoms, atom); } if (atomfile) free(atomfile); /* default operation: -slumt */ if ( m.do_time == 0 && m.do_merge == 0 && m.do_unmerge == 0 && m.do_autoclean == 0 && m.do_sync == 0 && m.do_running == 0 && m.do_average == 0 && m.do_summary == 0 ) { m.do_merge = 1; m.do_unmerge = 1; if (array_cnt(atoms) == 0) { m.do_sync = 1; if (start_time == 0) m.show_lastmerge = 1; } m.do_time = 1; } /* handle deps */ if (m.do_summary) m.do_average = 1; /* handle -a / -t conflict */ if (m.do_average && m.do_time) { warn("-a (or -c) and -t cannot be used together, dropping -t"); m.do_time = 0; } /* handle -a / -r conflict */ if (m.do_average && m.do_running) { warn("-a (or -c) and -r cannot be used together, dropping -a"); m.do_average = 0; } /* handle -H / -M conflict */ if (m.do_human && m.do_machine) { warn("-H and -M cannot be used together, dropping -M: " "only humans make mistakes"); m.do_machine = 0; } /* handle -s + atoms */ if (m.do_sync && array_cnt(atoms) > 0) { warn("-s cannot be used when specifying atoms, dropping -s"); m.do_sync = 0; } /* handle -l / -d conflict */ if (start_time != 0 && m.show_lastmerge) { if (m.show_emerge) warn("-l and -d cannot be used together, dropping -l"); m.show_lastmerge = 0; } /* set default for -t, -a or -r */ if ((m.do_average || m.do_time || m.do_running) && !(m.do_merge || m.do_unmerge || m.do_autoclean || m.do_sync)) { m.do_merge = 1; m.do_unmerge = 1; if (array_cnt(atoms) == 0) m.do_sync = 1; } /* set format if none given */ if (m.fmt == NULL) { if (verbose) m.fmt = "%[CATEGORY]%[PF]"; else m.fmt = "%[CATEGORY]%[PN]"; } if (m.do_running) { array_t *new_atoms = NULL; if (runningmode > 1) { warn("running without /proc scanning, heuristics only"); } else { new_atoms = probe_proc(atoms); } if (new_atoms != NULL && array_cnt(new_atoms) == 0) { /* proc supported, found nothing running */ start_time = LONG_MAX; } /* NOTE: new_atoms == atoms when new_atoms != NULL */ } if (start_time < LONG_MAX) do_emerge_log(logfile, &m, atoms, start_time, end_time); array_for_each(atoms, i, atom) atom_implode(atom); xarrayfree_int(atoms); free(logfile); return EXIT_SUCCESS; }