blob: 6a096d25109d1cf14943c3ab73a1ac767af95e3e [file] [log] [blame]
/*
* /proc/uid support
*/
#include <linux/cpufreq_times.h>
#include <linux/fs.h>
#include <linux/hashtable.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/rtmutex.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include "internal.h"
static struct proc_dir_entry *proc_uid;
#define UID_HASH_BITS 10
static DECLARE_HASHTABLE(proc_uid_hash_table, UID_HASH_BITS);
/*
* use rt_mutex here to avoid priority inversion between high-priority readers
* of these files and tasks calling proc_register_uid().
*/
static DEFINE_RT_MUTEX(proc_uid_lock); /* proc_uid_hash_table */
struct uid_hash_entry {
uid_t uid;
struct hlist_node hash;
};
/* Caller must hold proc_uid_lock */
static bool uid_hash_entry_exists_locked(uid_t uid)
{
struct uid_hash_entry *entry;
hash_for_each_possible(proc_uid_hash_table, entry, hash, uid) {
if (entry->uid == uid)
return true;
}
return false;
}
void proc_register_uid(kuid_t kuid)
{
struct uid_hash_entry *entry;
bool exists;
uid_t uid = from_kuid_munged(current_user_ns(), kuid);
rt_mutex_lock(&proc_uid_lock);
exists = uid_hash_entry_exists_locked(uid);
rt_mutex_unlock(&proc_uid_lock);
if (exists)
return;
entry = kzalloc(sizeof(struct uid_hash_entry), GFP_KERNEL);
if (!entry)
return;
entry->uid = uid;
rt_mutex_lock(&proc_uid_lock);
if (uid_hash_entry_exists_locked(uid))
kfree(entry);
else
hash_add(proc_uid_hash_table, &entry->hash, uid);
rt_mutex_unlock(&proc_uid_lock);
}
struct uid_entry {
const char *name;
int len;
umode_t mode;
const struct inode_operations *iop;
const struct file_operations *fop;
};
#define NOD(NAME, MODE, IOP, FOP) { \
.name = (NAME), \
.len = sizeof(NAME) - 1, \
.mode = MODE, \
.iop = IOP, \
.fop = FOP, \
}
#ifdef CONFIG_CPU_FREQ_TIMES
static const struct file_operations proc_uid_time_in_state_operations = {
.open = single_uid_time_in_state_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
static const struct uid_entry uid_base_stuff[] = {
#ifdef CONFIG_CPU_FREQ_TIMES
NOD("time_in_state", 0444, NULL, &proc_uid_time_in_state_operations),
#endif
};
static const struct inode_operations proc_uid_def_inode_operations = {
.setattr = proc_setattr,
};
static struct inode *proc_uid_make_inode(struct super_block *sb, kuid_t kuid)
{
struct inode *inode;
inode = new_inode(sb);
if (!inode)
return NULL;
inode->i_ino = get_next_ino();
inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
inode->i_op = &proc_uid_def_inode_operations;
inode->i_uid = kuid;
return inode;
}
static int proc_uident_instantiate(struct inode *dir, struct dentry *dentry,
struct task_struct *unused, const void *ptr)
{
const struct uid_entry *u = ptr;
struct inode *inode;
inode = proc_uid_make_inode(dir->i_sb, dir->i_uid);
if (!inode)
return -ENOENT;
inode->i_mode = u->mode;
if (S_ISDIR(inode->i_mode))
set_nlink(inode, 2);
if (u->iop)
inode->i_op = u->iop;
if (u->fop)
inode->i_fop = u->fop;
d_add(dentry, inode);
return 0;
}
static struct dentry *proc_uid_base_lookup(struct inode *dir,
struct dentry *dentry,
unsigned int flags)
{
const struct uid_entry *u, *last;
unsigned int nents = ARRAY_SIZE(uid_base_stuff);
if (nents == 0)
return ERR_PTR(-ENOENT);
last = &uid_base_stuff[nents - 1];
for (u = uid_base_stuff; u <= last; u++) {
if (u->len != dentry->d_name.len)
continue;
if (!memcmp(dentry->d_name.name, u->name, u->len))
break;
}
if (u > last)
return ERR_PTR(-ENOENT);
return ERR_PTR(proc_uident_instantiate(dir, dentry, NULL, u));
}
static int proc_uid_base_readdir(struct file *file, struct dir_context *ctx)
{
unsigned int nents = ARRAY_SIZE(uid_base_stuff);
const struct uid_entry *u;
if (!dir_emit_dots(file, ctx))
return 0;
if (ctx->pos >= nents + 2)
return 0;
for (u = uid_base_stuff + (ctx->pos - 2);
u < uid_base_stuff + nents; u++) {
if (!proc_fill_cache(file, ctx, u->name, u->len,
proc_uident_instantiate, NULL, u))
break;
ctx->pos++;
}
return 0;
}
static const struct inode_operations proc_uid_base_inode_operations = {
.lookup = proc_uid_base_lookup,
.setattr = proc_setattr,
};
static const struct file_operations proc_uid_base_operations = {
.read = generic_read_dir,
.iterate = proc_uid_base_readdir,
.llseek = default_llseek,
};
static int proc_uid_instantiate(struct inode *dir, struct dentry *dentry,
struct task_struct *unused, const void *ptr)
{
unsigned int i, len;
nlink_t nlinks;
kuid_t *kuid = (kuid_t *)ptr;
struct inode *inode = proc_uid_make_inode(dir->i_sb, *kuid);
if (!inode)
return -ENOENT;
inode->i_mode = S_IFDIR | 0555;
inode->i_op = &proc_uid_base_inode_operations;
inode->i_fop = &proc_uid_base_operations;
inode->i_flags |= S_IMMUTABLE;
nlinks = 2;
len = ARRAY_SIZE(uid_base_stuff);
for (i = 0; i < len; ++i) {
if (S_ISDIR(uid_base_stuff[i].mode))
++nlinks;
}
set_nlink(inode, nlinks);
d_add(dentry, inode);
return 0;
}
static int proc_uid_readdir(struct file *file, struct dir_context *ctx)
{
int last_shown, i;
unsigned long bkt;
struct uid_hash_entry *entry;
if (!dir_emit_dots(file, ctx))
return 0;
i = 0;
last_shown = ctx->pos - 2;
rt_mutex_lock(&proc_uid_lock);
hash_for_each(proc_uid_hash_table, bkt, entry, hash) {
int len;
char buf[PROC_NUMBUF];
if (i < last_shown)
continue;
len = snprintf(buf, sizeof(buf), "%u", entry->uid);
if (!proc_fill_cache(file, ctx, buf, len,
proc_uid_instantiate, NULL, &entry->uid))
break;
i++;
ctx->pos++;
}
rt_mutex_unlock(&proc_uid_lock);
return 0;
}
static struct dentry *proc_uid_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
int result = -ENOENT;
uid_t uid = name_to_int(&dentry->d_name);
bool uid_exists;
rt_mutex_lock(&proc_uid_lock);
uid_exists = uid_hash_entry_exists_locked(uid);
rt_mutex_unlock(&proc_uid_lock);
if (uid_exists) {
kuid_t kuid = make_kuid(current_user_ns(), uid);
result = proc_uid_instantiate(dir, dentry, NULL, &kuid);
}
return ERR_PTR(result);
}
static const struct file_operations proc_uid_operations = {
.read = generic_read_dir,
.iterate = proc_uid_readdir,
.llseek = default_llseek,
};
static const struct inode_operations proc_uid_inode_operations = {
.lookup = proc_uid_lookup,
.setattr = proc_setattr,
};
int __init proc_uid_init(void)
{
proc_uid = proc_mkdir("uid", NULL);
if (!proc_uid)
return -ENOMEM;
proc_uid->proc_iops = &proc_uid_inode_operations;
proc_uid->proc_fops = &proc_uid_operations;
return 0;
}