/* resourcelimits.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2020, 2021, 2026 Askar Almatov

    This file is part of Trafficmeter.

    Trafficmeter is free software: you can redistribute it and/or modify it under the terms of the GNU General
    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    Trafficmeter 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 GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License along with Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/resource.h>

#include "resourcelimits.h"
#include "tmdefs.h"

using namespace trafficmeter;

using std::string;
using std::vector;
using std::to_string;

/**************************************************************************************************************/
static long
getSysctl_( const string& name )
{
    long    value = -1;
    FILE*   fp = popen( ("sysctl " + name + " 2> /dev/null").c_str(), "r" );

    if ( fp != nullptr )
    {
        char*   buffer = nullptr;
        size_t  bufferSize = 0;
        long    length = getline( &buffer, &bufferSize, fp );

        if ( length > 0 && strncmp(buffer, name.c_str(), name.size()) == 0 )
        {
            value = atol( buffer + name.size() + 2 );
        }

        free( buffer );
        pclose( fp );
    }

    return value;
}

/**************************************************************************************************************/
static string
setSysctl_( const string& name, long value )
{
    string  result;
    FILE*   fp = popen( ("sysctl " + name + "=" + to_string(value) + " 2> /dev/null").c_str(), "r" );

    if ( fp != nullptr )
    {
        char*   buffer = nullptr;
        size_t  bufferSize = 0;
        long    length = getline( &buffer, &bufferSize, fp );

        if ( length > 0 )
        {
            result = buffer;
            result.pop_back();
        }

        free( buffer );
        pclose( fp );
    }

    return std::move( result );
}

/**************************************************************************************************************/
bool
ResourceLimits::set()
{
    long  realMem = getSysctl_( "hw.realmem" );

    if ( realMem >= 0 )
    {
        if ( getSysctl_("vm.max_user_wired") < realMem >> 12 )
        {
            results_.push_back( setSysctl_("vm.max_user_wired", realMem>>12) );
        }

        struct rlimit   rMemlock;
        rlim_t          oldMemlock;

        getrlimit( RLIMIT_MEMLOCK, &rMemlock );
        oldMemlock = rMemlock.rlim_cur;

        if ( oldMemlock < realMem )
        {
            rMemlock.rlim_cur = realMem;
            rMemlock.rlim_max = realMem;

            if ( setrlimit(RLIMIT_MEMLOCK, &rMemlock) != 0 )
            {
                return false;
            }

            results_.push_back( "memlock: " + to_string(oldMemlock) + " -> " + to_string(rMemlock.rlim_cur) );
        }
    }

    struct rlimit   rNofile;
    rlim_t          oldNofile;

    getrlimit( RLIMIT_NOFILE, &rNofile );
    oldNofile = rNofile.rlim_cur;

    if ( oldNofile < MAX_FILES )
    {
        int     lowNofile = oldNofile;
        rlim_t  highNofile = MAX_FILES;

        do
        {
            rNofile.rlim_cur = highNofile;
            rNofile.rlim_max = highNofile;

            if ( setrlimit(RLIMIT_NOFILE, &rNofile) != 0 )
            {
                highNofile -= ( highNofile - lowNofile ) >> 1;
            }
            else
            {
                highNofile += ( highNofile - lowNofile ) >> 1;
                lowNofile = rNofile.rlim_cur;
            }
        }
        while ( highNofile < MAX_FILES && highNofile > lowNofile + 128 );

        getrlimit( RLIMIT_NOFILE, &rNofile );
        results_.push_back( "nofile: " + to_string(oldNofile) + " -> " + to_string(rNofile.rlim_cur) );
    }

    return true;
}

/**************************************************************************************************************/
const vector<string>&
ResourceLimits::results() const
{
    return results_;
}
