static int SelectProcTimeCounterRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line) { if ((min == CF_NOINT) || (max == CF_NOINT)) { return false; } int i = GetProcColumnIndex(name1, name2, names); if (i != -1) { time_t value = (time_t) TimeCounter2Int(line[i]); if (value == CF_NOINT) { Log(LOG_LEVEL_INFO, "Failed to extract a valid integer from %c => '%s' in process list", name1[i], line[i]); return false; } if ((min <= value) && (value <= max)) { Log(LOG_LEVEL_VERBOSE, "Selection filter matched counter range" " '%s/%s' = '%s' in [%jd,%jd] (= %jd secs)", name1, name2, line[i], (intmax_t)min, (intmax_t)max, (intmax_t)value); return true; } else { LogDebug(LOG_MOD_PS, "Selection filter REJECTED counter range" " '%s/%s' = '%s' in [%jd,%jd] (= %jd secs)", name1, name2, line[i], (intmax_t) min, (intmax_t) max, (intmax_t) value); return false; } } return false; }
static int SelectProcTimeCounterRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line) { int i; time_t value; if (min == CF_NOINT || max == CF_NOINT) { return false; } if ((i = GetProcColumnIndex(name1, name2, names)) != -1) { value = (time_t) TimeCounter2Int(line[i]); if (value == CF_NOINT) { CfOut(cf_inform, "", "Failed to extract a valid integer from %c => \"%s\" in process list\n", name1[i], line[i]); return false; } if (min <= value && value <= max) { CfOut(cf_verbose, "", "Selection filter matched counter range %s/%s = %s in [%ld,%ld] (= %ld secs)\n", name1, name2, line[i], min, max, value); return true; } else { CfDebug("Selection filter REJECTED counter range %s/%s = %s in [%ld,%ld] (= %ld secs)\n", name1, name2, line[i], min, max, value); return false; } } return false; }
static void MaybeFixStartTime(const char *line, time_t pstime, char **names, char **fields) { /* Since start times can be very imprecise (e.g. just a past day's * date, or a past year), calculate a better value from elapsed * time, if available: */ int k = GetProcColumnIndex("ELAPSED", "ELAPSED", names); if (k != -1) { const long elapsed = TimeCounter2Int(fields[k]); if (elapsed != CF_NOINT) /* Only use if parsed successfully ! */ { int j = GetProcColumnIndex("STIME", "START", names), ns[3]; /* Trust the reported value if it matches hh:mm[:ss], though: */ if (sscanf(fields[j], "%d:%d:%d", ns, ns + 1, ns + 2) < 2) { time_t value = pstime - (time_t) elapsed; Log(LOG_LEVEL_DEBUG, "processes: Replacing parsed start time %s with %s", fields[j], ctime(&value)); free(fields[j]); xasprintf(fields + j, "%ld", value); } } else if (fields[k]) { Log(LOG_LEVEL_VERBOSE, "Parsing problem was in ELAPSED field of '%s'", line); } } }
static int SplitProcLine(const char *proc, time_t pstime, char **names, int *start, int *end, char **line) { if (proc == NULL || proc[0] == '\0') { return false; } memset(line, 0, sizeof(char *) * CF_PROCCOLS); int prior = -1; /* End of last header-selected field. */ const size_t linelen = strlen(proc); const char *sp = proc; /* Just after last space-separated field. */ /* Scan in parallel for two heuristics: space-delimited fields * found using sp, and ones inferred from the column headers. */ /* TODO: we test with isspace() mostly, which makes sense as TAB * might be used to enable field alignment; but we make no attempt * to account for the alignment offsets this may introduce, that * would mess up the heuristic based on column headers. Hard to * do robustly, as we'd need to assume a tab-width and apply the * same accounting to the headers (where start and end are indices * of bytes, we'd need a third array for tab-induced offsets * between headers). */ for (int i = 0; i < CF_PROCCOLS && names[i] != NULL; i++) { /* Space-delimited heuristic, from sp to just before ep: */ while (isspace((unsigned char) sp[0])) { sp++; } const char *ep = sp; /* Header-driven heuristic, field from proc[s] to proc[e]. * Start with the column header's position and maybe grow * outwards. */ int s = start[i], e; /* If the previous field over-spilled into this one, our start * may be under our header, not directly below the header's * start; but only believe this if the earlier field is * followed by one space and the subsequent field does start * under our header. Otherwise, allow that the prior field * may be abutting this field, or this field may have * overflowed left into the prior field causing the prior to * (mistakenly) think it includes the present. */ if (s <= prior && prior + 2 <= end[i] && proc[prior + 1] == ' ' && proc[prior + 2] != '\0' && !isspace((unsigned char) proc[prior + 2])) { s = prior + 2; } if (i + 1 == CF_PROCCOLS || names[i + 1] == NULL) { e = linelen - 1; /* Extend space-delimited field to line end: */ while (ep[0] && ep[0] != '\n') { ep++; } /* But trim trailing hspace: */ while (isspace((unsigned char)ep[-1])) { ep--; } } else { e = end[i]; /* It's possible the present field has been shunted left * to make way for an over-sized number or date to the * right. This can confuse the numeric check below, on * which left-overspill is conditioned - it fails to * recognise this field as numeric, so doesn't check for * it overspilling to the left. Look to see whether we * should move e left a bit: this is a conservative check, * that'll be revisited below. */ int back = start[i + 1]; while (back > s && !isspace((unsigned char) proc[back])) { back--; } if (back > s && back <= e && /* back > s implies isspace(proc[back]), with no * further space before the next field; if this is a * single space, it's credibly the separator between * our field, shunted left, and this over-spilled * field: */ proc[back] == ' ' && !isspace((unsigned char) proc[back - 1])) { /* So we have a non-empty field that ends under our * header but before e; adjust e. */ e = back - 1; } /* Extend space-delimited to next space: */ while (ep[0] && !isspace((unsigned char)ep[0])) { ep++; } } /* ep points at the space (or '\0') *following* the word or * final field. */ /* Some ps stimes may contain spaces, e.g. "Jan 25" */ if (strcmp(names[i], "STIME") == 0 && ep - sp == 3) { const char *np = ep; while (isspace((unsigned char) np[0])) { np++; } if (isdigit((unsigned char) np[0])) { do { np++; } while (isdigit((unsigned char) np[0])); ep = np; } } /* Numeric columns, including times, are apt to grow leftwards * and be right-aligned; identify candidates for this by * proc[e] being a digit. Text columns are typically * left-aligned and may grow rightwards; identify candidates * for this by proc[s] being alphabetic. Some columns shall * match both (e.g. STIME). Some numeric columns may grow * left even as far as under the heading of the next column * (seen with ps -fel's SZ spilling left into ADDR's space on * Linux). While widening, it's OK to include a stray space; * we'll trim that afterwards. Overspill from neighbouring * columns can muck up alignment, so "digit" means any * character that can appear in a numeric field (we may need * to tweak "alphabetic" likewise for text fields; the user * field can, for example, have a '+' appended). */ bool overspilt = false; /* Numeric fields may include [-.:] and perhaps other * punctuators: */ #define IsNumberish(c) (isdigit((unsigned char)(c)) || ispunct((unsigned char)(c))) /* Right-aligned numeric: move s left until we run outside the * field or find space. */ if (IsNumberish(proc[e])) { bool number = i > 0; /* Should we check for under-spill ? */ int outer = number ? end[i - 1] + 1 : 0; while (s >= outer && !isspace((unsigned char) proc[s])) { if (number && !IsNumberish(proc[s])) { number = false; } s--; } /* Numeric field might overspill under previous header: */ if (s < outer) { int spill = s; s = outer; /* By default, don't overlap previous column. */ if (number && IsNumberish(proc[spill])) { outer = start[i - 1]; /* Explore all the way to the start-column of the * previous field's header. If we get there, in * digits-and-punctuation, we've got two numeric * fields that abut; we can't do better than assume * the boundary is under the right end of the previous * column label (which is what our parsing of the * previous column assumed). So leave s where it is, * just after the previous field's header's * end-column. */ while (spill > outer) { spill--; if (!IsNumberish(proc[spill])) { s = spill + 1; /* Confirmed overlap. */ break; } } } } overspilt = IsNumberish(proc[e + 1]); } #undef IsNumberish bool abut = false; /* Left-aligned text or numeric misaligned by overspill; move * e right (if there's any right to move into; last column * already reaches end of proc): */ if (proc[e + 1] && (overspilt || isalpha((unsigned char) proc[s]))) { assert(i + 1 < CF_PROCCOLS && names[i + 1]); int outer = start[i + 1]; /* Start of next field's header. */ int beyond = end[i + 1]; /* End of next field's header. */ if (beyond > linelen) { /* Command is shorter than its header. */ assert(i + 2 >= CF_PROCCOLS || NULL == names[i + 2]); beyond = linelen; } assert(beyond >= outer); int out = e; do { out++; } while (out < beyond && !isspace((unsigned char) proc[out])); if (out < outer) { /* Simple extension to the right, no overlap: we're on * a space just before the next field's header. */ e = out - 1; } else if (out == beyond) { /* This looks like we're actually looking at the next * field over-spilling left so far that it reaches * under our header; but we did previously check for * this and amend e left-wards if it's credible; so * we're now looking at a case where it isn't; * probably our columns abut, with no space in * between. The best we can do is include the text * between columns as part of both columns :-( */ abut = true; prior = e; /* Before adjusting it: */ e = outer - 1; } else { /* Our word appears to over-spill under the next * header. See if that's credible. If the next is a * left-aligned field, it'll follow ours after exactly * a simple space. If it's right-aligned (numeric), * it'll still follow after a single space unless it * has enough space to fit under its header after more * than one space. */ assert(out < beyond && isspace((unsigned char) proc[out])); if ((proc[out] == ' ' && proc[out + 1] && !isspace((unsigned char) proc[out + 1])) || (isdigit((unsigned char) proc[beyond]) && isspace((unsigned char) proc[beyond + 1]))) { e = out - 1; } else { e = outer - 1; } } } if (!abut) { prior = e; } /* Strip off any leading and trailing spaces: */ while (isspace((unsigned char) proc[s])) { s++; } /* ... but stop if the field is empty ! */ while (s <= e && isspace((unsigned char) proc[e])) { e--; } /* Grumble if the two heuristics don't agree: */ size_t wordlen = ep - sp; if (e < s ? ep > sp : (sp != proc + s || ep != proc + e + 1)) { char word[CF_SMALLBUF]; if (wordlen >= CF_SMALLBUF) { wordlen = CF_SMALLBUF - 1; } memcpy(word, sp, wordlen); word[wordlen] = '\0'; char column[CF_SMALLBUF]; if (s <= e) { /* Copy proc[s through e] inclusive: */ const size_t len = MIN(1 + e - s, CF_SMALLBUF - 1); memcpy(column, proc + s, len); column[len] = '\0'; } else { column[0] = '\0'; } Log(LOG_LEVEL_VERBOSE, "Unreliable fuzzy parsing of ps output (%s) %s: '%s' != '%s'", proc, names[i], word, column); } /* Fall back on word if column got an empty answer: */ line[i] = e < s ? xstrndup(sp, ep - sp) : xstrndup(proc + s, 1 + e - s); sp = ep; } /* Since start times can be very imprecise (e.g. just a past day's * date, or a past year), calculate a better value from elapsed * time, if available: */ int k = GetProcColumnIndex("ELAPSED", "ELAPSED", names); if (k != -1) { const long elapsed = TimeCounter2Int(line[k]); if (elapsed != CF_NOINT) /* Only use if parsed successfully ! */ { int j = GetProcColumnIndex("STIME", "START", names), ns[3]; /* Trust the reported value if it matches hh:mm[:ss], though: */ if (sscanf(line[j], "%d:%d:%d", ns, ns + 1, ns + 2) < 2) { time_t value = pstime - (time_t) elapsed; Log(LOG_LEVEL_DEBUG, "SplitProcLine: Replacing parsed start time %s with %s", line[j], ctime(&value)); free(line[j]); xasprintf(line + j, "%ld", value); } } } return true; }
static int SplitProcLine(const char *proc, char **names, int *start, int *end, char **line) { if (proc == NULL || proc[0] == '\0') { return false; } memset(line, 0, sizeof(char *) * CF_PROCCOLS); const char *sp = proc; /* Scan in parallel for two heuristics: space-delimited fields * found using sp, and ones inferred from the column headers. */ for (int i = 0; i < CF_PROCCOLS && names[i] != NULL; i++) { /* Space-delimited heuristic, from sp to just before ep: */ while (isspace((unsigned char) sp[0])) { sp++; } const char *ep = sp; /* Header-driven heuristic, field from proc[s] to proc[e]. * Start with the column header's position and maybe grow * outwards. */ int s = start[i], e; if (i + 1 == CF_PROCCOLS || names[i + 1] == NULL) { e = strlen(proc) - 1; /* Extend space-delimited field to line end: */ while (ep[0] && ep[0] != '\n') { ep++; } /* But trim trailing hspace: */ while (isspace((unsigned char)ep[-1])) { ep--; } } else { e = end[i]; /* Extend space-delimited to next space: */ while (ep[0] && !isspace((unsigned char)ep[0])) { ep++; } } /* ep points at the space (or '\0') *following* the word or * final field. */ /* Some ps stimes may contain spaces, e.g. "Jan 25" */ if (strcmp(names[i], "STIME") == 0 && ep - sp == 3) { const char *np = ep; while (isspace((unsigned char) np[0])) { np++; } if (isdigit((unsigned char) np[0])) { do { np++; } while (isdigit((unsigned char) np[0])); ep = np; } } /* Numeric columns, including times, are apt to grow leftwards * and be right-aligned; identify candidates for this by * proc[e] being a digit. Text columns are typically * left-aligned and may grow rightwards; identify candidates * for this by proc[s] being alphabetic. Some columns shall * match both (e.g. STIME). Some numeric columns may grow * left even as far as under the heading of the next column * (seen with ps -fel's SZ spilling left into ADDR's space on * Linux). While widening, it's OK to include a stray space; * we'll trim that afterwards. Overspill from neighbouring * columns can muck up alignment, so "digit" means any * character that can appear in a numeric field (we may need * to tweak "alphabetic" likewise for text fields; the user * field can, for example, have a '+' appended). */ bool overspilt = false; /* Numeric fields may include [-.:] and perhaps other * punctuators: */ #define IsNumberish(c) (isdigit((unsigned char)(c)) || ispunct((unsigned char)(c))) /* Right-aligned numeric: move s left until we run outside the * field or find space. */ if (IsNumberish(proc[e])) { bool number = i > 0; /* Should we check for under-spill ? */ int outer = number ? end[i - 1] + 1 : 0; while (s >= outer && !isspace((unsigned char) proc[s])) { if (number && !IsNumberish(proc[s])) { number = false; } s--; } /* Numeric field might overspill under previous header: */ if (s < outer) { int spill = s; s = outer; /* By default, don't overlap previous column. */ if (number && IsNumberish(proc[spill])) { outer = start[i - 1]; /* Explore all the way to the start-column of the * previous field's header. If we get there, in * digits-and-punctuation, we've got two numeric * fields that abut; we can't do better than assume * the boundary is under the right end of the previous * column label (which is what our parsing of the * previous column assumed). So leave s where it is, * just after the previous field's header's * end-column. */ while (spill > outer) { spill--; if (!IsNumberish(proc[spill])) { s = spill + 1; /* Confirmed overlap. */ break; } } } } overspilt = IsNumberish(proc[e + 1]); } #undef IsNumberish /* Left-aligned text or numeric misaligned by overspill; * move e right (but never under next heading): */ if (overspilt || isalpha((unsigned char) proc[s])) { int outer; if (i + 1 < CF_PROCCOLS && names[i + 1]) { outer = start[i + 1]; } else { outer = strlen(proc); } /* Simplify loop condition; we want e to end *before* next * field, not on its first byte (or the terminator): */ outer--; while (e < outer && !isspace((unsigned char) proc[e])) { e++; } } /* Strip off any leading and trailing spaces: */ while (isspace((unsigned char) proc[s])) { s++; } /* ... but stop if the field is empty ! */ while (s <= e && isspace((unsigned char) proc[e])) { e--; } /* Grumble if the two heuristics don't agree: */ size_t wordlen = ep - sp; if (e < s ? ep > sp : (sp != proc + s || ep != proc + e + 1)) { char word[CF_SMALLBUF]; if (wordlen >= CF_SMALLBUF) { wordlen = CF_SMALLBUF - 1; } memcpy(word, sp, wordlen); word[wordlen] = '\0'; char column[CF_SMALLBUF]; if (s <= e) { /* Copy proc[s through e] inclusive: */ const size_t len = MIN(1 + e - s, CF_SMALLBUF - 1); memcpy(column, proc + s, len); column[len] = '\0'; } else { column[0] = '\0'; } Log(LOG_LEVEL_INFO, "Unacceptable model uncertainty examining process(%s): '%s' != '%s'", proc, word, column); } /* Fall back on word if column got an empty answer: */ line[i] = e < s ? xstrndup(sp, ep - sp) : xstrndup(proc + s, 1 + e - s); sp = ep; } /* Since start times can be very imprecise (e.g. just a past day's * date, or a past year), calculate a better value from elapsed * time, if available: */ int k = GetProcColumnIndex("ELAPSED", "ELAPSED", names); if (k != -1) { const long elapsed = TimeCounter2Int(line[k]); if (elapsed != CF_NOINT) /* Only use if parsed successfully ! */ { int j = GetProcColumnIndex("STIME", "START", names), ns[3]; /* Trust the reported value if it matches hh:mm[:ss], though: */ if (sscanf(line[j], "%d:%d:%d", ns, ns + 1, ns + 2) < 2) { /* TODO: use time of ps-run, not time(NULL), which may be later. */ time_t value = time(NULL) - (time_t) elapsed; Log(LOG_LEVEL_DEBUG, "SplitProcLine: Replacing parsed start time %s with %s", line[j], ctime(&value)); free(line[j]); xasprintf(line + j, "%ld", value); } } } return true; }