Repository: ansible-collections/community.general

Issue:
kernel_blacklist - Error while setting attributes: /tmp/xyz: Operation not supported
### Summary

I wanted to blacklist some kernel modules using the ready-made kernel_blacklist in Ansible.

My sample code:
```yaml
- name: Remove potentially affected (and unused) modules
  community.general.kernel_blacklist:
    name: "{{ line_item }}"
    state: present
    blacklist_file: "/etc/modprobe.d/{{ line_item }}-blacklist.conf"
  with_items:
    - cifs
    - cls_rsvp
  loop_control:
    loop_var: line_item
```

As I understand it, the module should create such a file and add content to it.

As a result, when you run the playbook, the files are created but their contents are empty:
```bash
$ find /etc/modprobe.d/ -size 0 -ls
  6030631      0 -rw-r--r--   1 root     root            0 paź  7 12:19 /etc/modprobe.d/cls_rsvp-blacklist.conf
  6029638      0 -rw-r--r--   1 root     root            0 paź  7 12:19 /etc/modprobe.d/cifs-blacklist.conf
```

Additionally, when launching the playbook, I receive a failure message. Below traceback ( -vvv)
```
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_community.general.kernel_blacklist_payload_rk2m8l96/ansible_community.general.kernel_blacklist_payload.zip/ansible/module_utils/basic.py", line 1003, in set_attributes_if_different
    raise Exception("Error while setting attributes: %s" % (out + err))
Exception: Error while setting attributes: /tmp/tmpnholykn5: Operation not supported

failed: [local] (item=cls_rsvp) => {
    "ansible_loop_var": "line_item",
    "changed": false,
    "details": "Error while setting attributes: /tmp/tmpnholykn5: Operation not supported\n",
    "gid": 0,
    "group": "root",
    "invocation": {
        "module_args": {
            "blacklist_file": "/etc/modprobe.d/cls_rsvp-blacklist.conf",
            "name": "cls_rsvp",
            "state": "present"
        }
    },
    "line_item": "cls_rsvp",
    "mode": "0644",
    "msg": "chattr failed",
    "owner": "root",
    "path": "/tmp/tmpnholykn5",
    "size": 0,
    "state": "file",
    "uid": 0
}
```

I don't know why module display `Error while setting attributes: /tmp/tmpwn_d8ybv: Operation not supported`. 
In bash shell work perfect:
```bash
# rm -fv /etc/modprobe.d/cifs-blacklist.conf
removed '/etc/modprobe.d/cifs-blacklist.conf'
# touch /etc/modprobe.d/cifs-blacklist.conf
# echo 'test test' > /etc/modprobe.d/cifs-blacklist.conf
```

### Issue Type

Bug Report

### Component Name

kernel_blacklist

### Ansible Version

```bash
$ ansible --version
ansible [core 2.14.10]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/bkida/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.11/site-packages/ansible
  ansible collection location = /home/bkida/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.11.5 (main, Aug 28 2023, 00:00:00) [GCC 13.2.1 20230728 (Red Hat 13.2.1-1)] (/usr/bin/python3)
  jinja version = 3.0.3
  libyaml = True
```


### Community.general Version

```bash
$ ansible-galaxy collection list community.general

# /usr/lib/python3.11/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 6.6.2  

# /usr/share/ansible/collections/ansible_collections
Collection        Version
----------------- -------
community.general 6.6.0  

```


### Configuration

```bash
$ ansible-config dump --only-changed
CONFIG_FILE() = /etc/ansible/ansible.cfg
```


### OS / Environment

Fedora 38

### Steps to Reproduce

Copy example code from summary description and run on the same ansible / community.general version

### Expected Results

Working module ;)

### Actual Results

`Error while setting attributes: /tmp/tmpwn_d8ybv: Operation not supported`

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/kernel_blacklist.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/kernel_blacklist.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @matze
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
The module copies the current file to a temporary place (while preserving permissions) and atomically moves it back.

The "preserving permissions" part of the first copying step seems to fail.

This extra copying step was added in 2ad7ed4f83701b2919e287de0cd50b17884371de, more specifically in https://github.com/ansible-collections/community.general/pull/3329/commits/b42ca595ec85aaf5e71d0abfe5bfbfc019097777. @russoz do you remember why you did it that way?
@felixfontein TBH, not really. But I found a better way to do it.


Resolving patch:
diff --git a/plugins/modules/kernel_blacklist.py b/plugins/modules/kernel_blacklist.py
--- a/plugins/modules/kernel_blacklist.py
+++ b/plugins/modules/kernel_blacklist.py
@@ -53,7 +53,6 @@
 
 import os
 import re
-import tempfile
 
 from ansible_collections.community.general.plugins.module_utils.module_helper import StateModuleHelper
 
@@ -106,16 +105,10 @@ def state_present(self):
 
     def __quit_module__(self):
         if self.has_changed() and not self.module.check_mode:
-            dummy, tmpfile = tempfile.mkstemp()
-            try:
-                os.remove(tmpfile)
-                self.module.preserved_copy(self.vars.filename, tmpfile)  # ensure right perms/ownership
-                with open(tmpfile, 'w') as fd:
-                    fd.writelines(["{0}\n".format(x) for x in self.vars.lines])
-                self.module.atomic_move(tmpfile, self.vars.filename)
-            finally:
-                if os.path.exists(tmpfile):
-                    os.remove(tmpfile)
+            bkp = self.module.backup_local(self.vars.filename)
+            with open(self.vars.filename, "w") as fd:
+                fd.writelines(["{0}\n".format(x) for x in self.vars.lines])
+            self.module.add_cleanup_file(bkp)
 
 
 def main():


Test patch:
diff --git a/tests/integration/targets/kernel_blacklist/aliases b/tests/integration/targets/kernel_blacklist/aliases
--- a/tests/integration/targets/kernel_blacklist/aliases
+++ b/tests/integration/targets/kernel_blacklist/aliases
@@ -3,3 +3,7 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 azp/posix/1
+skip/aix
+skip/freebsd
+skip/osx
+skip/macos
diff --git a/tests/integration/targets/kernel_blacklist/handlers/main.yml b/tests/integration/targets/kernel_blacklist/handlers/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/kernel_blacklist/handlers/main.yml
@@ -0,0 +1,10 @@
+---
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+- name: Remove modprobe.d
+  ansible.builtin.file:
+    path: /etc/modprobe.d
+    state: absent
+    
\ No newline at end of file
diff --git a/tests/integration/targets/kernel_blacklist/tasks/main.yml b/tests/integration/targets/kernel_blacklist/tasks/main.yml
--- a/tests/integration/targets/kernel_blacklist/tasks/main.yml
+++ b/tests/integration/targets/kernel_blacklist/tasks/main.yml
@@ -46,12 +46,12 @@
 - name: assert file is unchanged
   assert:
     that:
-    - bl_test_1 is not changed
-    - bl_test_1a is not changed
-    - orig_stat.stat.size == stat_test_1.stat.size
-    - orig_stat.stat.checksum == stat_test_1.stat.checksum
-    - orig_stat.stat.mtime == stat_test_1.stat.mtime
-    - stat_test_1.stat.checksum == expected_content | checksum
+      - bl_test_1 is not changed
+      - bl_test_1a is not changed
+      - orig_stat.stat.size == stat_test_1.stat.size
+      - orig_stat.stat.checksum == stat_test_1.stat.checksum
+      - orig_stat.stat.mtime == stat_test_1.stat.mtime
+      - stat_test_1.stat.checksum == expected_content | checksum
   vars:
     expected_content: |
       # Copyright (c) Ansible Project
@@ -65,7 +65,7 @@
 - name: test deprecation
   assert:
     that:
-    - "'deprecations' not in bl_test_1 or (ansible_version.major == 2 and ansible_version.minor == 12)"
+      - "'deprecations' not in bl_test_1 or (ansible_version.major == 2 and ansible_version.minor == 12)"
 
 - name: add new item to list
   community.general.kernel_blacklist:
@@ -82,8 +82,8 @@
 - name: assert element is added
   assert:
     that:
-    - bl_test_2 is changed
-    - slurp_test_2.content|b64decode == content
+      - bl_test_2 is changed
+      - slurp_test_2.content|b64decode == content
   vars:
     content: |
       # Copyright (c) Ansible Project
@@ -107,11 +107,11 @@
     src: '{{ bl_file }}'
   register: slurp_test_3
 
-- name: assert element is added
+- name: assert element is removed
   assert:
     that:
-    - bl_test_3 is changed
-    - slurp_test_3.content|b64decode == content
+      - bl_test_3 is changed
+      - slurp_test_3.content|b64decode == content
   vars:
     content: |
       # Copyright (c) Ansible Project
@@ -121,3 +121,35 @@
       blacklist aaaa
       blacklist cccc
       blacklist dddd
+
+############################################################################################################################################
+#
+# Issue 7362
+#
+
+- name: Create /etc/modprobe.d
+  ansible.builtin.file:
+    path: /etc/modprobe.d
+    state: directory
+    mode: '0755'
+    owner: root
+    group: root
+  notify: Remove modprobe.d
+
+- name: Create cls_rsvp file
+  ansible.builtin.copy:
+    dest: /etc/modprobe.d/cls_rsvp-blacklist.conf
+    content: |
+      blacklist cls_rsvp
+    mode: '0644'
+
+- name: Block potentially affected (and unused) modules (7362)
+  community.general.kernel_blacklist:
+    name: "{{ line_item }}"
+    state: present
+    blacklist_file: "/etc/modprobe.d/{{ line_item }}-blacklist.conf"
+  with_items:
+    - cifs
+    - cls_rsvp
+  loop_control:
+    loop_var: line_item