d86fcfc0创建于 2023年5月14日历史提交
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 */

#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/fdtable.h>
#include <linux/sched/task.h>
#include <linux/sched/signal.h>
#include "../drivers/staging/android/ashmem.h"

#define PURGEABLE_ASHMEM_SHRINKALL_ARG 0

struct purgeable_ashmem_trigger_args {
	struct seq_file *seq;
	struct task_struct *tsk;
};

static int purgeable_ashmem_trigger_cb(const void *data,
	struct file *f, unsigned int fd)
{
	const struct purgeable_ashmem_trigger_args *args = data;
	struct task_struct *tsk = args->tsk;
	struct purgeable_ashmem_metadata pmdata;

	if (!is_ashmem_file(f))
		return 0;
	if (!get_purgeable_ashmem_metadata(f, &pmdata))
		return 0;
	if (pmdata.is_purgeable) {
		pmdata.name = pmdata.name == NULL ? "" : pmdata.name;
		seq_printf(args->seq,
			"%s,%u,%u,%ld,%s,%zu,%u,%u,%d,%d\n",
			tsk->comm, tsk->pid, fd, (long)tsk->signal->oom_score_adj,
			pmdata.name, pmdata.size, pmdata.id, pmdata.create_time,
			pmdata.refc, pmdata.purged);
	}
	return 0;
}

static ssize_t purgeable_ashmem_trigger_write(struct file *file,
	const char __user *buffer, size_t count, loff_t *ppos)
{
	char *buf;
	unsigned int ashmem_id = 0;
	unsigned int create_time = 0;
	const unsigned int params_num = 2;
	const struct cred *cred = current_cred();

	if (!cred)
		return 0;

	if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
	    !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
		pr_err("no permission to shrink purgeable ashmem!\n");
		return 0;
	}
	buf = memdup_user_nul(buffer, count);
	buf = strstrip(buf);
	if (sscanf(buf, "%u %u", &ashmem_id, &create_time) != params_num)
		return -EINVAL;
	if (ashmem_id == PURGEABLE_ASHMEM_SHRINKALL_ARG &&
	    create_time == PURGEABLE_ASHMEM_SHRINKALL_ARG)
		ashmem_shrinkall();
	else
		ashmem_shrink_by_id(ashmem_id, create_time);
	return count;
}

static int purgeable_ashmem_trigger_show(struct seq_file *s, void *d)
{
	struct task_struct *tsk = NULL;
	struct purgeable_ashmem_trigger_args cb_args;
	const struct cred *cred = current_cred();

	if (!cred)
		return -EINVAL;

	if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
	    !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
		pr_err("no permission to shrink purgeable ashmem!\n");
		return -EINVAL;
	}
	seq_puts(s, "Process purgeable ashmem detail info:\n");
	seq_puts(s, "----------------------------------------------------\n");
	seq_printf(s, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
			"process_name", "pid", "adj", "fd",
			"ashmem_name", "size", "id", "time", "ref_count", "purged");

	ashmem_mutex_lock();
	rcu_read_lock();
	for_each_process(tsk) {
		if (tsk->flags & PF_KTHREAD)
			continue;
		cb_args.seq = s;
		cb_args.tsk = tsk;

		task_lock(tsk);
		iterate_fd(tsk->files, 0,
			purgeable_ashmem_trigger_cb, (void *)&cb_args);
		task_unlock(tsk);
	}
	rcu_read_unlock();
	ashmem_mutex_unlock();
	seq_puts(s, "----------------------------------------------------\n");
	return 0;
}

static int purgeable_ashmem_trigger_open(struct inode *inode,
	struct file *file)
{
	return single_open(file, purgeable_ashmem_trigger_show,
					   inode->i_private);
}

static const struct proc_ops purgeable_ashmem_trigger_fops = {
	.proc_open = purgeable_ashmem_trigger_open,
	.proc_write = purgeable_ashmem_trigger_write,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release,
};

void init_purgeable_ashmem_trigger(void)
{
	struct proc_dir_entry *entry = NULL;

	entry = proc_create_data("purgeable_ashmem_trigger", 0666,
			NULL, &purgeable_ashmem_trigger_fops, NULL);
	if (!entry)
		pr_err("Failed to create purgeable ashmem trigger\n");
}