From 2f4c30ce3beb3d5ada0224ef74ca2c04bb8f2a08 Mon Sep 17 00:00:00 2001 From: "Alexander E. Patrakov" Date: Mon, 16 Dec 2019 15:45:52 +0500 Subject: [PATCH] Restore fstab and remove the mount point after a failed mount Properly fixes ansible/ansible#59183 without breaking NFS. --- plugins/modules/mount.py | 53 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/plugins/modules/mount.py b/plugins/modules/mount.py index 3da1dc0..c8b5855 100644 --- a/plugins/modules/mount.py +++ b/plugins/modules/mount.py @@ -153,13 +153,14 @@ EXAMPLES = r''' ''' +import errno import os import platform from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.posix.plugins.module_utils.ismount import ismount from ansible.module_utils.six import iteritems -from ansible.module_utils._text import to_native +from ansible.module_utils._text import to_bytes, to_native def write_fstab(module, lines, path): @@ -195,8 +196,15 @@ def _escape_fstab(v): def set_mount(module, args): """Set/change a mount point location in fstab.""" + name, backup_lines, changed = _set_mount_save_old(module, args) + return name, changed + + +def _set_mount_save_old(module, args): + """Set/change a mount point location in fstab. Save the old fstab contents.""" to_write = [] + old_lines = [] exists = False changed = False escaped_args = dict([(k, _escape_fstab(v)) for k, v in iteritems(args)]) @@ -207,6 +215,8 @@ def set_mount(module, args): '%(src)s - %(name)s %(fstype)s %(passno)s %(boot)s %(opts)s\n') for line in open(args['fstab'], 'r').readlines(): + old_lines.append(line) + if not line.strip(): to_write.append(line) @@ -289,7 +299,7 @@ def set_mount(module, args): if changed and not module.check_mode: write_fstab(module, to_write, args['fstab']) - return (args['name'], changed) + return (args['name'], old_lines, changed) def unset_mount(module, args): @@ -716,14 +726,34 @@ def main(): changed = True elif state == 'mounted': + dirs_created = [] if not os.path.exists(name) and not module.check_mode: try: - os.makedirs(name) + # Something like mkdir -p but with the possibility to undo. + # Based on some copy-paste from the "file" module. + curpath = '' + for dirname in name.strip('/').split('/'): + curpath = '/'.join([curpath, dirname]) + # Remove leading slash if we're creating a relative path + if not os.path.isabs(name): + curpath = curpath.lstrip('/') + + b_curpath = to_bytes(curpath, errors='surrogate_or_strict') + if not os.path.exists(b_curpath): + try: + os.mkdir(b_curpath) + dirs_created.append(b_curpath) + except OSError as ex: + # Possibly something else created the dir since the os.path.exists + # check above. As long as it's a dir, we don't need to error out. + if not (ex.errno == errno.EEXIST and os.path.isdir(b_curpath)): + raise + except (OSError, IOError) as e: module.fail_json( msg="Error making dir %s: %s" % (name, to_native(e))) - name, changed = set_mount(module, args) + name, backup_lines, changed = _set_mount_save_old(module, args) res = 0 if ( @@ -740,6 +770,21 @@ def main(): res, msg = mount(module, args) if res: + # Not restoring fstab after a failed mount was reported as a bug, + # ansible/ansible#59183 + # A non-working fstab entry may break the system at the reboot, + # so undo all the changes if possible. + try: + write_fstab(module, backup_lines, args['fstab']) + except Exception: + pass + + try: + for dirname in dirs_created[::-1]: + os.rmdir(dirname) + except Exception: + pass + module.fail_json(msg="Error mounting %s: %s" % (name, msg)) elif state == 'present': name, changed = set_mount(module, args)