#!/usr/bin/php
<?php
//
// Configure & Tune  MySQL / MariaDB
// Copyright (c) 2017-2019 Nagios Enterprises, LLC. All rights reserved.
//
// update mysql variables to those below. first check if the user has specified larger values (if so, leave them alone)

// from the mysql docs:
// The first command specifies the value in bytes. 
// The second specifies the value in megabytes. 
// For variables that take a numeric value, the value can be given with a suffix of K, M, or G (either uppercase or lowercase) to indicate a multiplier of 1024, 10242 or 10243.
// (For example, when used to set max_allowed_packet, the suffixes indicate units of kilobytes, megabytes, or gigabytes.)

$mysql_variables = array(
    'max_allowed_packet'        => '32M',
    'query_cache_size'          => '16M',
    'query_cache_limit'         => '4M',
    'tmp_table_size'            => '64M',
    'max_heap_table_size'       => '64M',
    'key_buffer_size'           => '32M',
    'table_open_cache'          => '32',
    'innodb_file_per_table'     => '1',
    'innodb_log_buffer_size'    => '32M',
    'innodb_buffer_pool_size'   => '1G',
    'innodb_log_file_size'      => '256M'
);

// query_cache_size and query_cache_limit removed in MySQL 8.0.3
$mysql8_variables = array(
    'max_allowed_packet'        => '32M',
    'tmp_table_size'            => '64M',
    'max_heap_table_size'       => '64M',
    'key_buffer_size'           => '32M',
    'table_open_cache'          => '32',
    'innodb_file_per_table'     => '1',
    'innodb_log_buffer_size'    => '32M',
    'innodb_buffer_pool_size'   => '1G',
    'innodb_log_file_size'      => '256M'
);

$mycnf_files = array(
    '/etc/my.cnf',
    '/etc/alternatives/my.cnf',
    '/etc/mysql/mysql.conf.d/mysqld.cnf',
    '/etc/mysql/my.cnf',
    '/var/lib/mysql/my.cnf',
);

$mycnfd_dirs = array(
    '/etc/my.cnf.d/',
    '/etc/mysql/my.cnf.d/',
    '/var/lib/mysql/my.cnf.d/',
    '/etc/mysql/',
);


if ($argc >= 2 && $argv[1] == '--mysql8') {
    $mysql_variables = $mysql8_variables;
}

$file_locations = stage1();
cleanup($file_locations);
exit();

// Returns an array of all the potential my.cnf files.
function stage1() {
    global $mycnf_files;
    global $mycnfd_dirs;

    // check the known good file locations first
    foreach ($mycnf_files as $mycnf_file) {
        if (file_exists($mycnf_file) && is_readable($mycnf_file)) {
            if (update_mysql_cnf_file($mycnf_file)) {

                echo "Updated MySQL/MariaDB configuration file successfully ({$mycnf_file})\n";
                return array($mycnf_file);
            }
        }
    }

    // check the directories (the files in these directories)
    foreach ($mycnfd_dirs as $mycnfd_dir) {

        if (file_exists($mycnfd_dir) && is_dir($mycnfd_dir) && is_executable($mycnfd_dir) && is_readable($mycnfd_dir)) {

            $mycnfd_files = scandir($mycnfd_dir);
            $updated_mycnf_files = array();
            foreach ($mycnfd_files as $mycnf_file) {

                if ($mycnf_file == '.' || $mycnf_file == '..')
                    continue;

                $mycnf_file = $mycnfd_dir . $mycnf_file;

                if (!is_dir($mycnf_file) && is_readable($mycnf_file)) {

                    if (update_mysql_cnf_file($mycnf_file)) {
                        $updated_mycnf_files[] = $mycnf_file;
                    }
                }
            }

            if (count($updated_mycnf_files) > 0) {

                echo "Updated the following MySQL/MariaDB configuration files successfully:\n" . implode("\n", $updated_mycnf_files) . "\n";
                return $updated_mycnf_files;
            }
        }
    }

    // if we ran out of options there, we desparately try and find a my.cnf file
    echo "About to search for a suitable MySQL/MariaDB configuration file. This may take a while.";
    exec('find / -type f -name "my.cnf"', $mycnf_files, $return_var);
    if ($return_var === 0) {

        $updated = false;

        // if we got a good return value, then we try and update *EACH*
        // of these files.
        // since this should ONLY BE RAN ON A CLEAN SYSTEM, this isn't a big deal
        // but there may be some laying around in /usr/share - which is why we do all of them
        $updated_mycnf_files = array();
        foreach ($mycnf_files as $mycnf_file) {
            if (file_exists($mycnf_file) && is_readable($mycnf_file)) {
                if (update_mysql_cnf_file($mycnf_file)) {

                    $updated = true;
                    $updated_mycnf_files[] = $mycnf_file;
                }
            }
        }

        if ($updated) {
            echo "Updated MySQL/MariaDB configurationd file successfully:\n" . implode("\n", $updated_mycnf_files) ."\n";
            return $updated_mycnf_files;
        }
    }

    // if we made it this far there is nothing we can do
    // but it shouldn't stop the installation
    fwrite(STDERR, "No suitable MySQL/MariaDB configuration file found for update.");
    exit();
}

// For now, just deletes ib_logfiles in every $datadir set by the input my.cnf files.
function cleanup($mycnf_files) {
    // Get system information
    $fusion_sys = getcwd() . '/fusion-sys.cfg';
    $ini = parse_ini_file($fusion_sys);
    
    if($ini['dist'] != 'el9') {
        foreach($mycnf_files as $mycnf_file) {
            echo "Reading $mycnf_file ...\n";

            $datadir = exec("cat $mycnf_file | grep datadir");
            $datadir = explode("=", $datadir);
            $datadir = $datadir[1]; 
            // Note: This will break if you put an = sign in your mysql data directory.
            // So please don't do that.

            $anything_removed = false;
            if (file_exists("$datadir/ib_logfile0")) {
                $anything_removed |= unlink("$datadir/ib_logfile0");
            }
            if (file_exists("$datadir/ib_logfile1")) {
                $anything_removed |= unlink("$datadir/ib_logfile1");
            }
            if ($anything_removed) {
                echo "Removed ib_logfiles from $datadir";
            }
        }
    }
}

// this is the same as (val1 > val2)
function compare_two_mysql_values($val1, $val2) {

    $val1_last_char = strtoupper(substr($val1, -1));
    $val2_last_char = strtoupper(substr($val2, -1));

    // if the end character is the same, or if they are both numeric, then this is easy, we just return the comparison
    if ($val1_last_char == $val2_last_char || (is_numeric($val1_last_char) && is_numeric($val2_last_char))) {
        return (float) $val1 > (float) $val2;
    }

    // now we need to get both vals into a number we can compare
    // since it can only be G, M, or K, or nothing, we should break them all down into nothing (which is really bytes)

    if ($val1_last_char == 'G')
        $val1 = (float) $val1 * 1024 * 1024 * 1024;
    if ($val2_last_char == 'G')
        $val2 = (float) $val2 * 1024 * 1024 * 1024;

    if ($val1_last_char == 'M')
        $val1 = (float) $val1 * 1024 * 1024;
    if ($val2_last_char == 'M')
        $val2 = (float) $val2 * 1024 * 1024;

    if ($val1_last_char == 'K')
        $val1 = (float) $val1 * 1024;
    if ($val2_last_char == 'K')
        $val2 = (float) $val2 * 1024;

    // now we can just compare them
    return $val1 > $val2;
}

// check a file for [mysqld] section and update as required
// will exit immediately if no write permission exists on file specified
// return false if didn't contain [mysqld] section or if write failed somehow
// return true if [mysqld] section was found and the file was updated
function update_mysql_cnf_file($mycnf_file) {

    global $mysql_variables;
 
    if (!is_writable($mycnf_file)) {
        fwrite(STDERR, "Unable to write to {$mycnf_file}. You should be root user.\n");
        exit(1);
    }

    // look for [mysqld] in /etc/my.cnf
    $mycnf = file_get_contents($mycnf_file);
    $mycnf_original = $mycnf;

    if (($mysqld_start_pos = strpos($mycnf, '[mysqld]')) !== false) {

        $errors_found = false;

        // perfect, we found it!
        // now we build a string that starts with the line directly AFTER the '[mysqld]' to the rest of the file
        $mycnf = substr($mycnf, $mysqld_start_pos + strlen('[mysqld]'));

        // find the next section starting point (starts with a '[')
        $next_section = null;
        if (($next_section_start_pos = strpos($mycnf, '[')) !== false) {

            // we need to figure out the name of the section in question for later calculations
            // the offset for 1 here is to get rid of the start '['
            $next_section = substr($mycnf, $next_section_start_pos + 1);
            if (($next_section_end_pos = strpos($next_section, ']')) !== false) {

                $next_section = substr($next_section, 0, $next_section_end_pos);
            }

            // if we found another section, we just adjust the text
            $mycnf = substr($mycnf, 0, $next_section_start_pos);
        }

        // now break the configuration section into an array of lines
        $mycnf_lines = explode("\n", $mycnf);

        // cycle through the variable list
        foreach ($mysql_variables as $variable => $value) {

            // and we cycle through the mycnf lines and see if our variable exists
            // if it does, then we do some comparison magic
            // if it doesn't we just add it to the lines
            $found = false;
            $write_line = false;
            $original_value = '';
            foreach ($mycnf_lines as $line_index => $mycnf_line) {

                // get the line data, and if it isn't how we expect it, then:
                // A) their mysql config isn't working anyway
                // B) we don't even check to see if our variable name is in there
                $line_data = explode("=", $mycnf_line);
                if (count($line_data) != 2) {

                    $errors_found = true;
                    continue;
                }

                $line_variable = trim($line_data[0]);
                $line_value = trim($line_data[1]);

                // is this one of the variables we are looking for?
                if ($variable == $line_variable) {

                    $found = true;

                    //echo "LINE VAR: $line_variable\nLINE VAL: $line_value\n";
                    //echo "     VAR: $variable     \n     VAL: $value\n";

                    // we only do anything if value > line value
                    if (compare_two_mysql_values($value, $line_value)) {

                        // reset this line, we'll add our data in the block below this foreach
                        $mycnf_lines[$line_index] = '';
                        $original_value = $line_value;
                        $write_line = true;
                    }
                }
            } // foreach mycnf_lines

            // if we didn't find the variable, or if we found it and needed to take action
            if (!$found || $write_line) {

                $comment = "# Added by Nagios " . @date('Y-m-d H:i:s O T');
                if (!empty($original_value))
                    $comment .= ", original value -> ({$original_value})";

                $mycnf_lines[] = $comment;
                $mycnf_lines[] = "{$variable} = {$value}";
                $mycnf_lines[] = '';
            }
        } // foreach mysql_variables

        // this is our rewritten mycnf section
        $mycnf_section = implode("\n", $mycnf_lines);

        // now we get the sections prior to and after where the original body of the [mysqld] section was
        $mycnf_beginning = substr($mycnf_original, 0, $mysqld_start_pos + strlen('[mysqld]'));
        $mycnf_end = '';
        if ($next_section !== null) {
            $mycnf_end = substr($mycnf_original, strpos($mycnf_original, "[$next_section]"));
        }

        // build the new file
        $mycnf = $mycnf_beginning . $mycnf_section . "\n" . $mycnf_end;

        if (file_put_contents($mycnf_file, $mycnf) === false)
            return false;

        return true;

    } // if found mysqld

    return false;
}
