1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <processes_select.h>
26
27 #include <string.h>
28
29 #include <eval_context.h>
30 #include <files_names.h>
31 #include <conversion.h>
32 #include <matching.h>
33 #include <systype.h>
34 #include <string_lib.h> /* Chop */
35 #include <regex.h> /* CompileRegex,StringMatchWithPrecompiledRegex,StringMatchFull */
36 #include <item_lib.h>
37 #include <file_lib.h> // SetUmask(), RestoreUmask()
38 #include <pipes.h>
39 #include <files_interfaces.h>
40 #include <rlist.h>
41 #include <policy.h>
42 #include <zones.h>
43 #include <printsize.h>
44 #include <known_dirs.h>
45
46 # ifdef HAVE_GETZONEID
47 #include <sequence.h>
48 #define MAX_ZONENAME_SIZE 64
49 # endif
50
51 #ifdef _WIN32
52 #define TABLE_STORAGE
53 #else
54 #define TABLE_STORAGE static
55 #endif
56 TABLE_STORAGE Item *PROCESSTABLE = NULL;
57
58 typedef enum
59 {
60 /*
61 * In this mode, all columns must be present by the occurrence of at least
62 * one non-whitespace character.
63 */
64 PCA_AllColumnsPresent,
65 /*
66 * In this mode, if a process is a zombie, and if there is nothing but
67 * whitespace in the byte columns directly below a header, that column is
68 * skipped.
69 *
70 * This means that very little text shifting will be tolerated, preferably
71 * none, or small enough that all column entries still remain under their
72 * own header. It also means that a zombie process must be detectable by
73 * reading the 'Z' state directly below the 'S' or 'ST' header.
74 */
75 PCA_ZombieSkipEmptyColumns,
76 } PsColumnAlgorithm;
77
78 /*
79 Ideally this should be autoconf-tested, but it's really hard to do so. See
80 SplitProcLine() to see the exact effects this has.
81 */
82 static const PsColumnAlgorithm PS_COLUMN_ALGORITHM[] =
83 {
84 [PLATFORM_CONTEXT_UNKNOWN] = PCA_AllColumnsPresent,
85 [PLATFORM_CONTEXT_OPENVZ] = PCA_AllColumnsPresent, /* virt_host_vz_vzps */
86 [PLATFORM_CONTEXT_HP] = PCA_AllColumnsPresent, /* hpux */
87 [PLATFORM_CONTEXT_AIX] = PCA_ZombieSkipEmptyColumns, /* aix */
88 [PLATFORM_CONTEXT_LINUX] = PCA_AllColumnsPresent, /* linux */
89 [PLATFORM_CONTEXT_BUSYBOX] = PCA_AllColumnsPresent, /* linux */
90 [PLATFORM_CONTEXT_SOLARIS] = PCA_AllColumnsPresent, /* solaris >= 11 */
91 [PLATFORM_CONTEXT_SUN_SOLARIS] = PCA_AllColumnsPresent, /* solaris < 11 */
92 [PLATFORM_CONTEXT_FREEBSD] = PCA_AllColumnsPresent, /* freebsd */
93 [PLATFORM_CONTEXT_NETBSD] = PCA_AllColumnsPresent, /* netbsd */
94 [PLATFORM_CONTEXT_CRAYOS] = PCA_AllColumnsPresent, /* cray */
95 [PLATFORM_CONTEXT_WINDOWS_NT] = PCA_AllColumnsPresent, /* NT - cygnus */
96 [PLATFORM_CONTEXT_SYSTEMV] = PCA_AllColumnsPresent, /* unixware */
97 [PLATFORM_CONTEXT_OPENBSD] = PCA_AllColumnsPresent, /* openbsd */
98 [PLATFORM_CONTEXT_CFSCO] = PCA_AllColumnsPresent, /* sco */
99 [PLATFORM_CONTEXT_DARWIN] = PCA_AllColumnsPresent, /* darwin */
100 [PLATFORM_CONTEXT_QNX] = PCA_AllColumnsPresent, /* qnx */
101 [PLATFORM_CONTEXT_DRAGONFLY] = PCA_AllColumnsPresent, /* dragonfly */
102 [PLATFORM_CONTEXT_MINGW] = PCA_AllColumnsPresent, /* mingw */
103 [PLATFORM_CONTEXT_VMWARE] = PCA_AllColumnsPresent, /* vmware */
104 [PLATFORM_CONTEXT_ANDROID] = PCA_AllColumnsPresent, /* android */
105 };
106
107 #if defined(__sun) || defined(TEST_UNIT_TEST)
108 static StringMap *UCB_PS_MAP = NULL;
109 // These will be tried in order.
110 static const char *UCB_STYLE_PS[] = {
111 "/usr/ucb/ps",
112 "/bin/ps",
113 NULL
114 };
115 static const char *UCB_STYLE_PS_ARGS = "axwww";
116 static const PsColumnAlgorithm UCB_STYLE_PS_COLUMN_ALGORITHM = PCA_ZombieSkipEmptyColumns;
117 #endif
118
119 static bool SelectProcRangeMatch(char *name1, char *name2, int min, int max, char **names, char **line);
120 static bool SelectProcRegexMatch(const char *name1, const char *name2, const char *regex, bool anchored, char **colNames, char **line);
121 static bool SplitProcLine(const char *proc,
122 time_t pstime,
123 char **names,
124 int *start,
125 int *end,
126 PsColumnAlgorithm pca,
127 char **line);
128 static bool SelectProcTimeCounterRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line);
129 static bool SelectProcTimeAbsRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line);
130 static int GetProcColumnIndex(const char *name1, const char *name2, char **names);
131 static void GetProcessColumnNames(const char *proc, char **names, int *start, int *end);
132 static int ExtractPid(char *psentry, char **names, int *end);
133 static void ApplyPlatformExtraTable(char **names, char **columns);
134
135 /***************************************************************************/
136
SelectProcess(const char * procentry,time_t pstime,char ** names,int * start,int * end,const char * process_regex,const ProcessSelect * a,bool attrselect)137 static bool SelectProcess(const char *procentry,
138 time_t pstime,
139 char **names,
140 int *start,
141 int *end,
142 const char *process_regex,
143 const ProcessSelect *a,
144 bool attrselect)
145 {
146 bool result = true;
147 char *column[CF_PROCCOLS];
148 Rlist *rp;
149
150 assert(process_regex);
151 assert(a != NULL);
152
153 StringSet *process_select_attributes = StringSetNew();
154
155 memset(column, 0, sizeof(column));
156
157 if (!SplitProcLine(procentry, pstime, names, start, end,
158 PS_COLUMN_ALGORITHM[VPSHARDCLASS], column))
159 {
160 result = false;
161 goto cleanup;
162 }
163
164 ApplyPlatformExtraTable(names, column);
165
166 for (int i = 0; names[i] != NULL; i++)
167 {
168 LogDebug(LOG_MOD_PS, "In SelectProcess, COL[%s] = '%s'",
169 names[i], column[i]);
170 }
171
172 if (!SelectProcRegexMatch("CMD", "COMMAND", process_regex, false, names, column))
173 {
174 result = false;
175 goto cleanup;
176 }
177
178 if (!attrselect)
179 {
180 // If we are not considering attributes, then the matching is done.
181 goto cleanup;
182 }
183
184 for (rp = a->owner; rp != NULL; rp = rp->next)
185 {
186 if (rp->val.type == RVAL_TYPE_FNCALL)
187 {
188 Log(LOG_LEVEL_VERBOSE,
189 "Function call '%s' in process_select body was not resolved, skipping",
190 RlistFnCallValue(rp)->name);
191 }
192 else if (SelectProcRegexMatch("USER", "UID", RlistScalarValue(rp), true, names, column))
193 {
194 StringSetAdd(process_select_attributes, xstrdup("process_owner"));
195 break;
196 }
197 }
198
199 if (SelectProcRangeMatch("PID", "PID", a->min_pid, a->max_pid, names, column))
200 {
201 StringSetAdd(process_select_attributes, xstrdup("pid"));
202 }
203
204 if (SelectProcRangeMatch("PPID", "PPID", a->min_ppid, a->max_ppid, names, column))
205 {
206 StringSetAdd(process_select_attributes, xstrdup("ppid"));
207 }
208
209 if (SelectProcRangeMatch("PGID", "PGID", a->min_pgid, a->max_pgid, names, column))
210 {
211 StringSetAdd(process_select_attributes, xstrdup("pgid"));
212 }
213
214 if (SelectProcRangeMatch("VSZ", "SZ", a->min_vsize, a->max_vsize, names, column))
215 {
216 StringSetAdd(process_select_attributes, xstrdup("vsize"));
217 }
218
219 if (SelectProcRangeMatch("RSS", "RSS", a->min_rsize, a->max_rsize, names, column))
220 {
221 StringSetAdd(process_select_attributes, xstrdup("rsize"));
222 }
223
224 if (SelectProcTimeCounterRangeMatch("TIME", "TIME", a->min_ttime, a->max_ttime, names, column))
225 {
226 StringSetAdd(process_select_attributes, xstrdup("ttime"));
227 }
228
229 if (SelectProcTimeAbsRangeMatch
230 ("STIME", "START", a->min_stime, a->max_stime, names, column))
231 {
232 StringSetAdd(process_select_attributes, xstrdup("stime"));
233 }
234
235 if (SelectProcRangeMatch("NI", "PRI", a->min_pri, a->max_pri, names, column))
236 {
237 StringSetAdd(process_select_attributes, xstrdup("priority"));
238 }
239
240 if (SelectProcRangeMatch("NLWP", "NLWP", a->min_thread, a->max_thread, names, column))
241 {
242 StringSetAdd(process_select_attributes, xstrdup("threads"));
243 }
244
245 if (SelectProcRegexMatch("S", "STAT", a->status, true, names, column))
246 {
247 StringSetAdd(process_select_attributes, xstrdup("status"));
248 }
249
250 if (SelectProcRegexMatch("CMD", "COMMAND", a->command, true, names, column))
251 {
252 StringSetAdd(process_select_attributes, xstrdup("command"));
253 }
254
255 if (SelectProcRegexMatch("TTY", "TTY", a->tty, true, names, column))
256 {
257 StringSetAdd(process_select_attributes, xstrdup("tty"));
258 }
259
260 if (!a->process_result)
261 {
262 if (StringSetSize(process_select_attributes) == 0)
263 {
264 result = EvalProcessResult("", process_select_attributes);
265 }
266 else
267 {
268 Writer *w = StringWriter();
269 StringSetIterator iter = StringSetIteratorInit(process_select_attributes);
270 char *attr = StringSetIteratorNext(&iter);
271 WriterWrite(w, attr);
272
273 while ((attr = StringSetIteratorNext(&iter)))
274 {
275 WriterWriteChar(w, '.');
276 WriterWrite(w, attr);
277 }
278
279 result = EvalProcessResult(StringWriterData(w), process_select_attributes);
280 WriterClose(w);
281 }
282 }
283 else
284 {
285 result = EvalProcessResult(a->process_result, process_select_attributes);
286 }
287
288 cleanup:
289 StringSetDestroy(process_select_attributes);
290
291 for (int i = 0; column[i] != NULL; i++)
292 {
293 free(column[i]);
294 }
295
296 return result;
297 }
298
SelectProcesses(const char * process_name,const ProcessSelect * a,bool attrselect)299 Item *SelectProcesses(const char *process_name, const ProcessSelect *a, bool attrselect)
300 {
301 assert(a != NULL);
302 const Item *processes = PROCESSTABLE;
303 Item *result = NULL;
304
305 if (processes == NULL)
306 {
307 return result;
308 }
309
310 char *names[CF_PROCCOLS];
311 int start[CF_PROCCOLS];
312 int end[CF_PROCCOLS];
313
314 GetProcessColumnNames(processes->name, names, start, end);
315
316 /* TODO: use actual time of ps-run, as time(NULL) may be later. */
317 time_t pstime = time(NULL);
318
319 for (Item *ip = processes->next; ip != NULL; ip = ip->next)
320 {
321 if (NULL_OR_EMPTY(ip->name))
322 {
323 continue;
324 }
325
326 if (!SelectProcess(ip->name, pstime, names, start, end, process_name, a, attrselect))
327 {
328 continue;
329 }
330
331 pid_t pid = ExtractPid(ip->name, names, end);
332
333 if (pid == -1)
334 {
335 Log(LOG_LEVEL_VERBOSE, "Unable to extract pid while looking for %s", process_name);
336 continue;
337 }
338
339 PrependItem(&result, ip->name, "");
340 result->counter = (int)pid;
341 }
342
343 for (int i = 0; i < CF_PROCCOLS; i++)
344 {
345 free(names[i]);
346 }
347
348 return result;
349 }
350
SelectProcRangeMatch(char * name1,char * name2,int min,int max,char ** names,char ** line)351 static bool SelectProcRangeMatch(char *name1, char *name2, int min, int max, char **names, char **line)
352 {
353 int i;
354 long value;
355
356 if ((min == CF_NOINT) || (max == CF_NOINT))
357 {
358 return false;
359 }
360
361 if ((i = GetProcColumnIndex(name1, name2, names)) != -1)
362 {
363 value = IntFromString(line[i]);
364
365 if (value == CF_NOINT)
366 {
367 Log(LOG_LEVEL_INFO, "Failed to extract a valid integer from '%s' => '%s' in process list", names[i],
368 line[i]);
369 return false;
370 }
371
372 if ((min <= value) && (value <= max))
373 {
374 return true;
375 }
376 else
377 {
378 return false;
379 }
380 }
381
382 return false;
383 }
384
385 /***************************************************************************/
386
TimeCounter2Int(const char * s)387 static long TimeCounter2Int(const char *s)
388 {
389 long days, hours, minutes, seconds;
390
391 if (s == NULL)
392 {
393 return CF_NOINT;
394 }
395
396 /* If we match dd-hh:mm[:ss], believe it: */
397 int got = sscanf(s, "%ld-%ld:%ld:%ld", &days, &hours, &minutes, &seconds);
398 if (got > 2)
399 {
400 /* All but perhaps seconds set */
401 }
402 /* Failing that, try matching hh:mm[:ss] */
403 else if (1 < (got = sscanf(s, "%ld:%ld:%ld", &hours, &minutes, &seconds)))
404 {
405 /* All but days and perhaps seconds set */
406 days = 0;
407 got++;
408 }
409 else
410 {
411 Log(LOG_LEVEL_ERR,
412 "Unable to parse 'ps' time field as [dd-]hh:mm[:ss], got '%s'",
413 s);
414 return CF_NOINT;
415 }
416 assert(got > 2); /* i.e. all but maybe seconds have been set */
417 /* Clear seconds if unset: */
418 if (got < 4)
419 {
420 seconds = 0;
421 }
422
423 LogDebug(LOG_MOD_PS, "TimeCounter2Int:"
424 " Parsed '%s' as elapsed time '%ld-%02ld:%02ld:%02ld'",
425 s, days, hours, minutes, seconds);
426
427 /* Convert to seconds: */
428 return ((days * 24 + hours) * 60 + minutes) * 60 + seconds;
429 }
430
SelectProcTimeCounterRangeMatch(char * name1,char * name2,time_t min,time_t max,char ** names,char ** line)431 static bool SelectProcTimeCounterRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line)
432 {
433 if ((min == CF_NOINT) || (max == CF_NOINT))
434 {
435 return false;
436 }
437
438 int i = GetProcColumnIndex(name1, name2, names);
439 if (i != -1)
440 {
441 time_t value = (time_t) TimeCounter2Int(line[i]);
442
443 if (value == CF_NOINT)
444 {
445 Log(LOG_LEVEL_INFO,
446 "Failed to extract a valid integer from %s => '%s' in process list",
447 names[i], line[i]);
448 return false;
449 }
450
451 if ((min <= value) && (value <= max))
452 {
453 Log(LOG_LEVEL_VERBOSE, "Selection filter matched counter range"
454 " '%s/%s' = '%s' in [%jd,%jd] (= %jd secs)",
455 name1, name2, line[i], (intmax_t)min, (intmax_t)max, (intmax_t)value);
456 return true;
457 }
458 else
459 {
460 LogDebug(LOG_MOD_PS, "Selection filter REJECTED counter range"
461 " '%s/%s' = '%s' in [%jd,%jd] (= %jd secs)",
462 name1, name2, line[i],
463 (intmax_t) min, (intmax_t) max, (intmax_t) value);
464 return false;
465 }
466 }
467
468 return false;
469 }
470
TimeAbs2Int(const char * s)471 static time_t TimeAbs2Int(const char *s)
472 {
473 if (s == NULL)
474 {
475 return CF_NOINT;
476 }
477
478 struct tm tm;
479 localtime_r(&CFSTARTTIME, &tm);
480 tm.tm_sec = 0;
481 tm.tm_isdst = -1;
482
483 /* Try various ways to parse s: */
484 char word[4]; /* Abbreviated month name */
485 long ns[3]; /* Miscellaneous numbers, diverse readings */
486 int got = sscanf(s, "%2ld:%2ld:%2ld", ns, ns + 1, ns + 2);
487 if (1 < got) /* Hr:Min[:Sec] */
488 {
489 tm.tm_hour = ns[0];
490 tm.tm_min = ns[1];
491 if (got == 3)
492 {
493 tm.tm_sec = ns[2];
494 }
495 }
496 /* or MMM dd (the %ld shall ignore any leading space) */
497 else if (sscanf(s, "%3[a-zA-Z]%ld", word, ns) == 2 &&
498 /* Only match if word is a valid month text: */
499 0 < (ns[1] = Month2Int(word)))
500 {
501 int month = ns[1] - 1;
502 if (tm.tm_mon < month)
503 {
504 /* Wrapped around */
505 tm.tm_year--;
506 }
507 tm.tm_mon = month;
508 tm.tm_mday = ns[0];
509 tm.tm_hour = 0;
510 tm.tm_min = 0;
511 }
512 /* or just year, or seconds since 1970 */
513 else if (sscanf(s, "%ld", ns) == 1)
514 {
515 if (ns[0] > 9999)
516 {
517 /* Seconds since 1970.
518 *
519 * This is the amended value SplitProcLine() replaces
520 * start time with if it's imprecise and a better value
521 * can be calculated from elapsed time.
522 */
523 return (time_t)ns[0];
524 }
525 /* else year, at most four digits; either 4-digit CE or
526 * already relative to 1900. */
527
528 memset(&tm, 0, sizeof(tm));
529 tm.tm_year = ns[0] < 999 ? ns[0] : ns[0] - 1900;
530 tm.tm_isdst = -1;
531 }
532 else
533 {
534 return CF_NOINT;
535 }
536
537 return mktime(&tm);
538 }
539
SelectProcTimeAbsRangeMatch(char * name1,char * name2,time_t min,time_t max,char ** names,char ** line)540 static bool SelectProcTimeAbsRangeMatch(char *name1, char *name2, time_t min, time_t max, char **names, char **line)
541 {
542 int i;
543 time_t value;
544
545 if ((min == CF_NOINT) || (max == CF_NOINT))
546 {
547 return false;
548 }
549
550 if ((i = GetProcColumnIndex(name1, name2, names)) != -1)
551 {
552 value = TimeAbs2Int(line[i]);
553
554 if (value == CF_NOINT)
555 {
556 Log(LOG_LEVEL_INFO, "Failed to extract a valid integer from %c => '%s' in process list", name1[i],
557 line[i]);
558 return false;
559 }
560
561 if ((min <= value) && (value <= max))
562 {
563 Log(LOG_LEVEL_VERBOSE, "Selection filter matched absolute '%s/%s' = '%s(%jd)' in [%jd,%jd]", name1, name2, line[i], (intmax_t)value,
564 (intmax_t)min, (intmax_t)max);
565 return true;
566 }
567 else
568 {
569 return false;
570 }
571 }
572
573 return false;
574 }
575
576 /***************************************************************************/
577
SelectProcRegexMatch(const char * name1,const char * name2,const char * regex,bool anchored,char ** colNames,char ** line)578 static bool SelectProcRegexMatch(const char *name1, const char *name2,
579 const char *regex, bool anchored,
580 char **colNames, char **line)
581 {
582 int i;
583
584 if (regex == NULL)
585 {
586 return false;
587 }
588
589 if ((i = GetProcColumnIndex(name1, name2, colNames)) != -1)
590 {
591 if (anchored)
592 {
593 return StringMatchFull(regex, line[i]);
594 }
595 else
596 {
597 size_t s, e;
598 return StringMatch(regex, line[i], &s, &e);
599 }
600 }
601
602 return false;
603 }
604
PrintStringIndexLine(int prefix_spaces,int len)605 static void PrintStringIndexLine(int prefix_spaces, int len)
606 {
607 char arrow_str[CF_BUFSIZE];
608 arrow_str[0] = '^';
609 arrow_str[1] = '\0';
610 char index_str[CF_BUFSIZE];
611 index_str[0] = '0';
612 index_str[1] = '\0';
613 for (int lineindex = 10; lineindex <= len; lineindex += 10)
614 {
615 char num[PRINTSIZE(lineindex)];
616 xsnprintf(num, sizeof(num), "%10d", lineindex);
617 strlcat(index_str, num, sizeof(index_str));
618 strlcat(arrow_str, " ^", sizeof(arrow_str));
619 }
620
621 // Prefix the beginning of the indexes with the given number.
622 LogDebug(LOG_MOD_PS, "%*s%s", prefix_spaces, "", arrow_str);
623 LogDebug(LOG_MOD_PS, "%*s%s", prefix_spaces, "Index: ", index_str);
624 }
625
MaybeFixStartTime(const char * line,time_t pstime,char ** names,char ** fields)626 static void MaybeFixStartTime(const char *line,
627 time_t pstime,
628 char **names,
629 char **fields)
630 {
631 /* Since start times can be very imprecise (e.g. just a past day's
632 * date, or a past year), calculate a better value from elapsed
633 * time, if available: */
634 int k = GetProcColumnIndex("ELAPSED", "ELAPSED", names);
635 if (k != -1)
636 {
637 const long elapsed = TimeCounter2Int(fields[k]);
638 if (elapsed != CF_NOINT) /* Only use if parsed successfully ! */
639 {
640 int j = GetProcColumnIndex("STIME", "START", names), ns[3];
641 /* Trust the reported value if it matches hh:mm[:ss], though: */
642 if (sscanf(fields[j], "%d:%d:%d", ns, ns + 1, ns + 2) < 2)
643 {
644 time_t value = pstime - (time_t) elapsed;
645
646 LogDebug(LOG_MOD_PS,
647 "processes: Replacing parsed start time %s with %s",
648 fields[j], ctime(&value));
649
650 free(fields[j]);
651 xasprintf(fields + j, "%ld", value);
652 }
653 }
654 else if (fields[k])
655 {
656 Log(LOG_LEVEL_VERBOSE,
657 "Parsing problem was in ELAPSED field of '%s'",
658 line);
659 }
660 }
661 }
662
663 /*******************************************************************/
664 /* fields must be char *fields[CF_PROCCOLS] in fact. */
665 /* pstime should be the time at which ps was run. */
666
SplitProcLine(const char * line,time_t pstime,char ** names,int * start,int * end,PsColumnAlgorithm pca,char ** fields)667 static bool SplitProcLine(const char *line,
668 time_t pstime,
669 char **names,
670 int *start,
671 int *end,
672 PsColumnAlgorithm pca,
673 char **fields)
674 {
675 if (line == NULL || line[0] == '\0')
676 {
677 return false;
678 }
679
680 const size_t checklen = strlen(line);
681 if (checklen > INT_MAX) {
682 Log(LOG_LEVEL_ERR, "Proc line too long (%zu > %d)", checklen, INT_MAX);
683 return false;
684 }
685 int linelen = (int) checklen;
686
687 if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
688 {
689 LogDebug(LOG_MOD_PS, "Parsing ps line: '%s'", line);
690 // Makes the entry line up with the line above.
691 PrintStringIndexLine(18, linelen);
692 }
693
694 /*
695 All platforms have been verified to not produce overlapping fields with
696 currently used ps tools, and hence we can parse based on space separation
697 (with some caveats, see below).
698
699 Dates may have spaces in them, like "May 4", or not, like "May4". Prefer
700 to match a date without spaces as long as it contains a number, but fall
701 back to parsing letters followed by space(s) and a date.
702
703 Commands will also have extra spaces, but it is always the last field, so
704 we just include spaces at this point in the parsing.
705
706 An additional complication is that some platforms (only AIX is known at
707 the time of writing) can have empty columns when a process is a
708 zombie. The plan is to match for this by checking the range between start
709 and end directly below the header (same byte position). If the whole range
710 is whitespace we consider the entry missing. The columns are (presumably)
711 guaranteed to line up correctly for this, since zombie processes do not
712 have memory usage which can produce large, potentially alignment-altering
713 numbers. However, we cannot do this whitespace check in general, because
714 non-zombie processes may shift columns in a way that leaves some columns
715 apparently (but not actually) empty. Zombie processes have state Z and
716 command <defunct> on AIX. Similarly processes marked with command
717 <exiting> also have missing columns and need to be skipped. (AIX only).
718
719 Take these two examples:
720
721 AIX:
722 USER PID PPID PGID %CPU %MEM VSZ NI S STIME TIME COMMAND
723 root 1 0 0 0.0 0.0 784 20 A Nov 28 00:00:00 /etc/init
724 root 1835344 1 1835344 0.0 0.0 944 20 A Nov 28 00:00:00 /usr/lib/errdemon
725 root 2097594 1 1638802 0.0 0.0 596 20 A Nov 28 00:00:05 /usr/sbin/syncd 60
726 root 3408328 1 3408328 0.0 0.0 888 20 A Nov 28 00:00:00 /usr/sbin/srcmstr
727 root 4325852 3408328 4325852 0.0 0.0 728 20 A Nov 28 00:00:00 /usr/sbin/syslogd
728 root 4784534 3408328 4784534 0.0 0.0 1212 20 A Nov 28 00:00:00 sendmail: accepting connections
729 root 5898690 1 5898690 0.0 0.0 1040 20 A Nov 28 00:00:00 /usr/sbin/cron
730 6095244 8913268 8913268 20 Z 00:00:00 <defunct>
731 root 6160866 3408328 6160866 0.0 0.0 1612 20 A Nov 28 00:00:00 /opt/rsct/bin/IBM.ServiceRMd
732 6750680 17826152 17826152 20 Z 00:00:00 <defunct>
733 root 7143692 3408328 7143692 0.0 0.0 476 20 A Nov 28 00:00:00 /var/perf/pm/bin/pmperfrec
734 root 7340384 8651136 8651136 0.0 0.0 500 20 A Nov 28 00:00:00 [trspoolm]
735 root 7602560 8978714 7602560 0.0 0.0 636 20 A Nov 28 00:00:00 sshd: u0013628 [priv]
736 7733720 - - - A - <exiting>
737
738 Solaris 9:
739 USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND
740 jenkins 29769 0.0 0.0 810 2976 pts/1 S 07:22:43 0:00 /usr/bin/perl ../../ps.pl
741 jenkins 29835 - - 0 0 ? Z - 0:00 <defunct>
742 jenkins 10026 0.0 0.3 30927 143632 ? S Jan_21 01:18:58 /usr/jdk/jdk1.6.0_45/bin/java -jar slave.jar
743
744 Due to how the process state 'S' is shifted under the 'S' header in the
745 second example, it is not possible to separate between this and a missing
746 column. Counting spaces is no good, because commands can contain an
747 arbitrary number of spaces, and there is no way to know the byte position
748 where a command begins. Hence the only way is to base this algorithm on
749 platform and only do the "empty column detection" when:
750 * The platform is known to produce empty columns for zombie processes
751 (see PCA_ZombieSkipEmptyColumns)
752 * The platform is known to not shift columns when the process is a
753 zombie.
754 * It is a zombie / exiting / idle process
755 (These states provide almost no useful info in ps output)
756 */
757
758 bool skip = false;
759
760 if (pca == PCA_ZombieSkipEmptyColumns)
761 {
762 // Find out if the process is a zombie.
763 for (int field = 0; names[field] && !skip; field++)
764 {
765 if (strcmp(names[field], "S") == 0 ||
766 strcmp(names[field], "ST") == 0)
767 {
768 // Check for zombie state.
769 for (int pos = start[field]; pos <= end[field] && pos < linelen && !skip; pos++)
770 {
771 // 'Z' letter with word boundary on each side.
772 if (isspace(line[pos - 1])
773 && line[pos] == 'Z'
774 && (isspace(line[pos + 1])
775 || line[pos + 1] == '\0'))
776 {
777 LogDebug(LOG_MOD_PS, "Detected zombie process, "
778 "skipping parsing of empty ps fields.");
779 skip = true;
780 }
781 }
782 }
783 else if (strcmp(names[field], "COMMAND") == 0)
784 {
785 // Check for exiting or idle state.
786 for (int pos = start[field]; pos <= end[field] && pos < linelen && !skip; pos++)
787 {
788 if (!isspace(line[pos])) // Skip spaces
789 {
790 if (strncmp(line + pos, "<exiting>", 9) == 0 ||
791 strncmp(line + pos, "<idle>", 6) == 0)
792 {
793 LogDebug(LOG_MOD_PS, "Detected exiting/idle process, "
794 "skipping parsing of empty ps fields.");
795 skip = true;
796 }
797 else
798 {
799 break;
800 }
801 }
802 }
803 }
804 }
805 }
806
807 int field = 0;
808 int pos = 0;
809 while (names[field])
810 {
811 // Some sanity checks.
812 if (pos >= linelen)
813 {
814 if (pca == PCA_ZombieSkipEmptyColumns && skip)
815 {
816 LogDebug(LOG_MOD_PS, "Assuming '%s' field is empty, "
817 "since ps line '%s' is not long enough to reach under its "
818 "header.", names[field], line);
819 fields[field] = xstrdup("");
820 field++;
821 continue;
822 }
823 else
824 {
825 Log(LOG_LEVEL_ERR, "ps output line '%s' is shorter than its "
826 "associated header.", line);
827 return false;
828 }
829 }
830
831 bool cmd = (strcmp(names[field], "CMD") == 0 ||
832 strcmp(names[field], "COMMAND") == 0);
833 bool stime = !cmd && (strcmp(names[field], "STIME") == 0);
834
835 // Equal boolean results, either both must be true, or both must be
836 // false. IOW we must either both be at the last field, and it must be
837 // CMD, or none of those. |
838 // v
839 if ((names[field + 1] != NULL) == cmd)
840 {
841 Log(LOG_LEVEL_ERR, "Last field of ps output '%s' is not "
842 "CMD/COMMAND.", line);
843 return false;
844 }
845
846 // If zombie/exiting, check if field is empty.
847 if (pca == PCA_ZombieSkipEmptyColumns && skip)
848 {
849 int empty_pos = start[field];
850 bool empty = true;
851 while (empty_pos <= end[field])
852 {
853 if (!isspace(line[empty_pos]))
854 {
855 empty = false;
856 break;
857 }
858 empty_pos++;
859 }
860 if (empty)
861 {
862 LogDebug(LOG_MOD_PS, "Detected empty"
863 " '%s' field between positions %d and %d",
864 names[field], start[field], end[field]);
865 fields[field] = xstrdup("");
866 pos = end[field] + 1;
867 field++;
868 continue;
869 }
870 else
871 {
872 LogDebug(LOG_MOD_PS, "Detected non-empty "
873 "'%s' field between positions %d and %d",
874 names[field], start[field], end[field]);
875 }
876 }
877
878 // Preceding space.
879 while (isspace(line[pos]))
880 {
881 pos++;
882 }
883
884 // Field.
885 int last = pos;
886 if (cmd)
887 {
888 // Last field, slurp up the rest, but discard trailing whitespace.
889 last = linelen;
890 while (last > pos && isspace(line[last - 1]))
891 {
892 last--;
893 }
894 }
895 else if (stime)
896 {
897 while (isalpha(line[last]))
898 {
899 last++;
900 }
901 if (isspace(line[last]))
902 {
903 // In this case we expect spaces followed by a number.
904 // It means what we first read was the month, now is the date.
905 do
906 {
907 last++;
908 } while (isspace(line[last]));
909 if (!isdigit(line[last]))
910 {
911 char fmt[200];
912 xsnprintf(fmt, sizeof(fmt), "Unable to parse STIME entry in ps "
913 "output line '%%s': Expected day number after "
914 "'%%.%ds'", (last - 1) - pos);
915 Log(LOG_LEVEL_ERR, fmt, line, line + pos);
916 return false;
917 }
918 }
919 while (line[last] && !isspace(line[last]))
920 {
921 last++;
922 }
923 }
924 else
925 {
926 // Generic fields
927 while (line[last] && !isspace(line[last]))
928 {
929 last++;
930 }
931 }
932
933 // Make a copy and store in fields.
934 fields[field] = xstrndup(line + pos, last - pos);
935 LogDebug(LOG_MOD_PS, "'%s' field '%s'"
936 " extracted from between positions %d and %d",
937 names[field], fields[field], pos, last - 1);
938
939 pos = last;
940 field++;
941 }
942
943 MaybeFixStartTime(line, pstime, names, fields);
944
945 return true;
946 }
947
948 /*******************************************************************/
949
GetProcColumnIndex(const char * name1,const char * name2,char ** names)950 static int GetProcColumnIndex(const char *name1, const char *name2, char **names)
951 {
952 for (int i = 0; names[i] != NULL; i++)
953 {
954 if (strcmp(names[i], name1) == 0 ||
955 strcmp(names[i], name2) == 0)
956 {
957 return i;
958 }
959 }
960
961 LogDebug(LOG_MOD_PS, "Process column %s/%s"
962 " was not supported on this system",
963 name1, name2);
964
965 return -1;
966 }
967
968 /**********************************************************************************/
969
IsProcessNameRunning(char * procNameRegex)970 bool IsProcessNameRunning(char *procNameRegex)
971 {
972 char *colHeaders[CF_PROCCOLS];
973 int start[CF_PROCCOLS] = { 0 };
974 int end[CF_PROCCOLS] = { 0 };
975 bool matched = false;
976 int i;
977
978 memset(colHeaders, 0, sizeof(colHeaders));
979
980 if (PROCESSTABLE == NULL)
981 {
982 Log(LOG_LEVEL_ERR, "IsProcessNameRunning: PROCESSTABLE is empty");
983 return false;
984 }
985 /* TODO: use actual time of ps-run, not time(NULL), which may be later. */
986 time_t pstime = time(NULL);
987
988 GetProcessColumnNames(PROCESSTABLE->name, colHeaders, start, end);
989
990 for (const Item *ip = PROCESSTABLE->next; !matched && ip != NULL; ip = ip->next) // iterate over ps lines
991 {
992 char *lineSplit[CF_PROCCOLS];
993 memset(lineSplit, 0, sizeof(lineSplit));
994
995 if (NULL_OR_EMPTY(ip->name))
996 {
997 continue;
998 }
999
1000 if (!SplitProcLine(ip->name, pstime, colHeaders, start, end,
1001 PS_COLUMN_ALGORITHM[VPSHARDCLASS], lineSplit))
1002 {
1003 Log(LOG_LEVEL_ERR, "IsProcessNameRunning: Could not split process line '%s'", ip->name);
1004 goto loop_cleanup;
1005 }
1006
1007 ApplyPlatformExtraTable(colHeaders, lineSplit);
1008
1009 if (SelectProcRegexMatch("CMD", "COMMAND", procNameRegex, true, colHeaders, lineSplit))
1010 {
1011 matched = true;
1012 }
1013
1014 loop_cleanup:
1015 for (i = 0; lineSplit[i] != NULL; i++)
1016 {
1017 free(lineSplit[i]);
1018 }
1019 }
1020
1021 for (i = 0; colHeaders[i] != NULL; i++)
1022 {
1023 free(colHeaders[i]);
1024 }
1025
1026 return matched;
1027 }
1028
1029
GetProcessColumnNames(const char * proc,char ** names,int * start,int * end)1030 static void GetProcessColumnNames(const char *proc, char **names, int *start, int *end)
1031 {
1032 char title[16];
1033 int col, offset = 0;
1034
1035 if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
1036 {
1037 LogDebug(LOG_MOD_PS, "Parsing ps line: '%s'", proc);
1038 // Makes the entry line up with the line above.
1039 PrintStringIndexLine(18, strlen(proc));
1040 }
1041
1042 for (col = 0; col < CF_PROCCOLS; col++)
1043 {
1044 start[col] = end[col] = -1;
1045 names[col] = NULL;
1046 }
1047
1048 col = 0;
1049
1050 for (const char *sp = proc; *sp != '\0'; sp++)
1051 {
1052 offset = sp - proc;
1053
1054 if (isspace((unsigned char) *sp))
1055 {
1056 if (start[col] != -1)
1057 {
1058 LogDebug(LOG_MOD_PS, "End of '%s' is %d", title, offset - 1);
1059 end[col++] = offset - 1;
1060 if (col >= CF_PROCCOLS) /* No space for more columns. */
1061 {
1062 size_t blank = strspn(sp, " \t\r\n\f\v");
1063 if (sp[blank]) /* i.e. that wasn't everything. */
1064 {
1065 /* If this happens, we have more columns in
1066 * our ps output than space to store them.
1067 * Update the #define CF_PROCCOLS (last seen
1068 * in libpromises/cf3.defs.h) to a bigger
1069 * number ! */
1070 Log(LOG_LEVEL_ERR,
1071 "Process table lacks space for last columns: %s",
1072 sp + blank);
1073 }
1074 break;
1075 }
1076 }
1077 }
1078 else if (start[col] == -1)
1079 {
1080 if (col == 0)
1081 {
1082 // The first column always extends all the way to the left.
1083 start[col] = 0;
1084 }
1085 else
1086 {
1087 start[col] = offset;
1088 }
1089
1090 if (sscanf(sp, "%15s", title) == 1)
1091 {
1092 LogDebug(LOG_MOD_PS, "Start of '%s' is at offset: %d",
1093 title, offset);
1094 LogDebug(LOG_MOD_PS, "Col[%d] = '%s'", col, title);
1095
1096 names[col] = xstrdup(title);
1097 }
1098 }
1099 }
1100
1101 if (end[col] == -1)
1102 {
1103 LogDebug(LOG_MOD_PS, "End of '%s' is %d", title, offset);
1104 end[col] = offset;
1105 }
1106 }
1107
1108 #ifndef _WIN32
GetProcessOptions(void)1109 static const char *GetProcessOptions(void)
1110 {
1111
1112 # ifdef __linux__
1113 if (strncmp(VSYSNAME.release, "2.4", 3) == 0)
1114 {
1115 // No threads on 2.4 kernels, so omit nlwp
1116 return "-eo user,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss:9,stime,etime,time,args";
1117 }
1118 # endif
1119
1120 return VPSOPTS[VPSHARDCLASS];
1121 }
1122 #endif
1123
ExtractPid(char * psentry,char ** names,int * end)1124 static int ExtractPid(char *psentry, char **names, int *end)
1125 {
1126 int offset = 0;
1127
1128 for (int col = 0; col < CF_PROCCOLS; col++)
1129 {
1130 if (strcmp(names[col], "PID") == 0)
1131 {
1132 if (col > 0)
1133 {
1134 offset = end[col - 1];
1135 }
1136 break;
1137 }
1138 }
1139
1140 for (const char *sp = psentry + offset; *sp != '\0'; sp++) /* if first field contains alpha, skip */
1141 {
1142 /* If start with alphanum then skip it till the first space */
1143
1144 if (isalnum((unsigned char) *sp))
1145 {
1146 while (*sp != ' ' && *sp != '\0')
1147 {
1148 sp++;
1149 }
1150 }
1151
1152 while (*sp == ' ' || *sp == '\t')
1153 {
1154 sp++;
1155 }
1156
1157 int pid;
1158 if (sscanf(sp, "%d", &pid) == 1 && pid != -1)
1159 {
1160 return pid;
1161 }
1162 }
1163
1164 return -1;
1165 }
1166
1167 # ifndef _WIN32
1168 # ifdef HAVE_GETZONEID
1169 /* ListLookup with the following return semantics
1170 * -1 if the first argument is smaller than the second
1171 * 0 if the arguments are equal
1172 * 1 if the first argument is bigger than the second
1173 */
PidListCompare(const void * pid1,const void * pid2,ARG_UNUSED void * user_data)1174 int PidListCompare(const void *pid1, const void *pid2, ARG_UNUSED void *user_data)
1175 {
1176 int p1 = (intptr_t)(void *)pid1;
1177 int p2 = (intptr_t)(void *)pid2;
1178
1179 if (p1 < p2)
1180 {
1181 return -1;
1182 }
1183 else if (p1 > p2)
1184 {
1185 return 1;
1186 }
1187 return 0;
1188 }
1189 /* Load processes using zone-aware ps
1190 * to obtain solaris list of global
1191 * process ids for root and non-root
1192 * users to lookup later */
ZLoadProcesstable(Seq * pidlist,Seq * rootpidlist)1193 int ZLoadProcesstable(Seq *pidlist, Seq *rootpidlist)
1194 {
1195
1196 char *names[CF_PROCCOLS];
1197 int start[CF_PROCCOLS];
1198 int end[CF_PROCCOLS];
1199
1200 const char *pscmd = "/usr/bin/ps -Aleo zone,user,pid";
1201
1202 FILE *psf = cf_popen(pscmd, "r", false);
1203 if (psf == NULL)
1204 {
1205 Log(LOG_LEVEL_ERR, "ZLoadProcesstable: Couldn't open the process list with command %s.", pscmd);
1206 return false;
1207 }
1208
1209 size_t pbuff_size = CF_BUFSIZE;
1210 char *pbuff = xmalloc(pbuff_size);
1211 bool header = true;
1212
1213 while (true)
1214 {
1215 ssize_t res = CfReadLine(&pbuff, &pbuff_size, psf);
1216 if (res == -1)
1217 {
1218 if (!feof(psf))
1219 {
1220 Log(LOG_LEVEL_ERR, "IsGlobalProcess(char **, int): Unable to read process list with command '%s'. (fread: %s)", pscmd, GetErrorStr());
1221 cf_pclose(psf);
1222 free(pbuff);
1223 return false;
1224 }
1225 else
1226 {
1227 break;
1228 }
1229 }
1230 Chop(pbuff, pbuff_size);
1231 if (header) /* This line is the header. */
1232 {
1233 GetProcessColumnNames(pbuff, &names[0], start, end);
1234 }
1235 else
1236 {
1237 int pid = ExtractPid(pbuff, &names[0], end);
1238
1239 size_t zone_offset = strspn(pbuff, " ");
1240 size_t zone_end_offset = strcspn(pbuff + zone_offset, " ") + zone_offset;
1241 size_t user_offset = strspn(pbuff + zone_end_offset, " ") + zone_end_offset;
1242 size_t user_end_offset = strcspn(pbuff + user_offset, " ") + user_offset;
1243 bool is_global = (zone_end_offset - zone_offset == 6
1244 && strncmp(pbuff + zone_offset, "global", 6) == 0);
1245 bool is_root = (user_end_offset - user_offset == 4
1246 && strncmp(pbuff + user_offset, "root", 4) == 0);
1247
1248 if (is_global && is_root)
1249 {
1250 SeqAppend(rootpidlist, (void*)(intptr_t)pid);
1251 }
1252 else if (is_global && !is_root)
1253 {
1254 SeqAppend(pidlist, (void*)(intptr_t)pid);
1255 }
1256 }
1257
1258 header = false;
1259 }
1260 cf_pclose(psf);
1261 free(pbuff);
1262 return true;
1263 }
PidInSeq(Seq * list,int pid)1264 bool PidInSeq(Seq *list, int pid)
1265 {
1266 void *res = SeqLookup(list, (void *)(intptr_t)pid, PidListCompare);
1267 int result = (intptr_t)(void*)res;
1268
1269 if (result == pid)
1270 {
1271 return true;
1272 }
1273 return false;
1274 }
1275 /* return true if the process with
1276 * pid is in the global zone */
IsGlobalProcess(int pid,Seq * pidlist,Seq * rootpidlist)1277 int IsGlobalProcess(int pid, Seq *pidlist, Seq *rootpidlist)
1278 {
1279 if (PidInSeq(pidlist, pid) || PidInSeq(rootpidlist, pid))
1280 {
1281 return true;
1282 }
1283 else
1284 {
1285 return false;
1286 }
1287 }
ZCopyProcessList(Item ** dest,const Item * source,Seq * pidlist,char ** names,int * end)1288 void ZCopyProcessList(Item **dest, const Item *source, Seq *pidlist, char **names, int *end)
1289 {
1290 int gpid = ExtractPid(source->name, names, end);
1291
1292 if (PidInSeq(pidlist, gpid))
1293 {
1294 PrependItem(dest, source->name, "");
1295 }
1296 }
1297 # endif /* HAVE_GETZONEID */
1298
CheckPsLineLimitations(void)1299 static void CheckPsLineLimitations(void)
1300 {
1301 #ifdef __hpux
1302 FILE *ps_fd;
1303 int ret;
1304 char limit[21];
1305 char *buf = NULL;
1306 size_t bufsize = 0;
1307
1308 ps_fd = safe_fopen("/etc/default/ps", "r");
1309 if (!ps_fd)
1310 {
1311 Log(LOG_LEVEL_VERBOSE, "Could not open '/etc/default/ps' "
1312 "to check ps line length limitations.");
1313 return;
1314 }
1315
1316 while (true)
1317 {
1318 ret = CfReadLine(&buf, &bufsize, ps_fd);
1319 if (ret < 0)
1320 {
1321 break;
1322 }
1323
1324 ret = sscanf(buf, "DEFAULT_CMD_LINE_WIDTH = %20[0-9]", limit);
1325
1326 if (ret == 1)
1327 {
1328 if (atoi(limit) < 1024)
1329 {
1330 Log(LOG_LEVEL_VERBOSE, "ps line length limit is less than 1024. "
1331 "Consider adjusting the DEFAULT_CMD_LINE_WIDTH setting in /etc/default/ps "
1332 "in order to guarantee correct process matching.");
1333 }
1334 break;
1335 }
1336 }
1337
1338 free(buf);
1339 fclose(ps_fd);
1340 #endif // __hpux
1341 }
1342 #endif // _WIN32
1343
GetProcessTableLegend(void)1344 const char *GetProcessTableLegend(void)
1345 {
1346 if (PROCESSTABLE)
1347 {
1348 // First entry in the table is legend.
1349 return PROCESSTABLE->name;
1350 }
1351 else
1352 {
1353 return "<Process table not loaded>";
1354 }
1355 }
1356
1357 #if defined(__sun) || defined(TEST_UNIT_TEST)
OpenUcbPsPipe(void)1358 static FILE *OpenUcbPsPipe(void)
1359 {
1360 for (int i = 0; UCB_STYLE_PS[i]; i++)
1361 {
1362 struct stat statbuf;
1363 if (stat(UCB_STYLE_PS[i], &statbuf) < 0)
1364 {
1365 Log(LOG_LEVEL_VERBOSE,
1366 "%s not found, cannot be used for extra process information",
1367 UCB_STYLE_PS[i]);
1368 continue;
1369 }
1370 if (!(statbuf.st_mode & 0111))
1371 {
1372 Log(LOG_LEVEL_VERBOSE,
1373 "%s not executable, cannot be used for extra process information",
1374 UCB_STYLE_PS[i]);
1375 continue;
1376 }
1377
1378 char *ps_cmd;
1379 xasprintf(&ps_cmd, "%s %s", UCB_STYLE_PS[i],
1380 UCB_STYLE_PS_ARGS);
1381
1382 FILE *cmd = cf_popen(ps_cmd, "rt", false);
1383 if (!cmd)
1384 {
1385 Log(LOG_LEVEL_WARNING, "Could not execute \"%s\", extra process "
1386 "information not available. "
1387 "Process command line length may be limited to 80 characters.",
1388 ps_cmd);
1389 }
1390
1391 free(ps_cmd);
1392
1393 return cmd;
1394 }
1395
1396 Log(LOG_LEVEL_VERBOSE, "No eligible tool for extra process information "
1397 "found. Skipping.");
1398
1399 return NULL;
1400 }
1401
ReadFromUcbPsPipe(FILE * cmd)1402 static void ReadFromUcbPsPipe(FILE *cmd)
1403 {
1404 char *names[CF_PROCCOLS];
1405 memset(names, 0, sizeof(names));
1406 int start[CF_PROCCOLS];
1407 int end[CF_PROCCOLS];
1408 char *line = NULL;
1409 size_t linesize = 0;
1410 bool header = true;
1411 time_t pstime = time(NULL);
1412 int pidcol = -1;
1413 int cmdcol = -1;
1414 while (CfReadLine(&line, &linesize, cmd) > 0)
1415 {
1416 if (header)
1417 {
1418 GetProcessColumnNames(line, names, start, end);
1419
1420 for (int i = 0; names[i]; i++)
1421 {
1422 if (strcmp(names[i], "PID") == 0)
1423 {
1424 pidcol = i;
1425 }
1426 else if (strcmp(names[i], "COMMAND") == 0
1427 || strcmp(names[i], "CMD") == 0)
1428 {
1429 cmdcol = i;
1430 }
1431 }
1432
1433 if (pidcol < 0 || cmdcol < 0)
1434 {
1435 Log(LOG_LEVEL_ERR,
1436 "Could not find PID and/or CMD/COMMAND column in "
1437 "ps output: \"%s\"", line);
1438 break;
1439 }
1440
1441 header = false;
1442 continue;
1443 }
1444
1445
1446 char *columns[CF_PROCCOLS];
1447 memset(columns, 0, sizeof(columns));
1448 if (!SplitProcLine(line, pstime, names, start, end,
1449 UCB_STYLE_PS_COLUMN_ALGORITHM, columns))
1450 {
1451 Log(LOG_LEVEL_WARNING,
1452 "Not able to parse ps output: \"%s\"", line);
1453 }
1454
1455 StringMapInsert(UCB_PS_MAP, columns[pidcol], columns[cmdcol]);
1456 // We avoid strdup'ing these strings by claiming ownership here.
1457 columns[pidcol] = NULL;
1458 columns[cmdcol] = NULL;
1459
1460 for (int i = 0; i < CF_PROCCOLS; i++)
1461 {
1462 // There may be some null entries here, but since we "steal"
1463 // strings in the section above, we may have set some of them to
1464 // NULL and there may be following non-NULL fields.
1465 free(columns[i]);
1466 }
1467 }
1468
1469 if (!feof(cmd) && ferror(cmd))
1470 {
1471 Log(LOG_LEVEL_ERR, "Error while reading output from ps: %s",
1472 GetErrorStr());
1473 }
1474
1475 for (int i = 0; names[i] && i < CF_PROCCOLS; i++)
1476 {
1477 free(names[i]);
1478 }
1479
1480 free(line);
1481 }
1482
ClearPlatformExtraTable(void)1483 static void ClearPlatformExtraTable(void)
1484 {
1485 if (UCB_PS_MAP)
1486 {
1487 StringMapDestroy(UCB_PS_MAP);
1488 UCB_PS_MAP = NULL;
1489 }
1490 }
1491
LoadPlatformExtraTable(void)1492 static void LoadPlatformExtraTable(void)
1493 {
1494 if (UCB_PS_MAP)
1495 {
1496 return;
1497 }
1498
1499 UCB_PS_MAP = StringMapNew();
1500
1501 FILE *cmd = OpenUcbPsPipe();
1502 if (!cmd)
1503 {
1504 return;
1505 }
1506 ReadFromUcbPsPipe(cmd);
1507 if (cf_pclose(cmd) != 0)
1508 {
1509 Log(LOG_LEVEL_WARNING, "Command returned non-zero while gathering "
1510 "extra process information.");
1511 // Make an empty map, in this case. The information can't be trusted.
1512 StringMapClear(UCB_PS_MAP);
1513 }
1514 }
1515
ApplyPlatformExtraTable(char ** names,char ** columns)1516 static void ApplyPlatformExtraTable(char **names, char **columns)
1517 {
1518 int pidcol = -1;
1519
1520 for (int i = 0; names[i] && columns[i]; i++)
1521 {
1522 if (strcmp(names[i], "PID") == 0)
1523 {
1524 pidcol = i;
1525 break;
1526 }
1527 }
1528
1529 if (pidcol == -1 || !StringMapHasKey(UCB_PS_MAP, columns[pidcol]))
1530 {
1531 return;
1532 }
1533
1534 for (int i = 0; names[i] && columns[i]; i++)
1535 {
1536 if (strcmp(names[i], "COMMAND") == 0 || strcmp(names[i], "CMD") == 0)
1537 {
1538 free(columns[i]);
1539 columns[i] = xstrdup(StringMapGet(UCB_PS_MAP, columns[pidcol]));
1540 break;
1541 }
1542 }
1543 }
1544
1545 #else
LoadPlatformExtraTable(void)1546 static inline void LoadPlatformExtraTable(void)
1547 {
1548 }
1549
ClearPlatformExtraTable(void)1550 static inline void ClearPlatformExtraTable(void)
1551 {
1552 }
1553
ApplyPlatformExtraTable(ARG_UNUSED char ** names,ARG_UNUSED char ** columns)1554 static inline void ApplyPlatformExtraTable(ARG_UNUSED char **names, ARG_UNUSED char **columns)
1555 {
1556 }
1557 #endif
1558
1559 #ifndef _WIN32
LoadProcessTable()1560 bool LoadProcessTable()
1561 {
1562 FILE *prp;
1563 char pscomm[CF_MAXLINKSIZE];
1564 Item *rootprocs = NULL;
1565 Item *otherprocs = NULL;
1566
1567
1568 if (PROCESSTABLE)
1569 {
1570 Log(LOG_LEVEL_VERBOSE, "Reusing cached process table");
1571 return true;
1572 }
1573
1574 LoadPlatformExtraTable();
1575
1576 CheckPsLineLimitations();
1577
1578 const char *psopts = GetProcessOptions();
1579
1580 snprintf(pscomm, CF_MAXLINKSIZE, "%s %s", VPSCOMM[VPSHARDCLASS], psopts);
1581
1582 Log(LOG_LEVEL_VERBOSE, "Observe process table with %s", pscomm);
1583
1584 if ((prp = cf_popen(pscomm, "r", false)) == NULL)
1585 {
1586 Log(LOG_LEVEL_ERR, "Couldn't open the process list with command '%s'. (popen: %s)", pscomm, GetErrorStr());
1587 return false;
1588 }
1589
1590 size_t vbuff_size = CF_BUFSIZE;
1591 char *vbuff = xmalloc(vbuff_size);
1592
1593 # ifdef HAVE_GETZONEID
1594
1595 char *names[CF_PROCCOLS];
1596 int start[CF_PROCCOLS];
1597 int end[CF_PROCCOLS];
1598 Seq *pidlist = SeqNew(1, NULL);
1599 Seq *rootpidlist = SeqNew(1, NULL);
1600 bool global_zone = IsGlobalZone();
1601
1602 if (global_zone)
1603 {
1604 int res = ZLoadProcesstable(pidlist, rootpidlist);
1605
1606 if (res == false)
1607 {
1608 Log(LOG_LEVEL_ERR, "Unable to load solaris zone process table.");
1609 return false;
1610 }
1611 }
1612
1613 # endif
1614
1615 ARG_UNUSED bool header = true; /* used only if HAVE_GETZONEID */
1616
1617 for (;;)
1618 {
1619 ssize_t res = CfReadLine(&vbuff, &vbuff_size, prp);
1620 if (res == -1)
1621 {
1622 if (!feof(prp))
1623 {
1624 Log(LOG_LEVEL_ERR, "Unable to read process list with command '%s'. (fread: %s)", pscomm, GetErrorStr());
1625 cf_pclose(prp);
1626 free(vbuff);
1627 return false;
1628 }
1629 else
1630 {
1631 break;
1632 }
1633 }
1634 Chop(vbuff, vbuff_size);
1635
1636 # ifdef HAVE_GETZONEID
1637
1638 if (global_zone)
1639 {
1640 if (header)
1641 { /* this is the banner so get the column header names for later use*/
1642 GetProcessColumnNames(vbuff, &names[0], start, end);
1643 }
1644 else
1645 {
1646 int gpid = ExtractPid(vbuff, names, end);
1647
1648 if (!IsGlobalProcess(gpid, pidlist, rootpidlist))
1649 {
1650 continue;
1651 }
1652 }
1653 }
1654
1655 # endif
1656 AppendItem(&PROCESSTABLE, vbuff, "");
1657
1658 header = false;
1659 }
1660
1661 cf_pclose(prp);
1662
1663 /* Now save the data */
1664 const char* const statedir = GetStateDir();
1665
1666 snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_procs", statedir, FILE_SEPARATOR);
1667
1668 RawSaveItemList(PROCESSTABLE, vbuff, NewLineMode_Unix);
1669
1670 # ifdef HAVE_GETZONEID
1671 if (global_zone) /* pidlist and rootpidlist are empty if we're not in the global zone */
1672 {
1673 Item *ip = PROCESSTABLE;
1674 while (ip != NULL)
1675 {
1676 ZCopyProcessList(&rootprocs, ip, rootpidlist, names, end);
1677 ip = ip->next;
1678 }
1679 ReverseItemList(rootprocs);
1680 ip = PROCESSTABLE;
1681 while (ip != NULL)
1682 {
1683 ZCopyProcessList(&otherprocs, ip, pidlist, names, end);
1684 ip = ip->next;
1685 }
1686 ReverseItemList(otherprocs);
1687 }
1688 else
1689 # endif
1690 {
1691 CopyList(&rootprocs, PROCESSTABLE);
1692 CopyList(&otherprocs, PROCESSTABLE);
1693
1694 while (DeleteItemNotContaining(&rootprocs, "root"))
1695 {
1696 }
1697
1698 while (DeleteItemContaining(&otherprocs, "root"))
1699 {
1700 }
1701 }
1702 if (otherprocs)
1703 {
1704 PrependItem(&rootprocs, otherprocs->name, NULL);
1705 }
1706
1707 // TODO: Change safe_fopen() to default to 0600, then remove this.
1708 const mode_t old_umask = SetUmask(0077);
1709
1710 snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_rootprocs", statedir, FILE_SEPARATOR);
1711 RawSaveItemList(rootprocs, vbuff, NewLineMode_Unix);
1712 DeleteItemList(rootprocs);
1713
1714 snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_otherprocs", statedir, FILE_SEPARATOR);
1715 RawSaveItemList(otherprocs, vbuff, NewLineMode_Unix);
1716 DeleteItemList(otherprocs);
1717
1718 RestoreUmask(old_umask);
1719
1720 free(vbuff);
1721 return true;
1722 }
1723 # endif
1724
ClearProcessTable(void)1725 void ClearProcessTable(void)
1726 {
1727 ClearPlatformExtraTable();
1728
1729 DeleteItemList(PROCESSTABLE);
1730 PROCESSTABLE = NULL;
1731 }
1732