Merge branch 'tmp-bab1564' into msm-4.4
* tmp-bab1564: ANDROID: mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED android: base-cfg: Add CONFIG_INET_DIAG_DESTROY cpufreq: interactive: only apply interactive boost when enabled cpufreq: interactive: fix policy locking ANDROID: dm verity fec: add sysfs attribute fec/corrected ANDROID: android: base-cfg: enable CONFIG_DM_VERITY_FEC UPSTREAM: dm verity: add ignore_zero_blocks feature UPSTREAM: dm verity: add support for forward error correction UPSTREAM: dm verity: factor out verity_for_bv_block() UPSTREAM: dm verity: factor out structures and functions useful to separate object UPSTREAM: dm verity: move dm-verity.c to dm-verity-target.c UPSTREAM: dm verity: separate function for parsing opt args UPSTREAM: dm verity: clean up duplicate hashing code UPSTREAM: dm: don't save and restore bi_private mm: Export do_munmap sdcardfs: remove unneeded __init and __exit sdcardfs: Remove unused code fs: Export d_absolute_path sdcardfs: remove effectless config option inotify: Fix erroneous update of bit count fs: sdcardfs: Declare LOOKUP_CASE_INSENSITIVE unconditionally trace: cpufreq: fix typo in min/max cpufreq sdcardfs: Add support for d_canonical_path vfs: add d_canonical_path for stacked filesystem support sdcardfs: Bring up to date with Android M permissions: Changed type-casting in packagelist management Port of sdcardfs to 4.4 Included sdcardfs source code for kernel 3.0 ANDROID: usb: gadget: Add support for MTP OS desc CHROMIUM: usb: gadget: f_accessory: add .raw_request callback CHROMIUM: usb: gadget: audio_source: add .free_func callback CHROMIUM: usb: gadget: f_mtp: fix usb_ss_ep_comp_descriptor CHROMIUM: usb: gadget: f_mtp: Add SuperSpeed support FROMLIST: mmc: block: fix ABI regression of mmc_blk_ioctl FROMLIST: mm: ASLR: use get_random_long() FROMLIST: drivers: char: random: add get_random_long() FROMLIST: pstore-ram: fix NULL reference when used with pdata usb: u_ether: Add missing rx_work init ANDROID: dm-crypt: run in a WQ_HIGHPRI workqueue misc: uid_stat: Include linux/atomic.h instead of asm/atomic.h hid-sensor-hub.c: fix wrong do_div() usage power: Provide dummy log_suspend_abort_reason() if SUSPEND is disabled PM / suspend: Add dependency on RTC_LIB drivers: power: use 'current' instead of 'get_current()' video: adf: Set ADF_MEMBLOCK to boolean video: adf: Fix modular build net: ppp: Fix modular build for PPPOLAC and PPPOPNS net: pppolac/pppopns: Replace msg.msg_iov with iov_iter_kvec() ANDROID: mmc: sdio: Disable retuning in sdio_reset_comm() ANDROID: mmc: Move tracepoint creation and export symbols ANDROID: kernel/watchdog: fix unused variable warning ANDROID: usb: gadget: f_mtp: don't use le16 for u8 field ANDROID: lowmemorykiller: fix declaration order warnings ANDROID: net: fix 'const' warnings net: diag: support v4mapped sockets in inet_diag_find_one_icsk() net: tcp: deal with listen sockets properly in tcp_abort. tcp: diag: add support for request sockets to tcp_abort() net: diag: Support destroying TCP sockets. net: diag: Support SOCK_DESTROY for inet sockets. net: diag: Add the ability to destroy a socket. net: diag: split inet_diag_dump_one_icsk into two Revert "mmc: Extend wakelock if bus is dead" Revert "mmc: core: Hold a wake lock accross delayed work + mmc rescan" ANDROID: mmc: move to a SCHED_FIFO thread Conflicts: drivers/cpufreq/cpufreq_interactive.c drivers/misc/uid_stat.c drivers/mmc/card/block.c drivers/mmc/card/queue.c drivers/mmc/card/queue.h drivers/mmc/core/core.c drivers/mmc/core/sdio.c drivers/staging/android/lowmemorykiller.c drivers/usb/gadget/function/f_mtp.c kernel/watchdog.c Signed-off-by: Runmin Wang <runminw@codeaurora.org> Change-Id: Ibb4db11c57395f67dee86211a110c462e6181552
This commit is contained in:
commit
c568eb7aca
66 changed files with 5978 additions and 337 deletions
|
@ -26,3 +26,9 @@ switching-sched.txt
|
|||
- Switching I/O schedulers at runtime
|
||||
writeback_cache_control.txt
|
||||
- Control of volatile write back caches
|
||||
mmc-max-speed.txt
|
||||
- eMMC layer speed simulation, related to /sys/block/mmcblk*/
|
||||
attributes:
|
||||
max_read_speed
|
||||
max_write_speed
|
||||
cache_size
|
||||
|
|
38
Documentation/block/mmc-max-speed.txt
Normal file
38
Documentation/block/mmc-max-speed.txt
Normal file
|
@ -0,0 +1,38 @@
|
|||
eMMC Block layer simulation speed controls in /sys/block/mmcblk*/
|
||||
===============================================
|
||||
|
||||
Turned on with CONFIG_MMC_SIMULATE_MAX_SPEED which enables MMC device speed
|
||||
limiting. Used to test and simulate the behavior of the system when
|
||||
confronted with a slow MMC.
|
||||
|
||||
Enables max_read_speed, max_write_speed and cache_size attributes and module
|
||||
default parameters to control the write or read maximum KB/second speed
|
||||
behaviors.
|
||||
|
||||
NB: There is room for improving the algorithm for aspects tied directly to
|
||||
eMMC specific behavior. For instance, wear leveling and stalls from an
|
||||
exhausted erase pool. We would expect that if there was a need to provide
|
||||
similar speed simulation controls to other types of block devices, aspects of
|
||||
their behavior are modelled separately (e.g. head seek times, heat assist,
|
||||
shingling and rotational latency).
|
||||
|
||||
/sys/block/mmcblk0/max_read_speed:
|
||||
|
||||
Number of KB/second reads allowed to the block device. Used to test and
|
||||
simulate the behavior of the system when confronted with a slow reading MMC.
|
||||
Set to 0 or "off" to place no speed limit.
|
||||
|
||||
/sys/block/mmcblk0/max_write_speed:
|
||||
|
||||
Number of KB/second writes allowed to the block device. Used to test and
|
||||
simulate the behavior of the system when confronted with a slow writing MMC.
|
||||
Set to 0 or "off" to place no speed limit.
|
||||
|
||||
/sys/block/mmcblk0/cache_size:
|
||||
|
||||
Number of MB of high speed memory or high speed SLC cache expected on the
|
||||
eMMC device being simulated. Used to help simulate the write-back behavior
|
||||
more accurately. The assumption is the cache has no delay, but draws down
|
||||
in the background to the MLC/TLC primary store at the max_write_speed rate.
|
||||
Any write speed delays will show up when the cache is full, or when an I/O
|
||||
request to flush is issued.
|
|
@ -18,11 +18,11 @@ Construction Parameters
|
|||
|
||||
0 is the original format used in the Chromium OS.
|
||||
The salt is appended when hashing, digests are stored continuously and
|
||||
the rest of the block is padded with zeros.
|
||||
the rest of the block is padded with zeroes.
|
||||
|
||||
1 is the current format that should be used for new devices.
|
||||
The salt is prepended when hashing and each digest is
|
||||
padded with zeros to the power of two.
|
||||
padded with zeroes to the power of two.
|
||||
|
||||
<dev>
|
||||
This is the device containing data, the integrity of which needs to be
|
||||
|
@ -79,6 +79,37 @@ restart_on_corruption
|
|||
not compatible with ignore_corruption and requires user space support to
|
||||
avoid restart loops.
|
||||
|
||||
ignore_zero_blocks
|
||||
Do not verify blocks that are expected to contain zeroes and always return
|
||||
zeroes instead. This may be useful if the partition contains unused blocks
|
||||
that are not guaranteed to contain zeroes.
|
||||
|
||||
use_fec_from_device <fec_dev>
|
||||
Use forward error correction (FEC) to recover from corruption if hash
|
||||
verification fails. Use encoding data from the specified device. This
|
||||
may be the same device where data and hash blocks reside, in which case
|
||||
fec_start must be outside data and hash areas.
|
||||
|
||||
If the encoding data covers additional metadata, it must be accessible
|
||||
on the hash device after the hash blocks.
|
||||
|
||||
Note: block sizes for data and hash devices must match. Also, if the
|
||||
verity <dev> is encrypted the <fec_dev> should be too.
|
||||
|
||||
fec_roots <num>
|
||||
Number of generator roots. This equals to the number of parity bytes in
|
||||
the encoding data. For example, in RS(M, N) encoding, the number of roots
|
||||
is M-N.
|
||||
|
||||
fec_blocks <num>
|
||||
The number of encoding data blocks on the FEC device. The block size for
|
||||
the FEC device is <data_block_size>.
|
||||
|
||||
fec_start <offset>
|
||||
This is the offset, in <data_block_size> blocks, from the start of the
|
||||
FEC device to the beginning of the encoding data.
|
||||
|
||||
|
||||
Theory of operation
|
||||
===================
|
||||
|
||||
|
@ -98,6 +129,11 @@ per-block basis. This allows for a lightweight hash computation on first read
|
|||
into the page cache. Block hashes are stored linearly, aligned to the nearest
|
||||
block size.
|
||||
|
||||
If forward error correction (FEC) support is enabled any recovery of
|
||||
corrupted data will be verified using the cryptographic hash of the
|
||||
corresponding data. This is why combining error correction with
|
||||
integrity checking is essential.
|
||||
|
||||
Hash Tree
|
||||
---------
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ CONFIG_CGROUP_SCHED=y
|
|||
CONFIG_CP15_BARRIER_EMULATION=y
|
||||
CONFIG_DM_CRYPT=y
|
||||
CONFIG_DM_VERITY=y
|
||||
CONFIG_DM_VERITY_FEC=y
|
||||
CONFIG_EMBEDDED=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_HIGH_RES_TIMERS=y
|
||||
|
@ -28,6 +29,7 @@ CONFIG_INET6_AH=y
|
|||
CONFIG_INET6_ESP=y
|
||||
CONFIG_INET6_IPCOMP=y
|
||||
CONFIG_INET=y
|
||||
CONFIG_INET_DIAG_DESTROY=y
|
||||
CONFIG_INET_ESP=y
|
||||
CONFIG_INET_XFRM_MODE_TUNNEL=y
|
||||
CONFIG_IP6_NF_FILTER=y
|
||||
|
|
|
@ -173,7 +173,7 @@ unsigned long arch_mmap_rnd(void)
|
|||
{
|
||||
unsigned long rnd;
|
||||
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1);
|
||||
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
@ -53,10 +53,10 @@ unsigned long arch_mmap_rnd(void)
|
|||
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (test_thread_flag(TIF_32BIT))
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_compat_bits) - 1);
|
||||
rnd = get_random_long() & ((1UL << mmap_rnd_compat_bits) - 1);
|
||||
else
|
||||
#endif
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1);
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ unsigned long arch_mmap_rnd(void)
|
|||
{
|
||||
unsigned long rnd;
|
||||
|
||||
rnd = (unsigned long)get_random_int();
|
||||
rnd = get_random_long();
|
||||
rnd <<= PAGE_SHIFT;
|
||||
if (TASK_IS_32BIT_ADDR)
|
||||
rnd &= 0xfffffful;
|
||||
|
@ -174,7 +174,7 @@ void arch_pick_mmap_layout(struct mm_struct *mm)
|
|||
|
||||
static inline unsigned long brk_rnd(void)
|
||||
{
|
||||
unsigned long rnd = get_random_int();
|
||||
unsigned long rnd = get_random_long();
|
||||
|
||||
rnd = rnd << PAGE_SHIFT;
|
||||
/* 8MB for 32bit, 256MB for 64bit */
|
||||
|
|
|
@ -1659,9 +1659,9 @@ static inline unsigned long brk_rnd(void)
|
|||
|
||||
/* 8MB for 32bit, 1GB for 64bit */
|
||||
if (is_32bit_task())
|
||||
rnd = (long)(get_random_int() % (1<<(23-PAGE_SHIFT)));
|
||||
rnd = (get_random_long() % (1UL<<(23-PAGE_SHIFT)));
|
||||
else
|
||||
rnd = (long)(get_random_int() % (1<<(30-PAGE_SHIFT)));
|
||||
rnd = (get_random_long() % (1UL<<(30-PAGE_SHIFT)));
|
||||
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
@ -59,9 +59,9 @@ unsigned long arch_mmap_rnd(void)
|
|||
|
||||
/* 8MB for 32bit, 1GB for 64bit */
|
||||
if (is_32bit_task())
|
||||
rnd = (unsigned long)get_random_int() % (1<<(23-PAGE_SHIFT));
|
||||
rnd = get_random_long() % (1<<(23-PAGE_SHIFT));
|
||||
else
|
||||
rnd = (unsigned long)get_random_int() % (1<<(30-PAGE_SHIFT));
|
||||
rnd = get_random_long() % (1UL<<(30-PAGE_SHIFT));
|
||||
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
@ -264,7 +264,7 @@ static unsigned long mmap_rnd(void)
|
|||
unsigned long rnd = 0UL;
|
||||
|
||||
if (current->flags & PF_RANDOMIZE) {
|
||||
unsigned long val = get_random_int();
|
||||
unsigned long val = get_random_long();
|
||||
if (test_thread_flag(TIF_32BIT))
|
||||
rnd = (val % (1UL << (23UL-PAGE_SHIFT)));
|
||||
else
|
||||
|
|
|
@ -71,12 +71,12 @@ unsigned long arch_mmap_rnd(void)
|
|||
|
||||
if (mmap_is_ia32())
|
||||
#ifdef CONFIG_COMPAT
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_compat_bits) - 1);
|
||||
rnd = get_random_long() & ((1UL << mmap_rnd_compat_bits) - 1);
|
||||
#else
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1);
|
||||
#endif
|
||||
else
|
||||
rnd = (unsigned long)get_random_int() & ((1 << mmap_rnd_bits) - 1);
|
||||
rnd = get_random_long() & ((1UL << mmap_rnd_bits) - 1);
|
||||
|
||||
return rnd << PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include <linux/timer.h>
|
||||
#include <linux/wakeup_reason.h>
|
||||
|
||||
#include <asm/current.h>
|
||||
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
|
@ -1412,7 +1414,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
|
|||
goto Complete;
|
||||
|
||||
data.dev = dev;
|
||||
data.tsk = get_current();
|
||||
data.tsk = current;
|
||||
init_timer_on_stack(&timer);
|
||||
timer.expires = jiffies + HZ * 12;
|
||||
timer.function = dpm_drv_timeout;
|
||||
|
|
|
@ -1818,6 +1818,28 @@ unsigned int get_random_int(void)
|
|||
}
|
||||
EXPORT_SYMBOL(get_random_int);
|
||||
|
||||
/*
|
||||
* Same as get_random_int(), but returns unsigned long.
|
||||
*/
|
||||
unsigned long get_random_long(void)
|
||||
{
|
||||
__u32 *hash;
|
||||
unsigned long ret;
|
||||
|
||||
if (arch_get_random_long(&ret))
|
||||
return ret;
|
||||
|
||||
hash = get_cpu_var(get_random_int_hash);
|
||||
|
||||
hash[0] += current->pid + jiffies + random_get_entropy();
|
||||
md5_transform(hash, random_int_secret);
|
||||
ret = *(unsigned long *)hash;
|
||||
put_cpu_var(get_random_int_hash);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(get_random_long);
|
||||
|
||||
/*
|
||||
* randomize_range() returns a start address such that
|
||||
*
|
||||
|
|
|
@ -218,7 +218,8 @@ int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
|
|||
goto done_proc;
|
||||
}
|
||||
|
||||
remaining_bytes = do_div(buffer_size, sizeof(__s32));
|
||||
remaining_bytes = buffer_size % sizeof(__s32);
|
||||
buffer_size = buffer_size / sizeof(__s32);
|
||||
if (buffer_size) {
|
||||
for (i = 0; i < buffer_size; ++i) {
|
||||
hid_set_field(report->field[field_index], i,
|
||||
|
|
|
@ -475,6 +475,18 @@ config DM_VERITY
|
|||
|
||||
If unsure, say N.
|
||||
|
||||
config DM_VERITY_FEC
|
||||
bool "Verity forward error correction support"
|
||||
depends on DM_VERITY
|
||||
select REED_SOLOMON
|
||||
select REED_SOLOMON_DEC8
|
||||
---help---
|
||||
Add forward error correction support to dm-verity. This option
|
||||
makes it possible to use pre-generated error correction data to
|
||||
recover from corrupted blocks.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config DM_SWITCH
|
||||
tristate "Switch target support (EXPERIMENTAL)"
|
||||
depends on BLK_DEV_DM
|
||||
|
|
|
@ -16,6 +16,7 @@ dm-cache-mq-y += dm-cache-policy-mq.o
|
|||
dm-cache-smq-y += dm-cache-policy-smq.o
|
||||
dm-cache-cleaner-y += dm-cache-policy-cleaner.o
|
||||
dm-era-y += dm-era-target.o
|
||||
dm-verity-y += dm-verity-target.o
|
||||
md-mod-y += md.o bitmap.o
|
||||
raid456-y += raid5.o raid5-cache.o
|
||||
|
||||
|
@ -64,3 +65,7 @@ obj-$(CONFIG_DM_REQ_CRYPT) += dm-req-crypt.o
|
|||
ifeq ($(CONFIG_DM_UEVENT),y)
|
||||
dm-mod-objs += dm-uevent.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_DM_VERITY_FEC),y)
|
||||
dm-verity-objs += dm-verity-fec.o
|
||||
endif
|
||||
|
|
|
@ -118,14 +118,12 @@ static void iot_io_end(struct io_tracker *iot, sector_t len)
|
|||
*/
|
||||
struct dm_hook_info {
|
||||
bio_end_io_t *bi_end_io;
|
||||
void *bi_private;
|
||||
};
|
||||
|
||||
static void dm_hook_bio(struct dm_hook_info *h, struct bio *bio,
|
||||
bio_end_io_t *bi_end_io, void *bi_private)
|
||||
{
|
||||
h->bi_end_io = bio->bi_end_io;
|
||||
h->bi_private = bio->bi_private;
|
||||
|
||||
bio->bi_end_io = bi_end_io;
|
||||
bio->bi_private = bi_private;
|
||||
|
@ -134,7 +132,6 @@ static void dm_hook_bio(struct dm_hook_info *h, struct bio *bio,
|
|||
static void dm_unhook_bio(struct dm_hook_info *h, struct bio *bio)
|
||||
{
|
||||
bio->bi_end_io = h->bi_end_io;
|
||||
bio->bi_private = h->bi_private;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
|
|
@ -1864,16 +1864,24 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
|
|||
}
|
||||
|
||||
ret = -ENOMEM;
|
||||
cc->io_queue = alloc_workqueue("kcryptd_io", WQ_MEM_RECLAIM, 1);
|
||||
cc->io_queue = alloc_workqueue("kcryptd_io",
|
||||
WQ_HIGHPRI |
|
||||
WQ_MEM_RECLAIM,
|
||||
1);
|
||||
if (!cc->io_queue) {
|
||||
ti->error = "Couldn't create kcryptd io queue";
|
||||
goto bad;
|
||||
}
|
||||
|
||||
if (test_bit(DM_CRYPT_SAME_CPU, &cc->flags))
|
||||
cc->crypt_queue = alloc_workqueue("kcryptd", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM, 1);
|
||||
cc->crypt_queue = alloc_workqueue("kcryptd",
|
||||
WQ_HIGHPRI |
|
||||
WQ_MEM_RECLAIM, 1);
|
||||
else
|
||||
cc->crypt_queue = alloc_workqueue("kcryptd", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM | WQ_UNBOUND,
|
||||
cc->crypt_queue = alloc_workqueue("kcryptd",
|
||||
WQ_HIGHPRI |
|
||||
WQ_MEM_RECLAIM |
|
||||
WQ_UNBOUND,
|
||||
num_online_cpus());
|
||||
if (!cc->crypt_queue) {
|
||||
ti->error = "Couldn't create kcryptd queue";
|
||||
|
|
|
@ -207,7 +207,6 @@ struct dm_snap_pending_exception {
|
|||
*/
|
||||
struct bio *full_bio;
|
||||
bio_end_io_t *full_bio_end_io;
|
||||
void *full_bio_private;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -1495,10 +1494,8 @@ out:
|
|||
snapshot_bios = bio_list_get(&pe->snapshot_bios);
|
||||
origin_bios = bio_list_get(&pe->origin_bios);
|
||||
full_bio = pe->full_bio;
|
||||
if (full_bio) {
|
||||
if (full_bio)
|
||||
full_bio->bi_end_io = pe->full_bio_end_io;
|
||||
full_bio->bi_private = pe->full_bio_private;
|
||||
}
|
||||
increment_pending_exceptions_done_count();
|
||||
|
||||
up_write(&s->lock);
|
||||
|
@ -1604,7 +1601,6 @@ static void start_full_bio(struct dm_snap_pending_exception *pe,
|
|||
|
||||
pe->full_bio = bio;
|
||||
pe->full_bio_end_io = bio->bi_end_io;
|
||||
pe->full_bio_private = bio->bi_private;
|
||||
|
||||
callback_data = dm_kcopyd_prepare_callback(s->kcopyd_client,
|
||||
copy_callback, pe);
|
||||
|
|
861
drivers/md/dm-verity-fec.c
Normal file
861
drivers/md/dm-verity-fec.c
Normal file
|
@ -0,0 +1,861 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Google, Inc.
|
||||
*
|
||||
* Author: Sami Tolvanen <samitolvanen@google.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#include "dm-verity-fec.h"
|
||||
#include <linux/math64.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#define DM_MSG_PREFIX "verity-fec"
|
||||
|
||||
/*
|
||||
* If error correction has been configured, returns true.
|
||||
*/
|
||||
bool verity_fec_is_enabled(struct dm_verity *v)
|
||||
{
|
||||
return v->fec && v->fec->dev;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a pointer to dm_verity_fec_io after dm_verity_io and its variable
|
||||
* length fields.
|
||||
*/
|
||||
static inline struct dm_verity_fec_io *fec_io(struct dm_verity_io *io)
|
||||
{
|
||||
return (struct dm_verity_fec_io *) verity_io_digest_end(io->v, io);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an interleaved offset for a byte in RS block.
|
||||
*/
|
||||
static inline u64 fec_interleave(struct dm_verity *v, u64 offset)
|
||||
{
|
||||
u32 mod;
|
||||
|
||||
mod = do_div(offset, v->fec->rsn);
|
||||
return offset + mod * (v->fec->rounds << v->data_dev_block_bits);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode an RS block using Reed-Solomon.
|
||||
*/
|
||||
static int fec_decode_rs8(struct dm_verity *v, struct dm_verity_fec_io *fio,
|
||||
u8 *data, u8 *fec, int neras)
|
||||
{
|
||||
int i;
|
||||
uint16_t par[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN];
|
||||
|
||||
for (i = 0; i < v->fec->roots; i++)
|
||||
par[i] = fec[i];
|
||||
|
||||
return decode_rs8(fio->rs, data, par, v->fec->rsn, NULL, neras,
|
||||
fio->erasures, 0, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read error-correcting codes for the requested RS block. Returns a pointer
|
||||
* to the data block. Caller is responsible for releasing buf.
|
||||
*/
|
||||
static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index,
|
||||
unsigned *offset, struct dm_buffer **buf)
|
||||
{
|
||||
u64 position, block;
|
||||
u8 *res;
|
||||
|
||||
position = (index + rsb) * v->fec->roots;
|
||||
block = position >> v->data_dev_block_bits;
|
||||
*offset = (unsigned)(position - (block << v->data_dev_block_bits));
|
||||
|
||||
res = dm_bufio_read(v->fec->bufio, v->fec->start + block, buf);
|
||||
if (unlikely(IS_ERR(res))) {
|
||||
DMERR("%s: FEC %llu: parity read failed (block %llu): %ld",
|
||||
v->data_dev->name, (unsigned long long)rsb,
|
||||
(unsigned long long)(v->fec->start + block),
|
||||
PTR_ERR(res));
|
||||
*buf = NULL;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Loop over each preallocated buffer slot. */
|
||||
#define fec_for_each_prealloc_buffer(__i) \
|
||||
for (__i = 0; __i < DM_VERITY_FEC_BUF_PREALLOC; __i++)
|
||||
|
||||
/* Loop over each extra buffer slot. */
|
||||
#define fec_for_each_extra_buffer(io, __i) \
|
||||
for (__i = DM_VERITY_FEC_BUF_PREALLOC; __i < DM_VERITY_FEC_BUF_MAX; __i++)
|
||||
|
||||
/* Loop over each allocated buffer. */
|
||||
#define fec_for_each_buffer(io, __i) \
|
||||
for (__i = 0; __i < (io)->nbufs; __i++)
|
||||
|
||||
/* Loop over each RS block in each allocated buffer. */
|
||||
#define fec_for_each_buffer_rs_block(io, __i, __j) \
|
||||
fec_for_each_buffer(io, __i) \
|
||||
for (__j = 0; __j < 1 << DM_VERITY_FEC_BUF_RS_BITS; __j++)
|
||||
|
||||
/*
|
||||
* Return a pointer to the current RS block when called inside
|
||||
* fec_for_each_buffer_rs_block.
|
||||
*/
|
||||
static inline u8 *fec_buffer_rs_block(struct dm_verity *v,
|
||||
struct dm_verity_fec_io *fio,
|
||||
unsigned i, unsigned j)
|
||||
{
|
||||
return &fio->bufs[i][j * v->fec->rsn];
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an index to the current RS block when called inside
|
||||
* fec_for_each_buffer_rs_block.
|
||||
*/
|
||||
static inline unsigned fec_buffer_rs_index(unsigned i, unsigned j)
|
||||
{
|
||||
return (i << DM_VERITY_FEC_BUF_RS_BITS) + j;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode all RS blocks from buffers and copy corrected bytes into fio->output
|
||||
* starting from block_offset.
|
||||
*/
|
||||
static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio,
|
||||
u64 rsb, int byte_index, unsigned block_offset,
|
||||
int neras)
|
||||
{
|
||||
int r, corrected = 0, res;
|
||||
struct dm_buffer *buf;
|
||||
unsigned n, i, offset;
|
||||
u8 *par, *block;
|
||||
|
||||
par = fec_read_parity(v, rsb, block_offset, &offset, &buf);
|
||||
if (IS_ERR(par))
|
||||
return PTR_ERR(par);
|
||||
|
||||
/*
|
||||
* Decode the RS blocks we have in bufs. Each RS block results in
|
||||
* one corrected target byte and consumes fec->roots parity bytes.
|
||||
*/
|
||||
fec_for_each_buffer_rs_block(fio, n, i) {
|
||||
block = fec_buffer_rs_block(v, fio, n, i);
|
||||
res = fec_decode_rs8(v, fio, block, &par[offset], neras);
|
||||
if (res < 0) {
|
||||
dm_bufio_release(buf);
|
||||
|
||||
r = res;
|
||||
goto error;
|
||||
}
|
||||
|
||||
corrected += res;
|
||||
fio->output[block_offset] = block[byte_index];
|
||||
|
||||
block_offset++;
|
||||
if (block_offset >= 1 << v->data_dev_block_bits)
|
||||
goto done;
|
||||
|
||||
/* read the next block when we run out of parity bytes */
|
||||
offset += v->fec->roots;
|
||||
if (offset >= 1 << v->data_dev_block_bits) {
|
||||
dm_bufio_release(buf);
|
||||
|
||||
par = fec_read_parity(v, rsb, block_offset, &offset, &buf);
|
||||
if (unlikely(IS_ERR(par)))
|
||||
return PTR_ERR(par);
|
||||
}
|
||||
}
|
||||
done:
|
||||
r = corrected;
|
||||
error:
|
||||
if (r < 0 && neras)
|
||||
DMERR_LIMIT("%s: FEC %llu: failed to correct: %d",
|
||||
v->data_dev->name, (unsigned long long)rsb, r);
|
||||
else if (r > 0) {
|
||||
DMWARN_LIMIT("%s: FEC %llu: corrected %d errors",
|
||||
v->data_dev->name, (unsigned long long)rsb, r);
|
||||
atomic_add_unless(&v->fec->corrected, 1, INT_MAX);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate data block erasures using verity hashes.
|
||||
*/
|
||||
static int fec_is_erasure(struct dm_verity *v, struct dm_verity_io *io,
|
||||
u8 *want_digest, u8 *data)
|
||||
{
|
||||
if (unlikely(verity_hash(v, verity_io_hash_desc(v, io),
|
||||
data, 1 << v->data_dev_block_bits,
|
||||
verity_io_real_digest(v, io))))
|
||||
return 0;
|
||||
|
||||
return memcmp(verity_io_real_digest(v, io), want_digest,
|
||||
v->digest_size) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read data blocks that are part of the RS block and deinterleave as much as
|
||||
* fits into buffers. Check for erasure locations if @neras is non-NULL.
|
||||
*/
|
||||
static int fec_read_bufs(struct dm_verity *v, struct dm_verity_io *io,
|
||||
u64 rsb, u64 target, unsigned block_offset,
|
||||
int *neras)
|
||||
{
|
||||
bool is_zero;
|
||||
int i, j, target_index = -1;
|
||||
struct dm_buffer *buf;
|
||||
struct dm_bufio_client *bufio;
|
||||
struct dm_verity_fec_io *fio = fec_io(io);
|
||||
u64 block, ileaved;
|
||||
u8 *bbuf, *rs_block;
|
||||
u8 want_digest[v->digest_size];
|
||||
unsigned n, k;
|
||||
|
||||
if (neras)
|
||||
*neras = 0;
|
||||
|
||||
/*
|
||||
* read each of the rsn data blocks that are part of the RS block, and
|
||||
* interleave contents to available bufs
|
||||
*/
|
||||
for (i = 0; i < v->fec->rsn; i++) {
|
||||
ileaved = fec_interleave(v, rsb * v->fec->rsn + i);
|
||||
|
||||
/*
|
||||
* target is the data block we want to correct, target_index is
|
||||
* the index of this block within the rsn RS blocks
|
||||
*/
|
||||
if (ileaved == target)
|
||||
target_index = i;
|
||||
|
||||
block = ileaved >> v->data_dev_block_bits;
|
||||
bufio = v->fec->data_bufio;
|
||||
|
||||
if (block >= v->data_blocks) {
|
||||
block -= v->data_blocks;
|
||||
|
||||
/*
|
||||
* blocks outside the area were assumed to contain
|
||||
* zeros when encoding data was generated
|
||||
*/
|
||||
if (unlikely(block >= v->fec->hash_blocks))
|
||||
continue;
|
||||
|
||||
block += v->hash_start;
|
||||
bufio = v->bufio;
|
||||
}
|
||||
|
||||
bbuf = dm_bufio_read(bufio, block, &buf);
|
||||
if (unlikely(IS_ERR(bbuf))) {
|
||||
DMWARN_LIMIT("%s: FEC %llu: read failed (%llu): %ld",
|
||||
v->data_dev->name,
|
||||
(unsigned long long)rsb,
|
||||
(unsigned long long)block, PTR_ERR(bbuf));
|
||||
|
||||
/* assume the block is corrupted */
|
||||
if (neras && *neras <= v->fec->roots)
|
||||
fio->erasures[(*neras)++] = i;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* locate erasures if the block is on the data device */
|
||||
if (bufio == v->fec->data_bufio &&
|
||||
verity_hash_for_block(v, io, block, want_digest,
|
||||
&is_zero) == 0) {
|
||||
/* skip known zero blocks entirely */
|
||||
if (is_zero)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* skip if we have already found the theoretical
|
||||
* maximum number (i.e. fec->roots) of erasures
|
||||
*/
|
||||
if (neras && *neras <= v->fec->roots &&
|
||||
fec_is_erasure(v, io, want_digest, bbuf))
|
||||
fio->erasures[(*neras)++] = i;
|
||||
}
|
||||
|
||||
/*
|
||||
* deinterleave and copy the bytes that fit into bufs,
|
||||
* starting from block_offset
|
||||
*/
|
||||
fec_for_each_buffer_rs_block(fio, n, j) {
|
||||
k = fec_buffer_rs_index(n, j) + block_offset;
|
||||
|
||||
if (k >= 1 << v->data_dev_block_bits)
|
||||
goto done;
|
||||
|
||||
rs_block = fec_buffer_rs_block(v, fio, n, j);
|
||||
rs_block[i] = bbuf[k];
|
||||
}
|
||||
done:
|
||||
dm_bufio_release(buf);
|
||||
}
|
||||
|
||||
return target_index;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate RS control structure and FEC buffers from preallocated mempools,
|
||||
* and attempt to allocate as many extra buffers as available.
|
||||
*/
|
||||
static int fec_alloc_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio)
|
||||
{
|
||||
unsigned n;
|
||||
|
||||
if (!fio->rs) {
|
||||
fio->rs = mempool_alloc(v->fec->rs_pool, 0);
|
||||
if (unlikely(!fio->rs)) {
|
||||
DMERR("failed to allocate RS");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
fec_for_each_prealloc_buffer(n) {
|
||||
if (fio->bufs[n])
|
||||
continue;
|
||||
|
||||
fio->bufs[n] = mempool_alloc(v->fec->prealloc_pool, GFP_NOIO);
|
||||
if (unlikely(!fio->bufs[n])) {
|
||||
DMERR("failed to allocate FEC buffer");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
/* try to allocate the maximum number of buffers */
|
||||
fec_for_each_extra_buffer(fio, n) {
|
||||
if (fio->bufs[n])
|
||||
continue;
|
||||
|
||||
fio->bufs[n] = mempool_alloc(v->fec->extra_pool, GFP_NOIO);
|
||||
/* we can manage with even one buffer if necessary */
|
||||
if (unlikely(!fio->bufs[n]))
|
||||
break;
|
||||
}
|
||||
fio->nbufs = n;
|
||||
|
||||
if (!fio->output) {
|
||||
fio->output = mempool_alloc(v->fec->output_pool, GFP_NOIO);
|
||||
|
||||
if (!fio->output) {
|
||||
DMERR("failed to allocate FEC page");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize buffers and clear erasures. fec_read_bufs() assumes buffers are
|
||||
* zeroed before deinterleaving.
|
||||
*/
|
||||
static void fec_init_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio)
|
||||
{
|
||||
unsigned n;
|
||||
|
||||
fec_for_each_buffer(fio, n)
|
||||
memset(fio->bufs[n], 0, v->fec->rsn << DM_VERITY_FEC_BUF_RS_BITS);
|
||||
|
||||
memset(fio->erasures, 0, sizeof(fio->erasures));
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode all RS blocks in a single data block and return the target block
|
||||
* (indicated by @offset) in fio->output. If @use_erasures is non-zero, uses
|
||||
* hashes to locate erasures.
|
||||
*/
|
||||
static int fec_decode_rsb(struct dm_verity *v, struct dm_verity_io *io,
|
||||
struct dm_verity_fec_io *fio, u64 rsb, u64 offset,
|
||||
bool use_erasures)
|
||||
{
|
||||
int r, neras = 0;
|
||||
unsigned pos;
|
||||
|
||||
r = fec_alloc_bufs(v, fio);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
for (pos = 0; pos < 1 << v->data_dev_block_bits; ) {
|
||||
fec_init_bufs(v, fio);
|
||||
|
||||
r = fec_read_bufs(v, io, rsb, offset, pos,
|
||||
use_erasures ? &neras : NULL);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
r = fec_decode_bufs(v, fio, rsb, r, pos, neras);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pos += fio->nbufs << DM_VERITY_FEC_BUF_RS_BITS;
|
||||
}
|
||||
|
||||
/* Always re-validate the corrected block against the expected hash */
|
||||
r = verity_hash(v, verity_io_hash_desc(v, io), fio->output,
|
||||
1 << v->data_dev_block_bits,
|
||||
verity_io_real_digest(v, io));
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
if (memcmp(verity_io_real_digest(v, io), verity_io_want_digest(v, io),
|
||||
v->digest_size)) {
|
||||
DMERR_LIMIT("%s: FEC %llu: failed to correct (%d erasures)",
|
||||
v->data_dev->name, (unsigned long long)rsb, neras);
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fec_bv_copy(struct dm_verity *v, struct dm_verity_io *io, u8 *data,
|
||||
size_t len)
|
||||
{
|
||||
struct dm_verity_fec_io *fio = fec_io(io);
|
||||
|
||||
memcpy(data, &fio->output[fio->output_pos], len);
|
||||
fio->output_pos += len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Correct errors in a block. Copies corrected block to dest if non-NULL,
|
||||
* otherwise to a bio_vec starting from iter.
|
||||
*/
|
||||
int verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io,
|
||||
enum verity_block_type type, sector_t block, u8 *dest,
|
||||
struct bvec_iter *iter)
|
||||
{
|
||||
int r;
|
||||
struct dm_verity_fec_io *fio = fec_io(io);
|
||||
u64 offset, res, rsb;
|
||||
|
||||
if (!verity_fec_is_enabled(v))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (type == DM_VERITY_BLOCK_TYPE_METADATA)
|
||||
block += v->data_blocks;
|
||||
|
||||
/*
|
||||
* For RS(M, N), the continuous FEC data is divided into blocks of N
|
||||
* bytes. Since block size may not be divisible by N, the last block
|
||||
* is zero padded when decoding.
|
||||
*
|
||||
* Each byte of the block is covered by a different RS(M, N) code,
|
||||
* and each code is interleaved over N blocks to make it less likely
|
||||
* that bursty corruption will leave us in unrecoverable state.
|
||||
*/
|
||||
|
||||
offset = block << v->data_dev_block_bits;
|
||||
|
||||
res = offset;
|
||||
div64_u64(res, v->fec->rounds << v->data_dev_block_bits);
|
||||
|
||||
/*
|
||||
* The base RS block we can feed to the interleaver to find out all
|
||||
* blocks required for decoding.
|
||||
*/
|
||||
rsb = offset - res * (v->fec->rounds << v->data_dev_block_bits);
|
||||
|
||||
/*
|
||||
* Locating erasures is slow, so attempt to recover the block without
|
||||
* them first. Do a second attempt with erasures if the corruption is
|
||||
* bad enough.
|
||||
*/
|
||||
r = fec_decode_rsb(v, io, fio, rsb, offset, false);
|
||||
if (r < 0) {
|
||||
r = fec_decode_rsb(v, io, fio, rsb, offset, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (dest)
|
||||
memcpy(dest, fio->output, 1 << v->data_dev_block_bits);
|
||||
else if (iter) {
|
||||
fio->output_pos = 0;
|
||||
r = verity_for_bv_block(v, io, iter, fec_bv_copy);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up per-bio data.
|
||||
*/
|
||||
void verity_fec_finish_io(struct dm_verity_io *io)
|
||||
{
|
||||
unsigned n;
|
||||
struct dm_verity_fec *f = io->v->fec;
|
||||
struct dm_verity_fec_io *fio = fec_io(io);
|
||||
|
||||
if (!verity_fec_is_enabled(io->v))
|
||||
return;
|
||||
|
||||
mempool_free(fio->rs, f->rs_pool);
|
||||
|
||||
fec_for_each_prealloc_buffer(n)
|
||||
mempool_free(fio->bufs[n], f->prealloc_pool);
|
||||
|
||||
fec_for_each_extra_buffer(fio, n)
|
||||
mempool_free(fio->bufs[n], f->extra_pool);
|
||||
|
||||
mempool_free(fio->output, f->output_pool);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize per-bio data.
|
||||
*/
|
||||
void verity_fec_init_io(struct dm_verity_io *io)
|
||||
{
|
||||
struct dm_verity_fec_io *fio = fec_io(io);
|
||||
|
||||
if (!verity_fec_is_enabled(io->v))
|
||||
return;
|
||||
|
||||
fio->rs = NULL;
|
||||
memset(fio->bufs, 0, sizeof(fio->bufs));
|
||||
fio->nbufs = 0;
|
||||
fio->output = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Append feature arguments and values to the status table.
|
||||
*/
|
||||
unsigned verity_fec_status_table(struct dm_verity *v, unsigned sz,
|
||||
char *result, unsigned maxlen)
|
||||
{
|
||||
if (!verity_fec_is_enabled(v))
|
||||
return sz;
|
||||
|
||||
DMEMIT(" " DM_VERITY_OPT_FEC_DEV " %s "
|
||||
DM_VERITY_OPT_FEC_BLOCKS " %llu "
|
||||
DM_VERITY_OPT_FEC_START " %llu "
|
||||
DM_VERITY_OPT_FEC_ROOTS " %d",
|
||||
v->fec->dev->name,
|
||||
(unsigned long long)v->fec->blocks,
|
||||
(unsigned long long)v->fec->start,
|
||||
v->fec->roots);
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
void verity_fec_dtr(struct dm_verity *v)
|
||||
{
|
||||
struct dm_verity_fec *f = v->fec;
|
||||
struct kobject *kobj = &f->kobj_holder.kobj;
|
||||
|
||||
if (!verity_fec_is_enabled(v))
|
||||
goto out;
|
||||
|
||||
mempool_destroy(f->rs_pool);
|
||||
mempool_destroy(f->prealloc_pool);
|
||||
mempool_destroy(f->extra_pool);
|
||||
kmem_cache_destroy(f->cache);
|
||||
|
||||
if (f->data_bufio)
|
||||
dm_bufio_client_destroy(f->data_bufio);
|
||||
if (f->bufio)
|
||||
dm_bufio_client_destroy(f->bufio);
|
||||
|
||||
if (f->dev)
|
||||
dm_put_device(v->ti, f->dev);
|
||||
|
||||
if (kobj->state_initialized) {
|
||||
kobject_put(kobj);
|
||||
wait_for_completion(dm_get_completion_from_kobject(kobj));
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(f);
|
||||
v->fec = NULL;
|
||||
}
|
||||
|
||||
static void *fec_rs_alloc(gfp_t gfp_mask, void *pool_data)
|
||||
{
|
||||
struct dm_verity *v = (struct dm_verity *)pool_data;
|
||||
|
||||
return init_rs(8, 0x11d, 0, 1, v->fec->roots);
|
||||
}
|
||||
|
||||
static void fec_rs_free(void *element, void *pool_data)
|
||||
{
|
||||
struct rs_control *rs = (struct rs_control *)element;
|
||||
|
||||
if (rs)
|
||||
free_rs(rs);
|
||||
}
|
||||
|
||||
bool verity_is_fec_opt_arg(const char *arg_name)
|
||||
{
|
||||
return (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_DEV) ||
|
||||
!strcasecmp(arg_name, DM_VERITY_OPT_FEC_BLOCKS) ||
|
||||
!strcasecmp(arg_name, DM_VERITY_OPT_FEC_START) ||
|
||||
!strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS));
|
||||
}
|
||||
|
||||
int verity_fec_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v,
|
||||
unsigned *argc, const char *arg_name)
|
||||
{
|
||||
int r;
|
||||
struct dm_target *ti = v->ti;
|
||||
const char *arg_value;
|
||||
unsigned long long num_ll;
|
||||
unsigned char num_c;
|
||||
char dummy;
|
||||
|
||||
if (!*argc) {
|
||||
ti->error = "FEC feature arguments require a value";
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
arg_value = dm_shift_arg(as);
|
||||
(*argc)--;
|
||||
|
||||
if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_DEV)) {
|
||||
r = dm_get_device(ti, arg_value, FMODE_READ, &v->fec->dev);
|
||||
if (r) {
|
||||
ti->error = "FEC device lookup failed";
|
||||
return r;
|
||||
}
|
||||
|
||||
} else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_BLOCKS)) {
|
||||
if (sscanf(arg_value, "%llu%c", &num_ll, &dummy) != 1 ||
|
||||
((sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT))
|
||||
>> (v->data_dev_block_bits - SECTOR_SHIFT) != num_ll)) {
|
||||
ti->error = "Invalid " DM_VERITY_OPT_FEC_BLOCKS;
|
||||
return -EINVAL;
|
||||
}
|
||||
v->fec->blocks = num_ll;
|
||||
|
||||
} else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_START)) {
|
||||
if (sscanf(arg_value, "%llu%c", &num_ll, &dummy) != 1 ||
|
||||
((sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT)) >>
|
||||
(v->data_dev_block_bits - SECTOR_SHIFT) != num_ll)) {
|
||||
ti->error = "Invalid " DM_VERITY_OPT_FEC_START;
|
||||
return -EINVAL;
|
||||
}
|
||||
v->fec->start = num_ll;
|
||||
|
||||
} else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS)) {
|
||||
if (sscanf(arg_value, "%hhu%c", &num_c, &dummy) != 1 || !num_c ||
|
||||
num_c < (DM_VERITY_FEC_RSM - DM_VERITY_FEC_MAX_RSN) ||
|
||||
num_c > (DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN)) {
|
||||
ti->error = "Invalid " DM_VERITY_OPT_FEC_ROOTS;
|
||||
return -EINVAL;
|
||||
}
|
||||
v->fec->roots = num_c;
|
||||
|
||||
} else {
|
||||
ti->error = "Unrecognized verity FEC feature request";
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t corrected_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct dm_verity_fec *f = container_of(kobj, struct dm_verity_fec,
|
||||
kobj_holder.kobj);
|
||||
|
||||
return sprintf(buf, "%d\n", atomic_read(&f->corrected));
|
||||
}
|
||||
|
||||
static struct kobj_attribute attr_corrected = __ATTR_RO(corrected);
|
||||
|
||||
static struct attribute *fec_attrs[] = {
|
||||
&attr_corrected.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct kobj_type fec_ktype = {
|
||||
.sysfs_ops = &kobj_sysfs_ops,
|
||||
.default_attrs = fec_attrs
|
||||
};
|
||||
|
||||
/*
|
||||
* Allocate dm_verity_fec for v->fec. Must be called before verity_fec_ctr.
|
||||
*/
|
||||
int verity_fec_ctr_alloc(struct dm_verity *v)
|
||||
{
|
||||
struct dm_verity_fec *f;
|
||||
|
||||
f = kzalloc(sizeof(struct dm_verity_fec), GFP_KERNEL);
|
||||
if (!f) {
|
||||
v->ti->error = "Cannot allocate FEC structure";
|
||||
return -ENOMEM;
|
||||
}
|
||||
v->fec = f;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate arguments and preallocate memory. Must be called after arguments
|
||||
* have been parsed using verity_fec_parse_opt_args.
|
||||
*/
|
||||
int verity_fec_ctr(struct dm_verity *v)
|
||||
{
|
||||
int r;
|
||||
struct dm_verity_fec *f = v->fec;
|
||||
struct dm_target *ti = v->ti;
|
||||
struct mapped_device *md = dm_table_get_md(ti->table);
|
||||
u64 hash_blocks;
|
||||
|
||||
if (!verity_fec_is_enabled(v)) {
|
||||
verity_fec_dtr(v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Create a kobject and sysfs attributes */
|
||||
init_completion(&f->kobj_holder.completion);
|
||||
|
||||
r = kobject_init_and_add(&f->kobj_holder.kobj, &fec_ktype,
|
||||
&disk_to_dev(dm_disk(md))->kobj, "%s", "fec");
|
||||
if (r) {
|
||||
ti->error = "Cannot create kobject";
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* FEC is computed over data blocks, possible metadata, and
|
||||
* hash blocks. In other words, FEC covers total of fec_blocks
|
||||
* blocks consisting of the following:
|
||||
*
|
||||
* data blocks | hash blocks | metadata (optional)
|
||||
*
|
||||
* We allow metadata after hash blocks to support a use case
|
||||
* where all data is stored on the same device and FEC covers
|
||||
* the entire area.
|
||||
*
|
||||
* If metadata is included, we require it to be available on the
|
||||
* hash device after the hash blocks.
|
||||
*/
|
||||
|
||||
hash_blocks = v->hash_blocks - v->hash_start;
|
||||
|
||||
/*
|
||||
* Require matching block sizes for data and hash devices for
|
||||
* simplicity.
|
||||
*/
|
||||
if (v->data_dev_block_bits != v->hash_dev_block_bits) {
|
||||
ti->error = "Block sizes must match to use FEC";
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!f->roots) {
|
||||
ti->error = "Missing " DM_VERITY_OPT_FEC_ROOTS;
|
||||
return -EINVAL;
|
||||
}
|
||||
f->rsn = DM_VERITY_FEC_RSM - f->roots;
|
||||
|
||||
if (!f->blocks) {
|
||||
ti->error = "Missing " DM_VERITY_OPT_FEC_BLOCKS;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
f->rounds = f->blocks;
|
||||
if (sector_div(f->rounds, f->rsn))
|
||||
f->rounds++;
|
||||
|
||||
/*
|
||||
* Due to optional metadata, f->blocks can be larger than
|
||||
* data_blocks and hash_blocks combined.
|
||||
*/
|
||||
if (f->blocks < v->data_blocks + hash_blocks || !f->rounds) {
|
||||
ti->error = "Invalid " DM_VERITY_OPT_FEC_BLOCKS;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Metadata is accessed through the hash device, so we require
|
||||
* it to be large enough.
|
||||
*/
|
||||
f->hash_blocks = f->blocks - v->data_blocks;
|
||||
if (dm_bufio_get_device_size(v->bufio) < f->hash_blocks) {
|
||||
ti->error = "Hash device is too small for "
|
||||
DM_VERITY_OPT_FEC_BLOCKS;
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
f->bufio = dm_bufio_client_create(f->dev->bdev,
|
||||
1 << v->data_dev_block_bits,
|
||||
1, 0, NULL, NULL);
|
||||
if (IS_ERR(f->bufio)) {
|
||||
ti->error = "Cannot initialize FEC bufio client";
|
||||
return PTR_ERR(f->bufio);
|
||||
}
|
||||
|
||||
if (dm_bufio_get_device_size(f->bufio) <
|
||||
((f->start + f->rounds * f->roots) >> v->data_dev_block_bits)) {
|
||||
ti->error = "FEC device is too small";
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
f->data_bufio = dm_bufio_client_create(v->data_dev->bdev,
|
||||
1 << v->data_dev_block_bits,
|
||||
1, 0, NULL, NULL);
|
||||
if (IS_ERR(f->data_bufio)) {
|
||||
ti->error = "Cannot initialize FEC data bufio client";
|
||||
return PTR_ERR(f->data_bufio);
|
||||
}
|
||||
|
||||
if (dm_bufio_get_device_size(f->data_bufio) < v->data_blocks) {
|
||||
ti->error = "Data device is too small";
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
/* Preallocate an rs_control structure for each worker thread */
|
||||
f->rs_pool = mempool_create(num_online_cpus(), fec_rs_alloc,
|
||||
fec_rs_free, (void *) v);
|
||||
if (!f->rs_pool) {
|
||||
ti->error = "Cannot allocate RS pool";
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
f->cache = kmem_cache_create("dm_verity_fec_buffers",
|
||||
f->rsn << DM_VERITY_FEC_BUF_RS_BITS,
|
||||
0, 0, NULL);
|
||||
if (!f->cache) {
|
||||
ti->error = "Cannot create FEC buffer cache";
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Preallocate DM_VERITY_FEC_BUF_PREALLOC buffers for each thread */
|
||||
f->prealloc_pool = mempool_create_slab_pool(num_online_cpus() *
|
||||
DM_VERITY_FEC_BUF_PREALLOC,
|
||||
f->cache);
|
||||
if (!f->prealloc_pool) {
|
||||
ti->error = "Cannot allocate FEC buffer prealloc pool";
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
f->extra_pool = mempool_create_slab_pool(0, f->cache);
|
||||
if (!f->extra_pool) {
|
||||
ti->error = "Cannot allocate FEC buffer extra pool";
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Preallocate an output buffer for each thread */
|
||||
f->output_pool = mempool_create_kmalloc_pool(num_online_cpus(),
|
||||
1 << v->data_dev_block_bits);
|
||||
if (!f->output_pool) {
|
||||
ti->error = "Cannot allocate FEC output pool";
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Reserve space for our per-bio data */
|
||||
ti->per_bio_data_size += sizeof(struct dm_verity_fec_io);
|
||||
|
||||
return 0;
|
||||
}
|
155
drivers/md/dm-verity-fec.h
Normal file
155
drivers/md/dm-verity-fec.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Google, Inc.
|
||||
*
|
||||
* Author: Sami Tolvanen <samitolvanen@google.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*/
|
||||
|
||||
#ifndef DM_VERITY_FEC_H
|
||||
#define DM_VERITY_FEC_H
|
||||
|
||||
#include "dm.h"
|
||||
#include "dm-verity.h"
|
||||
#include <linux/rslib.h>
|
||||
|
||||
/* Reed-Solomon(M, N) parameters */
|
||||
#define DM_VERITY_FEC_RSM 255
|
||||
#define DM_VERITY_FEC_MAX_RSN 253
|
||||
#define DM_VERITY_FEC_MIN_RSN 231 /* ~10% space overhead */
|
||||
|
||||
/* buffers for deinterleaving and decoding */
|
||||
#define DM_VERITY_FEC_BUF_PREALLOC 1 /* buffers to preallocate */
|
||||
#define DM_VERITY_FEC_BUF_RS_BITS 4 /* 1 << RS blocks per buffer */
|
||||
/* we need buffers for at most 1 << block size RS blocks */
|
||||
#define DM_VERITY_FEC_BUF_MAX \
|
||||
(1 << (PAGE_SHIFT - DM_VERITY_FEC_BUF_RS_BITS))
|
||||
|
||||
#define DM_VERITY_OPT_FEC_DEV "use_fec_from_device"
|
||||
#define DM_VERITY_OPT_FEC_BLOCKS "fec_blocks"
|
||||
#define DM_VERITY_OPT_FEC_START "fec_start"
|
||||
#define DM_VERITY_OPT_FEC_ROOTS "fec_roots"
|
||||
|
||||
/* configuration */
|
||||
struct dm_verity_fec {
|
||||
struct dm_dev *dev; /* parity data device */
|
||||
struct dm_bufio_client *data_bufio; /* for data dev access */
|
||||
struct dm_bufio_client *bufio; /* for parity data access */
|
||||
sector_t start; /* parity data start in blocks */
|
||||
sector_t blocks; /* number of blocks covered */
|
||||
sector_t rounds; /* number of interleaving rounds */
|
||||
sector_t hash_blocks; /* blocks covered after v->hash_start */
|
||||
unsigned char roots; /* number of parity bytes, M-N of RS(M, N) */
|
||||
unsigned char rsn; /* N of RS(M, N) */
|
||||
mempool_t *rs_pool; /* mempool for fio->rs */
|
||||
mempool_t *prealloc_pool; /* mempool for preallocated buffers */
|
||||
mempool_t *extra_pool; /* mempool for extra buffers */
|
||||
mempool_t *output_pool; /* mempool for output */
|
||||
struct kmem_cache *cache; /* cache for buffers */
|
||||
atomic_t corrected; /* corrected errors */
|
||||
struct dm_kobject_holder kobj_holder; /* for sysfs attributes */
|
||||
};
|
||||
|
||||
/* per-bio data */
|
||||
struct dm_verity_fec_io {
|
||||
struct rs_control *rs; /* Reed-Solomon state */
|
||||
int erasures[DM_VERITY_FEC_MAX_RSN]; /* erasures for decode_rs8 */
|
||||
u8 *bufs[DM_VERITY_FEC_BUF_MAX]; /* bufs for deinterleaving */
|
||||
unsigned nbufs; /* number of buffers allocated */
|
||||
u8 *output; /* buffer for corrected output */
|
||||
size_t output_pos;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DM_VERITY_FEC
|
||||
|
||||
/* each feature parameter requires a value */
|
||||
#define DM_VERITY_OPTS_FEC 8
|
||||
|
||||
extern bool verity_fec_is_enabled(struct dm_verity *v);
|
||||
|
||||
extern int verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io,
|
||||
enum verity_block_type type, sector_t block,
|
||||
u8 *dest, struct bvec_iter *iter);
|
||||
|
||||
extern unsigned verity_fec_status_table(struct dm_verity *v, unsigned sz,
|
||||
char *result, unsigned maxlen);
|
||||
|
||||
extern void verity_fec_finish_io(struct dm_verity_io *io);
|
||||
extern void verity_fec_init_io(struct dm_verity_io *io);
|
||||
|
||||
extern bool verity_is_fec_opt_arg(const char *arg_name);
|
||||
extern int verity_fec_parse_opt_args(struct dm_arg_set *as,
|
||||
struct dm_verity *v, unsigned *argc,
|
||||
const char *arg_name);
|
||||
|
||||
extern void verity_fec_dtr(struct dm_verity *v);
|
||||
|
||||
extern int verity_fec_ctr_alloc(struct dm_verity *v);
|
||||
extern int verity_fec_ctr(struct dm_verity *v);
|
||||
|
||||
#else /* !CONFIG_DM_VERITY_FEC */
|
||||
|
||||
#define DM_VERITY_OPTS_FEC 0
|
||||
|
||||
static inline bool verity_fec_is_enabled(struct dm_verity *v)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int verity_fec_decode(struct dm_verity *v,
|
||||
struct dm_verity_io *io,
|
||||
enum verity_block_type type,
|
||||
sector_t block, u8 *dest,
|
||||
struct bvec_iter *iter)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline unsigned verity_fec_status_table(struct dm_verity *v,
|
||||
unsigned sz, char *result,
|
||||
unsigned maxlen)
|
||||
{
|
||||
return sz;
|
||||
}
|
||||
|
||||
static inline void verity_fec_finish_io(struct dm_verity_io *io)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void verity_fec_init_io(struct dm_verity_io *io)
|
||||
{
|
||||
}
|
||||
|
||||
static inline bool verity_is_fec_opt_arg(const char *arg_name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int verity_fec_parse_opt_args(struct dm_arg_set *as,
|
||||
struct dm_verity *v,
|
||||
unsigned *argc,
|
||||
const char *arg_name)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline void verity_fec_dtr(struct dm_verity *v)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int verity_fec_ctr_alloc(struct dm_verity *v)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int verity_fec_ctr(struct dm_verity *v)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_DM_VERITY_FEC */
|
||||
|
||||
#endif /* DM_VERITY_FEC_H */
|
|
@ -14,12 +14,11 @@
|
|||
* access behavior.
|
||||
*/
|
||||
|
||||
#include "dm-bufio.h"
|
||||
#include "dm-verity.h"
|
||||
#include "dm-verity-fec.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device-mapper.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <crypto/hash.h>
|
||||
|
||||
#define DM_MSG_PREFIX "verity"
|
||||
|
||||
|
@ -28,83 +27,18 @@
|
|||
|
||||
#define DM_VERITY_DEFAULT_PREFETCH_SIZE 262144
|
||||
|
||||
#define DM_VERITY_MAX_LEVELS 63
|
||||
#define DM_VERITY_MAX_CORRUPTED_ERRS 100
|
||||
|
||||
#define DM_VERITY_OPT_LOGGING "ignore_corruption"
|
||||
#define DM_VERITY_OPT_RESTART "restart_on_corruption"
|
||||
#define DM_VERITY_OPT_IGN_ZEROES "ignore_zero_blocks"
|
||||
|
||||
#define DM_VERITY_OPTS_MAX (2 + DM_VERITY_OPTS_FEC)
|
||||
|
||||
static unsigned dm_verity_prefetch_cluster = DM_VERITY_DEFAULT_PREFETCH_SIZE;
|
||||
|
||||
module_param_named(prefetch_cluster, dm_verity_prefetch_cluster, uint, S_IRUGO | S_IWUSR);
|
||||
|
||||
enum verity_mode {
|
||||
DM_VERITY_MODE_EIO,
|
||||
DM_VERITY_MODE_LOGGING,
|
||||
DM_VERITY_MODE_RESTART
|
||||
};
|
||||
|
||||
enum verity_block_type {
|
||||
DM_VERITY_BLOCK_TYPE_DATA,
|
||||
DM_VERITY_BLOCK_TYPE_METADATA
|
||||
};
|
||||
|
||||
struct dm_verity {
|
||||
struct dm_dev *data_dev;
|
||||
struct dm_dev *hash_dev;
|
||||
struct dm_target *ti;
|
||||
struct dm_bufio_client *bufio;
|
||||
char *alg_name;
|
||||
struct crypto_shash *tfm;
|
||||
u8 *root_digest; /* digest of the root block */
|
||||
u8 *salt; /* salt: its size is salt_size */
|
||||
unsigned salt_size;
|
||||
sector_t data_start; /* data offset in 512-byte sectors */
|
||||
sector_t hash_start; /* hash start in blocks */
|
||||
sector_t data_blocks; /* the number of data blocks */
|
||||
sector_t hash_blocks; /* the number of hash blocks */
|
||||
unsigned char data_dev_block_bits; /* log2(data blocksize) */
|
||||
unsigned char hash_dev_block_bits; /* log2(hash blocksize) */
|
||||
unsigned char hash_per_block_bits; /* log2(hashes in hash block) */
|
||||
unsigned char levels; /* the number of tree levels */
|
||||
unsigned char version;
|
||||
unsigned digest_size; /* digest size for the current hash algorithm */
|
||||
unsigned shash_descsize;/* the size of temporary space for crypto */
|
||||
int hash_failed; /* set to 1 if hash of any block failed */
|
||||
enum verity_mode mode; /* mode for handling verification errors */
|
||||
unsigned corrupted_errs;/* Number of errors for corrupted blocks */
|
||||
|
||||
struct workqueue_struct *verify_wq;
|
||||
|
||||
/* starting blocks for each tree level. 0 is the lowest level. */
|
||||
sector_t hash_level_block[DM_VERITY_MAX_LEVELS];
|
||||
};
|
||||
|
||||
struct dm_verity_io {
|
||||
struct dm_verity *v;
|
||||
|
||||
/* original values of bio->bi_end_io and bio->bi_private */
|
||||
bio_end_io_t *orig_bi_end_io;
|
||||
void *orig_bi_private;
|
||||
|
||||
sector_t block;
|
||||
unsigned n_blocks;
|
||||
|
||||
struct bvec_iter iter;
|
||||
|
||||
struct work_struct work;
|
||||
|
||||
/*
|
||||
* Three variably-size fields follow this struct:
|
||||
*
|
||||
* u8 hash_desc[v->shash_descsize];
|
||||
* u8 real_digest[v->digest_size];
|
||||
* u8 want_digest[v->digest_size];
|
||||
*
|
||||
* To access them use: io_hash_desc(), io_real_digest() and io_want_digest().
|
||||
*/
|
||||
};
|
||||
|
||||
struct dm_verity_prefetch_work {
|
||||
struct work_struct work;
|
||||
struct dm_verity *v;
|
||||
|
@ -112,21 +46,6 @@ struct dm_verity_prefetch_work {
|
|||
unsigned n_blocks;
|
||||
};
|
||||
|
||||
static struct shash_desc *io_hash_desc(struct dm_verity *v, struct dm_verity_io *io)
|
||||
{
|
||||
return (struct shash_desc *)(io + 1);
|
||||
}
|
||||
|
||||
static u8 *io_real_digest(struct dm_verity *v, struct dm_verity_io *io)
|
||||
{
|
||||
return (u8 *)(io + 1) + v->shash_descsize;
|
||||
}
|
||||
|
||||
static u8 *io_want_digest(struct dm_verity *v, struct dm_verity_io *io)
|
||||
{
|
||||
return (u8 *)(io + 1) + v->shash_descsize + v->digest_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary structure appended to each dm-bufio buffer. If the value
|
||||
* hash_verified is nonzero, hash of the block has been verified.
|
||||
|
@ -173,6 +92,84 @@ static sector_t verity_position_at_level(struct dm_verity *v, sector_t block,
|
|||
return block >> (level * v->hash_per_block_bits);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper for crypto_shash_init, which handles verity salting.
|
||||
*/
|
||||
static int verity_hash_init(struct dm_verity *v, struct shash_desc *desc)
|
||||
{
|
||||
int r;
|
||||
|
||||
desc->tfm = v->tfm;
|
||||
desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
|
||||
|
||||
r = crypto_shash_init(desc);
|
||||
|
||||
if (unlikely(r < 0)) {
|
||||
DMERR("crypto_shash_init failed: %d", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
if (likely(v->version >= 1)) {
|
||||
r = crypto_shash_update(desc, v->salt, v->salt_size);
|
||||
|
||||
if (unlikely(r < 0)) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verity_hash_update(struct dm_verity *v, struct shash_desc *desc,
|
||||
const u8 *data, size_t len)
|
||||
{
|
||||
int r = crypto_shash_update(desc, data, len);
|
||||
|
||||
if (unlikely(r < 0))
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int verity_hash_final(struct dm_verity *v, struct shash_desc *desc,
|
||||
u8 *digest)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (unlikely(!v->version)) {
|
||||
r = crypto_shash_update(desc, v->salt, v->salt_size);
|
||||
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
r = crypto_shash_final(desc, digest);
|
||||
|
||||
if (unlikely(r < 0))
|
||||
DMERR("crypto_shash_final failed: %d", r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int verity_hash(struct dm_verity *v, struct shash_desc *desc,
|
||||
const u8 *data, size_t len, u8 *digest)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = verity_hash_init(v, desc);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
r = verity_hash_update(v, desc, data, len);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
return verity_hash_final(v, desc, digest);
|
||||
}
|
||||
|
||||
static void verity_hash_at_level(struct dm_verity *v, sector_t block, int level,
|
||||
sector_t *hash_block, unsigned *offset)
|
||||
{
|
||||
|
@ -246,17 +243,17 @@ out:
|
|||
* Verify hash of a metadata block pertaining to the specified data block
|
||||
* ("block" argument) at a specified level ("level" argument).
|
||||
*
|
||||
* On successful return, io_want_digest(v, io) contains the hash value for
|
||||
* a lower tree level or for the data block (if we're at the lowest leve).
|
||||
* On successful return, verity_io_want_digest(v, io) contains the hash value
|
||||
* for a lower tree level or for the data block (if we're at the lowest level).
|
||||
*
|
||||
* If "skip_unverified" is true, unverified buffer is skipped and 1 is returned.
|
||||
* If "skip_unverified" is false, unverified buffer is hashed and verified
|
||||
* against current value of io_want_digest(v, io).
|
||||
* against current value of verity_io_want_digest(v, io).
|
||||
*/
|
||||
static int verity_verify_level(struct dm_verity_io *io, sector_t block,
|
||||
int level, bool skip_unverified)
|
||||
static int verity_verify_level(struct dm_verity *v, struct dm_verity_io *io,
|
||||
sector_t block, int level, bool skip_unverified,
|
||||
u8 *want_digest)
|
||||
{
|
||||
struct dm_verity *v = io->v;
|
||||
struct dm_buffer *buf;
|
||||
struct buffer_aux *aux;
|
||||
u8 *data;
|
||||
|
@ -273,172 +270,185 @@ static int verity_verify_level(struct dm_verity_io *io, sector_t block,
|
|||
aux = dm_bufio_get_aux_data(buf);
|
||||
|
||||
if (!aux->hash_verified) {
|
||||
struct shash_desc *desc;
|
||||
u8 *result;
|
||||
|
||||
if (skip_unverified) {
|
||||
r = 1;
|
||||
goto release_ret_r;
|
||||
}
|
||||
|
||||
desc = io_hash_desc(v, io);
|
||||
desc->tfm = v->tfm;
|
||||
desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
|
||||
r = crypto_shash_init(desc);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_init failed: %d", r);
|
||||
r = verity_hash(v, verity_io_hash_desc(v, io),
|
||||
data, 1 << v->hash_dev_block_bits,
|
||||
verity_io_real_digest(v, io));
|
||||
if (unlikely(r < 0))
|
||||
goto release_ret_r;
|
||||
}
|
||||
|
||||
if (likely(v->version >= 1)) {
|
||||
r = crypto_shash_update(desc, v->salt, v->salt_size);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
goto release_ret_r;
|
||||
}
|
||||
}
|
||||
|
||||
r = crypto_shash_update(desc, data, 1 << v->hash_dev_block_bits);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
goto release_ret_r;
|
||||
}
|
||||
|
||||
if (!v->version) {
|
||||
r = crypto_shash_update(desc, v->salt, v->salt_size);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
goto release_ret_r;
|
||||
}
|
||||
}
|
||||
|
||||
result = io_real_digest(v, io);
|
||||
r = crypto_shash_final(desc, result);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_final failed: %d", r);
|
||||
goto release_ret_r;
|
||||
}
|
||||
if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) {
|
||||
if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_METADATA,
|
||||
hash_block)) {
|
||||
r = -EIO;
|
||||
goto release_ret_r;
|
||||
}
|
||||
} else
|
||||
if (likely(memcmp(verity_io_real_digest(v, io), want_digest,
|
||||
v->digest_size) == 0))
|
||||
aux->hash_verified = 1;
|
||||
else if (verity_fec_decode(v, io,
|
||||
DM_VERITY_BLOCK_TYPE_METADATA,
|
||||
hash_block, data, NULL) == 0)
|
||||
aux->hash_verified = 1;
|
||||
else if (verity_handle_err(v,
|
||||
DM_VERITY_BLOCK_TYPE_METADATA,
|
||||
hash_block)) {
|
||||
r = -EIO;
|
||||
goto release_ret_r;
|
||||
}
|
||||
}
|
||||
|
||||
data += offset;
|
||||
|
||||
memcpy(io_want_digest(v, io), data, v->digest_size);
|
||||
|
||||
dm_bufio_release(buf);
|
||||
return 0;
|
||||
memcpy(want_digest, data, v->digest_size);
|
||||
r = 0;
|
||||
|
||||
release_ret_r:
|
||||
dm_bufio_release(buf);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find a hash for a given block, write it to digest and verify the integrity
|
||||
* of the hash tree if necessary.
|
||||
*/
|
||||
int verity_hash_for_block(struct dm_verity *v, struct dm_verity_io *io,
|
||||
sector_t block, u8 *digest, bool *is_zero)
|
||||
{
|
||||
int r = 0, i;
|
||||
|
||||
if (likely(v->levels)) {
|
||||
/*
|
||||
* First, we try to get the requested hash for
|
||||
* the current block. If the hash block itself is
|
||||
* verified, zero is returned. If it isn't, this
|
||||
* function returns 1 and we fall back to whole
|
||||
* chain verification.
|
||||
*/
|
||||
r = verity_verify_level(v, io, block, 0, true, digest);
|
||||
if (likely(r <= 0))
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(digest, v->root_digest, v->digest_size);
|
||||
|
||||
for (i = v->levels - 1; i >= 0; i--) {
|
||||
r = verity_verify_level(v, io, block, i, false, digest);
|
||||
if (unlikely(r))
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
if (!r && v->zero_digest)
|
||||
*is_zero = !memcmp(v->zero_digest, digest, v->digest_size);
|
||||
else
|
||||
*is_zero = false;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls function process for 1 << v->data_dev_block_bits bytes in the bio_vec
|
||||
* starting from iter.
|
||||
*/
|
||||
int verity_for_bv_block(struct dm_verity *v, struct dm_verity_io *io,
|
||||
struct bvec_iter *iter,
|
||||
int (*process)(struct dm_verity *v,
|
||||
struct dm_verity_io *io, u8 *data,
|
||||
size_t len))
|
||||
{
|
||||
unsigned todo = 1 << v->data_dev_block_bits;
|
||||
struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_bio_data_size);
|
||||
|
||||
do {
|
||||
int r;
|
||||
u8 *page;
|
||||
unsigned len;
|
||||
struct bio_vec bv = bio_iter_iovec(bio, *iter);
|
||||
|
||||
page = kmap_atomic(bv.bv_page);
|
||||
len = bv.bv_len;
|
||||
|
||||
if (likely(len >= todo))
|
||||
len = todo;
|
||||
|
||||
r = process(v, io, page + bv.bv_offset, len);
|
||||
kunmap_atomic(page);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
bio_advance_iter(bio, iter, len);
|
||||
todo -= len;
|
||||
} while (todo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verity_bv_hash_update(struct dm_verity *v, struct dm_verity_io *io,
|
||||
u8 *data, size_t len)
|
||||
{
|
||||
return verity_hash_update(v, verity_io_hash_desc(v, io), data, len);
|
||||
}
|
||||
|
||||
static int verity_bv_zero(struct dm_verity *v, struct dm_verity_io *io,
|
||||
u8 *data, size_t len)
|
||||
{
|
||||
memset(data, 0, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify one "dm_verity_io" structure.
|
||||
*/
|
||||
static int verity_verify_io(struct dm_verity_io *io)
|
||||
{
|
||||
bool is_zero;
|
||||
struct dm_verity *v = io->v;
|
||||
struct bio *bio = dm_bio_from_per_bio_data(io,
|
||||
v->ti->per_bio_data_size);
|
||||
struct bvec_iter start;
|
||||
unsigned b;
|
||||
int i;
|
||||
|
||||
for (b = 0; b < io->n_blocks; b++) {
|
||||
struct shash_desc *desc;
|
||||
u8 *result;
|
||||
int r;
|
||||
unsigned todo;
|
||||
struct shash_desc *desc = verity_io_hash_desc(v, io);
|
||||
|
||||
if (likely(v->levels)) {
|
||||
r = verity_hash_for_block(v, io, io->block + b,
|
||||
verity_io_want_digest(v, io),
|
||||
&is_zero);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
if (is_zero) {
|
||||
/*
|
||||
* First, we try to get the requested hash for
|
||||
* the current block. If the hash block itself is
|
||||
* verified, zero is returned. If it isn't, this
|
||||
* function returns 0 and we fall back to whole
|
||||
* chain verification.
|
||||
* If we expect a zero block, don't validate, just
|
||||
* return zeros.
|
||||
*/
|
||||
int r = verity_verify_level(io, io->block + b, 0, true);
|
||||
if (likely(!r))
|
||||
goto test_block_hash;
|
||||
if (r < 0)
|
||||
r = verity_for_bv_block(v, io, &io->iter,
|
||||
verity_bv_zero);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(io_want_digest(v, io), v->root_digest, v->digest_size);
|
||||
|
||||
for (i = v->levels - 1; i >= 0; i--) {
|
||||
int r = verity_verify_level(io, io->block + b, i, false);
|
||||
if (unlikely(r))
|
||||
return r;
|
||||
}
|
||||
|
||||
test_block_hash:
|
||||
desc = io_hash_desc(v, io);
|
||||
desc->tfm = v->tfm;
|
||||
desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
|
||||
r = crypto_shash_init(desc);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_init failed: %d", r);
|
||||
r = verity_hash_init(v, desc);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
}
|
||||
|
||||
if (likely(v->version >= 1)) {
|
||||
r = crypto_shash_update(desc, v->salt, v->salt_size);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
todo = 1 << v->data_dev_block_bits;
|
||||
do {
|
||||
u8 *page;
|
||||
unsigned len;
|
||||
struct bio_vec bv = bio_iter_iovec(bio, io->iter);
|
||||
|
||||
page = kmap_atomic(bv.bv_page);
|
||||
len = bv.bv_len;
|
||||
if (likely(len >= todo))
|
||||
len = todo;
|
||||
r = crypto_shash_update(desc, page + bv.bv_offset, len);
|
||||
kunmap_atomic(page);
|
||||
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
bio_advance_iter(bio, &io->iter, len);
|
||||
todo -= len;
|
||||
} while (todo);
|
||||
|
||||
if (!v->version) {
|
||||
r = crypto_shash_update(desc, v->salt, v->salt_size);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_update failed: %d", r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
result = io_real_digest(v, io);
|
||||
r = crypto_shash_final(desc, result);
|
||||
if (r < 0) {
|
||||
DMERR("crypto_shash_final failed: %d", r);
|
||||
start = io->iter;
|
||||
r = verity_for_bv_block(v, io, &io->iter, verity_bv_hash_update);
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
}
|
||||
if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) {
|
||||
if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA,
|
||||
io->block + b))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
r = verity_hash_final(v, desc, verity_io_real_digest(v, io));
|
||||
if (unlikely(r < 0))
|
||||
return r;
|
||||
|
||||
if (likely(memcmp(verity_io_real_digest(v, io),
|
||||
verity_io_want_digest(v, io), v->digest_size) == 0))
|
||||
continue;
|
||||
else if (verity_fec_decode(v, io, DM_VERITY_BLOCK_TYPE_DATA,
|
||||
io->block + b, NULL, &start) == 0)
|
||||
continue;
|
||||
else if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA,
|
||||
io->block + b))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -453,9 +463,10 @@ static void verity_finish_io(struct dm_verity_io *io, int error)
|
|||
struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_bio_data_size);
|
||||
|
||||
bio->bi_end_io = io->orig_bi_end_io;
|
||||
bio->bi_private = io->orig_bi_private;
|
||||
bio->bi_error = error;
|
||||
|
||||
verity_fec_finish_io(io);
|
||||
|
||||
bio_endio(bio);
|
||||
}
|
||||
|
||||
|
@ -470,7 +481,7 @@ static void verity_end_io(struct bio *bio)
|
|||
{
|
||||
struct dm_verity_io *io = bio->bi_private;
|
||||
|
||||
if (bio->bi_error) {
|
||||
if (bio->bi_error && !verity_fec_is_enabled(io->v)) {
|
||||
verity_finish_io(io, bio->bi_error);
|
||||
return;
|
||||
}
|
||||
|
@ -566,7 +577,6 @@ static int verity_map(struct dm_target *ti, struct bio *bio)
|
|||
io = dm_per_bio_data(bio, ti->per_bio_data_size);
|
||||
io->v = v;
|
||||
io->orig_bi_end_io = bio->bi_end_io;
|
||||
io->orig_bi_private = bio->bi_private;
|
||||
io->block = bio->bi_iter.bi_sector >> (v->data_dev_block_bits - SECTOR_SHIFT);
|
||||
io->n_blocks = bio->bi_iter.bi_size >> v->data_dev_block_bits;
|
||||
|
||||
|
@ -574,6 +584,8 @@ static int verity_map(struct dm_target *ti, struct bio *bio)
|
|||
bio->bi_private = io;
|
||||
io->iter = bio->bi_iter;
|
||||
|
||||
verity_fec_init_io(io);
|
||||
|
||||
verity_submit_prefetch(v, io);
|
||||
|
||||
generic_make_request(bio);
|
||||
|
@ -588,6 +600,7 @@ static void verity_status(struct dm_target *ti, status_type_t type,
|
|||
unsigned status_flags, char *result, unsigned maxlen)
|
||||
{
|
||||
struct dm_verity *v = ti->private;
|
||||
unsigned args = 0;
|
||||
unsigned sz = 0;
|
||||
unsigned x;
|
||||
|
||||
|
@ -614,8 +627,17 @@ static void verity_status(struct dm_target *ti, status_type_t type,
|
|||
else
|
||||
for (x = 0; x < v->salt_size; x++)
|
||||
DMEMIT("%02x", v->salt[x]);
|
||||
if (v->mode != DM_VERITY_MODE_EIO)
|
||||
args++;
|
||||
if (verity_fec_is_enabled(v))
|
||||
args += DM_VERITY_OPTS_FEC;
|
||||
if (v->zero_digest)
|
||||
args++;
|
||||
if (!args)
|
||||
return;
|
||||
DMEMIT(" %u", args);
|
||||
if (v->mode != DM_VERITY_MODE_EIO) {
|
||||
DMEMIT(" 1 ");
|
||||
DMEMIT(" ");
|
||||
switch (v->mode) {
|
||||
case DM_VERITY_MODE_LOGGING:
|
||||
DMEMIT(DM_VERITY_OPT_LOGGING);
|
||||
|
@ -627,6 +649,9 @@ static void verity_status(struct dm_target *ti, status_type_t type,
|
|||
BUG();
|
||||
}
|
||||
}
|
||||
if (v->zero_digest)
|
||||
DMEMIT(" " DM_VERITY_OPT_IGN_ZEROES);
|
||||
sz = verity_fec_status_table(v, sz, result, maxlen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -677,6 +702,7 @@ static void verity_dtr(struct dm_target *ti)
|
|||
|
||||
kfree(v->salt);
|
||||
kfree(v->root_digest);
|
||||
kfree(v->zero_digest);
|
||||
|
||||
if (v->tfm)
|
||||
crypto_free_shash(v->tfm);
|
||||
|
@ -689,9 +715,94 @@ static void verity_dtr(struct dm_target *ti)
|
|||
if (v->data_dev)
|
||||
dm_put_device(ti, v->data_dev);
|
||||
|
||||
verity_fec_dtr(v);
|
||||
|
||||
kfree(v);
|
||||
}
|
||||
|
||||
static int verity_alloc_zero_digest(struct dm_verity *v)
|
||||
{
|
||||
int r = -ENOMEM;
|
||||
struct shash_desc *desc;
|
||||
u8 *zero_data;
|
||||
|
||||
v->zero_digest = kmalloc(v->digest_size, GFP_KERNEL);
|
||||
|
||||
if (!v->zero_digest)
|
||||
return r;
|
||||
|
||||
desc = kmalloc(v->shash_descsize, GFP_KERNEL);
|
||||
|
||||
if (!desc)
|
||||
return r; /* verity_dtr will free zero_digest */
|
||||
|
||||
zero_data = kzalloc(1 << v->data_dev_block_bits, GFP_KERNEL);
|
||||
|
||||
if (!zero_data)
|
||||
goto out;
|
||||
|
||||
r = verity_hash(v, desc, zero_data, 1 << v->data_dev_block_bits,
|
||||
v->zero_digest);
|
||||
|
||||
out:
|
||||
kfree(desc);
|
||||
kfree(zero_data);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v)
|
||||
{
|
||||
int r;
|
||||
unsigned argc;
|
||||
struct dm_target *ti = v->ti;
|
||||
const char *arg_name;
|
||||
|
||||
static struct dm_arg _args[] = {
|
||||
{0, DM_VERITY_OPTS_MAX, "Invalid number of feature args"},
|
||||
};
|
||||
|
||||
r = dm_read_arg_group(_args, as, &argc, &ti->error);
|
||||
if (r)
|
||||
return -EINVAL;
|
||||
|
||||
if (!argc)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
arg_name = dm_shift_arg(as);
|
||||
argc--;
|
||||
|
||||
if (!strcasecmp(arg_name, DM_VERITY_OPT_LOGGING)) {
|
||||
v->mode = DM_VERITY_MODE_LOGGING;
|
||||
continue;
|
||||
|
||||
} else if (!strcasecmp(arg_name, DM_VERITY_OPT_RESTART)) {
|
||||
v->mode = DM_VERITY_MODE_RESTART;
|
||||
continue;
|
||||
|
||||
} else if (!strcasecmp(arg_name, DM_VERITY_OPT_IGN_ZEROES)) {
|
||||
r = verity_alloc_zero_digest(v);
|
||||
if (r) {
|
||||
ti->error = "Cannot allocate zero digest";
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (verity_is_fec_opt_arg(arg_name)) {
|
||||
r = verity_fec_parse_opt_args(as, v, &argc, arg_name);
|
||||
if (r)
|
||||
return r;
|
||||
continue;
|
||||
}
|
||||
|
||||
ti->error = "Unrecognized verity feature request";
|
||||
return -EINVAL;
|
||||
} while (argc && !r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Target parameters:
|
||||
* <version> The current format is version 1.
|
||||
|
@ -710,18 +821,13 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
|
|||
{
|
||||
struct dm_verity *v;
|
||||
struct dm_arg_set as;
|
||||
const char *opt_string;
|
||||
unsigned int num, opt_params;
|
||||
unsigned int num;
|
||||
unsigned long long num_ll;
|
||||
int r;
|
||||
int i;
|
||||
sector_t hash_position;
|
||||
char dummy;
|
||||
|
||||
static struct dm_arg _args[] = {
|
||||
{0, 1, "Invalid number of feature args"},
|
||||
};
|
||||
|
||||
v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL);
|
||||
if (!v) {
|
||||
ti->error = "Cannot allocate verity structure";
|
||||
|
@ -730,6 +836,10 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
|
|||
ti->private = v;
|
||||
v->ti = ti;
|
||||
|
||||
r = verity_fec_ctr_alloc(v);
|
||||
if (r)
|
||||
goto bad;
|
||||
|
||||
if ((dm_table_get_mode(ti->table) & ~FMODE_READ)) {
|
||||
ti->error = "Device must be readonly";
|
||||
r = -EINVAL;
|
||||
|
@ -866,29 +976,9 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
|
|||
as.argc = argc;
|
||||
as.argv = argv;
|
||||
|
||||
r = dm_read_arg_group(_args, &as, &opt_params, &ti->error);
|
||||
if (r)
|
||||
r = verity_parse_opt_args(&as, v);
|
||||
if (r < 0)
|
||||
goto bad;
|
||||
|
||||
while (opt_params) {
|
||||
opt_params--;
|
||||
opt_string = dm_shift_arg(&as);
|
||||
if (!opt_string) {
|
||||
ti->error = "Not enough feature arguments";
|
||||
r = -EINVAL;
|
||||
goto bad;
|
||||
}
|
||||
|
||||
if (!strcasecmp(opt_string, DM_VERITY_OPT_LOGGING))
|
||||
v->mode = DM_VERITY_MODE_LOGGING;
|
||||
else if (!strcasecmp(opt_string, DM_VERITY_OPT_RESTART))
|
||||
v->mode = DM_VERITY_MODE_RESTART;
|
||||
else {
|
||||
ti->error = "Invalid feature arguments";
|
||||
r = -EINVAL;
|
||||
goto bad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v->hash_per_block_bits =
|
||||
|
@ -938,8 +1028,6 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
|
|||
goto bad;
|
||||
}
|
||||
|
||||
ti->per_bio_data_size = roundup(sizeof(struct dm_verity_io) + v->shash_descsize + v->digest_size * 2, __alignof__(struct dm_verity_io));
|
||||
|
||||
/* WQ_UNBOUND greatly improves performance when running on ramdisk */
|
||||
v->verify_wq = alloc_workqueue("kverityd", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM | WQ_UNBOUND, num_online_cpus());
|
||||
if (!v->verify_wq) {
|
||||
|
@ -948,6 +1036,16 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
|
|||
goto bad;
|
||||
}
|
||||
|
||||
ti->per_bio_data_size = sizeof(struct dm_verity_io) +
|
||||
v->shash_descsize + v->digest_size * 2;
|
||||
|
||||
r = verity_fec_ctr(v);
|
||||
if (r)
|
||||
goto bad;
|
||||
|
||||
ti->per_bio_data_size = roundup(ti->per_bio_data_size,
|
||||
__alignof__(struct dm_verity_io));
|
||||
|
||||
return 0;
|
||||
|
||||
bad:
|
||||
|
@ -958,7 +1056,7 @@ bad:
|
|||
|
||||
static struct target_type verity_target = {
|
||||
.name = "verity",
|
||||
.version = {1, 2, 0},
|
||||
.version = {1, 3, 0},
|
||||
.module = THIS_MODULE,
|
||||
.ctr = verity_ctr,
|
||||
.dtr = verity_dtr,
|
129
drivers/md/dm-verity.h
Normal file
129
drivers/md/dm-verity.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Red Hat, Inc.
|
||||
* Copyright (C) 2015 Google, Inc.
|
||||
*
|
||||
* Author: Mikulas Patocka <mpatocka@redhat.com>
|
||||
*
|
||||
* Based on Chromium dm-verity driver (C) 2011 The Chromium OS Authors
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#ifndef DM_VERITY_H
|
||||
#define DM_VERITY_H
|
||||
|
||||
#include "dm-bufio.h"
|
||||
#include <linux/device-mapper.h>
|
||||
#include <crypto/hash.h>
|
||||
|
||||
#define DM_VERITY_MAX_LEVELS 63
|
||||
|
||||
enum verity_mode {
|
||||
DM_VERITY_MODE_EIO,
|
||||
DM_VERITY_MODE_LOGGING,
|
||||
DM_VERITY_MODE_RESTART
|
||||
};
|
||||
|
||||
enum verity_block_type {
|
||||
DM_VERITY_BLOCK_TYPE_DATA,
|
||||
DM_VERITY_BLOCK_TYPE_METADATA
|
||||
};
|
||||
|
||||
struct dm_verity_fec;
|
||||
|
||||
struct dm_verity {
|
||||
struct dm_dev *data_dev;
|
||||
struct dm_dev *hash_dev;
|
||||
struct dm_target *ti;
|
||||
struct dm_bufio_client *bufio;
|
||||
char *alg_name;
|
||||
struct crypto_shash *tfm;
|
||||
u8 *root_digest; /* digest of the root block */
|
||||
u8 *salt; /* salt: its size is salt_size */
|
||||
u8 *zero_digest; /* digest for a zero block */
|
||||
unsigned salt_size;
|
||||
sector_t data_start; /* data offset in 512-byte sectors */
|
||||
sector_t hash_start; /* hash start in blocks */
|
||||
sector_t data_blocks; /* the number of data blocks */
|
||||
sector_t hash_blocks; /* the number of hash blocks */
|
||||
unsigned char data_dev_block_bits; /* log2(data blocksize) */
|
||||
unsigned char hash_dev_block_bits; /* log2(hash blocksize) */
|
||||
unsigned char hash_per_block_bits; /* log2(hashes in hash block) */
|
||||
unsigned char levels; /* the number of tree levels */
|
||||
unsigned char version;
|
||||
unsigned digest_size; /* digest size for the current hash algorithm */
|
||||
unsigned shash_descsize;/* the size of temporary space for crypto */
|
||||
int hash_failed; /* set to 1 if hash of any block failed */
|
||||
enum verity_mode mode; /* mode for handling verification errors */
|
||||
unsigned corrupted_errs;/* Number of errors for corrupted blocks */
|
||||
|
||||
struct workqueue_struct *verify_wq;
|
||||
|
||||
/* starting blocks for each tree level. 0 is the lowest level. */
|
||||
sector_t hash_level_block[DM_VERITY_MAX_LEVELS];
|
||||
|
||||
struct dm_verity_fec *fec; /* forward error correction */
|
||||
};
|
||||
|
||||
struct dm_verity_io {
|
||||
struct dm_verity *v;
|
||||
|
||||
/* original value of bio->bi_end_io */
|
||||
bio_end_io_t *orig_bi_end_io;
|
||||
|
||||
sector_t block;
|
||||
unsigned n_blocks;
|
||||
|
||||
struct bvec_iter iter;
|
||||
|
||||
struct work_struct work;
|
||||
|
||||
/*
|
||||
* Three variably-size fields follow this struct:
|
||||
*
|
||||
* u8 hash_desc[v->shash_descsize];
|
||||
* u8 real_digest[v->digest_size];
|
||||
* u8 want_digest[v->digest_size];
|
||||
*
|
||||
* To access them use: verity_io_hash_desc(), verity_io_real_digest()
|
||||
* and verity_io_want_digest().
|
||||
*/
|
||||
};
|
||||
|
||||
static inline struct shash_desc *verity_io_hash_desc(struct dm_verity *v,
|
||||
struct dm_verity_io *io)
|
||||
{
|
||||
return (struct shash_desc *)(io + 1);
|
||||
}
|
||||
|
||||
static inline u8 *verity_io_real_digest(struct dm_verity *v,
|
||||
struct dm_verity_io *io)
|
||||
{
|
||||
return (u8 *)(io + 1) + v->shash_descsize;
|
||||
}
|
||||
|
||||
static inline u8 *verity_io_want_digest(struct dm_verity *v,
|
||||
struct dm_verity_io *io)
|
||||
{
|
||||
return (u8 *)(io + 1) + v->shash_descsize + v->digest_size;
|
||||
}
|
||||
|
||||
static inline u8 *verity_io_digest_end(struct dm_verity *v,
|
||||
struct dm_verity_io *io)
|
||||
{
|
||||
return verity_io_want_digest(v, io) + v->digest_size;
|
||||
}
|
||||
|
||||
extern int verity_for_bv_block(struct dm_verity *v, struct dm_verity_io *io,
|
||||
struct bvec_iter *iter,
|
||||
int (*process)(struct dm_verity *v,
|
||||
struct dm_verity_io *io,
|
||||
u8 *data, size_t len));
|
||||
|
||||
extern int verity_hash(struct dm_verity *v, struct shash_desc *desc,
|
||||
const u8 *data, size_t len, u8 *digest);
|
||||
|
||||
extern int verity_hash_for_block(struct dm_verity *v, struct dm_verity_io *io,
|
||||
sector_t block, u8 *digest, bool *is_zero);
|
||||
|
||||
#endif /* DM_VERITY_H */
|
153
drivers/misc/uid_stat.c
Normal file
153
drivers/misc/uid_stat.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
/* drivers/misc/uid_stat.c
|
||||
*
|
||||
* Copyright (C) 2008 - 2009 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/uid_stat.h>
|
||||
#include <net/activity_stats.h>
|
||||
|
||||
static DEFINE_SPINLOCK(uid_lock);
|
||||
static LIST_HEAD(uid_list);
|
||||
static struct proc_dir_entry *parent;
|
||||
|
||||
struct uid_stat {
|
||||
struct list_head link;
|
||||
uid_t uid;
|
||||
atomic_t tcp_rcv;
|
||||
atomic_t tcp_snd;
|
||||
};
|
||||
|
||||
static struct uid_stat *find_uid_stat(uid_t uid) {
|
||||
struct uid_stat *entry;
|
||||
|
||||
list_for_each_entry(entry, &uid_list, link) {
|
||||
if (entry->uid == uid) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int uid_stat_atomic_int_show(struct seq_file *m, void *v)
|
||||
{
|
||||
unsigned int bytes;
|
||||
atomic_t *counter = m->private;
|
||||
|
||||
bytes = (unsigned int) (atomic_read(counter) + INT_MIN);
|
||||
seq_printf(m, "%u\n", bytes);
|
||||
return seq_has_overflowed(m) ? -ENOSPC : 0;
|
||||
}
|
||||
|
||||
static int uid_stat_read_atomic_int_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, uid_stat_atomic_int_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations uid_stat_read_atomic_int_fops = {
|
||||
.open = uid_stat_read_atomic_int_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
};
|
||||
|
||||
/* Create a new entry for tracking the specified uid. */
|
||||
static struct uid_stat *create_stat(uid_t uid) {
|
||||
struct uid_stat *new_uid;
|
||||
/* Create the uid stat struct and append it to the list. */
|
||||
new_uid = kmalloc(sizeof(struct uid_stat), GFP_ATOMIC);
|
||||
if (!new_uid)
|
||||
return NULL;
|
||||
|
||||
new_uid->uid = uid;
|
||||
/* Counters start at INT_MIN, so we can track 4GB of network traffic. */
|
||||
atomic_set(&new_uid->tcp_rcv, INT_MIN);
|
||||
atomic_set(&new_uid->tcp_snd, INT_MIN);
|
||||
|
||||
list_add_tail(&new_uid->link, &uid_list);
|
||||
return new_uid;
|
||||
}
|
||||
|
||||
static void create_stat_proc(struct uid_stat *new_uid)
|
||||
{
|
||||
char uid_s[32];
|
||||
struct proc_dir_entry *entry;
|
||||
sprintf(uid_s, "%d", new_uid->uid);
|
||||
entry = proc_mkdir(uid_s, parent);
|
||||
|
||||
/* Keep reference to uid_stat so we know what uid to read stats from. */
|
||||
proc_create_data("tcp_snd", S_IRUGO, entry,
|
||||
&uid_stat_read_atomic_int_fops, &new_uid->tcp_snd);
|
||||
|
||||
proc_create_data("tcp_rcv", S_IRUGO, entry,
|
||||
&uid_stat_read_atomic_int_fops, &new_uid->tcp_rcv);
|
||||
}
|
||||
|
||||
static struct uid_stat *find_or_create_uid_stat(uid_t uid)
|
||||
{
|
||||
struct uid_stat *entry;
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&uid_lock, flags);
|
||||
entry = find_uid_stat(uid);
|
||||
if (entry) {
|
||||
spin_unlock_irqrestore(&uid_lock, flags);
|
||||
return entry;
|
||||
}
|
||||
entry = create_stat(uid);
|
||||
spin_unlock_irqrestore(&uid_lock, flags);
|
||||
if (entry)
|
||||
create_stat_proc(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
int uid_stat_tcp_snd(uid_t uid, int size) {
|
||||
struct uid_stat *entry;
|
||||
activity_stats_update();
|
||||
entry = find_or_create_uid_stat(uid);
|
||||
if (!entry)
|
||||
return -1;
|
||||
atomic_add(size, &entry->tcp_snd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_stat_tcp_rcv(uid_t uid, int size) {
|
||||
struct uid_stat *entry;
|
||||
activity_stats_update();
|
||||
entry = find_or_create_uid_stat(uid);
|
||||
if (!entry)
|
||||
return -1;
|
||||
atomic_add(size, &entry->tcp_rcv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init uid_stat_init(void)
|
||||
{
|
||||
parent = proc_mkdir("uid_stat", NULL);
|
||||
if (!parent) {
|
||||
pr_err("uid_stat: failed to create proc entry\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__initcall(uid_stat_init);
|
|
@ -68,3 +68,15 @@ config MMC_TEST
|
|||
|
||||
This driver is only of interest to those developing or
|
||||
testing a host driver. Most people should say N here.
|
||||
|
||||
config MMC_SIMULATE_MAX_SPEED
|
||||
bool "Turn on maximum speed control per block device"
|
||||
depends on MMC_BLOCK
|
||||
help
|
||||
Say Y here to enable MMC device speed limiting. Used to test and
|
||||
simulate the behavior of the system when confronted with a slow MMC.
|
||||
|
||||
Enables max_read_speed, max_write_speed and cache_size attributes to
|
||||
control the write or read maximum KB/second speed behaviors.
|
||||
|
||||
If unsure, say N here.
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
#include <linux/pm_runtime.h>
|
||||
#include <linux/ioprio.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/mmc.h>
|
||||
|
||||
#include <linux/mmc/ioctl.h>
|
||||
|
@ -329,6 +328,249 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
|
||||
static int max_read_speed, max_write_speed, cache_size = 4;
|
||||
|
||||
module_param(max_read_speed, int, S_IRUSR | S_IRGRP);
|
||||
MODULE_PARM_DESC(max_read_speed, "maximum KB/s read speed 0=off");
|
||||
module_param(max_write_speed, int, S_IRUSR | S_IRGRP);
|
||||
MODULE_PARM_DESC(max_write_speed, "maximum KB/s write speed 0=off");
|
||||
module_param(cache_size, int, S_IRUSR | S_IRGRP);
|
||||
MODULE_PARM_DESC(cache_size, "MB high speed memory or SLC cache");
|
||||
|
||||
/*
|
||||
* helper macros and expectations:
|
||||
* size - unsigned long number of bytes
|
||||
* jiffies - unsigned long HZ timestamp difference
|
||||
* speed - unsigned KB/s transfer rate
|
||||
*/
|
||||
#define size_and_speed_to_jiffies(size, speed) \
|
||||
((size) * HZ / (speed) / 1024UL)
|
||||
#define jiffies_and_speed_to_size(jiffies, speed) \
|
||||
(((speed) * (jiffies) * 1024UL) / HZ)
|
||||
#define jiffies_and_size_to_speed(jiffies, size) \
|
||||
((size) * HZ / (jiffies) / 1024UL)
|
||||
|
||||
/* Limits to report warning */
|
||||
/* jiffies_and_size_to_speed(10*HZ, queue_max_hw_sectors(q) * 512UL) ~ 25 */
|
||||
#define MIN_SPEED(q) 250 /* 10 times faster than a floppy disk */
|
||||
#define MAX_SPEED(q) jiffies_and_size_to_speed(1, queue_max_sectors(q) * 512UL)
|
||||
|
||||
#define speed_valid(speed) ((speed) > 0)
|
||||
|
||||
static const char off[] = "off\n";
|
||||
|
||||
static int max_speed_show(int speed, char *buf)
|
||||
{
|
||||
if (speed)
|
||||
return scnprintf(buf, PAGE_SIZE, "%uKB/s\n", speed);
|
||||
else
|
||||
return scnprintf(buf, PAGE_SIZE, off);
|
||||
}
|
||||
|
||||
static int max_speed_store(const char *buf, struct request_queue *q)
|
||||
{
|
||||
unsigned int limit, set = 0;
|
||||
|
||||
if (!strncasecmp(off, buf, sizeof(off) - 2))
|
||||
return set;
|
||||
if (kstrtouint(buf, 0, &set) || (set > INT_MAX))
|
||||
return -EINVAL;
|
||||
if (set == 0)
|
||||
return set;
|
||||
limit = MAX_SPEED(q);
|
||||
if (set > limit)
|
||||
pr_warn("max speed %u ineffective above %u\n", set, limit);
|
||||
limit = MIN_SPEED(q);
|
||||
if (set < limit)
|
||||
pr_warn("max speed %u painful below %u\n", set, limit);
|
||||
return set;
|
||||
}
|
||||
|
||||
static ssize_t max_write_speed_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
|
||||
int ret = max_speed_show(atomic_read(&md->queue.max_write_speed), buf);
|
||||
|
||||
mmc_blk_put(md);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t max_write_speed_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
|
||||
int set = max_speed_store(buf, md->queue.queue);
|
||||
|
||||
if (set < 0) {
|
||||
mmc_blk_put(md);
|
||||
return set;
|
||||
}
|
||||
|
||||
atomic_set(&md->queue.max_write_speed, set);
|
||||
mmc_blk_put(md);
|
||||
return count;
|
||||
}
|
||||
|
||||
static const DEVICE_ATTR(max_write_speed, S_IRUGO | S_IWUSR,
|
||||
max_write_speed_show, max_write_speed_store);
|
||||
|
||||
static ssize_t max_read_speed_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
|
||||
int ret = max_speed_show(atomic_read(&md->queue.max_read_speed), buf);
|
||||
|
||||
mmc_blk_put(md);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t max_read_speed_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
|
||||
int set = max_speed_store(buf, md->queue.queue);
|
||||
|
||||
if (set < 0) {
|
||||
mmc_blk_put(md);
|
||||
return set;
|
||||
}
|
||||
|
||||
atomic_set(&md->queue.max_read_speed, set);
|
||||
mmc_blk_put(md);
|
||||
return count;
|
||||
}
|
||||
|
||||
static const DEVICE_ATTR(max_read_speed, S_IRUGO | S_IWUSR,
|
||||
max_read_speed_show, max_read_speed_store);
|
||||
|
||||
static ssize_t cache_size_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
|
||||
struct mmc_queue *mq = &md->queue;
|
||||
int cache_size = atomic_read(&mq->cache_size);
|
||||
int ret;
|
||||
|
||||
if (!cache_size)
|
||||
ret = scnprintf(buf, PAGE_SIZE, off);
|
||||
else {
|
||||
int speed = atomic_read(&mq->max_write_speed);
|
||||
|
||||
if (!speed_valid(speed))
|
||||
ret = scnprintf(buf, PAGE_SIZE, "%uMB\n", cache_size);
|
||||
else { /* We accept race between cache_jiffies and cache_used */
|
||||
unsigned long size = jiffies_and_speed_to_size(
|
||||
jiffies - mq->cache_jiffies, speed);
|
||||
long used = atomic_long_read(&mq->cache_used);
|
||||
|
||||
if (size >= used)
|
||||
size = 0;
|
||||
else
|
||||
size = (used - size) * 100 / cache_size
|
||||
/ 1024UL / 1024UL;
|
||||
|
||||
ret = scnprintf(buf, PAGE_SIZE, "%uMB %lu%% used\n",
|
||||
cache_size, size);
|
||||
}
|
||||
}
|
||||
|
||||
mmc_blk_put(md);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t cache_size_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mmc_blk_data *md;
|
||||
unsigned int set = 0;
|
||||
|
||||
if (strncasecmp(off, buf, sizeof(off) - 2)
|
||||
&& (kstrtouint(buf, 0, &set) || (set > INT_MAX)))
|
||||
return -EINVAL;
|
||||
|
||||
md = mmc_blk_get(dev_to_disk(dev));
|
||||
atomic_set(&md->queue.cache_size, set);
|
||||
mmc_blk_put(md);
|
||||
return count;
|
||||
}
|
||||
|
||||
static const DEVICE_ATTR(cache_size, S_IRUGO | S_IWUSR,
|
||||
cache_size_show, cache_size_store);
|
||||
|
||||
/* correct for write-back */
|
||||
static long mmc_blk_cache_used(struct mmc_queue *mq, unsigned long waitfor)
|
||||
{
|
||||
long used = 0;
|
||||
int speed = atomic_read(&mq->max_write_speed);
|
||||
|
||||
if (speed_valid(speed)) {
|
||||
unsigned long size = jiffies_and_speed_to_size(
|
||||
waitfor - mq->cache_jiffies, speed);
|
||||
used = atomic_long_read(&mq->cache_used);
|
||||
|
||||
if (size >= used)
|
||||
used = 0;
|
||||
else
|
||||
used -= size;
|
||||
}
|
||||
|
||||
atomic_long_set(&mq->cache_used, used);
|
||||
mq->cache_jiffies = waitfor;
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
static void mmc_blk_simulate_delay(
|
||||
struct mmc_queue *mq,
|
||||
struct request *req,
|
||||
unsigned long waitfor)
|
||||
{
|
||||
int max_speed;
|
||||
|
||||
if (!req)
|
||||
return;
|
||||
|
||||
max_speed = (rq_data_dir(req) == READ)
|
||||
? atomic_read(&mq->max_read_speed)
|
||||
: atomic_read(&mq->max_write_speed);
|
||||
if (speed_valid(max_speed)) {
|
||||
unsigned long bytes = blk_rq_bytes(req);
|
||||
|
||||
if (rq_data_dir(req) != READ) {
|
||||
int cache_size = atomic_read(&mq->cache_size);
|
||||
|
||||
if (cache_size) {
|
||||
unsigned long size = cache_size * 1024L * 1024L;
|
||||
long used = mmc_blk_cache_used(mq, waitfor);
|
||||
|
||||
used += bytes;
|
||||
atomic_long_set(&mq->cache_used, used);
|
||||
bytes = 0;
|
||||
if (used > size)
|
||||
bytes = used - size;
|
||||
}
|
||||
}
|
||||
waitfor += size_and_speed_to_jiffies(bytes, max_speed);
|
||||
if (time_is_after_jiffies(waitfor)) {
|
||||
long msecs = jiffies_to_msecs(waitfor - jiffies);
|
||||
|
||||
if (likely(msecs > 0))
|
||||
msleep(msecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define mmc_blk_simulate_delay(mq, req, waitfor)
|
||||
|
||||
#endif
|
||||
static ssize_t
|
||||
num_wr_reqs_to_start_packing_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
|
@ -1870,6 +2112,23 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req)
|
|||
}
|
||||
|
||||
end_req:
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
else if (atomic_read(&mq->cache_size)) {
|
||||
long used = mmc_blk_cache_used(mq, jiffies);
|
||||
|
||||
if (used) {
|
||||
int speed = atomic_read(&mq->max_write_speed);
|
||||
|
||||
if (speed_valid(speed)) {
|
||||
unsigned long msecs = jiffies_to_msecs(
|
||||
size_and_speed_to_jiffies(
|
||||
used, speed));
|
||||
if (msecs)
|
||||
msleep(msecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
blk_end_request_all(req, ret);
|
||||
|
||||
return ret ? 0 : 1;
|
||||
|
@ -3346,6 +3605,9 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
|
|||
struct mmc_async_req *areq;
|
||||
const u8 packed_nr = 2;
|
||||
u8 reqs = 0;
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
unsigned long waitfor = jiffies;
|
||||
#endif
|
||||
|
||||
if (!rqc && !mq->mqrq_prev->req)
|
||||
return 0;
|
||||
|
@ -3396,6 +3658,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
|
|||
*/
|
||||
mmc_blk_reset_success(md, type);
|
||||
|
||||
mmc_blk_simulate_delay(mq, rqc, waitfor);
|
||||
|
||||
if (mmc_packed_cmd(mq_rq->cmd_type)) {
|
||||
ret = mmc_blk_end_packed_req(mq_rq);
|
||||
break;
|
||||
|
@ -3968,6 +4232,14 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md)
|
|||
card->ext_csd.boot_ro_lockable)
|
||||
device_remove_file(disk_to_dev(md->disk),
|
||||
&md->power_ro_lock);
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
device_remove_file(disk_to_dev(md->disk),
|
||||
&dev_attr_max_write_speed);
|
||||
device_remove_file(disk_to_dev(md->disk),
|
||||
&dev_attr_max_read_speed);
|
||||
device_remove_file(disk_to_dev(md->disk),
|
||||
&dev_attr_cache_size);
|
||||
#endif
|
||||
|
||||
del_gendisk(md->disk);
|
||||
}
|
||||
|
@ -4003,6 +4275,24 @@ static int mmc_add_disk(struct mmc_blk_data *md)
|
|||
ret = device_create_file(disk_to_dev(md->disk), &md->force_ro);
|
||||
if (ret)
|
||||
goto force_ro_fail;
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
atomic_set(&md->queue.max_write_speed, max_write_speed);
|
||||
ret = device_create_file(disk_to_dev(md->disk),
|
||||
&dev_attr_max_write_speed);
|
||||
if (ret)
|
||||
goto max_write_speed_fail;
|
||||
atomic_set(&md->queue.max_read_speed, max_read_speed);
|
||||
ret = device_create_file(disk_to_dev(md->disk),
|
||||
&dev_attr_max_read_speed);
|
||||
if (ret)
|
||||
goto max_read_speed_fail;
|
||||
atomic_set(&md->queue.cache_size, cache_size);
|
||||
atomic_long_set(&md->queue.cache_used, 0);
|
||||
md->queue.cache_jiffies = jiffies;
|
||||
ret = device_create_file(disk_to_dev(md->disk), &dev_attr_cache_size);
|
||||
if (ret)
|
||||
goto cache_size_fail;
|
||||
#endif
|
||||
|
||||
if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) &&
|
||||
card->ext_csd.boot_ro_lockable) {
|
||||
|
@ -4056,6 +4346,14 @@ no_pack_for_random_fails:
|
|||
num_wr_reqs_to_start_packing_fail:
|
||||
device_remove_file(disk_to_dev(md->disk), &md->power_ro_lock);
|
||||
power_ro_lock_fail:
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
device_remove_file(disk_to_dev(md->disk), &dev_attr_cache_size);
|
||||
cache_size_fail:
|
||||
device_remove_file(disk_to_dev(md->disk), &dev_attr_max_read_speed);
|
||||
max_read_speed_fail:
|
||||
device_remove_file(disk_to_dev(md->disk), &dev_attr_max_write_speed);
|
||||
max_write_speed_fail:
|
||||
#endif
|
||||
device_remove_file(disk_to_dev(md->disk), &md->force_ro);
|
||||
force_ro_fail:
|
||||
del_gendisk(md->disk);
|
||||
|
|
|
@ -77,6 +77,14 @@ struct mmc_queue {
|
|||
int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *);
|
||||
void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *);
|
||||
void (*cmdq_shutdown)(struct mmc_queue *);
|
||||
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
|
||||
atomic_t max_write_speed;
|
||||
atomic_t max_read_speed;
|
||||
atomic_t cache_size;
|
||||
/* i/o tracking */
|
||||
atomic_long_t cache_used;
|
||||
unsigned long cache_jiffies;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *,
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <linux/pm.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/mmc.h>
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
|
@ -51,6 +52,11 @@
|
|||
#include "sd_ops.h"
|
||||
#include "sdio_ops.h"
|
||||
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_erase_start);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_erase_end);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_rw_start);
|
||||
EXPORT_TRACEPOINT_SYMBOL_GPL(mmc_blk_rw_end);
|
||||
|
||||
/* If the device is not responding */
|
||||
#define MMC_CORE_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "host.h"
|
||||
#include "sd.h"
|
||||
#include "sdio_bus.h"
|
||||
#include "mmc_ops.h"
|
||||
|
|
|
@ -150,7 +150,7 @@ config PPPOL2TP
|
|||
if TTY
|
||||
|
||||
config PPPOLAC
|
||||
bool "PPP on L2TP Access Concentrator"
|
||||
tristate "PPP on L2TP Access Concentrator"
|
||||
depends on PPP && INET
|
||||
help
|
||||
L2TP (RFC 2661) is a tunneling protocol widely used in virtual private
|
||||
|
@ -159,7 +159,7 @@ config PPPOLAC
|
|||
fairly simple and suited for clients.
|
||||
|
||||
config PPPOPNS
|
||||
bool "PPP on PPTP Network Server"
|
||||
tristate "PPP on PPTP Network Server"
|
||||
depends on PPP && INET
|
||||
help
|
||||
PPTP (RFC 2637) is a tunneling protocol widely used in virtual private
|
||||
|
|
|
@ -206,11 +206,10 @@ static void pppolac_xmit_core(struct work_struct *delivery_work)
|
|||
while ((skb = skb_dequeue(&delivery_queue))) {
|
||||
struct sock *sk_udp = skb->sk;
|
||||
struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = (struct iovec *)&iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT,
|
||||
};
|
||||
struct msghdr msg = { 0 };
|
||||
|
||||
iov_iter_kvec(&msg.msg_iter, WRITE | ITER_KVEC, &iov, 1,
|
||||
skb->len);
|
||||
sk_udp->sk_prot->sendmsg(sk_udp, &msg, skb->len);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
|
|
@ -189,11 +189,10 @@ static void pppopns_xmit_core(struct work_struct *delivery_work)
|
|||
while ((skb = skb_dequeue(&delivery_queue))) {
|
||||
struct sock *sk_raw = skb->sk;
|
||||
struct kvec iov = {.iov_base = skb->data, .iov_len = skb->len};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = (struct iovec *)&iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT,
|
||||
};
|
||||
struct msghdr msg = { 0 };
|
||||
|
||||
iov_iter_kvec(&msg.msg_iter, WRITE | ITER_KVEC, &iov, 1,
|
||||
skb->len);
|
||||
sk_raw->sk_prot->sendmsg(sk_raw, &msg, skb->len);
|
||||
kfree_skb(skb);
|
||||
}
|
||||
|
|
|
@ -448,12 +448,19 @@ static void acc_hid_close(struct hid_device *hid)
|
|||
{
|
||||
}
|
||||
|
||||
static int acc_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
|
||||
__u8 *buf, size_t len, unsigned char rtype, int reqtype)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hid_ll_driver acc_hid_ll_driver = {
|
||||
.parse = acc_hid_parse,
|
||||
.start = acc_hid_start,
|
||||
.stop = acc_hid_stop,
|
||||
.open = acc_hid_open,
|
||||
.close = acc_hid_close,
|
||||
.raw_request = acc_hid_raw_request,
|
||||
};
|
||||
|
||||
static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev,
|
||||
|
|
|
@ -583,6 +583,11 @@ static void audio_disable(struct usb_function *f)
|
|||
usb_ep_disable(audio->in_ep);
|
||||
}
|
||||
|
||||
static void audio_free_func(struct usb_function *f)
|
||||
{
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static void audio_build_desc(struct audio_dev *audio)
|
||||
|
@ -827,6 +832,7 @@ static struct audio_dev _audio_dev = {
|
|||
.set_alt = audio_set_alt,
|
||||
.setup = audio_setup,
|
||||
.disable = audio_disable,
|
||||
.free_func = audio_free_func,
|
||||
},
|
||||
.lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock),
|
||||
.idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs),
|
||||
|
|
|
@ -158,7 +158,7 @@ static struct usb_interface_descriptor ptp_interface_desc = {
|
|||
.bInterfaceProtocol = 1,
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor mtp_superspeed_in_desc = {
|
||||
static struct usb_endpoint_descriptor mtp_ss_in_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_IN,
|
||||
|
@ -166,8 +166,8 @@ static struct usb_endpoint_descriptor mtp_superspeed_in_desc = {
|
|||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor mtp_superspeed_in_comp_desc = {
|
||||
.bLength = sizeof(mtp_superspeed_in_comp_desc),
|
||||
static struct usb_ss_ep_comp_descriptor mtp_ss_in_comp_desc = {
|
||||
.bLength = sizeof(mtp_ss_in_comp_desc),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 2 values can be tweaked if necessary */
|
||||
|
@ -175,7 +175,8 @@ static struct usb_ss_ep_comp_descriptor mtp_superspeed_in_comp_desc = {
|
|||
/* .bmAttributes = 0, */
|
||||
};
|
||||
|
||||
static struct usb_endpoint_descriptor mtp_superspeed_out_desc = {
|
||||
|
||||
static struct usb_endpoint_descriptor mtp_ss_out_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_DIR_OUT,
|
||||
|
@ -183,8 +184,8 @@ static struct usb_endpoint_descriptor mtp_superspeed_out_desc = {
|
|||
.wMaxPacketSize = cpu_to_le16(1024),
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor mtp_superspeed_out_comp_desc = {
|
||||
.bLength = sizeof(mtp_superspeed_out_comp_desc),
|
||||
static struct usb_ss_ep_comp_descriptor mtp_ss_out_comp_desc = {
|
||||
.bLength = sizeof(mtp_ss_out_comp_desc),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 2 values can be tweaked if necessary */
|
||||
|
@ -231,8 +232,8 @@ static struct usb_endpoint_descriptor mtp_intr_desc = {
|
|||
.bInterval = 6,
|
||||
};
|
||||
|
||||
static struct usb_ss_ep_comp_descriptor mtp_superspeed_intr_comp_desc = {
|
||||
.bLength = sizeof(mtp_superspeed_intr_comp_desc),
|
||||
static struct usb_ss_ep_comp_descriptor mtp_intr_ss_comp_desc = {
|
||||
.bLength = sizeof(mtp_intr_ss_comp_desc),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
|
||||
|
||||
/* the following 3 values can be tweaked if necessary */
|
||||
|
@ -259,12 +260,12 @@ static struct usb_descriptor_header *hs_mtp_descs[] = {
|
|||
|
||||
static struct usb_descriptor_header *ss_mtp_descs[] = {
|
||||
(struct usb_descriptor_header *) &mtp_interface_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_in_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_in_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_out_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_out_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_in_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_in_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_out_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_out_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_intr_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_intr_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_intr_ss_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -286,12 +287,12 @@ static struct usb_descriptor_header *hs_ptp_descs[] = {
|
|||
|
||||
static struct usb_descriptor_header *ss_ptp_descs[] = {
|
||||
(struct usb_descriptor_header *) &ptp_interface_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_in_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_in_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_out_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_out_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_in_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_in_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_out_desc,
|
||||
(struct usb_descriptor_header *) &mtp_ss_out_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_intr_desc,
|
||||
(struct usb_descriptor_header *) &mtp_superspeed_intr_comp_desc,
|
||||
(struct usb_descriptor_header *) &mtp_intr_ss_comp_desc,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -352,7 +353,7 @@ struct ext_mtp_desc mtp_ext_config_desc = {
|
|||
.dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)),
|
||||
.bcdVersion = __constant_cpu_to_le16(0x0100),
|
||||
.wIndex = __constant_cpu_to_le16(4),
|
||||
.bCount = __constant_cpu_to_le16(1),
|
||||
.bCount = 1,
|
||||
},
|
||||
.function = {
|
||||
.bFirstInterfaceNumber = 0,
|
||||
|
@ -395,6 +396,8 @@ struct mtp_instance {
|
|||
struct usb_function_instance func_inst;
|
||||
const char *name;
|
||||
struct mtp_dev *dev;
|
||||
char mtp_ext_compat_id[16];
|
||||
struct usb_os_desc mtp_os_desc;
|
||||
};
|
||||
|
||||
/* temporary variable used between mtp_open() and mtp_gadget_bind() */
|
||||
|
@ -1389,6 +1392,7 @@ mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
struct mtp_dev *dev = func_to_mtp(f);
|
||||
int id;
|
||||
int ret;
|
||||
struct mtp_instance *fi_mtp;
|
||||
|
||||
dev->cdev = cdev;
|
||||
DBG(cdev, "mtp_function_bind dev: %p\n", dev);
|
||||
|
@ -1406,6 +1410,18 @@ mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
mtp_string_defs[INTERFACE_STRING_INDEX].id = ret;
|
||||
mtp_interface_desc.iInterface = ret;
|
||||
}
|
||||
|
||||
fi_mtp = container_of(f->fi, struct mtp_instance, func_inst);
|
||||
|
||||
if (cdev->use_os_string) {
|
||||
f->os_desc_table = kzalloc(sizeof(*f->os_desc_table),
|
||||
GFP_KERNEL);
|
||||
if (!f->os_desc_table)
|
||||
return -ENOMEM;
|
||||
f->os_desc_n = 1;
|
||||
f->os_desc_table[0].os_desc = &fi_mtp->mtp_os_desc;
|
||||
}
|
||||
|
||||
/* allocate endpoints */
|
||||
ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc,
|
||||
&mtp_fullspeed_out_desc, &mtp_intr_desc);
|
||||
|
@ -1419,18 +1435,32 @@ mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
|
|||
mtp_highspeed_out_desc.bEndpointAddress =
|
||||
mtp_fullspeed_out_desc.bEndpointAddress;
|
||||
}
|
||||
/* support super speed hardware */
|
||||
if (gadget_is_superspeed(c->cdev->gadget)) {
|
||||
unsigned max_burst;
|
||||
|
||||
/* Calculate bMaxBurst, we know packet size is 1024 */
|
||||
max_burst = min_t(unsigned, MTP_BULK_BUFFER_SIZE / 1024, 15);
|
||||
mtp_ss_in_desc.bEndpointAddress =
|
||||
mtp_fullspeed_in_desc.bEndpointAddress;
|
||||
mtp_ss_in_comp_desc.bMaxBurst = max_burst;
|
||||
mtp_ss_out_desc.bEndpointAddress =
|
||||
mtp_fullspeed_out_desc.bEndpointAddress;
|
||||
mtp_ss_out_comp_desc.bMaxBurst = max_burst;
|
||||
}
|
||||
|
||||
/* support super speed hardware */
|
||||
if (gadget_is_superspeed(c->cdev->gadget)) {
|
||||
mtp_superspeed_in_desc.bEndpointAddress =
|
||||
mtp_ss_in_desc.bEndpointAddress =
|
||||
mtp_fullspeed_in_desc.bEndpointAddress;
|
||||
mtp_superspeed_out_desc.bEndpointAddress =
|
||||
mtp_ss_out_desc.bEndpointAddress =
|
||||
mtp_fullspeed_out_desc.bEndpointAddress;
|
||||
}
|
||||
|
||||
DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
|
||||
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
|
||||
f->name, dev->ep_in->name, dev->ep_out->name);
|
||||
gadget_is_superspeed(c->cdev->gadget) ? "super" :
|
||||
(gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full"),
|
||||
f->name, dev->ep_in->name, dev->ep_out->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1449,6 +1479,8 @@ mtp_function_unbind(struct usb_configuration *c, struct usb_function *f)
|
|||
mtp_request_free(req, dev->ep_intr);
|
||||
dev->state = STATE_OFFLINE;
|
||||
dev->is_ptp = false;
|
||||
kfree(f->os_desc_table);
|
||||
f->os_desc_n = 0;
|
||||
}
|
||||
|
||||
static int mtp_function_set_alt(struct usb_function *f,
|
||||
|
@ -1748,6 +1780,7 @@ static void mtp_free_inst(struct usb_function_instance *fi)
|
|||
fi_mtp = to_fi_mtp(fi);
|
||||
kfree(fi_mtp->name);
|
||||
mtp_cleanup();
|
||||
kfree(fi_mtp->mtp_os_desc.group.default_groups);
|
||||
kfree(fi_mtp);
|
||||
}
|
||||
|
||||
|
@ -1755,6 +1788,8 @@ struct usb_function_instance *alloc_inst_mtp_ptp(bool mtp_config)
|
|||
{
|
||||
struct mtp_instance *fi_mtp;
|
||||
int ret = 0;
|
||||
struct usb_os_desc *descs[1];
|
||||
char *names[1];
|
||||
|
||||
fi_mtp = kzalloc(sizeof(*fi_mtp), GFP_KERNEL);
|
||||
if (!fi_mtp)
|
||||
|
@ -1762,6 +1797,13 @@ struct usb_function_instance *alloc_inst_mtp_ptp(bool mtp_config)
|
|||
fi_mtp->func_inst.set_inst_name = mtp_set_inst_name;
|
||||
fi_mtp->func_inst.free_func_inst = mtp_free_inst;
|
||||
|
||||
fi_mtp->mtp_os_desc.ext_compat_id = fi_mtp->mtp_ext_compat_id;
|
||||
INIT_LIST_HEAD(&fi_mtp->mtp_os_desc.ext_prop);
|
||||
descs[0] = &fi_mtp->mtp_os_desc;
|
||||
names[0] = "MTP";
|
||||
usb_os_desc_prepare_interf_dir(&fi_mtp->func_inst.group, 1,
|
||||
descs, names, THIS_MODULE);
|
||||
|
||||
if (mtp_config) {
|
||||
ret = mtp_setup_configfs(fi_mtp);
|
||||
if (ret) {
|
||||
|
|
|
@ -11,4 +11,4 @@ menuconfig ADF_FBDEV
|
|||
menuconfig ADF_MEMBLOCK
|
||||
depends on ADF
|
||||
depends on HAVE_MEMBLOCK
|
||||
tristate "Helper for using memblocks as buffers in ADF drivers"
|
||||
bool "Helper for using memblocks as buffers in ADF drivers"
|
||||
|
|
|
@ -2,13 +2,15 @@ ccflags-y := -Idrivers/staging/android
|
|||
|
||||
CFLAGS_adf.o := -I$(src)
|
||||
|
||||
obj-$(CONFIG_ADF) += adf.o \
|
||||
obj-$(CONFIG_ADF) += adf_core.o
|
||||
|
||||
adf_core-y := adf.o \
|
||||
adf_client.o \
|
||||
adf_fops.o \
|
||||
adf_format.o \
|
||||
adf_sysfs.o
|
||||
|
||||
obj-$(CONFIG_COMPAT) += adf_fops32.o
|
||||
adf_core-$(CONFIG_COMPAT) += adf_fops32.o
|
||||
|
||||
obj-$(CONFIG_ADF_FBDEV) += adf_fbdev.o
|
||||
|
||||
|
|
|
@ -199,6 +199,7 @@ if MISC_FILESYSTEMS
|
|||
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"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#
|
||||
# 14 Sep 2000, Christoph Hellwig <hch@infradead.org>
|
||||
# Rewritten to use lists instead of if-statements.
|
||||
#
|
||||
#
|
||||
|
||||
obj-y := open.o read_write.o file_table.o super.o \
|
||||
char_dev.o stat.o exec.o pipe.o namei.o fcntl.o \
|
||||
|
@ -59,7 +59,7 @@ obj-y += devpts/
|
|||
|
||||
obj-$(CONFIG_PROFILING) += dcookies.o
|
||||
obj-$(CONFIG_DLM) += dlm/
|
||||
|
||||
|
||||
# Do not add any filesystems before this line
|
||||
obj-$(CONFIG_FSCACHE) += fscache/
|
||||
obj-$(CONFIG_REISERFS_FS) += reiserfs/
|
||||
|
@ -81,6 +81,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/
|
||||
|
|
|
@ -651,7 +651,7 @@ static unsigned long randomize_stack_top(unsigned long stack_top)
|
|||
|
||||
if ((current->flags & PF_RANDOMIZE) &&
|
||||
!(current->personality & ADDR_NO_RANDOMIZE)) {
|
||||
random_variable = (unsigned long) get_random_int();
|
||||
random_variable = get_random_long();
|
||||
random_variable &= STACK_RND_MASK;
|
||||
random_variable <<= PAGE_SHIFT;
|
||||
}
|
||||
|
|
|
@ -3020,6 +3020,7 @@ char *d_absolute_path(const struct path *path,
|
|||
return ERR_PTR(error);
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL(d_absolute_path);
|
||||
|
||||
/*
|
||||
* same as __d_path but appends "(deleted)" for unlinked files.
|
||||
|
|
|
@ -702,6 +702,8 @@ SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname,
|
|||
struct fsnotify_group *group;
|
||||
struct inode *inode;
|
||||
struct path path;
|
||||
struct path alteredpath;
|
||||
struct path *canonical_path = &path;
|
||||
struct fd f;
|
||||
int ret;
|
||||
unsigned flags = 0;
|
||||
|
@ -741,13 +743,22 @@ SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname,
|
|||
if (ret)
|
||||
goto fput_and_out;
|
||||
|
||||
/* support stacked filesystems */
|
||||
if(path.dentry && path.dentry->d_op) {
|
||||
if (path.dentry->d_op->d_canonical_path) {
|
||||
path.dentry->d_op->d_canonical_path(path.dentry, &alteredpath);
|
||||
canonical_path = &alteredpath;
|
||||
path_put(&path);
|
||||
}
|
||||
}
|
||||
|
||||
/* inode held in place by reference to path; group by fget on fd */
|
||||
inode = path.dentry->d_inode;
|
||||
inode = canonical_path->dentry->d_inode;
|
||||
group = f.file->private_data;
|
||||
|
||||
/* create/update an inode mark */
|
||||
ret = inotify_update_watch(group, inode, mask);
|
||||
path_put(&path);
|
||||
path_put(canonical_path);
|
||||
fput_and_out:
|
||||
fdput(f);
|
||||
return ret;
|
||||
|
|
|
@ -554,7 +554,7 @@ static int ramoops_parse_dt(struct platform_device *pdev,
|
|||
static int ramoops_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct ramoops_platform_data *pdata = platform_get_drvdata(pdev);
|
||||
struct ramoops_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct ramoops_context *cxt = &oops_cxt;
|
||||
size_t dump_mem_sz;
|
||||
phys_addr_t paddr;
|
||||
|
@ -666,7 +666,6 @@ static int ramoops_probe(struct platform_device *pdev)
|
|||
cxt->size, (unsigned long long)cxt->phys_addr,
|
||||
cxt->ecc_info.ecc_size, cxt->ecc_info.block_size);
|
||||
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
return 0;
|
||||
|
||||
fail_buf:
|
||||
|
|
13
fs/sdcardfs/Kconfig
Normal file
13
fs/sdcardfs/Kconfig
Normal file
|
@ -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.
|
7
fs/sdcardfs/Makefile
Normal file
7
fs/sdcardfs/Makefile
Normal file
|
@ -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
|
182
fs/sdcardfs/dentry.c
Normal file
182
fs/sdcardfs/dentry.c
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
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)) {
|
||||
d_drop(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);
|
||||
|
||||
spin_lock(&lower_dentry->d_lock);
|
||||
if (d_unhashed(lower_dentry)) {
|
||||
spin_unlock(&lower_dentry->d_lock);
|
||||
d_drop(dentry);
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
spin_unlock(&lower_dentry->d_lock);
|
||||
|
||||
if (parent_lower_dentry != lower_cur_parent_dentry) {
|
||||
d_drop(dentry);
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dentry < lower_dentry) {
|
||||
spin_lock(&dentry->d_lock);
|
||||
spin_lock(&lower_dentry->d_lock);
|
||||
} else {
|
||||
spin_lock(&lower_dentry->d_lock);
|
||||
spin_lock(&dentry->d_lock);
|
||||
}
|
||||
|
||||
if (dentry->d_name.len != lower_dentry->d_name.len) {
|
||||
__d_drop(dentry);
|
||||
err = 0;
|
||||
} else if (strncasecmp(dentry->d_name.name, lower_dentry->d_name.name,
|
||||
dentry->d_name.len) != 0) {
|
||||
__d_drop(dentry);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void sdcardfs_d_release(struct dentry *dentry)
|
||||
{
|
||||
/* 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);
|
||||
return;
|
||||
}
|
||||
|
||||
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 = vfat_striptail_len(qstr);
|
||||
len = qstr->len;
|
||||
|
||||
hash = init_name_hash();
|
||||
while (len--)
|
||||
//hash = partial_name_hash(nls_tolower(t, *name++), hash);
|
||||
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 *parent,
|
||||
const struct dentry *dentry,
|
||||
unsigned int len, const char *str, const struct qstr *name)
|
||||
{
|
||||
/* This function is copy of vfat_cmpi */
|
||||
// FIXME Should we support national language?
|
||||
//struct nls_table *t = MSDOS_SB(parent->d_sb)->nls_io;
|
||||
//unsigned int alen, blen;
|
||||
|
||||
/* A filename cannot end in '.' or we treat it like it has none */
|
||||
/*
|
||||
alen = vfat_striptail_len(name);
|
||||
blen = __vfat_striptail_len(len, str);
|
||||
if (alen == blen) {
|
||||
if (nls_strnicmp(t, name->name, str, alen) == 0)
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
if (name->len == len) {
|
||||
if (strncasecmp(name->name, str, len) == 0)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct dentry_operations sdcardfs_ci_dops = {
|
||||
.d_revalidate = sdcardfs_d_revalidate,
|
||||
.d_release = sdcardfs_d_release,
|
||||
.d_hash = sdcardfs_hash_ci,
|
||||
.d_compare = sdcardfs_cmp_ci,
|
||||
.d_canonical_path = sdcardfs_get_real_lower,
|
||||
};
|
||||
|
265
fs/sdcardfs/derived_perm.c
Normal file
265
fs/sdcardfs/derived_perm.c
Normal file
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* 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->perm = PERM_INHERIT;
|
||||
ci->userid = pi->userid;
|
||||
ci->d_uid = pi->d_uid;
|
||||
ci->under_android = pi->under_android;
|
||||
}
|
||||
|
||||
/* helper function for derived state */
|
||||
void setup_derived_state(struct inode *inode, perm_t perm,
|
||||
userid_t userid, uid_t uid, bool under_android)
|
||||
{
|
||||
struct sdcardfs_inode_info *info = SDCARDFS_I(inode);
|
||||
|
||||
info->perm = perm;
|
||||
info->userid = userid;
|
||||
info->d_uid = uid;
|
||||
info->under_android = under_android;
|
||||
}
|
||||
|
||||
/* 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, struct dentry *newdentry)
|
||||
{
|
||||
struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb);
|
||||
struct sdcardfs_inode_info *info = SDCARDFS_I(dentry->d_inode);
|
||||
struct sdcardfs_inode_info *parent_info= SDCARDFS_I(parent->d_inode);
|
||||
appid_t appid;
|
||||
|
||||
/* 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.
|
||||
* The derived state will be updated on the last
|
||||
* stage of each system call by fix_derived_permission(inode).
|
||||
*/
|
||||
|
||||
inherit_derived_state(parent->d_inode, dentry->d_inode);
|
||||
|
||||
/* Derive custom permissions based on parent and current node */
|
||||
switch (parent_info->perm) {
|
||||
case PERM_INHERIT:
|
||||
/* Already inherited above */
|
||||
break;
|
||||
case PERM_PRE_ROOT:
|
||||
/* Legacy internal layout places users at top level */
|
||||
info->perm = PERM_ROOT;
|
||||
info->userid = simple_strtoul(newdentry->d_name.name, NULL, 10);
|
||||
break;
|
||||
case PERM_ROOT:
|
||||
/* Assume masked off by default. */
|
||||
if (!strcasecmp(newdentry->d_name.name, "Android")) {
|
||||
/* App-specific directories inside; let anyone traverse */
|
||||
info->perm = PERM_ANDROID;
|
||||
info->under_android = true;
|
||||
}
|
||||
break;
|
||||
case PERM_ANDROID:
|
||||
if (!strcasecmp(newdentry->d_name.name, "data")) {
|
||||
/* App-specific directories inside; let anyone traverse */
|
||||
info->perm = PERM_ANDROID_DATA;
|
||||
} else if (!strcasecmp(newdentry->d_name.name, "obb")) {
|
||||
/* App-specific directories inside; let anyone traverse */
|
||||
info->perm = PERM_ANDROID_OBB;
|
||||
/* Single OBB directory is always shared */
|
||||
} else if (!strcasecmp(newdentry->d_name.name, "media")) {
|
||||
/* App-specific directories inside; let anyone traverse */
|
||||
info->perm = PERM_ANDROID_MEDIA;
|
||||
}
|
||||
break;
|
||||
case PERM_ANDROID_DATA:
|
||||
case PERM_ANDROID_OBB:
|
||||
case PERM_ANDROID_MEDIA:
|
||||
appid = get_appid(sbi->pkgl_id, newdentry->d_name.name);
|
||||
if (appid != 0) {
|
||||
info->d_uid = multiuser_get_uid(parent_info->userid, appid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void get_derived_permission(struct dentry *parent, struct dentry *dentry)
|
||||
{
|
||||
get_derived_permission_new(parent, dentry, dentry);
|
||||
}
|
||||
|
||||
void get_derive_permissions_recursive(struct dentry *parent) {
|
||||
struct dentry *dentry;
|
||||
list_for_each_entry(dentry, &parent->d_subdirs, d_child) {
|
||||
if (dentry && dentry->d_inode) {
|
||||
mutex_lock(&dentry->d_inode->i_mutex);
|
||||
get_derived_permission(parent, dentry);
|
||||
fix_derived_permission(dentry->d_inode);
|
||||
get_derive_permissions_recursive(dentry);
|
||||
mutex_unlock(&dentry->d_inode->i_mutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* main function for updating derived permission */
|
||||
inline void update_derived_permission_lock(struct dentry *dentry)
|
||||
{
|
||||
struct dentry *parent;
|
||||
|
||||
if(!dentry || !dentry->d_inode) {
|
||||
printk(KERN_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
|
||||
*/
|
||||
mutex_lock(&dentry->d_inode->i_mutex);
|
||||
if(IS_ROOT(dentry)) {
|
||||
//setup_default_pre_root_state(dentry->d_inode);
|
||||
} else {
|
||||
parent = dget_parent(dentry);
|
||||
if(parent) {
|
||||
get_derived_permission(parent, dentry);
|
||||
dput(parent);
|
||||
}
|
||||
}
|
||||
fix_derived_permission(dentry->d_inode);
|
||||
mutex_unlock(&dentry->d_inode->i_mutex);
|
||||
}
|
||||
|
||||
int need_graft_path(struct dentry *dentry)
|
||||
{
|
||||
int ret = 0;
|
||||
struct dentry *parent = dget_parent(dentry);
|
||||
struct sdcardfs_inode_info *parent_info= SDCARDFS_I(parent->d_inode);
|
||||
struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb);
|
||||
|
||||
if(parent_info->perm == PERM_ANDROID &&
|
||||
!strcasecmp(dentry->d_name.name, "obb")) {
|
||||
|
||||
/* /Android/obb is the base obbpath of DERIVED_UNIFIED */
|
||||
if(!(sbi->options.multiuser == false
|
||||
&& parent_info->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;
|
||||
|
||||
/* 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);
|
||||
//lower_parent = lock_parent(lower_path->dentry);
|
||||
|
||||
path_buf = kmalloc(PATH_MAX, GFP_ATOMIC);
|
||||
if(!path_buf) {
|
||||
ret = 1;
|
||||
printk(KERN_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) ||
|
||||
strcasecmp(sbi->obbpath_s, obbpath_s)) {
|
||||
ret = 1;
|
||||
}
|
||||
kfree(path_buf);
|
||||
}
|
||||
|
||||
//unlock_dir(lower_parent);
|
||||
path_put(&di->lower_path);
|
||||
}
|
||||
}
|
||||
spin_unlock(&di->lock);
|
||||
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(parent->d_inode);
|
||||
struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb);
|
||||
|
||||
spin_lock(&SDCARDFS_D(dentry)->lock);
|
||||
if (sbi->options.multiuser) {
|
||||
if(parent_info->perm == PERM_PRE_ROOT &&
|
||||
!strcasecmp(dentry->d_name.name, "obb")) {
|
||||
ret = 1;
|
||||
}
|
||||
} else if (parent_info->perm == PERM_ANDROID &&
|
||||
!strcasecmp(dentry->d_name.name, "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 */
|
||||
printk(KERN_INFO "sdcardfs: the sbi->obbpath is found\n");
|
||||
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. */
|
||||
printk(KERN_INFO "sdcardfs: the sbi->obbpath is not available\n");
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
356
fs/sdcardfs/file.c
Normal file
356
fs/sdcardfs/file.c
Normal file
|
@ -0,0 +1,356 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/* check disk space */
|
||||
if (!check_min_free_space(dentry, count, 0)) {
|
||||
printk(KERN_INFO "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) {
|
||||
fsstack_copy_inode_size(d_inode(dentry),
|
||||
file_inode(lower_file));
|
||||
fsstack_copy_attr_times(d_inode(dentry),
|
||||
file_inode(lower_file));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
lower_file = sdcardfs_lower_file(file);
|
||||
|
||||
/* XXX: use vfs_ioctl if/when VFS exports it */
|
||||
if (!lower_file || !lower_file->f_op)
|
||||
goto out;
|
||||
if (lower_file->f_op->unlocked_ioctl)
|
||||
err = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg);
|
||||
|
||||
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;
|
||||
|
||||
lower_file = sdcardfs_lower_file(file);
|
||||
|
||||
/* XXX: use vfs_ioctl if/when VFS exports it */
|
||||
if (!lower_file || !lower_file->f_op)
|
||||
goto out;
|
||||
if (lower_file->f_op->compat_ioctl)
|
||||
err = lower_file->f_op->compat_ioctl(lower_file, cmd, arg);
|
||||
|
||||
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;
|
||||
printk(KERN_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) {
|
||||
printk(KERN_ERR "sdcardfs: lower mmap failed %d\n", err);
|
||||
goto out;
|
||||
}
|
||||
saved_vm_ops = vma->vm_ops; /* save: came from lower ->mmap */
|
||||
err = do_munmap(current->mm, vma->vm_start,
|
||||
vma->vm_end - vma->vm_start);
|
||||
if (err) {
|
||||
printk(KERN_ERR "sdcardfs: do_munmap failed %d\n", err);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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(parent->d_inode, dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED(sbi, saved_cred);
|
||||
|
||||
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_CRED(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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
/* trimmed directory options */
|
||||
const struct file_operations sdcardfs_dir_fops = {
|
||||
.llseek = generic_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,
|
||||
};
|
802
fs/sdcardfs/inode.c
Normal file
802
fs/sdcardfs/inode.c
Normal file
|
@ -0,0 +1,802 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
/* Do not directly use this function. Use OVERRIDE_CRED() instead. */
|
||||
const struct cred * override_fsids(struct sdcardfs_sb_info* sbi)
|
||||
{
|
||||
struct cred * cred;
|
||||
const struct cred * old_cred;
|
||||
|
||||
cred = prepare_creds();
|
||||
if (!cred)
|
||||
return NULL;
|
||||
|
||||
cred->fsuid = make_kuid(&init_user_ns, sbi->options.fs_low_uid);
|
||||
cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid);
|
||||
|
||||
old_cred = override_creds(cred);
|
||||
|
||||
return old_cred;
|
||||
}
|
||||
|
||||
/* Do not directly use this function, use REVERT_CRED() instead. */
|
||||
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 dentry *lower_parent_dentry = NULL;
|
||||
struct path lower_path;
|
||||
const struct cred *saved_cred = NULL;
|
||||
|
||||
if(!check_caller_access_to_name(dir, dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
goto out_eacces;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred);
|
||||
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
lower_parent_dentry = lock_parent(lower_dentry);
|
||||
|
||||
/* set last 16bytes of mode field to 0664 */
|
||||
mode = (mode & S_IFMT) | 00664;
|
||||
err = vfs_create(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)->userid);
|
||||
if (err)
|
||||
goto out;
|
||||
fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir));
|
||||
fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));
|
||||
|
||||
out:
|
||||
unlock_dir(lower_parent_dentry);
|
||||
sdcardfs_put_lower_path(dentry, &lower_path);
|
||||
REVERT_CRED(saved_cred);
|
||||
out_eacces:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int sdcardfs_link(struct dentry *old_dentry, struct inode *dir,
|
||||
struct dentry *new_dentry)
|
||||
{
|
||||
struct dentry *lower_old_dentry;
|
||||
struct dentry *lower_new_dentry;
|
||||
struct dentry *lower_dir_dentry;
|
||||
u64 file_size_save;
|
||||
int err;
|
||||
struct path lower_old_path, lower_new_path;
|
||||
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb));
|
||||
|
||||
file_size_save = i_size_read(d_inode(old_dentry));
|
||||
sdcardfs_get_lower_path(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_dir_dentry = lock_parent(lower_new_dentry);
|
||||
|
||||
err = vfs_link(lower_old_dentry, d_inode(lower_dir_dentry),
|
||||
lower_new_dentry, NULL);
|
||||
if (err || !d_inode(lower_new_dentry))
|
||||
goto out;
|
||||
|
||||
err = sdcardfs_interpose(new_dentry, dir->i_sb, &lower_new_path);
|
||||
if (err)
|
||||
goto out;
|
||||
fsstack_copy_attr_times(dir, d_inode(lower_new_dentry));
|
||||
fsstack_copy_inode_size(dir, d_inode(lower_new_dentry));
|
||||
set_nlink(d_inode(old_dentry),
|
||||
sdcardfs_lower_inode(d_inode(old_dentry))->i_nlink);
|
||||
i_size_write(d_inode(new_dentry), file_size_save);
|
||||
out:
|
||||
unlock_dir(lower_dir_dentry);
|
||||
sdcardfs_put_lower_path(old_dentry, &lower_old_path);
|
||||
sdcardfs_put_lower_path(new_dentry, &lower_new_path);
|
||||
REVERT_CRED();
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int sdcardfs_unlink(struct inode *dir, struct dentry *dentry)
|
||||
{
|
||||
int err;
|
||||
struct dentry *lower_dentry;
|
||||
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.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
goto out_eacces;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred);
|
||||
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
dget(lower_dentry);
|
||||
lower_dir_dentry = lock_parent(lower_dentry);
|
||||
|
||||
err = vfs_unlink(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_CRED(saved_cred);
|
||||
out_eacces:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int sdcardfs_symlink(struct inode *dir, struct dentry *dentry,
|
||||
const char *symname)
|
||||
{
|
||||
int err;
|
||||
struct dentry *lower_dentry;
|
||||
struct dentry *lower_parent_dentry = NULL;
|
||||
struct path lower_path;
|
||||
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb));
|
||||
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
lower_parent_dentry = lock_parent(lower_dentry);
|
||||
|
||||
err = vfs_symlink(d_inode(lower_parent_dentry), lower_dentry, symname);
|
||||
if (err)
|
||||
goto out;
|
||||
err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path);
|
||||
if (err)
|
||||
goto out;
|
||||
fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir));
|
||||
fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));
|
||||
|
||||
out:
|
||||
unlock_dir(lower_parent_dentry);
|
||||
sdcardfs_put_lower_path(dentry, &lower_path);
|
||||
REVERT_CRED();
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
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 {
|
||||
printk(KERN_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 dentry *lower_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_info *pi = SDCARDFS_I(dir);
|
||||
char *page_buf;
|
||||
char *nomedia_dir_name;
|
||||
char *nomedia_fullpath;
|
||||
int fullpath_namelen;
|
||||
int touch_err = 0;
|
||||
|
||||
if(!check_caller_access_to_name(dir, dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
goto out_eacces;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred);
|
||||
|
||||
/* check disk space */
|
||||
if (!check_min_free_space(dentry, 0, 1)) {
|
||||
printk(KERN_INFO "sdcardfs: No minimum free space.\n");
|
||||
err = -ENOSPC;
|
||||
goto out_revert;
|
||||
}
|
||||
|
||||
/* the lower_dentry is negative here */
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
lower_parent_dentry = lock_parent(lower_dentry);
|
||||
|
||||
/* set last 16bytes of mode field to 0775 */
|
||||
mode = (mode & S_IFMT) | 00775;
|
||||
err = vfs_mkdir(d_inode(lower_parent_dentry), lower_dentry, mode);
|
||||
|
||||
if (err)
|
||||
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, pi->userid);
|
||||
if (err)
|
||||
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);
|
||||
|
||||
if ((!sbi->options.multiuser) && (!strcasecmp(dentry->d_name.name, "obb"))
|
||||
&& (pi->perm == PERM_ANDROID) && (pi->userid == 0))
|
||||
make_nomedia_in_obb = 1;
|
||||
|
||||
/* When creating /Android/data and /Android/obb, mark them as .nomedia */
|
||||
if (make_nomedia_in_obb ||
|
||||
((pi->perm == PERM_ANDROID) && (!strcasecmp(dentry->d_name.name, "data")))) {
|
||||
|
||||
page_buf = (char *)__get_free_page(GFP_KERNEL);
|
||||
if (!page_buf) {
|
||||
printk(KERN_ERR "sdcardfs: failed to allocate page buf\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
nomedia_dir_name = d_absolute_path(&lower_path, page_buf, PAGE_SIZE);
|
||||
if (IS_ERR(nomedia_dir_name)) {
|
||||
free_page((unsigned long)page_buf);
|
||||
printk(KERN_ERR "sdcardfs: failed to get .nomedia dir name\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fullpath_namelen = page_buf + PAGE_SIZE - nomedia_dir_name - 1;
|
||||
fullpath_namelen += strlen("/.nomedia");
|
||||
nomedia_fullpath = kzalloc(fullpath_namelen + 1, GFP_KERNEL);
|
||||
if (!nomedia_fullpath) {
|
||||
free_page((unsigned long)page_buf);
|
||||
printk(KERN_ERR "sdcardfs: failed to allocate .nomedia fullpath buf\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
strcpy(nomedia_fullpath, nomedia_dir_name);
|
||||
free_page((unsigned long)page_buf);
|
||||
strcat(nomedia_fullpath, "/.nomedia");
|
||||
touch_err = touch(nomedia_fullpath, 0664);
|
||||
if (touch_err) {
|
||||
printk(KERN_ERR "sdcardfs: failed to touch(%s): %d\n",
|
||||
nomedia_fullpath, touch_err);
|
||||
kfree(nomedia_fullpath);
|
||||
goto out;
|
||||
}
|
||||
kfree(nomedia_fullpath);
|
||||
}
|
||||
out:
|
||||
unlock_dir(lower_parent_dentry);
|
||||
sdcardfs_put_lower_path(dentry, &lower_path);
|
||||
out_revert:
|
||||
REVERT_CRED(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;
|
||||
int err;
|
||||
struct path lower_path;
|
||||
const struct cred *saved_cred = NULL;
|
||||
|
||||
if(!check_caller_access_to_name(dir, dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
goto out_eacces;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred);
|
||||
|
||||
/* 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_dir_dentry = lock_parent(lower_dentry);
|
||||
|
||||
err = vfs_rmdir(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_CRED(saved_cred);
|
||||
out_eacces:
|
||||
return err;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int sdcardfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
|
||||
dev_t dev)
|
||||
{
|
||||
int err;
|
||||
struct dentry *lower_dentry;
|
||||
struct dentry *lower_parent_dentry = NULL;
|
||||
struct path lower_path;
|
||||
|
||||
OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb));
|
||||
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
lower_parent_dentry = lock_parent(lower_dentry);
|
||||
|
||||
err = vfs_mknod(d_inode(lower_parent_dentry), lower_dentry, mode, dev);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path);
|
||||
if (err)
|
||||
goto out;
|
||||
fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir));
|
||||
fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));
|
||||
|
||||
out:
|
||||
unlock_dir(lower_parent_dentry);
|
||||
sdcardfs_put_lower_path(dentry, &lower_path);
|
||||
REVERT_CRED();
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
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 dentry *trap = NULL;
|
||||
struct dentry *new_parent = NULL;
|
||||
struct path lower_old_path, lower_new_path;
|
||||
const struct cred *saved_cred = NULL;
|
||||
|
||||
if(!check_caller_access_to_name(old_dir, old_dentry->d_name.name) ||
|
||||
!check_caller_access_to_name(new_dir, new_dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" new_dentry: %s, task:%s\n",
|
||||
__func__, new_dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
goto out_eacces;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED(SDCARDFS_SB(old_dir->i_sb), saved_cred);
|
||||
|
||||
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_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_rename(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));
|
||||
|
||||
/* update the derived permission of the old_dentry
|
||||
* with its new parent
|
||||
*/
|
||||
new_parent = dget_parent(new_dentry);
|
||||
if(new_parent) {
|
||||
if(d_inode(old_dentry)) {
|
||||
update_derived_permission_lock(old_dentry);
|
||||
}
|
||||
dput(new_parent);
|
||||
}
|
||||
}
|
||||
/* At this point, not all dentry information has been moved, so
|
||||
* we pass along new_dentry for the name.*/
|
||||
mutex_lock(&d_inode(old_dentry)->i_mutex);
|
||||
get_derived_permission_new(new_dentry->d_parent, old_dentry, new_dentry);
|
||||
fix_derived_permission(d_inode(old_dentry));
|
||||
get_derive_permissions_recursive(old_dentry);
|
||||
mutex_unlock(&d_inode(old_dentry)->i_mutex);
|
||||
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_CRED(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(struct inode *inode, int mask)
|
||||
{
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Permission check on sdcardfs inode.
|
||||
* Calling process should have AID_SDCARD_RW permission
|
||||
*/
|
||||
err = generic_permission(inode, mask);
|
||||
|
||||
/* XXX
|
||||
* Original sdcardfs code calls inode_permission(lower_inode,.. )
|
||||
* for checking inode permission. But doing such things here seems
|
||||
* duplicated work, because the functions called after this func,
|
||||
* such as vfs_create, vfs_unlink, vfs_rename, and etc,
|
||||
* does exactly same thing, i.e., they calls inode_permission().
|
||||
* So we just let they do the things.
|
||||
* If there are any security hole, just uncomment following if block.
|
||||
*/
|
||||
#if 0
|
||||
if (!err) {
|
||||
/*
|
||||
* Permission check on lower_inode(=EXT4).
|
||||
* we check it with AID_MEDIA_RW permission
|
||||
*/
|
||||
struct inode *lower_inode;
|
||||
OVERRIDE_CRED(SDCARDFS_SB(inode->sb));
|
||||
|
||||
lower_inode = sdcardfs_lower_inode(inode);
|
||||
err = inode_permission(lower_inode, mask);
|
||||
|
||||
REVERT_CRED();
|
||||
}
|
||||
#endif
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
static int sdcardfs_setattr(struct dentry *dentry, struct iattr *ia)
|
||||
{
|
||||
int err;
|
||||
struct dentry *lower_dentry;
|
||||
struct inode *inode;
|
||||
struct inode *lower_inode;
|
||||
struct path lower_path;
|
||||
struct iattr lower_ia;
|
||||
struct dentry *parent;
|
||||
|
||||
inode = d_inode(dentry);
|
||||
|
||||
/*
|
||||
* Check if user has permission to change inode. We don't check if
|
||||
* this user can change the lower inode: that should happen when
|
||||
* calling notify_change on the lower inode.
|
||||
*/
|
||||
err = inode_change_ok(inode, ia);
|
||||
|
||||
/* no vfs_XXX operations required, cred overriding will be skipped. wj*/
|
||||
if (!err) {
|
||||
/* check the Android group ID */
|
||||
parent = dget_parent(dentry);
|
||||
if(!check_caller_access_to_name(d_inode(parent), dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
err = -EACCES;
|
||||
}
|
||||
dput(parent);
|
||||
}
|
||||
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
lower_inode = sdcardfs_lower_inode(inode);
|
||||
|
||||
/* prepare our own lower struct iattr (with the lower file) */
|
||||
memcpy(&lower_ia, ia, sizeof(lower_ia));
|
||||
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 (current->mm)
|
||||
down_write(¤t->mm->mmap_sem);
|
||||
if (ia->ia_valid & ATTR_SIZE) {
|
||||
err = inode_newsize_ok(inode, ia->ia_size);
|
||||
if (err) {
|
||||
if (current->mm)
|
||||
up_write(¤t->mm->mmap_sem);
|
||||
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.
|
||||
*/
|
||||
mutex_lock(&d_inode(lower_dentry)->i_mutex);
|
||||
err = notify_change(lower_dentry, &lower_ia, /* note: lower_ia */
|
||||
NULL);
|
||||
mutex_unlock(&d_inode(lower_dentry)->i_mutex);
|
||||
if (current->mm)
|
||||
up_write(¤t->mm->mmap_sem);
|
||||
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);
|
||||
out_err:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int sdcardfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
||||
struct kstat *stat)
|
||||
{
|
||||
struct dentry *lower_dentry;
|
||||
struct inode *inode;
|
||||
struct inode *lower_inode;
|
||||
struct path lower_path;
|
||||
struct dentry *parent;
|
||||
|
||||
parent = dget_parent(dentry);
|
||||
if(!check_caller_access_to_name(d_inode(parent), dentry->d_name.name)) {
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
dput(parent);
|
||||
return -EACCES;
|
||||
}
|
||||
dput(parent);
|
||||
|
||||
inode = d_inode(dentry);
|
||||
|
||||
sdcardfs_get_lower_path(dentry, &lower_path);
|
||||
lower_dentry = lower_path.dentry;
|
||||
lower_inode = sdcardfs_lower_inode(inode);
|
||||
|
||||
|
||||
sdcardfs_copy_and_fix_attrs(inode, lower_inode);
|
||||
fsstack_copy_inode_size(inode, lower_inode);
|
||||
|
||||
|
||||
generic_fillattr(inode, stat);
|
||||
sdcardfs_put_lower_path(dentry, &lower_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct inode_operations sdcardfs_symlink_iops = {
|
||||
.permission = sdcardfs_permission,
|
||||
.setattr = 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,
|
||||
#if 0
|
||||
.permission = sdcardfs_permission,
|
||||
#endif
|
||||
.unlink = sdcardfs_unlink,
|
||||
.mkdir = sdcardfs_mkdir,
|
||||
.rmdir = sdcardfs_rmdir,
|
||||
.rename = sdcardfs_rename,
|
||||
.setattr = sdcardfs_setattr,
|
||||
.getattr = sdcardfs_getattr,
|
||||
/* XXX Following operations are implemented,
|
||||
* but FUSE(sdcard) or FAT does not support them
|
||||
* These methods are *NOT* perfectly tested.
|
||||
.symlink = sdcardfs_symlink,
|
||||
.link = sdcardfs_link,
|
||||
.mknod = sdcardfs_mknod,
|
||||
*/
|
||||
};
|
||||
|
||||
const struct inode_operations sdcardfs_main_iops = {
|
||||
.permission = sdcardfs_permission,
|
||||
.setattr = sdcardfs_setattr,
|
||||
.getattr = sdcardfs_getattr,
|
||||
};
|
384
fs/sdcardfs/lookup.c
Normal file
384
fs/sdcardfs/lookup.c
Normal file
|
@ -0,0 +1,384 @@
|
|||
/*
|
||||
* 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)
|
||||
{
|
||||
if (sdcardfs_dentry_cachep)
|
||||
kmem_cache_destroy(sdcardfs_dentry_cachep);
|
||||
}
|
||||
|
||||
void free_dentry_private_data(struct dentry *dentry)
|
||||
{
|
||||
if (!dentry || !dentry->d_fsdata)
|
||||
return;
|
||||
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)->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 */
|
||||
int err;
|
||||
|
||||
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) {
|
||||
err = -EACCES;
|
||||
iput(lower_inode);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
/* if found a cached inode, then just return it */
|
||||
if (!(inode->i_state & I_NEW))
|
||||
return inode;
|
||||
|
||||
/* initialize new inode */
|
||||
info = SDCARDFS_I(inode);
|
||||
|
||||
inode->i_ino = lower_inode->i_ino;
|
||||
if (!igrab(lower_inode)) {
|
||||
err = -ESTALE;
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
sdcardfs_set_lower_inode(inode, lower_inode);
|
||||
|
||||
inode->i_version++;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connect a 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)
|
||||
{
|
||||
int err = 0;
|
||||
struct inode *inode;
|
||||
struct inode *lower_inode;
|
||||
struct super_block *lower_sb;
|
||||
|
||||
lower_inode = lower_path->dentry->d_inode;
|
||||
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) {
|
||||
err = -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)) {
|
||||
err = PTR_ERR(inode);
|
||||
goto out;
|
||||
}
|
||||
|
||||
d_add(dentry, inode);
|
||||
update_derived_permission_lock(dentry);
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 char *name;
|
||||
struct path lower_path;
|
||||
struct qstr this;
|
||||
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.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, 0,
|
||||
&lower_path);
|
||||
|
||||
/* 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. */
|
||||
printk(KERN_INFO "sdcardfs: base obbpath is not available\n");
|
||||
sdcardfs_put_reset_orig_path(dentry);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
sdcardfs_set_lower_path(dentry, &lower_path);
|
||||
err = sdcardfs_interpose(dentry, dentry->d_sb, &lower_path, id);
|
||||
if (err) /* 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 */
|
||||
this.name = name;
|
||||
this.len = strlen(name);
|
||||
this.hash = full_name_hash(this.name, this.len);
|
||||
lower_dentry = d_lookup(lower_dir_dentry, &this);
|
||||
if (lower_dentry)
|
||||
goto setup_lower;
|
||||
|
||||
lower_dentry = d_alloc(lower_dir_dentry, &this);
|
||||
if (!lower_dentry) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
d_add(lower_dentry, NULL); /* instantiate and hash */
|
||||
|
||||
setup_lower:
|
||||
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:
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
/*
|
||||
* On success:
|
||||
* fills dentry object appropriate values and returns NULL.
|
||||
* On fail (== error)
|
||||
* returns error ptr
|
||||
*
|
||||
* @dir : Parent inode. It is locked (dir->i_mutex)
|
||||
* @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(parent->d_inode, dentry->d_name.name)) {
|
||||
ret = ERR_PTR(-EACCES);
|
||||
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
|
||||
" dentry: %s, task:%s\n",
|
||||
__func__, dentry->d_name.name, current->comm);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* save current_cred and override it */
|
||||
OVERRIDE_CRED_PTR(SDCARDFS_SB(dir->i_sb), saved_cred);
|
||||
|
||||
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)->userid);
|
||||
if (IS_ERR(ret))
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
if (ret)
|
||||
dentry = ret;
|
||||
if (dentry->d_inode) {
|
||||
fsstack_copy_attr_times(dentry->d_inode,
|
||||
sdcardfs_lower_inode(dentry->d_inode));
|
||||
/* get drived permission */
|
||||
mutex_lock(&dentry->d_inode->i_mutex);
|
||||
get_derived_permission(parent, dentry);
|
||||
fix_derived_permission(dentry->d_inode);
|
||||
mutex_unlock(&dentry->d_inode->i_mutex);
|
||||
}
|
||||
/* update parent directory's atime */
|
||||
fsstack_copy_attr_atime(parent->d_inode,
|
||||
sdcardfs_lower_inode(parent->d_inode));
|
||||
|
||||
out:
|
||||
sdcardfs_put_lower_path(parent, &lower_parent_path);
|
||||
REVERT_CRED(saved_cred);
|
||||
out_err:
|
||||
dput(parent);
|
||||
return ret;
|
||||
}
|
402
fs/sdcardfs/main.c
Normal file
402
fs/sdcardfs/main.c
Normal file
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* 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/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/parser.h>
|
||||
|
||||
enum {
|
||||
Opt_fsuid,
|
||||
Opt_fsgid,
|
||||
Opt_gid,
|
||||
Opt_debug,
|
||||
Opt_lower_fs,
|
||||
Opt_mask,
|
||||
Opt_multiuser, // May need?
|
||||
Opt_userid,
|
||||
Opt_reserved_mb,
|
||||
Opt_err,
|
||||
};
|
||||
|
||||
static const match_table_t sdcardfs_tokens = {
|
||||
{Opt_fsuid, "fsuid=%u"},
|
||||
{Opt_fsgid, "fsgid=%u"},
|
||||
{Opt_gid, "gid=%u"},
|
||||
{Opt_debug, "debug"},
|
||||
{Opt_mask, "mask=%u"},
|
||||
{Opt_userid, "userid=%d"},
|
||||
{Opt_multiuser, "multiuser"},
|
||||
{Opt_reserved_mb, "reserved_mb=%u"},
|
||||
{Opt_err, NULL}
|
||||
};
|
||||
|
||||
static int parse_options(struct super_block *sb, char *options, int silent,
|
||||
int *debug, struct sdcardfs_mount_options *opts)
|
||||
{
|
||||
char *p;
|
||||
substring_t args[MAX_OPT_ARGS];
|
||||
int option;
|
||||
|
||||
/* by default, we use AID_MEDIA_RW as uid, gid */
|
||||
opts->fs_low_uid = AID_MEDIA_RW;
|
||||
opts->fs_low_gid = AID_MEDIA_RW;
|
||||
opts->mask = 0;
|
||||
opts->multiuser = false;
|
||||
opts->fs_user_id = 0;
|
||||
opts->gid = 0;
|
||||
/* by default, 0MB is reserved */
|
||||
opts->reserved_mb = 0;
|
||||
|
||||
*debug = 0;
|
||||
|
||||
if (!options)
|
||||
return 0;
|
||||
|
||||
while ((p = strsep(&options, ",")) != NULL) {
|
||||
int token;
|
||||
if (!*p)
|
||||
continue;
|
||||
|
||||
token = match_token(p, sdcardfs_tokens, args);
|
||||
|
||||
switch (token) {
|
||||
case Opt_debug:
|
||||
*debug = 1;
|
||||
break;
|
||||
case Opt_fsuid:
|
||||
if (match_int(&args[0], &option))
|
||||
return 0;
|
||||
opts->fs_low_uid = option;
|
||||
break;
|
||||
case Opt_fsgid:
|
||||
if (match_int(&args[0], &option))
|
||||
return 0;
|
||||
opts->fs_low_gid = option;
|
||||
break;
|
||||
case Opt_gid:
|
||||
if (match_int(&args[0], &option))
|
||||
return 0;
|
||||
opts->gid = option;
|
||||
break;
|
||||
case Opt_userid:
|
||||
if (match_int(&args[0], &option))
|
||||
return 0;
|
||||
opts->fs_user_id = option;
|
||||
break;
|
||||
case Opt_mask:
|
||||
if (match_int(&args[0], &option))
|
||||
return 0;
|
||||
opts->mask = option;
|
||||
break;
|
||||
case Opt_multiuser:
|
||||
opts->multiuser = true;
|
||||
break;
|
||||
case Opt_reserved_mb:
|
||||
if (match_int(&args[0], &option))
|
||||
return 0;
|
||||
opts->reserved_mb = option;
|
||||
break;
|
||||
/* unknown option */
|
||||
default:
|
||||
if (!silent) {
|
||||
printk( KERN_ERR "Unrecognized mount option \"%s\" "
|
||||
"or missing value", p);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (*debug) {
|
||||
printk( KERN_INFO "sdcardfs : options - debug:%d\n", *debug);
|
||||
printk( KERN_INFO "sdcardfs : options - uid:%d\n",
|
||||
opts->fs_low_uid);
|
||||
printk( KERN_INFO "sdcardfs : options - gid:%d\n",
|
||||
opts->fs_low_gid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#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);
|
||||
LIST_HEAD(sdcardfs_super_list);
|
||||
EXPORT_SYMBOL_GPL(sdcardfs_super_list_lock);
|
||||
EXPORT_SYMBOL_GPL(sdcardfs_super_list);
|
||||
|
||||
/*
|
||||
* There is no need to lock the sdcardfs_super_info's rwsem as there is no
|
||||
* way anyone can have a reference to the superblock at this point in time.
|
||||
*/
|
||||
static int sdcardfs_read_super(struct super_block *sb, const char *dev_name,
|
||||
void *raw_data, int silent)
|
||||
{
|
||||
int err = 0;
|
||||
int debug;
|
||||
struct super_block *lower_sb;
|
||||
struct path lower_path;
|
||||
struct sdcardfs_sb_info *sb_info;
|
||||
struct inode *inode;
|
||||
|
||||
printk(KERN_INFO "sdcardfs version 2.0\n");
|
||||
|
||||
if (!dev_name) {
|
||||
printk(KERN_ERR
|
||||
"sdcardfs: read_super: missing dev_name argument\n");
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "sdcardfs: dev_name -> %s\n", dev_name);
|
||||
printk(KERN_INFO "sdcardfs: options -> %s\n", (char *)raw_data);
|
||||
|
||||
/* parse lower path */
|
||||
err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
|
||||
&lower_path);
|
||||
if (err) {
|
||||
printk(KERN_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)) {
|
||||
printk(KERN_CRIT "sdcardfs: read_super: out of memory\n");
|
||||
err = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
sb_info = sb->s_fs_info;
|
||||
/* parse options */
|
||||
err = parse_options(sb, raw_data, silent, &debug, &sb_info->options);
|
||||
if (err) {
|
||||
printk(KERN_ERR "sdcardfs: invalid options\n");
|
||||
goto out_freesbi;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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, lower_path.dentry->d_inode, 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_iput;
|
||||
}
|
||||
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(sb->s_root->d_inode, PERM_PRE_ROOT, sb_info->options.fs_user_id, AID_ROOT, false);
|
||||
snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name);
|
||||
/*err = prepare_dir(sb_info->obbpath_s,
|
||||
sb_info->options.fs_low_uid,
|
||||
sb_info->options.fs_low_gid, 00755);*/
|
||||
} else {
|
||||
setup_derived_state(sb->s_root->d_inode, PERM_ROOT, sb_info->options.fs_low_uid, AID_ROOT, false);
|
||||
snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name);
|
||||
}
|
||||
fix_derived_permission(sb->s_root->d_inode);
|
||||
sb_info->sb = sb;
|
||||
list_add(&sb_info->list, &sdcardfs_super_list);
|
||||
mutex_unlock(&sdcardfs_super_list_lock);
|
||||
|
||||
if (!silent)
|
||||
printk(KERN_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);
|
||||
out_iput:
|
||||
iput(inode);
|
||||
out_sput:
|
||||
/* drop refs we took earlier */
|
||||
atomic_dec(&lower_sb->s_active);
|
||||
out_freesbi:
|
||||
kfree(SDCARDFS_SB(sb));
|
||||
sb->s_fs_info = NULL;
|
||||
out_free:
|
||||
path_put(&lower_path);
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* A feature which supports mount_nodev() with options */
|
||||
static struct dentry *mount_nodev_with_options(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name, void *data,
|
||||
int (*fill_super)(struct super_block *, const char *, void *, int))
|
||||
|
||||
{
|
||||
int error;
|
||||
struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);
|
||||
|
||||
if (IS_ERR(s))
|
||||
return ERR_CAST(s);
|
||||
|
||||
s->s_flags = flags;
|
||||
|
||||
error = fill_super(s, dev_name, data, flags & MS_SILENT ? 1 : 0);
|
||||
if (error) {
|
||||
deactivate_locked_super(s);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
s->s_flags |= MS_ACTIVE;
|
||||
return dget(s->s_root);
|
||||
}
|
||||
|
||||
struct dentry *sdcardfs_mount(struct file_system_type *fs_type, int flags,
|
||||
const char *dev_name, void *raw_data)
|
||||
{
|
||||
/*
|
||||
* dev_name is a lower_path_name,
|
||||
* raw_data is a option string.
|
||||
*/
|
||||
return mount_nodev_with_options(fs_type, flags, dev_name,
|
||||
raw_data, sdcardfs_read_super);
|
||||
}
|
||||
|
||||
void sdcardfs_kill_sb(struct super_block *sb) {
|
||||
struct sdcardfs_sb_info *sbi;
|
||||
if (sb->s_magic == SDCARDFS_SUPER_MAGIC) {
|
||||
sbi = SDCARDFS_SB(sb);
|
||||
mutex_lock(&sdcardfs_super_list_lock);
|
||||
list_del(&sbi->list);
|
||||
mutex_unlock(&sdcardfs_super_list_lock);
|
||||
}
|
||||
generic_shutdown_super(sb);
|
||||
}
|
||||
|
||||
static struct file_system_type sdcardfs_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = SDCARDFS_NAME,
|
||||
.mount = sdcardfs_mount,
|
||||
.kill_sb = sdcardfs_kill_sb,
|
||||
.fs_flags = 0,
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Erez Zadok, Filesystems and Storage Lab, Stony Brook University"
|
||||
" (http://www.fsl.cs.sunysb.edu/)");
|
||||
MODULE_DESCRIPTION("Wrapfs " SDCARDFS_VERSION
|
||||
" (http://wrapfs.filesystems.org/)");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(init_sdcardfs_fs);
|
||||
module_exit(exit_sdcardfs_fs);
|
81
fs/sdcardfs/mmap.c
Normal file
81
fs/sdcardfs/mmap.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 int sdcardfs_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
|
||||
{
|
||||
int err;
|
||||
struct file *file, *lower_file;
|
||||
const struct vm_operations_struct *lower_vm_ops;
|
||||
struct vm_area_struct lower_vma;
|
||||
|
||||
memcpy(&lower_vma, vma, sizeof(struct vm_area_struct));
|
||||
file = lower_vma.vm_file;
|
||||
lower_vm_ops = SDCARDFS_F(file)->lower_vm_ops;
|
||||
BUG_ON(!lower_vm_ops);
|
||||
|
||||
lower_file = sdcardfs_lower_file(file);
|
||||
/*
|
||||
* XXX: vm_ops->fault may be called in parallel. Because we have to
|
||||
* resort to temporarily changing the vma->vm_file to point to the
|
||||
* lower file, a concurrent invocation of sdcardfs_fault could see a
|
||||
* different value. In this workaround, we keep a different copy of
|
||||
* the vma structure in our stack, so we never expose a different
|
||||
* value of the vma->vm_file called to us, even temporarily. A
|
||||
* better fix would be to change the calling semantics of ->fault to
|
||||
* take an explicit file pointer.
|
||||
*/
|
||||
lower_vma.vm_file = lower_file;
|
||||
err = lower_vm_ops->fault(&lower_vma, vmf);
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t sdcardfs_direct_IO(struct kiocb *iocb,
|
||||
struct iov_iter *iter, loff_t pos)
|
||||
{
|
||||
/*
|
||||
* This function returns zero on purpose in order to support direct IO.
|
||||
* __dentry_open checks a_ops->direct_IO and returns EINVAL if it is null.
|
||||
*
|
||||
* However, this function won't be called by certain file operations
|
||||
* including generic fs functions. * reads and writes are delivered to
|
||||
* the lower file systems and the direct IOs will be handled by them.
|
||||
*
|
||||
* NOTE: exceptionally, on the recent kernels (since Linux 3.8.x),
|
||||
* swap_writepage invokes this function directly.
|
||||
*/
|
||||
printk(KERN_INFO "%s, operation is not supported\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: the default address_space_ops for sdcardfs is empty. We cannot set
|
||||
* our inode->i_mapping->a_ops to NULL because too many code paths expect
|
||||
* the a_ops vector to be non-NULL.
|
||||
*/
|
||||
const struct address_space_operations sdcardfs_aops = {
|
||||
/* empty on purpose */
|
||||
.direct_IO = sdcardfs_direct_IO,
|
||||
};
|
||||
|
||||
const struct vm_operations_struct sdcardfs_vm_ops = {
|
||||
.fault = sdcardfs_fault,
|
||||
};
|
37
fs/sdcardfs/multiuser.h
Normal file
37
fs/sdcardfs/multiuser.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 MULTIUSER_APP_PER_USER_RANGE 100000
|
||||
|
||||
typedef uid_t userid_t;
|
||||
typedef uid_t appid_t;
|
||||
|
||||
static inline userid_t multiuser_get_user_id(uid_t uid) {
|
||||
return uid / MULTIUSER_APP_PER_USER_RANGE;
|
||||
}
|
||||
|
||||
static inline appid_t multiuser_get_app_id(uid_t uid) {
|
||||
return uid % MULTIUSER_APP_PER_USER_RANGE;
|
||||
}
|
||||
|
||||
static inline uid_t multiuser_get_uid(userid_t userId, appid_t appId) {
|
||||
return userId * MULTIUSER_APP_PER_USER_RANGE + (appId % MULTIUSER_APP_PER_USER_RANGE);
|
||||
}
|
||||
|
437
fs/sdcardfs/packagelist.c
Normal file
437
fs/sdcardfs/packagelist.c
Normal file
|
@ -0,0 +1,437 @@
|
|||
/*
|
||||
* 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/delay.h>
|
||||
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/configfs.h>
|
||||
|
||||
#define STRING_BUF_SIZE (512)
|
||||
|
||||
struct hashtable_entry {
|
||||
struct hlist_node hlist;
|
||||
void *key;
|
||||
unsigned int value;
|
||||
};
|
||||
|
||||
struct sb_list {
|
||||
struct super_block *sb;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct packagelist_data {
|
||||
DECLARE_HASHTABLE(package_to_appid,8);
|
||||
struct mutex hashtable_lock;
|
||||
|
||||
};
|
||||
|
||||
static struct packagelist_data *pkgl_data_all;
|
||||
|
||||
static struct kmem_cache *hashtable_entry_cachep;
|
||||
|
||||
static unsigned int str_hash(const char *key) {
|
||||
int i;
|
||||
unsigned int h = strlen(key);
|
||||
char *data = (char *)key;
|
||||
|
||||
for (i = 0; i < strlen(key); i++) {
|
||||
h = h * 31 + *data;
|
||||
data++;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
appid_t get_appid(void *pkgl_id, const char *app_name)
|
||||
{
|
||||
struct packagelist_data *pkgl_dat = pkgl_data_all;
|
||||
struct hashtable_entry *hash_cur;
|
||||
unsigned int hash = str_hash(app_name);
|
||||
appid_t ret_id;
|
||||
|
||||
mutex_lock(&pkgl_dat->hashtable_lock);
|
||||
hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) {
|
||||
if (!strcasecmp(app_name, hash_cur->key)) {
|
||||
ret_id = (appid_t)hash_cur->value;
|
||||
mutex_unlock(&pkgl_dat->hashtable_lock);
|
||||
return ret_id;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pkgl_dat->hashtable_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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 char* name) {
|
||||
|
||||
/* Always block security-sensitive files at root */
|
||||
if (parent_node && SDCARDFS_I(parent_node)->perm == PERM_ROOT) {
|
||||
if (!strcasecmp(name, "autorun.inf")
|
||||
|| !strcasecmp(name, ".android_secure")
|
||||
|| !strcasecmp(name, "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;
|
||||
}
|
||||
|
||||
/* This function is used when file opening. The open flags must be
|
||||
* checked before calling check_caller_access_to_name() */
|
||||
int open_flags_to_access_mode(int open_flags) {
|
||||
if((open_flags & O_ACCMODE) == O_RDONLY) {
|
||||
return 0; /* R_OK */
|
||||
} else if ((open_flags & O_ACCMODE) == O_WRONLY) {
|
||||
return 1; /* W_OK */
|
||||
} else {
|
||||
/* Probably O_RDRW, but treat as default to be safe */
|
||||
return 1; /* R_OK | W_OK */
|
||||
}
|
||||
}
|
||||
|
||||
static int insert_str_to_int_lock(struct packagelist_data *pkgl_dat, char *key,
|
||||
unsigned int value)
|
||||
{
|
||||
struct hashtable_entry *hash_cur;
|
||||
struct hashtable_entry *new_entry;
|
||||
unsigned int hash = str_hash(key);
|
||||
|
||||
hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) {
|
||||
if (!strcasecmp(key, hash_cur->key)) {
|
||||
hash_cur->value = value;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
new_entry = kmem_cache_alloc(hashtable_entry_cachep, GFP_KERNEL);
|
||||
if (!new_entry)
|
||||
return -ENOMEM;
|
||||
new_entry->key = kstrdup(key, GFP_KERNEL);
|
||||
new_entry->value = value;
|
||||
hash_add(pkgl_dat->package_to_appid, &new_entry->hlist, hash);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fixup_perms(struct super_block *sb) {
|
||||
if (sb && sb->s_magic == SDCARDFS_SUPER_MAGIC) {
|
||||
mutex_lock(&sb->s_root->d_inode->i_mutex);
|
||||
get_derive_permissions_recursive(sb->s_root);
|
||||
mutex_unlock(&sb->s_root->d_inode->i_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static int insert_str_to_int(struct packagelist_data *pkgl_dat, char *key,
|
||||
unsigned int value) {
|
||||
int ret;
|
||||
struct sdcardfs_sb_info *sbinfo;
|
||||
mutex_lock(&sdcardfs_super_list_lock);
|
||||
mutex_lock(&pkgl_dat->hashtable_lock);
|
||||
ret = insert_str_to_int_lock(pkgl_dat, key, value);
|
||||
mutex_unlock(&pkgl_dat->hashtable_lock);
|
||||
|
||||
list_for_each_entry(sbinfo, &sdcardfs_super_list, list) {
|
||||
if (sbinfo) {
|
||||
fixup_perms(sbinfo->sb);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&sdcardfs_super_list_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void remove_str_to_int_lock(struct hashtable_entry *h_entry) {
|
||||
kfree(h_entry->key);
|
||||
hash_del(&h_entry->hlist);
|
||||
kmem_cache_free(hashtable_entry_cachep, h_entry);
|
||||
}
|
||||
|
||||
static void remove_str_to_int(struct packagelist_data *pkgl_dat, const char *key)
|
||||
{
|
||||
struct sdcardfs_sb_info *sbinfo;
|
||||
struct hashtable_entry *hash_cur;
|
||||
unsigned int hash = str_hash(key);
|
||||
mutex_lock(&sdcardfs_super_list_lock);
|
||||
mutex_lock(&pkgl_dat->hashtable_lock);
|
||||
hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) {
|
||||
if (!strcasecmp(key, hash_cur->key)) {
|
||||
remove_str_to_int_lock(hash_cur);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pkgl_dat->hashtable_lock);
|
||||
list_for_each_entry(sbinfo, &sdcardfs_super_list, list) {
|
||||
if (sbinfo) {
|
||||
fixup_perms(sbinfo->sb);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&sdcardfs_super_list_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
static void remove_all_hashentrys(struct packagelist_data *pkgl_dat)
|
||||
{
|
||||
struct hashtable_entry *hash_cur;
|
||||
struct hlist_node *h_t;
|
||||
int i;
|
||||
mutex_lock(&pkgl_dat->hashtable_lock);
|
||||
hash_for_each_safe(pkgl_dat->package_to_appid, i, h_t, hash_cur, hlist)
|
||||
remove_str_to_int_lock(hash_cur);
|
||||
mutex_unlock(&pkgl_dat->hashtable_lock);
|
||||
hash_init(pkgl_dat->package_to_appid);
|
||||
}
|
||||
|
||||
static struct packagelist_data * packagelist_create(void)
|
||||
{
|
||||
struct packagelist_data *pkgl_dat;
|
||||
|
||||
pkgl_dat = kmalloc(sizeof(*pkgl_dat), GFP_KERNEL | __GFP_ZERO);
|
||||
if (!pkgl_dat) {
|
||||
printk(KERN_ERR "sdcardfs: Failed to create hash\n");
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
mutex_init(&pkgl_dat->hashtable_lock);
|
||||
hash_init(pkgl_dat->package_to_appid);
|
||||
|
||||
return pkgl_dat;
|
||||
}
|
||||
|
||||
static void packagelist_destroy(struct packagelist_data *pkgl_dat)
|
||||
{
|
||||
remove_all_hashentrys(pkgl_dat);
|
||||
printk(KERN_INFO "sdcardfs: destroyed packagelist pkgld\n");
|
||||
kfree(pkgl_dat);
|
||||
}
|
||||
|
||||
struct package_appid {
|
||||
struct config_item item;
|
||||
int add_pid;
|
||||
};
|
||||
|
||||
static inline struct package_appid *to_package_appid(struct config_item *item)
|
||||
{
|
||||
return item ? container_of(item, struct package_appid, item) : NULL;
|
||||
}
|
||||
|
||||
static ssize_t package_appid_attr_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
ssize_t count;
|
||||
count = sprintf(page, "%d\n", get_appid(pkgl_data_all, item->ci_name));
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t package_appid_attr_store(struct config_item *item,
|
||||
const char *page, size_t count)
|
||||
{
|
||||
struct package_appid *package_appid = to_package_appid(item);
|
||||
unsigned long tmp;
|
||||
char *p = (char *) page;
|
||||
int ret;
|
||||
|
||||
tmp = simple_strtoul(p, &p, 10);
|
||||
if (!p || (*p && (*p != '\n')))
|
||||
return -EINVAL;
|
||||
|
||||
if (tmp > INT_MAX)
|
||||
return -ERANGE;
|
||||
ret = insert_str_to_int(pkgl_data_all, item->ci_name, (unsigned int)tmp);
|
||||
package_appid->add_pid = tmp;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct configfs_attribute package_appid_attr_add_pid = {
|
||||
.ca_owner = THIS_MODULE,
|
||||
.ca_name = "appid",
|
||||
.ca_mode = S_IRUGO | S_IWUGO,
|
||||
.show = package_appid_attr_show,
|
||||
.store = package_appid_attr_store,
|
||||
};
|
||||
|
||||
static struct configfs_attribute *package_appid_attrs[] = {
|
||||
&package_appid_attr_add_pid,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void package_appid_release(struct config_item *item)
|
||||
{
|
||||
printk(KERN_INFO "sdcardfs: removing %s\n", item->ci_dentry->d_name.name);
|
||||
/* item->ci_name is freed already, so we rely on the dentry */
|
||||
remove_str_to_int(pkgl_data_all, item->ci_dentry->d_name.name);
|
||||
kfree(to_package_appid(item));
|
||||
}
|
||||
|
||||
static struct configfs_item_operations package_appid_item_ops = {
|
||||
.release = package_appid_release,
|
||||
};
|
||||
|
||||
static struct config_item_type package_appid_type = {
|
||||
.ct_item_ops = &package_appid_item_ops,
|
||||
.ct_attrs = package_appid_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
||||
struct sdcardfs_packages {
|
||||
struct config_group group;
|
||||
};
|
||||
|
||||
static inline struct sdcardfs_packages *to_sdcardfs_packages(struct config_item *item)
|
||||
{
|
||||
return item ? container_of(to_config_group(item), struct sdcardfs_packages, group) : NULL;
|
||||
}
|
||||
|
||||
static struct config_item *sdcardfs_packages_make_item(struct config_group *group, const char *name)
|
||||
{
|
||||
struct package_appid *package_appid;
|
||||
|
||||
package_appid = kzalloc(sizeof(struct package_appid), GFP_KERNEL);
|
||||
if (!package_appid)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
config_item_init_type_name(&package_appid->item, name,
|
||||
&package_appid_type);
|
||||
|
||||
package_appid->add_pid = 0;
|
||||
|
||||
return &package_appid->item;
|
||||
}
|
||||
|
||||
static ssize_t packages_attr_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
struct hashtable_entry *hash_cur;
|
||||
struct hlist_node *h_t;
|
||||
int i;
|
||||
int count = 0;
|
||||
mutex_lock(&pkgl_data_all->hashtable_lock);
|
||||
hash_for_each_safe(pkgl_data_all->package_to_appid, i, h_t, hash_cur, hlist)
|
||||
count += snprintf(page + count, PAGE_SIZE - count, "%s %d\n", (char *)hash_cur->key, hash_cur->value);
|
||||
mutex_unlock(&pkgl_data_all->hashtable_lock);
|
||||
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct configfs_attribute sdcardfs_packages_attr_description = {
|
||||
.ca_owner = THIS_MODULE,
|
||||
.ca_name = "packages_gid.list",
|
||||
.ca_mode = S_IRUGO,
|
||||
.show = packages_attr_show,
|
||||
};
|
||||
|
||||
static struct configfs_attribute *sdcardfs_packages_attrs[] = {
|
||||
&sdcardfs_packages_attr_description,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void sdcardfs_packages_release(struct config_item *item)
|
||||
{
|
||||
|
||||
printk(KERN_INFO "sdcardfs: destroyed something?\n");
|
||||
kfree(to_sdcardfs_packages(item));
|
||||
}
|
||||
|
||||
static struct configfs_item_operations sdcardfs_packages_item_ops = {
|
||||
.release = sdcardfs_packages_release,
|
||||
};
|
||||
|
||||
/*
|
||||
* Note that, since no extra work is required on ->drop_item(),
|
||||
* no ->drop_item() is provided.
|
||||
*/
|
||||
static struct configfs_group_operations sdcardfs_packages_group_ops = {
|
||||
.make_item = sdcardfs_packages_make_item,
|
||||
};
|
||||
|
||||
static struct config_item_type sdcardfs_packages_type = {
|
||||
.ct_item_ops = &sdcardfs_packages_item_ops,
|
||||
.ct_group_ops = &sdcardfs_packages_group_ops,
|
||||
.ct_attrs = sdcardfs_packages_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct configfs_subsystem sdcardfs_packages_subsys = {
|
||||
.su_group = {
|
||||
.cg_item = {
|
||||
.ci_namebuf = "sdcardfs",
|
||||
.ci_type = &sdcardfs_packages_type,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static int configfs_sdcardfs_init(void)
|
||||
{
|
||||
int ret;
|
||||
struct configfs_subsystem *subsys = &sdcardfs_packages_subsys;
|
||||
|
||||
config_group_init(&subsys->su_group);
|
||||
mutex_init(&subsys->su_mutex);
|
||||
ret = configfs_register_subsystem(subsys);
|
||||
if (ret) {
|
||||
printk(KERN_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_subsys);
|
||||
}
|
||||
|
||||
int packagelist_init(void)
|
||||
{
|
||||
hashtable_entry_cachep =
|
||||
kmem_cache_create("packagelist_hashtable_entry",
|
||||
sizeof(struct hashtable_entry), 0, 0, NULL);
|
||||
if (!hashtable_entry_cachep) {
|
||||
printk(KERN_ERR "sdcardfs: failed creating pkgl_hashtable entry slab cache\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pkgl_data_all = packagelist_create();
|
||||
configfs_sdcardfs_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void packagelist_exit(void)
|
||||
{
|
||||
configfs_sdcardfs_exit();
|
||||
packagelist_destroy(pkgl_data_all);
|
||||
if (hashtable_entry_cachep)
|
||||
kmem_cache_destroy(hashtable_entry_cachep);
|
||||
}
|
530
fs/sdcardfs/sdcardfs.h
Normal file
530
fs/sdcardfs/sdcardfs.h
Normal file
|
@ -0,0 +1,530 @@
|
|||
/*
|
||||
* 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/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 "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 printk(KERN_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_PACKAGE_INFO 1027
|
||||
|
||||
#define fix_derived_permission(x) \
|
||||
do { \
|
||||
(x)->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(x)->d_uid); \
|
||||
(x)->i_gid = make_kgid(&init_user_ns, get_gid(SDCARDFS_I(x))); \
|
||||
(x)->i_mode = ((x)->i_mode & S_IFMT) | get_mode(SDCARDFS_I(x));\
|
||||
} while (0)
|
||||
|
||||
|
||||
/* OVERRIDE_CRED() and REVERT_CRED()
|
||||
* OVERRID_CRED()
|
||||
* backup original task->cred
|
||||
* and modifies task->cred->fsuid/fsgid to specified value.
|
||||
* REVERT_CRED()
|
||||
* restore original task->cred->fsuid/fsgid.
|
||||
* These two macro should be used in pair, and OVERRIDE_CRED() should be
|
||||
* placed at the beginning of a function, right after variable declaration.
|
||||
*/
|
||||
#define OVERRIDE_CRED(sdcardfs_sbi, saved_cred) \
|
||||
saved_cred = override_fsids(sdcardfs_sbi); \
|
||||
if (!saved_cred) { return -ENOMEM; }
|
||||
|
||||
#define OVERRIDE_CRED_PTR(sdcardfs_sbi, saved_cred) \
|
||||
saved_cred = override_fsids(sdcardfs_sbi); \
|
||||
if (!saved_cred) { return ERR_PTR(-ENOMEM); }
|
||||
|
||||
#define REVERT_CRED(saved_cred) revert_fsids(saved_cred)
|
||||
|
||||
#define DEBUG_CRED() \
|
||||
printk("KAKJAGI: %s:%d fsuid %d fsgid %d\n", \
|
||||
__FUNCTION__, __LINE__, \
|
||||
(int)current->cred->fsuid, \
|
||||
(int)current->cred->fsgid);
|
||||
|
||||
/* 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,
|
||||
} perm_t;
|
||||
|
||||
struct sdcardfs_sb_info;
|
||||
struct sdcardfs_mount_options;
|
||||
|
||||
/* Do not directly use this function. Use OVERRIDE_CRED() instead. */
|
||||
const struct cred * override_fsids(struct sdcardfs_sb_info* sbi);
|
||||
/* 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;
|
||||
};
|
||||
|
||||
/* sdcardfs inode data in memory */
|
||||
struct sdcardfs_inode_info {
|
||||
struct inode *lower_inode;
|
||||
/* state derived based on current position in hierachy */
|
||||
perm_t perm;
|
||||
userid_t userid;
|
||||
uid_t d_uid;
|
||||
bool under_android;
|
||||
|
||||
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;
|
||||
gid_t gid;
|
||||
mode_t mask;
|
||||
bool multiuser;
|
||||
unsigned int reserved_mb;
|
||||
};
|
||||
|
||||
/* 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 int get_gid(struct sdcardfs_inode_info *info) {
|
||||
struct sdcardfs_sb_info *sb_info = SDCARDFS_SB(info->vfs_inode.i_sb);
|
||||
if (sb_info->options.gid == AID_SDCARD_RW) {
|
||||
/* 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(info->userid, sb_info->options.gid);
|
||||
}
|
||||
}
|
||||
static inline int get_mode(struct sdcardfs_inode_info *info) {
|
||||
int owner_mode;
|
||||
int filtered_mode;
|
||||
struct sdcardfs_sb_info *sb_info = SDCARDFS_SB(info->vfs_inode.i_sb);
|
||||
int visible_mode = 0775 & ~sb_info->options.mask;
|
||||
|
||||
if (info->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 (info->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 (sb_info->options.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(void *pkgl_id, const char *app_name);
|
||||
extern int check_caller_access_to_name(struct inode *parent_node, const char* name);
|
||||
extern int open_flags_to_access_mode(int open_flags);
|
||||
extern int packagelist_init(void);
|
||||
extern void packagelist_exit(void);
|
||||
|
||||
/* for derived_perm.c */
|
||||
extern void setup_derived_state(struct inode *inode, perm_t perm,
|
||||
userid_t userid, uid_t uid, bool under_android);
|
||||
extern void get_derived_permission(struct dentry *parent, struct dentry *dentry);
|
||||
extern void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, struct dentry *newdentry);
|
||||
extern void get_derive_permissions_recursive(struct dentry *parent);
|
||||
|
||||
extern void update_derived_permission_lock(struct dentry *dentry);
|
||||
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);
|
||||
mutex_lock_nested(&d_inode(dir)->i_mutex, I_MUTEX_PARENT);
|
||||
return dir;
|
||||
}
|
||||
|
||||
static inline void unlock_dir(struct dentry *dir)
|
||||
{
|
||||
mutex_unlock(&d_inode(dir)->i_mutex);
|
||||
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_mkdir(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;
|
||||
mutex_lock(&d_inode(dent)->i_mutex);
|
||||
notify_change(dent, &attrs, NULL);
|
||||
mutex_unlock(&d_inode(dent)->i_mutex);
|
||||
|
||||
out_dput:
|
||||
dput(dent);
|
||||
|
||||
out_unlock:
|
||||
/* parent dentry locked by lookup_create */
|
||||
mutex_unlock(&d_inode(parent.dentry)->i_mutex);
|
||||
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 */
|
||||
static inline void sdcardfs_copy_and_fix_attrs(struct inode *dest, const struct inode *src)
|
||||
{
|
||||
dest->i_mode = (src->i_mode & S_IFMT) | get_mode(SDCARDFS_I(dest));
|
||||
dest->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(dest)->d_uid);
|
||||
dest->i_gid = make_kgid(&init_user_ns, get_gid(SDCARDFS_I(dest)));
|
||||
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);
|
||||
}
|
||||
#endif /* not _SDCARDFS_H_ */
|
222
fs/sdcardfs/super.c
Normal file
222
fs/sdcardfs/super.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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 "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;
|
||||
|
||||
/* 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) {
|
||||
printk(KERN_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;
|
||||
}
|
||||
|
||||
/*
|
||||
* @flags: numeric mount options
|
||||
* @options: mount options string
|
||||
*/
|
||||
static int sdcardfs_remount_fs(struct super_block *sb, int *flags, char *options)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* The VFS will take care of "ro" and "rw" flags among others. We
|
||||
* can safely accept a few flags (RDONLY, MANDLOCK), and honor
|
||||
* SILENT, but anything else left over is an error.
|
||||
*/
|
||||
if ((*flags & ~(MS_RDONLY | MS_MANDLOCK | MS_SILENT)) != 0) {
|
||||
printk(KERN_ERR
|
||||
"sdcardfs: remount flags 0x%x unsupported\n", *flags);
|
||||
err = -EINVAL;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
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;
|
||||
|
||||
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));
|
||||
|
||||
i->vfs_inode.i_version = 1;
|
||||
return &i->vfs_inode;
|
||||
}
|
||||
|
||||
static void sdcardfs_destroy_inode(struct inode *inode)
|
||||
{
|
||||
kmem_cache_free(sdcardfs_inode_cachep, SDCARDFS_I(inode));
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
sdcardfs_inode_cachep =
|
||||
kmem_cache_create("sdcardfs_inode_cache",
|
||||
sizeof(struct sdcardfs_inode_info), 0,
|
||||
SLAB_RECLAIM_ACCOUNT, init_once);
|
||||
if (!sdcardfs_inode_cachep)
|
||||
err = -ENOMEM;
|
||||
return err;
|
||||
}
|
||||
|
||||
/* sdcardfs inode cache destructor */
|
||||
void sdcardfs_destroy_inode_cache(void)
|
||||
{
|
||||
if (sdcardfs_inode_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 seq_file *m, struct dentry *root)
|
||||
{
|
||||
struct sdcardfs_sb_info *sbi = SDCARDFS_SB(root->d_sb);
|
||||
struct sdcardfs_mount_options *opts = &sbi->options;
|
||||
|
||||
if (opts->fs_low_uid != 0)
|
||||
seq_printf(m, ",uid=%u", opts->fs_low_uid);
|
||||
if (opts->fs_low_gid != 0)
|
||||
seq_printf(m, ",gid=%u", opts->fs_low_gid);
|
||||
|
||||
if (opts->multiuser)
|
||||
seq_printf(m, ",multiuser");
|
||||
|
||||
if (opts->reserved_mb != 0)
|
||||
seq_printf(m, ",reserved=%uMB", opts->reserved_mb);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const struct super_operations sdcardfs_sops = {
|
||||
.put_super = sdcardfs_put_super,
|
||||
.statfs = sdcardfs_statfs,
|
||||
.remount_fs = sdcardfs_remount_fs,
|
||||
.evict_inode = sdcardfs_evict_inode,
|
||||
.umount_begin = sdcardfs_umount_begin,
|
||||
.show_options = sdcardfs_show_options,
|
||||
.alloc_inode = sdcardfs_alloc_inode,
|
||||
.destroy_inode = sdcardfs_destroy_inode,
|
||||
.drop_inode = generic_delete_inode,
|
||||
};
|
|
@ -161,6 +161,7 @@ struct dentry_operations {
|
|||
struct vfsmount *(*d_automount)(struct path *);
|
||||
int (*d_manage)(struct dentry *, bool);
|
||||
struct inode *(*d_select_inode)(struct dentry *, unsigned);
|
||||
void (*d_canonical_path)(const struct dentry *, struct path *);
|
||||
struct dentry *(*d_real)(struct dentry *, struct inode *);
|
||||
} ____cacheline_aligned;
|
||||
|
||||
|
|
|
@ -75,6 +75,8 @@ extern struct dentry *user_path_create(int, const char __user *, struct path *,
|
|||
extern void done_path_create(struct path *, struct dentry *);
|
||||
extern struct dentry *kern_path_locked(const char *, struct path *);
|
||||
extern int kern_path_mountpoint(int, const char *, struct path *, unsigned int);
|
||||
extern int vfs_path_lookup(struct dentry *, struct vfsmount *,
|
||||
const char *, unsigned int, struct path *);
|
||||
|
||||
extern struct dentry *lookup_one_len(const char *, struct dentry *, int);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ extern const struct file_operations random_fops, urandom_fops;
|
|||
#endif
|
||||
|
||||
unsigned int get_random_int(void);
|
||||
unsigned long get_random_long(void);
|
||||
unsigned long randomize_range(unsigned long start, unsigned long end, unsigned long len);
|
||||
|
||||
u32 prandom_u32(void);
|
||||
|
|
|
@ -47,10 +47,6 @@ struct linger {
|
|||
struct msghdr {
|
||||
void *msg_name; /* ptr to socket address structure */
|
||||
int msg_namelen; /* size of socket address structure */
|
||||
#if defined(CONFIG_PPPOLAC) || defined(CONFIG_PPPOPNS)
|
||||
struct iovec *msg_iov; /* scatter/gather array */
|
||||
__kernel_size_t msg_iovlen; /* # elements in msg_iov */
|
||||
#endif
|
||||
struct iov_iter msg_iter; /* data */
|
||||
void *msg_control; /* ancillary data */
|
||||
__kernel_size_t msg_controllen; /* ancillary data buffer length */
|
||||
|
|
|
@ -21,7 +21,12 @@
|
|||
#define MAX_SUSPEND_ABORT_LEN 256
|
||||
|
||||
void log_wakeup_reason(int irq);
|
||||
void log_suspend_abort_reason(const char *fmt, ...);
|
||||
int check_wakeup_reason(int irq);
|
||||
|
||||
#ifdef CONFIG_SUSPEND
|
||||
void log_suspend_abort_reason(const char *fmt, ...);
|
||||
#else
|
||||
static inline void log_suspend_abort_reason(const char *fmt, ...) { }
|
||||
#endif
|
||||
|
||||
#endif /* _LINUX_WAKEUP_REASON_H */
|
||||
|
|
|
@ -135,7 +135,7 @@ TRACE_EVENT(cpu_frequency_limits,
|
|||
|
||||
TP_fast_assign(
|
||||
__entry->min_freq = min_freq;
|
||||
__entry->max_freq = min_freq;
|
||||
__entry->max_freq = max_freq;
|
||||
__entry->cpu_id = cpu_id;
|
||||
),
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
#define REISER2FS_SUPER_MAGIC_STRING "ReIsEr2Fs"
|
||||
#define REISER2FS_JR_SUPER_MAGIC_STRING "ReIsEr3Fs"
|
||||
|
||||
#define SDCARDFS_SUPER_MAGIC 0xb550ca10
|
||||
|
||||
#define SMB_SUPER_MAGIC 0x517B
|
||||
#define CGROUP_SUPER_MAGIC 0x27e0eb
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
config SUSPEND
|
||||
bool "Suspend to RAM and standby"
|
||||
depends on ARCH_SUSPEND_POSSIBLE
|
||||
select RTC_LIB
|
||||
default y
|
||||
---help---
|
||||
Allow the system to enter sleep states in which main memory is
|
||||
|
|
|
@ -121,7 +121,7 @@ static unsigned long soft_lockup_nmi_warn;
|
|||
unsigned int __read_mostly hardlockup_panic =
|
||||
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC_VALUE;
|
||||
#ifdef CONFIG_HARDLOCKUP_DETECTOR_NMI
|
||||
static unsigned long hardlockup_allcpu_dumped;
|
||||
static unsigned long __maybe_unused hardlockup_allcpu_dumped;
|
||||
#endif
|
||||
/*
|
||||
* We may not want to enable hard lockup detection by default in all cases,
|
||||
|
|
|
@ -2634,6 +2634,7 @@ int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
|
|||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(do_munmap);
|
||||
|
||||
int vm_munmap(unsigned long start, size_t len)
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue