1 /*
2 Copyright 2020 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 int 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 size_t linelen = strlen(line);
681
682 if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
683 {
684 LogDebug(LOG_MOD_PS, "Parsing ps line: '%s'", line);
685 // Makes the entry line up with the line above.
686 PrintStringIndexLine(18, linelen);
687 }
688
689 /*
690 All platforms have been verified to not produce overlapping fields with
691 currently used ps tools, and hence we can parse based on space separation
692 (with some caveats, see below).
693
694 Dates may have spaces in them, like "May 4", or not, like "May4". Prefer
695 to match a date without spaces as long as it contains a number, but fall
696 back to parsing letters followed by space(s) and a date.
697
698 Commands will also have extra spaces, but it is always the last field, so
699 we just include spaces at this point in the parsing.
700
701 An additional complication is that some platforms (only AIX is known at
702 the time of writing) can have empty columns when a process is a
703 zombie. The plan is to match for this by checking the range between start
704 and end directly below the header (same byte position). If the whole range
705 is whitespace we consider the entry missing. The columns are (presumably)
706 guaranteed to line up correctly for this, since zombie processes do not
707 have memory usage which can produce large, potentially alignment-altering
708 numbers. However, we cannot do this whitespace check in general, because
709 non-zombie processes may shift columns in a way that leaves some columns
710 apparently (but not actually) empty. Zombie processes have state Z and
711 command <defunct> on AIX. Similarly processes marked with command
712 <exiting> also have missing columns and need to be skipped. (AIX only).
713
714 Take these two examples:
715
716 AIX:
717 USER PID PPID PGID %CPU %MEM VSZ NI S STIME TIME COMMAND
718 root 1 0 0 0.0 0.0 784 20 A Nov 28 00:00:00 /etc/init
719 root 1835344 1 1835344 0.0 0.0 944 20 A Nov 28 00:00:00 /usr/lib/errdemon
720 root 2097594 1 1638802 0.0 0.0 596 20 A Nov 28 00:00:05 /usr/sbin/syncd 60
721 root 3408328 1 3408328 0.0 0.0 888 20 A Nov 28 00:00:00 /usr/sbin/srcmstr
722 root 4325852 3408328 4325852 0.0 0.0 728 20 A Nov 28 00:00:00 /usr/sbin/syslogd
723 root 4784534 3408328 4784534 0.0 0.0 1212 20 A Nov 28 00:00:00 sendmail: accepting connections
724 root 5898690 1 5898690 0.0 0.0 1040 20 A Nov 28 00:00:00 /usr/sbin/cron
725 6095244 8913268 8913268 20 Z 00:00:00 <defunct>
726 root 6160866 3408328 6160866 0.0 0.0 1612 20 A Nov 28 00:00:00 /opt/rsct/bin/IBM.ServiceRMd
727 6750680 17826152 17826152 20 Z 00:00:00 <defunct>
728 root 7143692 3408328 7143692 0.0 0.0 476 20 A Nov 28 00:00:00 /var/perf/pm/bin/pmperfrec
729 root 7340384 8651136 8651136 0.0 0.0 500 20 A Nov 28 00:00:00 [trspoolm]
730 root 7602560 8978714 7602560 0.0 0.0 636 20 A Nov 28 00:00:00 sshd: u0013628 [priv]
731 7733720 - - - A - <exiting>
732
733 Solaris 9:
734 USER PID %CPU %MEM SZ RSS TT S STIME TIME COMMAND
735 jenkins 29769 0.0 0.0 810 2976 pts/1 S 07:22:43 0:00 /usr/bin/perl ../../ps.pl
736 jenkins 29835 - - 0 0 ? Z - 0:00 <defunct>
737 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
738
739 Due to how the process state 'S' is shifted under the 'S' header in the
740 second example, it is not possible to separate between this and a missing
741 column. Counting spaces is no good, because commands can contain an
742 arbitrary number of spaces, and there is no way to know the byte position
743 where a command begins. Hence the only way is to base this algorithm on
744 platform and only do the "empty column detection" when:
745 * The platform is known to produce empty columns for zombie processes
746 (see PCA_ZombieSkipEmptyColumns)
747 * The platform is known to not shift columns when the process is a
748 zombie.
749 * It is a zombie / exiting / idle process
750 (These states provide almost no useful info in ps output)
751 */
752
753 bool skip = false;
754
755 if (pca == PCA_ZombieSkipEmptyColumns)
756 {
757 // Find out if the process is a zombie.
758 for (int field = 0; names[field] && !skip; field++)
759 {
760 if (strcmp(names[field], "S") == 0 ||
761 strcmp(names[field], "ST") == 0)
762 {
763 // Check for zombie state.
764 for (int pos = start[field]; pos <= end[field] && pos < linelen && !skip; pos++)
765 {
766 // 'Z' letter with word boundary on each side.
767 if (isspace(line[pos - 1])
768 && line[pos] == 'Z'
769 && (isspace(line[pos + 1])
770 || line[pos + 1] == '\0'))
771 {
772 LogDebug(LOG_MOD_PS, "Detected zombie process, "
773 "skipping parsing of empty ps fields.");
774 skip = true;
775 }
776 }
777 }
778 else if (strcmp(names[field], "COMMAND") == 0)
779 {
780 // Check for exiting or idle state.
781 for (int pos = start[field]; pos <= end[field] && pos < linelen && !skip; pos++)
782 {
783 if (!isspace(line[pos])) // Skip spaces
784 {
785 if (strncmp(line + pos, "<exiting>", 9) == 0 ||
786 strncmp(line + pos, "<idle>", 6) == 0)
787 {
788 LogDebug(LOG_MOD_PS, "Detected exiting/idle process, "
789 "skipping parsing of empty ps fields.");
790 skip = true;
791 }
792 else
793 {
794 break;
795 }
796 }
797 }
798 }
799 }
800 }
801
802 int field = 0;
803 int pos = 0;
804 while (names[field])
805 {
806 // Some sanity checks.
807 if (pos >= linelen)
808 {
809 if (pca == PCA_ZombieSkipEmptyColumns && skip)
810 {
811 LogDebug(LOG_MOD_PS, "Assuming '%s' field is empty, "
812 "since ps line '%s' is not long enough to reach under its "
813 "header.", names[field], line);
814 fields[field] = xstrdup("");
815 field++;
816 continue;
817 }
818 else
819 {
820 Log(LOG_LEVEL_ERR, "ps output line '%s' is shorter than its "
821 "associated header.", line);
822 return false;
823 }
824 }
825
826 bool cmd = (strcmp(names[field], "CMD") == 0 ||
827 strcmp(names[field], "COMMAND") == 0);
828 bool stime = !cmd && (strcmp(names[field], "STIME") == 0);
829
830 // Equal boolean results, either both must be true, or both must be
831 // false. IOW we must either both be at the last field, and it must be
832 // CMD, or none of those. |
833 // v
834 if ((names[field + 1] != NULL) == cmd)
835 {
836 Log(LOG_LEVEL_ERR, "Last field of ps output '%s' is not "
837 "CMD/COMMAND.", line);
838 return false;
839 }
840
841 // If zombie/exiting, check if field is empty.
842 if (pca == PCA_ZombieSkipEmptyColumns && skip)
843 {
844 int empty_pos = start[field];
845 bool empty = true;
846 while (empty_pos <= end[field])
847 {
848 if (!isspace(line[empty_pos]))
849 {
850 empty = false;
851 break;
852 }
853 empty_pos++;
854 }
855 if (empty)
856 {
857 LogDebug(LOG_MOD_PS, "Detected empty"
858 " '%s' field between positions %d and %d",
859 names[field], start[field], end[field]);
860 fields[field] = xstrdup("");
861 pos = end[field] + 1;
862 field++;
863 continue;
864 }
865 else
866 {
867 LogDebug(LOG_MOD_PS, "Detected non-empty "
868 "'%s' field between positions %d and %d",
869 names[field], start[field], end[field]);
870 }
871 }
872
873 // Preceding space.
874 while (isspace(line[pos]))
875 {
876 pos++;
877 }
878
879 // Field.
880 int last = pos;
881 if (cmd)
882 {
883 // Last field, slurp up the rest, but discard trailing whitespace.
884 last = linelen;
885 while (last > pos && isspace(line[last - 1]))
886 {
887 last--;
888 }
889 }
890 else if (stime)
891 {
892 while (isalpha(line[last]))
893 {
894 last++;
895 }
896 if (isspace(line[last]))
897 {
898 // In this case we expect spaces followed by a number.
899 // It means what we first read was the month, now is the date.
900 do
901 {
902 last++;
903 } while (isspace(line[last]));
904 if (!isdigit(line[last]))
905 {
906 char fmt[200];
907 xsnprintf(fmt, sizeof(fmt), "Unable to parse STIME entry in ps "
908 "output line '%%s': Expected day number after "
909 "'%%.%ds'", (last - 1) - pos);
910 Log(LOG_LEVEL_ERR, fmt, line, line + pos);
911 return false;
912 }
913 }
914 while (line[last] && !isspace(line[last]))
915 {
916 last++;
917 }
918 }
919 else
920 {
921 // Generic fields
922 while (line[last] && !isspace(line[last]))
923 {
924 last++;
925 }
926 }
927
928 // Make a copy and store in fields.
929 fields[field] = xstrndup(line + pos, last - pos);
930 LogDebug(LOG_MOD_PS, "'%s' field '%s'"
931 " extracted from between positions %d and %d",
932 names[field], fields[field], pos, last - 1);
933
934 pos = last;
935 field++;
936 }
937
938 MaybeFixStartTime(line, pstime, names, fields);
939
940 return true;
941 }
942
943 /*******************************************************************/
944
GetProcColumnIndex(const char * name1,const char * name2,char ** names)945 static int GetProcColumnIndex(const char *name1, const char *name2, char **names)
946 {
947 for (int i = 0; names[i] != NULL; i++)
948 {
949 if (strcmp(names[i], name1) == 0 ||
950 strcmp(names[i], name2) == 0)
951 {
952 return i;
953 }
954 }
955
956 LogDebug(LOG_MOD_PS, "Process column %s/%s"
957 " was not supported on this system",
958 name1, name2);
959
960 return -1;
961 }
962
963 /**********************************************************************************/
964
IsProcessNameRunning(char * procNameRegex)965 bool IsProcessNameRunning(char *procNameRegex)
966 {
967 char *colHeaders[CF_PROCCOLS];
968 int start[CF_PROCCOLS] = { 0 };
969 int end[CF_PROCCOLS] = { 0 };
970 bool matched = false;
971 int i;
972
973 memset(colHeaders, 0, sizeof(colHeaders));
974
975 if (PROCESSTABLE == NULL)
976 {
977 Log(LOG_LEVEL_ERR, "IsProcessNameRunning: PROCESSTABLE is empty");
978 return false;
979 }
980 /* TODO: use actual time of ps-run, not time(NULL), which may be later. */
981 time_t pstime = time(NULL);
982
983 GetProcessColumnNames(PROCESSTABLE->name, colHeaders, start, end);
984
985 for (const Item *ip = PROCESSTABLE->next; !matched && ip != NULL; ip = ip->next) // iterate over ps lines
986 {
987 char *lineSplit[CF_PROCCOLS];
988 memset(lineSplit, 0, sizeof(lineSplit));
989
990 if (NULL_OR_EMPTY(ip->name))
991 {
992 continue;
993 }
994
995 if (!SplitProcLine(ip->name, pstime, colHeaders, start, end,
996 PS_COLUMN_ALGORITHM[VPSHARDCLASS], lineSplit))
997 {
998 Log(LOG_LEVEL_ERR, "IsProcessNameRunning: Could not split process line '%s'", ip->name);
999 goto loop_cleanup;
1000 }
1001
1002 ApplyPlatformExtraTable(colHeaders, lineSplit);
1003
1004 if (SelectProcRegexMatch("CMD", "COMMAND", procNameRegex, true, colHeaders, lineSplit))
1005 {
1006 matched = true;
1007 }
1008
1009 loop_cleanup:
1010 for (i = 0; lineSplit[i] != NULL; i++)
1011 {
1012 free(lineSplit[i]);
1013 }
1014 }
1015
1016 for (i = 0; colHeaders[i] != NULL; i++)
1017 {
1018 free(colHeaders[i]);
1019 }
1020
1021 return matched;
1022 }
1023
1024
GetProcessColumnNames(const char * proc,char ** names,int * start,int * end)1025 static void GetProcessColumnNames(const char *proc, char **names, int *start, int *end)
1026 {
1027 char title[16];
1028 int col, offset = 0;
1029
1030 if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
1031 {
1032 LogDebug(LOG_MOD_PS, "Parsing ps line: '%s'", proc);
1033 // Makes the entry line up with the line above.
1034 PrintStringIndexLine(18, strlen(proc));
1035 }
1036
1037 for (col = 0; col < CF_PROCCOLS; col++)
1038 {
1039 start[col] = end[col] = -1;
1040 names[col] = NULL;
1041 }
1042
1043 col = 0;
1044
1045 for (const char *sp = proc; *sp != '\0'; sp++)
1046 {
1047 offset = sp - proc;
1048
1049 if (isspace((unsigned char) *sp))
1050 {
1051 if (start[col] != -1)
1052 {
1053 LogDebug(LOG_MOD_PS, "End of '%s' is %d", title, offset - 1);
1054 end[col++] = offset - 1;
1055 if (col >= CF_PROCCOLS) /* No space for more columns. */
1056 {
1057 size_t blank = strspn(sp, " \t\r\n\f\v");
1058 if (sp[blank]) /* i.e. that wasn't everything. */
1059 {
1060 /* If this happens, we have more columns in
1061 * our ps output than space to store them.
1062 * Update the #define CF_PROCCOLS (last seen
1063 * in libpromises/cf3.defs.h) to a bigger
1064 * number ! */
1065 Log(LOG_LEVEL_ERR,
1066 "Process table lacks space for last columns: %s",
1067 sp + blank);
1068 }
1069 break;
1070 }
1071 }
1072 }
1073 else if (start[col] == -1)
1074 {
1075 if (col == 0)
1076 {
1077 // The first column always extends all the way to the left.
1078 start[col] = 0;
1079 }
1080 else
1081 {
1082 start[col] = offset;
1083 }
1084
1085 if (sscanf(sp, "%15s", title) == 1)
1086 {
1087 LogDebug(LOG_MOD_PS, "Start of '%s' is at offset: %d",
1088 title, offset);
1089 LogDebug(LOG_MOD_PS, "Col[%d] = '%s'", col, title);
1090
1091 names[col] = xstrdup(title);
1092 }
1093 }
1094 }
1095
1096 if (end[col] == -1)
1097 {
1098 LogDebug(LOG_MOD_PS, "End of '%s' is %d", title, offset);
1099 end[col] = offset;
1100 }
1101 }
1102
1103 #ifndef _WIN32
GetProcessOptions(void)1104 static const char *GetProcessOptions(void)
1105 {
1106
1107 # ifdef __linux__
1108 if (strncmp(VSYSNAME.release, "2.4", 3) == 0)
1109 {
1110 // No threads on 2.4 kernels, so omit nlwp
1111 return "-eo user,pid,ppid,pgid,pcpu,pmem,vsz,ni,rss:9,stime,etime,time,args";
1112 }
1113 # endif
1114
1115 return VPSOPTS[VPSHARDCLASS];
1116 }
1117 #endif
1118
ExtractPid(char * psentry,char ** names,int * end)1119 static int ExtractPid(char *psentry, char **names, int *end)
1120 {
1121 int offset = 0;
1122
1123 for (int col = 0; col < CF_PROCCOLS; col++)
1124 {
1125 if (strcmp(names[col], "PID") == 0)
1126 {
1127 if (col > 0)
1128 {
1129 offset = end[col - 1];
1130 }
1131 break;
1132 }
1133 }
1134
1135 for (const char *sp = psentry + offset; *sp != '\0'; sp++) /* if first field contains alpha, skip */
1136 {
1137 /* If start with alphanum then skip it till the first space */
1138
1139 if (isalnum((unsigned char) *sp))
1140 {
1141 while (*sp != ' ' && *sp != '\0')
1142 {
1143 sp++;
1144 }
1145 }
1146
1147 while (*sp == ' ' || *sp == '\t')
1148 {
1149 sp++;
1150 }
1151
1152 int pid;
1153 if (sscanf(sp, "%d", &pid) == 1 && pid != -1)
1154 {
1155 return pid;
1156 }
1157 }
1158
1159 return -1;
1160 }
1161
1162 # ifndef _WIN32
1163 # ifdef HAVE_GETZONEID
1164 /* ListLookup with the following return semantics
1165 * -1 if the first argument is smaller than the second
1166 * 0 if the arguments are equal
1167 * 1 if the first argument is bigger than the second
1168 */
PidListCompare(const void * pid1,const void * pid2,ARG_UNUSED void * user_data)1169 int PidListCompare(const void *pid1, const void *pid2, ARG_UNUSED void *user_data)
1170 {
1171 int p1 = (intptr_t)(void *)pid1;
1172 int p2 = (intptr_t)(void *)pid2;
1173
1174 if (p1 < p2)
1175 {
1176 return -1;
1177 }
1178 else if (p1 > p2)
1179 {
1180 return 1;
1181 }
1182 return 0;
1183 }
1184 /* Load processes using zone-aware ps
1185 * to obtain solaris list of global
1186 * process ids for root and non-root
1187 * users to lookup later */
ZLoadProcesstable(Seq * pidlist,Seq * rootpidlist)1188 int ZLoadProcesstable(Seq *pidlist, Seq *rootpidlist)
1189 {
1190
1191 char *names[CF_PROCCOLS];
1192 int start[CF_PROCCOLS];
1193 int end[CF_PROCCOLS];
1194
1195 const char *pscmd = "/usr/bin/ps -Aleo zone,user,pid";
1196
1197 FILE *psf = cf_popen(pscmd, "r", false);
1198 if (psf == NULL)
1199 {
1200 Log(LOG_LEVEL_ERR, "ZLoadProcesstable: Couldn't open the process list with command %s.", pscmd);
1201 return false;
1202 }
1203
1204 size_t pbuff_size = CF_BUFSIZE;
1205 char *pbuff = xmalloc(pbuff_size);
1206 bool header = true;
1207
1208 while (true)
1209 {
1210 ssize_t res = CfReadLine(&pbuff, &pbuff_size, psf);
1211 if (res == -1)
1212 {
1213 if (!feof(psf))
1214 {
1215 Log(LOG_LEVEL_ERR, "IsGlobalProcess(char **, int): Unable to read process list with command '%s'. (fread: %s)", pscmd, GetErrorStr());
1216 cf_pclose(psf);
1217 free(pbuff);
1218 return false;
1219 }
1220 else
1221 {
1222 break;
1223 }
1224 }
1225 Chop(pbuff, pbuff_size);
1226 if (header) /* This line is the header. */
1227 {
1228 GetProcessColumnNames(pbuff, &names[0], start, end);
1229 }
1230 else
1231 {
1232 int pid = ExtractPid(pbuff, &names[0], end);
1233
1234 size_t zone_offset = strspn(pbuff, " ");
1235 size_t zone_end_offset = strcspn(pbuff + zone_offset, " ") + zone_offset;
1236 size_t user_offset = strspn(pbuff + zone_end_offset, " ") + zone_end_offset;
1237 size_t user_end_offset = strcspn(pbuff + user_offset, " ") + user_offset;
1238 bool is_global = (zone_end_offset - zone_offset == 6
1239 && strncmp(pbuff + zone_offset, "global", 6) == 0);
1240 bool is_root = (user_end_offset - user_offset == 4
1241 && strncmp(pbuff + user_offset, "root", 4) == 0);
1242
1243 if (is_global && is_root)
1244 {
1245 SeqAppend(rootpidlist, (void*)(intptr_t)pid);
1246 }
1247 else if (is_global && !is_root)
1248 {
1249 SeqAppend(pidlist, (void*)(intptr_t)pid);
1250 }
1251 }
1252
1253 header = false;
1254 }
1255 cf_pclose(psf);
1256 free(pbuff);
1257 return true;
1258 }
PidInSeq(Seq * list,int pid)1259 bool PidInSeq(Seq *list, int pid)
1260 {
1261 void *res = SeqLookup(list, (void *)(intptr_t)pid, PidListCompare);
1262 int result = (intptr_t)(void*)res;
1263
1264 if (result == pid)
1265 {
1266 return true;
1267 }
1268 return false;
1269 }
1270 /* return true if the process with
1271 * pid is in the global zone */
IsGlobalProcess(int pid,Seq * pidlist,Seq * rootpidlist)1272 int IsGlobalProcess(int pid, Seq *pidlist, Seq *rootpidlist)
1273 {
1274 if (PidInSeq(pidlist, pid) || PidInSeq(rootpidlist, pid))
1275 {
1276 return true;
1277 }
1278 else
1279 {
1280 return false;
1281 }
1282 }
ZCopyProcessList(Item ** dest,const Item * source,Seq * pidlist,char ** names,int * end)1283 void ZCopyProcessList(Item **dest, const Item *source, Seq *pidlist, char **names, int *end)
1284 {
1285 int gpid = ExtractPid(source->name, names, end);
1286
1287 if (PidInSeq(pidlist, gpid))
1288 {
1289 PrependItem(dest, source->name, "");
1290 }
1291 }
1292 # endif /* HAVE_GETZONEID */
1293
CheckPsLineLimitations(void)1294 static void CheckPsLineLimitations(void)
1295 {
1296 #ifdef __hpux
1297 FILE *ps_fd;
1298 int ret;
1299 char limit[21];
1300 char *buf = NULL;
1301 size_t bufsize = 0;
1302
1303 ps_fd = safe_fopen("/etc/default/ps", "r");
1304 if (!ps_fd)
1305 {
1306 Log(LOG_LEVEL_VERBOSE, "Could not open '/etc/default/ps' "
1307 "to check ps line length limitations.");
1308 return;
1309 }
1310
1311 while (true)
1312 {
1313 ret = CfReadLine(&buf, &bufsize, ps_fd);
1314 if (ret < 0)
1315 {
1316 break;
1317 }
1318
1319 ret = sscanf(buf, "DEFAULT_CMD_LINE_WIDTH = %20[0-9]", limit);
1320
1321 if (ret == 1)
1322 {
1323 if (atoi(limit) < 1024)
1324 {
1325 Log(LOG_LEVEL_VERBOSE, "ps line length limit is less than 1024. "
1326 "Consider adjusting the DEFAULT_CMD_LINE_WIDTH setting in /etc/default/ps "
1327 "in order to guarantee correct process matching.");
1328 }
1329 break;
1330 }
1331 }
1332
1333 free(buf);
1334 fclose(ps_fd);
1335 #endif // __hpux
1336 }
1337 #endif // _WIN32
1338
GetProcessTableLegend(void)1339 const char *GetProcessTableLegend(void)
1340 {
1341 if (PROCESSTABLE)
1342 {
1343 // First entry in the table is legend.
1344 return PROCESSTABLE->name;
1345 }
1346 else
1347 {
1348 return "<Process table not loaded>";
1349 }
1350 }
1351
1352 #if defined(__sun) || defined(TEST_UNIT_TEST)
OpenUcbPsPipe(void)1353 static FILE *OpenUcbPsPipe(void)
1354 {
1355 for (int i = 0; UCB_STYLE_PS[i]; i++)
1356 {
1357 struct stat statbuf;
1358 if (stat(UCB_STYLE_PS[i], &statbuf) < 0)
1359 {
1360 Log(LOG_LEVEL_VERBOSE,
1361 "%s not found, cannot be used for extra process information",
1362 UCB_STYLE_PS[i]);
1363 continue;
1364 }
1365 if (!(statbuf.st_mode & 0111))
1366 {
1367 Log(LOG_LEVEL_VERBOSE,
1368 "%s not executable, cannot be used for extra process information",
1369 UCB_STYLE_PS[i]);
1370 continue;
1371 }
1372
1373 char *ps_cmd;
1374 xasprintf(&ps_cmd, "%s %s", UCB_STYLE_PS[i],
1375 UCB_STYLE_PS_ARGS);
1376
1377 FILE *cmd = cf_popen(ps_cmd, "rt", false);
1378 if (!cmd)
1379 {
1380 Log(LOG_LEVEL_WARNING, "Could not execute \"%s\", extra process "
1381 "information not available. "
1382 "Process command line length may be limited to 80 characters.",
1383 ps_cmd);
1384 }
1385
1386 free(ps_cmd);
1387
1388 return cmd;
1389 }
1390
1391 Log(LOG_LEVEL_VERBOSE, "No eligible tool for extra process information "
1392 "found. Skipping.");
1393
1394 return NULL;
1395 }
1396
ReadFromUcbPsPipe(FILE * cmd)1397 static void ReadFromUcbPsPipe(FILE *cmd)
1398 {
1399 char *names[CF_PROCCOLS];
1400 memset(names, 0, sizeof(names));
1401 int start[CF_PROCCOLS];
1402 int end[CF_PROCCOLS];
1403 char *line = NULL;
1404 size_t linesize = 0;
1405 bool header = true;
1406 time_t pstime = time(NULL);
1407 int pidcol = -1;
1408 int cmdcol = -1;
1409 while (CfReadLine(&line, &linesize, cmd) > 0)
1410 {
1411 if (header)
1412 {
1413 GetProcessColumnNames(line, names, start, end);
1414
1415 for (int i = 0; names[i]; i++)
1416 {
1417 if (strcmp(names[i], "PID") == 0)
1418 {
1419 pidcol = i;
1420 }
1421 else if (strcmp(names[i], "COMMAND") == 0
1422 || strcmp(names[i], "CMD") == 0)
1423 {
1424 cmdcol = i;
1425 }
1426 }
1427
1428 if (pidcol < 0 || cmdcol < 0)
1429 {
1430 Log(LOG_LEVEL_ERR,
1431 "Could not find PID and/or CMD/COMMAND column in "
1432 "ps output: \"%s\"", line);
1433 break;
1434 }
1435
1436 header = false;
1437 continue;
1438 }
1439
1440
1441 char *columns[CF_PROCCOLS];
1442 memset(columns, 0, sizeof(columns));
1443 if (!SplitProcLine(line, pstime, names, start, end,
1444 UCB_STYLE_PS_COLUMN_ALGORITHM, columns))
1445 {
1446 Log(LOG_LEVEL_WARNING,
1447 "Not able to parse ps output: \"%s\"", line);
1448 }
1449
1450 StringMapInsert(UCB_PS_MAP, columns[pidcol], columns[cmdcol]);
1451 // We avoid strdup'ing these strings by claiming ownership here.
1452 columns[pidcol] = NULL;
1453 columns[cmdcol] = NULL;
1454
1455 for (int i = 0; i < CF_PROCCOLS; i++)
1456 {
1457 // There may be some null entries here, but since we "steal"
1458 // strings in the section above, we may have set some of them to
1459 // NULL and there may be following non-NULL fields.
1460 free(columns[i]);
1461 }
1462 }
1463
1464 if (!feof(cmd) && ferror(cmd))
1465 {
1466 Log(LOG_LEVEL_ERR, "Error while reading output from ps: %s",
1467 GetErrorStr());
1468 }
1469
1470 for (int i = 0; names[i] && i < CF_PROCCOLS; i++)
1471 {
1472 free(names[i]);
1473 }
1474
1475 free(line);
1476 }
1477
ClearPlatformExtraTable(void)1478 static void ClearPlatformExtraTable(void)
1479 {
1480 if (UCB_PS_MAP)
1481 {
1482 StringMapDestroy(UCB_PS_MAP);
1483 UCB_PS_MAP = NULL;
1484 }
1485 }
1486
LoadPlatformExtraTable(void)1487 static void LoadPlatformExtraTable(void)
1488 {
1489 if (UCB_PS_MAP)
1490 {
1491 return;
1492 }
1493
1494 UCB_PS_MAP = StringMapNew();
1495
1496 FILE *cmd = OpenUcbPsPipe();
1497 if (!cmd)
1498 {
1499 return;
1500 }
1501 ReadFromUcbPsPipe(cmd);
1502 if (cf_pclose(cmd) != 0)
1503 {
1504 Log(LOG_LEVEL_WARNING, "Command returned non-zero while gathering "
1505 "extra process information.");
1506 // Make an empty map, in this case. The information can't be trusted.
1507 StringMapClear(UCB_PS_MAP);
1508 }
1509 }
1510
ApplyPlatformExtraTable(char ** names,char ** columns)1511 static void ApplyPlatformExtraTable(char **names, char **columns)
1512 {
1513 int pidcol = -1;
1514
1515 for (int i = 0; names[i] && columns[i]; i++)
1516 {
1517 if (strcmp(names[i], "PID") == 0)
1518 {
1519 pidcol = i;
1520 break;
1521 }
1522 }
1523
1524 if (pidcol == -1 || !StringMapHasKey(UCB_PS_MAP, columns[pidcol]))
1525 {
1526 return;
1527 }
1528
1529 for (int i = 0; names[i] && columns[i]; i++)
1530 {
1531 if (strcmp(names[i], "COMMAND") == 0 || strcmp(names[i], "CMD") == 0)
1532 {
1533 free(columns[i]);
1534 columns[i] = xstrdup(StringMapGet(UCB_PS_MAP, columns[pidcol]));
1535 break;
1536 }
1537 }
1538 }
1539
1540 #else
LoadPlatformExtraTable(void)1541 static inline void LoadPlatformExtraTable(void)
1542 {
1543 }
1544
ClearPlatformExtraTable(void)1545 static inline void ClearPlatformExtraTable(void)
1546 {
1547 }
1548
ApplyPlatformExtraTable(ARG_UNUSED char ** names,ARG_UNUSED char ** columns)1549 static inline void ApplyPlatformExtraTable(ARG_UNUSED char **names, ARG_UNUSED char **columns)
1550 {
1551 }
1552 #endif
1553
1554 #ifndef _WIN32
LoadProcessTable()1555 bool LoadProcessTable()
1556 {
1557 FILE *prp;
1558 char pscomm[CF_MAXLINKSIZE];
1559 Item *rootprocs = NULL;
1560 Item *otherprocs = NULL;
1561
1562
1563 if (PROCESSTABLE)
1564 {
1565 Log(LOG_LEVEL_VERBOSE, "Reusing cached process table");
1566 return true;
1567 }
1568
1569 LoadPlatformExtraTable();
1570
1571 CheckPsLineLimitations();
1572
1573 const char *psopts = GetProcessOptions();
1574
1575 snprintf(pscomm, CF_MAXLINKSIZE, "%s %s", VPSCOMM[VPSHARDCLASS], psopts);
1576
1577 Log(LOG_LEVEL_VERBOSE, "Observe process table with %s", pscomm);
1578
1579 if ((prp = cf_popen(pscomm, "r", false)) == NULL)
1580 {
1581 Log(LOG_LEVEL_ERR, "Couldn't open the process list with command '%s'. (popen: %s)", pscomm, GetErrorStr());
1582 return false;
1583 }
1584
1585 size_t vbuff_size = CF_BUFSIZE;
1586 char *vbuff = xmalloc(vbuff_size);
1587
1588 # ifdef HAVE_GETZONEID
1589
1590 char *names[CF_PROCCOLS];
1591 int start[CF_PROCCOLS];
1592 int end[CF_PROCCOLS];
1593 Seq *pidlist = SeqNew(1, NULL);
1594 Seq *rootpidlist = SeqNew(1, NULL);
1595 bool global_zone = IsGlobalZone();
1596
1597 if (global_zone)
1598 {
1599 int res = ZLoadProcesstable(pidlist, rootpidlist);
1600
1601 if (res == false)
1602 {
1603 Log(LOG_LEVEL_ERR, "Unable to load solaris zone process table.");
1604 return false;
1605 }
1606 }
1607
1608 # endif
1609
1610 ARG_UNUSED bool header = true; /* used only if HAVE_GETZONEID */
1611
1612 for (;;)
1613 {
1614 ssize_t res = CfReadLine(&vbuff, &vbuff_size, prp);
1615 if (res == -1)
1616 {
1617 if (!feof(prp))
1618 {
1619 Log(LOG_LEVEL_ERR, "Unable to read process list with command '%s'. (fread: %s)", pscomm, GetErrorStr());
1620 cf_pclose(prp);
1621 free(vbuff);
1622 return false;
1623 }
1624 else
1625 {
1626 break;
1627 }
1628 }
1629 Chop(vbuff, vbuff_size);
1630
1631 # ifdef HAVE_GETZONEID
1632
1633 if (global_zone)
1634 {
1635 if (header)
1636 { /* this is the banner so get the column header names for later use*/
1637 GetProcessColumnNames(vbuff, &names[0], start, end);
1638 }
1639 else
1640 {
1641 int gpid = ExtractPid(vbuff, names, end);
1642
1643 if (!IsGlobalProcess(gpid, pidlist, rootpidlist))
1644 {
1645 continue;
1646 }
1647 }
1648 }
1649
1650 # endif
1651 AppendItem(&PROCESSTABLE, vbuff, "");
1652
1653 header = false;
1654 }
1655
1656 cf_pclose(prp);
1657
1658 /* Now save the data */
1659 const char* const statedir = GetStateDir();
1660
1661 snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_procs", statedir, FILE_SEPARATOR);
1662
1663 RawSaveItemList(PROCESSTABLE, vbuff, NewLineMode_Unix);
1664
1665 # ifdef HAVE_GETZONEID
1666 if (global_zone) /* pidlist and rootpidlist are empty if we're not in the global zone */
1667 {
1668 Item *ip = PROCESSTABLE;
1669 while (ip != NULL)
1670 {
1671 ZCopyProcessList(&rootprocs, ip, rootpidlist, names, end);
1672 ip = ip->next;
1673 }
1674 ReverseItemList(rootprocs);
1675 ip = PROCESSTABLE;
1676 while (ip != NULL)
1677 {
1678 ZCopyProcessList(&otherprocs, ip, pidlist, names, end);
1679 ip = ip->next;
1680 }
1681 ReverseItemList(otherprocs);
1682 }
1683 else
1684 # endif
1685 {
1686 CopyList(&rootprocs, PROCESSTABLE);
1687 CopyList(&otherprocs, PROCESSTABLE);
1688
1689 while (DeleteItemNotContaining(&rootprocs, "root"))
1690 {
1691 }
1692
1693 while (DeleteItemContaining(&otherprocs, "root"))
1694 {
1695 }
1696 }
1697 if (otherprocs)
1698 {
1699 PrependItem(&rootprocs, otherprocs->name, NULL);
1700 }
1701
1702 // TODO: Change safe_fopen() to default to 0600, then remove this.
1703 const mode_t old_umask = SetUmask(0077);
1704
1705 snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_rootprocs", statedir, FILE_SEPARATOR);
1706 RawSaveItemList(rootprocs, vbuff, NewLineMode_Unix);
1707 DeleteItemList(rootprocs);
1708
1709 snprintf(vbuff, CF_MAXVARSIZE, "%s%ccf_otherprocs", statedir, FILE_SEPARATOR);
1710 RawSaveItemList(otherprocs, vbuff, NewLineMode_Unix);
1711 DeleteItemList(otherprocs);
1712
1713 RestoreUmask(old_umask);
1714
1715 free(vbuff);
1716 return true;
1717 }
1718 # endif
1719
ClearProcessTable(void)1720 void ClearProcessTable(void)
1721 {
1722 ClearPlatformExtraTable();
1723
1724 DeleteItemList(PROCESSTABLE);
1725 PROCESSTABLE = NULL;
1726 }
1727