/*
htop - vmkernel/VMKernelProcessList.c
(C) 2014 Hisham H. Muhammad
Copyright 2015-2026 Rivoreo
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/

/*{
#include "ProcessList.h"

typedef struct {
	ProcessList super;

	uint32_t vsi_world_id;
	uint32_t vsi_world_info_id;
	uint32_t vsi_world_name_id;
	uint32_t vsi_sched_id;
	uint32_t vsi_sched_memclients_id;
	uint32_t vsi_sched_memclients_memstats_id;
	uint32_t vsi_sched_memclients_memstats_common_id;
	uint32_t vsi_sched_memclients_memstats_uw_id;
	uint32_t vsi_userworld_id;
	uint32_t vsi_userworld_cartel_id;
	uint32_t vsi_userworld_cartel_cmdline_id;
	uint64_t vsi_world_cksum;
	uint64_t vsi_world_info_cksum;
	uint64_t vsi_world_name_cksum;
	uint64_t vsi_sched_cksum;
	uint64_t vsi_sched_memclients_cksum;
	uint64_t vsi_sched_memclients_memstats_cksum;
	uint64_t vsi_sched_memclients_memstats_common_cksum;
	uint64_t vsi_sched_memclients_memstats_uw_cksum;
	uint64_t vsi_userworld_cksum;
	uint64_t vsi_userworld_cartel_cksum;
	uint64_t vsi_userworld_cartel_cmdline_cksum;

	size_t world_info_size;
} VMKernelProcessList;
}*/

#include "VMKernelProcessList.h"
#include "VMKernelProcess.h"
#include "StringUtils.h"
#include "Settings.h"
#include "Platform.h"
#include "CRT.h"
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>

struct world_info {
	uint32_t world_id;
	uint32_t group_id;
	uint32_t userspace_id;
	uint32_t cartel_id;
	uint32_t parent_cartel_id;
	uint32_t cartel_group_id;
	uint32_t session_id;
	uint32_t flags;
	uint32_t ref_count;
	uint32_t reap_calls;
	uint8_t scsi_active;
	uint8_t security_domain;
#if 0
	uint8_t _unknown[26];
#else
	uint8_t _unknown[30];
#endif
};

// Size values are in kibibyte
struct memstats_common_32 {
	uint32_t reservation;
	uint32_t limit;
	uint32_t reservation_limit;
	int32_t shares;
	uint32_t max_mem_size;
	uint32_t size;
	uint32_t touched;
	uint32_t written;
	uint32_t current_size;
	uint32_t target_size;
	uint32_t overhead;
	uint32_t overhead_touched;
	uint32_t overhead_max;
	uint32_t zero;
	uint32_t poison;
	uint32_t swapped;
	uint32_t zipped;
	uint32_t llswapped;
	uint32_t llswap_used;
	uint32_t mapped;
	uint32_t copy_on_write_mapped;
	uint32_t shared_saved;
	uint32_t zip_saved;
	uint32_t commit_target;
	uint32_t swap_target;
	uint64_t pages_per_share;
	uint32_t swap_scope;
	uint32_t _reserved_1;
	uint8_t use_reliable_mem;
	uint32_t min_commit_target;
	uint32_t commit_charged;
	union {
		struct {
			uint8_t client_responsive;
		} __attribute__((__packed__)) v5;
		struct {
			uint32_t _reserved_2;
			uint8_t client_responsive;
			uint8_t _reserved_3[2];
		} __attribute__((__packed__)) v6;
	};
} __attribute__((__packed__));

// Size values are in kibibyte
struct memstats_common_64 {
	uint64_t reservation;
	uint64_t limit;
	uint64_t reservation_limit;
	int32_t shares;
	uint64_t max_mem_size;
	uint64_t size;
	uint64_t touched;
	uint64_t written;
	uint64_t current_size;
	uint64_t target_size;
	uint64_t overhead;
	uint64_t overhead_touched;
	uint64_t overhead_max;
	uint64_t zero;
	uint64_t poison;
	uint64_t swapped;
	uint64_t zipped;
	uint64_t llswapped;
	uint64_t ro_vpmem;
	uint64_t rw_vpmem;
	uint64_t uncached_vpmem;
	uint64_t llswap_used;
	uint64_t mapped;
	uint64_t copy_on_write_mapped;
	uint64_t shared_saved;
	uint64_t zip_saved;
	uint64_t commit_target;
	uint64_t swap_target;
	uint64_t pages_per_share;
	uint32_t swap_scope;
	uint32_t _reserved_1;
	uint8_t use_reliable_mem;
	uint64_t consumed;
	uint64_t min_commit_target;
	uint64_t commit_charged;
	uint8_t client_responsive;
	uint8_t _reserved_2[2];
} __attribute__((__packed__));

size_t _check_common_32 = sizeof(struct memstats_common_32);

#if 0
struct memstats_uw_64 {
	struct memstats_common64 common;
	uint64_t total_reserve;
	uint64_t pinned_reserve;
	uint64_t mem_bstore;
	uint64_t swap_bstore;
	uint64_t swappable;
	uint64_t shm;
	uint64_t cow;
	uint64_t pageable;
	uint64_t pageable_unacct;
	uint64_t swapped;
	uint64_t pinned;
	uint64_t prealloced;
} __attribute__((__packed__));
#endif

ProcessList* ProcessList_new(UsersTable* usersTable, const Hashtable *pidWhiteList, uid_t userId) {
   VMKernelProcessList *this = xCalloc(1, sizeof(VMKernelProcessList));
   ProcessList_init(&this->super, Class(VMKernelProcess), usersTable, pidWhiteList, userId);

   int e = Platform_vsiGetNodeIdAndChecksum(0, "world", &this->vsi_world_id, &this->vsi_world_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo world", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_world_id, "info",
      &this->vsi_world_info_id, &this->vsi_world_info_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo world.info", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_world_id, "name",
      &this->vsi_world_name_id, &this->vsi_world_name_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo world.name", e);
   e = Platform_vsiGetNodeIdAndChecksum(0, "sched", &this->vsi_sched_id, &this->vsi_sched_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo sched", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_sched_id, "memClients",
      &this->vsi_sched_memclients_id, &this->vsi_sched_memclients_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo sched.memClients", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_sched_memclients_id, "memStats",
      &this->vsi_sched_memclients_memstats_id, &this->vsi_sched_memclients_memstats_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo sched.memClients.memStats", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_sched_memclients_memstats_id, "totalCommon",
      &this->vsi_sched_memclients_memstats_common_id, &this->vsi_sched_memclients_memstats_common_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo sched.memClients.memStats.totalCommon", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_sched_memclients_memstats_id, "uw",
      &this->vsi_sched_memclients_memstats_uw_id, &this->vsi_sched_memclients_memstats_uw_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo sched.memClients.memStats.uw", e);
   e = Platform_vsiGetNodeIdAndChecksum(0, "userworld",
      &this->vsi_userworld_id, &this->vsi_userworld_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo userworld", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_userworld_id, "cartel",
      &this->vsi_userworld_cartel_id, &this->vsi_userworld_cartel_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo userworld.cartel", e);
   e = Platform_vsiGetNodeIdAndChecksum(this->vsi_userworld_cartel_id, "cmdline",
      &this->vsi_userworld_cartel_cmdline_id, &this->vsi_userworld_cartel_cmdline_cksum);
   if(e) CRT_fatalError("VSI_GetNodeInfo userworld.cartel.cmdline", e);

   Platform_checkVMkernelVersion();
   switch(Platform_running_vmkernel_version) {
      case VMKERNEL_VERSION_5_5:
      case VMKERNEL_VERSION_6_7:
         this->world_info_size = 68;
         break;
      case VMKERNEL_VERSION_6_0:
      case VMKERNEL_VERSION_6_5:
         this->world_info_size = 72;
         break;
   }

   return &this->super;
}

void ProcessList_delete(ProcessList* this) {
   ProcessList_done(this);
   free(this);
}

static bool get_world_info(const VMKernelProcessList *this, Process *proc) {
	size_t list_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	struct vsi_list *list = xMalloc(list_size);
	memset(list, 0, list_size);
	list->type_or_version = 1;
	list->allocated_count = 1;
	list->param_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	list->self_ptr = (uintptr_t)list;
	Platform_vsiListAddInt(list, proc->pid);
	struct world_info info;
	assert(this->world_info_size <= sizeof info);
	int e = Platform_vsiGet(this->vsi_world_info_id, this->vsi_world_info_cksum,
		list, &info, this->world_info_size);
	free(list);
	if(e) return false;
	VMKernelProcess *vmk_proc = (VMKernelProcess *)proc;
	vmk_proc->group_id = info.group_id;
	vmk_proc->userspace_id = info.userspace_id;
	proc->tgid = info.cartel_id;
	proc->ppid = info.parent_cartel_id;
	vmk_proc->cartel_group_id = info.cartel_group_id;
	proc->session = info.session_id;
	vmk_proc->is_kernel_process = info.flags & WORLD_SYSTEM;
	return true;
}

static void get_world_name(const VMKernelProcessList *this, Process *proc) {
	size_t list_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	struct vsi_list *list = xMalloc(list_size);
	memset(list, 0, list_size);
	list->type_or_version = 1;
	list->allocated_count = 1;
	list->param_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	list->self_ptr = (uintptr_t)list;
	Platform_vsiListAddInt(list, proc->pid);
	char buffer[128];
	int e = Platform_vsiGet(this->vsi_world_name_id, this->vsi_world_name_cksum,
		list, buffer, sizeof buffer);
	free(list);
	proc->name = xStrdup(e ? "" : buffer);
}

static bool get_cartel_command(const VMKernelProcessList *this, Process *proc) {
	if(proc->tgid <= 0) return false;
	size_t list_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	struct vsi_list *list = xMalloc(list_size);
	memset(list, 0, list_size);
	list->type_or_version = 1;
	list->allocated_count = 1;
	list->param_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	list->self_ptr = (uintptr_t)list;
	Platform_vsiListAddInt(list, proc->tgid);
	char buffer[1024];
	int e = Platform_vsiGet(this->vsi_userworld_cartel_cmdline_id,
		this->vsi_userworld_cartel_cmdline_cksum, list, buffer, sizeof buffer);
	free(list);
	if(e) return false;
	proc->comm = xStrdup(buffer);
	return true;
}

static void get_memory_stats(const VMKernelProcessList *this, Process *proc) {
	size_t list_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	struct vsi_list *list = xMalloc(list_size);
	memset(list, 0, list_size);
	list->type_or_version = 1;
	list->allocated_count = 1;
	list->param_size = sizeof(struct vsi_list) + sizeof(struct vsi_param);
	list->self_ptr = (uintptr_t)list;
	Platform_vsiListAddInt(list, proc->pid);
	switch(Platform_running_vmkernel_version) {
			struct memstats_common_32 common32;
			struct memstats_common_64 common64;
			int e;
		case VMKERNEL_VERSION_5_5:
			assert(128 < sizeof common32);
			e = Platform_vsiGet(this->vsi_sched_memclients_memstats_common_id,
				this->vsi_sched_memclients_memstats_common_cksum, list,
				&common32, 128);
			goto copy_common32_values;
		case VMKERNEL_VERSION_6_0:
			assert(132 == sizeof common32);
			e = Platform_vsiGet(this->vsi_sched_memclients_memstats_common_id,
				this->vsi_sched_memclients_memstats_common_cksum, list,
				&common32, 132);
		copy_common32_values:
			if(e) goto failure;
			proc->m_size = common32.size / CRT_page_size_kibibyte;
			proc->m_resident = common32.mapped / CRT_page_size_kibibyte;
			break;
		case VMKERNEL_VERSION_6_5:
		case VMKERNEL_VERSION_6_7:
			e = Platform_vsiGet(this->vsi_sched_memclients_memstats_common_id,
				this->vsi_sched_memclients_memstats_common_cksum, list,
				&common64, sizeof common64);
			if(e) goto failure;
			proc->m_size = common64.size / CRT_page_size_kibibyte;
			proc->m_resident = common64.mapped / CRT_page_size_kibibyte;
			break;
		failure:
			proc->m_size = 0;
			proc->m_resident = 0;
			break;
		default:
			abort();
	}
	free(list);
}

void ProcessList_goThroughEntries(ProcessList *super, bool skip_processes) {
	VMKernelProcessList *this = (VMKernelProcessList *)super;
	struct vsi_list *worlds_list = xMalloc(sizeof(struct vsi_list));
	memset(worlds_list, 0, sizeof(struct vsi_list));
	worlds_list->type_or_version = 1;
	worlds_list->param_size = sizeof(struct vsi_list);
	worlds_list->self_ptr = (uintptr_t)worlds_list;
	int e = Platform_vsiGetList(this->vsi_world_id, this->vsi_world_cksum,
		worlds_list, sizeof(struct vsi_list));
	if(e) CRT_fatalError("VSI_GetList", e);
	size_t count = worlds_list->instance_count;
	worlds_list->instance_count = 0;
	size_t param_size = sizeof(struct vsi_list) + sizeof(struct vsi_param) * count;
	size_t string_size = 128 * count;
	size_t list_size = param_size + string_size;
	worlds_list = xRealloc(worlds_list, list_size);
	worlds_list->allocated_count = count;
	worlds_list->param_size = param_size;
	worlds_list->string_size = string_size;
	worlds_list->string_offset = param_size;
	worlds_list->self_ptr = (uintptr_t)worlds_list;
	e = Platform_vsiGetList(this->vsi_world_id, this->vsi_world_cksum, worlds_list, list_size);
	if(e) CRT_fatalError("VSI_GetList", e);
	for(size_t i = 0; i < worlds_list->instance_count; i++) {
		pid_t pid = Platform_vsiListGetValue(worlds_list, i);
		bool is_existing;
		Process *proc = ProcessList_getProcess(super, pid, &is_existing, (Process_New)VMKernelProcess_new);
		if(!get_world_info(this, proc)) {
			if(!is_existing) Process_delete((Object *)proc);
			continue;
		}
		if(!is_existing) {
			proc->state = '?';
			ProcessList_add(super, proc);
		}
		get_memory_stats(this, proc);
		if(!is_existing || ProcessList_shouldUpdateProcessNames(super)) {
			free(proc->name);
			free(proc->comm);
			get_world_name(this, proc);
			if(!get_cartel_command(this, proc)) proc->comm = xStrdup(proc->name);
		}
		if(Process_isKernelProcess(proc)) {
			super->kernel_process_count++;
			super->kernel_thread_count++;
			if(proc->tgid == 0) proc->tgid = pid;
		}
		super->totalTasks++;
		proc->updated = true;
	}
	free(worlds_list);
}
