|  | /* drivers/misc/uid_stat.c | 
|  | * | 
|  | * Copyright (C) 2008 - 2009 Google, Inc. | 
|  | * | 
|  | * This software is licensed under the terms of the GNU General Public | 
|  | * License version 2, as published by the Free Software Foundation, and | 
|  | * may be copied, distributed, and modified under those terms. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <asm/atomic.h> | 
|  |  | 
|  | #include <linux/err.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/list.h> | 
|  | #include <linux/proc_fs.h> | 
|  | #include <linux/seq_file.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/stat.h> | 
|  | #include <linux/uid_stat.h> | 
|  | #include <net/activity_stats.h> | 
|  |  | 
|  | static DEFINE_SPINLOCK(uid_lock); | 
|  | static LIST_HEAD(uid_list); | 
|  | static struct proc_dir_entry *parent; | 
|  |  | 
|  | struct uid_stat { | 
|  | struct list_head link; | 
|  | uid_t uid; | 
|  | atomic_t tcp_rcv; | 
|  | atomic_t tcp_snd; | 
|  | }; | 
|  |  | 
|  | static struct uid_stat *find_uid_stat(uid_t uid) { | 
|  | struct uid_stat *entry; | 
|  |  | 
|  | list_for_each_entry(entry, &uid_list, link) { | 
|  | if (entry->uid == uid) { | 
|  | return entry; | 
|  | } | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int uid_stat_atomic_int_show(struct seq_file *m, void *v) | 
|  | { | 
|  | unsigned int bytes; | 
|  | atomic_t *counter = m->private; | 
|  |  | 
|  | bytes = (unsigned int) (atomic_read(counter) + INT_MIN); | 
|  | return seq_printf(m, "%u\n", bytes); | 
|  | } | 
|  |  | 
|  | static int uid_stat_read_atomic_int_open(struct inode *inode, struct file *file) | 
|  | { | 
|  | return single_open(file, uid_stat_atomic_int_show, PDE_DATA(inode)); | 
|  | } | 
|  |  | 
|  | static const struct file_operations uid_stat_read_atomic_int_fops = { | 
|  | .open		= uid_stat_read_atomic_int_open, | 
|  | .read		= seq_read, | 
|  | .llseek		= seq_lseek, | 
|  | .release	= seq_release, | 
|  | }; | 
|  |  | 
|  | /* Create a new entry for tracking the specified uid. */ | 
|  | static struct uid_stat *create_stat(uid_t uid) { | 
|  | struct uid_stat *new_uid; | 
|  | /* Create the uid stat struct and append it to the list. */ | 
|  | new_uid = kmalloc(sizeof(struct uid_stat), GFP_ATOMIC); | 
|  | if (!new_uid) | 
|  | return NULL; | 
|  |  | 
|  | new_uid->uid = uid; | 
|  | /* Counters start at INT_MIN, so we can track 4GB of network traffic. */ | 
|  | atomic_set(&new_uid->tcp_rcv, INT_MIN); | 
|  | atomic_set(&new_uid->tcp_snd, INT_MIN); | 
|  |  | 
|  | list_add_tail(&new_uid->link, &uid_list); | 
|  | return new_uid; | 
|  | } | 
|  |  | 
|  | static void create_stat_proc(struct uid_stat *new_uid) | 
|  | { | 
|  | char uid_s[32]; | 
|  | struct proc_dir_entry *entry; | 
|  | sprintf(uid_s, "%d", new_uid->uid); | 
|  | entry = proc_mkdir(uid_s, parent); | 
|  |  | 
|  | /* Keep reference to uid_stat so we know what uid to read stats from. */ | 
|  | proc_create_data("tcp_snd", S_IRUGO, entry, | 
|  | &uid_stat_read_atomic_int_fops, &new_uid->tcp_snd); | 
|  |  | 
|  | proc_create_data("tcp_rcv", S_IRUGO, entry, | 
|  | &uid_stat_read_atomic_int_fops, &new_uid->tcp_rcv); | 
|  | } | 
|  |  | 
|  | static struct uid_stat *find_or_create_uid_stat(uid_t uid) | 
|  | { | 
|  | struct uid_stat *entry; | 
|  | unsigned long flags; | 
|  | spin_lock_irqsave(&uid_lock, flags); | 
|  | entry = find_uid_stat(uid); | 
|  | if (entry) { | 
|  | spin_unlock_irqrestore(&uid_lock, flags); | 
|  | return entry; | 
|  | } | 
|  | entry = create_stat(uid); | 
|  | spin_unlock_irqrestore(&uid_lock, flags); | 
|  | if (entry) | 
|  | create_stat_proc(entry); | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | int uid_stat_tcp_snd(uid_t uid, int size) { | 
|  | struct uid_stat *entry; | 
|  | activity_stats_update(); | 
|  | entry = find_or_create_uid_stat(uid); | 
|  | if (!entry) | 
|  | return -1; | 
|  | atomic_add(size, &entry->tcp_snd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int uid_stat_tcp_rcv(uid_t uid, int size) { | 
|  | struct uid_stat *entry; | 
|  | activity_stats_update(); | 
|  | entry = find_or_create_uid_stat(uid); | 
|  | if (!entry) | 
|  | return -1; | 
|  | atomic_add(size, &entry->tcp_rcv); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int __init uid_stat_init(void) | 
|  | { | 
|  | parent = proc_mkdir("uid_stat", NULL); | 
|  | if (!parent) { | 
|  | pr_err("uid_stat: failed to create proc entry\n"); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | __initcall(uid_stat_init); |