# Solaris (SunOS) OS parameter collector (SERVER-1794)
#
# Sources:
#   - /etc/system whitelist            -> category "system"
#   - prctl process/task resource ctls -> category "prctl"
#   - ndd network tunables             -> category "network"
#
# The per-project prctl loop from the reference script (which spawns
# `newtask` for every project on each pass) is intentionally omitted here to
# avoid the process-spawn overhead on a periodic collector; process/task
# resource controls are collected once instead.  Default keys are managed in
# code; extra keys can be requested by the user through the custom option.

import os
import platform
import re
import threading
import whatap.util.process_util as process_util
import whatap.util.logging_util as logging_util
from whatap.osinfo.os_parameter_common import OsParameter, BackgroundCollector

OS_NAME = platform.system() or 'SunOS'

# /etc/system whitelist (see solaris.sh reference)
_SYSTEM_KEYS = [
    'rlim_fd_cur', 'rlim_fd_max', 'maxusers', 'hires_tick',
    'shmsys:shminfo_shmmax', 'shmsys:shminfo_shmmni', 'shmsys:shminfo_shmseg',
    'semsys:seminfo_semmni', 'semsys:seminfo_semmns', 'semsys:seminfo_semmsl',
    'semsys:seminfo_semopm', 'msgsys:msginfo_msgmax', 'msgsys:msginfo_msgmnb',
    'msgsys:msginfo_msgmni', 'autoup', 'tune_t_fsflush',
]

# prctl process/task resource controls (see solaris.sh reference)
_PRCTL_KEYS = [
    'process.max-cpu-time', 'process.max-file-size', 'process.max-data-size',
    'process.max-stack-size', 'process.max-core-size',
    'process.max-file-descriptor', 'process.max-address-space',
    'process.max-sem-nsems', 'process.max-sem-ops', 'process.max-msg-qbytes',
    'process.max-msg-messages', 'process.max-sigqueue-size', 'task.max-lwps',
    'task.max-processes',
]

# network tunables per device (see solaris.sh reference)
_NDD_KEYS = {
    '/dev/tcp': [
        'tcp_conn_req_max_q', 'tcp_conn_req_max_q0', 'tcp_xmit_hiwat',
        'tcp_recv_hiwat', 'tcp_max_buf', 'tcp_cwnd_max', 'tcp_smallest_anon_port',
        'tcp_largest_anon_port', 'tcp_time_wait_interval', 'tcp_keepalive_interval',
        'tcp_ip_abort_interval', 'tcp_rexmit_interval_initial',
        'tcp_rexmit_interval_min', 'tcp_rexmit_interval_max', 'tcp_naglim_def',
        'tcp_mss_def',
    ],
    '/dev/udp': [
        'udp_xmit_hiwat', 'udp_recv_hiwat', 'udp_max_buf', 'udp_smallest_anon_port',
        'udp_largest_anon_port', 'udp_do_checksum',
    ],
    '/dev/ip': [
        'ip_forwarding', 'ip_strict_dst_multihoming', 'ip_send_redirects',
        'ip_ignore_redirect', 'ip_respond_to_echo_broadcast',
        'ip_forward_directed_broadcasts', 'ip_path_mtu_discovery',
        'ip_ire_arp_interval', 'ip_ire_flush_interval', 'ip_icmp_err_interval',
    ],
    '/dev/arp': [
        'arp_cleanup_interval', 'arp_publish_count', 'arp_publish_interval',
        'arp_probe_delay',
    ],
}

_NDD_DEVICES = ['/dev/tcp', '/dev/udp', '/dev/ip', '/dev/arp']

_NDD_ERROR_RE = re.compile(
    r'non-existent|invalid|unknown|no such|not supported|'
    r'for a list of valid names|operation failed|permission denied|cannot|error',
    re.IGNORECASE)

_SET_RE = re.compile(r'^set\s+(.+?)\s*=\s*(.+)$')

_custom_params = []
_custom_lock = threading.Lock()


def SetCustomParams(params):
    global _custom_params
    with _custom_lock:
        _custom_params = list(params or [])


def _get_custom_params():
    with _custom_lock:
        return list(_custom_params)


def _read_etc_system():
    """Return {key: value} of "set key=value" lines from /etc/system."""
    values = {}
    path = '/etc/system'
    try:
        if not os.access(path, os.R_OK):
            return values
        with open(path, 'r') as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith('*'):
                    continue
                m = _SET_RE.match(line)
                if not m:
                    continue
                key = m.group(1).strip()
                val = m.group(2).strip().replace(',', ' ')
                if val:
                    values[key] = val
    except Exception as e:
        logging_util.error('os_parameter_solaris /etc/system read error:', e)
    return values


def _read_prctl():
    """Return {resource.privilege: value} for process/task resource controls."""
    values = {}
    try:
        out = process_util.executeCommandShellWithTimeout('prctl -P $$ 2>/dev/null', timeout=10)
    except Exception:
        return values
    if not out:
        return values
    want = set(_PRCTL_KEYS)
    for line in out.split('\n'):
        parts = line.split()
        if len(parts) < 3:
            continue
        res, priv, val = parts[0], parts[1], parts[2]
        if res in want and priv in ('basic', 'privileged', 'system') and val:
            values['%s.%s' % (res, priv)] = val.replace(',', ' ')
    return values


def _has_ndd():
    try:
        return bool(process_util.executeCommandShellWithTimeout(
            'command -v ndd 2>/dev/null', timeout=5))
    except Exception:
        return False


def _is_valid_ndd_value(value):
    if not value:
        return False
    if _NDD_ERROR_RE.search(value):
        return False
    if len(value.split('\n')) > 1:
        return False
    return True


def _get_ndd_value(device, key):
    try:
        out = process_util.executeCommandShellWithTimeout(
            'ndd -get %s %s 2>&1' % (device, key), timeout=10)
    except Exception:
        return None
    out = out.strip() if out else out
    if _is_valid_ndd_value(out):
        return out.replace(',', ' ')
    return None


def _collect():
    result = []
    seen = set()

    system_values = _read_etc_system()
    for key in _SYSTEM_KEYS:
        if key in system_values:
            result.append(OsParameter(key, 'system', OS_NAME, system_values[key], custom=False))
            seen.add((key, 'system'))

    for key, value in _read_prctl().items():
        result.append(OsParameter(key, 'prctl', OS_NAME, value, custom=False))
        seen.add((key, 'prctl'))

    have_ndd = _has_ndd()
    if have_ndd:
        for device, keys in _NDD_KEYS.items():
            for key in keys:
                value = _get_ndd_value(device, key)
                if value is None:
                    continue
                result.append(OsParameter(key, 'network', OS_NAME, value, custom=False))
                seen.add((key, 'network'))

    for param, category_hint in _get_custom_params():
        value = None
        found_category = None

        if param in system_values:
            value = system_values[param]
            found_category = 'system'
        elif have_ndd:
            for device in _NDD_DEVICES:
                value = _get_ndd_value(device, param)
                if value is not None:
                    found_category = 'network'
                    break

        if value is None:
            continue
        category = category_hint or found_category or 'custom'
        if (param, category) in seen:
            continue
        result.append(OsParameter(param, category, OS_NAME, value, custom=True))
        seen.add((param, category))

    return result


_collector = BackgroundCollector('os_parameter_solaris', _collect, interval=300)


def GetOsParameterList(custom_params=None):
    if custom_params is not None:
        SetCustomParams(custom_params)
    return _collector.get()
