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