Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7ab4abd
Added utility functions to override immutable bit
larsewi Mar 28, 2025
f08eb51
Added body syntax for controlling file system attributes
larsewi Jan 28, 2025
3ff16ba
Files promise can now modify immutable bit in file system attributes
larsewi Feb 6, 2025
019d229
Store override_immutable variable in stack frame
larsewi Jul 1, 2025
d9d9afc
content attribute can now override immutable bit
larsewi Jul 1, 2025
40cfca1
copy_from attribute can now override immutable bit
larsewi Jul 1, 2025
2d786d3
delete attribute can now override immutable bit
larsewi Jul 1, 2025
f9afaec
edit_line and edit_xml attributes can now override immutable bit
larsewi Jul 1, 2025
e1d949c
touch attribute can now override immutable bit
larsewi Jul 1, 2025
5108f93
transformer attribute can now override immutable bit
larsewi Jul 1, 2025
fb78427
rename attribute can now override immutable bit
larsewi Jul 1, 2025
d4e7957
perms attribute can now override immutable bit
larsewi Jul 1, 2025
9f7ba9d
acl attribute can now override immutable bit
larsewi Jul 1, 2025
9ae8e61
evalfunction.c: Removed trailing whitespace
larsewi Jul 1, 2025
752d9c0
Added acceptance test to set immutable bit
larsewi Jun 13, 2025
ad44ed1
Added acceptance test to clear immutable bit
larsewi Jun 13, 2025
96fe049
Added acceptance test for immutable with content
larsewi Jun 13, 2025
d11dd07
Added acceptance test for immutable with copy_from
larsewi Jun 13, 2025
b2d55f9
Added acceptance test for immutable with delete
larsewi Jun 13, 2025
926c684
Added acceptance test for immutable with edit_line
larsewi Jun 13, 2025
a3883f1
Added acceptance test for immutable with edit_xml
larsewi Jun 13, 2025
070fcc5
Added acceptance test for immutable with perms
larsewi Jun 13, 2025
d92d0c0
Added acceptance test for immutable with touch
larsewi Jun 13, 2025
dbf1151
Added acceptance test for immutable with edit_template
larsewi Jun 13, 2025
003e524
Added acceptance test for immutable with acl
larsewi Jun 13, 2025
203ee40
Added acceptance test for immutable with transformer
larsewi Jun 13, 2025
cd0bb6e
Added acceptance test for immutable with rename
larsewi Jun 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cf-agent/acl_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include <rlist.h>
#include <eval_context.h>
#include <unix.h> /* GetGroupID(), GetUserID() */
#include <override_fsattrs.h>
#include <fsattrs.h>

#ifdef HAVE_ACL_H
# include <acl.h>
Expand Down Expand Up @@ -389,6 +391,11 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method,
acl_free(acl_text_str);
return false;
}

bool was_immutable = false;
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
FSAttrsResult res = TemporarilyClearImmutableBit(changes_path, override_immutable, &was_immutable);

if ((retv = acl_set_file(changes_path, acl_type, acl_new)) != 0)
{
RecordFailure(ctx, pp, a,
Expand All @@ -406,6 +413,8 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method,
}
acl_free(acl_text_str);

ResetTemporarilyClearedImmutableBit(changes_path, override_immutable, res, was_immutable);

RecordChange(ctx, pp, a, "%s ACL on '%s' successfully changed", acl_type_str, file_path);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
}
Expand Down
8 changes: 4 additions & 4 deletions cf-agent/files_edit.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c
RecordNoChange(ctx, pp, a, "No edit changes to file '%s' need saving",
ec->filename);
}
else if (SaveItemListAsFile(ec->file_start, ec->changes_filename, a, ec->new_line_mode))
else if (SaveItemListAsFile(ctx, ec->file_start, ec->changes_filename, a, ec->new_line_mode))
{
RecordChange(ctx, pp, a, "Edited file '%s'", ec->filename);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
Expand All @@ -151,7 +151,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c
ec->filename);
}
}
else if (SaveXmlDocAsFile(ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
else if (SaveXmlDocAsFile(ctx, ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
{
RecordChange(ctx, pp, a, "Edited xml file '%s'", ec->filename);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
Expand Down Expand Up @@ -254,8 +254,8 @@ static bool SaveXmlCallback(const char *dest_filename, void *param,

/*********************************************************************/

bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
{
return SaveAsFile(&SaveXmlCallback, doc, file, a, new_line_mode);
return SaveAsFile(ctx, &SaveXmlCallback, doc, file, a, new_line_mode);
}
#endif /* HAVE_LIBXML2 */
2 changes: 1 addition & 1 deletion cf-agent/files_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec,

#ifdef HAVE_LIBXML2
bool LoadFileAsXmlDoc(xmlDocPtr *doc, const char *file, EditDefaults ed, bool only_checks);
bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file,
bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file,
const Attributes *a, NewLineMode new_line_mode);
#endif

Expand Down
133 changes: 131 additions & 2 deletions cf-agent/verify_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
#include <evalfunction.h>
#include <changes_chroot.h> /* PrepareChangesChroot(), RecordFileChangedInChroot() */
#include <cf3.defs.h>
#include <fsattrs.h>
#include <override_fsattrs.h>

static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp);
static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp);
Expand Down Expand Up @@ -345,6 +347,67 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
changes_path = chrooted_path;
}

bool is_immutable = false; /* We assume not in case of failure */
FSAttrsResult res = FSAttrsGetImmutableFlag(changes_path, &is_immutable);
if (res != FS_ATTRS_SUCCESS)
{
Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE,
"Failed to get the state of the immutable bit from file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
}

if (a.havefsattrs && a.fsattrs.haveimmutable && !a.fsattrs.immutable)
{
/* Here we only handle the clearing of the immutable bit. Later we'll
* handle the setting of the immutable bit. */
if (is_immutable)
{
res = FSAttrsUpdateImmutableFlag(changes_path, false);
switch (res)
{
case FS_ATTRS_SUCCESS:
RecordChange(ctx, pp, &a,
"Cleared the immutable bit on file '%s'",
changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
break;
case FS_ATTRS_FAILURE:
RecordFailure(ctx, pp, &a,
"Failed to clear the immutable bit on file '%s'",
changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
break;
case FS_ATTRS_NOT_SUPPORTED:
/* We will not treat this as a promise failure because this
* will happen on many platforms and filesystems. Instead we
* will log a verbose message to make it apparent for the
* users. */
Log(LOG_LEVEL_VERBOSE,
"Failed to clear the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* File does not exist. Nothing to do really, but let's log a
* debug message for good measures */
Log(LOG_LEVEL_DEBUG,
"Failed to clear the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
}
}
else
{
RecordNoChange(ctx, pp, &a,
"The immutable bit is not set on file '%s' as promised",
changes_path);
}
}

/* If we encounter any promises to mutate the file and the immutable
* attribute in body fsattrs is "true", we will override the immutable bit
* by temporarily clearing it whenever needed. */
EvalContextOverrideImmutableSet(ctx, a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable);

if (lstat(changes_path, &oslb) == -1) /* Careful if the object is a link */
{
if ((a.create) || (a.touch))
Expand Down Expand Up @@ -586,6 +649,51 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
}
}

if (a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable)
{
/* Here we only handle the setting of the immutable bit. Previously we
* handled the clearing of the immutable bit. */
if (is_immutable)
{
RecordNoChange(ctx, pp, &a,
"The immutable bit is already set on file '%s' as promised",
changes_path);
}
else
{
res = FSAttrsUpdateImmutableFlag(changes_path, true);
switch (res)
{
case FS_ATTRS_SUCCESS:
Log(LOG_LEVEL_VERBOSE, "Set the immutable bit on file '%s'",
changes_path);
break;
case FS_ATTRS_FAILURE:
/* Things still may be fine as long as the agent does not try to mutate the file */
Log(LOG_LEVEL_VERBOSE,
"Failed to set the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
case FS_ATTRS_NOT_SUPPORTED:
/* We will not treat this as a promise failure because this
* will happen on many platforms and filesystems. Instead we
* will log a verbose message to make it apparent for the
* users. */
Log(LOG_LEVEL_VERBOSE,
"Failed to set the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
case FS_ATTRS_DOES_NOT_EXIST:
/* File does not exist. Nothing to do really, but let's log a
* debug message for good measures */
Log(LOG_LEVEL_DEBUG,
"Failed to set the immutable bit on file '%s': %s",
changes_path, FSAttrsErrorCodeToString(res));
break;
}
}
}

// Once more in case a file has been created as a result of editing or copying

exists = (lstat(changes_path, &osb) != -1);
Expand All @@ -603,6 +711,9 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
}

exit:
/* Reset this to false before next file promise */
EvalContextOverrideImmutableSet(ctx, false);

free(chrooted_path);
if (AttrHasNoAction(&a))
{
Expand Down Expand Up @@ -686,20 +797,31 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path,

if (!HashesMatch(existing_content_digest, promised_content_digest, CF_DEFAULT_DIGEST))
{
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (!MakingChanges(ctx, pp, attr, &result,
"update file '%s' with content '%s'",
path, attr->content))
{
return result;
}

FILE *f = safe_fopen(changes_path, "w");
char override_path[PATH_MAX];
if (!OverrideImmutableBegin(changes_path, override_path, sizeof(override_path), override_immutable))
{
RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path);
return PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}

FILE *f = safe_fopen(override_path, "w");
if (f == NULL)
{
RecordFailure(ctx, pp, attr, "Cannot open file '%s' for writing", path);
OverrideImmutableCommit(changes_path, override_path, override_immutable, true);
return PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}

bool override_abort = false;

Writer *w = FileWriter(f);
if (WriterWriteLen(w, attr->content, bytes_to_write) == bytes_to_write )
{
Expand All @@ -714,9 +836,16 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path,
RecordFailure(ctx, pp, attr,
"Failed to update file '%s' with content '%s'",
path, attr->content);
override_abort = true;
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
WriterClose(w);

if (!OverrideImmutableCommit(changes_path, override_path, override_immutable, override_abort))
{
RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
}

return result;
Expand Down Expand Up @@ -861,7 +990,7 @@ static PromiseResult RenderTemplateMustache(EvalContext *ctx,
edcontext->filename, message);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
else if (SaveAsFile(SaveBufferCallback, output_buffer,
else if (SaveAsFile(ctx, SaveBufferCallback, output_buffer,
edcontext->changes_filename, attr,
edcontext->new_line_mode))
{
Expand Down
40 changes: 25 additions & 15 deletions cf-agent/verify_files_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
#include <known_dirs.h>
#include <changes_chroot.h> /* PrepareChangesChroot(), RecordFileChangedInChroot() */
#include <unix.h> /* GetGroupName(), GetUserName() */
#include <override_fsattrs.h>

#include <cf-windows-functions.h>

Expand Down Expand Up @@ -1925,7 +1926,8 @@
else
#endif
{
if (rename(changes_new, changes_dest) == 0)
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (OverrideImmutableRename(changes_new, changes_dest, override_immutable))
{
RecordChange(ctx, pp, attr, "Moved '%s' to '%s'", new, dest);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
Expand All @@ -1937,7 +1939,7 @@
dest, GetErrorStr());
*result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);

if (backupok && (rename(changes_backup, changes_dest) == 0))
if (backupok && OverrideImmutableRename(changes_backup, changes_dest, override_immutable))
{
RecordChange(ctx, pp, attr, "Restored '%s' from '%s'", dest, backup);
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
Expand Down Expand Up @@ -2041,6 +2043,10 @@
}
}

const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
bool was_immutable = false;
FSAttrsResult res = TemporarilyClearImmutableBit(file, override_immutable, &was_immutable);

Log(LOG_LEVEL_INFO, "Transforming '%s' with '%s'", file, command_str);
if ((pop = cf_popen(changes_command, "r", true)) == NULL)
{
Expand All @@ -2058,7 +2064,7 @@

for (;;)
{
ssize_t res = CfReadLine(&line, &line_size, pop);

Check notice

Code scanning / CodeQL

Declaration hides variable Note

Variable res hides another variable of the same name (on
line 2076
).
if (res == -1)
{
if (!feof(pop))
Expand All @@ -2084,6 +2090,8 @@

transRetcode = cf_pclose(pop);

ResetTemporarilyClearedImmutableBit(file, override_immutable, res, was_immutable);

if (VerifyCommandRetcode(ctx, transRetcode, attr, pp, result))
{
Log(LOG_LEVEL_INFO, "Transformer '%s' => '%s' seemed to work ok", file, command_str);
Expand Down Expand Up @@ -2174,10 +2182,10 @@
{
if (!FileInRepository(newname))
{
if (rename(changes_path, changes_newname) == -1)
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable))
{
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'. (rename: %s)",
path, GetErrorStr());
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
else
Expand Down Expand Up @@ -2312,7 +2320,8 @@

if (MakingChanges(ctx, pp, attr, &result, "rename file '%s' to '%s'", path, newname))
{
if (safe_chmod(changes_path, newperm) == 0)
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (OverrideImmutableChmod(changes_path, newperm, override_immutable))
{
RecordChange(ctx, pp, attr, "Changed permissions of '%s' to 'mode %04jo'",
path, (uintmax_t)newperm);
Expand All @@ -2327,10 +2336,9 @@

if (!FileInRepository(newname))
{
if (rename(changes_path, changes_newname) == -1)
if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable))
{
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'. (rename: %s)",
path, GetErrorStr());
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path);
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
free(chrooted_path);
return result;
Expand Down Expand Up @@ -2417,8 +2425,8 @@
{
if (!S_ISDIR(sb->st_mode)) /* file,symlink */
{
int ret = unlink(lastnode);
if (ret == -1)
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (!OverrideImmutableDelete(lastnode, override_immutable))
{
RecordFailure(ctx, pp, attr, "Couldn't unlink '%s' tidying. (unlink: %s)",
path, GetErrorStr());
Expand Down Expand Up @@ -2482,15 +2490,16 @@
PromiseResult result = PROMISE_RESULT_NOOP;
if (MakingChanges(ctx, pp, attr, &result, "update time stamps for '%s'", path))
{
if (utime(ToChangesPath(path), NULL) != -1)
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (OverrideImmutableUtime(ToChangesPath(path), override_immutable, NULL))
{
RecordChange(ctx, pp, attr, "Touched (updated time stamps) for path '%s'", path);
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
}
else
{
RecordFailure(ctx, pp, attr, "Touch '%s' failed to update timestamps. (utime: %s)",
path, GetErrorStr());
RecordFailure(ctx, pp, attr, "Touch '%s' failed to update timestamps",
path);
Comment thread
larsewi marked this conversation as resolved.
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
}
}
Expand Down Expand Up @@ -2644,7 +2653,8 @@
if (MakingChanges(ctx, pp, attr, &result, "change permissions of '%s' from %04jo to %04jo",
file, (uintmax_t)dstat->st_mode & 07777, (uintmax_t)newperm & 07777))
{
if (safe_chmod(changes_file, newperm & 07777) == -1)
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
if (!OverrideImmutableChmod(changes_file, newperm & 07777, override_immutable))
{
RecordFailure(ctx, pp, attr, "Failed to change permissions of '%s'. (chmod: %s)",
file, GetErrorStr());
Expand Down
1 change: 1 addition & 0 deletions libpromises/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ libpromises_la_SOURCES = \
modes.c \
monitoring_read.c monitoring_read.h \
ornaments.c ornaments.h \
override_fsattrs.c override_fsattrs.h \
policy.c policy.h \
parser.c parser.h \
parser_helpers.h \
Expand Down
Loading
Loading