Skip to content

Commit 8d55063

Browse files
authored
Merge pull request cfengine#5833 from larsewi/immut
ENT-10961, CFE-1840: Files promise can now modify immutable bit in file system attributes
2 parents e77400e + cd0bb6e commit 8d55063

30 files changed

+1791
-40
lines changed

cf-agent/acl_posix.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include <rlist.h>
3434
#include <eval_context.h>
3535
#include <unix.h> /* GetGroupID(), GetUserID() */
36+
#include <override_fsattrs.h>
37+
#include <fsattrs.h>
3638

3739
#ifdef HAVE_ACL_H
3840
# include <acl.h>
@@ -389,6 +391,11 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method,
389391
acl_free(acl_text_str);
390392
return false;
391393
}
394+
395+
bool was_immutable = false;
396+
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
397+
FSAttrsResult res = TemporarilyClearImmutableBit(changes_path, override_immutable, &was_immutable);
398+
392399
if ((retv = acl_set_file(changes_path, acl_type, acl_new)) != 0)
393400
{
394401
RecordFailure(ctx, pp, a,
@@ -406,6 +413,8 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method,
406413
}
407414
acl_free(acl_text_str);
408415

416+
ResetTemporarilyClearedImmutableBit(changes_path, override_immutable, res, was_immutable);
417+
409418
RecordChange(ctx, pp, a, "%s ACL on '%s' successfully changed", acl_type_str, file_path);
410419
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
411420
}

cf-agent/files_edit.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c
128128
RecordNoChange(ctx, pp, a, "No edit changes to file '%s' need saving",
129129
ec->filename);
130130
}
131-
else if (SaveItemListAsFile(ec->file_start, ec->changes_filename, a, ec->new_line_mode))
131+
else if (SaveItemListAsFile(ctx, ec->file_start, ec->changes_filename, a, ec->new_line_mode))
132132
{
133133
RecordChange(ctx, pp, a, "Edited file '%s'", ec->filename);
134134
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
@@ -151,7 +151,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c
151151
ec->filename);
152152
}
153153
}
154-
else if (SaveXmlDocAsFile(ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
154+
else if (SaveXmlDocAsFile(ctx, ec->xmldoc, ec->changes_filename, a, ec->new_line_mode))
155155
{
156156
RecordChange(ctx, pp, a, "Edited xml file '%s'", ec->filename);
157157
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
@@ -254,8 +254,8 @@ static bool SaveXmlCallback(const char *dest_filename, void *param,
254254

255255
/*********************************************************************/
256256

257-
bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
257+
bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode)
258258
{
259-
return SaveAsFile(&SaveXmlCallback, doc, file, a, new_line_mode);
259+
return SaveAsFile(ctx, &SaveXmlCallback, doc, file, a, new_line_mode);
260260
}
261261
#endif /* HAVE_LIBXML2 */

cf-agent/files_edit.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec,
6161

6262
#ifdef HAVE_LIBXML2
6363
bool LoadFileAsXmlDoc(xmlDocPtr *doc, const char *file, EditDefaults ed, bool only_checks);
64-
bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file,
64+
bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file,
6565
const Attributes *a, NewLineMode new_line_mode);
6666
#endif
6767

cf-agent/verify_files.c

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
#include <evalfunction.h>
6161
#include <changes_chroot.h> /* PrepareChangesChroot(), RecordFileChangedInChroot() */
6262
#include <cf3.defs.h>
63+
#include <fsattrs.h>
64+
#include <override_fsattrs.h>
6365

6466
static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp);
6567
static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp);
@@ -345,6 +347,67 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
345347
changes_path = chrooted_path;
346348
}
347349

350+
bool is_immutable = false; /* We assume not in case of failure */
351+
FSAttrsResult res = FSAttrsGetImmutableFlag(changes_path, &is_immutable);
352+
if (res != FS_ATTRS_SUCCESS)
353+
{
354+
Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE,
355+
"Failed to get the state of the immutable bit from file '%s': %s",
356+
changes_path, FSAttrsErrorCodeToString(res));
357+
}
358+
359+
if (a.havefsattrs && a.fsattrs.haveimmutable && !a.fsattrs.immutable)
360+
{
361+
/* Here we only handle the clearing of the immutable bit. Later we'll
362+
* handle the setting of the immutable bit. */
363+
if (is_immutable)
364+
{
365+
res = FSAttrsUpdateImmutableFlag(changes_path, false);
366+
switch (res)
367+
{
368+
case FS_ATTRS_SUCCESS:
369+
RecordChange(ctx, pp, &a,
370+
"Cleared the immutable bit on file '%s'",
371+
changes_path);
372+
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
373+
break;
374+
case FS_ATTRS_FAILURE:
375+
RecordFailure(ctx, pp, &a,
376+
"Failed to clear the immutable bit on file '%s'",
377+
changes_path);
378+
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
379+
break;
380+
case FS_ATTRS_NOT_SUPPORTED:
381+
/* We will not treat this as a promise failure because this
382+
* will happen on many platforms and filesystems. Instead we
383+
* will log a verbose message to make it apparent for the
384+
* users. */
385+
Log(LOG_LEVEL_VERBOSE,
386+
"Failed to clear the immutable bit on file '%s': %s",
387+
changes_path, FSAttrsErrorCodeToString(res));
388+
break;
389+
case FS_ATTRS_DOES_NOT_EXIST:
390+
/* File does not exist. Nothing to do really, but let's log a
391+
* debug message for good measures */
392+
Log(LOG_LEVEL_DEBUG,
393+
"Failed to clear the immutable bit on file '%s': %s",
394+
changes_path, FSAttrsErrorCodeToString(res));
395+
break;
396+
}
397+
}
398+
else
399+
{
400+
RecordNoChange(ctx, pp, &a,
401+
"The immutable bit is not set on file '%s' as promised",
402+
changes_path);
403+
}
404+
}
405+
406+
/* If we encounter any promises to mutate the file and the immutable
407+
* attribute in body fsattrs is "true", we will override the immutable bit
408+
* by temporarily clearing it whenever needed. */
409+
EvalContextOverrideImmutableSet(ctx, a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable);
410+
348411
if (lstat(changes_path, &oslb) == -1) /* Careful if the object is a link */
349412
{
350413
if ((a.create) || (a.touch))
@@ -586,6 +649,51 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
586649
}
587650
}
588651

652+
if (a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable)
653+
{
654+
/* Here we only handle the setting of the immutable bit. Previously we
655+
* handled the clearing of the immutable bit. */
656+
if (is_immutable)
657+
{
658+
RecordNoChange(ctx, pp, &a,
659+
"The immutable bit is already set on file '%s' as promised",
660+
changes_path);
661+
}
662+
else
663+
{
664+
res = FSAttrsUpdateImmutableFlag(changes_path, true);
665+
switch (res)
666+
{
667+
case FS_ATTRS_SUCCESS:
668+
Log(LOG_LEVEL_VERBOSE, "Set the immutable bit on file '%s'",
669+
changes_path);
670+
break;
671+
case FS_ATTRS_FAILURE:
672+
/* Things still may be fine as long as the agent does not try to mutate the file */
673+
Log(LOG_LEVEL_VERBOSE,
674+
"Failed to set the immutable bit on file '%s': %s",
675+
changes_path, FSAttrsErrorCodeToString(res));
676+
break;
677+
case FS_ATTRS_NOT_SUPPORTED:
678+
/* We will not treat this as a promise failure because this
679+
* will happen on many platforms and filesystems. Instead we
680+
* will log a verbose message to make it apparent for the
681+
* users. */
682+
Log(LOG_LEVEL_VERBOSE,
683+
"Failed to set the immutable bit on file '%s': %s",
684+
changes_path, FSAttrsErrorCodeToString(res));
685+
break;
686+
case FS_ATTRS_DOES_NOT_EXIST:
687+
/* File does not exist. Nothing to do really, but let's log a
688+
* debug message for good measures */
689+
Log(LOG_LEVEL_DEBUG,
690+
"Failed to set the immutable bit on file '%s': %s",
691+
changes_path, FSAttrsErrorCodeToString(res));
692+
break;
693+
}
694+
}
695+
}
696+
589697
// Once more in case a file has been created as a result of editing or copying
590698

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

605713
exit:
714+
/* Reset this to false before next file promise */
715+
EvalContextOverrideImmutableSet(ctx, false);
716+
606717
free(chrooted_path);
607718
if (AttrHasNoAction(&a))
608719
{
@@ -686,20 +797,31 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path,
686797

687798
if (!HashesMatch(existing_content_digest, promised_content_digest, CF_DEFAULT_DIGEST))
688799
{
800+
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
689801
if (!MakingChanges(ctx, pp, attr, &result,
690802
"update file '%s' with content '%s'",
691803
path, attr->content))
692804
{
693805
return result;
694806
}
695807

696-
FILE *f = safe_fopen(changes_path, "w");
808+
char override_path[PATH_MAX];
809+
if (!OverrideImmutableBegin(changes_path, override_path, sizeof(override_path), override_immutable))
810+
{
811+
RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path);
812+
return PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
813+
}
814+
815+
FILE *f = safe_fopen(override_path, "w");
697816
if (f == NULL)
698817
{
699818
RecordFailure(ctx, pp, attr, "Cannot open file '%s' for writing", path);
819+
OverrideImmutableCommit(changes_path, override_path, override_immutable, true);
700820
return PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
701821
}
702822

823+
bool override_abort = false;
824+
703825
Writer *w = FileWriter(f);
704826
if (WriterWriteLen(w, attr->content, bytes_to_write) == bytes_to_write )
705827
{
@@ -714,9 +836,16 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path,
714836
RecordFailure(ctx, pp, attr,
715837
"Failed to update file '%s' with content '%s'",
716838
path, attr->content);
839+
override_abort = true;
717840
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
718841
}
719842
WriterClose(w);
843+
844+
if (!OverrideImmutableCommit(changes_path, override_path, override_immutable, override_abort))
845+
{
846+
RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path);
847+
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
848+
}
720849
}
721850

722851
return result;
@@ -861,7 +990,7 @@ static PromiseResult RenderTemplateMustache(EvalContext *ctx,
861990
edcontext->filename, message);
862991
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
863992
}
864-
else if (SaveAsFile(SaveBufferCallback, output_buffer,
993+
else if (SaveAsFile(ctx, SaveBufferCallback, output_buffer,
865994
edcontext->changes_filename, attr,
866995
edcontext->new_line_mode))
867996
{

cf-agent/verify_files_utils.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#include <known_dirs.h>
6868
#include <changes_chroot.h> /* PrepareChangesChroot(), RecordFileChangedInChroot() */
6969
#include <unix.h> /* GetGroupName(), GetUserName() */
70+
#include <override_fsattrs.h>
7071

7172
#include <cf-windows-functions.h>
7273

@@ -1925,7 +1926,8 @@ bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, con
19251926
else
19261927
#endif
19271928
{
1928-
if (rename(changes_new, changes_dest) == 0)
1929+
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
1930+
if (OverrideImmutableRename(changes_new, changes_dest, override_immutable))
19291931
{
19301932
RecordChange(ctx, pp, attr, "Moved '%s' to '%s'", new, dest);
19311933
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
@@ -1937,7 +1939,7 @@ bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, con
19371939
dest, GetErrorStr());
19381940
*result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
19391941

1940-
if (backupok && (rename(changes_backup, changes_dest) == 0))
1942+
if (backupok && OverrideImmutableRename(changes_backup, changes_dest, override_immutable))
19411943
{
19421944
RecordChange(ctx, pp, attr, "Restored '%s' from '%s'", dest, backup);
19431945
*result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
@@ -2041,6 +2043,10 @@ static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr,
20412043
}
20422044
}
20432045

2046+
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
2047+
bool was_immutable = false;
2048+
FSAttrsResult res = TemporarilyClearImmutableBit(file, override_immutable, &was_immutable);
2049+
20442050
Log(LOG_LEVEL_INFO, "Transforming '%s' with '%s'", file, command_str);
20452051
if ((pop = cf_popen(changes_command, "r", true)) == NULL)
20462052
{
@@ -2084,6 +2090,8 @@ static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr,
20842090

20852091
transRetcode = cf_pclose(pop);
20862092

2093+
ResetTemporarilyClearedImmutableBit(file, override_immutable, res, was_immutable);
2094+
20872095
if (VerifyCommandRetcode(ctx, transRetcode, attr, pp, result))
20882096
{
20892097
Log(LOG_LEVEL_INFO, "Transformer '%s' => '%s' seemed to work ok", file, command_str);
@@ -2174,10 +2182,10 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat
21742182
{
21752183
if (!FileInRepository(newname))
21762184
{
2177-
if (rename(changes_path, changes_newname) == -1)
2185+
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
2186+
if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable))
21782187
{
2179-
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'. (rename: %s)",
2180-
path, GetErrorStr());
2188+
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path);
21812189
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
21822190
}
21832191
else
@@ -2312,7 +2320,8 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat
23122320

23132321
if (MakingChanges(ctx, pp, attr, &result, "rename file '%s' to '%s'", path, newname))
23142322
{
2315-
if (safe_chmod(changes_path, newperm) == 0)
2323+
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
2324+
if (OverrideImmutableChmod(changes_path, newperm, override_immutable))
23162325
{
23172326
RecordChange(ctx, pp, attr, "Changed permissions of '%s' to 'mode %04jo'",
23182327
path, (uintmax_t)newperm);
@@ -2327,10 +2336,9 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat
23272336

23282337
if (!FileInRepository(newname))
23292338
{
2330-
if (rename(changes_path, changes_newname) == -1)
2339+
if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable))
23312340
{
2332-
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'. (rename: %s)",
2333-
path, GetErrorStr());
2341+
RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path);
23342342
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
23352343
free(chrooted_path);
23362344
return result;
@@ -2417,8 +2425,8 @@ static PromiseResult VerifyDelete(EvalContext *ctx,
24172425
{
24182426
if (!S_ISDIR(sb->st_mode)) /* file,symlink */
24192427
{
2420-
int ret = unlink(lastnode);
2421-
if (ret == -1)
2428+
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
2429+
if (!OverrideImmutableDelete(lastnode, override_immutable))
24222430
{
24232431
RecordFailure(ctx, pp, attr, "Couldn't unlink '%s' tidying. (unlink: %s)",
24242432
path, GetErrorStr());
@@ -2482,15 +2490,16 @@ static PromiseResult TouchFile(EvalContext *ctx, char *path, const Attributes *a
24822490
PromiseResult result = PROMISE_RESULT_NOOP;
24832491
if (MakingChanges(ctx, pp, attr, &result, "update time stamps for '%s'", path))
24842492
{
2485-
if (utime(ToChangesPath(path), NULL) != -1)
2493+
bool override_immutable = EvalContextOverrideImmutableGet(ctx);
2494+
if (OverrideImmutableUtime(ToChangesPath(path), override_immutable, NULL))
24862495
{
24872496
RecordChange(ctx, pp, attr, "Touched (updated time stamps) for path '%s'", path);
24882497
result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
24892498
}
24902499
else
24912500
{
2492-
RecordFailure(ctx, pp, attr, "Touch '%s' failed to update timestamps. (utime: %s)",
2493-
path, GetErrorStr());
2501+
RecordFailure(ctx, pp, attr, "Touch '%s' failed to update timestamps",
2502+
path);
24942503
result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
24952504
}
24962505
}
@@ -2644,7 +2653,8 @@ static PromiseResult VerifyFileAttributes(EvalContext *ctx, const char *file, co
26442653
if (MakingChanges(ctx, pp, attr, &result, "change permissions of '%s' from %04jo to %04jo",
26452654
file, (uintmax_t)dstat->st_mode & 07777, (uintmax_t)newperm & 07777))
26462655
{
2647-
if (safe_chmod(changes_file, newperm & 07777) == -1)
2656+
const bool override_immutable = EvalContextOverrideImmutableGet(ctx);
2657+
if (!OverrideImmutableChmod(changes_file, newperm & 07777, override_immutable))
26482658
{
26492659
RecordFailure(ctx, pp, attr, "Failed to change permissions of '%s'. (chmod: %s)",
26502660
file, GetErrorStr());

libpromises/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ libpromises_la_SOURCES = \
137137
modes.c \
138138
monitoring_read.c monitoring_read.h \
139139
ornaments.c ornaments.h \
140+
override_fsattrs.c override_fsattrs.h \
140141
policy.c policy.h \
141142
parser.c parser.h \
142143
parser_helpers.h \

0 commit comments

Comments
 (0)