cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

files_editline.c
Go to the documentation of this file.
1 /*
2  Copyright 2019 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 <files_editline.h>
26 
27 #include <actuator.h>
28 #include <eval_context.h>
29 #include <promises.h>
30 #include <files_names.h>
31 #include <files_interfaces.h>
32 #include <vars.h>
33 #include <item_lib.h>
34 #include <sort.h>
35 #include <conversion.h>
36 #include <expand.h>
37 #include <scope.h>
38 #include <matching.h>
39 #include <match_scope.h>
40 #include <attributes.h>
41 #include <locks.h>
42 #include <string_lib.h>
43 #include <misc_lib.h>
44 #include <file_lib.h>
45 #include <rlist.h>
46 #include <policy.h>
47 #include <ornaments.h>
48 #include <verify_classes.h>
49 
50 #define CF_MAX_REPLACE 20
51 
52 /*****************************************************************************/
53 
55 {
63  elp_none
64 };
65 
66 static const char *const EDITLINETYPESEQUENCE[] =
67 {
68  "vars",
69  "classes",
70  "delete_lines",
71  "field_edits",
72  "insert_lines",
73  "replace_patterns",
74  "reports",
75  NULL
76 };
77 
78 static PromiseResult KeepEditLinePromise(EvalContext *ctx, const Promise *pp, void *param);
79 static PromiseResult VerifyLineDeletions(EvalContext *ctx, const Promise *pp, EditContext *edcontext);
80 static PromiseResult VerifyColumnEdits(EvalContext *ctx, const Promise *pp, EditContext *edcontext);
81 static PromiseResult VerifyPatterns(EvalContext *ctx, const Promise *pp, EditContext *edcontext);
82 static PromiseResult VerifyLineInsertions(EvalContext *ctx, const Promise *pp, EditContext *edcontext);
83 static bool InsertMultipleLinesToRegion(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
84 static bool InsertMultipleLinesAtLocation(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
85 static bool DeletePromisedLinesMatching(EvalContext *ctx, Item **start, Item *begin, Item *end, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
86 static bool InsertLineAtLocation(EvalContext *ctx, char *newline, Item **start, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
87 static bool InsertCompoundLineAtLocation(EvalContext *ctx, char *newline, Item **start, Item *begin_ptr, Item *end_ptr, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
88 static int ReplacePatterns(EvalContext *ctx, Item *start, Item *end, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
89 static bool EditColumns(EvalContext *ctx, Item *file_start, Item *file_end, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
90 static bool EditLineByColumn(EvalContext *ctx, Rlist **columns, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
91 static bool DoEditColumn(Rlist **columns, const Attributes *a, EditContext *edcontext);
92 static bool SanityCheckInsertions(const Attributes *a);
93 static bool SanityCheckDeletions(const Attributes *a, const Promise *pp);
94 static bool SelectLine(EvalContext *ctx, const char *line, const Attributes *a);
95 static bool NotAnchored(char *s);
96 static bool SelectRegion(EvalContext *ctx, Item *start, Item **begin_ptr, Item **end_ptr, const Attributes *a, EditContext *edcontext);
97 static bool MultiLineString(char *s);
98 static bool InsertFileAtLocation(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result);
99 
100 /*****************************************************************************/
101 /* Level */
102 /*****************************************************************************/
103 
104 bool ScheduleEditLineOperations(EvalContext *ctx, const Bundle *bp, const Attributes *a, const Promise *parentp, EditContext *edcontext)
105 {
106  enum editlinetypesequence type;
107  char lockname[CF_BUFSIZE];
108  CfLock thislock;
109  int pass;
110 
111  assert(strcmp(bp->type, "edit_line") == 0);
112 
113  snprintf(lockname, CF_BUFSIZE - 1, "masterfilelock-%s", edcontext->filename);
114  thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a->transaction.ifelapsed, a->transaction.expireafter, parentp, true);
115 
116  if (thislock.lock == NULL)
117  {
118  return false;
119  }
120 
121  EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_EDIT, "filename", edcontext->filename, CF_DATA_TYPE_STRING, "source=promise");
122 
123  for (pass = 1; pass < CF_DONEPASSES; pass++)
124  {
125  for (type = 0; EDITLINETYPESEQUENCE[type] != NULL; type++)
126  {
128  if (!sp)
129  {
130  continue;
131  }
132 
134  for (size_t ppi = 0; ppi < SeqLength(sp->promises); ppi++)
135  {
136  Promise *pp = SeqAt(sp->promises, ppi);
137 
138  ExpandPromise(ctx, pp, KeepEditLinePromise, edcontext);
139 
140  if (BundleAbort(ctx))
141  {
142  YieldCurrentLock(thislock);
144  return false;
145  }
146  }
148  }
149  }
150 
151  YieldCurrentLock(thislock);
152  return true;
153 }
154 
155 /*****************************************************************************/
156 
158 {
159  FILE *fp = NULL;
160  if ((fp = safe_fopen(a->edit_template, "rt" )) == NULL)
161  {
162  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a, "Unable to open template file '%s' to make '%s'", a->edit_template, pp->promiser);
164  return NULL;
165  }
166 
167  Bundle *bp = NULL;
168  {
169  char bundlename[CF_MAXVARSIZE];
170  snprintf(bundlename, CF_MAXVARSIZE, "temp_cf_bundle_%s", CanonifyName(a->edit_template));
171 
172  bp = PolicyAppendBundle(policy, "default", bundlename, "edit_line", NULL, NULL);
173  }
174  assert(bp);
175 
176  {
177  PromiseType *tp = BundleAppendPromiseType(bp, "insert_lines");
178  Promise *np = NULL;
179  Item *lines = NULL;
180  Item *stack = NULL;
181  char context[CF_BUFSIZE] = "any";
182  int lineno = 0;
183  size_t level = 0;
184 
185  size_t buffer_size = CF_BUFSIZE;
186  char *buffer = xmalloc(buffer_size);
187 
188  for (;;)
189  {
190  if (getline(&buffer, &buffer_size, fp) == -1)
191  {
192  if (!feof(fp))
193  {
194  Log(LOG_LEVEL_ERR, "While constructing template for '%s', error reading. (getline %s)",
195  pp->promiser, GetErrorStr());
196  break;
197  }
198  else /* feof */
199  {
200  break;
201  }
202  }
203 
204  lineno++;
205 
206  // Check closing syntax
207 
208  // Get Action operator
209  if (strncmp(buffer, "[%CFEngine", strlen("[%CFEngine")) == 0)
210  {
211  char op[CF_BUFSIZE] = "";
212  char brack[4] = "";
213 
214  sscanf(buffer+strlen("[%CFEngine"), "%1024s %3s", op, brack);
215 
216  if (strcmp(brack, "%]") != 0)
217  {
218  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a, "Template file '%s' syntax error, missing close \"%%]\" at line %d", a->edit_template, lineno);
220  return NULL;
221  }
222 
223  if (strcmp(op, "BEGIN") == 0)
224  {
225  PrependItem(&stack, context, NULL);
226  if (++level > 1)
227  {
228  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a, "Template file '%s' contains nested blocks which are not allowed, near line %d", a->edit_template, lineno);
230  return NULL;
231  }
232 
233  continue;
234  }
235 
236  if (strcmp(op, "END") == 0)
237  {
238  level--;
239  if (stack != NULL)
240  {
241  strcpy(context, stack->name);
242  DeleteItem(&stack, stack);
243  }
244  }
245 
246  if (strcmp(op + strlen(op)-2, "::") == 0)
247  {
248  *(op + strlen(op)-2) = '\0';
249  strcpy(context, op);
250  continue;
251  }
252 
253  size_t size = 0;
254  for (const Item *ip = lines; ip != NULL; ip = ip->next)
255  {
256  size += strlen(ip->name);
257  }
258 
259  char *promiser = NULL;
260  char *sp = promiser = xcalloc(1, size+1);
261 
262  for (const Item *ip = lines; ip != NULL; ip = ip->next)
263  {
264  const int len = strlen(ip->name);
265  memcpy(sp, ip->name, len);
266  sp += len;
267  }
268 
269  int nl = StripTrailingNewline(promiser, size);
270  CF_ASSERT(nl != -1, "StripTrailingNewline failure");
271 
272  np = PromiseTypeAppendPromise(tp, promiser, (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, context, NULL);
273  np->offset.line = lineno;
274  PromiseAppendConstraint(np, "insert_type", RvalNew("preserve_all_lines", RVAL_TYPE_SCALAR), false);
275 
276  DeleteItemList(lines);
277  free(promiser);
278  lines = NULL;
279  }
280  else
281  {
282  if (IsDefinedClass(ctx, context))
283  {
284  if (level > 0)
285  {
286  AppendItem(&lines, buffer, context);
287  }
288  else
289  {
290  //install independent promise line
291  StripTrailingNewline(buffer, buffer_size);
292  np = PromiseTypeAppendPromise(tp, buffer, (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, context, NULL);
293  np->offset.line = lineno;
294  PromiseAppendConstraint(np, "insert_type", RvalNew("preserve_all_lines", RVAL_TYPE_SCALAR), false);
295  }
296  }
297  }
298  }
299 
300  free(buffer);
301  }
302 
303  fclose(fp);
304 
305  return bp;
306 }
307 
308 /***************************************************************************/
309 /* Level */
310 /***************************************************************************/
311 
312 static PromiseResult KeepEditLinePromise(EvalContext *ctx, const Promise *pp, void *param)
313 {
314  EditContext *edcontext = param;
315 
316  PromiseBanner(ctx, pp);
317 
318  if (strcmp("classes", pp->parent_promise_type->name) == 0)
319  {
320  return VerifyClassPromise(ctx, pp, NULL);
321  }
322  else if (strcmp("delete_lines", pp->parent_promise_type->name) == 0)
323  {
324  return VerifyLineDeletions(ctx, pp, edcontext);
325  }
326  else if (strcmp("field_edits", pp->parent_promise_type->name) == 0)
327  {
328  return VerifyColumnEdits(ctx, pp, edcontext);
329  }
330  else if (strcmp("insert_lines", pp->parent_promise_type->name) == 0)
331  {
332  return VerifyLineInsertions(ctx, pp, edcontext);
333  }
334  else if (strcmp("replace_patterns", pp->parent_promise_type->name) == 0)
335  {
336  return VerifyPatterns(ctx, pp, edcontext);
337  }
338  else if (strcmp("reports", pp->parent_promise_type->name) == 0)
339  {
340  return VerifyReportPromise(ctx, pp);
341  }
342 
343  return PROMISE_RESULT_NOOP;
344 }
345 
346 /***************************************************************************/
347 /* Level */
348 /***************************************************************************/
349 
351 {
352  Item **start = &(edcontext->file_start);
353  Item *begin_ptr, *end_ptr;
354  CfLock thislock;
355  char lockname[CF_BUFSIZE];
356 
357  Attributes a = GetDeletionAttributes(ctx, pp);
359 
360  if (!SanityCheckDeletions(&a, pp))
361  {
362  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a, "The promised line deletion '%s' is inconsistent", pp->promiser);
364  }
365 
366 /* Are we working in a restricted region? */
367 
369  if (!a.haveregion)
370  {
371  begin_ptr = NULL;
372  end_ptr = NULL;
373  }
374  else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
375  {
377  {
379  "The promised line deletion '%s' could not select an edit region in '%s' (this is a good thing, as policy suggests deleting the markers)",
380  pp->promiser, edcontext->filename);
381  }
382  else
383  {
385  "The promised line deletion '%s' could not select an edit region in '%s' (but the delimiters were expected in the file)",
386  pp->promiser, edcontext->filename);
387  }
389  return result;
390  }
391  if (!end_ptr && a.region.select_end && !a.region.select_end_match_eof)
392  {
394  "The promised end pattern '%s' was not found when selecting region to delete in '%s'",
395  a.region.select_end, edcontext->filename);
397  return result;
398  }
399 
400  snprintf(lockname, CF_BUFSIZE - 1, "deleteline-%s-%s", pp->promiser, edcontext->filename);
401  thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction.ifelapsed, a.transaction.expireafter, pp, true);
402 
403  if (thislock.lock == NULL)
404  {
405  return PROMISE_RESULT_SKIPPED;
406  }
407 
408  if (DeletePromisedLinesMatching(ctx, start, begin_ptr, end_ptr, &a, pp, edcontext, &result))
409  {
410  (edcontext->num_edits)++;
411  }
412 
413  YieldCurrentLock(thislock);
414 
415  return result;
416 }
417 
418 /***************************************************************************/
419 
420 static PromiseResult VerifyColumnEdits(EvalContext *ctx, const Promise *pp, EditContext *edcontext)
421 {
422  Item **start = &(edcontext->file_start);
423  Item *begin_ptr, *end_ptr;
424  CfLock thislock;
425  char lockname[CF_BUFSIZE];
426 
427  Attributes a = GetColumnAttributes(ctx, pp);
429 
430  if (a.column.column_separator == NULL)
431  {
432  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, &a, "No field_separator in promise to edit by column for '%s'", pp->promiser);
434  return PROMISE_RESULT_WARN;
435  }
436 
437  if (a.column.select_column <= 0)
438  {
439  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, &a, "No select_field in promise to edit '%s'", pp->promiser);
441  return PROMISE_RESULT_WARN;
442  }
443 
444  if (!a.column.column_value)
445  {
446  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, &a, "No field_value is promised to column_edit '%s'", pp->promiser);
448  return PROMISE_RESULT_WARN;
449  }
450 
451 /* Are we working in a restricted region? */
452 
454  if (!a.haveregion)
455  {
456  begin_ptr = *start;
457  end_ptr = NULL; // EndOfList(*start);
458  }
459  else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
460  {
461  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a, "The promised column edit '%s' could not select an edit region in '%s'",
462  pp->promiser, edcontext->filename);
464  return result;
465  }
466 
467 /* locate and split line */
468 
469  snprintf(lockname, CF_BUFSIZE - 1, "column-%s-%s", pp->promiser, edcontext->filename);
470  thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction.ifelapsed, a.transaction.expireafter, pp, true);
471  if (thislock.lock == NULL)
472  {
473  return PROMISE_RESULT_SKIPPED;
474  }
475 
476  if (EditColumns(ctx, begin_ptr, end_ptr, &a, pp, edcontext, &result))
477  {
478  (edcontext->num_edits)++;
479  }
480 
481  YieldCurrentLock(thislock);
482 
483  return result;
484 }
485 
486 /***************************************************************************/
487 
488 static PromiseResult VerifyPatterns(EvalContext *ctx, const Promise *pp, EditContext *edcontext)
489 {
490  Item **start = &(edcontext->file_start);
491  Item *begin_ptr, *end_ptr;
492  CfLock thislock;
493  char lockname[CF_BUFSIZE];
494 
495  Log(LOG_LEVEL_VERBOSE, "Looking at pattern '%s'", pp->promiser);
496 
497 /* Are we working in a restricted region? */
498 
499  Attributes a = GetReplaceAttributes(ctx, pp);
501 
502  if (!a.replace.replace_value)
503  {
504  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a, "The promised pattern replace '%s' had no replacement string",
505  pp->promiser);
507  }
508 
510  if (!a.haveregion)
511  {
512  begin_ptr = *start;
513  end_ptr = NULL; //EndOfList(*start);
514  }
515  else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
516  {
518  "The promised pattern replace '%s' could not select an edit region in '%s'", pp->promiser,
519  edcontext->filename);
521  return result;
522  }
523 
524  snprintf(lockname, CF_BUFSIZE - 1, "replace-%s-%s", pp->promiser, edcontext->filename);
525  thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction.ifelapsed, a.transaction.expireafter, pp, true);
526 
527  if (thislock.lock == NULL)
528  {
529  return PROMISE_RESULT_SKIPPED;
530  }
531 
532 /* Make sure back references are expanded */
533 
534  if (ReplacePatterns(ctx, begin_ptr, end_ptr, &a, pp, edcontext, &result))
535  {
536  (edcontext->num_edits)++;
537  }
538 
540 
541  YieldCurrentLock(thislock);
542 
543  return result;
544 }
545 
546 /***************************************************************************/
547 
548 static bool SelectNextItemMatching(EvalContext *ctx, const char *regexp, Item *begin, Item *end, Item **match, Item **prev)
549 {
550  Item *ip_prev = NULL;
551 
552  *match = NULL;
553  *prev = NULL;
554 
555  for (Item *ip = begin; ip != end; ip = ip->next)
556  {
557  if (ip->name == NULL)
558  {
559  continue;
560  }
561 
562  if (FullTextMatch(ctx, regexp, ip->name))
563  {
564  *match = ip;
565  *prev = ip_prev;
566  return true;
567  }
568 
569  ip_prev = ip;
570  }
571 
572  return false;
573 }
574 
575 /***************************************************************************/
576 
577 static bool SelectLastItemMatching(EvalContext *ctx, const char *regexp, Item *begin, Item *end, Item **match, Item **prev)
578 {
579  Item *ip, *ip_last = NULL, *ip_prev = NULL;
580 
581  *match = NULL;
582  *prev = NULL;
583 
584  for (ip = begin; ip != end; ip = ip->next)
585  {
586  if (ip->name == NULL)
587  {
588  continue;
589  }
590 
591  if (FullTextMatch(ctx, regexp, ip->name))
592  {
593  *prev = ip_prev;
594  ip_last = ip;
595  }
596 
597  ip_prev = ip;
598  }
599 
600  if (ip_last)
601  {
602  *match = ip_last;
603  return true;
604  }
605 
606  return false;
607 }
608 
609 /***************************************************************************/
610 
611 static bool SelectItemMatching(EvalContext *ctx, Item *start, char *regex, Item *begin_ptr, Item *end_ptr, Item **match, Item **prev, char *fl)
612 {
613  Item *ip;
614  bool ret = false;
615 
616  *match = NULL;
617  *prev = NULL;
618 
619  if (regex == NULL)
620  {
621  return false;
622  }
623 
624  if (fl && (strcmp(fl, "first") == 0))
625  {
626  if (SelectNextItemMatching(ctx, regex, begin_ptr, end_ptr, match, prev))
627  {
628  ret = true;
629  }
630  }
631  else
632  {
633  if (SelectLastItemMatching(ctx, regex, begin_ptr, end_ptr, match, prev))
634  {
635  ret = true;
636  }
637  }
638 
639  if ((*match != NULL) && (*prev == NULL))
640  {
641  for (ip = start; (ip != NULL) && (ip != *match); ip = ip->next)
642  {
643  *prev = ip;
644  }
645  }
646 
647  return ret;
648 }
649 
650 /***************************************************************************/
651 
653 {
654  Item **start = &(edcontext->file_start), *match, *prev;
655  Item *begin_ptr, *end_ptr;
656  CfLock thislock;
657  char lockname[CF_BUFSIZE];
658 
659  Attributes a = GetInsertionAttributes(ctx, pp);
660  int allow_multi_lines = a.sourcetype && strcmp(a.sourcetype, "preserve_all_lines") == 0;
662 
663  if (!SanityCheckInsertions(&a))
664  {
665  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a, "The promised line insertion '%s' breaks its own promises",
666  pp->promiser);
668  }
669 
670  /* Are we working in a restricted region? */
671 
673 
674  if (!a.haveregion)
675  {
676  begin_ptr = *start;
677  end_ptr = NULL; //EndOfList(*start);
678  }
679  else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
680  {
682  "The promised line insertion '%s' could not select an edit region in '%s'",
683  pp->promiser, edcontext->filename);
685  return result;
686  }
687 
688  if (!end_ptr && a.region.select_end && !a.region.select_end_match_eof)
689  {
691  "The promised end pattern '%s' was not found when selecting region to insert in '%s'",
692  a.region.select_end, edcontext->filename);
694  return result;
695  }
696 
697  if (allow_multi_lines)
698  {
699  // promise to insert duplicates on first pass only
700  snprintf(lockname, CF_BUFSIZE - 1, "insertline-%s-%s-%lu", pp->promiser, edcontext->filename, (long unsigned int) pp->offset.line);
701  }
702  else
703  {
704  snprintf(lockname, CF_BUFSIZE - 1, "insertline-%s-%s", pp->promiser, edcontext->filename);
705  }
706 
707  thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction.ifelapsed, a.transaction.expireafter, pp, true);
708  if (thislock.lock == NULL)
709  {
710  return PROMISE_RESULT_SKIPPED;
711  }
712 
713  /* Are we looking for an anchored line inside the region? */
714 
715  if (a.location.line_matching == NULL)
716  {
717  if (InsertMultipleLinesToRegion(ctx, start, begin_ptr, end_ptr, &a, pp, edcontext, &result))
718  {
719  (edcontext->num_edits)++;
720  }
721  }
722  else
723  {
724  if (!SelectItemMatching(ctx, *start, a.location.line_matching, begin_ptr, end_ptr, &match, &prev, a.location.first_last))
725  {
726  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a, "The promised line insertion '%s' could not select a locator matching regex '%s' in '%s'", pp->promiser, a.location.line_matching, edcontext->filename);
728  YieldCurrentLock(thislock);
729  return result;
730  }
731 
732  if (InsertMultipleLinesAtLocation(ctx, start, begin_ptr, end_ptr, match, prev, &a, pp, edcontext, &result))
733  {
734  (edcontext->num_edits)++;
735  }
736  }
737 
738  YieldCurrentLock(thislock);
739 
740  return result;
741 }
742 
743 /***************************************************************************/
744 /* Level */
745 /***************************************************************************/
746 
747 static bool SelectRegion(EvalContext *ctx, Item *start,
748  Item **begin_ptr, Item **end_ptr,
749  const Attributes *a, EditContext *edcontext)
750 /*
751 
752 This should provide pointers to the first and last line of text that include the
753 delimiters, since we need to include those in case they are being deleted, etc.
754 It returns true if a match was identified, else false.
755 
756 If no such region matches, begin_ptr and end_ptr should point to NULL
757 
758 */
759 {
760  const char *const select_start = a->region.select_start;
761  const char *const select_end = a->region.select_end;
762  const int include_start = a->region.include_start;
763 
764  Item *ip, *beg = NULL, *end = NULL;
765 
766  for (ip = start; ip != NULL; ip = ip->next)
767  {
768  if (select_start)
769  {
770  if (!beg && FullTextMatch(ctx, select_start, ip->name))
771  {
772  if (!include_start)
773  {
774  if (ip->next == NULL)
775  {
777  "The promised start pattern '%s' found an empty region at the end of file '%s'",
778  select_start, edcontext->filename);
779  return false;
780  }
781  }
782 
783  beg = ip;
784  continue;
785  }
786  }
787 
788  if (select_end && beg)
789  {
790  if (!end && FullTextMatch(ctx, select_end, ip->name))
791  {
792  end = ip;
793  break;
794  }
795  }
796 
797  if (beg && end)
798  {
799  break;
800  }
801  }
802 
803  if (!beg && select_start)
804  {
806  "The promised start pattern '%s' was not found when selecting edit region in '%s'",
807  select_start, edcontext->filename);
808  return false;
809  }
810 
811  *begin_ptr = beg;
812  *end_ptr = end;
813 
814  return true;
815 }
816 
817 /*****************************************************************************/
818 
819 static int MatchRegion(EvalContext *ctx, const char *chunk, const Item *begin, const Item *end, bool regex)
820 /*
821  Match a region in between the selection delimiters. It is
822  called after SelectRegion. The end delimiter will be visible
823  here so we have to check for it. Can handle multi-line chunks
824 */
825 {
826  const Item *ip = begin;
827  size_t buf_size = strlen(chunk) + 1;
828  char *buf = xmalloc(buf_size);
829  int lines = 0;
830 
831  for (const char *sp = chunk; sp <= chunk + strlen(chunk); sp++)
832  {
833  buf[0] = '\0';
834  sscanf(sp, "%[^\n]", buf);
835  sp += strlen(buf);
836 
837  if (ip == NULL)
838  {
839  lines = 0;
840  goto bad;
841  }
842 
843  if (!regex && strcmp(buf, ip->name) != 0)
844  {
845  lines = 0;
846  goto bad;
847  }
848  if (regex && !FullTextMatch(ctx, buf, ip->name))
849  {
850  lines = 0;
851  goto bad;
852  }
853 
854  lines++;
855 
856  // We have to manually exclude the marked terminator
857 
858  if (ip == end)
859  {
860  lines = 0;
861  goto bad;
862  }
863 
864  // Now see if there is more
865 
866  if (ip->next)
867  {
868  ip = ip->next;
869  }
870  else // if the region runs out before the end
871  {
872  if (++sp <= chunk + strlen(chunk))
873  {
874  lines = 0;
875  goto bad;
876  }
877 
878  break;
879  }
880  }
881 
882 bad:
883  free(buf);
884  return lines;
885 }
886 
887 /*****************************************************************************/
888 
889 static bool InsertMultipleLinesToRegion(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, const Attributes *a,
890  const Promise *pp, EditContext *edcontext, PromiseResult *result)
891 {
892  Item *ip, *prev = NULL;
893  int allow_multi_lines = StringEqual(a->sourcetype, "preserve_all_lines");
894 
895  // Insert at the start of the file
896 
897  if (*start == NULL)
898  {
899  return InsertMultipleLinesAtLocation(ctx, start, begin_ptr, end_ptr, *start, prev, a, pp, edcontext, result);
900  }
901 
902  // Insert at the start of the region
903 
905  {
906  /* As region was already selected by SelectRegion() and we know
907  * what are the region boundaries (begin_ptr and end_ptr) there
908  * is no reason to iterate over whole file. */
909  for (ip = begin_ptr; ip != NULL; ip = ip->next)
910  {
911  if (ip == begin_ptr)
912  {
913  return InsertMultipleLinesAtLocation(ctx, start, begin_ptr, end_ptr, ip, prev, a, pp, edcontext, result);
914  }
915 
916  prev = ip;
917  }
918  }
919 
920  // Insert at the end of the region / else end of the file
921 
923  {
924  /* As region was already selected by SelectRegion() and we know
925  * what are the region boundaries (begin_ptr and end_ptr) there
926  * is no reason to iterate over whole file. It is safe to start from
927  * begin_ptr.
928  * As a bonus Redmine #7640 is fixed as we are not interested in
929  * matching values outside of the region we are iterating over. */
930  for (ip = begin_ptr; ip != NULL; ip = ip->next)
931  {
932  if (!allow_multi_lines && MatchRegion(ctx, pp->promiser, ip, end_ptr, false))
933  {
934  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Promised chunk '%s' exists within selected region of %s (promise kept)", pp->promiser, edcontext->filename);
935  return false;
936  }
937 
938  if (ip->next != NULL && ip->next == end_ptr)
939  {
940  return InsertMultipleLinesAtLocation(ctx, start, begin_ptr, end_ptr, ip, prev, a, pp, edcontext, result);
941  }
942 
943  if (ip->next == NULL)
944  {
945  return InsertMultipleLinesAtLocation(ctx, start, begin_ptr, end_ptr, ip, prev, a, pp, edcontext, result);
946  }
947 
948  prev = ip;
949  }
950  }
951 
952  return false;
953 }
954 
955 /***************************************************************************/
956 
957 static bool InsertMultipleLinesAtLocation(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, Item *location,
958  Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
959 
960 // Promises to insert a possibly multi-line promiser at the specificed location convergently,
961 // i.e. no insertion will be made if a neighbouring line matches
962 
963 {
964  const char *const type = a->sourcetype;
965  int isfileinsert = StringEqual(type, "file") || StringEqual(type, "file_preserve_block");
966 
967  if (isfileinsert)
968  {
969  return InsertFileAtLocation(ctx, start, begin_ptr, end_ptr, location, prev, a, pp, edcontext, result);
970  }
971  else
972  {
973  return InsertCompoundLineAtLocation(ctx, pp->promiser, start, begin_ptr, end_ptr, location,
974  prev, a, pp, edcontext, result);
975  }
976 }
977 
978 /***************************************************************************/
979 
980 static bool DeletePromisedLinesMatching(EvalContext *ctx, Item **start, Item *begin, Item *end, const Attributes *a,
981  const Promise *pp, EditContext *edcontext, PromiseResult *result)
982 {
983  Item *ip, *np = NULL, *lp, *initiator = begin, *terminator = NULL;
984  int i, matches, noedits = true;
985  bool retval = false;
986 
987  if (start == NULL)
988  {
989  return false;
990  }
991 
992 // Get a pointer from before the region so we can patch the hole later
993 
994  if (begin == NULL)
995  {
996  initiator = *start;
997  }
998  else
999  {
1000  if (a->region.include_start)
1001  {
1002  initiator = begin;
1003  }
1004  else
1005  {
1006  initiator = begin->next;
1007  }
1008  }
1009 
1010  if (end == NULL)
1011  {
1012  terminator = NULL;
1013  }
1014  else
1015  {
1016  if (a->region.include_end)
1017  {
1018  terminator = end->next;
1019  }
1020  else
1021  {
1022  terminator = end;
1023  }
1024  }
1025 
1026 // Now do the deletion
1027 
1028  for (ip = initiator; ip != terminator && ip != NULL; ip = np)
1029  {
1030  if (a->not_matching)
1031  {
1032  matches = !MatchRegion(ctx, pp->promiser, ip, terminator, true);
1033  }
1034  else
1035  {
1036  matches = MatchRegion(ctx, pp->promiser, ip, terminator, true);
1037  }
1038 
1039  if (matches)
1040  {
1041  Log(LOG_LEVEL_VERBOSE, "Multi-line region (%d lines) matched text in the file", matches);
1042  }
1043  else
1044  {
1045  Log(LOG_LEVEL_DEBUG, "Multi-line region didn't match text in the file");
1046  }
1047 
1048  if (!SelectLine(ctx, ip->name, a)) // Start search from location
1049  {
1050  np = ip->next;
1051  continue;
1052  }
1053 
1054  if (matches)
1055  {
1056  Log(LOG_LEVEL_VERBOSE, "Delete chunk of %d lines", matches);
1057 
1058  if (a->transaction.action == cfa_warn)
1059  {
1061  "Need to delete line '%s' from %s - but only a warning was promised", ip->name,
1062  edcontext->filename);
1063  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
1064  np = ip->next;
1065  noedits = false;
1066  }
1067  else
1068  {
1069  for (i = 1; i <= matches; i++)
1070  {
1071  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Deleting the promised line %d '%s' from %s", i, ip->name,
1072  edcontext->filename);
1073  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
1074  retval = true;
1075  noedits = false;
1076 
1077  if (ip->name != NULL)
1078  {
1079  free(ip->name);
1080  }
1081 
1082  np = ip->next;
1083  free((char *) ip);
1084 
1085  lp = ip;
1086 
1087  if (ip == *start)
1088  {
1089  if (initiator == *start)
1090  {
1091  initiator = np;
1092  }
1093  *start = np;
1094  }
1095  else
1096  {
1097  if (ip == initiator)
1098  {
1099  initiator = *start;
1100  }
1101 
1102  for (lp = initiator; lp->next != ip; lp = lp->next)
1103  {
1104  }
1105 
1106  lp->next = np;
1107  }
1108 
1109  (edcontext->num_edits)++;
1110 
1111  ip = np;
1112  }
1113  }
1114  }
1115  else
1116  {
1117  np = ip->next;
1118  }
1119  }
1120 
1121  if (noedits)
1122  {
1123  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No need to delete lines from %s, ok", edcontext->filename);
1124  }
1125 
1126  return retval;
1127 }
1128 
1129 /********************************************************************/
1130 
1131 static int ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end, const Attributes *a,
1132  const Promise *pp, EditContext *edcontext, PromiseResult *result)
1133 {
1134  char line_buff[CF_EXPANDSIZE];
1135  char after[CF_BUFSIZE];
1136  int match_len, start_off, end_off, once_only = false, retval = false;
1137  Item *ip;
1138  int notfound = true, cutoff = 1, replaced = false;
1139 
1140  if (StringEqual(a->replace.occurrences, "first"))
1141  {
1142  Log(LOG_LEVEL_WARNING, "Setting replace-occurrences policy to 'first' is not convergent");
1143  once_only = true;
1144  }
1145 
1146  Buffer *replace = BufferNew();
1147  for (ip = file_start; ip != NULL && ip != file_end; ip = ip->next)
1148  {
1149  if (ip->name == NULL)
1150  {
1151  continue;
1152  }
1153 
1154  cutoff = 1;
1155  strlcpy(line_buff, ip->name, sizeof(line_buff));
1156  replaced = false;
1157  match_len = 0;
1158 
1159  while (BlockTextMatch(ctx, pp->promiser, line_buff, &start_off, &end_off))
1160  {
1161  if (match_len == strlen(line_buff))
1162  {
1163  Log(LOG_LEVEL_VERBOSE, "Improper convergent expression matches defacto convergence, so accepting");
1164  break;
1165  }
1166 
1167  if (cutoff++ > CF_MAX_REPLACE)
1168  {
1169  Log(LOG_LEVEL_VERBOSE, "Too many replacements on this line");
1170  break;
1171  }
1172 
1173  match_len = end_off - start_off;
1174  BufferClear(replace);
1175  ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, a->replace.replace_value, replace);
1176 
1177  Log(LOG_LEVEL_VERBOSE, "Verifying replacement of '%s' with '%s', cutoff %d", pp->promiser, BufferData(replace),
1178  cutoff);
1179 
1180  // Save portion of line after substitution:
1181  strlcpy(after, line_buff + end_off, sizeof(after));
1182  // TODO: gripe if that truncated !
1183 
1184  // Substitute into line_buff:
1185  snprintf(line_buff + start_off, sizeof(line_buff) - start_off,
1186  "%s%s", BufferData(replace), after);
1187  // TODO: gripe if that truncated or failed !
1188  notfound = false;
1189  replaced = true;
1190 
1191  if (once_only)
1192  {
1193  Log(LOG_LEVEL_VERBOSE, "Replace first occurrence only (warning, this is not a convergent policy)");
1194  break;
1195  }
1196  }
1197 
1198  if (NotAnchored(pp->promiser) && BlockTextMatch(ctx, pp->promiser, line_buff, &start_off, &end_off))
1199  {
1201  "Promised replacement '%s' on line '%s' for pattern '%s' is not convergent while editing '%s'",
1202  line_buff, ip->name, pp->promiser, edcontext->filename);
1203  *result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1204  Log(LOG_LEVEL_ERR, "Because the regular expression '%s' still matches the replacement string '%s'",
1205  pp->promiser, line_buff);
1207  break;
1208  }
1209 
1210  if (a->transaction.action == cfa_warn)
1211  {
1213  "Need to replace line '%s' in '%s' - but only a warning was promised", pp->promiser,
1214  edcontext->filename);
1215  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
1216  continue;
1217  }
1218  else if (replaced)
1219  {
1220  free(ip->name);
1221  ip->name = xstrdup(line_buff);
1222  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Replaced pattern '%s' in '%s'", pp->promiser, edcontext->filename);
1223  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
1224  (edcontext->num_edits)++;
1225  retval = true;
1226 
1227  Log(LOG_LEVEL_VERBOSE, "cutoff %d, '%s'", cutoff, ip->name);
1228  Log(LOG_LEVEL_VERBOSE, "cutoff %d, '%s'", cutoff, line_buff);
1229 
1230  if (once_only)
1231  {
1232  Log(LOG_LEVEL_VERBOSE, "Replace first occurrence only (warning, this is not a convergent policy)");
1233  break;
1234  }
1235 
1236  if (BlockTextMatch(ctx, pp->promiser, ip->name, &start_off, &end_off))
1237  {
1239  "Promised replacement '%s' for pattern '%s' is not properly convergent while editing '%s'",
1240  ip->name, pp->promiser, edcontext->filename);
1241  *result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1243  "Because the regular expression '%s' still matches the end-state replacement string '%s'",
1244  pp->promiser, line_buff);
1246  }
1247  }
1248  }
1249 
1250  BufferDestroy(replace);
1251 
1252  if (notfound)
1253  {
1254  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No pattern '%s' in '%s'", pp->promiser, edcontext->filename);
1255  }
1256 
1257  return retval;
1258 }
1259 
1260 /********************************************************************/
1261 
1262 static bool EditColumns(EvalContext *ctx, Item *file_start, Item *file_end, const Attributes *a,
1263  const Promise *pp, EditContext *edcontext, PromiseResult *result)
1264 {
1265  char separator[CF_MAXVARSIZE];
1266  int s, e;
1267  bool retval = false;
1268  Item *ip;
1269  Rlist *columns = NULL;
1270 
1271  if (!ValidateRegEx(pp->promiser))
1272  {
1273  return false;
1274  }
1275 
1276  for (ip = file_start; ip != file_end; ip = ip->next)
1277  {
1278  if (ip->name == NULL)
1279  {
1280  continue;
1281  }
1282 
1283  if (!FullTextMatch(ctx, pp->promiser, ip->name))
1284  {
1285  continue;
1286  }
1287  else
1288  {
1289  Log(LOG_LEVEL_VERBOSE, "Matched line '%s'", ip->name);
1290  }
1291 
1292  if (!BlockTextMatch(ctx, a->column.column_separator, ip->name, &s, &e))
1293  {
1294  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_INTERRUPTED, pp, a, "Field edit, no fields found by promised pattern '%s' in '%s'",
1295  a->column.column_separator, edcontext->filename);
1296  *result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1297  return false;
1298  }
1299 
1300  if (e - s > CF_MAXVARSIZE / 2)
1301  {
1302  Log(LOG_LEVEL_ERR, "Line split criterion matches a huge part of the line, seems to be in error");
1303  return false;
1304  }
1305 
1306  strlcpy(separator, ip->name + s, e - s + 1);
1307 
1309  retval = EditLineByColumn(ctx, &columns, a, pp, edcontext, result);
1310 
1311  if (retval)
1312  {
1313  free(ip->name);
1314  ip->name = Rlist2String(columns, separator);
1315  }
1316 
1317  RlistDestroy(columns);
1318  }
1319 
1320  return retval;
1321 }
1322 
1323 /***************************************************************************/
1324 
1325 static bool SanityCheckInsertions(const Attributes *a)
1326 {
1327  long not = 0;
1328  long with = 0;
1329  bool ok = true;
1330  Rlist *rp;
1331  InsertMatchType opt;
1332  int exact = false, ignore_something = false;
1333  const bool preserve_block = StringEqual(a->sourcetype, "preserve_block");
1334  const LineSelect line_select = a->line_select;
1335 
1336  if (line_select.startwith_from_list)
1337  {
1338  with++;
1339  }
1340 
1341  if (line_select.not_startwith_from_list)
1342  {
1343  not++;
1344  }
1345 
1346  if (line_select.match_from_list)
1347  {
1348  with++;
1349  }
1350 
1351  if (line_select.not_match_from_list)
1352  {
1353  not++;
1354  }
1355 
1356  if (line_select.contains_from_list)
1357  {
1358  with++;
1359  }
1360 
1361  if (line_select.not_contains_from_list)
1362  {
1363  not++;
1364  }
1365 
1366  if (not > 1)
1367  {
1369  "Line insertion selection promise is meaningless - the alternatives are mutually exclusive (only one is allowed)");
1370  ok = false;
1371  }
1372 
1373  if (with && not)
1374  {
1376  "Line insertion selection promise is meaningless - cannot mix positive and negative constraints");
1377  ok = false;
1378  }
1379 
1380  for (rp = a->insert_match; rp != NULL; rp = rp->next)
1381  {
1383 
1384  switch (opt)
1385  {
1387  exact = true;
1388  break;
1389  default:
1390  ignore_something = true;
1391  if (preserve_block)
1392  {
1393  Log(LOG_LEVEL_ERR, "Line insertion should not use whitespace policy with preserve_block");
1394  ok = false;
1395  }
1396  break;
1397  }
1398  }
1399 
1400  if (exact && ignore_something)
1401  {
1403  "Line insertion selection promise is meaningless - cannot mix exact_match with other ignore whitespace options");
1404  ok = false;
1405  }
1406 
1407  return ok;
1408 }
1409 
1410 /***************************************************************************/
1411 
1412 static bool SanityCheckDeletions(const Attributes *a, const Promise *pp)
1413 {
1414  if (MultiLineString(pp->promiser))
1415  {
1416  if (a->not_matching)
1417  {
1419  "Makes no sense to promise multi-line delete with not_matching. Cannot be satisfied for all lines as a block.");
1420  // FIXME: This function always returns true (!)
1421  }
1422  }
1423 
1424  return true;
1425 }
1426 
1427 /***************************************************************************/
1428 
1429 /* XXX */
1430 static bool MatchPolicy(EvalContext *ctx, const char *camel, const char *haystack, Rlist *insert_match, const Promise *pp)
1431 {
1432  char *final = NULL;
1433  bool ok = false;
1434  bool escaped = false;
1435  Item *list = SplitString(camel, '\n');
1436 
1437  //Split into separate lines first
1438  for (Item *ip = list; ip != NULL; ip = ip->next)
1439  {
1440  ok = false;
1441  bool direct_cmp = (strcmp(camel, haystack) == 0);
1442 
1443  final = xstrdup(ip->name);
1444  size_t final_size = strlen(final) + 1;
1445 
1446  if (insert_match == NULL)
1447  {
1448  // No whitespace policy means exact_match
1449  ok = ok || direct_cmp;
1450  break;
1451  }
1452 
1453  for (Rlist *rp = insert_match; rp != NULL; rp = rp->next)
1454  {
1455  const InsertMatchType opt =
1457 
1458  /* Exact match can be done immediately */
1459 
1460  if (opt == INSERT_MATCH_TYPE_EXACT)
1461  {
1462  if ((rp->next != NULL) || (rp != insert_match))
1463  {
1464  Log(LOG_LEVEL_ERR, "Multiple policies conflict with \"exact_match\", using exact match");
1466  }
1467 
1468  ok = ok || direct_cmp;
1469  break;
1470  }
1471 
1472  if (!escaped)
1473  {
1474  // Need to escape the original string once here in case it contains regex chars when non-exact match
1475  // Check size of escaped string, and realloc if necessary
1476  size_t escape_regex_len = EscapeRegexCharsLen(ip->name);
1477  if (escape_regex_len + 1 > final_size)
1478  {
1479  final = xrealloc(final, escape_regex_len + 1);
1480  final_size = escape_regex_len + 1;
1481  }
1482 
1483  EscapeRegexChars(ip->name, final, final_size);
1484  escaped = true;
1485  }
1486 
1488  {
1489  // Strip initial and final first
1490  char *firstchar, *lastchar;
1491  for (firstchar = final; isspace((int)*firstchar); firstchar++);
1492  for (lastchar = final + strlen(final) - 1; (lastchar > firstchar) && (isspace((int)*lastchar)); lastchar--);
1493 
1494  // Since we're stripping space and replacing it with \s+, we need to account for that
1495  // when allocating work
1496  size_t work_size = final_size + 6; /* allocated size */
1497  char *work = xcalloc(1, work_size);
1498 
1499  /* We start only with the terminating '\0'. */
1500  size_t required_size = 1;
1501 
1502  for (char *sp = final; *sp != '\0'; sp++)
1503  {
1504  char toadd[4];
1505 
1506  if ((sp > firstchar) && (sp < lastchar))
1507  {
1508  if (isspace((int)*sp))
1509  {
1510  while (isspace((int)*(sp + 1)))
1511  {
1512  sp++;
1513  }
1514 
1515  required_size += 3;
1516  strcpy(toadd, "\\s+");
1517  }
1518  else
1519  {
1520  required_size++;
1521  toadd[0] = *sp;
1522  toadd[1] = '\0';
1523  }
1524  }
1525  else
1526  {
1527  required_size++;
1528  toadd[0] = *sp;
1529  toadd[1] = '\0';
1530  }
1531 
1532  if (required_size > work_size)
1533  {
1534  // Increase by a small amount extra, so we don't
1535  // reallocate every iteration
1536  work_size = required_size + 12;
1537  work = xrealloc(work, work_size);
1538  }
1539 
1540  if (strlcat(work, toadd, work_size) >= work_size)
1541  {
1542  UnexpectedError("Truncation concatenating '%s' to: %s",
1543  toadd, work);
1544  }
1545  }
1546 
1547  // Realloc and retry on truncation
1548  if (strlcpy(final, work, final_size) >= final_size)
1549  {
1550  final = xrealloc(final, work_size);
1551  final_size = work_size;
1552  strlcpy(final, work, final_size);
1553  }
1554 
1555  free(work);
1556  }
1557  else if (opt == INSERT_MATCH_TYPE_IGNORE_LEADING)
1558  {
1559  if (strncmp(final, "\\s*", 3) != 0)
1560  {
1561  char *sp;
1562  for (sp = final; isspace((int)*sp); sp++);
1563 
1564  size_t work_size = final_size + 3;
1565  char *work = xcalloc(1, work_size);
1566  strcpy(work, sp);
1567 
1568  if (snprintf(final, final_size, "\\s*%s", work) >= final_size - 1)
1569  {
1570  final = xrealloc(final, work_size);
1571  final_size = work_size;
1572  snprintf(final, final_size, "\\s*%s", work);
1573  }
1574 
1575  free(work);
1576  }
1577  }
1578  else if (opt == INSERT_MATCH_TYPE_IGNORE_TRAILING)
1579  {
1580  if (strncmp(final + strlen(final) - 4, "\\s*", 3) != 0)
1581  {
1582  size_t work_size = final_size + 3;
1583  char *work = xcalloc(1, work_size);
1584  strcpy(work, final);
1585 
1586  char *sp;
1587  for (sp = work + strlen(work) - 1; (sp > work) && (isspace((int)*sp)); sp--);
1588  *++sp = '\0';
1589  if (snprintf(final, final_size, "%s\\s*", work) >= final_size - 1)
1590  {
1591  final = xrealloc(final, work_size);
1592  final_size = work_size;
1593  snprintf(final, final_size, "%s\\s*", work);
1594  }
1595 
1596  free(work);
1597  }
1598  }
1599 
1600  ok = ok || (FullTextMatch(ctx, final, haystack));
1601  }
1602 
1603  assert(final_size > strlen(final));
1604  free(final);
1605  final = NULL;
1606  if (!ok) // All lines in region need to match to avoid insertions
1607  {
1608  break;
1609  }
1610  }
1611 
1612  free(final);
1613  DeleteItemList(list);
1614  return ok;
1615 }
1616 
1617 static bool IsItemInRegion(EvalContext *ctx, const char *item, const Item *begin_ptr, const Item *end_ptr, Rlist *insert_match, const Promise *pp)
1618 {
1619  for (const Item *ip = begin_ptr; ((ip != end_ptr) && (ip != NULL)); ip = ip->next)
1620  {
1621  if (MatchPolicy(ctx, item, ip->name, insert_match, pp))
1622  {
1623  return true;
1624  }
1625  }
1626 
1627  return false;
1628 }
1629 
1630 /***************************************************************************/
1631 
1632 static bool InsertFileAtLocation(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, Item *location,
1633  Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
1634 {
1635  FILE *fin;
1636  bool retval = false;
1637  Item *loc = NULL;
1638  const bool preserve_block = StringEqual(a->sourcetype, "file_preserve_block");
1639 
1640  if ((fin = safe_fopen(pp->promiser, "rt")) == NULL)
1641  {
1642  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a, "Could not read file '%s'. (fopen: %s)", pp->promiser, GetErrorStr());
1643  *result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1644  return false;
1645  }
1646 
1647  size_t buf_size = CF_BUFSIZE;
1648  char *buf = xmalloc(buf_size);
1649  loc = location;
1650  Buffer *exp = BufferNew();
1651 
1652  while (CfReadLine(&buf, &buf_size, fin) != -1)
1653  {
1654  BufferClear(exp);
1655  if (a->expandvars)
1656  {
1657  ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, buf, exp);
1658  }
1659  else
1660  {
1661  BufferAppend(exp, buf, strlen(buf));
1662  }
1663 
1664  if (!SelectLine(ctx, BufferData(exp), a))
1665  {
1666  continue;
1667  }
1668 
1669  if (!preserve_block && IsItemInRegion(ctx, BufferData(exp), begin_ptr, end_ptr, a->insert_match, pp))
1670  {
1672  "Promised file line '%s' exists within file %s (promise kept)", BufferData(exp), edcontext->filename);
1673  continue;
1674  }
1675 
1676  // Need to call CompoundLine here in case ExpandScalar has inserted \n into a string
1677 
1678  retval |= InsertCompoundLineAtLocation(ctx, BufferGet(exp), start, begin_ptr, end_ptr, loc, prev, a, pp, edcontext, result);
1679 
1680  if (preserve_block && !prev)
1681  {
1682  // If we are inserting a preserved block before, need to flip the implied order after the first insertion
1683  // to get the order of the block right
1684  //a->location.before_after = cfe_after;
1685  }
1686 
1687  if (prev)
1688  {
1689  prev = prev->next;
1690  }
1691  else
1692  {
1693  prev = *start;
1694  }
1695 
1696  if (loc)
1697  {
1698  loc = loc->next;
1699  }
1700  else
1701  {
1702  location = *start;
1703  }
1704 
1705  free(buf);
1706  buf = NULL;
1707  }
1708 
1709  if (ferror(fin))
1710  {
1711  if (errno == EISDIR)
1712  {
1713  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a, "Could not read file %s: Is a directory", pp->promiser);
1714  *result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1715  }
1716  else
1717  {
1718  UnexpectedError("Failed to read line from stream");
1719  }
1720  }
1721 
1722  fclose(fin);
1723  BufferDestroy(exp);
1724  return retval;
1725 }
1726 
1727 /***************************************************************************/
1728 
1729 static bool InsertCompoundLineAtLocation(EvalContext *ctx, char *chunk, Item **start, Item *begin_ptr, Item *end_ptr,
1730  Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext,
1731  PromiseResult *result)
1732 {
1733  bool retval = false;
1734  const char *const type = a->sourcetype;
1735  const bool preserve_all_lines = StringEqual(type, "preserve_all_lines");
1736  const bool preserve_block = type && (preserve_all_lines || strcmp(type, "preserve_block") == 0 || strcmp(type, "file_preserve_block") == 0);
1737 
1738  if (!preserve_all_lines && MatchRegion(ctx, chunk, location, NULL, false))
1739  {
1740  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Promised chunk '%s' exists within selected region of %s (promise kept)", pp->promiser, edcontext->filename);
1741  return false;
1742  }
1743 
1744  // Iterate over any lines within the chunk
1745 
1746  char *buf = NULL;
1747  size_t buf_size = 0;
1748  for (char *sp = chunk; sp <= chunk + strlen(chunk); sp++)
1749  {
1750  if (strlen(chunk) + 1 > buf_size)
1751  {
1752  buf_size = strlen(chunk) + 1;
1753  buf = xrealloc(buf, buf_size);
1754  }
1755 
1756  memset(buf, 0, buf_size);
1757  StringNotMatchingSetCapped(sp, buf_size, "\n", buf);
1758  sp += strlen(buf);
1759 
1760  if (!SelectLine(ctx, buf, a))
1761  {
1762  continue;
1763  }
1764 
1765  if (!preserve_block && IsItemInRegion(ctx, buf, begin_ptr, end_ptr, a->insert_match, pp))
1766  {
1767  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Promised chunk '%s' exists within selected region of %s (promise kept)", pp->promiser, edcontext->filename);
1768  continue;
1769  }
1770 
1771  retval |= InsertLineAtLocation(ctx, buf, start, location, prev, a, pp, edcontext, result);
1772 
1773  if (preserve_block && a->location.before_after == EDIT_ORDER_BEFORE && location == NULL && prev == NULL)
1774  {
1775  // If we are inserting a preserved block before, need to flip the implied order after the first insertion
1776  // to get the order of the block right
1777  // a.location.before_after = cfe_after;
1778  location = *start;
1779  }
1780 
1781  if (prev)
1782  {
1783  prev = prev->next;
1784  }
1785  else
1786  {
1787  prev = *start;
1788  }
1789 
1790  if (location)
1791  {
1792  location = location->next;
1793  }
1794  else
1795  {
1796  location = *start;
1797  }
1798  }
1799 
1800  free(buf);
1801  return retval;
1802 }
1803 
1804 static bool NeighbourItemMatches(EvalContext *ctx, const Item *file_start, const Item *location, const char *string, EditOrder pos, Rlist *insert_match,
1805  const Promise *pp)
1806 {
1807 /* Look for a line matching proposed insert before or after location */
1808 
1809  for (const Item *ip = file_start; ip != NULL; ip = ip->next)
1810  {
1811  if (pos == EDIT_ORDER_BEFORE)
1812  {
1813  if ((ip->next) && (ip->next == location))
1814  {
1815  if (MatchPolicy(ctx, string, ip->name, insert_match, pp))
1816  {
1817  return true;
1818  }
1819  else
1820  {
1821  return false;
1822  }
1823  }
1824  }
1825 
1826  if (pos == EDIT_ORDER_AFTER)
1827  {
1828  if (ip == location)
1829  {
1830  if ((ip->next) && (MatchPolicy(ctx, string, ip->next->name, insert_match, pp)))
1831  {
1832  return true;
1833  }
1834  else
1835  {
1836  return false;
1837  }
1838  }
1839  }
1840  }
1841 
1842  return false;
1843 }
1844 
1845 static bool InsertLineAtLocation(EvalContext *ctx, char *newline, Item **start, Item *location, Item *prev, const Attributes *a,
1846  const Promise *pp, EditContext *edcontext, PromiseResult *result)
1847 
1848 /* Check line neighbourhood in whole file to avoid edge effects, iff we are not preseving block structure */
1849 
1850 { int preserve_block = StringEqual(a->sourcetype, "preserve_block");
1851 
1852  if (!prev) /* Insert at first line */
1853  {
1855  {
1856  if (*start == NULL)
1857  {
1858  if (a->transaction.action == cfa_warn)
1859  {
1861  "Need to insert the promised line '%s' in %s - but only a warning was promised", newline,
1862  edcontext->filename);
1863  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
1864  return true;
1865  }
1866  else
1867  {
1868  PrependItemList(start, newline);
1869  (edcontext->num_edits)++;
1870  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Inserting the promised line '%s' into %s", newline,
1871  edcontext->filename);
1872  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
1873  return true;
1874  }
1875  }
1876 
1877  if (strcmp((*start)->name, newline) != 0)
1878  {
1879  if (a->transaction.action == cfa_warn)
1880  {
1882  "Need to prepend the promised line '%s' to %s - but only a warning was promised",
1883  newline, edcontext->filename);
1884  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
1885  return true;
1886  }
1887  else
1888  {
1889  PrependItemList(start, newline);
1890  (edcontext->num_edits)++;
1891  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Prepending the promised line '%s' to %s", newline,
1892  edcontext->filename);
1893  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
1894  return true;
1895  }
1896  }
1897  else
1898  {
1900  "Promised line '%s' exists at start of file %s (promise kept)", newline, edcontext->filename);
1901  return false;
1902  }
1903  }
1904  }
1905 
1907  {
1908  if (!preserve_block && NeighbourItemMatches(ctx, *start, location, newline, EDIT_ORDER_BEFORE, a->insert_match, pp))
1909  {
1910  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Promised line '%s' exists before locator in (promise kept)",
1911  newline);
1912  return false;
1913  }
1914  else
1915  {
1916  if (a->transaction.action == cfa_warn)
1917  {
1919  "Need to insert line '%s' into '%s' but only a warning was promised", newline,
1920  edcontext->filename);
1921  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
1922  return true;
1923  }
1924  else
1925  {
1926  InsertAfter(start, prev, newline);
1927  (edcontext->num_edits)++;
1928  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Inserting the promised line '%s' into '%s' before locator",
1929  newline, edcontext->filename);
1930  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
1931  return true;
1932  }
1933  }
1934  }
1935  else
1936  {
1937  if (!preserve_block && NeighbourItemMatches(ctx, *start, location, newline, EDIT_ORDER_AFTER, a->insert_match, pp))
1938  {
1939  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Promised line '%s' exists after locator (promise kept)",
1940  newline);
1941  return false;
1942  }
1943  else
1944  {
1945  if (a->transaction.action == cfa_warn)
1946  {
1948  "Need to insert line '%s' in '%s' but only a warning was promised", newline, edcontext->filename);
1949  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
1950  return true;
1951  }
1952  else
1953  {
1954  InsertAfter(start, location, newline);
1955  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Inserting the promised line '%s' into '%s' after locator",
1956  newline, edcontext->filename);
1957  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
1958  (edcontext->num_edits)++;
1959  return true;
1960  }
1961  }
1962  }
1963 }
1964 
1965 /***************************************************************************/
1966 
1967 static bool EditLineByColumn(EvalContext *ctx, Rlist **columns, const Attributes *a,
1968  const Promise *pp, EditContext *edcontext, PromiseResult *result)
1969 {
1970  Rlist *rp, *this_column = NULL;
1971  char sep[CF_MAXVARSIZE];
1972  int i, count = 0;
1973  bool retval = false;
1974 
1975 /* Now break up the line into a list - not we never remove an item/column */
1976 
1977  for (rp = *columns; rp != NULL; rp = rp->next)
1978  {
1979  count++;
1980 
1981  if (count == a->column.select_column)
1982  {
1983  Log(LOG_LEVEL_VERBOSE, "Stopped at field %d", count);
1984  break;
1985  }
1986  }
1987 
1988  if (a->column.select_column > count)
1989  {
1990  if (!a->column.extend_columns)
1991  {
1993  "The file %s has only %d fields, but there is a promise for field %d", edcontext->filename, count,
1994  a->column.select_column);
1995  *result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1996  return false;
1997  }
1998  else
1999  {
2000  for (i = 0; i < (a->column.select_column - count); i++)
2001  {
2002  RlistAppendScalar(columns, "");
2003  }
2004 
2005  count = 0;
2006 
2007  for (rp = *columns; rp != NULL; rp = rp->next)
2008  {
2009  count++;
2010  if (count == a->column.select_column)
2011  {
2012  Log(LOG_LEVEL_VERBOSE, "Stopped at column/field %d", count);
2013  break;
2014  }
2015  }
2016  }
2017  }
2018 
2019  if (a->column.value_separator != '\0')
2020  {
2021  /* internal separator, single char so split again */
2022 
2023  if (strstr(RlistScalarValue(rp), a->column.column_value) || strcmp(RlistScalarValue(rp), a->column.column_value) != 0)
2024  {
2026  retval = DoEditColumn(&this_column, a, edcontext);
2027  }
2028  else
2029  {
2030  retval = false;
2031  }
2032 
2033  if (retval)
2034  {
2035  if (a->transaction.action == cfa_warn)
2036  {
2037  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Need to edit field in %s but only warning promised",
2038  edcontext->filename);
2039  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
2040  retval = false;
2041  }
2042  else
2043  {
2044  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Edited field inside file object %s", edcontext->filename);
2045  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
2046  (edcontext->num_edits)++;
2047  free(RlistScalarValue(rp));
2048  sep[0] = a->column.value_separator;
2049  sep[1] = '\0';
2050  rp->val.item = Rlist2String(this_column, sep);
2051  }
2052  }
2053  else
2054  {
2055  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No need to edit field in %s", edcontext->filename);
2056  }
2057 
2058  RlistDestroy(this_column);
2059  return retval;
2060  }
2061  else
2062  {
2063  /* No separator, so we set the whole field to the value */
2064 
2065  if (a->column.column_operation && strcmp(a->column.column_operation, "delete") == 0)
2066  {
2067  if (a->transaction.action == cfa_warn)
2068  {
2070  "Need to delete field field value %s in %s but only a warning was promised", RlistScalarValue(rp),
2071  edcontext->filename);
2072  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
2073  return false;
2074  }
2075  else
2076  {
2077  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Deleting column field value %s in %s", RlistScalarValue(rp),
2078  edcontext->filename);
2079  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
2080  (edcontext->num_edits)++;
2081  free(rp->val.item);
2082  rp->val.item = xstrdup("");
2083  return true;
2084  }
2085  }
2086  else
2087  {
2088  if (a->transaction.action == cfa_warn)
2089  {
2091  "Need to set column field value %s to %s in %s but only a warning was promised",
2092  RlistScalarValue(rp), a->column.column_value, edcontext->filename);
2093  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
2094  return false;
2095  }
2096  else
2097  {
2098  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Setting whole column field value %s to %s in %s",
2099  RlistScalarValue(rp), a->column.column_value, edcontext->filename);
2100  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
2101  free(rp->val.item);
2102  rp->val.item = xstrdup(a->column.column_value);
2103  (edcontext->num_edits)++;
2104  return true;
2105  }
2106  }
2107  }
2108 
2109  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No need to edit column field value %s in %s", a->column.column_value,
2110  edcontext->filename);
2111 
2112  return false;
2113 }
2114 
2115 /***************************************************************************/
2116 
2117 static bool SelectLine(EvalContext *ctx, const char *line, const Attributes *a)
2118 {
2119  Rlist *rp, *c;
2120  int s, e;
2121  char *selector;
2122  const LineSelect line_select = a->line_select;
2123 
2124  if ((c = line_select.startwith_from_list))
2125  {
2126  for (rp = c; rp != NULL; rp = rp->next)
2127  {
2128  selector = RlistScalarValue(rp);
2129 
2130  if (strncmp(selector, line, strlen(selector)) == 0)
2131  {
2132  return true;
2133  }
2134  }
2135 
2136  return false;
2137  }
2138 
2139  if ((c = line_select.not_startwith_from_list))
2140  {
2141  for (rp = c; rp != NULL; rp = rp->next)
2142  {
2143  selector = RlistScalarValue(rp);
2144 
2145  if (strncmp(selector, line, strlen(selector)) == 0)
2146  {
2147  return false;
2148  }
2149  }
2150 
2151  return true;
2152  }
2153 
2154  if ((c = line_select.match_from_list))
2155  {
2156  for (rp = c; rp != NULL; rp = rp->next)
2157  {
2158  selector = RlistScalarValue(rp);
2159 
2160  if (FullTextMatch(ctx, selector, line))
2161  {
2162  return true;
2163  }
2164  }
2165 
2166  return false;
2167  }
2168 
2169  if ((c = line_select.not_match_from_list))
2170  {
2171  for (rp = c; rp != NULL; rp = rp->next)
2172  {
2173  selector = RlistScalarValue(rp);
2174 
2175  if (FullTextMatch(ctx, selector, line))
2176  {
2177  return false;
2178  }
2179  }
2180 
2181  return true;
2182  }
2183 
2184  if ((c = line_select.contains_from_list))
2185  {
2186  for (rp = c; rp != NULL; rp = rp->next)
2187  {
2188  selector = RlistScalarValue(rp);
2189 
2190  if (BlockTextMatch(ctx, selector, line, &s, &e))
2191  {
2192  return true;
2193  }
2194  }
2195 
2196  return false;
2197  }
2198 
2199  if ((c = line_select.not_contains_from_list))
2200  {
2201  for (rp = c; rp != NULL; rp = rp->next)
2202  {
2203  selector = RlistScalarValue(rp);
2204 
2205  if (BlockTextMatch(ctx, selector, line, &s, &e))
2206  {
2207  return false;
2208  }
2209  }
2210 
2211  return true;
2212  }
2213 
2214  return true;
2215 }
2216 
2217 /***************************************************************************/
2218 /* Level */
2219 /***************************************************************************/
2220 
2221 static bool DoEditColumn(Rlist **columns, const Attributes *a, EditContext *edcontext)
2222 {
2223  Rlist *rp, *found;
2224  bool retval = false;
2225 
2226  const char *const column_value = a->column.column_value;
2227  const char *const column_operation = a->column.column_operation;
2228 
2229  if (StringEqual(column_operation, "delete"))
2230  {
2231  while ((found = RlistKeyIn(*columns, column_value)))
2232  {
2233  Log(LOG_LEVEL_INFO, "Deleting column field sub-value '%s' in '%s'", column_value,
2234  edcontext->filename);
2235  RlistDestroyEntry(columns, found);
2236  retval = true;
2237  }
2238 
2239  return retval;
2240  }
2241 
2242  if (StringEqual(column_operation, "set"))
2243  {
2244  int length = RlistLen(*columns);
2245  if (length == 1 && strcmp(RlistScalarValue(*columns), column_value) == 0)
2246  {
2247  Log(LOG_LEVEL_VERBOSE, "Field sub-value set as promised");
2248  return false;
2249  }
2250  else if (length == 0 && strcmp("", column_value) == 0)
2251  {
2252  Log(LOG_LEVEL_VERBOSE, "Empty field sub-value set as promised");
2253  return false;
2254  }
2255 
2256  Log(LOG_LEVEL_INFO, "Setting field sub-value '%s' in '%s'", column_value, edcontext->filename);
2257  RlistDestroy(*columns);
2258  *columns = NULL;
2259  RlistPrependScalarIdemp(columns, column_value);
2260 
2261  return true;
2262  }
2263 
2264  if (StringEqual(column_operation, "prepend"))
2265  {
2266  if (RlistPrependScalarIdemp(columns, column_value))
2267  {
2268  Log(LOG_LEVEL_INFO, "Prepending field sub-value '%s' in '%s'", column_value, edcontext->filename);
2269  return true;
2270  }
2271  else
2272  {
2273  return false;
2274  }
2275  }
2276 
2277  if (StringEqual(column_operation, "alphanum"))
2278  {
2279  if (RlistPrependScalarIdemp(columns, column_value))
2280  {
2281  retval = true;
2282  }
2283 
2284  rp = AlphaSortRListNames(*columns);
2285  *columns = rp;
2286  return retval;
2287  }
2288 
2289 /* default operation is append */
2290 
2291  if (RlistAppendScalarIdemp(columns, column_value))
2292  {
2293  return true;
2294  }
2295  else
2296  {
2297  return false;
2298  }
2299 
2300  return false;
2301 }
2302 
2303 /********************************************************************/
2304 
2305 static bool NotAnchored(char *s)
2306 {
2307  if (*s != '^')
2308  {
2309  return true;
2310  }
2311 
2312  if (*(s + strlen(s) - 1) != '$')
2313  {
2314  return true;
2315  }
2316 
2317  return false;
2318 }
2319 
2320 /********************************************************************/
2321 
2322 static bool MultiLineString(char *s)
2323 {
2324  return (strchr(s, '\n') != NULL);
2325 }
PromiseResult PromiseResultUpdate(PromiseResult prior, PromiseResult evidence)
Definition: actuator.c:28
void * xmalloc(size_t size)
Definition: alloc-mini.c:46
void * xcalloc(size_t nmemb, size_t size)
Definition: alloc-mini.c:51
char * xstrdup(const char *str)
Definition: alloc-mini.c:56
void * xrealloc(void *ptr, size_t size)
Definition: alloc.c:51
Attributes GetDeletionAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:1474
Attributes GetReplaceAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:1524
Attributes GetColumnAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:1501
Attributes GetInsertionAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:1417
void BufferDestroy(Buffer *buffer)
Destroys a buffer and frees the memory associated with it.
Definition: buffer.c:72
Buffer * BufferNew(void)
Buffer initialization routine.
Definition: buffer.c:48
char * BufferGet(Buffer *buffer)
This functions allows direct access to the storage inside Buffer.
Definition: buffer.c:239
const char * BufferData(const Buffer *buffer)
Provides a pointer to the internal data.
Definition: buffer.c:470
void BufferClear(Buffer *buffer)
Clears the buffer.
Definition: buffer.c:457
void BufferAppend(Buffer *buffer, const char *bytes, unsigned int length)
Definition: buffer.c:269
@ RVAL_TYPE_SCALAR
Definition: cf3.defs.h:606
@ RVAL_TYPE_NOPROMISEE
Definition: cf3.defs.h:610
PromiseResult
Definition: cf3.defs.h:122
@ PROMISE_RESULT_CHANGE
Definition: cf3.defs.h:125
@ PROMISE_RESULT_INTERRUPTED
Definition: cf3.defs.h:130
@ PROMISE_RESULT_NOOP
Definition: cf3.defs.h:124
@ PROMISE_RESULT_WARN
Definition: cf3.defs.h:126
@ PROMISE_RESULT_SKIPPED
Definition: cf3.defs.h:123
InsertMatchType
Definition: cf3.defs.h:877
@ INSERT_MATCH_TYPE_EXACT
Definition: cf3.defs.h:881
@ INSERT_MATCH_TYPE_IGNORE_EMBEDDED
Definition: cf3.defs.h:880
@ INSERT_MATCH_TYPE_IGNORE_LEADING
Definition: cf3.defs.h:878
@ INSERT_MATCH_TYPE_IGNORE_TRAILING
Definition: cf3.defs.h:879
@ cfa_warn
Definition: cf3.defs.h:719
EditOrder
Definition: cf3.defs.h:513
@ EDIT_ORDER_BEFORE
Definition: cf3.defs.h:514
@ EDIT_ORDER_AFTER
Definition: cf3.defs.h:515
@ CF_DATA_TYPE_STRING
Definition: cf3.defs.h:369
#define CF_INFINITY
Definition: cf3.defs.h:66
#define CF_DONEPASSES
Definition: cf3.defs.h:344
time_t CFSTARTTIME
Definition: cf3globals.c:99
char VUQNAME[]
Definition: cf3globals.c:59
void free(void *)
char * Rlist2String(Rlist *list, char *sep)
Definition: conversion.c:202
InsertMatchType InsertMatchTypeFromString(const char *s)
Definition: conversion.c:85
#define CF_BUFSIZE
Definition: definitions.h:50
#define CF_EXPANDSIZE
Definition: definitions.h:51
#define CF_MAXVARSIZE
Definition: definitions.h:36
bool BundleAbort(EvalContext *ctx)
Definition: eval_context.c:803
void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt,...)
void EvalContextStackPushPromiseTypeFrame(EvalContext *ctx, const PromiseType *owner)
bool EvalContextVariableClearMatch(EvalContext *ctx)
void EvalContextStackPopFrame(EvalContext *ctx)
bool EvalContextVariablePutSpecial(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags)
static bool IsDefinedClass(const EvalContext *ctx, const char *context)
Definition: eval_context.h:213
char * ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out)
Definition: expand.c:516
PromiseResult ExpandPromise(EvalContext *ctx, const Promise *pp, PromiseActuator *act_on_promise, void *param)
Definition: expand.c:257
ssize_t CfReadLine(char **buff, size_t *size, FILE *fp)
Works exactly like posix 'getline', EXCEPT it does not include carriage return at the end.
Definition: file_lib.c:1476
FILE * safe_fopen(const char *const path, const char *const mode)
Definition: file_lib.c:812
#define CF_EDIT_IFELAPSED
Definition: files_edit.h:38
static int MatchRegion(EvalContext *ctx, const char *chunk, const Item *begin, const Item *end, bool regex)
static bool InsertFileAtLocation(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static bool SelectLine(EvalContext *ctx, const char *line, const Attributes *a)
static bool MultiLineString(char *s)
static bool SelectRegion(EvalContext *ctx, Item *start, Item **begin_ptr, Item **end_ptr, const Attributes *a, EditContext *edcontext)
static PromiseResult KeepEditLinePromise(EvalContext *ctx, const Promise *pp, void *param)
static PromiseResult VerifyLineInsertions(EvalContext *ctx, const Promise *pp, EditContext *edcontext)
static bool InsertMultipleLinesAtLocation(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static PromiseResult VerifyPatterns(EvalContext *ctx, const Promise *pp, EditContext *edcontext)
#define CF_MAX_REPLACE
static int ReplacePatterns(EvalContext *ctx, Item *start, Item *end, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
editlinetypesequence
@ elp_reports
@ elp_replace
@ elp_vars
@ elp_classes
@ elp_none
@ elp_delete
@ elp_columns
@ elp_insert
static bool IsItemInRegion(EvalContext *ctx, const char *item, const Item *begin_ptr, const Item *end_ptr, Rlist *insert_match, const Promise *pp)
static bool NeighbourItemMatches(EvalContext *ctx, const Item *file_start, const Item *location, const char *string, EditOrder pos, Rlist *insert_match, const Promise *pp)
static bool NotAnchored(char *s)
static PromiseResult VerifyColumnEdits(EvalContext *ctx, const Promise *pp, EditContext *edcontext)
static PromiseResult VerifyLineDeletions(EvalContext *ctx, const Promise *pp, EditContext *edcontext)
static bool EditColumns(EvalContext *ctx, Item *file_start, Item *file_end, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static bool InsertLineAtLocation(EvalContext *ctx, char *newline, Item **start, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static bool SelectNextItemMatching(EvalContext *ctx, const char *regexp, Item *begin, Item *end, Item **match, Item **prev)
static bool SanityCheckInsertions(const Attributes *a)
Bundle * MakeTemporaryBundleFromTemplate(EvalContext *ctx, Policy *policy, const Attributes *a, const Promise *pp, PromiseResult *result)
static bool InsertCompoundLineAtLocation(EvalContext *ctx, char *newline, Item **start, Item *begin_ptr, Item *end_ptr, Item *location, Item *prev, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static bool EditLineByColumn(EvalContext *ctx, Rlist **columns, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static bool DoEditColumn(Rlist **columns, const Attributes *a, EditContext *edcontext)
static bool InsertMultipleLinesToRegion(EvalContext *ctx, Item **start, Item *begin_ptr, Item *end_ptr, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
static bool DeletePromisedLinesMatching(EvalContext *ctx, Item **start, Item *begin, Item *end, const Attributes *a, const Promise *pp, EditContext *edcontext, PromiseResult *result)
bool ScheduleEditLineOperations(EvalContext *ctx, const Bundle *bp, const Attributes *a, const Promise *parentp, EditContext *edcontext)
static bool SelectLastItemMatching(EvalContext *ctx, const char *regexp, Item *begin, Item *end, Item **match, Item **prev)
static bool SelectItemMatching(EvalContext *ctx, Item *start, char *regex, Item *begin_ptr, Item *end_ptr, Item **match, Item **prev, char *fl)
static const char *const EDITLINETYPESEQUENCE[]
static bool MatchPolicy(EvalContext *ctx, const char *camel, const char *haystack, Rlist *insert_match, const Promise *pp)
static bool SanityCheckDeletions(const Attributes *a, const Promise *pp)
char * CanonifyName(const char *str)
Definition: files_names.c:483
ssize_t getline(char **lineptr, size_t *n, FILE *stream)
int errno
#define NULL
Definition: getopt1.c:56
void PrependItemList(Item **liststart, const char *itemstring)
Definition: item_lib.c:439
void AppendItem(Item **liststart, const char *itemstring, const char *classes)
Definition: item_lib.c:415
Item * SplitString(const char *string, char sep)
Definition: item_lib.c:546
void DeleteItemList(Item *item)
Definition: item_lib.c:808
void DeleteItem(Item **liststart, Item *item)
Definition: item_lib.c:823
void InsertAfter(Item **filestart, Item *ptr, const char *string)
Definition: item_lib.c:514
Item * PrependItem(Item **liststart, const char *itemstring, const char *classes)
Definition: item_lib.c:372
void YieldCurrentLock(CfLock lock)
Definition: locks.c:948
CfLock AcquireLock(EvalContext *ctx, const char *operand, const char *host, time_t now, int ifelapsed, int expireafter, const Promise *pp, bool ignoreProcesses)
Definition: locks.c:789
const char * GetErrorStr(void)
Definition: logging.c:275
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_WARNING
Definition: logging.h:43
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
@ LOG_LEVEL_INFO
Definition: logging.h:45
bool ValidateRegEx(const char *regex)
Definition: match_scope.c:112
bool BlockTextMatch(EvalContext *ctx, const char *regexp, const char *teststring, int *start, int *end)
Definition: match_scope.c:121
bool FullTextMatch(EvalContext *ctx, const char *regexp, const char *teststring)
Definition: match_scope.c:87
void EscapeRegexChars(char *str, char *strEsc, int strEscSz)
Definition: matching.c:331
size_t EscapeRegexCharsLen(const char *str)
Definition: matching.c:310
#define CF_ASSERT(condition,...)
Definition: misc_lib.h:51
#define UnexpectedError(...)
Definition: misc_lib.h:38
void PromiseBanner(EvalContext *ctx, const Promise *pp)
Definition: ornaments.c:127
Bundle * PolicyAppendBundle(Policy *policy, const char *ns, const char *name, const char *type, const Rlist *args, const char *source_path)
Definition: policy.c:1326
const Bundle * PromiseGetBundle(const Promise *pp)
Definition: policy.c:2671
PromiseType * BundleAppendPromiseType(Bundle *bundle, const char *name)
Definition: policy.c:1376
Constraint * PromiseAppendConstraint(Promise *pp, const char *lval, Rval rval, bool references_body)
Definition: policy.c:1506
Promise * PromiseTypeAppendPromise(PromiseType *type, const char *promiser, Rval promisee, const char *classes, const char *varclasses)
Definition: policy.c:1406
const PromiseType * BundleGetPromiseType(const Bundle *bp, const char *name)
Definition: policy.c:1600
void PromiseRef(LogLevel level, const Promise *pp)
Definition: promises.c:769
PromiseResult VerifyReportPromise(EvalContext *ctx, const Promise *pp)
char * RlistScalarValue(const Rlist *rlist)
Definition: rlist.c:83
Rlist * RlistFromSplitRegex(const char *string, const char *regex, size_t max_entries, bool allow_blanks)
Definition: rlist.c:1139
Rlist * RlistKeyIn(Rlist *list, const char *key)
Definition: rlist.c:196
void RlistDestroyEntry(Rlist **liststart, Rlist *entry)
Definition: rlist.c:972
Rlist * RlistFromSplitString(const char *string, char sep)
Definition: rlist.c:1067
void RlistDestroy(Rlist *rl)
Definition: rlist.c:501
Rval RvalNew(const void *item, RvalType type)
Definition: rlist.c:464
Rlist * RlistAppendScalar(Rlist **start, const char *scalar)
Definition: rlist.c:545
Rlist * RlistAppendScalarIdemp(Rlist **start, const char *scalar)
Definition: rlist.c:525
int RlistLen(const Rlist *start)
Definition: rlist.c:672
Rlist * RlistPrependScalarIdemp(Rlist **start, const char *scalar)
Definition: rlist.c:535
@ SPECIAL_SCOPE_EDIT
Definition: scope.h:35
size_t SeqLength(const Seq *seq)
Length of the sequence.
Definition: sequence.c:354
static void * SeqAt(const Seq *seq, int i)
Definition: sequence.h:57
Rlist * AlphaSortRListNames(Rlist *list)
Definition: sort.c:388
bool StringNotMatchingSetCapped(const char *isp, int limit, const char *exclude, char *obuf)
Definition: string_lib.c:1389
bool StringEqual(const char *const a, const char *const b)
Definition: string_lib.c:256
int StripTrailingNewline(char *str, size_t max_length)
Strips the newline character off a string, in place.
Definition: string_lib.c:1138
size_t strlcat(char *dst, const char *src, size_t siz)
Definition: strlcat.c:36
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:34
char * strstr(const char *haystack, const char *needle)
Definition: strstr.c:35
EditLocation location
Definition: cf3.defs.h:1609
int expandvars
Definition: cf3.defs.h:1621
EditRegion region
Definition: cf3.defs.h:1608
Rlist * insert_match
Definition: cf3.defs.h:1623
int not_matching
Definition: cf3.defs.h:1622
char * edit_template
Definition: cf3.defs.h:1557
int haveregion
Definition: cf3.defs.h:1613
EditColumn column
Definition: cf3.defs.h:1610
char * sourcetype
Definition: cf3.defs.h:1620
TransactionContext transaction
Definition: cf3.defs.h:1567
EditReplace replace
Definition: cf3.defs.h:1611
LineSelect line_select
Definition: cf3.defs.h:1619
Definition: buffer.h:50
Definition: policy.h:70
char * type
Definition: policy.h:73
char * lock
Definition: cf3.defs.h:895
char * column_operation
Definition: cf3.defs.h:1255
char * column_value
Definition: cf3.defs.h:1254
int blanks_ok
Definition: cf3.defs.h:1257
char value_separator
Definition: cf3.defs.h:1253
int extend_columns
Definition: cf3.defs.h:1256
int select_column
Definition: cf3.defs.h:1252
char * column_separator
Definition: cf3.defs.h:1251
int num_edits
Definition: files_edit.h:48
char * filename
Definition: files_edit.h:46
Item * file_start
Definition: files_edit.h:47
EditOrder before_after
Definition: cf3.defs.h:1236
char * line_matching
Definition: cf3.defs.h:1235
char * first_last
Definition: cf3.defs.h:1237
int include_start
Definition: cf3.defs.h:1244
bool select_end_match_eof
Definition: cf3.defs.h:1246
char * select_start
Definition: cf3.defs.h:1242
char * select_end
Definition: cf3.defs.h:1243
int include_end
Definition: cf3.defs.h:1245
char * occurrences
Definition: cf3.defs.h:1263
char * replace_value
Definition: cf3.defs.h:1262
Definition: item_lib.h:33
Item * next
Definition: item_lib.h:38
char * name
Definition: item_lib.h:34
Rlist * match_from_list
Definition: cf3.defs.h:1217
Rlist * contains_from_list
Definition: cf3.defs.h:1219
Rlist * not_startwith_from_list
Definition: cf3.defs.h:1216
Rlist * not_match_from_list
Definition: cf3.defs.h:1218
Rlist * not_contains_from_list
Definition: cf3.defs.h:1220
Rlist * startwith_from_list
Definition: cf3.defs.h:1215
Definition: policy.h:53
Seq * promises
Definition: policy.h:104
char * name
Definition: policy.h:103
PromiseType * parent_promise_type
Definition: policy.h:111
char * promiser
Definition: policy.h:115
SourceOffset offset
Definition: policy.h:121
Definition: rlist.h:35
Rval val
Definition: rlist.h:36
Rlist * next
Definition: rlist.h:37
Definition: cf3.defs.h:614
void * item
Definition: cf3.defs.h:615
size_t line
Definition: policy.h:65
enum cfopaction action
Definition: cf3.defs.h:927
PromiseResult VerifyClassPromise(EvalContext *ctx, const Promise *pp, void *param)