| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Daniel Campello <campello@google.com> |
| Date: Mon, 20 Jul 2015 16:23:50 -0700 |
| Subject: ANDROID: sdcardfs: Add sdcardfs filesystem |
| |
| Bug: 11118565 |
| Bug: 27915347 |
| Bug: 27992761 |
| Bug: 28024488 |
| Bug: 30013843 |
| Bug: 30954918 |
| Bug: 34133558 |
| Bug: 34262585 |
| Bug: 34542611 |
| Bug: 34691169 |
| Bug: 34723223 |
| Bug: 35307857 |
| Bug: 35331000 |
| Bug: 35633782 |
| Bug: 35643557 |
| Bug: 35666680 |
| bug: 35766959 |
| Bug: 35766959 |
| Bug: 35848445 |
| Bug: 36004503 |
| Bug: 36007653 |
| Bug: 36138424 |
| Bug: 36160015 |
| Bug: 37193650 |
| Bug: 37231161 |
| Bug: 37488099 |
| Bug: 37516160 |
| Bug: 38045152 |
| Bug: 38117720 |
| Bug: 38502532 |
| Bug: 62390017 |
| Bug: 63245673 |
| Bug: 63260873 |
| Bug: 63785372 |
| Bug: 64672411 |
| Bug: 70278506 |
| Bug: 72007585 |
| Bug: 73055997 |
| Bug: 73287721 |
| Bug: 75987238 |
| Bug: 77923821 |
| Bug: 78262592 |
| Bug: 111641492 |
| Bug: 111642636 |
| Bug: 111860541 |
| Change-Id: Ic1e01e602ce335d97342be54f3da0c5c65c087cc |
| Signed-off-by: Daniel Rosenberg <drosen@google.com> |
| [adelva: Folded the following changes into this patch: |
| 903cea7ab0b2 ("ANDROID: Included sdcardfs source code for kernel 3.0") |
| 612a725e3d97 ("ANDROID: Port of sdcardfs to 4.4") |
| e4187c55208b ("ANDROID: Changed type-casting in packagelist management") |
| cf76072a5cd8 ("ANDROID: sdcardfs: Bring up to date with Android M permissions:") |
| a43aa502c608 ("ANDROID: sdcardfs: Add support for d_canonical_path") |
| d8fefbf85af2 ("ANDROID: sdcardfs: remove effectless config option") |
| 416677409336 ("ANDROID: sdcardfs: Remove unused code") |
| 8e49a570d351 ("ANDROID: sdcardfs: remove unneeded __init and __exit") |
| 40ee0e93f1d7 ("ANDROID: sdcardfs: Truncate packages_gid.list on overflow") |
| b1d9602aa3fe ("ANDROID: sdcardfs: fix itnull.cocci warnings") |
| 60a177f5a167 ("ANDROID: sdcardfs: override umask on mkdir and create") |
| efb3d2695203 ("ANDROID: sdcardfs: Check for other cases on path lookup") |
| 0da87f63666f ("ANDROID: sdcardfs: Fix locking for permission fix up") |
| 75b93060655e ("ANDROID: sdcardfs: Switch package list to RCU") |
| 657b0a00f497 ("ANDROID: sdcardfs: Added top to sdcardfs_inode_info") |
| 5008d91cba25 ("ANDROID: sdcardfs: fix external storage exporting incorrect uid") |
| e06c452d0d07 ("ANDROID: sdcardfs: Move directory unlock before touch") |
| 72e5443a2816 ("ANDROID: sdcardfs: User new permission2 functions") |
| ae8be7da556d ("ANDROID: sdcardfs: Add gid and mask to private mount data") |
| 151a3efe57a6 ("ANDROID: sdcardfs: Use per mount permissions") |
| cff865a370f3 ("ANDROID: sdcardfs: Switch ->d_inode to d_inode()") |
| 065ac66804bf ("ANDROID: sdcardfs: Fix locking issue with permision fix up") |
| 31ea603eb3c4 ("ANDROID: sdcardfs: use wrappers to access i_mutex") |
| c25c2f5018a2 ("ANDROID: sdcardfs: add parent pointer into dentry name hash") |
| 58616bb4ec68 ("ANDROID: sdcardfs: get rid of 'parent' argument of ->d_compare()") |
| 1654d7ffdd20 ("ANDROID: sdcardfs: Propagate dentry down to inode_change_ok()") |
| 39335cac1d2f ("ANDROID: sdcardfs: make it use new .rename i_op") |
| 7622bb3fcc79 ("ANDROID: sdcardfs: eliminate the offset argument to ->direct_IO") |
| 843bd7295ee0 ("ANDROID: sdcardfs: Allow non-owners to touch") |
| e3d74804d174 ("ANDROID: sdcardfs: Refactor configfs interface") |
| 5833eda87a72 ("ANDROID: sdcardfs: add support for user permission isolation") |
| d83fb1f41dd4 ("ANDROID: sdcardfs: Remove redundant operation") |
| 8767af17c0e5 ("ANDROID: sdcardfs: Add GID Derivation to sdcardfs") |
| 7119d96ad3ee ("ANDROID: sdcardfs: switch to full_name_hash and qstr") |
| 778e02a54859 ("ANDROID: sdcardfs: Switch strcasecmp for internal call") |
| cd4965d04404 ("ANDROID: sdcardfs: Fix incorrect hash") |
| 40a2ee053505 ("ANDROID: sdcardfs: Add missing path_put") |
| da5342bac57a ("ANDROID: sdcardfs: Don't bother deleting freelist") |
| c91857b01e05 ("ANDROID: sdcardfs: implement vm_ops->page_mkwrite") |
| f62b3906044b ("ANDROID: sdcardfs: support direct-IO (DIO) operations") |
| c2e216d36d63 ("ANDROID: sdcardfs: Fix case insensitive lookup") |
| 57b92ab6f774 ("ANDROID: sdcardfs: rate limit warning print") |
| 8534cee39a81 ("ANDROID: sdcardfs: Replace get/put with d_lock") |
| 156085b2fccf ("ANDROID: sdcardfs: Use spin_lock_nested") |
| 8a260cabac4e ("ANDROID: sdcardfs: Switch to internal case insensitive compare") |
| a8d51569573c ("ANDROID: sdcardfs: Use d_invalidate instead of drop_recurisve") |
| 932a6071de63 ("ANDROID: sdcardfs: Get the blocksize from the lower fs") |
| 0ad4c0f87527 ("ANDROID: sdcardfs: declare MODULE_ALIAS_FS") |
| b97c83b5b683 ("ANDROID: sdcardfs: Use case insensitive hash function") |
| 9920dfb08265 ("ANDROID: sdcardfs: move path_put outside of spinlock") |
| f9a25348b233 ("ANDROID: sdcardfs: Remove uninformative prints") |
| 720d9030bea1 ("ANDROID: sdcardfs: Fix gid issue") |
| 4cbb7fa6e66c ("ANDROID: sdcardfs: correct order of descriptors") |
| 6cff6cc301ed ("ANDROID: sdcardfs: Fix formatting") |
| ac2a40412e26 ("ANDROID: sdcardfs: Fix style issues with comments") |
| 2212bb8ec064 ("ANDROID: sdcardfs: remove unneeded null check") |
| 4c1a0add8d21 ("ANDROID: sdcardfs: Use pr_[...] instead of printk") |
| 74535fe211ac ("ANDROID: sdcardfs: Use to kstrout") |
| e6cf8dffd014 ("ANDROID: sdcardfs: Use seq_puts over seq_printf") |
| 2b1ac93a90b6 ("ANDROID: sdcardfs: Fix style issues in macros") |
| bab6d117426f ("ANDROID: sdcardfs: remove unnecessary call to do_munmap") |
| 1c0bf09f19b6 ("ANDROID: sdcardfs: copy lower inode attributes in ->ioctl") |
| 42f3db55942b ("ANDROID: sdcardfs: fix ->llseek to update upper and lower offset") |
| 97ad6205055e ("ANDROID: sdcardfs: add read_iter/write_iter opeations") |
| be9abc81332b ("ANDROID: sdcardfs: use d_splice_alias") |
| 4e90114cb1b4 ("ANDROID: sdcardfs: update module info") |
| 0e1f7ab14924 ("ANDROID: sdcardfs: Directly pass lower file for mmap") |
| 28be4beb43f9 ("ANDROID: sdcardfs: Change cache GID value") |
| 9fc2c452aefe ("ANDROID: sdcardfs: ->iget fixes") |
| 9bb72cf15cbc ("ANDROID: sdcardfs: Don't do d_add for lower fs") |
| 1bc21a04c11b ("ANDROID: sdcardfs: Don't complain in fixup_lower_ownership") |
| 0fb5b10b28a9 ("ANDROID: sdcardfs: Use filesystem specific hash") |
| 30e2f0aadce2 ("ANDROID: sdcardfs: Copy meta-data from lower inode") |
| f748c7053194 ("ANDROID: sdcardfs: Avoid setting GIDs outside of valid ranges") |
| 3d38f08bacdb ("ANDROID: sdcardfs: Call lower fs's revalidate") |
| 2d1f1c203978 ("ANDROID: sdcardfs: Don't iput if we didn't igrab") |
| 857fc5e717fc ("ANDROID: sdcardfs: fix sdcardfs_destroy_inode for the inode RCU approach") |
| 4fceeccf1d23 ("ANDROID: sdcardfs: Move top to its own struct") |
| f51470044a15 ("ANDROID: sdcardfs: Check for NULL in revalidate") |
| 8c7f6c97ac81 ("ANDROID: sdcardfs: d_splice_alias can return error values") |
| 17da01b37d61 ("ANDROID: sdcardfs: remove dead function open_flags_to_access_mode()") |
| 16662dd604be ("ANDROID: sdcardfs: use mount_nodev and fix a issue in sdcardfs_kill_sb") |
| 43c0dca6039a ("ANDROID: sdcardfs: Remove unnecessary lock") |
| 48960c25cdc1 ("ANDROID: sdcardfs: override credential for ioctl to lower fs") |
| 5d6410b9a88d ("ANDROID: Sdcardfs: Move gid derivation under flag") |
| c7dd98431f83 ("ANDROID: sdcardfs: Add default_normal option") |
| db9bf31a5d86 ("ANDROID: sdcardfs: port to 4.14") |
| c70c9d1e82d2 ("ANDROID: sdcardfs: Use lower getattr times/size") |
| 04e961477d62 ("ANDROID: sdcardfs: Protect set_top") |
| 1ed04b79d281 ("ANDROID: sdcardfs: Hold i_mutex for i_size_write") |
| 77f52fc10982 ("ANDROID: sdcardfs: Set num in extension_details during make_item") |
| d71596efa247 ("ANDROID: sdcardfs: fix lock issue on 32 bit/SMP architectures") |
| ee6b07fced4a ("ANDROID: sdcardfs: Fix sdcardfs to stop creating cases-sensitive duplicate entries.") |
| ce12807d5b75 ("ANDROID: sdcardfs: Check for private data earlier") |
| c080450304cd ("ANDROID: sdcardfs: d_make_root calls iput") |
| 900e77796781 ("ANDROID: sdcardfs: Set s_root to NULL after putting") |
| 49092e89ffa4 ("ANDROID: sdcardfs: Don't d_drop in d_revalidate") |
| e1f978bc9b9c ("ANDROID: sdcardfs: fix potential crash when reserved_mb is not zero") |
| faa148eaf8ed ("ANDROID: sdcardfs: Check stacked filesystem depth") |
| 6edd721e972c ("ANDROID: sdcardfs: Don't use OVERRIDE_CRED macro") |
| 11ca578b4336 ("ANDROID: sdcardfs: Change current->fs under lock") |
| 83dea6ba6ea7 ("ANDROID: sdcardfs: Use inode iversion helpers") |
| 12064f3a794e ("ANDROID: sdcardfs: Add option to drop unused dentries") |
| d9fe221bbf84 ("ANDROID: sdcardfs: Add sandbox") |
| f544ad0b1547 ("ANDROID: sdcardfs: Add option to not link obb")] |
| Signed-off-by: Alistair Delva <adelva@google.com> |
| [drosen: folded in e6e368c99975 ("fs: sdcardfs: Add missing option to show_options")] |
| [maennich: Folded the following patch into this patch: |
| 2a8322aa8a90 ("ANDROID: sdcardfs: fix fall through in param parsing")] |
| Signed-off-by: Matthias Maennich <maennich@google.com> |
| --- |
| fs/Kconfig | 1 + |
| fs/Makefile | 1 + |
| fs/sdcardfs/Kconfig | 13 + |
| fs/sdcardfs/Makefile | 7 + |
| fs/sdcardfs/dentry.c | 196 +++++++++ |
| fs/sdcardfs/derived_perm.c | 477 ++++++++++++++++++++ |
| fs/sdcardfs/file.c | 467 ++++++++++++++++++++ |
| fs/sdcardfs/inode.c | 821 ++++++++++++++++++++++++++++++++++ |
| fs/sdcardfs/lookup.c | 470 ++++++++++++++++++++ |
| fs/sdcardfs/main.c | 455 +++++++++++++++++++ |
| fs/sdcardfs/mmap.c | 87 ++++ |
| fs/sdcardfs/multiuser.h | 53 +++ |
| fs/sdcardfs/packagelist.c | 882 +++++++++++++++++++++++++++++++++++++ |
| fs/sdcardfs/sdcardfs.h | 662 ++++++++++++++++++++++++++++ |
| fs/sdcardfs/super.c | 297 +++++++++++++ |
| 15 files changed, 4889 insertions(+) |
| create mode 100644 fs/sdcardfs/Kconfig |
| create mode 100644 fs/sdcardfs/Makefile |
| create mode 100644 fs/sdcardfs/dentry.c |
| create mode 100644 fs/sdcardfs/derived_perm.c |
| create mode 100644 fs/sdcardfs/file.c |
| create mode 100644 fs/sdcardfs/inode.c |
| create mode 100644 fs/sdcardfs/lookup.c |
| create mode 100644 fs/sdcardfs/main.c |
| create mode 100644 fs/sdcardfs/mmap.c |
| create mode 100644 fs/sdcardfs/multiuser.h |
| create mode 100644 fs/sdcardfs/packagelist.c |
| create mode 100644 fs/sdcardfs/sdcardfs.h |
| create mode 100644 fs/sdcardfs/super.c |
| |
| diff --git a/fs/Kconfig b/fs/Kconfig |
| index 2501e6f1f965..42fe99e24071 100644 |
| --- a/fs/Kconfig |
| +++ b/fs/Kconfig |
| @@ -243,6 +243,7 @@ source "fs/orangefs/Kconfig" |
| source "fs/adfs/Kconfig" |
| source "fs/affs/Kconfig" |
| source "fs/ecryptfs/Kconfig" |
| +source "fs/sdcardfs/Kconfig" |
| source "fs/hfs/Kconfig" |
| source "fs/hfsplus/Kconfig" |
| source "fs/befs/Kconfig" |
| diff --git a/fs/Makefile b/fs/Makefile |
| index 14231b4cf383..cc5524e8acda 100644 |
| --- a/fs/Makefile |
| +++ b/fs/Makefile |
| @@ -87,6 +87,7 @@ obj-$(CONFIG_ISO9660_FS) += isofs/ |
| obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+ |
| obj-$(CONFIG_HFS_FS) += hfs/ |
| obj-$(CONFIG_ECRYPT_FS) += ecryptfs/ |
| +obj-$(CONFIG_SDCARD_FS) += sdcardfs/ |
| obj-$(CONFIG_VXFS_FS) += freevxfs/ |
| obj-$(CONFIG_NFS_FS) += nfs/ |
| obj-$(CONFIG_EXPORTFS) += exportfs/ |
| diff --git a/fs/sdcardfs/Kconfig b/fs/sdcardfs/Kconfig |
| new file mode 100644 |
| index 000000000000..a1c103316ac7 |
| --- /dev/null |
| +++ b/fs/sdcardfs/Kconfig |
| @@ -0,0 +1,13 @@ |
| +config SDCARD_FS |
| + tristate "sdcard file system" |
| + depends on CONFIGFS_FS |
| + default n |
| + help |
| + Sdcardfs is based on Wrapfs file system. |
| + |
| +config SDCARD_FS_FADV_NOACTIVE |
| + bool "sdcardfs fadvise noactive support" |
| + depends on FADV_NOACTIVE |
| + default y |
| + help |
| + Sdcardfs supports fadvise noactive mode. |
| diff --git a/fs/sdcardfs/Makefile b/fs/sdcardfs/Makefile |
| new file mode 100644 |
| index 000000000000..b84fbb2b45a4 |
| --- /dev/null |
| +++ b/fs/sdcardfs/Makefile |
| @@ -0,0 +1,7 @@ |
| +SDCARDFS_VERSION="0.1" |
| + |
| +EXTRA_CFLAGS += -DSDCARDFS_VERSION=\"$(SDCARDFS_VERSION)\" |
| + |
| +obj-$(CONFIG_SDCARD_FS) += sdcardfs.o |
| + |
| +sdcardfs-y := dentry.o file.o inode.o main.o super.o lookup.o mmap.o packagelist.o derived_perm.o |
| diff --git a/fs/sdcardfs/dentry.c b/fs/sdcardfs/dentry.c |
| new file mode 100644 |
| index 000000000000..cb573f1efbfc |
| --- /dev/null |
| +++ b/fs/sdcardfs/dentry.c |
| @@ -0,0 +1,196 @@ |
| +/* |
| + * fs/sdcardfs/dentry.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| +#include "linux/ctype.h" |
| + |
| +/* |
| + * returns: -ERRNO if error (returned to user) |
| + * 0: tell VFS to invalidate dentry |
| + * 1: dentry is valid |
| + */ |
| +static int sdcardfs_d_revalidate(struct dentry *dentry, unsigned int flags) |
| +{ |
| + int err = 1; |
| + struct path parent_lower_path, lower_path; |
| + struct dentry *parent_dentry = NULL; |
| + struct dentry *parent_lower_dentry = NULL; |
| + struct dentry *lower_cur_parent_dentry = NULL; |
| + struct dentry *lower_dentry = NULL; |
| + struct inode *inode; |
| + struct sdcardfs_inode_data *data; |
| + |
| + if (flags & LOOKUP_RCU) |
| + return -ECHILD; |
| + |
| + spin_lock(&dentry->d_lock); |
| + if (IS_ROOT(dentry)) { |
| + spin_unlock(&dentry->d_lock); |
| + return 1; |
| + } |
| + spin_unlock(&dentry->d_lock); |
| + |
| + /* check uninitialized obb_dentry and |
| + * whether the base obbpath has been changed or not |
| + */ |
| + if (is_obbpath_invalid(dentry)) { |
| + return 0; |
| + } |
| + |
| + parent_dentry = dget_parent(dentry); |
| + sdcardfs_get_lower_path(parent_dentry, &parent_lower_path); |
| + sdcardfs_get_real_lower(dentry, &lower_path); |
| + parent_lower_dentry = parent_lower_path.dentry; |
| + lower_dentry = lower_path.dentry; |
| + lower_cur_parent_dentry = dget_parent(lower_dentry); |
| + |
| + if ((lower_dentry->d_flags & DCACHE_OP_REVALIDATE)) { |
| + err = lower_dentry->d_op->d_revalidate(lower_dentry, flags); |
| + if (err == 0) { |
| + goto out; |
| + } |
| + } |
| + |
| + spin_lock(&lower_dentry->d_lock); |
| + if (d_unhashed(lower_dentry)) { |
| + spin_unlock(&lower_dentry->d_lock); |
| + err = 0; |
| + goto out; |
| + } |
| + spin_unlock(&lower_dentry->d_lock); |
| + |
| + if (parent_lower_dentry != lower_cur_parent_dentry) { |
| + err = 0; |
| + goto out; |
| + } |
| + |
| + if (dentry < lower_dentry) { |
| + spin_lock(&dentry->d_lock); |
| + spin_lock_nested(&lower_dentry->d_lock, DENTRY_D_LOCK_NESTED); |
| + } else { |
| + spin_lock(&lower_dentry->d_lock); |
| + spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); |
| + } |
| + |
| + if (!qstr_case_eq(&dentry->d_name, &lower_dentry->d_name)) { |
| + err = 0; |
| + } |
| + |
| + if (dentry < lower_dentry) { |
| + spin_unlock(&lower_dentry->d_lock); |
| + spin_unlock(&dentry->d_lock); |
| + } else { |
| + spin_unlock(&dentry->d_lock); |
| + spin_unlock(&lower_dentry->d_lock); |
| + } |
| + if (!err) |
| + goto out; |
| + |
| + /* If our top's inode is gone, we may be out of date */ |
| + inode = igrab(d_inode(dentry)); |
| + if (inode) { |
| + data = top_data_get(SDCARDFS_I(inode)); |
| + if (!data || data->abandoned) { |
| + err = 0; |
| + } |
| + if (data) |
| + data_put(data); |
| + iput(inode); |
| + } |
| + |
| +out: |
| + dput(parent_dentry); |
| + dput(lower_cur_parent_dentry); |
| + sdcardfs_put_lower_path(parent_dentry, &parent_lower_path); |
| + sdcardfs_put_real_lower(dentry, &lower_path); |
| + return err; |
| +} |
| + |
| +/* 1 = delete, 0 = cache */ |
| +static int sdcardfs_d_delete(const struct dentry *d) |
| +{ |
| + return SDCARDFS_SB(d->d_sb)->options.nocache ? 1 : 0; |
| +} |
| + |
| +static void sdcardfs_d_release(struct dentry *dentry) |
| +{ |
| + if (!dentry || !dentry->d_fsdata) |
| + return; |
| + /* release and reset the lower paths */ |
| + if (has_graft_path(dentry)) |
| + sdcardfs_put_reset_orig_path(dentry); |
| + sdcardfs_put_reset_lower_path(dentry); |
| + free_dentry_private_data(dentry); |
| +} |
| + |
| +static int sdcardfs_hash_ci(const struct dentry *dentry, |
| + struct qstr *qstr) |
| +{ |
| + /* |
| + * This function is copy of vfat_hashi. |
| + * FIXME Should we support national language? |
| + * Refer to vfat_hashi() |
| + * struct nls_table *t = MSDOS_SB(dentry->d_sb)->nls_io; |
| + */ |
| + const unsigned char *name; |
| + unsigned int len; |
| + unsigned long hash; |
| + |
| + name = qstr->name; |
| + len = qstr->len; |
| + |
| + hash = init_name_hash(dentry); |
| + while (len--) |
| + hash = partial_name_hash(tolower(*name++), hash); |
| + qstr->hash = end_name_hash(hash); |
| + |
| + return 0; |
| +} |
| + |
| +/* |
| + * Case insensitive compare of two vfat names. |
| + */ |
| +static int sdcardfs_cmp_ci(const struct dentry *dentry, |
| + unsigned int len, const char *str, const struct qstr *name) |
| +{ |
| + /* FIXME Should we support national language? */ |
| + |
| + if (name->len == len) { |
| + if (str_n_case_eq(name->name, str, len)) |
| + return 0; |
| + } |
| + return 1; |
| +} |
| + |
| +static void sdcardfs_canonical_path(const struct path *path, |
| + struct path *actual_path) |
| +{ |
| + sdcardfs_get_real_lower(path->dentry, actual_path); |
| +} |
| + |
| +const struct dentry_operations sdcardfs_ci_dops = { |
| + .d_revalidate = sdcardfs_d_revalidate, |
| + .d_delete = sdcardfs_d_delete, |
| + .d_release = sdcardfs_d_release, |
| + .d_hash = sdcardfs_hash_ci, |
| + .d_compare = sdcardfs_cmp_ci, |
| + .d_canonical_path = sdcardfs_canonical_path, |
| +}; |
| + |
| diff --git a/fs/sdcardfs/derived_perm.c b/fs/sdcardfs/derived_perm.c |
| new file mode 100644 |
| index 000000000000..78a669c8a4d6 |
| --- /dev/null |
| +++ b/fs/sdcardfs/derived_perm.c |
| @@ -0,0 +1,477 @@ |
| +/* |
| + * fs/sdcardfs/derived_perm.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| + |
| +/* copy derived state from parent inode */ |
| +static void inherit_derived_state(struct inode *parent, struct inode *child) |
| +{ |
| + struct sdcardfs_inode_info *pi = SDCARDFS_I(parent); |
| + struct sdcardfs_inode_info *ci = SDCARDFS_I(child); |
| + |
| + ci->data->perm = PERM_INHERIT; |
| + ci->data->userid = pi->data->userid; |
| + ci->data->d_uid = pi->data->d_uid; |
| + ci->data->under_android = pi->data->under_android; |
| + ci->data->under_cache = pi->data->under_cache; |
| + ci->data->under_obb = pi->data->under_obb; |
| +} |
| + |
| +/* helper function for derived state */ |
| +void setup_derived_state(struct inode *inode, perm_t perm, userid_t userid, |
| + uid_t uid) |
| +{ |
| + struct sdcardfs_inode_info *info = SDCARDFS_I(inode); |
| + |
| + info->data->perm = perm; |
| + info->data->userid = userid; |
| + info->data->d_uid = uid; |
| + info->data->under_android = false; |
| + info->data->under_cache = false; |
| + info->data->under_obb = false; |
| +} |
| + |
| +/* While renaming, there is a point where we want the path from dentry, |
| + * but the name from newdentry |
| + */ |
| +void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, |
| + const struct qstr *name) |
| +{ |
| + struct sdcardfs_inode_info *info = SDCARDFS_I(d_inode(dentry)); |
| + struct sdcardfs_inode_info *parent_info = SDCARDFS_I(d_inode(parent)); |
| + struct sdcardfs_inode_data *parent_data = parent_info->data; |
| + appid_t appid; |
| + unsigned long user_num; |
| + int err; |
| + struct qstr q_Android = QSTR_LITERAL("Android"); |
| + struct qstr q_data = QSTR_LITERAL("data"); |
| + struct qstr q_sandbox = QSTR_LITERAL("sandbox"); |
| + struct qstr q_obb = QSTR_LITERAL("obb"); |
| + struct qstr q_media = QSTR_LITERAL("media"); |
| + struct qstr q_cache = QSTR_LITERAL("cache"); |
| + |
| + /* By default, each inode inherits from its parent. |
| + * the properties are maintained on its private fields |
| + * because the inode attributes will be modified with that of |
| + * its lower inode. |
| + * These values are used by our custom permission call instead |
| + * of using the inode permissions. |
| + */ |
| + |
| + inherit_derived_state(d_inode(parent), d_inode(dentry)); |
| + |
| + /* Files don't get special labels */ |
| + if (!S_ISDIR(d_inode(dentry)->i_mode)) { |
| + set_top(info, parent_info); |
| + return; |
| + } |
| + /* Derive custom permissions based on parent and current node */ |
| + switch (parent_data->perm) { |
| + case PERM_INHERIT: |
| + case PERM_ANDROID_PACKAGE_CACHE: |
| + set_top(info, parent_info); |
| + break; |
| + case PERM_PRE_ROOT: |
| + /* Legacy internal layout places users at top level */ |
| + info->data->perm = PERM_ROOT; |
| + err = kstrtoul(name->name, 10, &user_num); |
| + if (err) |
| + info->data->userid = 0; |
| + else |
| + info->data->userid = user_num; |
| + break; |
| + case PERM_ROOT: |
| + /* Assume masked off by default. */ |
| + if (qstr_case_eq(name, &q_Android)) { |
| + /* App-specific directories inside; let anyone traverse */ |
| + info->data->perm = PERM_ANDROID; |
| + info->data->under_android = true; |
| + } else { |
| + set_top(info, parent_info); |
| + } |
| + break; |
| + case PERM_ANDROID: |
| + if (qstr_case_eq(name, &q_data)) { |
| + /* App-specific directories inside; let anyone traverse */ |
| + info->data->perm = PERM_ANDROID_DATA; |
| + } else if (qstr_case_eq(name, &q_sandbox)) { |
| + /* App-specific directories inside; let anyone traverse */ |
| + info->data->perm = PERM_ANDROID_DATA; |
| + } else if (qstr_case_eq(name, &q_obb)) { |
| + /* App-specific directories inside; let anyone traverse */ |
| + info->data->perm = PERM_ANDROID_OBB; |
| + info->data->under_obb = true; |
| + /* Single OBB directory is always shared */ |
| + } else if (qstr_case_eq(name, &q_media)) { |
| + /* App-specific directories inside; let anyone traverse */ |
| + info->data->perm = PERM_ANDROID_MEDIA; |
| + } else { |
| + set_top(info, parent_info); |
| + } |
| + break; |
| + case PERM_ANDROID_OBB: |
| + case PERM_ANDROID_DATA: |
| + case PERM_ANDROID_MEDIA: |
| + info->data->perm = PERM_ANDROID_PACKAGE; |
| + appid = get_appid(name->name); |
| + if (appid != 0 && !is_excluded(name->name, parent_data->userid)) |
| + info->data->d_uid = |
| + multiuser_get_uid(parent_data->userid, appid); |
| + break; |
| + case PERM_ANDROID_PACKAGE: |
| + if (qstr_case_eq(name, &q_cache)) { |
| + info->data->perm = PERM_ANDROID_PACKAGE_CACHE; |
| + info->data->under_cache = true; |
| + } |
| + set_top(info, parent_info); |
| + break; |
| + } |
| +} |
| + |
| +void get_derived_permission(struct dentry *parent, struct dentry *dentry) |
| +{ |
| + get_derived_permission_new(parent, dentry, &dentry->d_name); |
| +} |
| + |
| +static appid_t get_type(const char *name) |
| +{ |
| + const char *ext = strrchr(name, '.'); |
| + appid_t id; |
| + |
| + if (ext && ext[0]) { |
| + ext = &ext[1]; |
| + id = get_ext_gid(ext); |
| + return id?:AID_MEDIA_RW; |
| + } |
| + return AID_MEDIA_RW; |
| +} |
| + |
| +void fixup_lower_ownership(struct dentry *dentry, const char *name) |
| +{ |
| + struct path path; |
| + struct inode *inode; |
| + struct inode *delegated_inode = NULL; |
| + int error; |
| + struct sdcardfs_inode_info *info; |
| + struct sdcardfs_inode_data *info_d; |
| + struct sdcardfs_inode_data *info_top; |
| + perm_t perm; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + uid_t uid = sbi->options.fs_low_uid; |
| + gid_t gid = sbi->options.fs_low_gid; |
| + struct iattr newattrs; |
| + |
| + if (!sbi->options.gid_derivation) |
| + return; |
| + |
| + info = SDCARDFS_I(d_inode(dentry)); |
| + info_d = info->data; |
| + perm = info_d->perm; |
| + if (info_d->under_obb) { |
| + perm = PERM_ANDROID_OBB; |
| + } else if (info_d->under_cache) { |
| + perm = PERM_ANDROID_PACKAGE_CACHE; |
| + } else if (perm == PERM_INHERIT) { |
| + info_top = top_data_get(info); |
| + perm = info_top->perm; |
| + data_put(info_top); |
| + } |
| + |
| + switch (perm) { |
| + case PERM_ROOT: |
| + case PERM_ANDROID: |
| + case PERM_ANDROID_DATA: |
| + case PERM_ANDROID_MEDIA: |
| + case PERM_ANDROID_PACKAGE: |
| + case PERM_ANDROID_PACKAGE_CACHE: |
| + uid = multiuser_get_uid(info_d->userid, uid); |
| + break; |
| + case PERM_ANDROID_OBB: |
| + uid = AID_MEDIA_OBB; |
| + break; |
| + case PERM_PRE_ROOT: |
| + default: |
| + break; |
| + } |
| + switch (perm) { |
| + case PERM_ROOT: |
| + case PERM_ANDROID: |
| + case PERM_ANDROID_DATA: |
| + case PERM_ANDROID_MEDIA: |
| + if (S_ISDIR(d_inode(dentry)->i_mode)) |
| + gid = multiuser_get_uid(info_d->userid, AID_MEDIA_RW); |
| + else |
| + gid = multiuser_get_uid(info_d->userid, get_type(name)); |
| + break; |
| + case PERM_ANDROID_OBB: |
| + gid = AID_MEDIA_OBB; |
| + break; |
| + case PERM_ANDROID_PACKAGE: |
| + if (uid_is_app(info_d->d_uid)) |
| + gid = multiuser_get_ext_gid(info_d->d_uid); |
| + else |
| + gid = multiuser_get_uid(info_d->userid, AID_MEDIA_RW); |
| + break; |
| + case PERM_ANDROID_PACKAGE_CACHE: |
| + if (uid_is_app(info_d->d_uid)) |
| + gid = multiuser_get_ext_cache_gid(info_d->d_uid); |
| + else |
| + gid = multiuser_get_uid(info_d->userid, AID_MEDIA_RW); |
| + break; |
| + case PERM_PRE_ROOT: |
| + default: |
| + break; |
| + } |
| + |
| + sdcardfs_get_lower_path(dentry, &path); |
| + inode = d_inode(path.dentry); |
| + if (d_inode(path.dentry)->i_gid.val != gid || d_inode(path.dentry)->i_uid.val != uid) { |
| +retry_deleg: |
| + newattrs.ia_valid = ATTR_GID | ATTR_UID | ATTR_FORCE; |
| + newattrs.ia_uid = make_kuid(current_user_ns(), uid); |
| + newattrs.ia_gid = make_kgid(current_user_ns(), gid); |
| + if (!S_ISDIR(inode->i_mode)) |
| + newattrs.ia_valid |= |
| + ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV; |
| + inode_lock(inode); |
| + error = security_path_chown(&path, newattrs.ia_uid, newattrs.ia_gid); |
| + if (!error) |
| + error = notify_change2(path.mnt, path.dentry, &newattrs, &delegated_inode); |
| + inode_unlock(inode); |
| + if (delegated_inode) { |
| + error = break_deleg_wait(&delegated_inode); |
| + if (!error) |
| + goto retry_deleg; |
| + } |
| + if (error) |
| + pr_debug("sdcardfs: Failed to touch up lower fs gid/uid for %s\n", name); |
| + } |
| + sdcardfs_put_lower_path(dentry, &path); |
| +} |
| + |
| +static int descendant_may_need_fixup(struct sdcardfs_inode_data *data, |
| + struct limit_search *limit) |
| +{ |
| + if (data->perm == PERM_ROOT) |
| + return (limit->flags & BY_USERID) ? |
| + data->userid == limit->userid : 1; |
| + if (data->perm == PERM_PRE_ROOT || data->perm == PERM_ANDROID) |
| + return 1; |
| + return 0; |
| +} |
| + |
| +static int needs_fixup(perm_t perm) |
| +{ |
| + if (perm == PERM_ANDROID_DATA || perm == PERM_ANDROID_OBB |
| + || perm == PERM_ANDROID_MEDIA) |
| + return 1; |
| + return 0; |
| +} |
| + |
| +static void __fixup_perms_recursive(struct dentry *dentry, struct limit_search *limit, int depth) |
| +{ |
| + struct dentry *child; |
| + struct sdcardfs_inode_info *info; |
| + |
| + /* |
| + * All paths will terminate their recursion on hitting PERM_ANDROID_OBB, |
| + * PERM_ANDROID_MEDIA, or PERM_ANDROID_DATA. This happens at a depth of |
| + * at most 3. |
| + */ |
| + WARN(depth > 3, "%s: Max expected depth exceeded!\n", __func__); |
| + spin_lock_nested(&dentry->d_lock, depth); |
| + if (!d_inode(dentry)) { |
| + spin_unlock(&dentry->d_lock); |
| + return; |
| + } |
| + info = SDCARDFS_I(d_inode(dentry)); |
| + |
| + if (needs_fixup(info->data->perm)) { |
| + list_for_each_entry(child, &dentry->d_subdirs, d_child) { |
| + spin_lock_nested(&child->d_lock, depth + 1); |
| + if (!(limit->flags & BY_NAME) || qstr_case_eq(&child->d_name, &limit->name)) { |
| + if (d_inode(child)) { |
| + get_derived_permission(dentry, child); |
| + fixup_tmp_permissions(d_inode(child)); |
| + spin_unlock(&child->d_lock); |
| + break; |
| + } |
| + } |
| + spin_unlock(&child->d_lock); |
| + } |
| + } else if (descendant_may_need_fixup(info->data, limit)) { |
| + list_for_each_entry(child, &dentry->d_subdirs, d_child) { |
| + __fixup_perms_recursive(child, limit, depth + 1); |
| + } |
| + } |
| + spin_unlock(&dentry->d_lock); |
| +} |
| + |
| +void fixup_perms_recursive(struct dentry *dentry, struct limit_search *limit) |
| +{ |
| + __fixup_perms_recursive(dentry, limit, 0); |
| +} |
| + |
| +/* main function for updating derived permission */ |
| +inline void update_derived_permission_lock(struct dentry *dentry) |
| +{ |
| + struct dentry *parent; |
| + |
| + if (!dentry || !d_inode(dentry)) { |
| + pr_err("sdcardfs: %s: invalid dentry\n", __func__); |
| + return; |
| + } |
| + /* FIXME: |
| + * 1. need to check whether the dentry is updated or not |
| + * 2. remove the root dentry update |
| + */ |
| + if (!IS_ROOT(dentry)) { |
| + parent = dget_parent(dentry); |
| + if (parent) { |
| + get_derived_permission(parent, dentry); |
| + dput(parent); |
| + } |
| + } |
| + fixup_tmp_permissions(d_inode(dentry)); |
| +} |
| + |
| +int need_graft_path(struct dentry *dentry) |
| +{ |
| + int ret = 0; |
| + struct dentry *parent = dget_parent(dentry); |
| + struct sdcardfs_inode_info *parent_info = SDCARDFS_I(d_inode(parent)); |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + struct qstr obb = QSTR_LITERAL("obb"); |
| + |
| + if (!sbi->options.unshared_obb && |
| + parent_info->data->perm == PERM_ANDROID && |
| + qstr_case_eq(&dentry->d_name, &obb)) { |
| + |
| + /* /Android/obb is the base obbpath of DERIVED_UNIFIED */ |
| + if (!(sbi->options.multiuser == false |
| + && parent_info->data->userid == 0)) { |
| + ret = 1; |
| + } |
| + } |
| + dput(parent); |
| + return ret; |
| +} |
| + |
| +int is_obbpath_invalid(struct dentry *dent) |
| +{ |
| + int ret = 0; |
| + struct sdcardfs_dentry_info *di = SDCARDFS_D(dent); |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dent->d_sb); |
| + char *path_buf, *obbpath_s; |
| + int need_put = 0; |
| + struct path lower_path; |
| + |
| + /* check the base obbpath has been changed. |
| + * this routine can check an uninitialized obb dentry as well. |
| + * regarding the uninitialized obb, refer to the sdcardfs_mkdir() |
| + */ |
| + spin_lock(&di->lock); |
| + if (di->orig_path.dentry) { |
| + if (!di->lower_path.dentry) { |
| + ret = 1; |
| + } else { |
| + path_get(&di->lower_path); |
| + |
| + path_buf = kmalloc(PATH_MAX, GFP_ATOMIC); |
| + if (!path_buf) { |
| + ret = 1; |
| + pr_err("sdcardfs: fail to allocate path_buf in %s.\n", __func__); |
| + } else { |
| + obbpath_s = d_path(&di->lower_path, path_buf, PATH_MAX); |
| + if (d_unhashed(di->lower_path.dentry) || |
| + !str_case_eq(sbi->obbpath_s, obbpath_s)) { |
| + ret = 1; |
| + } |
| + kfree(path_buf); |
| + } |
| + |
| + pathcpy(&lower_path, &di->lower_path); |
| + need_put = 1; |
| + } |
| + } |
| + spin_unlock(&di->lock); |
| + if (need_put) |
| + path_put(&lower_path); |
| + return ret; |
| +} |
| + |
| +int is_base_obbpath(struct dentry *dentry) |
| +{ |
| + int ret = 0; |
| + struct dentry *parent = dget_parent(dentry); |
| + struct sdcardfs_inode_info *parent_info = SDCARDFS_I(d_inode(parent)); |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + struct qstr q_obb = QSTR_LITERAL("obb"); |
| + |
| + spin_lock(&SDCARDFS_D(dentry)->lock); |
| + if (sbi->options.multiuser) { |
| + if (parent_info->data->perm == PERM_PRE_ROOT && |
| + qstr_case_eq(&dentry->d_name, &q_obb)) { |
| + ret = 1; |
| + } |
| + } else if (parent_info->data->perm == PERM_ANDROID && |
| + qstr_case_eq(&dentry->d_name, &q_obb)) { |
| + ret = 1; |
| + } |
| + spin_unlock(&SDCARDFS_D(dentry)->lock); |
| + return ret; |
| +} |
| + |
| +/* The lower_path will be stored to the dentry's orig_path |
| + * and the base obbpath will be copyed to the lower_path variable. |
| + * if an error returned, there's no change in the lower_path |
| + * returns: -ERRNO if error (0: no error) |
| + */ |
| +int setup_obb_dentry(struct dentry *dentry, struct path *lower_path) |
| +{ |
| + int err = 0; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + struct path obbpath; |
| + |
| + /* A local obb dentry must have its own orig_path to support rmdir |
| + * and mkdir of itself. Usually, we expect that the sbi->obbpath |
| + * is avaiable on this stage. |
| + */ |
| + sdcardfs_set_orig_path(dentry, lower_path); |
| + |
| + err = kern_path(sbi->obbpath_s, |
| + LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &obbpath); |
| + |
| + if (!err) { |
| + /* the obbpath base has been found */ |
| + pathcpy(lower_path, &obbpath); |
| + } else { |
| + /* if the sbi->obbpath is not available, we can optionally |
| + * setup the lower_path with its orig_path. |
| + * but, the current implementation just returns an error |
| + * because the sdcard daemon also regards this case as |
| + * a lookup fail. |
| + */ |
| + pr_info("sdcardfs: the sbi->obbpath is not available\n"); |
| + } |
| + return err; |
| +} |
| + |
| + |
| diff --git a/fs/sdcardfs/file.c b/fs/sdcardfs/file.c |
| new file mode 100644 |
| index 000000000000..271c4c4cb760 |
| --- /dev/null |
| +++ b/fs/sdcardfs/file.c |
| @@ -0,0 +1,467 @@ |
| +/* |
| + * fs/sdcardfs/file.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| +#ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE |
| +#include <linux/backing-dev.h> |
| +#endif |
| + |
| +static ssize_t sdcardfs_read(struct file *file, char __user *buf, |
| + size_t count, loff_t *ppos) |
| +{ |
| + int err; |
| + struct file *lower_file; |
| + struct dentry *dentry = file->f_path.dentry; |
| +#ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE |
| + struct backing_dev_info *bdi; |
| +#endif |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + |
| +#ifdef CONFIG_SDCARD_FS_FADV_NOACTIVE |
| + if (file->f_mode & FMODE_NOACTIVE) { |
| + if (!(lower_file->f_mode & FMODE_NOACTIVE)) { |
| + bdi = lower_file->f_mapping->backing_dev_info; |
| + lower_file->f_ra.ra_pages = bdi->ra_pages * 2; |
| + spin_lock(&lower_file->f_lock); |
| + lower_file->f_mode |= FMODE_NOACTIVE; |
| + spin_unlock(&lower_file->f_lock); |
| + } |
| + } |
| +#endif |
| + |
| + err = vfs_read(lower_file, buf, count, ppos); |
| + /* update our inode atime upon a successful lower read */ |
| + if (err >= 0) |
| + fsstack_copy_attr_atime(d_inode(dentry), |
| + file_inode(lower_file)); |
| + |
| + return err; |
| +} |
| + |
| +static ssize_t sdcardfs_write(struct file *file, const char __user *buf, |
| + size_t count, loff_t *ppos) |
| +{ |
| + int err; |
| + struct file *lower_file; |
| + struct dentry *dentry = file->f_path.dentry; |
| + struct inode *inode = d_inode(dentry); |
| + |
| + /* check disk space */ |
| + if (!check_min_free_space(dentry, count, 0)) { |
| + pr_err("No minimum free space.\n"); |
| + return -ENOSPC; |
| + } |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + err = vfs_write(lower_file, buf, count, ppos); |
| + /* update our inode times+sizes upon a successful lower write */ |
| + if (err >= 0) { |
| + if (sizeof(loff_t) > sizeof(long)) |
| + inode_lock(inode); |
| + fsstack_copy_inode_size(inode, file_inode(lower_file)); |
| + fsstack_copy_attr_times(inode, file_inode(lower_file)); |
| + if (sizeof(loff_t) > sizeof(long)) |
| + inode_unlock(inode); |
| + } |
| + |
| + return err; |
| +} |
| + |
| +static int sdcardfs_readdir(struct file *file, struct dir_context *ctx) |
| +{ |
| + int err; |
| + struct file *lower_file = NULL; |
| + struct dentry *dentry = file->f_path.dentry; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + |
| + lower_file->f_pos = file->f_pos; |
| + err = iterate_dir(lower_file, ctx); |
| + file->f_pos = lower_file->f_pos; |
| + if (err >= 0) /* copy the atime */ |
| + fsstack_copy_attr_atime(d_inode(dentry), |
| + file_inode(lower_file)); |
| + return err; |
| +} |
| + |
| +static long sdcardfs_unlocked_ioctl(struct file *file, unsigned int cmd, |
| + unsigned long arg) |
| +{ |
| + long err = -ENOTTY; |
| + struct file *lower_file; |
| + const struct cred *saved_cred = NULL; |
| + struct dentry *dentry = file->f_path.dentry; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + |
| + /* XXX: use vfs_ioctl if/when VFS exports it */ |
| + if (!lower_file || !lower_file->f_op) |
| + goto out; |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(sbi, SDCARDFS_I(file_inode(file))->data); |
| + if (!saved_cred) { |
| + err = -ENOMEM; |
| + goto out; |
| + } |
| + |
| + if (lower_file->f_op->unlocked_ioctl) |
| + err = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg); |
| + |
| + /* some ioctls can change inode attributes (EXT2_IOC_SETFLAGS) */ |
| + if (!err) |
| + sdcardfs_copy_and_fix_attrs(file_inode(file), |
| + file_inode(lower_file)); |
| + revert_fsids(saved_cred); |
| +out: |
| + return err; |
| +} |
| + |
| +#ifdef CONFIG_COMPAT |
| +static long sdcardfs_compat_ioctl(struct file *file, unsigned int cmd, |
| + unsigned long arg) |
| +{ |
| + long err = -ENOTTY; |
| + struct file *lower_file; |
| + const struct cred *saved_cred = NULL; |
| + struct dentry *dentry = file->f_path.dentry; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + |
| + /* XXX: use vfs_ioctl if/when VFS exports it */ |
| + if (!lower_file || !lower_file->f_op) |
| + goto out; |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(sbi, SDCARDFS_I(file_inode(file))->data); |
| + if (!saved_cred) { |
| + err = -ENOMEM; |
| + goto out; |
| + } |
| + |
| + if (lower_file->f_op->compat_ioctl) |
| + err = lower_file->f_op->compat_ioctl(lower_file, cmd, arg); |
| + |
| + revert_fsids(saved_cred); |
| +out: |
| + return err; |
| +} |
| +#endif |
| + |
| +static int sdcardfs_mmap(struct file *file, struct vm_area_struct *vma) |
| +{ |
| + int err = 0; |
| + bool willwrite; |
| + struct file *lower_file; |
| + const struct vm_operations_struct *saved_vm_ops = NULL; |
| + |
| + /* this might be deferred to mmap's writepage */ |
| + willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags); |
| + |
| + /* |
| + * File systems which do not implement ->writepage may use |
| + * generic_file_readonly_mmap as their ->mmap op. If you call |
| + * generic_file_readonly_mmap with VM_WRITE, you'd get an -EINVAL. |
| + * But we cannot call the lower ->mmap op, so we can't tell that |
| + * writeable mappings won't work. Therefore, our only choice is to |
| + * check if the lower file system supports the ->writepage, and if |
| + * not, return EINVAL (the same error that |
| + * generic_file_readonly_mmap returns in that case). |
| + */ |
| + lower_file = sdcardfs_lower_file(file); |
| + if (willwrite && !lower_file->f_mapping->a_ops->writepage) { |
| + err = -EINVAL; |
| + pr_err("sdcardfs: lower file system does not support writeable mmap\n"); |
| + goto out; |
| + } |
| + |
| + /* |
| + * find and save lower vm_ops. |
| + * |
| + * XXX: the VFS should have a cleaner way of finding the lower vm_ops |
| + */ |
| + if (!SDCARDFS_F(file)->lower_vm_ops) { |
| + err = lower_file->f_op->mmap(lower_file, vma); |
| + if (err) { |
| + pr_err("sdcardfs: lower mmap failed %d\n", err); |
| + goto out; |
| + } |
| + saved_vm_ops = vma->vm_ops; /* save: came from lower ->mmap */ |
| + } |
| + |
| + /* |
| + * Next 3 lines are all I need from generic_file_mmap. I definitely |
| + * don't want its test for ->readpage which returns -ENOEXEC. |
| + */ |
| + file_accessed(file); |
| + vma->vm_ops = &sdcardfs_vm_ops; |
| + |
| + file->f_mapping->a_ops = &sdcardfs_aops; /* set our aops */ |
| + if (!SDCARDFS_F(file)->lower_vm_ops) /* save for our ->fault */ |
| + SDCARDFS_F(file)->lower_vm_ops = saved_vm_ops; |
| + vma->vm_private_data = file; |
| + get_file(lower_file); |
| + vma->vm_file = lower_file; |
| + |
| +out: |
| + return err; |
| +} |
| + |
| +static int sdcardfs_open(struct inode *inode, struct file *file) |
| +{ |
| + int err = 0; |
| + struct file *lower_file = NULL; |
| + struct path lower_path; |
| + struct dentry *dentry = file->f_path.dentry; |
| + struct dentry *parent = dget_parent(dentry); |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + const struct cred *saved_cred = NULL; |
| + |
| + /* don't open unhashed/deleted files */ |
| + if (d_unhashed(dentry)) { |
| + err = -ENOENT; |
| + goto out_err; |
| + } |
| + |
| + if (!check_caller_access_to_name(d_inode(parent), &dentry->d_name)) { |
| + err = -EACCES; |
| + goto out_err; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(sbi, SDCARDFS_I(inode)->data); |
| + if (!saved_cred) { |
| + err = -ENOMEM; |
| + goto out_err; |
| + } |
| + |
| + file->private_data = |
| + kzalloc(sizeof(struct sdcardfs_file_info), GFP_KERNEL); |
| + if (!SDCARDFS_F(file)) { |
| + err = -ENOMEM; |
| + goto out_revert_cred; |
| + } |
| + |
| + /* open lower object and link sdcardfs's file struct to lower's */ |
| + sdcardfs_get_lower_path(file->f_path.dentry, &lower_path); |
| + lower_file = dentry_open(&lower_path, file->f_flags, current_cred()); |
| + path_put(&lower_path); |
| + if (IS_ERR(lower_file)) { |
| + err = PTR_ERR(lower_file); |
| + lower_file = sdcardfs_lower_file(file); |
| + if (lower_file) { |
| + sdcardfs_set_lower_file(file, NULL); |
| + fput(lower_file); /* fput calls dput for lower_dentry */ |
| + } |
| + } else { |
| + sdcardfs_set_lower_file(file, lower_file); |
| + } |
| + |
| + if (err) |
| + kfree(SDCARDFS_F(file)); |
| + else |
| + sdcardfs_copy_and_fix_attrs(inode, sdcardfs_lower_inode(inode)); |
| + |
| +out_revert_cred: |
| + revert_fsids(saved_cred); |
| +out_err: |
| + dput(parent); |
| + return err; |
| +} |
| + |
| +static int sdcardfs_flush(struct file *file, fl_owner_t id) |
| +{ |
| + int err = 0; |
| + struct file *lower_file = NULL; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + if (lower_file && lower_file->f_op && lower_file->f_op->flush) { |
| + filemap_write_and_wait(file->f_mapping); |
| + err = lower_file->f_op->flush(lower_file, id); |
| + } |
| + |
| + return err; |
| +} |
| + |
| +/* release all lower object references & free the file info structure */ |
| +static int sdcardfs_file_release(struct inode *inode, struct file *file) |
| +{ |
| + struct file *lower_file; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + if (lower_file) { |
| + sdcardfs_set_lower_file(file, NULL); |
| + fput(lower_file); |
| + } |
| + |
| + kfree(SDCARDFS_F(file)); |
| + return 0; |
| +} |
| + |
| +static int sdcardfs_fsync(struct file *file, loff_t start, loff_t end, |
| + int datasync) |
| +{ |
| + int err; |
| + struct file *lower_file; |
| + struct path lower_path; |
| + struct dentry *dentry = file->f_path.dentry; |
| + |
| + err = __generic_file_fsync(file, start, end, datasync); |
| + if (err) |
| + goto out; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + err = vfs_fsync_range(lower_file, start, end, datasync); |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| +out: |
| + return err; |
| +} |
| + |
| +static int sdcardfs_fasync(int fd, struct file *file, int flag) |
| +{ |
| + int err = 0; |
| + struct file *lower_file = NULL; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + if (lower_file->f_op && lower_file->f_op->fasync) |
| + err = lower_file->f_op->fasync(fd, lower_file, flag); |
| + |
| + return err; |
| +} |
| + |
| +/* |
| + * Sdcardfs cannot use generic_file_llseek as ->llseek, because it would |
| + * only set the offset of the upper file. So we have to implement our |
| + * own method to set both the upper and lower file offsets |
| + * consistently. |
| + */ |
| +static loff_t sdcardfs_file_llseek(struct file *file, loff_t offset, int whence) |
| +{ |
| + int err; |
| + struct file *lower_file; |
| + |
| + err = generic_file_llseek(file, offset, whence); |
| + if (err < 0) |
| + goto out; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + err = generic_file_llseek(lower_file, offset, whence); |
| + |
| +out: |
| + return err; |
| +} |
| + |
| +/* |
| + * Sdcardfs read_iter, redirect modified iocb to lower read_iter |
| + */ |
| +ssize_t sdcardfs_read_iter(struct kiocb *iocb, struct iov_iter *iter) |
| +{ |
| + int err; |
| + struct file *file = iocb->ki_filp, *lower_file; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + if (!lower_file->f_op->read_iter) { |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + |
| + get_file(lower_file); /* prevent lower_file from being released */ |
| + iocb->ki_filp = lower_file; |
| + err = lower_file->f_op->read_iter(iocb, iter); |
| + iocb->ki_filp = file; |
| + fput(lower_file); |
| + /* update upper inode atime as needed */ |
| + if (err >= 0 || err == -EIOCBQUEUED) |
| + fsstack_copy_attr_atime(file->f_path.dentry->d_inode, |
| + file_inode(lower_file)); |
| +out: |
| + return err; |
| +} |
| + |
| +/* |
| + * Sdcardfs write_iter, redirect modified iocb to lower write_iter |
| + */ |
| +ssize_t sdcardfs_write_iter(struct kiocb *iocb, struct iov_iter *iter) |
| +{ |
| + int err; |
| + struct file *file = iocb->ki_filp, *lower_file; |
| + struct inode *inode = file->f_path.dentry->d_inode; |
| + |
| + lower_file = sdcardfs_lower_file(file); |
| + if (!lower_file->f_op->write_iter) { |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + |
| + get_file(lower_file); /* prevent lower_file from being released */ |
| + iocb->ki_filp = lower_file; |
| + err = lower_file->f_op->write_iter(iocb, iter); |
| + iocb->ki_filp = file; |
| + fput(lower_file); |
| + /* update upper inode times/sizes as needed */ |
| + if (err >= 0 || err == -EIOCBQUEUED) { |
| + if (sizeof(loff_t) > sizeof(long)) |
| + inode_lock(inode); |
| + fsstack_copy_inode_size(inode, file_inode(lower_file)); |
| + fsstack_copy_attr_times(inode, file_inode(lower_file)); |
| + if (sizeof(loff_t) > sizeof(long)) |
| + inode_unlock(inode); |
| + } |
| +out: |
| + return err; |
| +} |
| + |
| +const struct file_operations sdcardfs_main_fops = { |
| + .llseek = generic_file_llseek, |
| + .read = sdcardfs_read, |
| + .write = sdcardfs_write, |
| + .unlocked_ioctl = sdcardfs_unlocked_ioctl, |
| +#ifdef CONFIG_COMPAT |
| + .compat_ioctl = sdcardfs_compat_ioctl, |
| +#endif |
| + .mmap = sdcardfs_mmap, |
| + .open = sdcardfs_open, |
| + .flush = sdcardfs_flush, |
| + .release = sdcardfs_file_release, |
| + .fsync = sdcardfs_fsync, |
| + .fasync = sdcardfs_fasync, |
| + .read_iter = sdcardfs_read_iter, |
| + .write_iter = sdcardfs_write_iter, |
| +}; |
| + |
| +/* trimmed directory options */ |
| +const struct file_operations sdcardfs_dir_fops = { |
| + .llseek = sdcardfs_file_llseek, |
| + .read = generic_read_dir, |
| + .iterate = sdcardfs_readdir, |
| + .unlocked_ioctl = sdcardfs_unlocked_ioctl, |
| +#ifdef CONFIG_COMPAT |
| + .compat_ioctl = sdcardfs_compat_ioctl, |
| +#endif |
| + .open = sdcardfs_open, |
| + .release = sdcardfs_file_release, |
| + .flush = sdcardfs_flush, |
| + .fsync = sdcardfs_fsync, |
| + .fasync = sdcardfs_fasync, |
| +}; |
| diff --git a/fs/sdcardfs/inode.c b/fs/sdcardfs/inode.c |
| new file mode 100644 |
| index 000000000000..4dd681e0d59d |
| --- /dev/null |
| +++ b/fs/sdcardfs/inode.c |
| @@ -0,0 +1,821 @@ |
| +/* |
| + * fs/sdcardfs/inode.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| +#include <linux/fs_struct.h> |
| +#include <linux/ratelimit.h> |
| +#include <linux/sched/task.h> |
| + |
| +const struct cred *override_fsids(struct sdcardfs_sb_info *sbi, |
| + struct sdcardfs_inode_data *data) |
| +{ |
| + struct cred *cred; |
| + const struct cred *old_cred; |
| + uid_t uid; |
| + |
| + cred = prepare_creds(); |
| + if (!cred) |
| + return NULL; |
| + |
| + if (sbi->options.gid_derivation) { |
| + if (data->under_obb) |
| + uid = AID_MEDIA_OBB; |
| + else |
| + uid = multiuser_get_uid(data->userid, sbi->options.fs_low_uid); |
| + } else { |
| + uid = sbi->options.fs_low_uid; |
| + } |
| + cred->fsuid = make_kuid(&init_user_ns, uid); |
| + cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid); |
| + |
| + old_cred = override_creds(cred); |
| + |
| + return old_cred; |
| +} |
| + |
| +void revert_fsids(const struct cred *old_cred) |
| +{ |
| + const struct cred *cur_cred; |
| + |
| + cur_cred = current->cred; |
| + revert_creds(old_cred); |
| + put_cred(cur_cred); |
| +} |
| + |
| +static int sdcardfs_create(struct inode *dir, struct dentry *dentry, |
| + umode_t mode, bool want_excl) |
| +{ |
| + int err; |
| + struct dentry *lower_dentry; |
| + struct vfsmount *lower_dentry_mnt; |
| + struct dentry *lower_parent_dentry = NULL; |
| + struct path lower_path; |
| + const struct cred *saved_cred = NULL; |
| + struct fs_struct *saved_fs; |
| + struct fs_struct *copied_fs; |
| + |
| + if (!check_caller_access_to_name(dir, &dentry->d_name)) { |
| + err = -EACCES; |
| + goto out_eacces; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb), |
| + SDCARDFS_I(dir)->data); |
| + if (!saved_cred) |
| + return -ENOMEM; |
| + |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + lower_dentry = lower_path.dentry; |
| + lower_dentry_mnt = lower_path.mnt; |
| + lower_parent_dentry = lock_parent(lower_dentry); |
| + |
| + /* set last 16bytes of mode field to 0664 */ |
| + mode = (mode & S_IFMT) | 00664; |
| + |
| + /* temporarily change umask for lower fs write */ |
| + saved_fs = current->fs; |
| + copied_fs = copy_fs_struct(current->fs); |
| + if (!copied_fs) { |
| + err = -ENOMEM; |
| + goto out_unlock; |
| + } |
| + copied_fs->umask = 0; |
| + task_lock(current); |
| + current->fs = copied_fs; |
| + task_unlock(current); |
| + |
| + err = vfs_create2(lower_dentry_mnt, d_inode(lower_parent_dentry), lower_dentry, mode, want_excl); |
| + if (err) |
| + goto out; |
| + |
| + err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path, |
| + SDCARDFS_I(dir)->data->userid); |
| + if (err) |
| + goto out; |
| + fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir)); |
| + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); |
| + fixup_lower_ownership(dentry, dentry->d_name.name); |
| + |
| +out: |
| + task_lock(current); |
| + current->fs = saved_fs; |
| + task_unlock(current); |
| + free_fs_struct(copied_fs); |
| +out_unlock: |
| + unlock_dir(lower_parent_dentry); |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + revert_fsids(saved_cred); |
| +out_eacces: |
| + return err; |
| +} |
| + |
| +static int sdcardfs_unlink(struct inode *dir, struct dentry *dentry) |
| +{ |
| + int err; |
| + struct dentry *lower_dentry; |
| + struct vfsmount *lower_mnt; |
| + struct inode *lower_dir_inode = sdcardfs_lower_inode(dir); |
| + struct dentry *lower_dir_dentry; |
| + struct path lower_path; |
| + const struct cred *saved_cred = NULL; |
| + |
| + if (!check_caller_access_to_name(dir, &dentry->d_name)) { |
| + err = -EACCES; |
| + goto out_eacces; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb), |
| + SDCARDFS_I(dir)->data); |
| + if (!saved_cred) |
| + return -ENOMEM; |
| + |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + lower_dentry = lower_path.dentry; |
| + lower_mnt = lower_path.mnt; |
| + dget(lower_dentry); |
| + lower_dir_dentry = lock_parent(lower_dentry); |
| + |
| + err = vfs_unlink2(lower_mnt, lower_dir_inode, lower_dentry, NULL); |
| + |
| + /* |
| + * Note: unlinking on top of NFS can cause silly-renamed files. |
| + * Trying to delete such files results in EBUSY from NFS |
| + * below. Silly-renamed files will get deleted by NFS later on, so |
| + * we just need to detect them here and treat such EBUSY errors as |
| + * if the upper file was successfully deleted. |
| + */ |
| + if (err == -EBUSY && lower_dentry->d_flags & DCACHE_NFSFS_RENAMED) |
| + err = 0; |
| + if (err) |
| + goto out; |
| + fsstack_copy_attr_times(dir, lower_dir_inode); |
| + fsstack_copy_inode_size(dir, lower_dir_inode); |
| + set_nlink(d_inode(dentry), |
| + sdcardfs_lower_inode(d_inode(dentry))->i_nlink); |
| + d_inode(dentry)->i_ctime = dir->i_ctime; |
| + d_drop(dentry); /* this is needed, else LTP fails (VFS won't do it) */ |
| +out: |
| + unlock_dir(lower_dir_dentry); |
| + dput(lower_dentry); |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + revert_fsids(saved_cred); |
| +out_eacces: |
| + return err; |
| +} |
| + |
| +static int touch(char *abs_path, mode_t mode) |
| +{ |
| + struct file *filp = filp_open(abs_path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, mode); |
| + |
| + if (IS_ERR(filp)) { |
| + if (PTR_ERR(filp) == -EEXIST) { |
| + return 0; |
| + } else { |
| + pr_err("sdcardfs: failed to open(%s): %ld\n", |
| + abs_path, PTR_ERR(filp)); |
| + return PTR_ERR(filp); |
| + } |
| + } |
| + filp_close(filp, current->files); |
| + return 0; |
| +} |
| + |
| +static int sdcardfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) |
| +{ |
| + int err; |
| + int make_nomedia_in_obb = 0; |
| + struct dentry *lower_dentry; |
| + struct vfsmount *lower_mnt; |
| + struct dentry *lower_parent_dentry = NULL; |
| + struct dentry *parent_dentry = NULL; |
| + struct path lower_path; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + const struct cred *saved_cred = NULL; |
| + struct sdcardfs_inode_data *pd = SDCARDFS_I(dir)->data; |
| + int touch_err = 0; |
| + struct fs_struct *saved_fs; |
| + struct fs_struct *copied_fs; |
| + struct qstr q_obb = QSTR_LITERAL("obb"); |
| + struct qstr q_data = QSTR_LITERAL("data"); |
| + |
| + if (!check_caller_access_to_name(dir, &dentry->d_name)) { |
| + err = -EACCES; |
| + goto out_eacces; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb), |
| + SDCARDFS_I(dir)->data); |
| + if (!saved_cred) |
| + return -ENOMEM; |
| + |
| + /* check disk space */ |
| + parent_dentry = dget_parent(dentry); |
| + if (!check_min_free_space(parent_dentry, 0, 1)) { |
| + pr_err("sdcardfs: No minimum free space.\n"); |
| + err = -ENOSPC; |
| + dput(parent_dentry); |
| + goto out_revert; |
| + } |
| + dput(parent_dentry); |
| + |
| + /* the lower_dentry is negative here */ |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + lower_dentry = lower_path.dentry; |
| + lower_mnt = lower_path.mnt; |
| + lower_parent_dentry = lock_parent(lower_dentry); |
| + |
| + /* set last 16bytes of mode field to 0775 */ |
| + mode = (mode & S_IFMT) | 00775; |
| + |
| + /* temporarily change umask for lower fs write */ |
| + saved_fs = current->fs; |
| + copied_fs = copy_fs_struct(current->fs); |
| + if (!copied_fs) { |
| + err = -ENOMEM; |
| + unlock_dir(lower_parent_dentry); |
| + goto out_unlock; |
| + } |
| + copied_fs->umask = 0; |
| + task_lock(current); |
| + current->fs = copied_fs; |
| + task_unlock(current); |
| + |
| + err = vfs_mkdir2(lower_mnt, d_inode(lower_parent_dentry), lower_dentry, mode); |
| + |
| + if (err) { |
| + unlock_dir(lower_parent_dentry); |
| + goto out; |
| + } |
| + |
| + /* if it is a local obb dentry, setup it with the base obbpath */ |
| + if (need_graft_path(dentry)) { |
| + |
| + err = setup_obb_dentry(dentry, &lower_path); |
| + if (err) { |
| + /* if the sbi->obbpath is not available, the lower_path won't be |
| + * changed by setup_obb_dentry() but the lower path is saved to |
| + * its orig_path. this dentry will be revalidated later. |
| + * but now, the lower_path should be NULL |
| + */ |
| + sdcardfs_put_reset_lower_path(dentry); |
| + |
| + /* the newly created lower path which saved to its orig_path or |
| + * the lower_path is the base obbpath. |
| + * therefore, an additional path_get is required |
| + */ |
| + path_get(&lower_path); |
| + } else |
| + make_nomedia_in_obb = 1; |
| + } |
| + |
| + err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path, pd->userid); |
| + if (err) { |
| + unlock_dir(lower_parent_dentry); |
| + goto out; |
| + } |
| + |
| + fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir)); |
| + fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry)); |
| + /* update number of links on parent directory */ |
| + set_nlink(dir, sdcardfs_lower_inode(dir)->i_nlink); |
| + fixup_lower_ownership(dentry, dentry->d_name.name); |
| + unlock_dir(lower_parent_dentry); |
| + if ((!sbi->options.multiuser) && (qstr_case_eq(&dentry->d_name, &q_obb)) |
| + && (pd->perm == PERM_ANDROID) && (pd->userid == 0)) |
| + make_nomedia_in_obb = 1; |
| + |
| + /* When creating /Android/data and /Android/obb, mark them as .nomedia */ |
| + if (make_nomedia_in_obb || |
| + ((pd->perm == PERM_ANDROID) |
| + && (qstr_case_eq(&dentry->d_name, &q_data)))) { |
| + revert_fsids(saved_cred); |
| + saved_cred = override_fsids(sbi, |
| + SDCARDFS_I(d_inode(dentry))->data); |
| + if (!saved_cred) { |
| + pr_err("sdcardfs: failed to set up .nomedia in %s: %d\n", |
| + lower_path.dentry->d_name.name, |
| + -ENOMEM); |
| + goto out; |
| + } |
| + set_fs_pwd(current->fs, &lower_path); |
| + touch_err = touch(".nomedia", 0664); |
| + if (touch_err) { |
| + pr_err("sdcardfs: failed to create .nomedia in %s: %d\n", |
| + lower_path.dentry->d_name.name, |
| + touch_err); |
| + goto out; |
| + } |
| + } |
| +out: |
| + task_lock(current); |
| + current->fs = saved_fs; |
| + task_unlock(current); |
| + |
| + free_fs_struct(copied_fs); |
| +out_unlock: |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| +out_revert: |
| + revert_fsids(saved_cred); |
| +out_eacces: |
| + return err; |
| +} |
| + |
| +static int sdcardfs_rmdir(struct inode *dir, struct dentry *dentry) |
| +{ |
| + struct dentry *lower_dentry; |
| + struct dentry *lower_dir_dentry; |
| + struct vfsmount *lower_mnt; |
| + int err; |
| + struct path lower_path; |
| + const struct cred *saved_cred = NULL; |
| + |
| + if (!check_caller_access_to_name(dir, &dentry->d_name)) { |
| + err = -EACCES; |
| + goto out_eacces; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb), |
| + SDCARDFS_I(dir)->data); |
| + if (!saved_cred) |
| + return -ENOMEM; |
| + |
| + /* sdcardfs_get_real_lower(): in case of remove an user's obb dentry |
| + * the dentry on the original path should be deleted. |
| + */ |
| + sdcardfs_get_real_lower(dentry, &lower_path); |
| + |
| + lower_dentry = lower_path.dentry; |
| + lower_mnt = lower_path.mnt; |
| + lower_dir_dentry = lock_parent(lower_dentry); |
| + |
| + err = vfs_rmdir2(lower_mnt, d_inode(lower_dir_dentry), lower_dentry); |
| + if (err) |
| + goto out; |
| + |
| + d_drop(dentry); /* drop our dentry on success (why not VFS's job?) */ |
| + if (d_inode(dentry)) |
| + clear_nlink(d_inode(dentry)); |
| + fsstack_copy_attr_times(dir, d_inode(lower_dir_dentry)); |
| + fsstack_copy_inode_size(dir, d_inode(lower_dir_dentry)); |
| + set_nlink(dir, d_inode(lower_dir_dentry)->i_nlink); |
| + |
| +out: |
| + unlock_dir(lower_dir_dentry); |
| + sdcardfs_put_real_lower(dentry, &lower_path); |
| + revert_fsids(saved_cred); |
| +out_eacces: |
| + return err; |
| +} |
| + |
| +/* |
| + * The locking rules in sdcardfs_rename are complex. We could use a simpler |
| + * superblock-level name-space lock for renames and copy-ups. |
| + */ |
| +static int sdcardfs_rename(struct inode *old_dir, struct dentry *old_dentry, |
| + struct inode *new_dir, struct dentry *new_dentry, |
| + unsigned int flags) |
| +{ |
| + int err = 0; |
| + struct dentry *lower_old_dentry = NULL; |
| + struct dentry *lower_new_dentry = NULL; |
| + struct dentry *lower_old_dir_dentry = NULL; |
| + struct dentry *lower_new_dir_dentry = NULL; |
| + struct vfsmount *lower_mnt = NULL; |
| + struct dentry *trap = NULL; |
| + struct path lower_old_path, lower_new_path; |
| + const struct cred *saved_cred = NULL; |
| + |
| + if (flags) |
| + return -EINVAL; |
| + |
| + if (!check_caller_access_to_name(old_dir, &old_dentry->d_name) || |
| + !check_caller_access_to_name(new_dir, &new_dentry->d_name)) { |
| + err = -EACCES; |
| + goto out_eacces; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(old_dir->i_sb), |
| + SDCARDFS_I(new_dir)->data); |
| + if (!saved_cred) |
| + return -ENOMEM; |
| + |
| + sdcardfs_get_real_lower(old_dentry, &lower_old_path); |
| + sdcardfs_get_lower_path(new_dentry, &lower_new_path); |
| + lower_old_dentry = lower_old_path.dentry; |
| + lower_new_dentry = lower_new_path.dentry; |
| + lower_mnt = lower_old_path.mnt; |
| + lower_old_dir_dentry = dget_parent(lower_old_dentry); |
| + lower_new_dir_dentry = dget_parent(lower_new_dentry); |
| + |
| + trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); |
| + /* source should not be ancestor of target */ |
| + if (trap == lower_old_dentry) { |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + /* target should not be ancestor of source */ |
| + if (trap == lower_new_dentry) { |
| + err = -ENOTEMPTY; |
| + goto out; |
| + } |
| + |
| + err = vfs_rename2(lower_mnt, |
| + d_inode(lower_old_dir_dentry), lower_old_dentry, |
| + d_inode(lower_new_dir_dentry), lower_new_dentry, |
| + NULL, 0); |
| + if (err) |
| + goto out; |
| + |
| + /* Copy attrs from lower dir, but i_uid/i_gid */ |
| + sdcardfs_copy_and_fix_attrs(new_dir, d_inode(lower_new_dir_dentry)); |
| + fsstack_copy_inode_size(new_dir, d_inode(lower_new_dir_dentry)); |
| + |
| + if (new_dir != old_dir) { |
| + sdcardfs_copy_and_fix_attrs(old_dir, d_inode(lower_old_dir_dentry)); |
| + fsstack_copy_inode_size(old_dir, d_inode(lower_old_dir_dentry)); |
| + } |
| + get_derived_permission_new(new_dentry->d_parent, old_dentry, &new_dentry->d_name); |
| + fixup_tmp_permissions(d_inode(old_dentry)); |
| + fixup_lower_ownership(old_dentry, new_dentry->d_name.name); |
| + d_invalidate(old_dentry); /* Can't fixup ownership recursively :( */ |
| +out: |
| + unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry); |
| + dput(lower_old_dir_dentry); |
| + dput(lower_new_dir_dentry); |
| + sdcardfs_put_real_lower(old_dentry, &lower_old_path); |
| + sdcardfs_put_lower_path(new_dentry, &lower_new_path); |
| + revert_fsids(saved_cred); |
| +out_eacces: |
| + return err; |
| +} |
| + |
| +#if 0 |
| +static int sdcardfs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) |
| +{ |
| + int err; |
| + struct dentry *lower_dentry; |
| + struct path lower_path; |
| + /* XXX readlink does not requires overriding credential */ |
| + |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + lower_dentry = lower_path.dentry; |
| + if (!d_inode(lower_dentry)->i_op || |
| + !d_inode(lower_dentry)->i_op->readlink) { |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + |
| + err = d_inode(lower_dentry)->i_op->readlink(lower_dentry, |
| + buf, bufsiz); |
| + if (err < 0) |
| + goto out; |
| + fsstack_copy_attr_atime(d_inode(dentry), d_inode(lower_dentry)); |
| + |
| +out: |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + return err; |
| +} |
| +#endif |
| + |
| +#if 0 |
| +static const char *sdcardfs_follow_link(struct dentry *dentry, void **cookie) |
| +{ |
| + char *buf; |
| + int len = PAGE_SIZE, err; |
| + mm_segment_t old_fs; |
| + |
| + /* This is freed by the put_link method assuming a successful call. */ |
| + buf = kmalloc(len, GFP_KERNEL); |
| + if (!buf) { |
| + buf = ERR_PTR(-ENOMEM); |
| + return buf; |
| + } |
| + |
| + /* read the symlink, and then we will follow it */ |
| + old_fs = get_fs(); |
| + set_fs(KERNEL_DS); |
| + err = sdcardfs_readlink(dentry, buf, len); |
| + set_fs(old_fs); |
| + if (err < 0) { |
| + kfree(buf); |
| + buf = ERR_PTR(err); |
| + } else { |
| + buf[err] = '\0'; |
| + } |
| + return *cookie = buf; |
| +} |
| +#endif |
| + |
| +static int sdcardfs_permission_wrn(struct inode *inode, int mask) |
| +{ |
| + WARN_RATELIMIT(1, "sdcardfs does not support permission. Use permission2.\n"); |
| + return -EINVAL; |
| +} |
| + |
| +void copy_attrs(struct inode *dest, const struct inode *src) |
| +{ |
| + dest->i_mode = src->i_mode; |
| + dest->i_uid = src->i_uid; |
| + dest->i_gid = src->i_gid; |
| + dest->i_rdev = src->i_rdev; |
| + dest->i_atime = src->i_atime; |
| + dest->i_mtime = src->i_mtime; |
| + dest->i_ctime = src->i_ctime; |
| + dest->i_blkbits = src->i_blkbits; |
| + dest->i_flags = src->i_flags; |
| +#ifdef CONFIG_FS_POSIX_ACL |
| + dest->i_acl = src->i_acl; |
| +#endif |
| +#ifdef CONFIG_SECURITY |
| + dest->i_security = src->i_security; |
| +#endif |
| +} |
| + |
| +static int sdcardfs_permission(struct vfsmount *mnt, struct inode *inode, int mask) |
| +{ |
| + int err; |
| + struct inode tmp; |
| + struct sdcardfs_inode_data *top = top_data_get(SDCARDFS_I(inode)); |
| + |
| + if (IS_ERR(mnt)) |
| + return PTR_ERR(mnt); |
| + |
| + if (!top) |
| + return -EINVAL; |
| + |
| + /* |
| + * Permission check on sdcardfs inode. |
| + * Calling process should have AID_SDCARD_RW permission |
| + * Since generic_permission only needs i_mode, i_uid, |
| + * i_gid, and i_sb, we can create a fake inode to pass |
| + * this information down in. |
| + * |
| + * The underlying code may attempt to take locks in some |
| + * cases for features we're not using, but if that changes, |
| + * locks must be dealt with to avoid undefined behavior. |
| + */ |
| + copy_attrs(&tmp, inode); |
| + tmp.i_uid = make_kuid(&init_user_ns, top->d_uid); |
| + tmp.i_gid = make_kgid(&init_user_ns, get_gid(mnt, inode->i_sb, top)); |
| + tmp.i_mode = (inode->i_mode & S_IFMT) |
| + | get_mode(mnt, SDCARDFS_I(inode), top); |
| + data_put(top); |
| + tmp.i_sb = inode->i_sb; |
| + if (IS_POSIXACL(inode)) |
| + pr_warn("%s: This may be undefined behavior...\n", __func__); |
| + err = generic_permission(&tmp, mask); |
| + return err; |
| +} |
| + |
| +static int sdcardfs_setattr_wrn(struct dentry *dentry, struct iattr *ia) |
| +{ |
| + WARN_RATELIMIT(1, "sdcardfs does not support setattr. User setattr2.\n"); |
| + return -EINVAL; |
| +} |
| + |
| +static int sdcardfs_setattr(struct vfsmount *mnt, struct dentry *dentry, struct iattr *ia) |
| +{ |
| + int err; |
| + struct dentry *lower_dentry; |
| + struct vfsmount *lower_mnt; |
| + struct inode *inode; |
| + struct inode *lower_inode; |
| + struct path lower_path; |
| + struct iattr lower_ia; |
| + struct dentry *parent; |
| + struct inode tmp; |
| + struct dentry tmp_d; |
| + struct sdcardfs_inode_data *top; |
| + |
| + const struct cred *saved_cred = NULL; |
| + |
| + inode = d_inode(dentry); |
| + top = top_data_get(SDCARDFS_I(inode)); |
| + |
| + if (!top) |
| + return -EINVAL; |
| + |
| + /* |
| + * Permission check on sdcardfs inode. |
| + * Calling process should have AID_SDCARD_RW permission |
| + * Since generic_permission only needs i_mode, i_uid, |
| + * i_gid, and i_sb, we can create a fake inode to pass |
| + * this information down in. |
| + * |
| + * The underlying code may attempt to take locks in some |
| + * cases for features we're not using, but if that changes, |
| + * locks must be dealt with to avoid undefined behavior. |
| + * |
| + */ |
| + copy_attrs(&tmp, inode); |
| + tmp.i_uid = make_kuid(&init_user_ns, top->d_uid); |
| + tmp.i_gid = make_kgid(&init_user_ns, get_gid(mnt, dentry->d_sb, top)); |
| + tmp.i_mode = (inode->i_mode & S_IFMT) |
| + | get_mode(mnt, SDCARDFS_I(inode), top); |
| + tmp.i_size = i_size_read(inode); |
| + data_put(top); |
| + tmp.i_sb = inode->i_sb; |
| + tmp_d.d_inode = &tmp; |
| + |
| + /* |
| + * Check if user has permission to change dentry. We don't check if |
| + * this user can change the lower inode: that should happen when |
| + * calling notify_change on the lower inode. |
| + */ |
| + /* prepare our own lower struct iattr (with the lower file) */ |
| + memcpy(&lower_ia, ia, sizeof(lower_ia)); |
| + /* Allow touch updating timestamps. A previous permission check ensures |
| + * we have write access. Changes to mode, owner, and group are ignored |
| + */ |
| + ia->ia_valid |= ATTR_FORCE; |
| + err = setattr_prepare(&tmp_d, ia); |
| + |
| + if (!err) { |
| + /* check the Android group ID */ |
| + parent = dget_parent(dentry); |
| + if (!check_caller_access_to_name(d_inode(parent), &dentry->d_name)) |
| + err = -EACCES; |
| + dput(parent); |
| + } |
| + |
| + if (err) |
| + goto out_err; |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(dentry->d_sb), |
| + SDCARDFS_I(inode)->data); |
| + if (!saved_cred) |
| + return -ENOMEM; |
| + |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + lower_dentry = lower_path.dentry; |
| + lower_mnt = lower_path.mnt; |
| + lower_inode = sdcardfs_lower_inode(inode); |
| + |
| + if (ia->ia_valid & ATTR_FILE) |
| + lower_ia.ia_file = sdcardfs_lower_file(ia->ia_file); |
| + |
| + lower_ia.ia_valid &= ~(ATTR_UID | ATTR_GID | ATTR_MODE); |
| + |
| + /* |
| + * If shrinking, first truncate upper level to cancel writing dirty |
| + * pages beyond the new eof; and also if its' maxbytes is more |
| + * limiting (fail with -EFBIG before making any change to the lower |
| + * level). There is no need to vmtruncate the upper level |
| + * afterwards in the other cases: we fsstack_copy_inode_size from |
| + * the lower level. |
| + */ |
| + if (ia->ia_valid & ATTR_SIZE) { |
| + err = inode_newsize_ok(&tmp, ia->ia_size); |
| + if (err) { |
| + goto out; |
| + } |
| + truncate_setsize(inode, ia->ia_size); |
| + } |
| + |
| + /* |
| + * mode change is for clearing setuid/setgid bits. Allow lower fs |
| + * to interpret this in its own way. |
| + */ |
| + if (lower_ia.ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) |
| + lower_ia.ia_valid &= ~ATTR_MODE; |
| + |
| + /* notify the (possibly copied-up) lower inode */ |
| + /* |
| + * Note: we use d_inode(lower_dentry), because lower_inode may be |
| + * unlinked (no inode->i_sb and i_ino==0. This happens if someone |
| + * tries to open(), unlink(), then ftruncate() a file. |
| + */ |
| + inode_lock(d_inode(lower_dentry)); |
| + err = notify_change2(lower_mnt, lower_dentry, &lower_ia, /* note: lower_ia */ |
| + NULL); |
| + inode_unlock(d_inode(lower_dentry)); |
| + if (err) |
| + goto out; |
| + |
| + /* get attributes from the lower inode and update derived permissions */ |
| + sdcardfs_copy_and_fix_attrs(inode, lower_inode); |
| + |
| + /* |
| + * Not running fsstack_copy_inode_size(inode, lower_inode), because |
| + * VFS should update our inode size, and notify_change on |
| + * lower_inode should update its size. |
| + */ |
| + |
| +out: |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + revert_fsids(saved_cred); |
| +out_err: |
| + return err; |
| +} |
| + |
| +static int sdcardfs_fillattr(struct vfsmount *mnt, struct inode *inode, |
| + struct kstat *lower_stat, struct kstat *stat) |
| +{ |
| + struct sdcardfs_inode_info *info = SDCARDFS_I(inode); |
| + struct sdcardfs_inode_data *top = top_data_get(info); |
| + struct super_block *sb = inode->i_sb; |
| + |
| + if (!top) |
| + return -EINVAL; |
| + |
| + stat->dev = inode->i_sb->s_dev; |
| + stat->ino = inode->i_ino; |
| + stat->mode = (inode->i_mode & S_IFMT) | get_mode(mnt, info, top); |
| + stat->nlink = inode->i_nlink; |
| + stat->uid = make_kuid(&init_user_ns, top->d_uid); |
| + stat->gid = make_kgid(&init_user_ns, get_gid(mnt, sb, top)); |
| + stat->rdev = inode->i_rdev; |
| + stat->size = lower_stat->size; |
| + stat->atime = lower_stat->atime; |
| + stat->mtime = lower_stat->mtime; |
| + stat->ctime = lower_stat->ctime; |
| + stat->blksize = lower_stat->blksize; |
| + stat->blocks = lower_stat->blocks; |
| + data_put(top); |
| + return 0; |
| +} |
| +static int sdcardfs_getattr(const struct path *path, struct kstat *stat, |
| + u32 request_mask, unsigned int flags) |
| +{ |
| + struct vfsmount *mnt = path->mnt; |
| + struct dentry *dentry = path->dentry; |
| + struct kstat lower_stat; |
| + struct path lower_path; |
| + struct dentry *parent; |
| + int err; |
| + |
| + parent = dget_parent(dentry); |
| + if (!check_caller_access_to_name(d_inode(parent), &dentry->d_name)) { |
| + dput(parent); |
| + return -EACCES; |
| + } |
| + dput(parent); |
| + |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + err = vfs_getattr(&lower_path, &lower_stat, request_mask, flags); |
| + if (err) |
| + goto out; |
| + sdcardfs_copy_and_fix_attrs(d_inode(dentry), |
| + d_inode(lower_path.dentry)); |
| + err = sdcardfs_fillattr(mnt, d_inode(dentry), &lower_stat, stat); |
| +out: |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + return err; |
| +} |
| + |
| +const struct inode_operations sdcardfs_symlink_iops = { |
| + .permission2 = sdcardfs_permission, |
| + .setattr2 = sdcardfs_setattr, |
| + /* XXX Following operations are implemented, |
| + * but FUSE(sdcard) or FAT does not support them |
| + * These methods are *NOT* perfectly tested. |
| + .readlink = sdcardfs_readlink, |
| + .follow_link = sdcardfs_follow_link, |
| + .put_link = kfree_put_link, |
| + */ |
| +}; |
| + |
| +const struct inode_operations sdcardfs_dir_iops = { |
| + .create = sdcardfs_create, |
| + .lookup = sdcardfs_lookup, |
| + .permission = sdcardfs_permission_wrn, |
| + .permission2 = sdcardfs_permission, |
| + .unlink = sdcardfs_unlink, |
| + .mkdir = sdcardfs_mkdir, |
| + .rmdir = sdcardfs_rmdir, |
| + .rename = sdcardfs_rename, |
| + .setattr = sdcardfs_setattr_wrn, |
| + .setattr2 = sdcardfs_setattr, |
| + .getattr = sdcardfs_getattr, |
| +}; |
| + |
| +const struct inode_operations sdcardfs_main_iops = { |
| + .permission = sdcardfs_permission_wrn, |
| + .permission2 = sdcardfs_permission, |
| + .setattr = sdcardfs_setattr_wrn, |
| + .setattr2 = sdcardfs_setattr, |
| + .getattr = sdcardfs_getattr, |
| +}; |
| diff --git a/fs/sdcardfs/lookup.c b/fs/sdcardfs/lookup.c |
| new file mode 100644 |
| index 000000000000..a5c9686090e0 |
| --- /dev/null |
| +++ b/fs/sdcardfs/lookup.c |
| @@ -0,0 +1,470 @@ |
| +/* |
| + * fs/sdcardfs/lookup.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| +#include "linux/delay.h" |
| + |
| +/* The dentry cache is just so we have properly sized dentries */ |
| +static struct kmem_cache *sdcardfs_dentry_cachep; |
| + |
| +int sdcardfs_init_dentry_cache(void) |
| +{ |
| + sdcardfs_dentry_cachep = |
| + kmem_cache_create("sdcardfs_dentry", |
| + sizeof(struct sdcardfs_dentry_info), |
| + 0, SLAB_RECLAIM_ACCOUNT, NULL); |
| + |
| + return sdcardfs_dentry_cachep ? 0 : -ENOMEM; |
| +} |
| + |
| +void sdcardfs_destroy_dentry_cache(void) |
| +{ |
| + kmem_cache_destroy(sdcardfs_dentry_cachep); |
| +} |
| + |
| +void free_dentry_private_data(struct dentry *dentry) |
| +{ |
| + kmem_cache_free(sdcardfs_dentry_cachep, dentry->d_fsdata); |
| + dentry->d_fsdata = NULL; |
| +} |
| + |
| +/* allocate new dentry private data */ |
| +int new_dentry_private_data(struct dentry *dentry) |
| +{ |
| + struct sdcardfs_dentry_info *info = SDCARDFS_D(dentry); |
| + |
| + /* use zalloc to init dentry_info.lower_path */ |
| + info = kmem_cache_zalloc(sdcardfs_dentry_cachep, GFP_ATOMIC); |
| + if (!info) |
| + return -ENOMEM; |
| + |
| + spin_lock_init(&info->lock); |
| + dentry->d_fsdata = info; |
| + |
| + return 0; |
| +} |
| + |
| +struct inode_data { |
| + struct inode *lower_inode; |
| + userid_t id; |
| +}; |
| + |
| +static int sdcardfs_inode_test(struct inode *inode, void *candidate_data/*void *candidate_lower_inode*/) |
| +{ |
| + struct inode *current_lower_inode = sdcardfs_lower_inode(inode); |
| + userid_t current_userid = SDCARDFS_I(inode)->data->userid; |
| + |
| + if (current_lower_inode == ((struct inode_data *)candidate_data)->lower_inode && |
| + current_userid == ((struct inode_data *)candidate_data)->id) |
| + return 1; /* found a match */ |
| + else |
| + return 0; /* no match */ |
| +} |
| + |
| +static int sdcardfs_inode_set(struct inode *inode, void *lower_inode) |
| +{ |
| + /* we do actual inode initialization in sdcardfs_iget */ |
| + return 0; |
| +} |
| + |
| +struct inode *sdcardfs_iget(struct super_block *sb, struct inode *lower_inode, userid_t id) |
| +{ |
| + struct sdcardfs_inode_info *info; |
| + struct inode_data data; |
| + struct inode *inode; /* the new inode to return */ |
| + |
| + if (!igrab(lower_inode)) |
| + return ERR_PTR(-ESTALE); |
| + |
| + data.id = id; |
| + data.lower_inode = lower_inode; |
| + inode = iget5_locked(sb, /* our superblock */ |
| + /* |
| + * hashval: we use inode number, but we can |
| + * also use "(unsigned long)lower_inode" |
| + * instead. |
| + */ |
| + lower_inode->i_ino, /* hashval */ |
| + sdcardfs_inode_test, /* inode comparison function */ |
| + sdcardfs_inode_set, /* inode init function */ |
| + &data); /* data passed to test+set fxns */ |
| + if (!inode) { |
| + iput(lower_inode); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + /* if found a cached inode, then just return it (after iput) */ |
| + if (!(inode->i_state & I_NEW)) { |
| + iput(lower_inode); |
| + return inode; |
| + } |
| + |
| + /* initialize new inode */ |
| + info = SDCARDFS_I(inode); |
| + |
| + inode->i_ino = lower_inode->i_ino; |
| + sdcardfs_set_lower_inode(inode, lower_inode); |
| + |
| + inode_inc_iversion_raw(inode); |
| + |
| + /* use different set of inode ops for symlinks & directories */ |
| + if (S_ISDIR(lower_inode->i_mode)) |
| + inode->i_op = &sdcardfs_dir_iops; |
| + else if (S_ISLNK(lower_inode->i_mode)) |
| + inode->i_op = &sdcardfs_symlink_iops; |
| + else |
| + inode->i_op = &sdcardfs_main_iops; |
| + |
| + /* use different set of file ops for directories */ |
| + if (S_ISDIR(lower_inode->i_mode)) |
| + inode->i_fop = &sdcardfs_dir_fops; |
| + else |
| + inode->i_fop = &sdcardfs_main_fops; |
| + |
| + inode->i_mapping->a_ops = &sdcardfs_aops; |
| + |
| + inode->i_atime.tv_sec = 0; |
| + inode->i_atime.tv_nsec = 0; |
| + inode->i_mtime.tv_sec = 0; |
| + inode->i_mtime.tv_nsec = 0; |
| + inode->i_ctime.tv_sec = 0; |
| + inode->i_ctime.tv_nsec = 0; |
| + |
| + /* properly initialize special inodes */ |
| + if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || |
| + S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) |
| + init_special_inode(inode, lower_inode->i_mode, |
| + lower_inode->i_rdev); |
| + |
| + /* all well, copy inode attributes */ |
| + sdcardfs_copy_and_fix_attrs(inode, lower_inode); |
| + fsstack_copy_inode_size(inode, lower_inode); |
| + |
| + unlock_new_inode(inode); |
| + return inode; |
| +} |
| + |
| +/* |
| + * Helper interpose routine, called directly by ->lookup to handle |
| + * spliced dentries. |
| + */ |
| +static struct dentry *__sdcardfs_interpose(struct dentry *dentry, |
| + struct super_block *sb, |
| + struct path *lower_path, |
| + userid_t id) |
| +{ |
| + struct inode *inode; |
| + struct inode *lower_inode; |
| + struct super_block *lower_sb; |
| + struct dentry *ret_dentry; |
| + |
| + lower_inode = d_inode(lower_path->dentry); |
| + lower_sb = sdcardfs_lower_super(sb); |
| + |
| + /* check that the lower file system didn't cross a mount point */ |
| + if (lower_inode->i_sb != lower_sb) { |
| + ret_dentry = ERR_PTR(-EXDEV); |
| + goto out; |
| + } |
| + |
| + /* |
| + * We allocate our new inode below by calling sdcardfs_iget, |
| + * which will initialize some of the new inode's fields |
| + */ |
| + |
| + /* inherit lower inode number for sdcardfs's inode */ |
| + inode = sdcardfs_iget(sb, lower_inode, id); |
| + if (IS_ERR(inode)) { |
| + ret_dentry = ERR_CAST(inode); |
| + goto out; |
| + } |
| + |
| + ret_dentry = d_splice_alias(inode, dentry); |
| + dentry = ret_dentry ?: dentry; |
| + if (!IS_ERR(dentry)) |
| + update_derived_permission_lock(dentry); |
| +out: |
| + return ret_dentry; |
| +} |
| + |
| +/* |
| + * Connect an sdcardfs inode dentry/inode with several lower ones. This is |
| + * the classic stackable file system "vnode interposition" action. |
| + * |
| + * @dentry: sdcardfs's dentry which interposes on lower one |
| + * @sb: sdcardfs's super_block |
| + * @lower_path: the lower path (caller does path_get/put) |
| + */ |
| +int sdcardfs_interpose(struct dentry *dentry, struct super_block *sb, |
| + struct path *lower_path, userid_t id) |
| +{ |
| + struct dentry *ret_dentry; |
| + |
| + ret_dentry = __sdcardfs_interpose(dentry, sb, lower_path, id); |
| + return PTR_ERR(ret_dentry); |
| +} |
| + |
| +struct sdcardfs_name_data { |
| + struct dir_context ctx; |
| + const struct qstr *to_find; |
| + char *name; |
| + bool found; |
| +}; |
| + |
| +static int sdcardfs_name_match(struct dir_context *ctx, const char *name, |
| + int namelen, loff_t offset, u64 ino, unsigned int d_type) |
| +{ |
| + struct sdcardfs_name_data *buf = container_of(ctx, struct sdcardfs_name_data, ctx); |
| + struct qstr candidate = QSTR_INIT(name, namelen); |
| + |
| + if (qstr_case_eq(buf->to_find, &candidate)) { |
| + memcpy(buf->name, name, namelen); |
| + buf->name[namelen] = 0; |
| + buf->found = true; |
| + return 1; |
| + } |
| + return 0; |
| +} |
| + |
| +/* |
| + * Main driver function for sdcardfs's lookup. |
| + * |
| + * Returns: NULL (ok), ERR_PTR if an error occurred. |
| + * Fills in lower_parent_path with <dentry,mnt> on success. |
| + */ |
| +static struct dentry *__sdcardfs_lookup(struct dentry *dentry, |
| + unsigned int flags, struct path *lower_parent_path, userid_t id) |
| +{ |
| + int err = 0; |
| + struct vfsmount *lower_dir_mnt; |
| + struct dentry *lower_dir_dentry = NULL; |
| + struct dentry *lower_dentry; |
| + const struct qstr *name; |
| + struct path lower_path; |
| + struct qstr dname; |
| + struct dentry *ret_dentry = NULL; |
| + struct sdcardfs_sb_info *sbi; |
| + |
| + sbi = SDCARDFS_SB(dentry->d_sb); |
| + /* must initialize dentry operations */ |
| + d_set_d_op(dentry, &sdcardfs_ci_dops); |
| + |
| + if (IS_ROOT(dentry)) |
| + goto out; |
| + |
| + name = &dentry->d_name; |
| + |
| + /* now start the actual lookup procedure */ |
| + lower_dir_dentry = lower_parent_path->dentry; |
| + lower_dir_mnt = lower_parent_path->mnt; |
| + |
| + /* Use vfs_path_lookup to check if the dentry exists or not */ |
| + err = vfs_path_lookup(lower_dir_dentry, lower_dir_mnt, name->name, 0, |
| + &lower_path); |
| + /* check for other cases */ |
| + if (err == -ENOENT) { |
| + struct file *file; |
| + const struct cred *cred = current_cred(); |
| + |
| + struct sdcardfs_name_data buffer = { |
| + .ctx.actor = sdcardfs_name_match, |
| + .to_find = name, |
| + .name = __getname(), |
| + .found = false, |
| + }; |
| + |
| + if (!buffer.name) { |
| + err = -ENOMEM; |
| + goto out; |
| + } |
| + file = dentry_open(lower_parent_path, O_RDONLY, cred); |
| + if (IS_ERR(file)) { |
| + err = PTR_ERR(file); |
| + goto put_name; |
| + } |
| + err = iterate_dir(file, &buffer.ctx); |
| + fput(file); |
| + if (err) |
| + goto put_name; |
| + |
| + if (buffer.found) |
| + err = vfs_path_lookup(lower_dir_dentry, |
| + lower_dir_mnt, |
| + buffer.name, 0, |
| + &lower_path); |
| + else |
| + err = -ENOENT; |
| +put_name: |
| + __putname(buffer.name); |
| + } |
| + |
| + /* no error: handle positive dentries */ |
| + if (!err) { |
| + /* check if the dentry is an obb dentry |
| + * if true, the lower_inode must be replaced with |
| + * the inode of the graft path |
| + */ |
| + |
| + if (need_graft_path(dentry)) { |
| + |
| + /* setup_obb_dentry() |
| + * The lower_path will be stored to the dentry's orig_path |
| + * and the base obbpath will be copyed to the lower_path variable. |
| + * if an error returned, there's no change in the lower_path |
| + * returns: -ERRNO if error (0: no error) |
| + */ |
| + err = setup_obb_dentry(dentry, &lower_path); |
| + |
| + if (err) { |
| + /* if the sbi->obbpath is not available, we can optionally |
| + * setup the lower_path with its orig_path. |
| + * but, the current implementation just returns an error |
| + * because the sdcard daemon also regards this case as |
| + * a lookup fail. |
| + */ |
| + pr_info("sdcardfs: base obbpath is not available\n"); |
| + sdcardfs_put_reset_orig_path(dentry); |
| + goto out; |
| + } |
| + } |
| + |
| + sdcardfs_set_lower_path(dentry, &lower_path); |
| + ret_dentry = |
| + __sdcardfs_interpose(dentry, dentry->d_sb, &lower_path, id); |
| + if (IS_ERR(ret_dentry)) { |
| + err = PTR_ERR(ret_dentry); |
| + /* path_put underlying path on error */ |
| + sdcardfs_put_reset_lower_path(dentry); |
| + } |
| + goto out; |
| + } |
| + |
| + /* |
| + * We don't consider ENOENT an error, and we want to return a |
| + * negative dentry. |
| + */ |
| + if (err && err != -ENOENT) |
| + goto out; |
| + |
| + /* instatiate a new negative dentry */ |
| + dname.name = name->name; |
| + dname.len = name->len; |
| + |
| + /* See if the low-level filesystem might want |
| + * to use its own hash |
| + */ |
| + lower_dentry = d_hash_and_lookup(lower_dir_dentry, &dname); |
| + if (IS_ERR(lower_dentry)) |
| + return lower_dentry; |
| + |
| + if (!lower_dentry) { |
| + /* We called vfs_path_lookup earlier, and did not get a negative |
| + * dentry then. Don't confuse the lower filesystem by forcing |
| + * one on it now... |
| + */ |
| + err = -ENOENT; |
| + goto out; |
| + } |
| + |
| + lower_path.dentry = lower_dentry; |
| + lower_path.mnt = mntget(lower_dir_mnt); |
| + sdcardfs_set_lower_path(dentry, &lower_path); |
| + |
| + /* |
| + * If the intent is to create a file, then don't return an error, so |
| + * the VFS will continue the process of making this negative dentry |
| + * into a positive one. |
| + */ |
| + if (flags & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET)) |
| + err = 0; |
| + |
| +out: |
| + if (err) |
| + return ERR_PTR(err); |
| + return ret_dentry; |
| +} |
| + |
| +/* |
| + * On success: |
| + * fills dentry object appropriate values and returns NULL. |
| + * On fail (== error) |
| + * returns error ptr |
| + * |
| + * @dir : Parent inode. |
| + * @dentry : Target dentry to lookup. we should set each of fields. |
| + * (dentry->d_name is initialized already) |
| + * @nd : nameidata of parent inode |
| + */ |
| +struct dentry *sdcardfs_lookup(struct inode *dir, struct dentry *dentry, |
| + unsigned int flags) |
| +{ |
| + struct dentry *ret = NULL, *parent; |
| + struct path lower_parent_path; |
| + int err = 0; |
| + const struct cred *saved_cred = NULL; |
| + |
| + parent = dget_parent(dentry); |
| + |
| + if (!check_caller_access_to_name(d_inode(parent), &dentry->d_name)) { |
| + ret = ERR_PTR(-EACCES); |
| + goto out_err; |
| + } |
| + |
| + /* save current_cred and override it */ |
| + saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb), |
| + SDCARDFS_I(dir)->data); |
| + if (!saved_cred) { |
| + ret = ERR_PTR(-ENOMEM); |
| + goto out_err; |
| + } |
| + |
| + sdcardfs_get_lower_path(parent, &lower_parent_path); |
| + |
| + /* allocate dentry private data. We free it in ->d_release */ |
| + err = new_dentry_private_data(dentry); |
| + if (err) { |
| + ret = ERR_PTR(err); |
| + goto out; |
| + } |
| + |
| + ret = __sdcardfs_lookup(dentry, flags, &lower_parent_path, |
| + SDCARDFS_I(dir)->data->userid); |
| + if (IS_ERR(ret)) |
| + goto out; |
| + if (ret) |
| + dentry = ret; |
| + if (d_inode(dentry)) { |
| + fsstack_copy_attr_times(d_inode(dentry), |
| + sdcardfs_lower_inode(d_inode(dentry))); |
| + /* get derived permission */ |
| + get_derived_permission(parent, dentry); |
| + fixup_tmp_permissions(d_inode(dentry)); |
| + fixup_lower_ownership(dentry, dentry->d_name.name); |
| + } |
| + /* update parent directory's atime */ |
| + fsstack_copy_attr_atime(d_inode(parent), |
| + sdcardfs_lower_inode(d_inode(parent))); |
| + |
| +out: |
| + sdcardfs_put_lower_path(parent, &lower_parent_path); |
| + revert_fsids(saved_cred); |
| +out_err: |
| + dput(parent); |
| + return ret; |
| +} |
| diff --git a/fs/sdcardfs/main.c b/fs/sdcardfs/main.c |
| new file mode 100644 |
| index 000000000000..818122410bfe |
| --- /dev/null |
| +++ b/fs/sdcardfs/main.c |
| @@ -0,0 +1,455 @@ |
| +/* |
| + * fs/sdcardfs/main.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| +#include <linux/fs_context.h> |
| +#include <linux/fs_parser.h> |
| +#include <linux/module.h> |
| +#include <linux/types.h> |
| +#include <linux/parser.h> |
| + |
| +enum sdcardfs_param { |
| + Opt_fsuid, |
| + Opt_fsgid, |
| + Opt_gid, |
| + Opt_debug, |
| + Opt_mask, |
| + Opt_multiuser, |
| + Opt_userid, |
| + Opt_reserved_mb, |
| + Opt_gid_derivation, |
| + Opt_default_normal, |
| + Opt_nocache, |
| + Opt_unshared_obb, |
| + Opt_err, |
| +}; |
| + |
| +static const struct fs_parameter_spec sdcardfs_param_specs[] = { |
| + fsparam_u32("fsuid", Opt_fsuid), |
| + fsparam_u32("fsgid", Opt_fsgid), |
| + fsparam_u32("gid", Opt_gid), |
| + fsparam_bool("debug", Opt_debug), |
| + fsparam_u32("mask", Opt_mask), |
| + fsparam_u32("userid", Opt_userid), |
| + fsparam_bool("multiuser", Opt_multiuser), |
| + fsparam_bool("derive_gid", Opt_gid_derivation), |
| + fsparam_bool("default_normal", Opt_default_normal), |
| + fsparam_bool("unshared_obb", Opt_unshared_obb), |
| + fsparam_u32("reserved_mb", Opt_reserved_mb), |
| + fsparam_bool("nocache", Opt_nocache), |
| + {} |
| +}; |
| + |
| +static const struct fs_parameter_description sdcardfs_parameters = { |
| + .name = "sdcardfs", |
| + .specs = sdcardfs_param_specs, |
| +}; |
| + |
| +static int sdcardfs_parse_param(struct fs_context *fc, struct fs_parameter *param) |
| +{ |
| + struct sdcardfs_context_options *fc_opts = fc->fs_private; |
| + struct sdcardfs_mount_options *opts = &fc_opts->opts; |
| + struct sdcardfs_vfsmount_options *vfsopts = &fc_opts->vfsopts; |
| + struct fs_parse_result result; |
| + int opt; |
| + |
| + opt = fs_parse(fc, &sdcardfs_parameters, param, &result); |
| + if (opt < 0) |
| + return opt; |
| + |
| + switch (opt) { |
| + case Opt_debug: |
| + opts->debug = true; |
| + break; |
| + case Opt_fsuid: |
| + opts->fs_low_uid = result.uint_32; |
| + break; |
| + case Opt_fsgid: |
| + opts->fs_low_gid = result.uint_32; |
| + break; |
| + case Opt_gid: |
| + vfsopts->gid = result.uint_32; |
| + break; |
| + case Opt_userid: |
| + opts->fs_user_id = result.uint_32; |
| + break; |
| + case Opt_mask: |
| + vfsopts->mask = result.uint_32; |
| + break; |
| + case Opt_multiuser: |
| + opts->multiuser = true; |
| + break; |
| + case Opt_reserved_mb: |
| + opts->reserved_mb = result.uint_32; |
| + break; |
| + case Opt_gid_derivation: |
| + opts->gid_derivation = true; |
| + break; |
| + case Opt_default_normal: |
| + opts->default_normal = true; |
| + break; |
| + case Opt_nocache: |
| + opts->nocache = true; |
| + break; |
| + case Opt_unshared_obb: |
| + opts->unshared_obb = true; |
| + break; |
| + default: |
| + return -EINVAL; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static void copy_sb_opts(struct sdcardfs_mount_options *opts, |
| + struct fs_context *fc) |
| +{ |
| + struct sdcardfs_context_options *fcopts = fc->fs_private; |
| + |
| + opts->debug = fcopts->opts.debug; |
| + opts->default_normal = fcopts->opts.default_normal; |
| + opts->fs_low_gid = fcopts->opts.fs_low_gid; |
| + opts->fs_low_uid = fcopts->opts.fs_low_uid; |
| + opts->fs_user_id = fcopts->opts.fs_user_id; |
| + opts->gid_derivation = fcopts->opts.gid_derivation; |
| + opts->multiuser = fcopts->opts.multiuser; |
| + opts->nocache = fcopts->opts.nocache; |
| + opts->reserved_mb = fcopts->opts.reserved_mb; |
| + opts->unshared_obb = fcopts->opts.unshared_obb; |
| +} |
| + |
| +#if 0 |
| +/* |
| + * our custom d_alloc_root work-alike |
| + * |
| + * we can't use d_alloc_root if we want to use our own interpose function |
| + * unchanged, so we simply call our own "fake" d_alloc_root |
| + */ |
| +static struct dentry *sdcardfs_d_alloc_root(struct super_block *sb) |
| +{ |
| + struct dentry *ret = NULL; |
| + |
| + if (sb) { |
| + static const struct qstr name = { |
| + .name = "/", |
| + .len = 1 |
| + }; |
| + |
| + ret = d_alloc(NULL, &name); |
| + if (ret) { |
| + d_set_d_op(ret, &sdcardfs_ci_dops); |
| + ret->d_sb = sb; |
| + ret->d_parent = ret; |
| + } |
| + } |
| + return ret; |
| +} |
| +#endif |
| + |
| +DEFINE_MUTEX(sdcardfs_super_list_lock); |
| +EXPORT_SYMBOL_GPL(sdcardfs_super_list_lock); |
| +LIST_HEAD(sdcardfs_super_list); |
| +EXPORT_SYMBOL_GPL(sdcardfs_super_list); |
| + |
| +struct sdcardfs_mount_private { |
| + struct vfsmount *mnt; |
| + const char *dev_name; |
| + void *raw_data; |
| +}; |
| + |
| +static int __sdcardfs_fill_super( |
| + struct super_block *sb, |
| + struct fs_context *fc) |
| +{ |
| + int err = 0; |
| + struct super_block *lower_sb; |
| + struct path lower_path; |
| + struct sdcardfs_sb_info *sb_info; |
| + struct inode *inode; |
| + const char *dev_name = fc->source; |
| + struct sdcardfs_context_options *fcopts = fc->fs_private; |
| + struct sdcardfs_mount_options *opts = &fcopts->opts; |
| + struct sdcardfs_vfsmount_options *mntopts = &fcopts->vfsopts; |
| + |
| + pr_info("sdcardfs version 2.0\n"); |
| + |
| + if (!dev_name) { |
| + pr_err("sdcardfs: read_super: missing dev_name argument\n"); |
| + err = -EINVAL; |
| + goto out; |
| + } |
| + |
| + pr_info("sdcardfs: dev_name -> %s\n", dev_name); |
| + pr_info("sdcardfs: gid=%d,mask=%x\n", mntopts->gid, mntopts->mask); |
| + |
| + /* parse lower path */ |
| + err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, |
| + &lower_path); |
| + if (err) { |
| + pr_err("sdcardfs: error accessing lower directory '%s'\n", dev_name); |
| + goto out; |
| + } |
| + |
| + /* allocate superblock private data */ |
| + sb->s_fs_info = kzalloc(sizeof(struct sdcardfs_sb_info), GFP_KERNEL); |
| + if (!SDCARDFS_SB(sb)) { |
| + pr_crit("sdcardfs: read_super: out of memory\n"); |
| + err = -ENOMEM; |
| + goto out_free; |
| + } |
| + |
| + sb_info = sb->s_fs_info; |
| + copy_sb_opts(&sb_info->options, fc); |
| + if (opts->debug) { |
| + pr_info("sdcardfs : options - debug:%d\n", opts->debug); |
| + pr_info("sdcardfs : options - gid:%d\n", mntopts->gid); |
| + pr_info("sdcardfs : options - mask:%d\n", mntopts->mask); |
| + } |
| + |
| + /* set the lower superblock field of upper superblock */ |
| + lower_sb = lower_path.dentry->d_sb; |
| + atomic_inc(&lower_sb->s_active); |
| + sdcardfs_set_lower_super(sb, lower_sb); |
| + |
| + sb->s_stack_depth = lower_sb->s_stack_depth + 1; |
| + if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { |
| + pr_err("sdcardfs: maximum fs stacking depth exceeded\n"); |
| + err = -EINVAL; |
| + goto out_sput; |
| + } |
| + |
| + /* inherit maxbytes from lower file system */ |
| + sb->s_maxbytes = lower_sb->s_maxbytes; |
| + |
| + /* |
| + * Our c/m/atime granularity is 1 ns because we may stack on file |
| + * systems whose granularity is as good. |
| + */ |
| + sb->s_time_gran = 1; |
| + |
| + sb->s_magic = SDCARDFS_SUPER_MAGIC; |
| + sb->s_op = &sdcardfs_sops; |
| + |
| + /* get a new inode and allocate our root dentry */ |
| + inode = sdcardfs_iget(sb, d_inode(lower_path.dentry), 0); |
| + if (IS_ERR(inode)) { |
| + err = PTR_ERR(inode); |
| + goto out_sput; |
| + } |
| + sb->s_root = d_make_root(inode); |
| + if (!sb->s_root) { |
| + err = -ENOMEM; |
| + goto out_sput; |
| + } |
| + d_set_d_op(sb->s_root, &sdcardfs_ci_dops); |
| + |
| + /* link the upper and lower dentries */ |
| + sb->s_root->d_fsdata = NULL; |
| + err = new_dentry_private_data(sb->s_root); |
| + if (err) |
| + goto out_freeroot; |
| + |
| + /* set the lower dentries for s_root */ |
| + sdcardfs_set_lower_path(sb->s_root, &lower_path); |
| + |
| + /* |
| + * No need to call interpose because we already have a positive |
| + * dentry, which was instantiated by d_make_root. Just need to |
| + * d_rehash it. |
| + */ |
| + d_rehash(sb->s_root); |
| + |
| + /* setup permission policy */ |
| + sb_info->obbpath_s = kzalloc(PATH_MAX, GFP_KERNEL); |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + if (sb_info->options.multiuser) { |
| + setup_derived_state(d_inode(sb->s_root), PERM_PRE_ROOT, |
| + sb_info->options.fs_user_id, AID_ROOT); |
| + snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name); |
| + } else { |
| + setup_derived_state(d_inode(sb->s_root), PERM_ROOT, |
| + sb_info->options.fs_user_id, AID_ROOT); |
| + snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name); |
| + } |
| + fixup_tmp_permissions(d_inode(sb->s_root)); |
| + sb_info->sb = sb; |
| + list_add(&sb_info->list, &sdcardfs_super_list); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| + |
| + if (!(fc->sb_flags & SB_SILENT)) |
| + pr_info("sdcardfs: mounted on top of %s type %s\n", |
| + dev_name, lower_sb->s_type->name); |
| + goto out; /* all is well */ |
| + |
| + /* no longer needed: free_dentry_private_data(sb->s_root); */ |
| +out_freeroot: |
| + dput(sb->s_root); |
| + sb->s_root = NULL; |
| +out_sput: |
| + /* drop refs we took earlier */ |
| + atomic_dec(&lower_sb->s_active); |
| + kfree(SDCARDFS_SB(sb)); |
| + sb->s_fs_info = NULL; |
| +out_free: |
| + path_put(&lower_path); |
| + |
| +out: |
| + return err; |
| +} |
| + |
| +static int sdcardfs_get_tree(struct fs_context *fc) |
| +{ |
| + return vfs_get_super(fc, vfs_get_independent_super, |
| + __sdcardfs_fill_super); |
| +} |
| + |
| +void *sdcardfs_alloc_mnt_data(void) |
| +{ |
| + return kmalloc(sizeof(struct sdcardfs_vfsmount_options), GFP_KERNEL); |
| +} |
| + |
| +void sdcardfs_kill_sb(struct super_block *sb) |
| +{ |
| + struct sdcardfs_sb_info *sbi; |
| + |
| + if (sb->s_magic == SDCARDFS_SUPER_MAGIC && sb->s_fs_info) { |
| + sbi = SDCARDFS_SB(sb); |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + list_del(&sbi->list); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| + } |
| + kill_anon_super(sb); |
| +} |
| + |
| +static void sdcardfs_free_fs_context(struct fs_context *fc) |
| +{ |
| + struct sdcardfs_context_options *fc_opts = fc->fs_private; |
| + |
| + kfree(fc_opts); |
| +} |
| + |
| +/* Most of the remount happens in sdcardfs_update_mnt_data */ |
| +static int sdcardfs_reconfigure_context(struct fs_context *fc) |
| +{ |
| + struct sdcardfs_context_options *fc_opts = fc->fs_private; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(fc->root->d_sb); |
| + |
| + sbi->options.debug = fc_opts->opts.debug; |
| + if (sbi->options.debug) { |
| + pr_info("sdcardfs : options - debug:%d\n", sbi->options.debug); |
| + pr_info("sdcardfs : options - gid:%d\n", fc_opts->vfsopts.gid); |
| + pr_info("sdcardfs : options - mask:%d\n", |
| + fc_opts->vfsopts.mask); |
| + } |
| + return 0; |
| +} |
| + |
| +/* reconfigure is handled by sdcardfs_update_mnt_data */ |
| +static const struct fs_context_operations sdcardfs_context_options_ops = { |
| + |
| + .parse_param = sdcardfs_parse_param, |
| + .get_tree = sdcardfs_get_tree, |
| + .free = sdcardfs_free_fs_context, |
| + .reconfigure = sdcardfs_reconfigure_context, |
| +}; |
| + |
| +static int sdcardfs_init_fs_context(struct fs_context *fc) |
| +{ |
| + struct sdcardfs_context_options *fc_opts = |
| + kmalloc(sizeof(struct sdcardfs_context_options), GFP_KERNEL); |
| + |
| + /* by default, we use AID_MEDIA_RW as uid, gid */ |
| + fc_opts->opts.fs_low_uid = AID_MEDIA_RW; |
| + fc_opts->opts.fs_low_gid = AID_MEDIA_RW; |
| + fc_opts->opts.fs_user_id = 0; |
| + fc_opts->vfsopts.gid = 0; |
| + fc_opts->vfsopts.mask = 0; |
| + |
| + /* by default, 0MB is reserved */ |
| + fc_opts->opts.reserved_mb = 0; |
| + /* by default, gid derivation is off */ |
| + fc_opts->opts.gid_derivation = false; |
| + fc_opts->opts.default_normal = false; |
| + fc_opts->opts.nocache = false; |
| + fc_opts->opts.multiuser = false; |
| + fc_opts->opts.debug = false; |
| + |
| + fc->fs_private = fc_opts; |
| + fc->ops = &sdcardfs_context_options_ops; |
| + return 0; |
| +} |
| + |
| +static struct file_system_type sdcardfs_fs_type = { |
| + .owner = THIS_MODULE, |
| + .name = SDCARDFS_NAME, |
| + .alloc_mnt_data = sdcardfs_alloc_mnt_data, |
| + .kill_sb = sdcardfs_kill_sb, |
| + .init_fs_context = sdcardfs_init_fs_context, |
| + .fs_flags = 0, |
| +}; |
| +MODULE_ALIAS_FS(SDCARDFS_NAME); |
| + |
| +static int __init init_sdcardfs_fs(void) |
| +{ |
| + int err; |
| + |
| + pr_info("Registering sdcardfs " SDCARDFS_VERSION "\n"); |
| + |
| + err = sdcardfs_init_inode_cache(); |
| + if (err) |
| + goto out; |
| + err = sdcardfs_init_dentry_cache(); |
| + if (err) |
| + goto out; |
| + err = packagelist_init(); |
| + if (err) |
| + goto out; |
| + err = register_filesystem(&sdcardfs_fs_type); |
| +out: |
| + if (err) { |
| + sdcardfs_destroy_inode_cache(); |
| + sdcardfs_destroy_dentry_cache(); |
| + packagelist_exit(); |
| + } |
| + return err; |
| +} |
| + |
| +static void __exit exit_sdcardfs_fs(void) |
| +{ |
| + sdcardfs_destroy_inode_cache(); |
| + sdcardfs_destroy_dentry_cache(); |
| + packagelist_exit(); |
| + unregister_filesystem(&sdcardfs_fs_type); |
| + pr_info("Completed sdcardfs module unload\n"); |
| +} |
| + |
| +/* Original wrapfs authors */ |
| +MODULE_AUTHOR("Erez Zadok, Filesystems and Storage Lab, Stony Brook University (http://www.fsl.cs.sunysb.edu/)"); |
| + |
| +/* Original sdcardfs authors */ |
| +MODULE_AUTHOR("Woojoong Lee, Daeho Jeong, Kitae Lee, Yeongjin Gil System Memory Lab., Samsung Electronics"); |
| + |
| +/* Current maintainer */ |
| +MODULE_AUTHOR("Daniel Rosenberg, Google"); |
| +MODULE_DESCRIPTION("Sdcardfs " SDCARDFS_VERSION); |
| +MODULE_LICENSE("GPL"); |
| + |
| +module_init(init_sdcardfs_fs); |
| +module_exit(exit_sdcardfs_fs); |
| diff --git a/fs/sdcardfs/mmap.c b/fs/sdcardfs/mmap.c |
| new file mode 100644 |
| index 000000000000..0ec0950c2447 |
| --- /dev/null |
| +++ b/fs/sdcardfs/mmap.c |
| @@ -0,0 +1,87 @@ |
| +/* |
| + * fs/sdcardfs/mmap.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| + |
| +static vm_fault_t sdcardfs_fault(struct vm_fault *vmf) |
| +{ |
| + vm_fault_t err; |
| + struct file *file; |
| + const struct vm_operations_struct *lower_vm_ops; |
| + |
| + file = (struct file *)vmf->vma->vm_private_data; |
| + lower_vm_ops = SDCARDFS_F(file)->lower_vm_ops; |
| + BUG_ON(!lower_vm_ops); |
| + |
| + err = lower_vm_ops->fault(vmf); |
| + return err; |
| +} |
| + |
| +static void sdcardfs_vm_open(struct vm_area_struct *vma) |
| +{ |
| + struct file *file = (struct file *)vma->vm_private_data; |
| + |
| + get_file(file); |
| +} |
| + |
| +static void sdcardfs_vm_close(struct vm_area_struct *vma) |
| +{ |
| + struct file *file = (struct file *)vma->vm_private_data; |
| + |
| + fput(file); |
| +} |
| + |
| +static vm_fault_t sdcardfs_page_mkwrite(struct vm_fault *vmf) |
| +{ |
| + vm_fault_t err = 0; |
| + struct file *file; |
| + const struct vm_operations_struct *lower_vm_ops; |
| + |
| + file = (struct file *)vmf->vma->vm_private_data; |
| + lower_vm_ops = SDCARDFS_F(file)->lower_vm_ops; |
| + BUG_ON(!lower_vm_ops); |
| + if (!lower_vm_ops->page_mkwrite) |
| + goto out; |
| + |
| + err = lower_vm_ops->page_mkwrite(vmf); |
| +out: |
| + return err; |
| +} |
| + |
| +static ssize_t sdcardfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter) |
| +{ |
| + /* |
| + * This function should never be called directly. We need it |
| + * to exist, to get past a check in open_check_o_direct(), |
| + * which is called from do_last(). |
| + */ |
| + return -EINVAL; |
| +} |
| + |
| +const struct address_space_operations sdcardfs_aops = { |
| + .direct_IO = sdcardfs_direct_IO, |
| +}; |
| + |
| +const struct vm_operations_struct sdcardfs_vm_ops = { |
| + .fault = sdcardfs_fault, |
| + .page_mkwrite = sdcardfs_page_mkwrite, |
| + .open = sdcardfs_vm_open, |
| + .close = sdcardfs_vm_close, |
| +}; |
| diff --git a/fs/sdcardfs/multiuser.h b/fs/sdcardfs/multiuser.h |
| new file mode 100644 |
| index 000000000000..85341e753f8c |
| --- /dev/null |
| +++ b/fs/sdcardfs/multiuser.h |
| @@ -0,0 +1,53 @@ |
| +/* |
| + * fs/sdcardfs/multiuser.h |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#define AID_USER_OFFSET 100000 /* offset for uid ranges for each user */ |
| +#define AID_APP_START 10000 /* first app user */ |
| +#define AID_APP_END 19999 /* last app user */ |
| +#define AID_CACHE_GID_START 20000 /* start of gids for apps to mark cached data */ |
| +#define AID_EXT_GID_START 30000 /* start of gids for apps to mark external data */ |
| +#define AID_EXT_CACHE_GID_START 40000 /* start of gids for apps to mark external cached data */ |
| +#define AID_EXT_CACHE_GID_END 49999 /* end of gids for apps to mark external cached data */ |
| +#define AID_SHARED_GID_START 50000 /* start of gids for apps in each user to share */ |
| + |
| +typedef uid_t userid_t; |
| +typedef uid_t appid_t; |
| + |
| +static inline uid_t multiuser_get_uid(userid_t user_id, appid_t app_id) |
| +{ |
| + return (user_id * AID_USER_OFFSET) + (app_id % AID_USER_OFFSET); |
| +} |
| + |
| +static inline bool uid_is_app(uid_t uid) |
| +{ |
| + appid_t appid = uid % AID_USER_OFFSET; |
| + |
| + return appid >= AID_APP_START && appid <= AID_APP_END; |
| +} |
| + |
| +static inline gid_t multiuser_get_ext_cache_gid(uid_t uid) |
| +{ |
| + return uid - AID_APP_START + AID_EXT_CACHE_GID_START; |
| +} |
| + |
| +static inline gid_t multiuser_get_ext_gid(uid_t uid) |
| +{ |
| + return uid - AID_APP_START + AID_EXT_GID_START; |
| +} |
| diff --git a/fs/sdcardfs/packagelist.c b/fs/sdcardfs/packagelist.c |
| new file mode 100644 |
| index 000000000000..4b9a5635f1e0 |
| --- /dev/null |
| +++ b/fs/sdcardfs/packagelist.c |
| @@ -0,0 +1,882 @@ |
| +/* |
| + * fs/sdcardfs/packagelist.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include "sdcardfs.h" |
| +#include <linux/hashtable.h> |
| +#include <linux/ctype.h> |
| +#include <linux/delay.h> |
| +#include <linux/radix-tree.h> |
| +#include <linux/dcache.h> |
| + |
| +#include <linux/init.h> |
| +#include <linux/module.h> |
| +#include <linux/slab.h> |
| + |
| +#include <linux/configfs.h> |
| + |
| +struct hashtable_entry { |
| + struct hlist_node hlist; |
| + struct hlist_node dlist; /* for deletion cleanup */ |
| + struct qstr key; |
| + atomic_t value; |
| +}; |
| + |
| +static DEFINE_HASHTABLE(package_to_appid, 8); |
| +static DEFINE_HASHTABLE(package_to_userid, 8); |
| +static DEFINE_HASHTABLE(ext_to_groupid, 8); |
| + |
| + |
| +static struct kmem_cache *hashtable_entry_cachep; |
| + |
| +static unsigned int full_name_case_hash(const void *salt, const unsigned char *name, unsigned int len) |
| +{ |
| + unsigned long hash = init_name_hash(salt); |
| + |
| + while (len--) |
| + hash = partial_name_hash(tolower(*name++), hash); |
| + return end_name_hash(hash); |
| +} |
| + |
| +static inline void qstr_init(struct qstr *q, const char *name) |
| +{ |
| + q->name = name; |
| + q->len = strlen(q->name); |
| + q->hash = full_name_case_hash(0, q->name, q->len); |
| +} |
| + |
| +static inline int qstr_copy(const struct qstr *src, struct qstr *dest) |
| +{ |
| + dest->name = kstrdup(src->name, GFP_KERNEL); |
| + dest->hash_len = src->hash_len; |
| + return !!dest->name; |
| +} |
| + |
| + |
| +static appid_t __get_appid(const struct qstr *key) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + appid_t ret_id; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(package_to_appid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + ret_id = atomic_read(&hash_cur->value); |
| + rcu_read_unlock(); |
| + return ret_id; |
| + } |
| + } |
| + rcu_read_unlock(); |
| + return 0; |
| +} |
| + |
| +appid_t get_appid(const char *key) |
| +{ |
| + struct qstr q; |
| + |
| + qstr_init(&q, key); |
| + return __get_appid(&q); |
| +} |
| + |
| +static appid_t __get_ext_gid(const struct qstr *key) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + appid_t ret_id; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(ext_to_groupid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + ret_id = atomic_read(&hash_cur->value); |
| + rcu_read_unlock(); |
| + return ret_id; |
| + } |
| + } |
| + rcu_read_unlock(); |
| + return 0; |
| +} |
| + |
| +appid_t get_ext_gid(const char *key) |
| +{ |
| + struct qstr q; |
| + |
| + qstr_init(&q, key); |
| + return __get_ext_gid(&q); |
| +} |
| + |
| +static appid_t __is_excluded(const struct qstr *app_name, userid_t user) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = app_name->hash; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (atomic_read(&hash_cur->value) == user && |
| + qstr_case_eq(app_name, &hash_cur->key)) { |
| + rcu_read_unlock(); |
| + return 1; |
| + } |
| + } |
| + rcu_read_unlock(); |
| + return 0; |
| +} |
| + |
| +appid_t is_excluded(const char *key, userid_t user) |
| +{ |
| + struct qstr q; |
| + qstr_init(&q, key); |
| + return __is_excluded(&q, user); |
| +} |
| + |
| +/* Kernel has already enforced everything we returned through |
| + * derive_permissions_locked(), so this is used to lock down access |
| + * even further, such as enforcing that apps hold sdcard_rw. |
| + */ |
| +int check_caller_access_to_name(struct inode *parent_node, const struct qstr *name) |
| +{ |
| + struct qstr q_autorun = QSTR_LITERAL("autorun.inf"); |
| + struct qstr q__android_secure = QSTR_LITERAL(".android_secure"); |
| + struct qstr q_android_secure = QSTR_LITERAL("android_secure"); |
| + |
| + /* Always block security-sensitive files at root */ |
| + if (parent_node && SDCARDFS_I(parent_node)->data->perm == PERM_ROOT) { |
| + if (qstr_case_eq(name, &q_autorun) |
| + || qstr_case_eq(name, &q__android_secure) |
| + || qstr_case_eq(name, &q_android_secure)) { |
| + return 0; |
| + } |
| + } |
| + |
| + /* Root always has access; access for any other UIDs should always |
| + * be controlled through packages.list. |
| + */ |
| + if (from_kuid(&init_user_ns, current_fsuid()) == 0) |
| + return 1; |
| + |
| + /* No extra permissions to enforce */ |
| + return 1; |
| +} |
| + |
| +static struct hashtable_entry *alloc_hashtable_entry(const struct qstr *key, |
| + appid_t value) |
| +{ |
| + struct hashtable_entry *ret = kmem_cache_alloc(hashtable_entry_cachep, |
| + GFP_KERNEL); |
| + if (!ret) |
| + return NULL; |
| + INIT_HLIST_NODE(&ret->dlist); |
| + INIT_HLIST_NODE(&ret->hlist); |
| + |
| + if (!qstr_copy(key, &ret->key)) { |
| + kmem_cache_free(hashtable_entry_cachep, ret); |
| + return NULL; |
| + } |
| + |
| + atomic_set(&ret->value, value); |
| + return ret; |
| +} |
| + |
| +static int insert_packagelist_appid_entry_locked(const struct qstr *key, appid_t value) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hashtable_entry *new_entry; |
| + unsigned int hash = key->hash; |
| + |
| + hash_for_each_possible_rcu(package_to_appid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + atomic_set(&hash_cur->value, value); |
| + return 0; |
| + } |
| + } |
| + new_entry = alloc_hashtable_entry(key, value); |
| + if (!new_entry) |
| + return -ENOMEM; |
| + hash_add_rcu(package_to_appid, &new_entry->hlist, hash); |
| + return 0; |
| +} |
| + |
| +static int insert_ext_gid_entry_locked(const struct qstr *key, appid_t value) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hashtable_entry *new_entry; |
| + unsigned int hash = key->hash; |
| + |
| + /* An extension can only belong to one gid */ |
| + hash_for_each_possible_rcu(ext_to_groupid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) |
| + return -EINVAL; |
| + } |
| + new_entry = alloc_hashtable_entry(key, value); |
| + if (!new_entry) |
| + return -ENOMEM; |
| + hash_add_rcu(ext_to_groupid, &new_entry->hlist, hash); |
| + return 0; |
| +} |
| + |
| +static int insert_userid_exclude_entry_locked(const struct qstr *key, userid_t value) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hashtable_entry *new_entry; |
| + unsigned int hash = key->hash; |
| + |
| + /* Only insert if not already present */ |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (atomic_read(&hash_cur->value) == value && |
| + qstr_case_eq(key, &hash_cur->key)) |
| + return 0; |
| + } |
| + new_entry = alloc_hashtable_entry(key, value); |
| + if (!new_entry) |
| + return -ENOMEM; |
| + hash_add_rcu(package_to_userid, &new_entry->hlist, hash); |
| + return 0; |
| +} |
| + |
| +static void fixup_all_perms_name(const struct qstr *key) |
| +{ |
| + struct sdcardfs_sb_info *sbinfo; |
| + struct limit_search limit = { |
| + .flags = BY_NAME, |
| + .name = QSTR_INIT(key->name, key->len), |
| + }; |
| + list_for_each_entry(sbinfo, &sdcardfs_super_list, list) { |
| + if (sbinfo_has_sdcard_magic(sbinfo)) |
| + fixup_perms_recursive(sbinfo->sb->s_root, &limit); |
| + } |
| +} |
| + |
| +static void fixup_all_perms_name_userid(const struct qstr *key, userid_t userid) |
| +{ |
| + struct sdcardfs_sb_info *sbinfo; |
| + struct limit_search limit = { |
| + .flags = BY_NAME | BY_USERID, |
| + .name = QSTR_INIT(key->name, key->len), |
| + .userid = userid, |
| + }; |
| + list_for_each_entry(sbinfo, &sdcardfs_super_list, list) { |
| + if (sbinfo_has_sdcard_magic(sbinfo)) |
| + fixup_perms_recursive(sbinfo->sb->s_root, &limit); |
| + } |
| +} |
| + |
| +static void fixup_all_perms_userid(userid_t userid) |
| +{ |
| + struct sdcardfs_sb_info *sbinfo; |
| + struct limit_search limit = { |
| + .flags = BY_USERID, |
| + .userid = userid, |
| + }; |
| + list_for_each_entry(sbinfo, &sdcardfs_super_list, list) { |
| + if (sbinfo_has_sdcard_magic(sbinfo)) |
| + fixup_perms_recursive(sbinfo->sb->s_root, &limit); |
| + } |
| +} |
| + |
| +static int insert_packagelist_entry(const struct qstr *key, appid_t value) |
| +{ |
| + int err; |
| + |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + err = insert_packagelist_appid_entry_locked(key, value); |
| + if (!err) |
| + fixup_all_perms_name(key); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| + |
| + return err; |
| +} |
| + |
| +static int insert_ext_gid_entry(const struct qstr *key, appid_t value) |
| +{ |
| + int err; |
| + |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + err = insert_ext_gid_entry_locked(key, value); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| + |
| + return err; |
| +} |
| + |
| +static int insert_userid_exclude_entry(const struct qstr *key, userid_t value) |
| +{ |
| + int err; |
| + |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + err = insert_userid_exclude_entry_locked(key, value); |
| + if (!err) |
| + fixup_all_perms_name_userid(key, value); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| + |
| + return err; |
| +} |
| + |
| +static void free_hashtable_entry(struct hashtable_entry *entry) |
| +{ |
| + kfree(entry->key.name); |
| + kmem_cache_free(hashtable_entry_cachep, entry); |
| +} |
| + |
| +static void remove_packagelist_entry_locked(const struct qstr *key) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + struct hlist_node *h_t; |
| + HLIST_HEAD(free_list); |
| + |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + } |
| + hash_for_each_possible_rcu(package_to_appid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key)) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + break; |
| + } |
| + } |
| + synchronize_rcu(); |
| + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) |
| + free_hashtable_entry(hash_cur); |
| +} |
| + |
| +static void remove_packagelist_entry(const struct qstr *key) |
| +{ |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + remove_packagelist_entry_locked(key); |
| + fixup_all_perms_name(key); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| +} |
| + |
| +static void remove_ext_gid_entry_locked(const struct qstr *key, gid_t group) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + |
| + hash_for_each_possible_rcu(ext_to_groupid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key) && atomic_read(&hash_cur->value) == group) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + synchronize_rcu(); |
| + free_hashtable_entry(hash_cur); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +static void remove_ext_gid_entry(const struct qstr *key, gid_t group) |
| +{ |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + remove_ext_gid_entry_locked(key, group); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| +} |
| + |
| +static void remove_userid_all_entry_locked(userid_t userid) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hlist_node *h_t; |
| + HLIST_HEAD(free_list); |
| + int i; |
| + |
| + hash_for_each_rcu(package_to_userid, i, hash_cur, hlist) { |
| + if (atomic_read(&hash_cur->value) == userid) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + } |
| + synchronize_rcu(); |
| + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) { |
| + free_hashtable_entry(hash_cur); |
| + } |
| +} |
| + |
| +static void remove_userid_all_entry(userid_t userid) |
| +{ |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + remove_userid_all_entry_locked(userid); |
| + fixup_all_perms_userid(userid); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| +} |
| + |
| +static void remove_userid_exclude_entry_locked(const struct qstr *key, userid_t userid) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = key->hash; |
| + |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(key, &hash_cur->key) && |
| + atomic_read(&hash_cur->value) == userid) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + synchronize_rcu(); |
| + free_hashtable_entry(hash_cur); |
| + break; |
| + } |
| + } |
| +} |
| + |
| +static void remove_userid_exclude_entry(const struct qstr *key, userid_t userid) |
| +{ |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + remove_userid_exclude_entry_locked(key, userid); |
| + fixup_all_perms_name_userid(key, userid); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| +} |
| + |
| +static void packagelist_destroy(void) |
| +{ |
| + struct hashtable_entry *hash_cur; |
| + struct hlist_node *h_t; |
| + HLIST_HEAD(free_list); |
| + int i; |
| + |
| + mutex_lock(&sdcardfs_super_list_lock); |
| + hash_for_each_rcu(package_to_appid, i, hash_cur, hlist) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + hash_for_each_rcu(package_to_userid, i, hash_cur, hlist) { |
| + hash_del_rcu(&hash_cur->hlist); |
| + hlist_add_head(&hash_cur->dlist, &free_list); |
| + } |
| + synchronize_rcu(); |
| + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) |
| + free_hashtable_entry(hash_cur); |
| + mutex_unlock(&sdcardfs_super_list_lock); |
| + pr_info("sdcardfs: destroyed packagelist pkgld\n"); |
| +} |
| + |
| +#define SDCARDFS_CONFIGFS_ATTR(_pfx, _name) \ |
| +static struct configfs_attribute _pfx##attr_##_name = { \ |
| + .ca_name = __stringify(_name), \ |
| + .ca_mode = S_IRUGO | S_IWUGO, \ |
| + .ca_owner = THIS_MODULE, \ |
| + .show = _pfx##_name##_show, \ |
| + .store = _pfx##_name##_store, \ |
| +} |
| + |
| +#define SDCARDFS_CONFIGFS_ATTR_RO(_pfx, _name) \ |
| +static struct configfs_attribute _pfx##attr_##_name = { \ |
| + .ca_name = __stringify(_name), \ |
| + .ca_mode = S_IRUGO, \ |
| + .ca_owner = THIS_MODULE, \ |
| + .show = _pfx##_name##_show, \ |
| +} |
| + |
| +#define SDCARDFS_CONFIGFS_ATTR_WO(_pfx, _name) \ |
| +static struct configfs_attribute _pfx##attr_##_name = { \ |
| + .ca_name = __stringify(_name), \ |
| + .ca_mode = S_IWUGO, \ |
| + .ca_owner = THIS_MODULE, \ |
| + .store = _pfx##_name##_store, \ |
| +} |
| + |
| +struct package_details { |
| + struct config_item item; |
| + struct qstr name; |
| +}; |
| + |
| +static inline struct package_details *to_package_details(struct config_item *item) |
| +{ |
| + return item ? container_of(item, struct package_details, item) : NULL; |
| +} |
| + |
| +static ssize_t package_details_appid_show(struct config_item *item, char *page) |
| +{ |
| + return scnprintf(page, PAGE_SIZE, "%u\n", __get_appid(&to_package_details(item)->name)); |
| +} |
| + |
| +static ssize_t package_details_appid_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + |
| + ret = insert_packagelist_entry(&to_package_details(item)->name, tmp); |
| + |
| + if (ret) |
| + return ret; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t package_details_excluded_userids_show(struct config_item *item, |
| + char *page) |
| +{ |
| + struct package_details *package_details = to_package_details(item); |
| + struct hashtable_entry *hash_cur; |
| + unsigned int hash = package_details->name.hash; |
| + int count = 0; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur, hlist, hash) { |
| + if (qstr_case_eq(&package_details->name, &hash_cur->key)) |
| + count += scnprintf(page + count, PAGE_SIZE - count, |
| + "%d ", atomic_read(&hash_cur->value)); |
| + } |
| + rcu_read_unlock(); |
| + if (count) |
| + count--; |
| + count += scnprintf(page + count, PAGE_SIZE - count, "\n"); |
| + return count; |
| +} |
| + |
| +static ssize_t package_details_excluded_userids_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + |
| + ret = insert_userid_exclude_entry(&to_package_details(item)->name, tmp); |
| + |
| + if (ret) |
| + return ret; |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t package_details_clear_userid_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + remove_userid_exclude_entry(&to_package_details(item)->name, tmp); |
| + return count; |
| +} |
| + |
| +static void package_details_release(struct config_item *item) |
| +{ |
| + struct package_details *package_details = to_package_details(item); |
| + |
| + pr_info("sdcardfs: removing %s\n", package_details->name.name); |
| + remove_packagelist_entry(&package_details->name); |
| + kfree(package_details->name.name); |
| + kfree(package_details); |
| +} |
| + |
| +SDCARDFS_CONFIGFS_ATTR(package_details_, appid); |
| +SDCARDFS_CONFIGFS_ATTR(package_details_, excluded_userids); |
| +SDCARDFS_CONFIGFS_ATTR_WO(package_details_, clear_userid); |
| + |
| +static struct configfs_attribute *package_details_attrs[] = { |
| + &package_details_attr_appid, |
| + &package_details_attr_excluded_userids, |
| + &package_details_attr_clear_userid, |
| + NULL, |
| +}; |
| + |
| +static struct configfs_item_operations package_details_item_ops = { |
| + .release = package_details_release, |
| +}; |
| + |
| +static struct config_item_type package_appid_type = { |
| + .ct_item_ops = &package_details_item_ops, |
| + .ct_attrs = package_details_attrs, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +struct extensions_value { |
| + struct config_group group; |
| + unsigned int num; |
| +}; |
| + |
| +struct extension_details { |
| + struct config_item item; |
| + struct qstr name; |
| + unsigned int num; |
| +}; |
| + |
| +static inline struct extensions_value *to_extensions_value(struct config_item *item) |
| +{ |
| + return item ? container_of(to_config_group(item), struct extensions_value, group) : NULL; |
| +} |
| + |
| +static inline struct extension_details *to_extension_details(struct config_item *item) |
| +{ |
| + return item ? container_of(item, struct extension_details, item) : NULL; |
| +} |
| + |
| +static void extension_details_release(struct config_item *item) |
| +{ |
| + struct extension_details *extension_details = to_extension_details(item); |
| + |
| + pr_info("sdcardfs: No longer mapping %s files to gid %d\n", |
| + extension_details->name.name, extension_details->num); |
| + remove_ext_gid_entry(&extension_details->name, extension_details->num); |
| + kfree(extension_details->name.name); |
| + kfree(extension_details); |
| +} |
| + |
| +static struct configfs_item_operations extension_details_item_ops = { |
| + .release = extension_details_release, |
| +}; |
| + |
| +static struct config_item_type extension_details_type = { |
| + .ct_item_ops = &extension_details_item_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct config_item *extension_details_make_item(struct config_group *group, const char *name) |
| +{ |
| + struct extensions_value *extensions_value = to_extensions_value(&group->cg_item); |
| + struct extension_details *extension_details = kzalloc(sizeof(struct extension_details), GFP_KERNEL); |
| + const char *tmp; |
| + int ret; |
| + |
| + if (!extension_details) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + tmp = kstrdup(name, GFP_KERNEL); |
| + if (!tmp) { |
| + kfree(extension_details); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + qstr_init(&extension_details->name, tmp); |
| + extension_details->num = extensions_value->num; |
| + ret = insert_ext_gid_entry(&extension_details->name, extensions_value->num); |
| + |
| + if (ret) { |
| + kfree(extension_details->name.name); |
| + kfree(extension_details); |
| + return ERR_PTR(ret); |
| + } |
| + config_item_init_type_name(&extension_details->item, name, &extension_details_type); |
| + |
| + return &extension_details->item; |
| +} |
| + |
| +static struct configfs_group_operations extensions_value_group_ops = { |
| + .make_item = extension_details_make_item, |
| +}; |
| + |
| +static struct config_item_type extensions_name_type = { |
| + .ct_group_ops = &extensions_value_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +static struct config_group *extensions_make_group(struct config_group *group, const char *name) |
| +{ |
| + struct extensions_value *extensions_value; |
| + unsigned int tmp; |
| + int ret; |
| + |
| + extensions_value = kzalloc(sizeof(struct extensions_value), GFP_KERNEL); |
| + if (!extensions_value) |
| + return ERR_PTR(-ENOMEM); |
| + ret = kstrtouint(name, 10, &tmp); |
| + if (ret) { |
| + kfree(extensions_value); |
| + return ERR_PTR(ret); |
| + } |
| + |
| + extensions_value->num = tmp; |
| + config_group_init_type_name(&extensions_value->group, name, |
| + &extensions_name_type); |
| + return &extensions_value->group; |
| +} |
| + |
| +static void extensions_drop_group(struct config_group *group, struct config_item *item) |
| +{ |
| + struct extensions_value *value = to_extensions_value(item); |
| + |
| + pr_info("sdcardfs: No longer mapping any files to gid %d\n", value->num); |
| + kfree(value); |
| +} |
| + |
| +static struct configfs_group_operations extensions_group_ops = { |
| + .make_group = extensions_make_group, |
| + .drop_item = extensions_drop_group, |
| +}; |
| + |
| +static struct config_item_type extensions_type = { |
| + .ct_group_ops = &extensions_group_ops, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +struct config_group extension_group = { |
| + .cg_item = { |
| + .ci_namebuf = "extensions", |
| + .ci_type = &extensions_type, |
| + }, |
| +}; |
| + |
| +static struct config_item *packages_make_item(struct config_group *group, const char *name) |
| +{ |
| + struct package_details *package_details; |
| + const char *tmp; |
| + |
| + package_details = kzalloc(sizeof(struct package_details), GFP_KERNEL); |
| + if (!package_details) |
| + return ERR_PTR(-ENOMEM); |
| + tmp = kstrdup(name, GFP_KERNEL); |
| + if (!tmp) { |
| + kfree(package_details); |
| + return ERR_PTR(-ENOMEM); |
| + } |
| + qstr_init(&package_details->name, tmp); |
| + config_item_init_type_name(&package_details->item, name, |
| + &package_appid_type); |
| + |
| + return &package_details->item; |
| +} |
| + |
| +static ssize_t packages_list_show(struct config_item *item, char *page) |
| +{ |
| + struct hashtable_entry *hash_cur_app; |
| + struct hashtable_entry *hash_cur_user; |
| + int i; |
| + int count = 0, written = 0; |
| + const char errormsg[] = "<truncated>\n"; |
| + unsigned int hash; |
| + |
| + rcu_read_lock(); |
| + hash_for_each_rcu(package_to_appid, i, hash_cur_app, hlist) { |
| + written = scnprintf(page + count, PAGE_SIZE - sizeof(errormsg) - count, "%s %d\n", |
| + hash_cur_app->key.name, atomic_read(&hash_cur_app->value)); |
| + hash = hash_cur_app->key.hash; |
| + hash_for_each_possible_rcu(package_to_userid, hash_cur_user, hlist, hash) { |
| + if (qstr_case_eq(&hash_cur_app->key, &hash_cur_user->key)) { |
| + written += scnprintf(page + count + written - 1, |
| + PAGE_SIZE - sizeof(errormsg) - count - written + 1, |
| + " %d\n", atomic_read(&hash_cur_user->value)) - 1; |
| + } |
| + } |
| + if (count + written == PAGE_SIZE - sizeof(errormsg) - 1) { |
| + count += scnprintf(page + count, PAGE_SIZE - count, errormsg); |
| + break; |
| + } |
| + count += written; |
| + } |
| + rcu_read_unlock(); |
| + |
| + return count; |
| +} |
| + |
| +static ssize_t packages_remove_userid_store(struct config_item *item, |
| + const char *page, size_t count) |
| +{ |
| + unsigned int tmp; |
| + int ret; |
| + |
| + ret = kstrtouint(page, 10, &tmp); |
| + if (ret) |
| + return ret; |
| + remove_userid_all_entry(tmp); |
| + return count; |
| +} |
| + |
| +static struct configfs_attribute packages_attr_packages_gid_list = { |
| + .ca_name = "packages_gid.list", |
| + .ca_mode = S_IRUGO, |
| + .ca_owner = THIS_MODULE, |
| + .show = packages_list_show, |
| +}; |
| + |
| +SDCARDFS_CONFIGFS_ATTR_WO(packages_, remove_userid); |
| + |
| +static struct configfs_attribute *packages_attrs[] = { |
| + &packages_attr_packages_gid_list, |
| + &packages_attr_remove_userid, |
| + NULL, |
| +}; |
| + |
| +/* |
| + * Note that, since no extra work is required on ->drop_item(), |
| + * no ->drop_item() is provided. |
| + */ |
| +static struct configfs_group_operations packages_group_ops = { |
| + .make_item = packages_make_item, |
| +}; |
| + |
| +static struct config_item_type packages_type = { |
| + .ct_group_ops = &packages_group_ops, |
| + .ct_attrs = packages_attrs, |
| + .ct_owner = THIS_MODULE, |
| +}; |
| + |
| +struct config_group *sd_default_groups[] = { |
| + &extension_group, |
| + NULL, |
| +}; |
| + |
| +static struct configfs_subsystem sdcardfs_packages = { |
| + .su_group = { |
| + .cg_item = { |
| + .ci_namebuf = "sdcardfs", |
| + .ci_type = &packages_type, |
| + }, |
| + }, |
| +}; |
| + |
| +static int configfs_sdcardfs_init(void) |
| +{ |
| + int ret, i; |
| + struct configfs_subsystem *subsys = &sdcardfs_packages; |
| + |
| + config_group_init(&subsys->su_group); |
| + for (i = 0; sd_default_groups[i]; i++) { |
| + config_group_init(sd_default_groups[i]); |
| + configfs_add_default_group(sd_default_groups[i], &subsys->su_group); |
| + } |
| + mutex_init(&subsys->su_mutex); |
| + ret = configfs_register_subsystem(subsys); |
| + if (ret) { |
| + pr_err("Error %d while registering subsystem %s\n", |
| + ret, |
| + subsys->su_group.cg_item.ci_namebuf); |
| + } |
| + return ret; |
| +} |
| + |
| +static void configfs_sdcardfs_exit(void) |
| +{ |
| + configfs_unregister_subsystem(&sdcardfs_packages); |
| +} |
| + |
| +int packagelist_init(void) |
| +{ |
| + hashtable_entry_cachep = |
| + kmem_cache_create("packagelist_hashtable_entry", |
| + sizeof(struct hashtable_entry), 0, 0, NULL); |
| + if (!hashtable_entry_cachep) { |
| + pr_err("sdcardfs: failed creating pkgl_hashtable entry slab cache\n"); |
| + return -ENOMEM; |
| + } |
| + |
| + configfs_sdcardfs_init(); |
| + return 0; |
| +} |
| + |
| +void packagelist_exit(void) |
| +{ |
| + configfs_sdcardfs_exit(); |
| + packagelist_destroy(); |
| + kmem_cache_destroy(hashtable_entry_cachep); |
| +} |
| diff --git a/fs/sdcardfs/sdcardfs.h b/fs/sdcardfs/sdcardfs.h |
| new file mode 100644 |
| index 000000000000..f813d23cddcd |
| --- /dev/null |
| +++ b/fs/sdcardfs/sdcardfs.h |
| @@ -0,0 +1,662 @@ |
| +/* |
| + * fs/sdcardfs/sdcardfs.h |
| + * |
| + * The sdcardfs v2.0 |
| + * This file system replaces the sdcard daemon on Android |
| + * On version 2.0, some of the daemon functions have been ported |
| + * to support the multi-user concepts of Android 4.4 |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#ifndef _SDCARDFS_H_ |
| +#define _SDCARDFS_H_ |
| + |
| +#include <linux/dcache.h> |
| +#include <linux/file.h> |
| +#include <linux/fs.h> |
| +#include <linux/aio.h> |
| +#include <linux/kref.h> |
| +#include <linux/mm.h> |
| +#include <linux/mount.h> |
| +#include <linux/namei.h> |
| +#include <linux/seq_file.h> |
| +#include <linux/statfs.h> |
| +#include <linux/fs_stack.h> |
| +#include <linux/magic.h> |
| +#include <linux/uaccess.h> |
| +#include <linux/slab.h> |
| +#include <linux/sched.h> |
| +#include <linux/types.h> |
| +#include <linux/security.h> |
| +#include <linux/string.h> |
| +#include <linux/list.h> |
| +#include <linux/iversion.h> |
| +#include <uapi/linux/mount.h> |
| +#include "multiuser.h" |
| + |
| +/* the file system name */ |
| +#define SDCARDFS_NAME "sdcardfs" |
| + |
| +/* sdcardfs root inode number */ |
| +#define SDCARDFS_ROOT_INO 1 |
| + |
| +/* useful for tracking code reachability */ |
| +#define UDBG pr_default("DBG:%s:%s:%d\n", __FILE__, __func__, __LINE__) |
| + |
| +#define SDCARDFS_DIRENT_SIZE 256 |
| + |
| +/* temporary static uid settings for development */ |
| +#define AID_ROOT 0 /* uid for accessing /mnt/sdcard & extSdcard */ |
| +#define AID_MEDIA_RW 1023 /* internal media storage write access */ |
| + |
| +#define AID_SDCARD_RW 1015 /* external storage write access */ |
| +#define AID_SDCARD_R 1028 /* external storage read access */ |
| +#define AID_SDCARD_PICS 1033 /* external storage photos access */ |
| +#define AID_SDCARD_AV 1034 /* external storage audio/video access */ |
| +#define AID_SDCARD_ALL 1035 /* access all users external storage */ |
| +#define AID_MEDIA_OBB 1059 /* obb files */ |
| + |
| +#define AID_SDCARD_IMAGE 1057 |
| + |
| +#define AID_PACKAGE_INFO 1027 |
| + |
| + |
| +/* |
| + * Permissions are handled by our permission function. |
| + * We don't want anyone who happens to look at our inode value to prematurely |
| + * block access, so store more permissive values. These are probably never |
| + * used. |
| + */ |
| +#define fixup_tmp_permissions(x) \ |
| + do { \ |
| + (x)->i_uid = make_kuid(&init_user_ns, \ |
| + SDCARDFS_I(x)->data->d_uid); \ |
| + (x)->i_gid = make_kgid(&init_user_ns, AID_SDCARD_RW); \ |
| + (x)->i_mode = ((x)->i_mode & S_IFMT) | 0775;\ |
| + } while (0) |
| + |
| +/* Android 5.0 support */ |
| + |
| +/* Permission mode for a specific node. Controls how file permissions |
| + * are derived for children nodes. |
| + */ |
| +typedef enum { |
| + /* Nothing special; this node should just inherit from its parent. */ |
| + PERM_INHERIT, |
| + /* This node is one level above a normal root; used for legacy layouts |
| + * which use the first level to represent user_id. |
| + */ |
| + PERM_PRE_ROOT, |
| + /* This node is "/" */ |
| + PERM_ROOT, |
| + /* This node is "/Android" */ |
| + PERM_ANDROID, |
| + /* This node is "/Android/data" */ |
| + PERM_ANDROID_DATA, |
| + /* This node is "/Android/obb" */ |
| + PERM_ANDROID_OBB, |
| + /* This node is "/Android/media" */ |
| + PERM_ANDROID_MEDIA, |
| + /* This node is "/Android/[data|media|obb]/[package]" */ |
| + PERM_ANDROID_PACKAGE, |
| + /* This node is "/Android/[data|media|obb]/[package]/cache" */ |
| + PERM_ANDROID_PACKAGE_CACHE, |
| +} perm_t; |
| + |
| +struct sdcardfs_sb_info; |
| +struct sdcardfs_mount_options; |
| +struct sdcardfs_inode_info; |
| +struct sdcardfs_inode_data; |
| + |
| +/* Do not directly use this function. Use OVERRIDE_CRED() instead. */ |
| +const struct cred *override_fsids(struct sdcardfs_sb_info *sbi, |
| + struct sdcardfs_inode_data *data); |
| +/* Do not directly use this function, use REVERT_CRED() instead. */ |
| +void revert_fsids(const struct cred *old_cred); |
| + |
| +/* operations vectors defined in specific files */ |
| +extern const struct file_operations sdcardfs_main_fops; |
| +extern const struct file_operations sdcardfs_dir_fops; |
| +extern const struct inode_operations sdcardfs_main_iops; |
| +extern const struct inode_operations sdcardfs_dir_iops; |
| +extern const struct inode_operations sdcardfs_symlink_iops; |
| +extern const struct super_operations sdcardfs_sops; |
| +extern const struct dentry_operations sdcardfs_ci_dops; |
| +extern const struct address_space_operations sdcardfs_aops, sdcardfs_dummy_aops; |
| +extern const struct vm_operations_struct sdcardfs_vm_ops; |
| + |
| +extern int sdcardfs_init_inode_cache(void); |
| +extern void sdcardfs_destroy_inode_cache(void); |
| +extern int sdcardfs_init_dentry_cache(void); |
| +extern void sdcardfs_destroy_dentry_cache(void); |
| +extern int new_dentry_private_data(struct dentry *dentry); |
| +extern void free_dentry_private_data(struct dentry *dentry); |
| +extern struct dentry *sdcardfs_lookup(struct inode *dir, struct dentry *dentry, |
| + unsigned int flags); |
| +extern struct inode *sdcardfs_iget(struct super_block *sb, |
| + struct inode *lower_inode, userid_t id); |
| +extern int sdcardfs_interpose(struct dentry *dentry, struct super_block *sb, |
| + struct path *lower_path, userid_t id); |
| + |
| +/* file private data */ |
| +struct sdcardfs_file_info { |
| + struct file *lower_file; |
| + const struct vm_operations_struct *lower_vm_ops; |
| +}; |
| + |
| +struct sdcardfs_inode_data { |
| + struct kref refcount; |
| + bool abandoned; |
| + |
| + perm_t perm; |
| + userid_t userid; |
| + uid_t d_uid; |
| + bool under_android; |
| + bool under_cache; |
| + bool under_obb; |
| +}; |
| + |
| +/* sdcardfs inode data in memory */ |
| +struct sdcardfs_inode_info { |
| + struct inode *lower_inode; |
| + /* state derived based on current position in hierarchy */ |
| + struct sdcardfs_inode_data *data; |
| + |
| + /* top folder for ownership */ |
| + spinlock_t top_lock; |
| + struct sdcardfs_inode_data *top_data; |
| + |
| + struct inode vfs_inode; |
| +}; |
| + |
| + |
| +/* sdcardfs dentry data in memory */ |
| +struct sdcardfs_dentry_info { |
| + spinlock_t lock; /* protects lower_path */ |
| + struct path lower_path; |
| + struct path orig_path; |
| +}; |
| + |
| +struct sdcardfs_mount_options { |
| + uid_t fs_low_uid; |
| + gid_t fs_low_gid; |
| + userid_t fs_user_id; |
| + bool multiuser; |
| + bool gid_derivation; |
| + bool default_normal; |
| + bool unshared_obb; |
| + unsigned int reserved_mb; |
| + bool nocache; |
| + bool debug; |
| +}; |
| + |
| +struct sdcardfs_vfsmount_options { |
| + gid_t gid; |
| + mode_t mask; |
| +}; |
| + |
| +struct sdcardfs_context_options { |
| + struct sdcardfs_mount_options opts; |
| + struct sdcardfs_vfsmount_options vfsopts; |
| +}; |
| + |
| +extern int parse_options_remount(struct super_block *sb, char *options, int silent, |
| + struct sdcardfs_vfsmount_options *vfsopts); |
| + |
| +/* sdcardfs super-block data in memory */ |
| +struct sdcardfs_sb_info { |
| + struct super_block *sb; |
| + struct super_block *lower_sb; |
| + /* derived perm policy : some of options have been added |
| + * to sdcardfs_mount_options (Android 4.4 support) |
| + */ |
| + struct sdcardfs_mount_options options; |
| + spinlock_t lock; /* protects obbpath */ |
| + char *obbpath_s; |
| + struct path obbpath; |
| + void *pkgl_id; |
| + struct list_head list; |
| +}; |
| + |
| +/* |
| + * inode to private data |
| + * |
| + * Since we use containers and the struct inode is _inside_ the |
| + * sdcardfs_inode_info structure, SDCARDFS_I will always (given a non-NULL |
| + * inode pointer), return a valid non-NULL pointer. |
| + */ |
| +static inline struct sdcardfs_inode_info *SDCARDFS_I(const struct inode *inode) |
| +{ |
| + return container_of(inode, struct sdcardfs_inode_info, vfs_inode); |
| +} |
| + |
| +/* dentry to private data */ |
| +#define SDCARDFS_D(dent) ((struct sdcardfs_dentry_info *)(dent)->d_fsdata) |
| + |
| +/* superblock to private data */ |
| +#define SDCARDFS_SB(super) ((struct sdcardfs_sb_info *)(super)->s_fs_info) |
| + |
| +/* file to private Data */ |
| +#define SDCARDFS_F(file) ((struct sdcardfs_file_info *)((file)->private_data)) |
| + |
| +/* file to lower file */ |
| +static inline struct file *sdcardfs_lower_file(const struct file *f) |
| +{ |
| + return SDCARDFS_F(f)->lower_file; |
| +} |
| + |
| +static inline void sdcardfs_set_lower_file(struct file *f, struct file *val) |
| +{ |
| + SDCARDFS_F(f)->lower_file = val; |
| +} |
| + |
| +/* inode to lower inode. */ |
| +static inline struct inode *sdcardfs_lower_inode(const struct inode *i) |
| +{ |
| + return SDCARDFS_I(i)->lower_inode; |
| +} |
| + |
| +static inline void sdcardfs_set_lower_inode(struct inode *i, struct inode *val) |
| +{ |
| + SDCARDFS_I(i)->lower_inode = val; |
| +} |
| + |
| +/* superblock to lower superblock */ |
| +static inline struct super_block *sdcardfs_lower_super( |
| + const struct super_block *sb) |
| +{ |
| + return SDCARDFS_SB(sb)->lower_sb; |
| +} |
| + |
| +static inline void sdcardfs_set_lower_super(struct super_block *sb, |
| + struct super_block *val) |
| +{ |
| + SDCARDFS_SB(sb)->lower_sb = val; |
| +} |
| + |
| +/* path based (dentry/mnt) macros */ |
| +static inline void pathcpy(struct path *dst, const struct path *src) |
| +{ |
| + dst->dentry = src->dentry; |
| + dst->mnt = src->mnt; |
| +} |
| + |
| +/* sdcardfs_get_pname functions calls path_get() |
| + * therefore, the caller must call "proper" path_put functions |
| + */ |
| +#define SDCARDFS_DENT_FUNC(pname) \ |
| +static inline void sdcardfs_get_##pname(const struct dentry *dent, \ |
| + struct path *pname) \ |
| +{ \ |
| + spin_lock(&SDCARDFS_D(dent)->lock); \ |
| + pathcpy(pname, &SDCARDFS_D(dent)->pname); \ |
| + path_get(pname); \ |
| + spin_unlock(&SDCARDFS_D(dent)->lock); \ |
| + return; \ |
| +} \ |
| +static inline void sdcardfs_put_##pname(const struct dentry *dent, \ |
| + struct path *pname) \ |
| +{ \ |
| + path_put(pname); \ |
| + return; \ |
| +} \ |
| +static inline void sdcardfs_set_##pname(const struct dentry *dent, \ |
| + struct path *pname) \ |
| +{ \ |
| + spin_lock(&SDCARDFS_D(dent)->lock); \ |
| + pathcpy(&SDCARDFS_D(dent)->pname, pname); \ |
| + spin_unlock(&SDCARDFS_D(dent)->lock); \ |
| + return; \ |
| +} \ |
| +static inline void sdcardfs_reset_##pname(const struct dentry *dent) \ |
| +{ \ |
| + spin_lock(&SDCARDFS_D(dent)->lock); \ |
| + SDCARDFS_D(dent)->pname.dentry = NULL; \ |
| + SDCARDFS_D(dent)->pname.mnt = NULL; \ |
| + spin_unlock(&SDCARDFS_D(dent)->lock); \ |
| + return; \ |
| +} \ |
| +static inline void sdcardfs_put_reset_##pname(const struct dentry *dent) \ |
| +{ \ |
| + struct path pname; \ |
| + spin_lock(&SDCARDFS_D(dent)->lock); \ |
| + if (SDCARDFS_D(dent)->pname.dentry) { \ |
| + pathcpy(&pname, &SDCARDFS_D(dent)->pname); \ |
| + SDCARDFS_D(dent)->pname.dentry = NULL; \ |
| + SDCARDFS_D(dent)->pname.mnt = NULL; \ |
| + spin_unlock(&SDCARDFS_D(dent)->lock); \ |
| + path_put(&pname); \ |
| + } else \ |
| + spin_unlock(&SDCARDFS_D(dent)->lock); \ |
| + return; \ |
| +} |
| + |
| +SDCARDFS_DENT_FUNC(lower_path) |
| +SDCARDFS_DENT_FUNC(orig_path) |
| + |
| +static inline bool sbinfo_has_sdcard_magic(struct sdcardfs_sb_info *sbinfo) |
| +{ |
| + return sbinfo && sbinfo->sb |
| + && sbinfo->sb->s_magic == SDCARDFS_SUPER_MAGIC; |
| +} |
| + |
| +static inline struct sdcardfs_inode_data *data_get( |
| + struct sdcardfs_inode_data *data) |
| +{ |
| + if (data) |
| + kref_get(&data->refcount); |
| + return data; |
| +} |
| + |
| +static inline struct sdcardfs_inode_data *top_data_get( |
| + struct sdcardfs_inode_info *info) |
| +{ |
| + struct sdcardfs_inode_data *top_data; |
| + |
| + spin_lock(&info->top_lock); |
| + top_data = data_get(info->top_data); |
| + spin_unlock(&info->top_lock); |
| + return top_data; |
| +} |
| + |
| +extern void data_release(struct kref *ref); |
| + |
| +static inline void data_put(struct sdcardfs_inode_data *data) |
| +{ |
| + kref_put(&data->refcount, data_release); |
| +} |
| + |
| +static inline void release_own_data(struct sdcardfs_inode_info *info) |
| +{ |
| + /* |
| + * This happens exactly once per inode. At this point, the inode that |
| + * originally held this data is about to be freed, and all references |
| + * to it are held as a top value, and will likely be released soon. |
| + */ |
| + info->data->abandoned = true; |
| + data_put(info->data); |
| +} |
| + |
| +static inline void set_top(struct sdcardfs_inode_info *info, |
| + struct sdcardfs_inode_info *top_owner) |
| +{ |
| + struct sdcardfs_inode_data *old_top; |
| + struct sdcardfs_inode_data *new_top = NULL; |
| + |
| + if (top_owner) |
| + new_top = top_data_get(top_owner); |
| + |
| + spin_lock(&info->top_lock); |
| + old_top = info->top_data; |
| + info->top_data = new_top; |
| + if (old_top) |
| + data_put(old_top); |
| + spin_unlock(&info->top_lock); |
| +} |
| + |
| +static inline int get_gid(struct vfsmount *mnt, |
| + struct super_block *sb, |
| + struct sdcardfs_inode_data *data) |
| +{ |
| + struct sdcardfs_vfsmount_options *vfsopts = mnt->data; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(sb); |
| + |
| + if (vfsopts->gid == AID_SDCARD_RW && !sbi->options.default_normal) |
| + /* As an optimization, certain trusted system components only run |
| + * as owner but operate across all users. Since we're now handing |
| + * out the sdcard_rw GID only to trusted apps, we're okay relaxing |
| + * the user boundary enforcement for the default view. The UIDs |
| + * assigned to app directories are still multiuser aware. |
| + */ |
| + return AID_SDCARD_RW; |
| + else |
| + return multiuser_get_uid(data->userid, vfsopts->gid); |
| +} |
| + |
| +static inline int get_mode(struct vfsmount *mnt, |
| + struct sdcardfs_inode_info *info, |
| + struct sdcardfs_inode_data *data) |
| +{ |
| + int owner_mode; |
| + int filtered_mode; |
| + struct sdcardfs_vfsmount_options *opts = mnt->data; |
| + int visible_mode = 0775 & ~opts->mask; |
| + |
| + |
| + if (data->perm == PERM_PRE_ROOT) { |
| + /* Top of multi-user view should always be visible to ensure |
| + * secondary users can traverse inside. |
| + */ |
| + visible_mode = 0711; |
| + } else if (data->under_android) { |
| + /* Block "other" access to Android directories, since only apps |
| + * belonging to a specific user should be in there; we still |
| + * leave +x open for the default view. |
| + */ |
| + if (opts->gid == AID_SDCARD_RW) |
| + visible_mode = visible_mode & ~0006; |
| + else |
| + visible_mode = visible_mode & ~0007; |
| + } |
| + owner_mode = info->lower_inode->i_mode & 0700; |
| + filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6)); |
| + return filtered_mode; |
| +} |
| + |
| +static inline int has_graft_path(const struct dentry *dent) |
| +{ |
| + int ret = 0; |
| + |
| + spin_lock(&SDCARDFS_D(dent)->lock); |
| + if (SDCARDFS_D(dent)->orig_path.dentry != NULL) |
| + ret = 1; |
| + spin_unlock(&SDCARDFS_D(dent)->lock); |
| + |
| + return ret; |
| +} |
| + |
| +static inline void sdcardfs_get_real_lower(const struct dentry *dent, |
| + struct path *real_lower) |
| +{ |
| + /* in case of a local obb dentry |
| + * the orig_path should be returned |
| + */ |
| + if (has_graft_path(dent)) |
| + sdcardfs_get_orig_path(dent, real_lower); |
| + else |
| + sdcardfs_get_lower_path(dent, real_lower); |
| +} |
| + |
| +static inline void sdcardfs_put_real_lower(const struct dentry *dent, |
| + struct path *real_lower) |
| +{ |
| + if (has_graft_path(dent)) |
| + sdcardfs_put_orig_path(dent, real_lower); |
| + else |
| + sdcardfs_put_lower_path(dent, real_lower); |
| +} |
| + |
| +extern struct mutex sdcardfs_super_list_lock; |
| +extern struct list_head sdcardfs_super_list; |
| + |
| +/* for packagelist.c */ |
| +extern appid_t get_appid(const char *app_name); |
| +extern appid_t get_ext_gid(const char *app_name); |
| +extern appid_t is_excluded(const char *app_name, userid_t userid); |
| +extern int check_caller_access_to_name(struct inode *parent_node, const struct qstr *name); |
| +extern int packagelist_init(void); |
| +extern void packagelist_exit(void); |
| + |
| +/* for derived_perm.c */ |
| +#define BY_NAME (1 << 0) |
| +#define BY_USERID (1 << 1) |
| +struct limit_search { |
| + unsigned int flags; |
| + struct qstr name; |
| + userid_t userid; |
| +}; |
| + |
| +extern void setup_derived_state(struct inode *inode, perm_t perm, |
| + userid_t userid, uid_t uid); |
| +extern void get_derived_permission(struct dentry *parent, struct dentry *dentry); |
| +extern void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, const struct qstr *name); |
| +extern void fixup_perms_recursive(struct dentry *dentry, struct limit_search *limit); |
| + |
| +extern void update_derived_permission_lock(struct dentry *dentry); |
| +void fixup_lower_ownership(struct dentry *dentry, const char *name); |
| +extern int need_graft_path(struct dentry *dentry); |
| +extern int is_base_obbpath(struct dentry *dentry); |
| +extern int is_obbpath_invalid(struct dentry *dentry); |
| +extern int setup_obb_dentry(struct dentry *dentry, struct path *lower_path); |
| + |
| +/* locking helpers */ |
| +static inline struct dentry *lock_parent(struct dentry *dentry) |
| +{ |
| + struct dentry *dir = dget_parent(dentry); |
| + |
| + inode_lock_nested(d_inode(dir), I_MUTEX_PARENT); |
| + return dir; |
| +} |
| + |
| +static inline void unlock_dir(struct dentry *dir) |
| +{ |
| + inode_unlock(d_inode(dir)); |
| + dput(dir); |
| +} |
| + |
| +static inline int prepare_dir(const char *path_s, uid_t uid, gid_t gid, mode_t mode) |
| +{ |
| + int err; |
| + struct dentry *dent; |
| + struct iattr attrs; |
| + struct path parent; |
| + |
| + dent = kern_path_locked(path_s, &parent); |
| + if (IS_ERR(dent)) { |
| + err = PTR_ERR(dent); |
| + if (err == -EEXIST) |
| + err = 0; |
| + goto out_unlock; |
| + } |
| + |
| + err = vfs_mkdir2(parent.mnt, d_inode(parent.dentry), dent, mode); |
| + if (err) { |
| + if (err == -EEXIST) |
| + err = 0; |
| + goto out_dput; |
| + } |
| + |
| + attrs.ia_uid = make_kuid(&init_user_ns, uid); |
| + attrs.ia_gid = make_kgid(&init_user_ns, gid); |
| + attrs.ia_valid = ATTR_UID | ATTR_GID; |
| + inode_lock(d_inode(dent)); |
| + notify_change2(parent.mnt, dent, &attrs, NULL); |
| + inode_unlock(d_inode(dent)); |
| + |
| +out_dput: |
| + dput(dent); |
| + |
| +out_unlock: |
| + /* parent dentry locked by lookup_create */ |
| + inode_unlock(d_inode(parent.dentry)); |
| + path_put(&parent); |
| + return err; |
| +} |
| + |
| +/* |
| + * Return 1, if a disk has enough free space, otherwise 0. |
| + * We assume that any files can not be overwritten. |
| + */ |
| +static inline int check_min_free_space(struct dentry *dentry, size_t size, int dir) |
| +{ |
| + int err; |
| + struct path lower_path; |
| + struct kstatfs statfs; |
| + u64 avail; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + |
| + if (sbi->options.reserved_mb) { |
| + /* Get fs stat of lower filesystem. */ |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + err = vfs_statfs(&lower_path, &statfs); |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + |
| + if (unlikely(err)) |
| + return 0; |
| + |
| + /* Invalid statfs informations. */ |
| + if (unlikely(statfs.f_bsize == 0)) |
| + return 0; |
| + |
| + /* if you are checking directory, set size to f_bsize. */ |
| + if (unlikely(dir)) |
| + size = statfs.f_bsize; |
| + |
| + /* available size */ |
| + avail = statfs.f_bavail * statfs.f_bsize; |
| + |
| + /* not enough space */ |
| + if ((u64)size > avail) |
| + return 0; |
| + |
| + /* enough space */ |
| + if ((avail - size) > (sbi->options.reserved_mb * 1024 * 1024)) |
| + return 1; |
| + |
| + return 0; |
| + } else |
| + return 1; |
| +} |
| + |
| +/* |
| + * Copies attrs and maintains sdcardfs managed attrs |
| + * Since our permission check handles all special permissions, set those to be open |
| + */ |
| +static inline void sdcardfs_copy_and_fix_attrs(struct inode *dest, const struct inode *src) |
| +{ |
| + dest->i_mode = (src->i_mode & S_IFMT) | S_IRWXU | S_IRWXG | |
| + S_IROTH | S_IXOTH; /* 0775 */ |
| + dest->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(dest)->data->d_uid); |
| + dest->i_gid = make_kgid(&init_user_ns, AID_SDCARD_RW); |
| + dest->i_rdev = src->i_rdev; |
| + dest->i_atime = src->i_atime; |
| + dest->i_mtime = src->i_mtime; |
| + dest->i_ctime = src->i_ctime; |
| + dest->i_blkbits = src->i_blkbits; |
| + dest->i_flags = src->i_flags; |
| + set_nlink(dest, src->i_nlink); |
| +} |
| + |
| +static inline bool str_case_eq(const char *s1, const char *s2) |
| +{ |
| + return !strcasecmp(s1, s2); |
| +} |
| + |
| +static inline bool str_n_case_eq(const char *s1, const char *s2, size_t len) |
| +{ |
| + return !strncasecmp(s1, s2, len); |
| +} |
| + |
| +static inline bool qstr_case_eq(const struct qstr *q1, const struct qstr *q2) |
| +{ |
| + return q1->len == q2->len && str_n_case_eq(q1->name, q2->name, q2->len); |
| +} |
| + |
| +#define QSTR_LITERAL(string) QSTR_INIT(string, sizeof(string)-1) |
| + |
| +#endif /* not _SDCARDFS_H_ */ |
| diff --git a/fs/sdcardfs/super.c b/fs/sdcardfs/super.c |
| new file mode 100644 |
| index 000000000000..6b1d27e5067b |
| --- /dev/null |
| +++ b/fs/sdcardfs/super.c |
| @@ -0,0 +1,297 @@ |
| +/* |
| + * fs/sdcardfs/super.c |
| + * |
| + * Copyright (c) 2013 Samsung Electronics Co. Ltd |
| + * Authors: Daeho Jeong, Woojoong Lee, Seunghwan Hyun, |
| + * Sunghwan Yun, Sungjong Seo |
| + * |
| + * This program has been developed as a stackable file system based on |
| + * the WrapFS which written by |
| + * |
| + * Copyright (c) 1998-2011 Erez Zadok |
| + * Copyright (c) 2009 Shrikar Archak |
| + * Copyright (c) 2003-2011 Stony Brook University |
| + * Copyright (c) 2003-2011 The Research Foundation of SUNY |
| + * |
| + * This file is dual licensed. It may be redistributed and/or modified |
| + * under the terms of the Apache 2.0 License OR version 2 of the GNU |
| + * General Public License. |
| + */ |
| + |
| +#include <linux/fs_context.h> |
| + |
| +#include "sdcardfs.h" |
| + |
| +/* |
| + * The inode cache is used with alloc_inode for both our inode info and the |
| + * vfs inode. |
| + */ |
| +static struct kmem_cache *sdcardfs_inode_cachep; |
| + |
| +/* |
| + * To support the top references, we must track some data separately. |
| + * An sdcardfs_inode_info always has a reference to its data, and once set up, |
| + * also has a reference to its top. The top may be itself, in which case it |
| + * holds two references to its data. When top is changed, it takes a ref to the |
| + * new data and then drops the ref to the old data. |
| + */ |
| +static struct kmem_cache *sdcardfs_inode_data_cachep; |
| + |
| +void data_release(struct kref *ref) |
| +{ |
| + struct sdcardfs_inode_data *data = |
| + container_of(ref, struct sdcardfs_inode_data, refcount); |
| + |
| + kmem_cache_free(sdcardfs_inode_data_cachep, data); |
| +} |
| + |
| +/* final actions when unmounting a file system */ |
| +static void sdcardfs_put_super(struct super_block *sb) |
| +{ |
| + struct sdcardfs_sb_info *spd; |
| + struct super_block *s; |
| + |
| + spd = SDCARDFS_SB(sb); |
| + if (!spd) |
| + return; |
| + |
| + if (spd->obbpath_s) { |
| + kfree(spd->obbpath_s); |
| + path_put(&spd->obbpath); |
| + } |
| + |
| + /* decrement lower super references */ |
| + s = sdcardfs_lower_super(sb); |
| + sdcardfs_set_lower_super(sb, NULL); |
| + atomic_dec(&s->s_active); |
| + |
| + kfree(spd); |
| + sb->s_fs_info = NULL; |
| +} |
| + |
| +static int sdcardfs_statfs(struct dentry *dentry, struct kstatfs *buf) |
| +{ |
| + int err; |
| + struct path lower_path; |
| + u32 min_blocks; |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb); |
| + |
| + sdcardfs_get_lower_path(dentry, &lower_path); |
| + err = vfs_statfs(&lower_path, buf); |
| + sdcardfs_put_lower_path(dentry, &lower_path); |
| + |
| + if (sbi->options.reserved_mb) { |
| + /* Invalid statfs informations. */ |
| + if (buf->f_bsize == 0) { |
| + pr_err("Returned block size is zero.\n"); |
| + return -EINVAL; |
| + } |
| + |
| + min_blocks = ((sbi->options.reserved_mb * 1024 * 1024)/buf->f_bsize); |
| + buf->f_blocks -= min_blocks; |
| + |
| + if (buf->f_bavail > min_blocks) |
| + buf->f_bavail -= min_blocks; |
| + else |
| + buf->f_bavail = 0; |
| + |
| + /* Make reserved blocks invisiable to media storage */ |
| + buf->f_bfree = buf->f_bavail; |
| + } |
| + |
| + /* set return buf to our f/s to avoid confusing user-level utils */ |
| + buf->f_type = SDCARDFS_SUPER_MAGIC; |
| + |
| + return err; |
| +} |
| + |
| +static void *sdcardfs_clone_mnt_data(void *data) |
| +{ |
| + struct sdcardfs_vfsmount_options *opt = kmalloc(sizeof(struct sdcardfs_vfsmount_options), GFP_KERNEL); |
| + struct sdcardfs_vfsmount_options *old = data; |
| + |
| + if (!opt) |
| + return NULL; |
| + opt->gid = old->gid; |
| + opt->mask = old->mask; |
| + return opt; |
| +} |
| + |
| +static void sdcardfs_copy_mnt_data(void *data, void *newdata) |
| +{ |
| + struct sdcardfs_vfsmount_options *old = data; |
| + struct sdcardfs_vfsmount_options *new = newdata; |
| + |
| + old->gid = new->gid; |
| + old->mask = new->mask; |
| +} |
| + |
| +static void sdcardfs_update_mnt_data(void *data, struct fs_context *fc) |
| +{ |
| + struct sdcardfs_vfsmount_options *opts = data; |
| + struct sdcardfs_context_options *fcopts = fc->fs_private; |
| + |
| + opts->gid = fcopts->vfsopts.gid; |
| + opts->mask = fcopts->vfsopts.mask; |
| +} |
| + |
| +/* |
| + * Called by iput() when the inode reference count reached zero |
| + * and the inode is not hashed anywhere. Used to clear anything |
| + * that needs to be, before the inode is completely destroyed and put |
| + * on the inode free list. |
| + */ |
| +static void sdcardfs_evict_inode(struct inode *inode) |
| +{ |
| + struct inode *lower_inode; |
| + |
| + truncate_inode_pages(&inode->i_data, 0); |
| + set_top(SDCARDFS_I(inode), NULL); |
| + clear_inode(inode); |
| + /* |
| + * Decrement a reference to a lower_inode, which was incremented |
| + * by our read_inode when it was created initially. |
| + */ |
| + lower_inode = sdcardfs_lower_inode(inode); |
| + sdcardfs_set_lower_inode(inode, NULL); |
| + iput(lower_inode); |
| +} |
| + |
| +static struct inode *sdcardfs_alloc_inode(struct super_block *sb) |
| +{ |
| + struct sdcardfs_inode_info *i; |
| + struct sdcardfs_inode_data *d; |
| + |
| + i = kmem_cache_alloc(sdcardfs_inode_cachep, GFP_KERNEL); |
| + if (!i) |
| + return NULL; |
| + |
| + /* memset everything up to the inode to 0 */ |
| + memset(i, 0, offsetof(struct sdcardfs_inode_info, vfs_inode)); |
| + |
| + d = kmem_cache_alloc(sdcardfs_inode_data_cachep, |
| + GFP_KERNEL | __GFP_ZERO); |
| + if (!d) { |
| + kmem_cache_free(sdcardfs_inode_cachep, i); |
| + return NULL; |
| + } |
| + |
| + i->data = d; |
| + kref_init(&d->refcount); |
| + i->top_data = d; |
| + spin_lock_init(&i->top_lock); |
| + kref_get(&d->refcount); |
| + |
| + inode_set_iversion(&i->vfs_inode, 1); |
| + return &i->vfs_inode; |
| +} |
| + |
| +static void i_callback(struct rcu_head *head) |
| +{ |
| + struct inode *inode = container_of(head, struct inode, i_rcu); |
| + |
| + release_own_data(SDCARDFS_I(inode)); |
| + kmem_cache_free(sdcardfs_inode_cachep, SDCARDFS_I(inode)); |
| +} |
| + |
| +static void sdcardfs_destroy_inode(struct inode *inode) |
| +{ |
| + call_rcu(&inode->i_rcu, i_callback); |
| +} |
| + |
| +/* sdcardfs inode cache constructor */ |
| +static void init_once(void *obj) |
| +{ |
| + struct sdcardfs_inode_info *i = obj; |
| + |
| + inode_init_once(&i->vfs_inode); |
| +} |
| + |
| +int sdcardfs_init_inode_cache(void) |
| +{ |
| + sdcardfs_inode_cachep = |
| + kmem_cache_create("sdcardfs_inode_cache", |
| + sizeof(struct sdcardfs_inode_info), 0, |
| + SLAB_RECLAIM_ACCOUNT, init_once); |
| + |
| + if (!sdcardfs_inode_cachep) |
| + return -ENOMEM; |
| + |
| + sdcardfs_inode_data_cachep = |
| + kmem_cache_create("sdcardfs_inode_data_cache", |
| + sizeof(struct sdcardfs_inode_data), 0, |
| + SLAB_RECLAIM_ACCOUNT, NULL); |
| + if (!sdcardfs_inode_data_cachep) { |
| + kmem_cache_destroy(sdcardfs_inode_cachep); |
| + return -ENOMEM; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +/* sdcardfs inode cache destructor */ |
| +void sdcardfs_destroy_inode_cache(void) |
| +{ |
| + kmem_cache_destroy(sdcardfs_inode_data_cachep); |
| + kmem_cache_destroy(sdcardfs_inode_cachep); |
| +} |
| + |
| +/* |
| + * Used only in nfs, to kill any pending RPC tasks, so that subsequent |
| + * code can actually succeed and won't leave tasks that need handling. |
| + */ |
| +static void sdcardfs_umount_begin(struct super_block *sb) |
| +{ |
| + struct super_block *lower_sb; |
| + |
| + lower_sb = sdcardfs_lower_super(sb); |
| + if (lower_sb && lower_sb->s_op && lower_sb->s_op->umount_begin) |
| + lower_sb->s_op->umount_begin(lower_sb); |
| +} |
| + |
| +static int sdcardfs_show_options(struct vfsmount *mnt, struct seq_file *m, |
| + struct dentry *root) |
| +{ |
| + struct sdcardfs_sb_info *sbi = SDCARDFS_SB(root->d_sb); |
| + struct sdcardfs_mount_options *opts = &sbi->options; |
| + struct sdcardfs_vfsmount_options *vfsopts = mnt->data; |
| + |
| + if (opts->fs_low_uid != 0) |
| + seq_printf(m, ",fsuid=%u", opts->fs_low_uid); |
| + if (opts->fs_low_gid != 0) |
| + seq_printf(m, ",fsgid=%u", opts->fs_low_gid); |
| + if (vfsopts->gid != 0) |
| + seq_printf(m, ",gid=%u", vfsopts->gid); |
| + if (opts->multiuser) |
| + seq_puts(m, ",multiuser"); |
| + if (vfsopts->mask) |
| + seq_printf(m, ",mask=%u", vfsopts->mask); |
| + if (opts->fs_user_id) |
| + seq_printf(m, ",userid=%u", opts->fs_user_id); |
| + if (opts->gid_derivation) |
| + seq_puts(m, ",derive_gid"); |
| + if (opts->default_normal) |
| + seq_puts(m, ",default_normal"); |
| + if (opts->reserved_mb != 0) |
| + seq_printf(m, ",reserved=%uMB", opts->reserved_mb); |
| + if (opts->nocache) |
| + seq_printf(m, ",nocache"); |
| + if (opts->unshared_obb) |
| + seq_printf(m, ",unshared_obb"); |
| + |
| + return 0; |
| +}; |
| + |
| +const struct super_operations sdcardfs_sops = { |
| + .put_super = sdcardfs_put_super, |
| + .statfs = sdcardfs_statfs, |
| + .clone_mnt_data = sdcardfs_clone_mnt_data, |
| + .copy_mnt_data = sdcardfs_copy_mnt_data, |
| + .update_mnt_data = sdcardfs_update_mnt_data, |
| + .evict_inode = sdcardfs_evict_inode, |
| + .umount_begin = sdcardfs_umount_begin, |
| + .show_options2 = sdcardfs_show_options, |
| + .alloc_inode = sdcardfs_alloc_inode, |
| + .destroy_inode = sdcardfs_destroy_inode, |
| + .drop_inode = generic_delete_inode, |
| +}; |