Skip to content

ENT-10961, CFE-1840: Files promise can now modify immutable bit in file system attributes #5752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 38 commits into from

Conversation

larsewi
Copy link
Contributor

@larsewi larsewi commented Apr 1, 2025

TODO:

  • handle content attribute
  • handle copy_from attribute
  • handle delete attribute
  • handle edit_line attribute
  • handle edit_xml attribute
  • handle perms attribute
  • handle touch attribute
  • handle edit_template attribute
  • handle acl attribute
  • handle transformer attribute
  • handle rename attribute

Is there any other ones that I've missed?

Build Status

@cf-bottom
Copy link

Thank you for submitting a PR! Maybe @craigcomstock can review this?

@larsewi larsewi force-pushed the override-immutable branch from bdd1918 to 69b1f52 Compare April 3, 2025 14:26
@larsewi larsewi force-pushed the override-immutable branch 5 times, most recently from 06b3676 to 578286c Compare April 4, 2025 15:13
@nickanderson
Copy link
Member

nickanderson commented Apr 8, 2025

MMM, the transformer attribute comes to mind.

  bundle agent __main__
  {
    files:
        "/tmp/example.txt"
          content => "Hello World";
  
        "/tmp/example.txt"
          transformer => "/usr/bin/gzip $(this.promiser)";
  
    reports:
        "$(with)" with => join( ",", lsdir( "/tmp", "example.*", "true" ));
  }

Looking at the files promise docs I imagine that create would also be affected:

bundle agent __main__
{
  vars:
      "check_file" string => "/tmp/check-file.txt";

  files:
      "$(check_file)"
        handle => "create_file",
        create => "true",
        if => "file_absent_report_kept";

   reports:
      "$(check_file) does not exist"
        classes => results( "namespace", "file_absent_report" );

      "$(check_file) was repaired"
        depends_on => { "create_file" };

      "$(check_file) exists"
        if => fileexists( "/tmp/check-file.txt" );
}
R: /tmp/check-file.txt does not exist
R: /tmp/check-file.txt was repaired
R: /tmp/check-file.txt exists

And the delete attribute would seem to be affected as well.

And rename too

bundle agent __main__
{
  files:
    "/tmp/example.txt"
      content => "Disable me!";

    "/tmp/example.txt"
      rename => default:disable;
}
# For reference:
# body rename disable
# # @brief Disable the file
# {
#         disable => "true";
# }
   info: Created file '/tmp/example.txt', mode 0600
   info: Updated file '/tmp/example.txt' with content 'Disable me!'
warning: File object '/tmp/example.txt' exists, contrary to promise
   info: Changed permissions of '/tmp/example.txt' to 'mode 0600'
   info: Disabled file '/tmp/example.txt' by renaming to '/tmp/example.txt.cfdisabled' with mode 0600

I added them to the description.

@larsewi larsewi force-pushed the override-immutable branch 6 times, most recently from f82867d to 2f16047 Compare May 27, 2025 09:10
@larsewi larsewi force-pushed the override-immutable branch 4 times, most recently from 7b81817 to 50ad347 Compare May 30, 2025 13:28
@larsewi larsewi marked this pull request as ready for review May 30, 2025 15:08
@larsewi larsewi force-pushed the override-immutable branch from 50ad347 to 2eec801 Compare June 2, 2025 07:54
@larsewi
Copy link
Contributor Author

larsewi commented Jun 2, 2025

@cf-bottom Jenkins with exotics please :)

@cf-bottom
Copy link

@larsewi larsewi force-pushed the override-immutable branch 3 times, most recently from b1c4763 to 0559bfe Compare June 3, 2025 09:20
larsewi added 19 commits June 13, 2025 12:17
The acl attribute of the files promise can now override the immutable
bit.

Ticket: ENT-10961, CFE-1840
Changelog: Commit
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: CFE-4529
Changelog: Title
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: CFE-4529
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
Ticket: ENT-10961, CFE-1840
Signed-off-by: Lars Erik Wik <[email protected]>
@larsewi larsewi force-pushed the override-immutable branch from 13f8333 to 7dc51ff Compare June 13, 2025 10:17
@cf-bottom
Copy link

cf-bottom commented Jun 13, 2025

Copy link
Contributor

@craigcomstock craigcomstock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! A high-level question about atomicity and maybe one typo. :)

}

/* We'll match the original file permissions on commit */
if (!CopyRegularFileDiskPerms(orig, copy, 0600))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should change the orig file to immutable just before this copy. Not 100% atomic but pretty close so that between this Begin() and the Commit() no one else can "easily" modify the file without also modifying the immutable bit of course.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is a good idea. Let's discuss in digital F2F format :)

* @return false in case of failure
* @note The immutable bit is reset to it's original state
*/
bool OverrideImmutableCommit(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it seems that we should have something like:

OverrideImmutableBegin(struct utimbuf *times) to capture the modified/access times when we begin the "transaction"

Also, during this Begin() we should make the file immutable to attempt to restrict access as much as possible while we do "work" and before we call Commit().

And then OverrideImmutableCommit() takes that as a parameter and if the original file has changed mod/access since Begin() we should fail to commit saying that the file changed inside of the window of time of the transaction to modify the immutable file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, during this Begin() we should make the file immutable to attempt to restrict access as much as possible while we do "work" and before we call Commit().

If we make the file immutable while another process is writing it, we can maybe cause it to be left in an inconsistent state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this very quickly is becoming like the yak shaving. The task is to implement immutable bit support. Not to make CFEngine file operations atomic (they never have been). I created a value-add project CFE-4551 to try to achieve this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All CFEngine file operations are supposed to be atomic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All CFEngine file operations are supposed to be atomic.

They unfortunately are not. However, this code uses the same attempt to achieve "atomic" file operations as before. I.e., write changes to a temporary file and swap it with the original.


if (a.havefsattrs && a.fsattrs.haveimmutable && !a.fsattrs.immutable)
{
/* Here we only handle the clearing of the immutable the immutable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* Here we only handle the clearing of the immutable the immutable
/* Here we only handle the clearing of the immutable


/* 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 when ever needed. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* by temporarily clearing it when ever needed. */
* by temporarily clearing it whenever needed. */

static const FnCallArg GET_ACLS_ARGS[] =
{
{CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Path to file or directory"},
{"default,access", CF_DATA_TYPE_OPTION, "Whether to get default or access ACL"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is our "menu" item list for an attribute right? Is this required and if not, defaulted to "default"? I will probably find out later in the review. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If me make it optional and default to one of them, then we cannot add any more arguments in the future. Maybe this is OK?

Also it does not make sense to default to "default", since "default" only works on directories. "default" is like a template ACL that is inherited by all child objects.

@vpodzime
Copy link
Contributor

olehermanse requested your review on this pull request.

😮‍💨 😅

@olehermanse olehermanse removed their request for review June 27, 2025 15:06
@olehermanse
Copy link
Member

😮‍💨 😅

@vpodzime I heard you like file systems and C code :)

Copy link
Contributor

@vpodzime vpodzime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me in general. Three things that I'd change:

  • split the GetACL() stuff into a separate PR
  • do not add anything to EvalContext it's already in the attributes, a macro/inline function to get the boolean should do
  • git reset BEFORE_ALL_THESE_CHANGES and then add the final version of the utility functions as one commit and then one or multiple commits for supporting the set_immutable in all the attributes

@@ -406,7 +406,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi
/* 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 when ever needed. */
const bool override_immutable = a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable;
EvalContextOverrideImmutableSet(ctx, a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already in the attributes and we do pass those into gazillions functions already, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not passed everywhere. We also would need to pass the is_immutable variable each function.

@@ -2000,7 +2000,7 @@ static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr,

if (!IsExecutable(CommandArg0(BufferData(command))))
{
RecordFailure(ctx, pp, attr, "Transformer '%s' for file '%s' failed", attr->transformer, file);
RecordFailure(ctx, pp, attr, " '%s' for file '%s' failed", attr->transformer, file);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I don't know how that happened. Maybe some typos in normal mode 😆

@larsewi
Copy link
Contributor Author

larsewi commented Jul 1, 2025

Superseded by #5833

@larsewi larsewi closed this Jul 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants