/*
* Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mount.h"
#define DEFINE_DLOG(_name) extern int fslog_##_name(const char *fmt, ...)
DEFINE_DLOG(dlog_mm);
DEFINE_DLOG(dlog_efs);
DEFINE_DLOG(dlog_etc);
DEFINE_DLOG(dlog_rmdir);
#define EXT_SHIFT 7
#define MAX_EXT (1 << 7)
#define MAX_DEPTH 2
#define MEDIA_MIN_SIZE (512 * 1024)
struct dlog_keyword {
struct hlist_node hlist;
const char *keyword;
};
struct dlog_keyword_hash_tbl {
DECLARE_HASHTABLE(table, 7);
};
enum {
DLOG_HT_EXTENSION = 0,
DLOG_HT_EXCEPTION,
DLOG_HT_MAX
};
enum {
DLOG_SUPP_PART_DATA = 0,
DLOG_SUPP_PART_EFS,
DLOG_SUPP_PART_FUSE,
DLOG_SUPP_PART_MAX
};
enum {
DLOG_MM = 0,
DLOG_EFS,
DLOG_ETC,
DLOG_RMDIR
};
static struct dlog_keyword_hash_tbl ht[DLOG_HT_MAX];
static const char *support_part[] = {
"data", "efs", "emulated", NULL,
};
static const char *extensions[] = {
/* image */
"arw", "bmp", "cr2", "dng", "gif",
"jpeg", "jpg", "nef", "nrw", "orf",
"pef", "png", "raf", "rw2", "srw",
"wbmp", "webp",
/* audio */
"3ga", "aac", "amr",
"awb", "dff", "dsf", "flac", "imy",
"m4a", "mid", "midi", "mka", "mp3",
"mpga", "mxmf", "oga", "ogg", "ota",
"rtttl", "rtx", "smf", "wav", "wma",
"xmf", "dcf",
/* video */
"3g2", "3gp", "3gpp", "3gpp2",
"ak3g", "asf", "avi", "divx", "flv",
"m2t", "m2ts", "m4v", "mkv", "mp4",
"mpeg", "mpg", "mts", "skm", "tp",
"trp", "ts", "webm", "wmv",
/* document */
"asc",
"csv", "doc", "docm", "docx", "dot",
"dotx", "htm", "html", "hwdt", "hwp",
"hwpx", "hwt", "memo", "pdf", "pot",
"potx", "pps", "ppsx", "ppt", "pptm",
"pptx", "rtf", "sdoc", "snb", "spd",
"xls", "xlsm", "xlsx", "xlt", "xltx",
"xml",
NULL,
};
static const char *exceptions[] = {
/* exception extension */
"db-shm", "shm", "bak", "mbak", "gz",
"log", "swap", "dcs", "tmp", "temp",
"txt", NULL,
};
static const char **dlog_keyword_tbl[DLOG_HT_MAX] = {
extensions, exceptions
};
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
static inline unsigned int dlog_full_name_hash(const char *name, unsigned int len)
{
return full_name_hash((void *)0x0, name, len);
}
#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) */
static inline unsigned int dlog_full_name_hash(const char *name, unsigned int len)
{
return full_name_hash(name, len);
}
#endif
bool is_ext(const char *name, const char token, struct dlog_keyword_hash_tbl *hash_tbl)
{
char *ext = strrchr(name, token);
struct dlog_keyword *hash_cur;
unsigned hash;
int i;
bool ret = false;
if (!ext || !ext[1])
return false;
ext = kstrdup(&ext[1], GFP_KERNEL);
if (!ext)
return false;
for (i = 0; ext[i]; i++)
ext[i] = tolower(ext[i]);
hash = dlog_full_name_hash(ext, strlen(ext));
hash_for_each_possible(hash_tbl->table, hash_cur, hlist, hash) {
if (!strcmp(ext, hash_cur->keyword)) {
ret = true;
break;
}
}
kfree(ext);
return ret;
}
int __init dlog_keyword_hash_init(void)
{
int i;
int tbl_idx = 0;
do {
hash_init(ht[tbl_idx].table);
for (i = 0; dlog_keyword_tbl[tbl_idx][i]; i++) {
struct dlog_keyword *hte;
hte = kzalloc(sizeof(struct dlog_keyword), GFP_KERNEL);
if (!hte)
return -ENOMEM;
INIT_HLIST_NODE(&hte->hlist);
hte->keyword = dlog_keyword_tbl[tbl_idx][i];
hash_add(ht[tbl_idx].table, &hte->hlist,
dlog_full_name_hash(hte->keyword, strlen(hte->keyword)));
}
tbl_idx++;
} while(tbl_idx < DLOG_HT_MAX);
return 0;
}
module_init(dlog_keyword_hash_init);
void __exit dlog_keyword_hash_exit(void)
{
int i;
int num_ht = 0;
struct dlog_keyword *hash_cur;
do {
hash_for_each(ht[num_ht].table, i, hash_cur, hlist)
kfree(hash_cur);
} while(++num_ht < DLOG_HT_MAX);
}
module_exit(dlog_keyword_hash_exit);
static int get_support_part_id(struct vfsmount *mnt)
{
int idx = 0;
struct mount *mount = real_mount(mnt);
/* check partition which need register delete log */
do {
if (!strcmp(mount->mnt_mountpoint->d_name.name, support_part[idx]))
return idx;
} while(support_part[++idx]);
return -1;
}
#ifndef SDFAT_SUPER_MAGIC
#define SDFAT_SUPER_MAGIC 0x5EC5DFA4
#endif
#ifndef FUSE_SUPER_MAGIC
#define FUSE_SUPER_MAGIC 0x65735546
#endif
static int is_sdcard(struct vfsmount *mnt)
{
/* internal storage (external storage till Andorid 8.x) */
if (mnt->mnt_sb->s_magic == SDCARDFS_SUPER_MAGIC)
return true;
/* internal storage (external storage till Andorid 11.x) */
else if (mnt->mnt_sb->s_magic == FUSE_SUPER_MAGIC)
return true;
/* external storage from Android 9.x */
else if (mnt->mnt_sb->s_magic == SDFAT_SUPER_MAGIC)
return true;
else if (mnt->mnt_sb->s_magic == MSDOS_SUPER_MAGIC)
return true;
return false;
}
static void make_prefix(int part_id, char *prefix)
{
if (part_id == DLOG_SUPP_PART_DATA)
*prefix = 'd';
else
*prefix = 's';
}
static void store_log(struct dentry *dentry, struct inode *inode,
struct path *path, int type, int part_id)
{
kuid_t euid = current->cred->euid;
unsigned long ino = inode ? inode->i_ino : 0;
loff_t isize = inode ? inode->i_size : 0;
char prefix = 0;
char *buf, *full_path;
char unit = 'B';
buf = kzalloc(PATH_MAX, GFP_KERNEL);
if (!buf) {
printk(KERN_ERR "%s memory alloc failed : ENOMEM\n", __func__);
return;
}
full_path = dentry_path_raw(dentry, buf, PATH_MAX);
if (IS_ERR(full_path))
goto out;
make_prefix(part_id, &prefix);
if (isize >> 10) {
isize >>= 10;
unit = 'K';
}
if (type == DLOG_MM) {
fslog_dlog_mm("[%c]\"%s\" (%u, %lu, %lu, %lld%c)\n", prefix, full_path,
euid, path->dentry->d_inode->i_ino, ino, isize, unit);
goto out;
}
if (type == DLOG_ETC) {
fslog_dlog_etc("[%c]\"%s\" (%u, %lu, %lu, %lld%c)\n", prefix, full_path,
euid, path->dentry->d_inode->i_ino, ino, isize, unit);
goto out;
}
if (type == DLOG_EFS) {
fslog_dlog_efs("\"%s\" (%lu, %lu, %lld%c)\n", full_path,
path->dentry->d_inode->i_ino, ino, isize, unit);
goto out;
}
if (type == DLOG_RMDIR)
fslog_dlog_rmdir("[%c]\"%s\" (%u, %lu)\n", prefix, full_path,
euid, path->dentry->d_inode->i_ino);
out:
kfree(buf);
}
void dlog_hook(struct dentry *dentry, struct inode *inode, struct path *path)
{
int part_id = get_support_part_id(path->mnt);
if ((part_id < 0) && !is_sdcard(path->mnt))
return;
/* for efs partition */
if (part_id == DLOG_SUPP_PART_EFS) {
store_log(dentry, inode, path, DLOG_EFS, part_id);
goto out;
}
/* for data partition`s only multimedia file */
if (is_ext(dentry->d_name.name, '.', &ht[DLOG_HT_EXTENSION])) {
if (i_size_read(inode) >= MEDIA_MIN_SIZE)
store_log(dentry, inode, path, DLOG_MM, part_id);
goto out;
}
/* for data partition except multimedia file */
if (!is_ext(dentry->d_name.name, '.', &ht[DLOG_HT_EXCEPTION])
&& !is_ext(dentry->d_name.name, '-', &ht[DLOG_HT_EXCEPTION]))
store_log(dentry, inode, path, DLOG_ETC, part_id);
out:
return;
}
static int get_dentry_depth(struct dentry *dentry)
{
int depth = 0;
struct dentry *tmp_de = dentry;
while (!IS_ROOT(tmp_de)) {
struct dentry *parent = tmp_de->d_parent;
depth++;
if (depth > MAX_DEPTH)
return -1;
tmp_de = parent;
}
return depth;
}
void dlog_hook_rmdir(struct dentry *dentry, struct path *path)
{
int depth = 0;
int part_id = get_support_part_id(path->mnt);
if (part_id != DLOG_SUPP_PART_DATA)
return;
depth = get_dentry_depth(dentry);
if (depth < 0)
return;
store_log(dentry, NULL, path, DLOG_RMDIR, part_id);
return;
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Logging unlink file path");