#!/usr/local/bin/python3.11
#coding: utf-8 -*-
# (c) 2014, David Lundgren <dlundgren@syberisle.net>
#
# This file is part of Ansible
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the MIT license.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# MIT License for more details.
#
# You should have received a copy of the MIT.
# If not, see <http://opensource.org/licenses/MIT>.

DOCUMENTATION = '''
---
module: kld
short_description: Add or remove kernel modules
requirements: []
version_added: 1.7
author: David Lundgren
description:
    - Add or remove kernel modules.
options:
    name:
        required: true
        description:
            - Name of kernel module to manage.
    load:
        required: false
        default: true
        choices: [ true, false ]
        description:
            - Whether the module should be loaded.
    boot:
        required: false
        default: false
        choices: [ true, false ]
        description:
            - Whether the module should be in /boot/loader.conf.
'''

EXAMPLES = '''
# Adds accf_http to the bootloader and loads it
- kld:
    name: accf_http
# Removes accf_http from the bootloader and loads it
- kld:
    name: accf_http
    load: true
    boot: false
'''

from ansible.module_utils.basic import *

class FreeBSDKernelModule(object):
    def __init__(self, module, name):
        self.module  = module
        self.name    = name
        self.changed = False
        self.sysrc   = module.get_bin_path('sysrc', True)

    def has_unknown_variable(self, out, err):
        # newer versions of sysrc use stderr instead of stdout
        return err.find("unknown variable") > 0 or out.find("unknown variable") > 0

    # Returns whether or not the module is set to load in /boot/loader.conf
    def booted(self):
        (rc, out, err) = self.module.run_command("%s -f /boot/loader.conf %s_load" %( self.sysrc, self.name ))
        return not self.has_unknown_variable(out, err)

    def boot_enable(self):
        if self.module.check_mode:
            self.changed = True
            return
        
        (rc, out, err) = self.module.run_command("%s -f /boot/loader.conf %s_load=YES" % (self.sysrc, self.name))
        if out.find("%s_load:" % (self.name)) != -1 and out.find("-> YES") != -1:
            self.changed = True
            return True
        else:
            return False
    
    def boot_disable(self):
        if self.module.check_mode:
            self.changed = True
            return
        
        (rc, out, err) = self.module.run_command("%s -f /boot/loader.conf -x %s_load" % (self.sysrc, self.name))
        if self.has_unknown_variable(out, err):
            return False

        self.changed = True
        return True

    def loaded(self):
        cmd = [self.module.get_bin_path('kldstat', True)]
        # -q is not available on FreeBSD before 6.0 so using it would break on those versions
        cmd.append('-n')
        cmd.append("%s.ko" % self.name)
        (rc, out, err) = self.module.run_command(' '.join(cmd))

        return rc == 0

    def load(self):
        if self.module.check_mode:
            self.changed = True
            return
        
        cmd = [self.module.get_bin_path('kldload', True)]
        cmd.append('-n')
        cmd.append(self.name)
        (rc, out, err) = self.module.run_command(' '.join(cmd))
        if rc == 0:
            self.changed = True
            return True
        else:
            return False

    def unload(self):
        if self.module.check_mode:
            self.changed = True
            return
        
        cmd = [self.module.get_bin_path('kldunload', True)]
        cmd.append(self.name)
        (rc, out, err) = self.module.run_command(' '.join(cmd))

        if err.find('kldunload: ') == 0:
            self.module.fail_json(msg=err.replace('kldunload: ', '').rstrip())

        if rc == 0:
            self.changed = True
            return True
        else:
            return False
        
def main():
    module = AnsibleModule(
        supports_check_mode = True,
        argument_spec       = dict(
            name = dict(
                required = True
            ),
            load = dict(
                default = True,
                type    = 'bool'
            ),
            boot = dict(
                default = True,
                type    = 'bool'
            )
        ),
    )
    
    name   = module.params.pop('name')
    load   = module.params.pop('load')
    boot   = module.params.pop('boot')
    result = {
        'name' : name,
        'load' : load,
        'boot' : boot
    }
    
    kld = FreeBSDKernelModule(module, name)

    if load:
        not kld.loaded() and kld.load()
    else:
        kld.loaded() and kld.unload()

    # ensure it is set for boot
    if boot:
        not kld.booted() and kld.boot_enable()
    else:
        kld.booted() and kld.boot_disable()

    result['changed'] = kld.changed

    module.exit_json(**result)

# import module snippets
main()
