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 <verify_processes.h>
26
27 #include <actuator.h>
28 #include <processes_select.h>
29 #include <eval_context.h>
30 #include <promises.h>
31 #include <class.h>
32 #include <vars.h>
33 #include <class.h>
34 #include <item_lib.h>
35 #include <conversion.h>
36 #include <matching.h>
37 #include <attributes.h>
38 #include <locks.h>
39 #include <exec_tools.h>
40 #include <rlist.h>
41 #include <policy.h>
42 #include <scope.h>
43 #include <ornaments.h>
44
45 static PromiseResult VerifyProcesses(EvalContext *ctx, const Attributes *a, const Promise *pp);
46 static bool ProcessSanityChecks(const Attributes *a, const Promise *pp);
47 static PromiseResult VerifyProcessOp(EvalContext *ctx, const Attributes *a, const Promise *pp);
48 static int FindPidMatches(Item **killlist, const Attributes *a, const char *promiser);
49
VerifyProcessesPromise(EvalContext * ctx,const Promise * pp)50 PromiseResult VerifyProcessesPromise(EvalContext *ctx, const Promise *pp)
51 {
52 Attributes a = GetProcessAttributes(ctx, pp);
53 ProcessSanityChecks(&a, pp);
54
55 return VerifyProcesses(ctx, &a, pp);
56 }
57
58 /*****************************************************************************/
59 /* Level */
60 /*****************************************************************************/
61
ProcessSanityChecks(const Attributes * a,const Promise * pp)62 static bool ProcessSanityChecks(const Attributes *a, const Promise *pp)
63 {
64 assert(a != NULL);
65 assert(pp != NULL);
66 // Note that an undefined match_value results in process_count.min_range and max_range being set to CF_NOINT
67 bool promised_zero = ((a->process_count.min_range == 0) && (a->process_count.max_range == 0));
68 bool ret = true;
69
70 if (a->restart_class)
71 {
72 if ((RlistKeyIn(a->signals, "term")) || (RlistKeyIn(a->signals, "kill")))
73 {
74 Log(LOG_LEVEL_WARNING, "Promise '%s' kills then restarts - never strictly converges",
75 pp->promiser);
76 PromiseRef(LOG_LEVEL_INFO, pp);
77 }
78
79 if (a->haveprocess_count)
80 {
81 Log(LOG_LEVEL_VERBOSE,
82 "Note both process_count and restart_class define classes. Please exercises caution that there is not a logic error. Review process_count match_range, in_range_define, and out_of_range_define with respect to restart_class.");
83 PromiseRef(LOG_LEVEL_VERBOSE, pp);
84 ret = false;
85 }
86 }
87
88 if (promised_zero && (a->restart_class))
89 {
90 Log(LOG_LEVEL_ERR, "Promise constraint conflicts - '%s' processes cannot have zero count if restarted",
91 pp->promiser);
92 PromiseRef(LOG_LEVEL_ERR, pp);
93 ret = false;
94 }
95
96 if ((a->haveselect) && (!a->process_select.process_result))
97 {
98 Log(LOG_LEVEL_ERR, "Process select constraint body promised no result (check body definition)");
99 PromiseRef(LOG_LEVEL_ERR, pp);
100 ret = false;
101 }
102
103 // CF_NOINT is the value assigned when match_range is not specified
104 if ((a->haveprocess_count) && ((a->process_count.min_range == CF_NOINT) || (a->process_count.max_range == CF_NOINT)))
105 {
106 Log(LOG_LEVEL_ERR, "process_count used without match_range, this promise will not define a class based on a count of matching promises.");
107 PromiseRef(LOG_LEVEL_ERR, pp);
108 ret = false;
109 }
110
111 if ((a->haveprocess_count) && !((a->process_count.in_range_define) || (a->process_count.out_of_range_define)))
112 {
113 Log(LOG_LEVEL_ERR, "process_count body is insufficiently defined. process_count must specify at least one of in_range_define or out_of_range_define. ");
114 PromiseRef(LOG_LEVEL_ERR, pp);
115 ret = false;
116 }
117
118
119 return ret;
120 }
121
122 /*****************************************************************************/
123
VerifyProcesses(EvalContext * ctx,const Attributes * a,const Promise * pp)124 static PromiseResult VerifyProcesses(EvalContext *ctx, const Attributes *a, const Promise *pp)
125 {
126 CfLock thislock;
127 char lockname[CF_BUFSIZE];
128
129 if (a->restart_class)
130 {
131 snprintf(lockname, CF_BUFSIZE - 1, "proc-%s-%s", pp->promiser, a->restart_class);
132 }
133 else
134 {
135 snprintf(lockname, CF_BUFSIZE - 1, "proc-%s-norestart", pp->promiser);
136 }
137
138 thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME,
139 a->transaction.ifelapsed, a->transaction.expireafter, pp, false);
140 if (thislock.lock == NULL)
141 {
142 return PROMISE_RESULT_SKIPPED;
143 }
144
145 PromiseBanner(ctx, pp);
146 PromiseResult result = VerifyProcessOp(ctx, a, pp);
147
148 YieldCurrentLock(thislock);
149
150 return result;
151 }
152
ProcessCountMaybeDefineClass(EvalContext * const ctx,const Rlist * const rp,const Promise * const pp,const char * const attribute_name)153 static void ProcessCountMaybeDefineClass(
154 EvalContext *const ctx,
155 const Rlist *const rp,
156 const Promise *const pp,
157 const char *const attribute_name)
158 {
159 assert(ctx != NULL);
160 assert(rp != NULL);
161 assert(pp != NULL);
162 assert(attribute_name != NULL && strlen(attribute_name) > 0);
163
164 if (rp->val.type == RVAL_TYPE_FNCALL)
165 {
166 Log(LOG_LEVEL_ERR,
167 "Body (process_count), for processes promise %s, has a failing function call (%s) in the %s attribute - cannot define the class",
168 pp->promiser,
169 RlistFnCallValue(rp)->name,
170 attribute_name);
171 return;
172 }
173
174 assert(rp->val.type == RVAL_TYPE_SCALAR);
175 ClassRef ref = ClassRefParse(RlistScalarValue(rp));
176 EvalContextClassPutSoft(
177 ctx, RlistScalarValue(rp), CONTEXT_SCOPE_NAMESPACE, "source=promise");
178 ClassRefDestroy(ref);
179 }
180
VerifyProcessOp(EvalContext * ctx,const Attributes * a,const Promise * pp)181 static PromiseResult VerifyProcessOp(EvalContext *ctx, const Attributes *a, const Promise *pp)
182 {
183 assert(a != NULL);
184 assert(pp != NULL);
185 bool do_signals = true;
186 int out_of_range;
187 int killed = 0;
188 bool need_to_restart = true;
189 Item *killlist = NULL;
190
191 int matches = FindPidMatches(&killlist, a, pp->promiser);
192
193 /* promise based on number of matches */
194
195 PromiseResult result = PROMISE_RESULT_NOOP;
196 if (a->process_count.min_range != CF_NOINT) /* if a range is specified */
197 {
198 if ((matches < a->process_count.min_range) || (matches > a->process_count.max_range))
199 {
200 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Process count for '%s' was out of promised range (%d found)", pp->promiser, matches);
201 result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
202 for (const Rlist *rp = a->process_count.out_of_range_define; rp != NULL; rp = rp->next)
203 {
204 ProcessCountMaybeDefineClass(ctx, rp, pp, "out_of_range_define");
205 }
206 out_of_range = true;
207 }
208 else
209 {
210 for (const Rlist *rp = a->process_count.in_range_define; rp != NULL; rp = rp->next)
211 {
212 ProcessCountMaybeDefineClass(ctx, rp, pp, "in_range_define");
213 }
214 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Process promise for '%s' is kept", pp->promiser);
215 out_of_range = false;
216 }
217 }
218 else
219 {
220 out_of_range = true;
221 }
222
223 if (!out_of_range)
224 {
225 DeleteItemList(killlist);
226 return result;
227 }
228
229 if (a->transaction.action == cfa_warn)
230 {
231 do_signals = false;
232 result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
233 }
234 else
235 {
236 do_signals = true;
237 }
238
239 /* signal/kill promises for existing matches */
240
241 if (do_signals && (matches > 0))
242 {
243 if (a->process_stop != NULL)
244 {
245 if (DONTDO)
246 {
247 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, a,
248 "Need to keep process-stop promise for '%s', but only a warning is promised", pp->promiser);
249 result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
250 }
251 else
252 {
253 if (IsExecutable(CommandArg0(a->process_stop)))
254 {
255 Log(LOG_LEVEL_DEBUG, "Found process_stop command '%s' is executable.", a->process_stop);
256
257 if (ShellCommandReturnsZero(a->process_stop, SHELL_TYPE_NONE))
258 {
259 cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a,
260 "Promise to stop '%s' repaired, '%s' returned zero",
261 pp->promiser, a->process_stop);
262 result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
263 }
264 else
265 {
266 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
267 "Promise to stop '%s' failed, '%s' returned nonzero",
268 pp->promiser, a->process_stop);
269 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
270 }
271 }
272 else
273 {
274 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
275 "Process promise to stop '%s' could not be kept because '%s' is not executable",
276 pp->promiser, a->process_stop);
277 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
278 DeleteItemList(killlist);
279 return result;
280 }
281 }
282 }
283
284 killed = DoAllSignals(ctx, killlist, a, pp, &result);
285 }
286
287 /* delegated promise to restart killed or non-existent entries */
288
289 need_to_restart = (a->restart_class != NULL) && (killed || (matches == 0));
290
291 DeleteItemList(killlist);
292
293 if (!need_to_restart)
294 {
295 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No restart promised for %s", pp->promiser);
296 return result;
297 }
298 else
299 {
300 if (a->transaction.action == cfa_warn)
301 {
302 cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a,
303 "Need to keep restart promise for '%s', but only a warning is promised", pp->promiser);
304 result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
305 }
306 else
307 {
308 PromiseResult status = killed ? PROMISE_RESULT_CHANGE : PROMISE_RESULT_NOOP;
309 cfPS(ctx, LOG_LEVEL_VERBOSE, status, pp, a,
310 "C: + Global class: %s ", a->restart_class);
311 result = PromiseResultUpdate(result, status);
312 EvalContextClassPutSoft(ctx, a->restart_class, CONTEXT_SCOPE_NAMESPACE, "source=promise");
313 }
314 }
315
316 return result;
317 }
318
319 #ifndef __MINGW32__
DoAllSignals(EvalContext * ctx,Item * siglist,const Attributes * a,const Promise * pp,PromiseResult * result)320 int DoAllSignals(EvalContext *ctx, Item *siglist, const Attributes *a, const Promise *pp, PromiseResult *result)
321 {
322 Item *ip;
323 Rlist *rp;
324 pid_t pid;
325 int killed = false;
326 bool failure = false;
327
328 if (siglist == NULL)
329 {
330 return 0;
331 }
332
333 if (a->signals == NULL)
334 {
335 Log(LOG_LEVEL_VERBOSE, "No signals to send for '%s'", pp->promiser);
336 return 0;
337 }
338
339 for (ip = siglist; ip != NULL; ip = ip->next)
340 {
341 pid = ip->counter;
342
343 for (rp = a->signals; rp != NULL; rp = rp->next)
344 {
345 int signal = SignalFromString(RlistScalarValue(rp));
346
347 if (!DONTDO)
348 {
349 if ((signal == SIGKILL) || (signal == SIGTERM))
350 {
351 killed = true;
352 }
353
354 if (kill(pid, signal) < 0)
355 {
356 cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a,
357 "Couldn't send promised signal '%s' (%d) to pid %jd (might be dead). (kill: %s)",
358 RlistScalarValue(rp), signal, (intmax_t)pid, GetErrorStr());
359 failure = true;
360 }
361 else
362 {
363 cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a,
364 "Signalled '%s' (%d) to process %jd (%s)",
365 RlistScalarValue(rp), signal, (intmax_t) pid, ip->name);
366 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
367 failure = false;
368 }
369 }
370 else
371 {
372 Log(LOG_LEVEL_ERR, "Need to keep signal promise '%s' in process entry '%s'",
373 RlistScalarValue(rp), ip->name);
374 }
375 }
376 }
377
378 if (failure)
379 {
380 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
381 }
382
383 return killed;
384 }
385 #endif
386
FindPidMatches(Item ** killlist,const Attributes * a,const char * promiser)387 static int FindPidMatches(Item **killlist, const Attributes *a, const char *promiser)
388 {
389 int matches = 0;
390 pid_t cfengine_pid = getpid();
391
392 Item *matched = SelectProcesses(promiser, &(a->process_select), a->haveselect);
393
394 for (Item *ip = matched; ip != NULL; ip = ip->next)
395 {
396 pid_t pid = ip->counter;
397
398 if (a->signals) /* There are some processes we don't want to signal. */
399 {
400 if (pid == 1)
401 {
402 if (RlistLen(a->signals) == 1 && RlistKeyIn(a->signals, "hup"))
403 {
404 Log(LOG_LEVEL_VERBOSE, "Okay to send only HUP to init");
405 }
406 else
407 {
408 continue;
409 }
410 }
411 else if (pid < 4)
412 {
413 Log(LOG_LEVEL_VERBOSE, "Will not signal or restart processes 0,1,2,3 (occurred while looking for %s)",
414 promiser);
415 continue;
416 }
417
418 if (pid == cfengine_pid)
419 {
420 Log(LOG_LEVEL_VERBOSE, "cf-agent will not signal itself!");
421 continue;
422 }
423 }
424
425 bool promised_zero = (a->process_count.min_range == 0) && (a->process_count.max_range == 0);
426
427 if ((a->transaction.action == cfa_warn) && promised_zero)
428 {
429 Log(LOG_LEVEL_WARNING, "Process alert '%s'", GetProcessTableLegend());
430 Log(LOG_LEVEL_WARNING, "Process alert '%s'", ip->name);
431 continue;
432 }
433
434 if (a->transaction.action == cfa_warn)
435 {
436 Log(LOG_LEVEL_WARNING, "Matched '%s'", ip->name);
437 }
438 else
439 {
440 Log(LOG_LEVEL_VERBOSE, "Matched '%s'", ip->name);
441 }
442
443 PrependItem(killlist, ip->name, "");
444 (*killlist)->counter = pid;
445 matches++;
446 }
447
448 DeleteItemList(matched);
449
450 return matches;
451 }
452