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