1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
4 */
5
6#include <linux/fs.h>
7#include <linux/proc_fs.h>
8#include <linux/seq_file.h>
9#include <linux/fdtable.h>
10#include <linux/sched/task.h>
11#include <linux/sched/signal.h>
12#include "../drivers/staging/android/ashmem.h"
13
14#define PURGEABLE_ASHMEM_SHRINKALL_ARG 0
15
16struct purgeable_ashmem_trigger_args {
17	struct seq_file *seq;
18	struct task_struct *tsk;
19};
20
21static int purgeable_ashmem_trigger_cb(const void *data,
22	struct file *f, unsigned int fd)
23{
24	const struct purgeable_ashmem_trigger_args *args = data;
25	struct task_struct *tsk = args->tsk;
26	struct purgeable_ashmem_metadata pmdata;
27
28	if (!is_ashmem_file(f))
29		return 0;
30	if (!get_purgeable_ashmem_metadata(f, &pmdata))
31		return 0;
32	if (pmdata.is_purgeable) {
33		pmdata.name = pmdata.name == NULL ? "" : pmdata.name;
34		seq_printf(args->seq,
35			"%s,%u,%u,%ld,%s,%zu,%u,%u,%d,%d\n",
36			tsk->comm, tsk->pid, fd, (long)tsk->signal->oom_score_adj,
37			pmdata.name, pmdata.size, pmdata.id, pmdata.create_time,
38			pmdata.refc, pmdata.purged);
39	}
40	return 0;
41}
42
43static ssize_t purgeable_ashmem_trigger_write(struct file *file,
44	const char __user *buffer, size_t count, loff_t *ppos)
45{
46	char *buf;
47	unsigned int ashmem_id = 0;
48	unsigned int create_time = 0;
49	const unsigned int params_num = 2;
50	const struct cred *cred = current_cred();
51
52	if (!cred)
53		return 0;
54
55	if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
56	    !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
57		pr_err("no permission to shrink purgeable ashmem!\n");
58		return 0;
59	}
60	buf = memdup_user_nul(buffer, count);
61	buf = strstrip(buf);
62	if (sscanf(buf, "%u %u", &ashmem_id, &create_time) != params_num)
63		return -EINVAL;
64	if (ashmem_id == PURGEABLE_ASHMEM_SHRINKALL_ARG &&
65	    create_time == PURGEABLE_ASHMEM_SHRINKALL_ARG)
66		ashmem_shrinkall();
67	else
68		ashmem_shrink_by_id(ashmem_id, create_time);
69	return count;
70}
71
72static int purgeable_ashmem_trigger_show(struct seq_file *s, void *d)
73{
74	struct task_struct *tsk = NULL;
75	struct purgeable_ashmem_trigger_args cb_args;
76	const struct cred *cred = current_cred();
77
78	if (!cred)
79		return -EINVAL;
80
81	if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
82	    !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
83		pr_err("no permission to shrink purgeable ashmem!\n");
84		return -EINVAL;
85	}
86	seq_puts(s, "Process purgeable ashmem detail info:\n");
87	seq_puts(s, "----------------------------------------------------\n");
88	seq_printf(s, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
89			"process_name", "pid", "adj", "fd",
90			"ashmem_name", "size", "id", "time", "ref_count", "purged");
91
92	ashmem_mutex_lock();
93	rcu_read_lock();
94	for_each_process(tsk) {
95		if (tsk->flags & PF_KTHREAD)
96			continue;
97		cb_args.seq = s;
98		cb_args.tsk = tsk;
99
100		task_lock(tsk);
101		iterate_fd(tsk->files, 0,
102			purgeable_ashmem_trigger_cb, (void *)&cb_args);
103		task_unlock(tsk);
104	}
105	rcu_read_unlock();
106	ashmem_mutex_unlock();
107	seq_puts(s, "----------------------------------------------------\n");
108	return 0;
109}
110
111static int purgeable_ashmem_trigger_open(struct inode *inode,
112	struct file *file)
113{
114	return single_open(file, purgeable_ashmem_trigger_show,
115					   inode->i_private);
116}
117
118static const struct proc_ops purgeable_ashmem_trigger_fops = {
119	.proc_open = purgeable_ashmem_trigger_open,
120	.proc_write = purgeable_ashmem_trigger_write,
121	.proc_read = seq_read,
122	.proc_lseek = seq_lseek,
123	.proc_release = single_release,
124};
125
126void init_purgeable_ashmem_trigger(void)
127{
128	struct proc_dir_entry *entry = NULL;
129
130	entry = proc_create_data("purgeable_ashmem_trigger", 0666,
131			NULL, &purgeable_ashmem_trigger_fops, NULL);
132	if (!entry)
133		pr_err("Failed to create purgeable ashmem trigger\n");
134}
135