Skip to content

Commit e27bdf8

Browse files
authored
Merge pull request #6072 from nickanderson/CFE-3866/master
CFE-3866: Allowed select_region to converge across passes
2 parents 3663dae + 1a411a0 commit e27bdf8

3 files changed

Lines changed: 157 additions & 31 deletions

File tree

cf-agent/files_edit.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,16 @@ typedef struct
4747
char *changes_filename;
4848
Item *file_start;
4949
int num_edits;
50+
int pass; // Current convergence pass (1 to CF_DONEPASSES-1)
5051
#ifdef HAVE_LIBXML2
5152
xmlDocPtr xmldoc;
5253
#endif
5354
NewLineMode new_line_mode;
5455
} EditContext;
5556

57+
// Check if we're on the final convergence pass where errors should be reported
58+
#define EDIT_CONTEXT_IS_FINAL_PASS(ec) ((ec)->pass >= CF_DONEPASSES - 1)
59+
5660
// filename must not be freed until FinishEditContext.
5761
EditContext *NewEditContext(char *filename, const Attributes *a);
5862
void FinishEditContext(EvalContext *ctx, EditContext *ec,

cf-agent/files_editline.c

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ static bool SanityCheckDeletions(const Attributes *a, const Promise *pp);
9696
static bool SelectLine(EvalContext *ctx, const char *line, const Attributes *a);
9797
static bool NotAnchored(char *s);
9898
static bool SelectRegion(EvalContext *ctx, Item *start, Item **begin_ptr, Item **end_ptr, const Attributes *a, EditContext *edcontext);
99+
static PromiseResult HandleSelectRegionFailure(EvalContext *ctx, const Promise *pp, const Attributes *a, EditContext *edcontext, const char *operation_type);
99100
static bool MultiLineString(char *s);
100101
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);
101102

@@ -131,6 +132,7 @@ bool ScheduleEditLineOperations(EvalContext *ctx, const Bundle *bp, const Attrib
131132

132133
for (pass = 1; pass < CF_DONEPASSES; pass++)
133134
{
135+
edcontext->pass = pass; // Track current pass for convergence
134136
for (type = 0; EDITLINETYPESEQUENCE[type] != NULL; type++)
135137
{
136138
const BundleSection *sp = BundleGetSection(bp, EDITLINETYPESEQUENCE[type]);
@@ -390,22 +392,7 @@ static PromiseResult VerifyLineDeletions(EvalContext *ctx, const Promise *pp, Ed
390392
}
391393
else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
392394
{
393-
if (a.region.include_end || a.region.include_start)
394-
{
395-
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a,
396-
"The promised line deletion '%s' could not select an edit region in '%s'"
397-
" (this is a good thing, as policy suggests deleting the markers)",
398-
pp->promiser, edcontext->filename);
399-
}
400-
else
401-
{
402-
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a,
403-
"The promised line deletion '%s' could not select an edit region in '%s'"
404-
" (but the delimiters were expected in the file)",
405-
pp->promiser, edcontext->filename);
406-
}
407-
result = PromiseResultUpdate(result, PROMISE_RESULT_INTERRUPTED);
408-
return result;
395+
return HandleSelectRegionFailure(ctx, pp, &a, edcontext, "line deletion");
409396
}
410397
if (!end_ptr && a.region.select_end && !a.region.select_end_match_eof)
411398
{
@@ -502,11 +489,7 @@ static PromiseResult VerifyColumnEdits(EvalContext *ctx, const Promise *pp, Edit
502489
}
503490
else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
504491
{
505-
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a,
506-
"The promised column edit '%s' could not select an edit region in '%s'",
507-
pp->promiser, edcontext->filename);
508-
result = PromiseResultUpdate(result, PROMISE_RESULT_INTERRUPTED);
509-
return result;
492+
return HandleSelectRegionFailure(ctx, pp, &a, edcontext, "column edit");
510493
}
511494

512495
/* locate and split line */
@@ -581,11 +564,7 @@ static PromiseResult VerifyPatterns(EvalContext *ctx, const Promise *pp, EditCon
581564
}
582565
else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
583566
{
584-
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a,
585-
"The promised pattern replace '%s' could not select an edit region in '%s'",
586-
pp->promiser, edcontext->filename);
587-
result = PromiseResultUpdate(result, PROMISE_RESULT_INTERRUPTED);
588-
return result;
567+
return HandleSelectRegionFailure(ctx, pp, &a, edcontext, "pattern replace");
589568
}
590569

591570
snprintf(lockname, CF_BUFSIZE - 1, "replace-%s-%s", pp->promiser, edcontext->filename);
@@ -774,11 +753,7 @@ static PromiseResult VerifyLineInsertions(EvalContext *ctx, const Promise *pp, E
774753
}
775754
else if (!SelectRegion(ctx, *start, &begin_ptr, &end_ptr, &a, edcontext))
776755
{
777-
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, &a,
778-
"The promised line insertion '%s' could not select an edit region in '%s'",
779-
pp->promiser, edcontext->filename);
780-
result = PromiseResultUpdate(result, PROMISE_RESULT_INTERRUPTED);
781-
return result;
756+
return HandleSelectRegionFailure(ctx, pp, &a, edcontext, "line insertion");
782757
}
783758

784759
if (!end_ptr && a.region.select_end && !a.region.select_end_match_eof)
@@ -939,6 +914,60 @@ If no such region matches, begin_ptr and end_ptr should point to NULL
939914

940915
/*****************************************************************************/
941916

917+
static PromiseResult HandleSelectRegionFailure(EvalContext *ctx, const Promise *pp,
918+
const Attributes *a, EditContext *edcontext,
919+
const char *operation_type)
920+
/*
921+
Common error handling for SelectRegion failures across multiple edit operations.
922+
Returns appropriate PromiseResult based on whether we're in final pass or not.
923+
Special handling for line deletions where missing region markers may be intended.
924+
*/
925+
{
926+
assert(pp != NULL);
927+
assert(a != NULL);
928+
assert(edcontext != NULL);
929+
assert(operation_type != NULL);
930+
931+
if (!EDIT_CONTEXT_IS_FINAL_PASS(edcontext))
932+
{
933+
int remaining_passes = (CF_DONEPASSES - 1) - edcontext->pass;
934+
Log(LOG_LEVEL_VERBOSE,
935+
"The promised %s '%s' could not select edit region in '%s' (pass %d/%d, %d more %s to try)",
936+
operation_type, pp->promiser, edcontext->filename,
937+
edcontext->pass, CF_DONEPASSES - 1, remaining_passes,
938+
remaining_passes == 1 ? "pass" : "passes");
939+
return PROMISE_RESULT_NOOP; // Allow retry in next pass
940+
}
941+
942+
// Special case for line deletions: missing markers might be intentional
943+
if (StringEqual(operation_type, "line deletion"))
944+
{
945+
if (a->region.include_end || a->region.include_start)
946+
{
947+
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a,
948+
"The promised %s '%s' could not select an edit region in '%s' after %d passes"
949+
" (this is a good thing, as policy suggests deleting the markers)",
950+
operation_type, pp->promiser, edcontext->filename, CF_DONEPASSES - 1);
951+
}
952+
else
953+
{
954+
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a,
955+
"The promised %s '%s' could not select an edit region in '%s' after %d passes"
956+
" (but the delimiters were expected in the file)",
957+
operation_type, pp->promiser, edcontext->filename, CF_DONEPASSES - 1);
958+
}
959+
return PROMISE_RESULT_INTERRUPTED;
960+
}
961+
962+
// Standard error for final pass
963+
cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a,
964+
"The promised %s '%s' could not select an edit region in '%s' after %d passes",
965+
operation_type, pp->promiser, edcontext->filename, CF_DONEPASSES - 1);
966+
return PROMISE_RESULT_INTERRUPTED;
967+
}
968+
969+
/*****************************************************************************/
970+
942971
static int MatchRegion(EvalContext *ctx, const char *chunk, const Item *begin, const Item *end, bool regex)
943972
/*
944973
Match a region in between the selection delimiters. It is
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
body file control
2+
{
3+
inputs => {
4+
"../../default.cf.sub",
5+
};
6+
}
7+
8+
bundle agent __main__
9+
{
10+
methods:
11+
"bundlesequence" usebundle => default("$(this.promise_filename)");
12+
}
13+
14+
bundle agent init
15+
{
16+
files:
17+
"$(G.testfile)"
18+
delete => init_delete;
19+
}
20+
21+
body delete init_delete
22+
{
23+
dirlinks => "delete";
24+
rmdirs => "true";
25+
}
26+
27+
bundle agent test
28+
{
29+
meta:
30+
"description"
31+
string => "Test that select_region converges across multiple edit_line passes when the region is created in an earlier pass",
32+
meta => { "CFE-3866" };
33+
34+
files:
35+
"$(G.testfile)"
36+
create => "true",
37+
edit_line => insert_section_then_add_content;
38+
}
39+
40+
bundle edit_line insert_section_then_add_content
41+
{
42+
# First promise: Create the section header
43+
insert_lines:
44+
"[section]"
45+
location => start;
46+
47+
# Second promise: Add content within the section using select_region
48+
# This should converge even though the section didn't exist when
49+
# select_region was first evaluated (pass 1)
50+
51+
insert_lines:
52+
"key=value"
53+
location => append,
54+
select_region => section_region;
55+
}
56+
57+
body location append
58+
{
59+
before_after => "after";
60+
}
61+
62+
body select_region section_region
63+
{
64+
select_start => "^\[section\]";
65+
include_start_delimiter => "true";
66+
select_end => "^\[.*\]";
67+
select_end_match_eof => "true";
68+
}
69+
70+
bundle agent check
71+
{
72+
vars:
73+
"expected"
74+
string => "[section]
75+
key=value
76+
";
77+
78+
"actual"
79+
string => readfile("$(G.testfile)", "1000");
80+
81+
classes:
82+
"ok" expression => strcmp("$(expected)", "$(actual)");
83+
84+
reports:
85+
DEBUG::
86+
"Expected: '$(expected)'";
87+
"Actual: '$(actual)'";
88+
89+
ok::
90+
"$(this.promise_filename) Pass";
91+
!ok::
92+
"$(this.promise_filename) FAIL";
93+
}

0 commit comments

Comments
 (0)