$31 GRAYBYTE WORDPRESS FILE MANAGER $37

SERVER : vnpttt-amd7f72-h1.vietnix.vn #1 SMP Fri May 24 12:42:50 UTC 2024
SERVER IP : 103.200.23.149 | ADMIN IP 216.73.216.22
OPTIONS : CRL = ON | WGT = ON | SDO = OFF | PKEX = OFF
DEACTIVATED : NONE

/opt/cloudlinux/venv/lib/python3.11/site-packages/clwpos/

HOME
Current File : /opt/cloudlinux/venv/lib/python3.11/site-packages/clwpos//daemon_redis_lib.py
# coding=utf-8
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2021 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#

# Redis manipulation library for Cloudlinux AccelerateWP daemon

# pylint: disable=no-absolute-import
import json
import logging
import pwd
import os
import subprocess
import signal
import psutil
import time
from logging import Logger
from typing import List, Optional, Tuple

from clcommon.clpwd import drop_privileges
from clcommon.utils import (
    run_command,
    ExternalProgramFailed,
    is_user_present
)
from clcommon.cpapi import cpusers
from clcommon.cpapi import get_main_username_by_uid
from clwpos.constants import REDIS_SERVER_BIN_FILE

from clwpos.cl_wpos_exceptions import WposError
from clwpos.utils import (
    USER_WPOS_DIR,
    is_run_under_user,
    drop_permissions_if_needed,
    run_in_cagefs_if_needed
)
from clcommon.cpapi.cpapiexceptions import NoPackage
from clwpos import gettext as _


logger = logging.getLogger(__name__)

_REDIS_CLI_BIN_FILE = '/opt/alt/redis/bin/redis-cli'


def _get_pids_for_file(file_path: str) -> List[int]:
    """
    Retrieves list of PID list processes, which uses file (using fuser utility)
    This can find any process (for example php), not only redis service process
    :param file_path: Filename to check
    :return: PID list
    """
    # in most cases this is correct path
    fuser_binary = '/usr/bin/fuser'
    # fallback to prev approach
    if not os.path.exists(fuser_binary):
        fuser_binary = '/sbin/fuser'

    try:
        # # /usr/sbin/fuser /home/cltest1/.clwpos/redis.sock
        # /home/cltest1/.clwpos/redis.sock: 55882 [105766 251507]
        std_out = run_command([fuser_binary, file_path], return_full_output=False)
        lines_list = std_out.split('\n')
        # Get PID list from output
        s_pid_list = lines_list[0].split(':')[1].strip()
        pid_list = []
        for s_pid in s_pid_list.split(' '):
            try:
                pid_list.append(int(s_pid.strip()))
            except ValueError:
                pass
        return pid_list
    except (ExternalProgramFailed, IndexError):
        pass
    return []


def _get_user_pids(username: str) -> List[int]:
    """
    Update PID list in cache for user using /bin/ps utility
    :param: username: Username to scan
    :return: None
    """
    # /bin/ps -o"pid" -u cltest1
    # PID
    # 1608661
    # 1638657
    # ......
    # Get user's PID list
    try:
        std_out = run_command(['/bin/ps', '-o', 'pid', '-u', username], return_full_output=False)
    except ExternalProgramFailed:
        return []
    lines_list = std_out.split('\n')
    if len(lines_list) < 2:
        return []
    # Remove header line
    user_pid_list = []
    lines_list = lines_list[1:]
    for line in lines_list:
        line = line.strip()
        if line:
            try:
                user_pid_list.append(int(line.strip()))
            except ValueError:
                pass
    return user_pid_list


def _get_user_redis_pids(username: str, home_dir: str) -> List[int]:
    """
    Get redis PID list for user
    :param username: user name
    :param home_dir: User's  homedir
    :return: PID list or [] if user has no redis
    """
    redis_socket_file = os.path.join(home_dir, USER_WPOS_DIR, 'redis.sock')
    pid_list_sock = _get_pids_for_file(redis_socket_file)
    user_pids = _get_user_pids(username)
    pid_list = []
    for pid in pid_list_sock:
        if pid in user_pids:
            pid_list.append(pid)
    return pid_list


def kill_process_by_pid(_logger: Logger, pid: int):
    """
    Kill process by pid
    :param _logger: Logger to log errors
    :param pid: Process pid to kill
    """
    if not is_run_under_user():
        raise WposError("Internal error! Trying to kill process with root privileges")

    try:
        os.kill(pid, signal.SIGTERM)  # 15
        time.sleep(5)
        try:
            os.kill(pid, signal.SIGKILL)  # 9
        except OSError:
            pass
    except OSError as e:
        _logger.warning("Can't kill redis process, pid %s; error: %s", pid, str(e))

    _logger.info('Killed process with pid=%s', str(pid))


def _kill_all_redises_for_user(logger: Logger, username: str):
    """
    Kill all user's redice processes
    :param logger: Logger to log errors
    :param username: User name
    """
    if not is_user_present(username):
        return
    user_pwd = pwd.getpwnam(username)
    redis_pid_list = _get_user_redis_pids(user_pwd.pw_name, user_pwd.pw_dir)
    logger.info('Killing redis with pid=%s for user=%s', str(redis_pid_list), username)

    with drop_privileges(username):
        for redis_pid in redis_pid_list:
            kill_process_by_pid(logger, redis_pid)



def kill_all_users_redises(logger: Logger):
    """
    Find and kill lost redices for all panel users
    :param logger: Daemon's logger
    """
    try:
        users = cpusers()
    except (OSError, IOError, IndexError, NoPackage) as e:
        logger.warning("Can't get user list from panel: %s", str(e))
        return
    for username in users:
        _kill_all_redises_for_user(logger, username)

def redis_socket_health_check(uid: int) -> bool:
    """
    /opt/alt/redis/bin/redis-cli -s /home/cltest1/.clwpos/redis.sock ping
    Could not connect to Redis at /home/cltest1/.clwpos/redis.sock: No such file or directory
    echo $?
    1
    /opt/alt/redis/bin/redis-cli -s /home/cltest1/.clwpos/redis.sock ping
    PONG
    echo $?
    0
    """
    try:
        username = get_main_username_by_uid(uid)
        user_pwd = pwd.getpwnam(username)
    except KeyError:
        logger.warning("Redis check error for user %s. No user with such uid", str(uid))
        return False

    redis_socket_path = os.path.join(user_pwd.pw_dir, USER_WPOS_DIR, 'redis.sock')
    redis_ping_cmd = [_REDIS_CLI_BIN_FILE, '-s', redis_socket_path, 'ping']
    with drop_permissions_if_needed(username):
        output = run_in_cagefs_if_needed(redis_ping_cmd)
        logger.info('Redis health check for user=%s, return code=%s, stdout=%s, stderr=%s',
                    username,
                    str(output.returncode),
                    str(output.stdout),
                    str(output.stderr))
    return output.returncode == 0

def is_user_redis_alive(user_id: int) -> Tuple[bool, bool, dict]:
    """
    Check user's redis is alive
    :param user_id: uid to check sockets
    return True/False - redis alive/not alive
    :return: Tuple: (redis is working/not working, is user present, errors dict)
      error  - (False, False {"result": "error", "context": "..."})
    """
    try:
        user_pwd = pwd.getpwuid(user_id)
        username = user_pwd.pw_name
    except KeyError:
        logger.warning("Redis check error for user %s. No user with such uid", str(user_id))
        return False, False, {"result": _("Redis check error for user with uid %(uid)s. No such user"),
                              "context": {"uid": str(user_id)}}

    try:
        is_redis_alive = redis_socket_health_check(user_id)
    except Exception as e:
        logger.warning("Redis check error for user %s. Error is: %s", username, str(e))
        return False, True, { "result": _("Redis CLI start error %(error)s for user %(user)s"),
                              "context": { "error": str(e), "user": username } }

    if not is_redis_alive:
        # Process start error
        return False, True, {"result": _("Redis CLI check error %(error)s for user %(user)s"),
                             "context": {"error": "Redis is not pingable for user, most likely it is not started",
                                         "user": username}}
    return True, True, {"result": "success"}


def _get_redis_pid_from_pid_file_with_wait(redis_pid_filename: str) -> Optional[int]:
    """
    Get redis process PID from redis pid file. Wait up to 10 seconds
    :param redis_pid_filename: Redis PID filename
    :return: Redis PID or None on error (pid file absent/invalid or redis not started)
    """
    for i in range(100):
        try:
            with open(redis_pid_filename, 'r') as f:
                pid = int(f.read().strip())
                os.kill(pid, 0)
                return pid
        except (OSError, IOError, ValueError):
            # Error, PID file absent/invalid or redis still absent
            time.sleep(0.1)
    # Error, redis not started or pid file read/parse error
    return None


def reload_redis_for_user_thread(username: str,
                                 old_redis_pid: Optional[int],
                                 force_reload: str = 'no') -> Tuple[Optional[int], dict]:
    """
    Reloads redis for supplied user via helper script. Should be trun in thread
    :param username: Username to setup redis
    :param old_redis_pid: Old Redis PID for kill
    :param force_reload: reload redis w/o checks
    :return: Tuple:
      If redis was started for user - (PID of new redis process, {"result": "success"})
      else - redis was not started  - (None, {"result": "error", "context": ""})
    """
    try:
        user_pwd = pwd.getpwnam(username)
    except (KeyError, OSError, ):
        logger.debug("Can't reload redis for user '%s'. User not found.", username)
        return None, {"result": _("Can't reload redis for user '%(user)s'. User not found."),
                      "context": {"user": username}}
    logger.info('Calling redis_reloader with parameters: username: %s, old redis pid: %s, force reload: %s',
                 username,
                str(old_redis_pid),
                force_reload)
    try:
        # Run redis_reloader_script
        proc = subprocess.Popen(['/usr/share/cloudlinux/wpos/redis_reloader.py', username,
                                 str(old_redis_pid), force_reload],
                                shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        stdout, stderr = proc.communicate()
    except (OSError, IOError,) as e:
        logger.warning("Reload redis error for user '%s'. Error is %s", username, str(e))
        return None, {"result": _("Reload redis error for user '%(user)s'. Error is %(msg)s"),
                      "context": {"user": username, "msg": str(e)}}
    try:
        reload_result_dict = json.loads(stdout)
        if reload_result_dict['result'] != 'success':
            return None, reload_result_dict
    except (KeyError, json.JSONDecodeError, TypeError):
        return None, {"result": _("Reload redis for user '%(user)s' decode error: %(error_msg)s"),
                      "context": {"user": username, "error_msg": stdout}}
    # Redis was started, get PID
    pidfile_path = os.path.join(user_pwd.pw_dir, USER_WPOS_DIR, 'redis.pid')
    redis_pid = None
    if reload_result_dict.get('redis_enabled', True):
        redis_pid = _get_redis_pid_from_pid_file_with_wait(pidfile_path)
    return redis_pid, {"result": "success"}


def parse_redises() -> List[Tuple[int, int]]:
    """
    Get redis process by parsing psutil.process_iter
    Return list of tuples: [(user_uid, process_pid)]
    """
    res = []
    for proc in psutil.process_iter(['name']):
        if proc.info['name'] == 'redis-server':
            res.append(_validate_redis_proc(proc))
    return list(filter(None, res))


def _validate_redis_proc(p: psutil.Process) -> Optional[Tuple[int, int]]:
    """
    Ensure that redis process is ours:
        1. Right binary (alt-redis)
        2. Right socket
    """
    try:
        cmd = ' '.join(p.cmdline())

        # Check if process is ours redis server
        if not cmd.startswith(REDIS_SERVER_BIN_FILE):
            return None
        if '.clwpos/redis.sock' not in cmd:
            return None

        uid = p.uids().real
        username = get_main_username_by_uid(uid)

        pw = pwd.getpwnam(username)
        user_home = pw.pw_dir

        sock_abspath = f'unixsocket:{user_home}/.clwpos/redis.sock'

        if sock_abspath in cmd:
            return uid, p.pid
        return None
    except Exception as e:
        logger.warning(f'Failed to validate redis process (pid={p.pid}): {e}')

    return None

Current_dir [ NOT WRITEABLE ] Document_root [ WRITEABLE ]


[ Back ]
NAME
SIZE
LAST TOUCH
USER
CAN-I?
FUNCTIONS
..
--
17 Dec 2025 3.08 AM
root / root
0755
__pycache__
--
16 Dec 2025 9.38 PM
root / root
0755
bin
--
16 Dec 2025 9.31 PM
root / root
0755
cli_versions
--
16 Dec 2025 9.31 PM
root / root
0755
feature_suites
--
16 Dec 2025 9.31 PM
root / root
0755
hooks
--
16 Dec 2025 9.31 PM
root / root
0755
migrations
--
16 Dec 2025 9.31 PM
root / root
0755
object_cache
--
16 Dec 2025 9.31 PM
root / root
0755
optimization_features
--
16 Dec 2025 9.31 PM
root / root
0755
php
--
16 Dec 2025 9.31 PM
root / root
0755
user
--
16 Dec 2025 9.31 PM
root / root
0755
__init__.py
0.906 KB
29 Sep 2025 8.34 PM
root / root
0644
billing.py
6.242 KB
29 Sep 2025 8.34 PM
root / root
0644
cl_wpos_exceptions.py
3.591 KB
29 Sep 2025 8.34 PM
root / root
0644
constants.py
5.562 KB
29 Sep 2025 8.34 PM
root / root
0644
create_user_uid_dirs.py
0.736 KB
29 Sep 2025 8.34 PM
root / root
0644
cron.py
2.138 KB
29 Sep 2025 8.34 PM
root / root
0644
daemon.py
37.119 KB
29 Sep 2025 8.34 PM
root / root
0644
daemon_base.py
2.844 KB
29 Sep 2025 8.34 PM
root / root
0644
daemon_config.py
0.606 KB
29 Sep 2025 8.34 PM
root / root
0644
daemon_redis_lib.py
11.932 KB
29 Sep 2025 8.34 PM
root / root
0644
daemon_subscription_handler.py
6.438 KB
29 Sep 2025 8.34 PM
root / root
0644
data_collector_utils.py
9.418 KB
29 Sep 2025 8.34 PM
root / root
0644
logsetup.py
4.045 KB
29 Sep 2025 8.34 PM
root / root
0644
papi.py
9.867 KB
29 Sep 2025 8.34 PM
root / root
0644
parse.py
2.104 KB
29 Sep 2025 8.34 PM
root / root
0644
redis_configuration_pid_file_cleaner.py
1.013 KB
29 Sep 2025 8.34 PM
root / root
0755
report_generator.py
21.176 KB
29 Sep 2025 8.34 PM
root / root
0644
scoped_cache.py
1.34 KB
29 Sep 2025 8.34 PM
root / root
0644
socket_utils.py
4.029 KB
29 Sep 2025 8.34 PM
root / root
0644
stats.py
12.016 KB
29 Sep 2025 8.34 PM
root / root
0644
utils.py
54.336 KB
29 Sep 2025 8.34 PM
root / root
0644
whmcs_utils.py
9.361 KB
29 Sep 2025 8.34 PM
root / root
0644
wp_config.py
0.708 KB
29 Sep 2025 8.34 PM
root / root
0644
wp_utils.py
16.33 KB
29 Sep 2025 8.34 PM
root / root
0644
wpos_admin.py
67.14 KB
29 Sep 2025 8.34 PM
root / root
0644
wpos_hooks.py
4.854 KB
29 Sep 2025 8.34 PM
root / root
0755
wpos_req_scanner.py
4.38 KB
29 Sep 2025 8.34 PM
root / root
0644

GRAYBYTE WORDPRESS FILE MANAGER @ 2026 CONTACT ME
Static GIF