Repository: ansible-collections/community.general

Issue:
capabilities: libcap-2.4.3 changed output of getcap
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
getcap output changed in version 2.43 vs. 2.26 of libcap, breaking community.general.capabilities module

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
capabilities

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```
ansible 2.10.1
  config file = /home/thomas/git/ansible/ansible.cfg
  configured module search path = ['/home/thomas/git/ansible/library']
  ansible python module location = /home/thomas/.python-venvs/_home_thomas_git_ansible/lib/python3.6/site-packages/ansible
  executable location = /home/thomas/.python-venvs/_home_thomas_git_ansible/bin/ansible
  python version = 3.6.9 (default, Jul 17 2020, 12:50:27) [GCC 8.4.0]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```
CACHE_PLUGIN(/home/thomas/git/ansible/ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/home/thomas/git/ansible/ansible.cfg) = factscache
CACHE_PLUGIN_TIMEOUT(/home/thomas/git/ansible/ansible.cfg) = 86400
DEFAULT_BECOME(/home/thomas/git/ansible/ansible.cfg) = True
DEFAULT_BECOME_ASK_PASS(/home/thomas/git/ansible/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/home/thomas/git/ansible/ansible.cfg) = sudo
DEFAULT_BECOME_USER(/home/thomas/git/ansible/ansible.cfg) = root
DEFAULT_CALLBACK_WHITELIST(/home/thomas/git/ansible/ansible.cfg) = ['profile_roles', 'profile_tasks', 'timer']
DEFAULT_FORKS(/home/thomas/git/ansible/ansible.cfg) = 12
DEFAULT_GATHERING(/home/thomas/git/ansible/ansible.cfg) = smart
DEFAULT_HOST_LIST(/home/thomas/git/ansible/ansible.cfg) = ['/home/thomas/git/ansible/inventory/production.py', '/home/thomas/git/ansible/inventory/nameserver_hcloud.yaml']
DEFAULT_MANAGED_STR(/home/thomas/git/ansible/ansible.cfg) = This file is managed by Ansible. Do not edit manually!
DEFAULT_MODULE_PATH(/home/thomas/git/ansible/ansible.cfg) = ['/home/thomas/git/ansible/library']
INTERPRETER_PYTHON(/home/thomas/git/ansible/ansible.cfg) = auto_silent
INVENTORY_ENABLED(/home/thomas/git/ansible/ansible.cfg) = ['script', 'hcloud']
MAX_FILE_SIZE_FOR_DIFF(/home/thomas/git/ansible/ansible.cfg) = 1048576
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
Host running ansible is Ubuntu 18.04, target system is Gentoo with sys-libs/libcap-2.4.3

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: ensure capabilities for ping are set
  community.general.capabilities:
    path: /bin/ping
    capability: cap_net_raw+ep
    state: present
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->
Ansible sets cap_net_raw+ep if it is not already set

##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->
Ansible fails getting current capabilities.

<!--- Paste verbatim command output between quotes -->
```paste below
fatal: [server20.tralios.de]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "capability": "cap_net_raw+ep",
            "path": "/bin/ping",
            "state": "present"
        }
    },
    "msg": "Unable to get capabilities of /bin/ping",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "/bin/ping cap_net_raw=ep",
    "stdout_lines": [
        "/bin/ping cap_net_raw=ep"
    ]
}
```

##### Additional information

Version 2.26:
```
➜  touch capabilities_test
➜  sudo setcap cap_net_raw+ep capabilities_test
➜  getcap capabilities_test 
capabilities_test = cap_net_raw+ep
```

Version 2.43:
```
➜  getcap capabilities_test 
capabilities_test cap_net_raw=ep
```


Discussion:
Files identified in the description:
None

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Files identified in the description:
* [`plugins/modules/system/capabilities.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/system/capabilities.py)

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @MorrisA @bcoca @d-little @flynn1973 @gforster @kairoaraujo @marvin-sinister @mator @molekuul @natefoo @ramooncamacho @wtcross
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
older version
```
$ dpkg -S /sbin/getcap  | cut -f1 -d':' | xargs apt info | grep -i version
Version: 1:2.25-2
$ getcap /bin/ping
/bin/ping = cap_net_raw+ep
```
fedora 31
```
$ rpm -qf `which getcap`
libcap-2.26-6.fc31.x86_64
$ getcap /bin/ping
/bin/ping = cap_net_admin,cap_net_raw+p
```
no out version (opensuse)
```
$ getcap /bin/ping
$ echo $?
0
$ rpm -qf `which getcap`
libcap-progs-2.25-lp151.3.70.x86_64
```
older no cap report (debian10):
```
$ dpkg -l libcap2-bin
ii  libcap2-bin    1:2.25-2     amd64        POSIX 1003.1e capabilities (utilities)
$ /sbin/getcap /bin/ping
/bin/ping =
```

newer version (archlinux):
```
$ pacman -Q libcap
libcap 2.43-1
$ getcap /bin/ping
/bin/ping cap_net_raw=p
```
debian sid/unstable:
```
$ dpkg -l libcap2-bin
ii  libcap2-bin    1:2.43-1     amd64        POSIX 1003.1e capabilities (utilities)
$ getcap /bin/ping
/bin/ping cap_net_raw=ep
```
so in the old version caps are after '=' sign , but in a newer version after space... 

original commit from [libcap.git](https://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git) :  
```
commit 177cd418031b1acfcf73fe3b1af9f3279828681c
Author: Andrew G. Morgan <morgan@kernel.org>
Date:   Tue Jul 21 22:58:05 2020 -0700

    A more compact form for the text representation of capabilities.

    While this does not change anything about the supported range of
    equivalent text specifications for capabilities, as accepted by
    cap_from_text(), this does alter the preferred output format of
    cap_to_text() to be two characters shorter in most cases. That is,
    what used to be summarized as:

       "= cap_foo+..."

    is now converted to the equivalent text:

       "cap_foo=..."

    which is also more intuitive.

    Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
```
@TWitzenrath  is it possible for you to test my PR ? thanks
@mator Thanks for your work! With your PR applied, I needed to change
```
 - name: ensure capabilities for ping are set
   capabilities:
     path: /bin/ping
-    capability: cap_net_raw+ep
+    capability: cap_net_raw=ep
```
in order to make the task not be "changed" every run. I don't mind, just a thing I noticed.

When setting capabilities on a file that has no capabilities set (or capabilities removed with setcap -r <file>) I get:
```
fatal: [server23]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "capability": "cap_net_raw=ep",
            "path": "/bin/ping",
            "state": "present"
        }
    },
    "msg": "Unable to get capabilities of /bin/ping",
    "stderr": "",
    "stderr_lines": [],
    "stdout": "/bin/ping",
    "stdout_lines": [
        "/bin/ping"
    ]
}
```
This worked before, regardless of the version of libcap.
@TWitzenrath , just pushed a change for a files without caps set... Thanks.

Resolving patch:
diff --git a/plugins/modules/system/capabilities.py b/plugins/modules/system/capabilities.py
--- a/plugins/modules/system/capabilities.py
+++ b/plugins/modules/system/capabilities.py
@@ -108,12 +108,18 @@ def getcap(self, path):
         #   '/foo ='
         # If file xattrs are unset the output will be:
         #   '/foo'
-        # If the file does not exist the output will be (with rc == 0...):
+        # If the file does not exist, the stderr will be (with rc == 0...):
         #   '/foo (No such file or directory)'
-        if rc != 0 or (stdout.strip() != path and stdout.count(' =') != 1):
+        if rc != 0 or stderr != "":
             self.module.fail_json(msg="Unable to get capabilities of %s" % path, stdout=stdout.strip(), stderr=stderr)
         if stdout.strip() != path:
-            caps = stdout.split(' =')[1].strip().split()
+            if ' =' in stdout:
+                # process output of an older version of libcap
+                caps = stdout.split(' =')[1].strip().split()
+            else:
+                # otherwise, we have a newer version here
+                # see original commit message of cap/v0.2.40-18-g177cd41 in libcap.git
+                caps = stdout.split()[1].strip().split()
             for cap in caps:
                 cap = cap.lower()
                 # getcap condenses capabilities with the same op/flags into a


Test patch: