From 36e88005e62d934be0e0e2d3dd43ce33eaef1c4d Mon Sep 17 00:00:00 2001 From: Nick Anderson Date: Wed, 1 Jul 2026 14:11:09 -0500 Subject: [PATCH 1/5] test: add acceptance test for CFE-2663 (empty trailing region) An insert_lines promise using select_region with select_end_match_eof cannot select the region of the final section when that section header is the last line of the file. The region is empty and sits at the end of the file, SelectRegion() rejects it, and the insertion fails with "could not select an edit region". This test fails today and will pass once SelectRegion() honors select_end_match_eof for an empty region at end of file. Ticket: CFE-2663 Changelog: None Signed-off-by: Nick Anderson (cherry picked from commit ebf30a2e8a6cb7507c15b71e1fd73c246705225c) --- tests/acceptance/31_tickets/CFE-2663/test.cf | 81 ++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/acceptance/31_tickets/CFE-2663/test.cf diff --git a/tests/acceptance/31_tickets/CFE-2663/test.cf b/tests/acceptance/31_tickets/CFE-2663/test.cf new file mode 100644 index 0000000000..ffcf713441 --- /dev/null +++ b/tests/acceptance/31_tickets/CFE-2663/test.cf @@ -0,0 +1,81 @@ +body file control +{ + inputs => { "../../default.sub.cf" }; +} + +bundle agent __main__ +{ + methods: + "bundlesequence" usebundle => default("$(this.promise_filename)"); +} + +bundle agent init +{ + vars: + # No trailing newline in the strings, so the "[section2]" header is + # genuinely the last line of the "actual" file. The region selected for + # section2 is therefore empty and sits at the end of the file. + "actual" string => "[section] +keyone=valueone +[section2]"; + + "expected" string => "[section] +keyone=valueone +[section2] +keytwo=valuetwo"; + + "files" slist => { "actual", "expected" }; + + files: + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; +} + +bundle edit_line init_insert(str) +{ + insert_lines: + "$(str)"; +} + +body edit_defaults init_empty +{ + empty_file_before_editing => "true"; +} + +bundle agent test +{ + meta: + "description" + string => "Insertion into an empty trailing region (the select_start delimiter is the last line of the file) must succeed when select_end_match_eof is true", + meta => { "CFE-2663" }; + + files: + "$(G.testfile).actual" + edit_line => insert_into_trailing_section; +} + +bundle edit_line insert_into_trailing_section +{ + insert_lines: + "keytwo=valuetwo" + select_region => ini_section("section2"); +} + +body select_region ini_section(x) +# @brief Restrict the edit to the lines in section [x], matching to EOF when +# [x] is the final section in the file. +{ + select_start => "\[$(x)\]\s*"; + select_end => "\[.*\]\s*"; + select_end_match_eof => "true"; +} + +bundle agent check +{ + methods: + "any" usebundle => dcs_check_diff("$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)"); +} From 24eb14c0b1b1864ca6b8e203d3e25af0f480eb4f Mon Sep 17 00:00:00 2001 From: Nick Anderson Date: Wed, 1 Jul 2026 17:05:56 -0500 Subject: [PATCH 2/5] Fixed select_region failing to select an empty region at end of file When select_region matched a start delimiter on the last line of the file and include_start_delimiter was false, SelectRegion() rejected the region as an "empty region at the end of file" and the edit failed with "could not select an edit region". This happened even when select_end_match_eof was true, which is meant to let the region extend to the end of the file. Now the empty region at end of file is accepted when select_end_match_eof is set, so a trailing section (its header being the last line of the file) can still be populated by insert_lines and similar promises. Ticket: CFE-2663 Changelog: Title Signed-off-by: Nick Anderson (cherry picked from commit c7bcc4452e2d2b98e6fd04819182c2ed798937ee) --- cf-agent/files_editline.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cf-agent/files_editline.c b/cf-agent/files_editline.c index 1e25c96c42..2676ff9baf 100644 --- a/cf-agent/files_editline.c +++ b/cf-agent/files_editline.c @@ -869,7 +869,13 @@ If no such region matches, begin_ptr and end_ptr should point to NULL { if (!include_start) { - if (ip->next == NULL) + /* The start delimiter is the last line of the file, so the + * region (which excludes the delimiter) is empty and sits + * at end of file. Reject it only when select_end_match_eof + * is not set. Otherwise the empty region at EOF is a valid, + * selectable region so that e.g. a trailing INI section can + * still be populated (CFE-2663). */ + if (ip->next == NULL && !a->region.select_end_match_eof) { Log(LOG_LEVEL_VERBOSE, "The promised start pattern '%s' found an empty region at the end of file '%s'", From 5eb700ba39fb6848c1684ec2856a3bd07e349947 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Fri, 10 Jul 2026 12:41:27 +0200 Subject: [PATCH 3/5] Fix format on CFE-2663/test.cf Signed-off-by: Victor Moene (cherry picked from commit ff805dd1225cf5216a6fd3526c662bbdebeea271) --- tests/acceptance/31_tickets/CFE-2663/test.cf | 53 ++++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/tests/acceptance/31_tickets/CFE-2663/test.cf b/tests/acceptance/31_tickets/CFE-2663/test.cf index ffcf713441..27300ef480 100644 --- a/tests/acceptance/31_tickets/CFE-2663/test.cf +++ b/tests/acceptance/31_tickets/CFE-2663/test.cf @@ -12,70 +12,69 @@ bundle agent __main__ bundle agent init { vars: - # No trailing newline in the strings, so the "[section2]" header is - # genuinely the last line of the "actual" file. The region selected for - # section2 is therefore empty and sits at the end of the file. - "actual" string => "[section] + # No trailing newline in the strings, so the "[section2]" header is + # genuinely the last line of the "actual" file. The region selected for + # section2 is therefore empty and sits at the end of the file. + "actual" string => "[section] keyone=valueone [section2]"; - - "expected" string => "[section] + "expected" string => "[section] keyone=valueone [section2] keytwo=valuetwo"; - - "files" slist => { "actual", "expected" }; + "files" slist => { "actual", "expected" }; files: - "$(G.testfile).$(files)" - create => "true", - edit_line => init_insert("$(init.$(files))"), - edit_defaults => init_empty; + "$(G.testfile).$(files)" + create => "true", + edit_line => init_insert("$(init.$(files))"), + edit_defaults => init_empty; } bundle edit_line init_insert(str) { insert_lines: - "$(str)"; + "$(str)"; } body edit_defaults init_empty { - empty_file_before_editing => "true"; + empty_file_before_editing => "true"; } bundle agent test { meta: - "description" - string => "Insertion into an empty trailing region (the select_start delimiter is the last line of the file) must succeed when select_end_match_eof is true", - meta => { "CFE-2663" }; + "description" + string => "Insertion into an empty trailing region (the select_start delimiter is the last line of the file) must succeed when select_end_match_eof is true", + meta => { "CFE-2663" }; files: - "$(G.testfile).actual" - edit_line => insert_into_trailing_section; + "$(G.testfile).actual" edit_line => insert_into_trailing_section; } bundle edit_line insert_into_trailing_section { insert_lines: - "keytwo=valuetwo" - select_region => ini_section("section2"); + "keytwo=valuetwo" select_region => ini_section("section2"); } body select_region ini_section(x) # @brief Restrict the edit to the lines in section [x], matching to EOF when # [x] is the final section in the file. { - select_start => "\[$(x)\]\s*"; - select_end => "\[.*\]\s*"; - select_end_match_eof => "true"; + select_start => "\[$(x)\]\s*"; + select_end => "\[.*\]\s*"; + select_end_match_eof => "true"; } bundle agent check { methods: - "any" usebundle => dcs_check_diff("$(G.testfile).actual", - "$(G.testfile).expected", - "$(this.promise_filename)"); + "any" + usebundle => dcs_check_diff( + "$(G.testfile).actual", + "$(G.testfile).expected", + "$(this.promise_filename)" + ); } From 12569da22050337d79d40e4b6c71b9247ed46f8c Mon Sep 17 00:00:00 2001 From: Nick Anderson Date: Fri, 10 Jul 2026 20:52:04 -0500 Subject: [PATCH 4/5] Bump libntech from `944af92` to `11be681` Pulls in the macOS build fixes from ENT-14208: the deprecated readdir_r use in unix_dir.c (PR #280) and the switch to __APPLE__ instead of DARWIN (PR #281). This unblocks the macOS build for the CFE-2663 backport. Stops deliberately at the ent-14208 merge, short of the CFE-3629 compiler-flag rework (libntech 257982d). That rework enables -Wgnu-folding-constant as an error, which breaks the pre-existing VLA in libpromises/evalfunction.c (char key[key_len]) that master has since removed but these backport branches still carry. Ticket: ENT-14208 Changelog: None Signed-off-by: Nick Anderson --- libntech | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libntech b/libntech index 944af926a7..11be6817ae 160000 --- a/libntech +++ b/libntech @@ -1 +1 @@ -Subproject commit 944af926a78c2410075419145510c4f4beb2901d +Subproject commit 11be6817ae8067aa3a1626a3871deb872093e0e2 From 1fb10386bc7e6e8897eb91f3c8b07601f063f527 Mon Sep 17 00:00:00 2001 From: Craig Comstock Date: Wed, 24 Jun 2026 10:49:50 -0500 Subject: [PATCH 5/5] Refactored variable length array code to use alloca() While VLAs are supported in C99 and C11, they do pose some portability issues so best to avoid their use. alloca() allocates on the stack frame and is free'd on exit from the function so essentially equivalent. Ticket: ENT-14208 Changelog: none (cherry picked from commit 6e952013b0c0d475102ec48c2907025d1aa9a629) --- cf-serverd/server_tls.c | 4 ++-- libpromises/evalfunction.c | 5 ++--- libpromises/ornaments.c | 2 +- libpromises/syslog_client.c | 5 +++-- tests/acceptance/mock_package_manager.c | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cf-serverd/server_tls.c b/cf-serverd/server_tls.c index edc185237b..10f0794320 100644 --- a/cf-serverd/server_tls.c +++ b/cf-serverd/server_tls.c @@ -226,8 +226,8 @@ bool ServerTLSPeek(ConnectionInfo *conn_info) const int peek_size = CF_INBAND_OFFSET + sizeof("CAUTH"); - char buf[peek_size]; - ssize_t got = recv(ConnectionInfoSocket(conn_info), buf, sizeof(buf), MSG_PEEK); + char *buf = alloca(peek_size); + ssize_t got = recv(ConnectionInfoSocket(conn_info), buf, peek_size, MSG_PEEK); assert(got <= peek_size); if (got < 0) { diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 3ab5ad0251..51fb2a9c7c 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -8528,9 +8528,8 @@ static FnCallResult FnCallClassFilterCsv(EvalContext *ctx, } else { - size_t const key_len = PRINTSIZE(size_t); - char key[key_len]; - xsnprintf(key, key_len, "%zu", i); + char key[PRINTSIZE(size_t)]; + xsnprintf(key, PRINTSIZE(size_t), "%zu", i); JsonObjectAppendString(class_container, key, diff --git a/libpromises/ornaments.c b/libpromises/ornaments.c index 469a6626a5..502137ea1a 100644 --- a/libpromises/ornaments.c +++ b/libpromises/ornaments.c @@ -170,7 +170,7 @@ void PromiseBanner(EvalContext *ctx, const Promise *pp) } const size_t n = 2*CF_MAXFRAGMENT + 3; - char pretty_promise_name[n+1]; + char *pretty_promise_name = alloca(n+1); pretty_promise_name[0] = '\0'; StringAppendAbbreviatedPromise(pretty_promise_name, pp->promiser, n, CF_MAXFRAGMENT); Log(LOG_LEVEL_VERBOSE, "P: Promiser/affected object: '%s'", pretty_promise_name); diff --git a/libpromises/syslog_client.c b/libpromises/syslog_client.c index 0b68048c56..0b55a0cc91 100644 --- a/libpromises/syslog_client.c +++ b/libpromises/syslog_client.c @@ -27,6 +27,8 @@ #include + +#define RFC3164_LENGTH 1024 /* * Set by cf-agent/cf-serverd from body agent/server control. */ @@ -112,8 +114,7 @@ void RemoteSysLog(int log_priority, const char *log_string) } else { - const size_t rfc3164_len = 1024; - char message[rfc3164_len]; + char message[RFC3164_LENGTH]; char timebuffer[26]; pid_t pid = getpid(); diff --git a/tests/acceptance/mock_package_manager.c b/tests/acceptance/mock_package_manager.c index 70ac0d2cd8..dea326bad1 100644 --- a/tests/acceptance/mock_package_manager.c +++ b/tests/acceptance/mock_package_manager.c @@ -8,7 +8,7 @@ static char AVAILABLE_PACKAGES_FILE_NAME[PATH_MAX]; static char INSTALLED_PACKAGES_FILE_NAME[PATH_MAX]; -static const int MAX_PACKAGE_ENTRY_LENGTH = 256; +#define MAX_PACKAGE_ENTRY_LENGTH 256 #define DEFAULT_ARCHITECTURE "x666"