Repository: ansible/ansible-modules-core

Issue:
mount module can't update flags on a mounted FS on OpenBSD
<!--- Verify first that your issue/request is not already reported in GitHub -->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report


##### COMPONENT NAME
<!--- Name of the plugin/module/task -->
mount module

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.0.0
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
OpenBSD

##### SUMMARY
<!--- Explain the problem briefly -->
https://github.com/ansible/ansible-modules-core/blob/devel/system/mount.py#L319 uses `-o remount`, which doesn't exist on OpenBSD. if you try to update the flags for an existing mountpoint, it will update fstab correctly, but then will try to remount/update the flags on the living FS, and mount module will fail.
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```
#grep local /etc/fstab                                                                                       
/dev/wd0g /usr/local ffs rw,wxallowed 0 0

#ansible master -m mount -a 'name=/usr/local opts=rw,wxallowed,nosuid state=mounted fstype=ffs src=/dev/wd0g'
master | FAILED! => {
    "changed": false, 
    "failed": true, 
    "msg": "Error mounting /usr/local: mount_ffs: -o remount: option not supported\n"
}
#grep local /etc/fstab                                                                                       
/dev/wd0g /usr/local ffs rw,wxallowed,nosuid 0 0
#mount |grep /usr/local
/dev/wd0g on /usr/local type ffs (local, wxallowed)
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
the living FS should have seen its opts updated.
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
mount module failed...  a possible fix would be to use `mount -u` which is more or less the equivalent of `mount -o remount`
<!--- Paste verbatim command output between quotes below -->
```
#mount -u -o nosuid /usr/local
#mount |grep /usr/local       
/dev/wd0g on /usr/local type ffs (local, nosuid, wxallowed)

```



Discussion:
<!-- @landryb: Thanks for filing this bug report! -->

@ansible ping, this issue is waiting for your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_bug_new --->

The relevant commit is a backport of a different patch
https://github.com/ansible/ansible-modules-core/commit/d843204575952a57e7e866bf21f93d6eb957ce04

> The relevant commit is a backport of a different patch
> d843204

Interesting, but that still uses `-o remount`...
This should be fixed by the inclusion of these two PRs: https://github.com/ansible/ansible-modules-core/pull/2445  https://github.com/ansible/ansible-modules-core/pull/5673

if you can confirm that latest devel or latest stable-2.2 work, that would be great.  If you can't I'll close this in a few days.  It works with my tests on FreeBSD (which also uses -u for remount).

needs_info
Tried with 2.2.1.0-0.1.rc1 (not sure it has those two PRs), there's no error message, but the living FS isn't updated.

before:
```
$mount|grep sd0d ; grep sd0d /etc/fstab  
/dev/sd0d on /home type ffs (local, nodev, nosuid)
/dev/sd0d /home ffs rw,nodev,nosuid 1 2
```
ansible invocation with local connection:
```
$doas ansible -vvvv -c local localhost -m mount -a 'name=/home opts=rw,nodev,nosuid,wxallowed state=mounted fstype=ffs src=/dev/sd0d dump=1 passno=2'

Using /etc/ansible/ansible.cfg as config file
 [WARNING]: provided hosts list is empty, only localhost is available

Loading callback plugin minimal of type stdout, v2.0 from /usr/local/lib/python2.7/site-packages/ansible/plugins/callback/__init__.pyc
Using module file /usr/local/lib/python2.7/site-packages/ansible/modules/core/system/mount.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: landry
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670 `" && echo ansible-tmp-1479806309.65-74141033850670="` echo $HOME/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670 `" ) && sleep 0'
<127.0.0.1> PUT /tmp/tmp2QsDsI TO /home/landry/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670/mount.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/landry/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670/ /home/landry/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670/mount.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/local/bin/python2.7 /home/landry/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670/mount.py; rm -rf "/home/landry/.ansible/tmp/ansible-tmp-1479806309.65-74141033850670/" > /dev/null 2>&1 && sleep 0'
localhost | SUCCESS => {
    "changed": true, 
    "dump": "1", 
    "fstab": "/etc/fstab", 
    "fstype": "ffs", 
    "invocation": {
        "module_args": {
            "boot": "yes", 
            "dump": "1", 
            "fstab": "/etc/fstab", 
            "fstype": "ffs", 
            "name": "/home", 
            "opts": "rw,nodev,nosuid,wxallowed", 
            "passno": "2", 
            "src": "/dev/sd0d", 
            "state": "mounted"
        }, 
        "module_name": "mount"
    }, 
    "name": "/home", 
    "opts": "rw,nodev,nosuid,wxallowed", 
    "passno": "2", 
    "src": "/dev/sd0d"
}

```
after:
```
$mount|grep sd0d ; grep sd0d /etc/fstab                                              
/dev/sd0d on /home type ffs (local, nodev, nosuid)
/dev/sd0d /home ffs rw,nodev,nosuid,wxallowed 1 2
```
According to https://github.com/ansible/ansible-modules-core/commits/stable-2.2 2.2.1.0-0.1.rc1 should have those two PRs, so.. i'm puzzled.
Same thing using ` -b --become-method=doas` instead of `doas ansible`
@landryb  What happens if you run "doas mount -u -F/etc/fstab /dev/sd0d" (which is what ansible is going to attempt first to remount on openbsd)?
Well.. on freebsd10 ```mount -u``` seems inconsistently broken...
* fstab has:
> /dev/ada0p3    /tmp    ufs    rw,nosuid     2 2
* mounted by boot
* Confirmed that nosuid was in the live mount options by running mount and checking output
* Removed ```,nosuid``` from the fstab line
* Ran mount -u -F /etc/fstab /dev/ada0p3
* Comfirmed that nosuid had been removed from the live mount.
* Re-added ```,nosuid``` to the fstab line
* Ran mount -u -F /etc/fstab /dev/ada0p3
* ```,nosuid``` **was not** added to the live mount options.

So between FreeBSD's behaviour and your report on OpenBSD I'm guessing that remounting with options specified in /etc/fstab is buggy and can't be relied on.  I'll change the code to always explicitly umount and then mount filesystems when changing the options on the *BSDs... That is not ideal as a filesystem in-use can't be unmounted (and at least some platforms allow remounting with different options in this case) but it's probably the best we can do in the short term.

Longer term, somebody could rewrite the mount module (or write a new mount module) which had a cleaner separation between fstab modification and adhoc mounting.  The adhoc mounting case (specifying the new mount options via "-o" on the command line)  appears to work on freebsd10.  I don't know if it works or not on other platforms.
> @landryb What happens if you run "doas mount -u -F/etc/fstab /dev/sd0d" (which is what ansible is going to attempt first to remount on openbsd)?

Afaict, our `mount` doesn't support -F, cf http://man.openbsd.org/mount.8. When i want to update flags, i just use `mount -u -o<theflag> <mountpoint>` or `mount -u -o<theflag> <device>`. That will just update the mentioned flags, not changing other flags already set.

When using `-u`,  the manpage says ` The options set in the fstab(5) table are ignored.` so even if the fstab has been correctly updated, the options to change should be specified again on the `mount -u` command.

And no, forcing umount/mount will definitely *not* work for in-use filesystems, blaming 'a possible bug in BSD mount' is ugly here.
Okay.  So we'll need to document that openbsd does not take the fstab parameter and then make it throw a nice error if it's specified and we're on openbsd.  I'll work on that an CC you on the Pull Request.

For unmount/mount, I agree it's not ideal.  We'll gladly take a PR to implement remount on the BSDs.

Resolving patch:
diff --git a/system/mount.py b/system/mount.py
--- a/system/mount.py
+++ b/system/mount.py
@@ -90,9 +90,11 @@
     choices: ["present", "absent", "mounted", "unmounted"]
   fstab:
     description:
-      - File to use instead of C(/etc/fstab). You shouldn't use that option
+      - File to use instead of C(/etc/fstab). You shouldn't use this option
         unless you really know what you are doing. This might be useful if
-        you need to configure mountpoints in a chroot environment.
+        you need to configure mountpoints in a chroot environment.  OpenBSD
+        does not allow specifying alternate fstab files with mount so do not
+        use this on OpenBSD with any state that operates on the live filesystem.
     required: false
     default: /etc/fstab (/etc/vfstab on Solaris)
   boot:
@@ -308,14 +310,14 @@ def unset_mount(module, args):
 
     return (args['name'], changed)
 
-def _set_fstab_args(args):
+def _set_fstab_args(fstab_file):
     result = []
-    if 'fstab' in args and args['fstab'] != '/etc/fstab':
+    if fstab_file and fstab_file != '/etc/fstab':
         if get_platform().lower().endswith('bsd'):
             result.append('-F')
         else:
             result.append('-T')
-        result.append(args['fstab'])
+        result.append(fstab_file)
     return result
 
 def mount(module, args):
@@ -328,7 +330,13 @@ def mount(module, args):
     if ismount(name):
         return remount(module, mount_bin, args)
 
-    cmd += _set_fstab_args(args)
+    if get_platform().lower() == 'openbsd':
+        # Use module.params['fstab'] here as args['fstab'] has been set to the
+        # default value.
+        if module.params['fstab'] is not None:
+            module.fail_json(msg='OpenBSD does not support alternate fstab files.  Do not specify the fstab parameter for OpenBSD hosts')
+    else:
+        cmd += _set_fstab_args(args['fstab'])
 
     cmd += [name]
 
@@ -364,7 +372,13 @@ def remount(module, mount_bin, args):
     else:
         cmd += ['-o', 'remount' ]
 
-    cmd += _set_fstab_args(args)
+    if get_platform().lower() == 'openbsd':
+        # Use module.params['fstab'] here as args['fstab'] has been set to the
+        # default value.
+        if module.params['fstab'] is not None:
+            module.fail_json(msg='OpenBSD does not support alternate fstab files.  Do not specify the fstab parameter for OpenBSD hosts')
+    else:
+        cmd += _set_fstab_args(args['fstab'])
     cmd += [ args['name'], ]
     out = err = ''
     try:
@@ -564,7 +578,7 @@ def main():
         argument_spec=dict(
             boot=dict(default='yes', choices=['yes', 'no']),
             dump=dict(),
-            fstab=dict(default='/etc/fstab'),
+            fstab=dict(default=None),
             fstype=dict(),
             name=dict(required=True, type='path'),
             opts=dict(),
@@ -586,26 +600,32 @@ def main():
     #   name, src, fstype, opts, boot, passno, state, fstab=/etc/vfstab
     # linux args:
     #   name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab
-    if get_platform() == 'SunOS':
+    # Note: Do not modify module.params['fstab'] as we need to know if the user
+    # explicitly specified it in mount() and remount()
+    if get_platform().lower() == 'sunos':
         args = dict(
             name=module.params['name'],
             opts='-',
             passno='-',
-            fstab='/etc/vfstab',
+            fstab=module.params['fstab'],
             boot='yes'
         )
+        if args['fstab'] is None:
+            args['fstab'] = '/etc/vfstab'
     else:
         args = dict(
             name=module.params['name'],
             opts='defaults',
             dump='0',
             passno='0',
-            fstab='/etc/fstab'
+            fstab=module.params['fstab']
         )
+        if args['fstab'] is None:
+            args['fstab'] = '/etc/fstab'
 
-    # FreeBSD doesn't have any 'default' so set 'rw' instead
-    if get_platform() == 'FreeBSD':
-        args['opts'] = 'rw'
+        # FreeBSD doesn't have any 'default' so set 'rw' instead
+        if get_platform() == 'FreeBSD':
+            args['opts'] = 'rw'
 
     linux_mounts = []
 
@@ -624,9 +644,6 @@ def main():
         if module.params[key] is not None:
             args[key] = module.params[key]
 
-    if get_platform() == 'SunOS' and args['fstab'] == '/etc/fstab':
-        args['fstab'] = '/etc/vfstab'
-
     # If fstab file does not exist, we first need to create it. This mainly
     # happens when fstab option is passed to the module.
     if not os.path.exists(args['fstab']):


Test patch: