+/*
+ * PASS (Power Aware System Service) - Process Group Resource Driver
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <util/log.h>
+#include <util/resource.h>
+#include <util/kernel.h>
+
+#include <tmonitor/tmonitor.h>
+
+#define PROC_DIR_PATH "/proc/"
+
+struct process_info {
+ pid_t pid;
+ pid_t ppid;
+ struct process_info *parent;
+};
+
+static int process_group_get_list(const struct resource *res,
+ const struct resource_attribute *attr,
+ void *data)
+{
+ struct process_info *pi, *target_pi, *parent_pi;
+ struct dirent *task_entry;
+ struct array_value *list = data;
+ char *statfields[PROCESS_STAT_FIELD_MAX];
+ char buf[BUFF_MAX];
+ GHashTableIter iter;
+ gpointer key, value;
+ GHashTable *process_hash;
+ GArray *process_array;
+ DIR *task_dir;
+ int i, ret = 0;
+ int *data_array;
+ pid_t pid;
+
+ if (!res || !res->priv || !attr || !data)
+ return -EINVAL;
+
+ task_dir = opendir(PROC_DIR_PATH);
+ if (!task_dir)
+ return -ESRCH;
+
+ process_hash = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, free);
+ if (!process_hash) {
+ ret = -ENOMEM;
+ goto out_close;
+ }
+
+ /* create list of all processes */
+ while ((task_entry = readdir(task_dir)) != NULL) {
+ const char *name = task_entry->d_name;
+
+ if (task_entry->d_type != DT_DIR && task_entry->d_type != DT_UNKNOWN)
+ continue;
+
+ if (name[0] < '0' || name[0] > '9')
+ continue;
+
+ pid = atoi(name);
+
+ ret = kernel_get_process_stat_fields(pid, buf, BUFF_MAX, statfields);
+ if (ret < 0)
+ continue; /* process might be terminated */
+
+ pi = calloc(1, sizeof(struct process_info));
+ if (!pi) {
+ ret = -ENOMEM;
+ goto out_free_hash;
+ }
+
+ pi->pid = pid;
+ pi->ppid = atoi(statfields[PROCESS_STAT_FIELD_PPID]);
+
+ if (g_hash_table_contains(process_hash, (gpointer)&pi->ppid)) {
+ parent_pi = g_hash_table_lookup(process_hash, (gpointer)&pi->ppid);
+
+ pi->parent = parent_pi;
+ }
+
+ g_hash_table_insert(process_hash, (gpointer)&pi->pid, (gpointer)pi);
+ }
+
+ target_pi = res->priv;
+
+ process_array = g_array_new(false, false, sizeof(int));
+ if (!process_array) {
+ ret = -ENOMEM;
+ goto out_free_hash;
+ }
+
+ if (target_pi->pid < 0) {
+ /* just add all processes into array if parent pid is negative */
+ g_hash_table_iter_init(&iter, process_hash);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ pi = (struct process_info *)value;
+ g_array_append_val(process_array, pi->pid);
+ }
+ } else {
+ g_hash_table_iter_init(&iter, process_hash);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ pi = (struct process_info *)value;
+
+ if (target_pi->pid == pi->pid || target_pi->pid == pi->ppid) {
+ g_array_append_val(process_array, pi->pid);
+ continue;
+ }
+
+ parent_pi = pi->parent;
+ while (parent_pi != NULL) {
+ if (parent_pi->ppid == target_pi->pid) {
+ g_array_append_val(process_array, pi->pid);
+ break;
+ }
+ parent_pi = parent_pi->parent;
+ }
+ }
+ }
+
+ if (list->data)
+ free(list->data);
+
+ data_array = list->data = malloc(sizeof(int) * process_array->len);
+ if (!data_array) {
+ ret = -ENOMEM;
+ goto out_free_array;
+ }
+ list->type = DATA_TYPE_INT;
+ list->length = process_array->len;
+
+ for (i = 0; i < list->length; i++)
+ data_array[i] = g_array_index(process_array, int, i);
+
+out_free_array:
+ g_array_free(process_array, true);
+out_free_hash:
+ g_hash_table_destroy(process_hash);
+out_close:
+ closedir(task_dir);
+
+ return ret;
+}
+
+static const struct resource_attribute process_group_attrs[] = {
+ {
+ .name = "PROCESS_GROUP_ATTR_LIST",
+ .id = PROCESS_GROUP_ATTR_LIST,
+ .type = DATA_TYPE_ARRAY,
+ .ops = {
+ .get = process_group_get_list,
+ .is_supported = resource_attr_supported_always,
+ },
+ },
+};
+
+static int process_group_setup_root_pid(const struct resource *res,
+ const struct resource_control *ctrl,
+ const void *data)
+{
+ struct process_info *target;
+ char *statfields[PROCESS_STAT_FIELD_MAX];
+ char buf[BUFF_MAX];
+ int target_pid;
+ int ret;
+
+ if (!res || !res->priv || !ctrl)
+ return -EINVAL;
+
+ target = res->priv;
+
+ target_pid = (int)(intptr_t)data;
+
+ ret = kernel_get_process_stat_fields(target_pid, buf, BUFF_MAX, statfields);
+ if (ret < 0) {
+ _E("target process pid is not valid");
+ target->pid = -1;
+ target->ppid = -1;
+ return ret;
+ }
+
+ target->pid = atoi(statfields[PROCESS_STAT_FIELD_PID]);
+ target->ppid = atoi(statfields[PROCESS_STAT_FIELD_PPID]);
+
+ return 0;
+}
+
+static const struct resource_control process_group_ctrls[] = {
+ {
+ .name = "PROCESS_GROUP_CTRL_ROOT_PID",
+ .id = PROCESS_GROUP_CTRL_ROOT_PID,
+ .ops = {
+ .set = process_group_setup_root_pid,
+ },
+ },
+};
+
+static int process_group_init(struct resource *res)
+{
+ struct process_info *target;
+
+ target = malloc(sizeof(struct process_info));
+ if (!target)
+ return -ENOMEM;
+
+ target->pid = -1;
+ target->ppid = -1;
+
+ res->priv = target;
+
+ return 0;
+}
+
+static void process_group_exit(struct resource *res)
+{
+ if (res && res->priv)
+ free(res->priv);
+}
+
+static const struct resource_driver process_group_driver = {
+ .name = "PROCESS_GROUP",
+ .type = RESOURCE_TYPE_PROCESS_GROUP,
+ .attrs = process_group_attrs,
+ .num_attrs = ARRAY_SIZE(process_group_attrs),
+ .ctrls = process_group_ctrls,
+ .num_ctrls = ARRAY_SIZE(process_group_ctrls),
+ .ops = {
+ .init = process_group_init,
+ .exit = process_group_exit,
+ },
+};
+RESOURCE_DRIVER_REGISTER(&process_group_driver)