| /* |
| * 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/version.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/mount.h> |
| #include <linux/hashtable.h> |
| #include <linux/ctype.h> |
| #include <linux/module.h> |
| #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 |
| |
| 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_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", 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; |
| } |
| |
| 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; |
| /* 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])) { |
| 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"); |
| MODULE_AUTHOR("Samsung Electronics Co., Ltd."); |