Skip to content

Commit d1927f3

Browse files
authored
Merge pull request #6025 from victormlg/replace_pattern
2 parents 0130e1d + 62bf4c9 commit d1927f3

File tree

3 files changed

+150
-18
lines changed

3 files changed

+150
-18
lines changed

cf-agent/files_editline.c

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,12 +1257,32 @@ static bool DeletePromisedLinesMatching(EvalContext *ctx, Item **start, Item *be
12571257

12581258
/********************************************************************/
12591259

1260+
static bool ReplaceBufferSafeWrite(char *line_buff, char *after, Buffer *replace, int start_off, int end_off,
1261+
size_t after_size, size_t line_buff_size)
1262+
{
1263+
int needed;
1264+
// Save portion of line after substitution:
1265+
needed = strlcpy(after, line_buff + end_off, after_size);
1266+
if (needed < 0 || (size_t) needed >= after_size)
1267+
{
1268+
return false;
1269+
}
1270+
needed = snprintf(line_buff + start_off, line_buff_size - start_off,
1271+
"%s%s", BufferData(replace), after);
1272+
if (needed < 0 || (size_t) needed >= (line_buff_size - start_off))
1273+
{
1274+
return false;
1275+
}
1276+
return true;
1277+
}
1278+
12601279
static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end, const Attributes *a,
12611280
const Promise *pp, EditContext *edcontext, PromiseResult *result)
12621281
{
12631282
assert(a != NULL);
12641283
assert(pp != NULL);
12651284
assert(edcontext != NULL);
1285+
bool allow_non_convergent = PromiseGetConstraintAsBoolean(ctx, "allow_non_convergent", pp);
12661286

12671287
char line_buff[CF_EXPANDSIZE];
12681288
char after[CF_BUFSIZE];
@@ -1313,14 +1333,13 @@ static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end,
13131333
Log(LOG_LEVEL_VERBOSE, "Verifying replacement of '%s' with '%s', cutoff %d", pp->promiser, BufferData(replace),
13141334
cutoff);
13151335

1316-
// Save portion of line after substitution:
1317-
strlcpy(after, line_buff + end_off, sizeof(after));
1318-
// TODO: gripe if that truncated !
1319-
1320-
// Substitute into line_buff:
1321-
snprintf(line_buff + start_off, sizeof(line_buff) - start_off,
1322-
"%s%s", BufferData(replace), after);
1323-
// TODO: gripe if that truncated or failed !
1336+
if (!ReplaceBufferSafeWrite(line_buff, after, replace, start_off, end_off, sizeof(after), sizeof(line_buff)))
1337+
{
1338+
RecordInterruption(ctx, pp, a, "Replacement string is too large. '%s' in '%s'",
1339+
a->column.column_separator, edcontext->filename);
1340+
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1341+
break;
1342+
}
13241343
notfound = false;
13251344
replaced = true;
13261345

@@ -1330,17 +1349,40 @@ static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end,
13301349
break;
13311350
}
13321351
}
1333-
1352+
char line_buff_cp[CF_EXPANDSIZE];
13341353
if (NotAnchored(pp->promiser) && BlockTextMatch(ctx, pp->promiser, line_buff, &start_off, &end_off))
13351354
{
1336-
RecordInterruption(ctx, pp, a,
1337-
"Promised replacement '%s' on line '%s' for pattern '%s'"
1338-
" is not convergent while editing '%s'"
1339-
" (regular expression matches the replacement string)",
1340-
line_buff, ip->name, pp->promiser, edcontext->filename);
1341-
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1342-
PromiseRef(LOG_LEVEL_ERR, pp);
1343-
break;
1355+
int needed = strlcpy(line_buff_cp, line_buff, sizeof(line_buff_cp));
1356+
if (needed < 0 || (size_t) needed >= sizeof(line_buff_cp))
1357+
{
1358+
RecordInterruption(ctx, pp, a, "Original string is too large. '%s' in '%s'",
1359+
a->column.column_separator, edcontext->filename);
1360+
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1361+
break;
1362+
}
1363+
1364+
if (!ReplaceBufferSafeWrite(line_buff_cp, after, replace, start_off, end_off, sizeof(after), sizeof(line_buff_cp)))
1365+
{
1366+
RecordInterruption(ctx, pp, a,
1367+
"Promised replacement '%s' on line '%s' for pattern '%s'"
1368+
" is not convergent while editing '%s'"
1369+
" (regular expression matches the replacement string)",
1370+
line_buff, ip->name, pp->promiser, edcontext->filename);
1371+
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1372+
break;
1373+
}
1374+
1375+
if (!allow_non_convergent || (strlen(line_buff) != strlen(line_buff_cp)))
1376+
{
1377+
RecordInterruption(ctx, pp, a,
1378+
"Promised replacement '%s' on line '%s' for pattern '%s'"
1379+
" is not convergent while editing '%s'"
1380+
" (regular expression matches the replacement string)",
1381+
line_buff, ip->name, pp->promiser, edcontext->filename);
1382+
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1383+
PromiseRef(LOG_LEVEL_ERR, pp);
1384+
break;
1385+
}
13441386
}
13451387

13461388
if (!MakingChanges(ctx, pp, a, result, "replace pattern '%s' in '%s'", pp->promiser,
@@ -1366,8 +1408,25 @@ static bool ReplacePatterns(EvalContext *ctx, Item *file_start, Item *file_end,
13661408
break;
13671409
}
13681410

1369-
if (BlockTextMatch(ctx, pp->promiser, ip->name, &start_off, &end_off))
1411+
if (BlockTextMatch(ctx, pp->promiser, ip->name, &start_off, &end_off) && (!allow_non_convergent
1412+
|| (strlen(line_buff) != strlen(line_buff_cp))))
13701413
{
1414+
int needed = strlcpy(line_buff_cp, line_buff, sizeof(line_buff_cp));
1415+
if (needed < 0 || (size_t) needed >= sizeof(line_buff_cp))
1416+
{
1417+
RecordInterruption(ctx, pp, a, "Original string is too large. '%s' in '%s'",
1418+
a->column.column_separator, edcontext->filename);
1419+
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1420+
break;
1421+
}
1422+
1423+
if (!ReplaceBufferSafeWrite(line_buff_cp, after, replace, start_off, end_off, sizeof(after), sizeof(line_buff_cp))) {
1424+
RecordInterruption(ctx, pp, a, "Replacement string is too large. '%s' in '%s'",
1425+
a->column.column_separator, edcontext->filename);
1426+
*result = PromiseResultUpdate(*result, PROMISE_RESULT_INTERRUPTED);
1427+
break;
1428+
}
1429+
13711430
RecordInterruption(ctx, pp, a,
13721431
"Promised replacement '%s' for pattern '%s' is not properly convergent while editing '%s'"
13731432
" (pattern still matches the end-state replacement string '%s', consider use"

libpromises/mod_files.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ static const ConstraintSyntax CF_COLUMN_BODIES[] =
131131
static const ConstraintSyntax CF_REPLACE_BODIES[] =
132132
{
133133
ConstraintSyntaxNewBody("replace_with", &replace_with_body, "Search-replace pattern", SYNTAX_STATUS_NORMAL),
134+
ConstraintSyntaxNewBool("allow_non_convergent", "Allow to use non-convergent regular expressions in replace_patterns. Defaults to false", SYNTAX_STATUS_NORMAL),
134135
ConstraintSyntaxNewNull()
135136
};
136137

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#######################################################
2+
#
3+
# Replace a pattern using non-convergent regexes
4+
#
5+
#######################################################
6+
7+
body common control
8+
{
9+
inputs => { "../../default.cf.sub" };
10+
bundlesequence => { default("$(this.promise_filename)") };
11+
version => "1.0";
12+
}
13+
14+
######################################################
15+
16+
bundle agent init
17+
{
18+
files:
19+
"/tmp/example.txt"
20+
content => "foo PORT=23 bar";
21+
22+
}
23+
24+
######################################################
25+
26+
bundle agent test
27+
{
28+
meta:
29+
"description" -> { "ENT-3417" }
30+
string => "Test that allow_non_convergent correctly replaces non-convergent regex";
31+
32+
files:
33+
"/tmp/example.txt"
34+
edit_line => _regex_replace( "PORT=[0-9]+", "PORT=22" );
35+
}
36+
37+
bundle edit_line _regex_replace(find,replace)
38+
{
39+
replace_patterns:
40+
"$(find)"
41+
replace_with => _value("$(replace)"),
42+
comment => "Search and replace string",
43+
allow_non_convergent => "true";
44+
}
45+
46+
body replace_with _value(x)
47+
{
48+
replace_value => "$(x)";
49+
occurrences => "all";
50+
}
51+
52+
######################################################
53+
54+
bundle agent check
55+
{
56+
vars:
57+
"file_content"
58+
string => readfile( "/tmp/example.txt" , "999" );
59+
60+
classes:
61+
"ok" expression => strcmp("$(file_content)", "foo PORT=22 bar");
62+
63+
files:
64+
"/tmp/example.txt"
65+
delete => tidy;
66+
67+
reports:
68+
ok::
69+
"$(this.promise_filename) Pass";
70+
!ok::
71+
"$(this.promise_filename) FAIL";
72+
}

0 commit comments

Comments
 (0)