Files
kernel_xiaomi_sm8250/fs/ext4/inline.c
Greg Kroah-Hartman 2d76dea417 Merge 4.19.323 into android-4.19-stable
Changes in 4.19.323
	staging: iio: frequency: ad9833: Get frequency value statically
	staging: iio: frequency: ad9833: Load clock using clock framework
	staging: iio: frequency: ad9834: Validate frequency parameter value
	usbnet: ipheth: fix carrier detection in modes 1 and 4
	net: ethernet: use ip_hdrlen() instead of bit shift
	net: phy: vitesse: repair vsc73xx autonegotiation
	scripts: kconfig: merge_config: config files: add a trailing newline
	arm64: dts: rockchip: override BIOS_DISABLE signal via GPIO hog on RK3399 Puma
	net/mlx5: Update the list of the PCI supported devices
	net: ftgmac100: Enable TX interrupt to avoid TX timeout
	net: dpaa: Pad packets to ETH_ZLEN
	soundwire: stream: Revert "soundwire: stream: fix programming slave ports for non-continous port maps"
	selftests/vm: remove call to ksft_set_plan()
	selftests/kcmp: remove call to ksft_set_plan()
	ASoC: allow module autoloading for table db1200_pids
	pinctrl: at91: make it work with current gpiolib
	microblaze: don't treat zero reserved memory regions as error
	net: ftgmac100: Ensure tx descriptor updates are visible
	wifi: iwlwifi: mvm: fix iwl_mvm_max_scan_ie_fw_cmd_room()
	wifi: iwlwifi: mvm: don't wait for tx queues if firmware is dead
	ASoC: tda7419: fix module autoloading
	spi: bcm63xx: Enable module autoloading
	x86/hyperv: Set X86_FEATURE_TSC_KNOWN_FREQ when Hyper-V provides frequency
	ocfs2: add bounds checking to ocfs2_xattr_find_entry()
	ocfs2: strict bound check before memcmp in ocfs2_xattr_find_entry()
	gpio: prevent potential speculation leaks in gpio_device_get_desc()
	USB: serial: pl2303: add device id for Macrosilicon MS3020
	ACPI: PMIC: Remove unneeded check in tps68470_pmic_opregion_probe()
	wifi: ath9k: fix parameter check in ath9k_init_debug()
	wifi: ath9k: Remove error checks when creating debugfs entries
	netfilter: nf_tables: elements with timeout below CONFIG_HZ never expire
	wifi: cfg80211: fix UBSAN noise in cfg80211_wext_siwscan()
	wifi: cfg80211: fix two more possible UBSAN-detected off-by-one errors
	wifi: mac80211: use two-phase skb reclamation in ieee80211_do_stop()
	can: bcm: Clear bo->bcm_proc_read after remove_proc_entry().
	Bluetooth: btusb: Fix not handling ZPL/short-transfer
	block, bfq: fix possible UAF for bfqq->bic with merge chain
	block, bfq: choose the last bfqq from merge chain in bfq_setup_cooperator()
	block, bfq: don't break merge chain in bfq_split_bfqq()
	spi: ppc4xx: handle irq_of_parse_and_map() errors
	spi: ppc4xx: Avoid returning 0 when failed to parse and map IRQ
	ARM: versatile: fix OF node leak in CPUs prepare
	reset: berlin: fix OF node leak in probe() error path
	clocksource/drivers/qcom: Add missing iounmap() on errors in msm_dt_timer_init()
	hwmon: (max16065) Fix overflows seen when writing limits
	mtd: slram: insert break after errors in parsing the map
	hwmon: (ntc_thermistor) fix module autoloading
	power: supply: max17042_battery: Fix SOC threshold calc w/ no current sense
	fbdev: hpfb: Fix an error handling path in hpfb_dio_probe()
	drm/stm: Fix an error handling path in stm_drm_platform_probe()
	drm/amd: fix typo
	drm/amdgpu: Replace one-element array with flexible-array member
	drm/amdgpu: properly handle vbios fake edid sizing
	drm/radeon: Replace one-element array with flexible-array member
	drm/radeon: properly handle vbios fake edid sizing
	drm/rockchip: vop: Allow 4096px width scaling
	drm/radeon/evergreen_cs: fix int overflow errors in cs track offsets
	jfs: fix out-of-bounds in dbNextAG() and diAlloc()
	drm/msm/a5xx: properly clear preemption records on resume
	drm/msm/a5xx: fix races in preemption evaluation stage
	ipmi: docs: don't advertise deprecated sysfs entries
	drm/msm: fix %s null argument error
	xen: use correct end address of kernel for conflict checking
	xen/swiotlb: simplify range_straddles_page_boundary()
	xen/swiotlb: add alignment check for dma buffers
	selftests/bpf: Fix error compiling test_lru_map.c
	xz: cleanup CRC32 edits from 2018
	kthread: add kthread_work tracepoints
	kthread: fix task state in kthread worker if being frozen
	jbd2: introduce/export functions jbd2_journal_submit|finish_inode_data_buffers()
	ext4: clear EXT4_GROUP_INFO_WAS_TRIMMED_BIT even mount with discard
	smackfs: Use rcu_assign_pointer() to ensure safe assignment in smk_set_cipso
	ext4: avoid negative min_clusters in find_group_orlov()
	ext4: return error on ext4_find_inline_entry
	ext4: avoid OOB when system.data xattr changes underneath the filesystem
	nilfs2: fix potential null-ptr-deref in nilfs_btree_insert()
	nilfs2: determine empty node blocks as corrupted
	nilfs2: fix potential oob read in nilfs_btree_check_delete()
	perf sched timehist: Fix missing free of session in perf_sched__timehist()
	perf sched timehist: Fixed timestamp error when unable to confirm event sched_in time
	perf time-utils: Fix 32-bit nsec parsing
	clk: rockchip: Set parent rate for DCLK_VOP clock on RK3228
	drivers: media: dvb-frontends/rtl2832: fix an out-of-bounds write error
	drivers: media: dvb-frontends/rtl2830: fix an out-of-bounds write error
	PCI: xilinx-nwl: Fix register misspelling
	RDMA/iwcm: Fix WARNING:at_kernel/workqueue.c:#check_flush_dependency
	pinctrl: single: fix missing error code in pcs_probe()
	clk: ti: dra7-atl: Fix leak of of_nodes
	pinctrl: mvebu: Fix devinit_dove_pinctrl_probe function
	RDMA/cxgb4: Added NULL check for lookup_atid
	ntb: intel: Fix the NULL vs IS_ERR() bug for debugfs_create_dir()
	nfsd: call cache_put if xdr_reserve_space returns NULL
	f2fs: enhance to update i_mode and acl atomically in f2fs_setattr()
	f2fs: fix typo
	f2fs: fix to update i_ctime in __f2fs_setxattr()
	f2fs: remove unneeded check condition in __f2fs_setxattr()
	f2fs: reduce expensive checkpoint trigger frequency
	coresight: tmc: sg: Do not leak sg_table
	netfilter: nf_reject_ipv6: fix nf_reject_ip6_tcphdr_put()
	net: seeq: Fix use after free vulnerability in ether3 Driver Due to Race Condition
	tcp: introduce tcp_skb_timestamp_us() helper
	tcp: check skb is non-NULL in tcp_rto_delta_us()
	net: qrtr: Update packets cloning when broadcasting
	netfilter: ctnetlink: compile ctnetlink_label_size with CONFIG_NF_CONNTRACK_EVENTS
	crypto: aead,cipher - zeroize key buffer after use
	Remove *.orig pattern from .gitignore
	soc: versatile: integrator: fix OF node leak in probe() error path
	USB: appledisplay: close race between probe and completion handler
	USB: misc: cypress_cy7c63: check for short transfer
	firmware_loader: Block path traversal
	tty: rp2: Fix reset with non forgiving PCIe host bridges
	drbd: Fix atomicity violation in drbd_uuid_set_bm()
	drbd: Add NULL check for net_conf to prevent dereference in state validation
	ACPI: sysfs: validate return type of _STR method
	f2fs: prevent possible int overflow in dir_block_index()
	f2fs: avoid potential int overflow in sanity_check_area_boundary()
	vfs: fix race between evice_inodes() and find_inode()&iput()
	fs: Fix file_set_fowner LSM hook inconsistencies
	nfs: fix memory leak in error path of nfs4_do_reclaim
	PCI: xilinx-nwl: Use irq_data_get_irq_chip_data()
	PCI: xilinx-nwl: Fix off-by-one in INTx IRQ handler
	soc: versatile: realview: fix memory leak during device remove
	soc: versatile: realview: fix soc_dev leak during device remove
	usb: yurex: Replace snprintf() with the safer scnprintf() variant
	USB: misc: yurex: fix race between read and write
	pps: remove usage of the deprecated ida_simple_xx() API
	pps: add an error check in parport_attach
	i2c: aspeed: Update the stop sw state when the bus recovery occurs
	i2c: isch: Add missed 'else'
	usb: yurex: Fix inconsistent locking bug in yurex_read()
	mailbox: rockchip: fix a typo in module autoloading
	mailbox: bcm2835: Fix timeout during suspend mode
	ceph: remove the incorrect Fw reference check when dirtying pages
	netfilter: uapi: NFTA_FLOWTABLE_HOOK is NLA_NESTED
	netfilter: nf_tables: prevent nf_skb_duplicated corruption
	r8152: Factor out OOB link list waits
	net: ethernet: lantiq_etop: fix memory disclosure
	net: avoid potential underflow in qdisc_pkt_len_init() with UFO
	net: add more sanity checks to qdisc_pkt_len_init()
	ipv4: ip_gre: Fix drops of small packets in ipgre_xmit
	sctp: set sk_state back to CLOSED if autobind fails in sctp_listen_start
	ALSA: hda/generic: Unconditionally prefer preferred_dacs pairs
	ALSA: hda/conexant: Fix conflicting quirk for System76 Pangolin
	f2fs: Require FMODE_WRITE for atomic write ioctls
	wifi: ath9k: fix possible integer overflow in ath9k_get_et_stats()
	wifi: ath9k_htc: Use __skb_set_length() for resetting urb before resubmit
	net: hisilicon: hip04: fix OF node leak in probe()
	net: hisilicon: hns_dsaf_mac: fix OF node leak in hns_mac_get_info()
	net: hisilicon: hns_mdio: fix OF node leak in probe()
	ACPICA: Fix memory leak if acpi_ps_get_next_namepath() fails
	ACPICA: Fix memory leak if acpi_ps_get_next_field() fails
	ACPI: EC: Do not release locks during operation region accesses
	ACPICA: check null return of ACPI_ALLOCATE_ZEROED() in acpi_db_convert_to_package()
	tipc: guard against string buffer overrun
	net: mvpp2: Increase size of queue_name buffer
	ipv4: Check !in_dev earlier for ioctl(SIOCSIFADDR).
	ipv4: Mask upper DSCP bits and ECN bits in NETLINK_FIB_LOOKUP family
	tcp: avoid reusing FIN_WAIT2 when trying to find port in connect() process
	ACPICA: iasl: handle empty connection_node
	wifi: mwifiex: Fix memcpy() field-spanning write warning in mwifiex_cmd_802_11_scan_ext()
	signal: Replace BUG_ON()s
	ALSA: asihpi: Fix potential OOB array access
	ALSA: hdsp: Break infinite MIDI input flush loop
	fbdev: pxafb: Fix possible use after free in pxafb_task()
	power: reset: brcmstb: Do not go into infinite loop if reset fails
	ata: sata_sil: Rename sil_blacklist to sil_quirks
	jfs: UBSAN: shift-out-of-bounds in dbFindBits
	jfs: Fix uaf in dbFreeBits
	jfs: check if leafidx greater than num leaves per dmap tree
	jfs: Fix uninit-value access of new_ea in ea_buffer
	drm/amd/display: Check stream before comparing them
	drm/amd/display: Fix index out of bounds in degamma hardware format translation
	drm/printer: Allow NULL data in devcoredump printer
	scsi: aacraid: Rearrange order of struct aac_srb_unit
	drm/radeon/r100: Handle unknown family in r100_cp_init_microcode()
	of/irq: Refer to actual buffer size in of_irq_parse_one()
	ext4: ext4_search_dir should return a proper error
	ext4: fix i_data_sem unlock order in ext4_ind_migrate()
	spi: s3c64xx: fix timeout counters in flush_fifo
	selftests: breakpoints: use remaining time to check if suspend succeed
	selftests: vDSO: fix vDSO symbols lookup for powerpc64
	i2c: xiic: Wait for TX empty to avoid missed TX NAKs
	spi: bcm63xx: Fix module autoloading
	perf/core: Fix small negative period being ignored
	parisc: Fix itlb miss handler for 64-bit programs
	ALSA: core: add isascii() check to card ID generator
	ext4: no need to continue when the number of entries is 1
	ext4: propagate errors from ext4_find_extent() in ext4_insert_range()
	ext4: fix incorrect tid assumption in __jbd2_log_wait_for_space()
	ext4: aovid use-after-free in ext4_ext_insert_extent()
	ext4: fix double brelse() the buffer of the extents path
	ext4: fix incorrect tid assumption in ext4_wait_for_tail_page_commit()
	parisc: Fix 64-bit userspace syscall path
	of/irq: Support #msi-cells=<0> in of_msi_get_domain
	jbd2: stop waiting for space when jbd2_cleanup_journal_tail() returns error
	ocfs2: fix the la space leak when unmounting an ocfs2 volume
	ocfs2: fix uninit-value in ocfs2_get_block()
	ocfs2: reserve space for inline xattr before attaching reflink tree
	ocfs2: cancel dqi_sync_work before freeing oinfo
	ocfs2: remove unreasonable unlock in ocfs2_read_blocks
	ocfs2: fix null-ptr-deref when journal load failed.
	ocfs2: fix possible null-ptr-deref in ocfs2_set_buffer_uptodate
	riscv: define ILLEGAL_POINTER_VALUE for 64bit
	aoe: fix the potential use-after-free problem in more places
	clk: rockchip: fix error for unknown clocks
	media: uapi/linux/cec.h: cec_msg_set_reply_to: zero flags
	media: venus: fix use after free bug in venus_remove due to race condition
	iio: magnetometer: ak8975: Fix reading for ak099xx sensors
	tomoyo: fallback to realpath if symlink's pathname does not exist
	Input: adp5589-keys - fix adp5589_gpio_get_value()
	btrfs: wait for fixup workers before stopping cleaner kthread during umount
	gpio: davinci: fix lazy disable
	ext4: avoid ext4_error()'s caused by ENOMEM in the truncate path
	ext4: fix slab-use-after-free in ext4_split_extent_at()
	ext4: update orig_path in ext4_find_extent()
	arm64: Add Cortex-715 CPU part definition
	arm64: cputype: Add Neoverse-N3 definitions
	arm64: errata: Expand speculative SSBS workaround once more
	uprobes: fix kernel info leak via "[uprobes]" vma
	nfsd: use ktime_get_seconds() for timestamps
	nfsd: fix delegation_blocked() to block correctly for at least 30 seconds
	rtc: at91sam9: drop platform_data support
	rtc: at91sam9: fix OF node leak in probe() error path
	ACPI: battery: Simplify battery hook locking
	ACPI: battery: Fix possible crash when unregistering a battery hook
	ext4: fix inode tree inconsistency caused by ENOMEM
	net: ethernet: cortina: Drop TSO support
	tracing: Remove precision vsnprintf() check from print event
	drm: Move drm_mode_setcrtc() local re-init to failure path
	drm/crtc: fix uninitialized variable use even harder
	virtio_console: fix misc probe bugs
	Input: synaptics-rmi4 - fix UAF of IRQ domain on driver removal
	bpf: Check percpu map value size first
	s390/facility: Disable compile time optimization for decompressor code
	s390/mm: Add cond_resched() to cmm_alloc/free_pages()
	ext4: nested locking for xattr inode
	s390/cpum_sf: Remove WARN_ON_ONCE statements
	ktest.pl: Avoid false positives with grub2 skip regex
	clk: bcm: bcm53573: fix OF node leak in init
	i2c: i801: Use a different adapter-name for IDF adapters
	PCI: Mark Creative Labs EMU20k2 INTx masking as broken
	media: videobuf2-core: clear memory related fields in __vb2_plane_dmabuf_put()
	usb: chipidea: udc: enable suspend interrupt after usb reset
	tools/iio: Add memory allocation failure check for trigger_name
	driver core: bus: Return -EIO instead of 0 when show/store invalid bus attribute
	fbdev: sisfb: Fix strbuf array overflow
	NFS: Remove print_overflow_msg()
	SUNRPC: Fix integer overflow in decode_rc_list()
	tcp: fix tcp_enter_recovery() to zero retrans_stamp when it's safe
	netfilter: br_netfilter: fix panic with metadata_dst skb
	Bluetooth: RFCOMM: FIX possible deadlock in rfcomm_sk_state_change
	gpio: aspeed: Add the flush write to ensure the write complete.
	clk: Add (devm_)clk_get_optional() functions
	clk: generalize devm_clk_get() a bit
	clk: Provide new devm_clk helpers for prepared and enabled clocks
	gpio: aspeed: Use devm_clk api to manage clock source
	igb: Do not bring the device up after non-fatal error
	net: ibm: emac: mal: fix wrong goto
	ppp: fix ppp_async_encode() illegal access
	net: ipv6: ensure we call ipv6_mc_down() at most once
	CDC-NCM: avoid overflow in sanity checking
	HID: plantronics: Workaround for an unexcepted opposite volume key
	Revert "usb: yurex: Replace snprintf() with the safer scnprintf() variant"
	usb: xhci: Fix problem with xhci resume from suspend
	usb: storage: ignore bogus device raised by JieLi BR21 USB sound chip
	net: Fix an unsafe loop on the list
	posix-clock: Fix missing timespec64 check in pc_clock_settime()
	arm64: probes: Remove broken LDR (literal) uprobe support
	arm64: probes: Fix simulate_ldr*_literal()
	PCI: Add function 0 DMA alias quirk for Glenfly Arise chip
	fat: fix uninitialized variable
	KVM: Fix a data race on last_boosted_vcpu in kvm_vcpu_on_spin()
	net: dsa: mv88e6xxx: Fix out-of-bound access
	s390/sclp_vt220: Convert newlines to CRLF instead of LFCR
	KVM: s390: Change virtual to physical address access in diag 0x258 handler
	x86/cpufeatures: Define X86_FEATURE_AMD_IBPB_RET
	drm/vmwgfx: Handle surface check failure correctly
	iio: dac: stm32-dac-core: add missing select REGMAP_MMIO in Kconfig
	iio: adc: ti-ads8688: add missing select IIO_(TRIGGERED_)BUFFER in Kconfig
	iio: hid-sensors: Fix an error handling path in _hid_sensor_set_report_latency()
	iio: light: opt3001: add missing full-scale range value
	Bluetooth: Remove debugfs directory on module init failure
	Bluetooth: btusb: Fix regression with fake CSR controllers 0a12:0001
	xhci: Fix incorrect stream context type macro
	USB: serial: option: add support for Quectel EG916Q-GL
	USB: serial: option: add Telit FN920C04 MBIM compositions
	parport: Proper fix for array out-of-bounds access
	x86/apic: Always explicitly disarm TSC-deadline timer
	nilfs2: propagate directory read errors from nilfs_find_entry()
	clk: Fix pointer casting to prevent oops in devm_clk_release()
	clk: Fix slab-out-of-bounds error in devm_clk_release()
	RDMA/bnxt_re: Fix incorrect AVID type in WQE structure
	RDMA/cxgb4: Fix RDMA_CM_EVENT_UNREACHABLE error for iWARP
	RDMA/bnxt_re: Return more meaningful error
	drm/msm/dsi: fix 32-bit signed integer extension in pclk_rate calculation
	macsec: don't increment counters for an unrelated SA
	net: ethernet: aeroflex: fix potential memory leak in greth_start_xmit_gbit()
	net: systemport: fix potential memory leak in bcm_sysport_xmit()
	usb: typec: altmode should keep reference to parent
	Bluetooth: bnep: fix wild-memory-access in proto_unregister
	arm64:uprobe fix the uprobe SWBP_INSN in big-endian
	arm64: probes: Fix uprobes for big-endian kernels
	KVM: s390: gaccess: Refactor gpa and length calculation
	KVM: s390: gaccess: Refactor access address range check
	KVM: s390: gaccess: Cleanup access to guest pages
	KVM: s390: gaccess: Check if guest address is in memslot
	udf: fix uninit-value use in udf_get_fileshortad
	jfs: Fix sanity check in dbMount
	net/sun3_82586: fix potential memory leak in sun3_82586_send_packet()
	be2net: fix potential memory leak in be_xmit()
	net: usb: usbnet: fix name regression
	posix-clock: posix-clock: Fix unbalanced locking in pc_clock_settime()
	ALSA: hda/realtek: Update default depop procedure
	drm/amd: Guard against bad data for ATIF ACPI method
	ACPI: button: Add DMI quirk for Samsung Galaxy Book2 to fix initial lid detection issue
	nilfs2: fix kernel bug due to missing clearing of buffer delay flag
	hv_netvsc: Fix VF namespace also in synthetic NIC NETDEV_REGISTER event
	selinux: improve error checking in sel_write_load()
	arm64/uprobes: change the uprobe_opcode_t typedef to fix the sparse warning
	xfrm: validate new SA's prefixlen using SA family when sel.family is unset
	usb: dwc3: remove generic PHY calibrate() calls
	usb: dwc3: Add splitdisable quirk for Hisilicon Kirin Soc
	usb: dwc3: core: Stop processing of pending events if controller is halted
	cgroup: Fix potential overflow issue when checking max_depth
	wifi: mac80211: skip non-uploaded keys in ieee80211_iter_keys
	gtp: simplify error handling code in 'gtp_encap_enable()'
	gtp: allow -1 to be specified as file description from userspace
	net/sched: stop qdisc_tree_reduce_backlog on TC_H_ROOT
	bpf: Fix out-of-bounds write in trie_get_next_key()
	net: support ip generic csum processing in skb_csum_hwoffload_help
	net: skip offload for NETIF_F_IPV6_CSUM if ipv6 header contains extension
	netfilter: nft_payload: sanitize offset and length before calling skb_checksum()
	firmware: arm_sdei: Fix the input parameter of cpuhp_remove_state()
	net: amd: mvme147: Fix probe banner message
	misc: sgi-gru: Don't disable preemption in GRU driver
	usbip: tools: Fix detach_port() invalid port error path
	usb: phy: Fix API devm_usb_put_phy() can not release the phy
	xhci: Fix Link TRB DMA in command ring stopped completion event
	Revert "driver core: Fix uevent_show() vs driver detach race"
	wifi: mac80211: do not pass a stopped vif to the driver in .get_txpower
	wifi: ath10k: Fix memory leak in management tx
	wifi: iwlegacy: Clear stale interrupts before resuming device
	nilfs2: fix potential deadlock with newly created symlinks
	ocfs2: pass u64 to ocfs2_truncate_inline maybe overflow
	nilfs2: fix kernel bug due to missing clearing of checked flag
	mm: shmem: fix data-race in shmem_getattr()
	vt: prevent kernel-infoleak in con_font_get()
	Linux 4.19.323

Change-Id: I2348f834187153067ab46b3b48b8fe7da9cee1f1
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2024-11-09 11:24:17 +00:00

2105 lines
51 KiB
C

// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (c) 2012 Taobao.
* Written by Tao Ma <boyu.mt@taobao.com>
*/
#include <linux/iomap.h>
#include <linux/fiemap.h>
#include <linux/iversion.h>
#include "ext4_jbd2.h"
#include "ext4.h"
#include "xattr.h"
#include "truncate.h"
#include <trace/events/android_fs.h>
#define EXT4_XATTR_SYSTEM_DATA "data"
#define EXT4_MIN_INLINE_DATA_SIZE ((sizeof(__le32) * EXT4_N_BLOCKS))
#define EXT4_INLINE_DOTDOT_OFFSET 2
#define EXT4_INLINE_DOTDOT_SIZE 4
static int ext4_get_inline_size(struct inode *inode)
{
if (EXT4_I(inode)->i_inline_off)
return EXT4_I(inode)->i_inline_size;
return 0;
}
static int get_max_inline_xattr_value_size(struct inode *inode,
struct ext4_iloc *iloc)
{
struct ext4_xattr_ibody_header *header;
struct ext4_xattr_entry *entry;
struct ext4_inode *raw_inode;
void *end;
int free, min_offs;
if (!EXT4_INODE_HAS_XATTR_SPACE(inode))
return 0;
min_offs = EXT4_SB(inode->i_sb)->s_inode_size -
EXT4_GOOD_OLD_INODE_SIZE -
EXT4_I(inode)->i_extra_isize -
sizeof(struct ext4_xattr_ibody_header);
/*
* We need to subtract another sizeof(__u32) since an in-inode xattr
* needs an empty 4 bytes to indicate the gap between the xattr entry
* and the name/value pair.
*/
if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR))
return EXT4_XATTR_SIZE(min_offs -
EXT4_XATTR_LEN(strlen(EXT4_XATTR_SYSTEM_DATA)) -
EXT4_XATTR_ROUND - sizeof(__u32));
raw_inode = ext4_raw_inode(iloc);
header = IHDR(inode, raw_inode);
entry = IFIRST(header);
end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size;
/* Compute min_offs. */
while (!IS_LAST_ENTRY(entry)) {
void *next = EXT4_XATTR_NEXT(entry);
if (next >= end) {
EXT4_ERROR_INODE(inode,
"corrupt xattr in inline inode");
return 0;
}
if (!entry->e_value_inum && entry->e_value_size) {
size_t offs = le16_to_cpu(entry->e_value_offs);
if (offs < min_offs)
min_offs = offs;
}
entry = next;
}
free = min_offs -
((void *)entry - (void *)IFIRST(header)) - sizeof(__u32);
if (EXT4_I(inode)->i_inline_off) {
entry = (struct ext4_xattr_entry *)
((void *)raw_inode + EXT4_I(inode)->i_inline_off);
free += EXT4_XATTR_SIZE(le32_to_cpu(entry->e_value_size));
goto out;
}
free -= EXT4_XATTR_LEN(strlen(EXT4_XATTR_SYSTEM_DATA));
if (free > EXT4_XATTR_ROUND)
free = EXT4_XATTR_SIZE(free - EXT4_XATTR_ROUND);
else
free = 0;
out:
return free;
}
/*
* Get the maximum size we now can store in an inode.
* If we can't find the space for a xattr entry, don't use the space
* of the extents since we have no space to indicate the inline data.
*/
int ext4_get_max_inline_size(struct inode *inode)
{
int error, max_inline_size;
struct ext4_iloc iloc;
if (EXT4_I(inode)->i_extra_isize == 0)
return 0;
error = ext4_get_inode_loc(inode, &iloc);
if (error) {
ext4_error_inode(inode, __func__, __LINE__, 0,
"can't get inode location %lu",
inode->i_ino);
return 0;
}
down_read(&EXT4_I(inode)->xattr_sem);
max_inline_size = get_max_inline_xattr_value_size(inode, &iloc);
up_read(&EXT4_I(inode)->xattr_sem);
brelse(iloc.bh);
if (!max_inline_size)
return 0;
return max_inline_size + EXT4_MIN_INLINE_DATA_SIZE;
}
/*
* this function does not take xattr_sem, which is OK because it is
* currently only used in a code path coming form ext4_iget, before
* the new inode has been unlocked
*/
int ext4_find_inline_data_nolock(struct inode *inode)
{
struct ext4_xattr_ibody_find is = {
.s = { .not_found = -ENODATA, },
};
struct ext4_xattr_info i = {
.name_index = EXT4_XATTR_INDEX_SYSTEM,
.name = EXT4_XATTR_SYSTEM_DATA,
};
int error;
if (EXT4_I(inode)->i_extra_isize == 0)
return 0;
error = ext4_get_inode_loc(inode, &is.iloc);
if (error)
return error;
error = ext4_xattr_ibody_find(inode, &i, &is);
if (error)
goto out;
if (!is.s.not_found) {
if (is.s.here->e_value_inum) {
EXT4_ERROR_INODE(inode, "inline data xattr refers "
"to an external xattr inode");
error = -EFSCORRUPTED;
goto out;
}
EXT4_I(inode)->i_inline_off = (u16)((void *)is.s.here -
(void *)ext4_raw_inode(&is.iloc));
EXT4_I(inode)->i_inline_size = EXT4_MIN_INLINE_DATA_SIZE +
le32_to_cpu(is.s.here->e_value_size);
}
out:
brelse(is.iloc.bh);
return error;
}
static int ext4_read_inline_data(struct inode *inode, void *buffer,
unsigned int len,
struct ext4_iloc *iloc)
{
struct ext4_xattr_entry *entry;
struct ext4_xattr_ibody_header *header;
int cp_len = 0;
struct ext4_inode *raw_inode;
if (!len)
return 0;
BUG_ON(len > EXT4_I(inode)->i_inline_size);
cp_len = len < EXT4_MIN_INLINE_DATA_SIZE ?
len : EXT4_MIN_INLINE_DATA_SIZE;
raw_inode = ext4_raw_inode(iloc);
memcpy(buffer, (void *)(raw_inode->i_block), cp_len);
len -= cp_len;
buffer += cp_len;
if (!len)
goto out;
header = IHDR(inode, raw_inode);
entry = (struct ext4_xattr_entry *)((void *)raw_inode +
EXT4_I(inode)->i_inline_off);
len = min_t(unsigned int, len,
(unsigned int)le32_to_cpu(entry->e_value_size));
memcpy(buffer,
(void *)IFIRST(header) + le16_to_cpu(entry->e_value_offs), len);
cp_len += len;
out:
return cp_len;
}
/*
* write the buffer to the inline inode.
* If 'create' is set, we don't need to do the extra copy in the xattr
* value since it is already handled by ext4_xattr_ibody_set.
* That saves us one memcpy.
*/
static void ext4_write_inline_data(struct inode *inode, struct ext4_iloc *iloc,
void *buffer, loff_t pos, unsigned int len)
{
struct ext4_xattr_entry *entry;
struct ext4_xattr_ibody_header *header;
struct ext4_inode *raw_inode;
int cp_len = 0;
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
return;
BUG_ON(!EXT4_I(inode)->i_inline_off);
BUG_ON(pos + len > EXT4_I(inode)->i_inline_size);
raw_inode = ext4_raw_inode(iloc);
buffer += pos;
if (pos < EXT4_MIN_INLINE_DATA_SIZE) {
cp_len = pos + len > EXT4_MIN_INLINE_DATA_SIZE ?
EXT4_MIN_INLINE_DATA_SIZE - pos : len;
memcpy((void *)raw_inode->i_block + pos, buffer, cp_len);
len -= cp_len;
buffer += cp_len;
pos += cp_len;
}
if (!len)
return;
pos -= EXT4_MIN_INLINE_DATA_SIZE;
header = IHDR(inode, raw_inode);
entry = (struct ext4_xattr_entry *)((void *)raw_inode +
EXT4_I(inode)->i_inline_off);
memcpy((void *)IFIRST(header) + le16_to_cpu(entry->e_value_offs) + pos,
buffer, len);
}
static int ext4_create_inline_data(handle_t *handle,
struct inode *inode, unsigned len)
{
int error;
void *value = NULL;
struct ext4_xattr_ibody_find is = {
.s = { .not_found = -ENODATA, },
};
struct ext4_xattr_info i = {
.name_index = EXT4_XATTR_INDEX_SYSTEM,
.name = EXT4_XATTR_SYSTEM_DATA,
};
error = ext4_get_inode_loc(inode, &is.iloc);
if (error)
return error;
BUFFER_TRACE(is.iloc.bh, "get_write_access");
error = ext4_journal_get_write_access(handle, is.iloc.bh);
if (error)
goto out;
if (len > EXT4_MIN_INLINE_DATA_SIZE) {
value = EXT4_ZERO_XATTR_VALUE;
len -= EXT4_MIN_INLINE_DATA_SIZE;
} else {
value = "";
len = 0;
}
/* Insert the the xttr entry. */
i.value = value;
i.value_len = len;
error = ext4_xattr_ibody_find(inode, &i, &is);
if (error)
goto out;
BUG_ON(!is.s.not_found);
error = ext4_xattr_ibody_set(handle, inode, &i, &is);
if (error) {
if (error == -ENOSPC)
ext4_clear_inode_state(inode,
EXT4_STATE_MAY_INLINE_DATA);
goto out;
}
memset((void *)ext4_raw_inode(&is.iloc)->i_block,
0, EXT4_MIN_INLINE_DATA_SIZE);
EXT4_I(inode)->i_inline_off = (u16)((void *)is.s.here -
(void *)ext4_raw_inode(&is.iloc));
EXT4_I(inode)->i_inline_size = len + EXT4_MIN_INLINE_DATA_SIZE;
ext4_clear_inode_flag(inode, EXT4_INODE_EXTENTS);
ext4_set_inode_flag(inode, EXT4_INODE_INLINE_DATA);
get_bh(is.iloc.bh);
error = ext4_mark_iloc_dirty(handle, inode, &is.iloc);
out:
brelse(is.iloc.bh);
return error;
}
static int ext4_update_inline_data(handle_t *handle, struct inode *inode,
unsigned int len)
{
int error;
void *value = NULL;
struct ext4_xattr_ibody_find is = {
.s = { .not_found = -ENODATA, },
};
struct ext4_xattr_info i = {
.name_index = EXT4_XATTR_INDEX_SYSTEM,
.name = EXT4_XATTR_SYSTEM_DATA,
};
/* If the old space is ok, write the data directly. */
if (len <= EXT4_I(inode)->i_inline_size)
return 0;
error = ext4_get_inode_loc(inode, &is.iloc);
if (error)
return error;
error = ext4_xattr_ibody_find(inode, &i, &is);
if (error)
goto out;
BUG_ON(is.s.not_found);
len -= EXT4_MIN_INLINE_DATA_SIZE;
value = kzalloc(len, GFP_NOFS);
if (!value) {
error = -ENOMEM;
goto out;
}
error = ext4_xattr_ibody_get(inode, i.name_index, i.name,
value, len);
if (error < 0)
goto out;
BUFFER_TRACE(is.iloc.bh, "get_write_access");
error = ext4_journal_get_write_access(handle, is.iloc.bh);
if (error)
goto out;
/* Update the xttr entry. */
i.value = value;
i.value_len = len;
error = ext4_xattr_ibody_set(handle, inode, &i, &is);
if (error)
goto out;
EXT4_I(inode)->i_inline_off = (u16)((void *)is.s.here -
(void *)ext4_raw_inode(&is.iloc));
EXT4_I(inode)->i_inline_size = EXT4_MIN_INLINE_DATA_SIZE +
le32_to_cpu(is.s.here->e_value_size);
ext4_set_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
get_bh(is.iloc.bh);
error = ext4_mark_iloc_dirty(handle, inode, &is.iloc);
out:
kfree(value);
brelse(is.iloc.bh);
return error;
}
static int ext4_prepare_inline_data(handle_t *handle, struct inode *inode,
unsigned int len)
{
int ret, size, no_expand;
struct ext4_inode_info *ei = EXT4_I(inode);
if (!ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA))
return -ENOSPC;
size = ext4_get_max_inline_size(inode);
if (size < len)
return -ENOSPC;
ext4_write_lock_xattr(inode, &no_expand);
if (ei->i_inline_off)
ret = ext4_update_inline_data(handle, inode, len);
else
ret = ext4_create_inline_data(handle, inode, len);
ext4_write_unlock_xattr(inode, &no_expand);
return ret;
}
static int ext4_destroy_inline_data_nolock(handle_t *handle,
struct inode *inode)
{
struct ext4_inode_info *ei = EXT4_I(inode);
struct ext4_xattr_ibody_find is = {
.s = { .not_found = 0, },
};
struct ext4_xattr_info i = {
.name_index = EXT4_XATTR_INDEX_SYSTEM,
.name = EXT4_XATTR_SYSTEM_DATA,
.value = NULL,
.value_len = 0,
};
int error;
if (!ei->i_inline_off)
return 0;
error = ext4_get_inode_loc(inode, &is.iloc);
if (error)
return error;
error = ext4_xattr_ibody_find(inode, &i, &is);
if (error)
goto out;
BUFFER_TRACE(is.iloc.bh, "get_write_access");
error = ext4_journal_get_write_access(handle, is.iloc.bh);
if (error)
goto out;
error = ext4_xattr_ibody_set(handle, inode, &i, &is);
if (error)
goto out;
memset((void *)ext4_raw_inode(&is.iloc)->i_block,
0, EXT4_MIN_INLINE_DATA_SIZE);
memset(ei->i_data, 0, EXT4_MIN_INLINE_DATA_SIZE);
if (ext4_has_feature_extents(inode->i_sb)) {
if (S_ISDIR(inode->i_mode) ||
S_ISREG(inode->i_mode) || S_ISLNK(inode->i_mode)) {
ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
ext4_ext_tree_init(handle, inode);
}
}
ext4_clear_inode_flag(inode, EXT4_INODE_INLINE_DATA);
get_bh(is.iloc.bh);
error = ext4_mark_iloc_dirty(handle, inode, &is.iloc);
EXT4_I(inode)->i_inline_off = 0;
EXT4_I(inode)->i_inline_size = 0;
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
out:
brelse(is.iloc.bh);
if (error == -ENODATA)
error = 0;
return error;
}
static int ext4_read_inline_page(struct inode *inode, struct page *page)
{
void *kaddr;
int ret = 0;
size_t len;
struct ext4_iloc iloc;
BUG_ON(!PageLocked(page));
BUG_ON(!ext4_has_inline_data(inode));
BUG_ON(page->index);
if (!EXT4_I(inode)->i_inline_off) {
ext4_warning(inode->i_sb, "inode %lu doesn't have inline data.",
inode->i_ino);
goto out;
}
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
goto out;
len = min_t(size_t, ext4_get_inline_size(inode), i_size_read(inode));
kaddr = kmap_atomic(page);
ret = ext4_read_inline_data(inode, kaddr, len, &iloc);
flush_dcache_page(page);
kunmap_atomic(kaddr);
zero_user_segment(page, len, PAGE_SIZE);
SetPageUptodate(page);
brelse(iloc.bh);
out:
return ret;
}
int ext4_readpage_inline(struct inode *inode, struct page *page)
{
int ret = 0;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
up_read(&EXT4_I(inode)->xattr_sem);
return -EAGAIN;
}
if (trace_android_fs_dataread_start_enabled()) {
char *path, pathbuf[MAX_TRACE_PATHBUF_LEN];
path = android_fstrace_get_pathname(pathbuf,
MAX_TRACE_PATHBUF_LEN,
inode);
trace_android_fs_dataread_start(inode, page_offset(page),
PAGE_SIZE, current->pid,
path, current->comm);
}
/*
* Current inline data can only exist in the 1st page,
* So for all the other pages, just set them uptodate.
*/
if (!page->index)
ret = ext4_read_inline_page(inode, page);
else if (!PageUptodate(page)) {
zero_user_segment(page, 0, PAGE_SIZE);
SetPageUptodate(page);
}
trace_android_fs_dataread_end(inode, page_offset(page), PAGE_SIZE);
up_read(&EXT4_I(inode)->xattr_sem);
unlock_page(page);
return ret >= 0 ? 0 : ret;
}
static int ext4_convert_inline_data_to_extent(struct address_space *mapping,
struct inode *inode,
unsigned flags)
{
int ret, needed_blocks, no_expand;
handle_t *handle = NULL;
int retries = 0, sem_held = 0;
struct page *page = NULL;
unsigned from, to;
struct ext4_iloc iloc;
if (!ext4_has_inline_data(inode)) {
/*
* clear the flag so that no new write
* will trap here again.
*/
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
return 0;
}
needed_blocks = ext4_writepage_trans_blocks(inode);
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
return ret;
retry:
handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE, needed_blocks);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
handle = NULL;
goto out;
}
/* We cannot recurse into the filesystem as the transaction is already
* started */
flags |= AOP_FLAG_NOFS;
page = grab_cache_page_write_begin(mapping, 0, flags);
if (!page) {
ret = -ENOMEM;
goto out;
}
ext4_write_lock_xattr(inode, &no_expand);
sem_held = 1;
/* If some one has already done this for us, just exit. */
if (!ext4_has_inline_data(inode)) {
ret = 0;
goto out;
}
from = 0;
to = ext4_get_inline_size(inode);
if (!PageUptodate(page)) {
ret = ext4_read_inline_page(inode, page);
if (ret < 0)
goto out;
}
ret = ext4_destroy_inline_data_nolock(handle, inode);
if (ret)
goto out;
if (ext4_should_dioread_nolock(inode)) {
ret = __block_write_begin(page, from, to,
ext4_get_block_unwritten);
} else
ret = __block_write_begin(page, from, to, ext4_get_block);
if (!ret && ext4_should_journal_data(inode)) {
ret = ext4_walk_page_buffers(handle, page_buffers(page),
from, to, NULL,
do_journal_get_write_access);
}
if (ret) {
unlock_page(page);
put_page(page);
page = NULL;
ext4_orphan_add(handle, inode);
ext4_write_unlock_xattr(inode, &no_expand);
sem_held = 0;
ext4_journal_stop(handle);
handle = NULL;
ext4_truncate_failed_write(inode);
/*
* If truncate failed early the inode might
* still be on the orphan list; we need to
* make sure the inode is removed from the
* orphan list in that case.
*/
if (inode->i_nlink)
ext4_orphan_del(NULL, inode);
}
if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry;
if (page)
block_commit_write(page, from, to);
out:
if (page) {
unlock_page(page);
put_page(page);
}
if (sem_held)
ext4_write_unlock_xattr(inode, &no_expand);
if (handle)
ext4_journal_stop(handle);
brelse(iloc.bh);
return ret;
}
/*
* Try to write data in the inode.
* If the inode has inline data, check whether the new write can be
* in the inode also. If not, create the page the handle, move the data
* to the page make it update and let the later codes create extent for it.
*/
int ext4_try_to_write_inline_data(struct address_space *mapping,
struct inode *inode,
loff_t pos, unsigned len,
unsigned flags,
struct page **pagep)
{
int ret;
handle_t *handle;
struct page *page;
struct ext4_iloc iloc;
if (pos + len > ext4_get_max_inline_size(inode))
goto convert;
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
return ret;
/*
* The possible write could happen in the inode,
* so try to reserve the space in inode first.
*/
handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
handle = NULL;
goto out;
}
ret = ext4_prepare_inline_data(handle, inode, pos + len);
if (ret && ret != -ENOSPC)
goto out;
/* We don't have space in inline inode, so convert it to extent. */
if (ret == -ENOSPC) {
ext4_journal_stop(handle);
brelse(iloc.bh);
goto convert;
}
ret = ext4_journal_get_write_access(handle, iloc.bh);
if (ret)
goto out;
flags |= AOP_FLAG_NOFS;
page = grab_cache_page_write_begin(mapping, 0, flags);
if (!page) {
ret = -ENOMEM;
goto out;
}
*pagep = page;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
ret = 0;
unlock_page(page);
put_page(page);
goto out_up_read;
}
if (!PageUptodate(page)) {
ret = ext4_read_inline_page(inode, page);
if (ret < 0) {
unlock_page(page);
put_page(page);
goto out_up_read;
}
}
ret = 1;
handle = NULL;
out_up_read:
up_read(&EXT4_I(inode)->xattr_sem);
out:
if (handle && (ret != 1))
ext4_journal_stop(handle);
brelse(iloc.bh);
return ret;
convert:
return ext4_convert_inline_data_to_extent(mapping,
inode, flags);
}
int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len,
unsigned copied, struct page *page)
{
int ret, no_expand;
void *kaddr;
struct ext4_iloc iloc;
if (unlikely(copied < len)) {
if (!PageUptodate(page)) {
copied = 0;
goto out;
}
}
ret = ext4_get_inode_loc(inode, &iloc);
if (ret) {
ext4_std_error(inode->i_sb, ret);
copied = 0;
goto out;
}
ext4_write_lock_xattr(inode, &no_expand);
BUG_ON(!ext4_has_inline_data(inode));
/*
* ei->i_inline_off may have changed since ext4_write_begin()
* called ext4_try_to_write_inline_data()
*/
(void) ext4_find_inline_data_nolock(inode);
kaddr = kmap_atomic(page);
ext4_write_inline_data(inode, &iloc, kaddr, pos, len);
kunmap_atomic(kaddr);
SetPageUptodate(page);
/* clear page dirty so that writepages wouldn't work for us. */
ClearPageDirty(page);
ext4_write_unlock_xattr(inode, &no_expand);
brelse(iloc.bh);
mark_inode_dirty(inode);
out:
return copied;
}
struct buffer_head *
ext4_journalled_write_inline_data(struct inode *inode,
unsigned len,
struct page *page)
{
int ret, no_expand;
void *kaddr;
struct ext4_iloc iloc;
ret = ext4_get_inode_loc(inode, &iloc);
if (ret) {
ext4_std_error(inode->i_sb, ret);
return NULL;
}
ext4_write_lock_xattr(inode, &no_expand);
kaddr = kmap_atomic(page);
ext4_write_inline_data(inode, &iloc, kaddr, 0, len);
kunmap_atomic(kaddr);
ext4_write_unlock_xattr(inode, &no_expand);
return iloc.bh;
}
/*
* Try to make the page cache and handle ready for the inline data case.
* We can call this function in 2 cases:
* 1. The inode is created and the first write exceeds inline size. We can
* clear the inode state safely.
* 2. The inode has inline data, then we need to read the data, make it
* update and dirty so that ext4_da_writepages can handle it. We don't
* need to start the journal since the file's metatdata isn't changed now.
*/
static int ext4_da_convert_inline_data_to_extent(struct address_space *mapping,
struct inode *inode,
unsigned flags,
void **fsdata)
{
int ret = 0, inline_size;
struct page *page;
page = grab_cache_page_write_begin(mapping, 0, flags);
if (!page)
return -ENOMEM;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
goto out;
}
inline_size = ext4_get_inline_size(inode);
if (!PageUptodate(page)) {
ret = ext4_read_inline_page(inode, page);
if (ret < 0)
goto out;
}
ret = __block_write_begin(page, 0, inline_size,
ext4_da_get_block_prep);
if (ret) {
up_read(&EXT4_I(inode)->xattr_sem);
unlock_page(page);
put_page(page);
ext4_truncate_failed_write(inode);
return ret;
}
SetPageDirty(page);
SetPageUptodate(page);
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
*fsdata = (void *)CONVERT_INLINE_DATA;
out:
up_read(&EXT4_I(inode)->xattr_sem);
if (page) {
unlock_page(page);
put_page(page);
}
return ret;
}
/*
* Prepare the write for the inline data.
* If the the data can be written into the inode, we just read
* the page and make it uptodate, and start the journal.
* Otherwise read the page, makes it dirty so that it can be
* handle in writepages(the i_disksize update is left to the
* normal ext4_da_write_end).
*/
int ext4_da_write_inline_data_begin(struct address_space *mapping,
struct inode *inode,
loff_t pos, unsigned len,
unsigned flags,
struct page **pagep,
void **fsdata)
{
int ret, inline_size;
handle_t *handle;
struct page *page;
struct ext4_iloc iloc;
int retries = 0;
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
return ret;
retry_journal:
handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto out;
}
inline_size = ext4_get_max_inline_size(inode);
ret = -ENOSPC;
if (inline_size >= pos + len) {
ret = ext4_prepare_inline_data(handle, inode, pos + len);
if (ret && ret != -ENOSPC)
goto out_journal;
}
/*
* We cannot recurse into the filesystem as the transaction
* is already started.
*/
flags |= AOP_FLAG_NOFS;
if (ret == -ENOSPC) {
ext4_journal_stop(handle);
ret = ext4_da_convert_inline_data_to_extent(mapping,
inode,
flags,
fsdata);
if (ret == -ENOSPC &&
ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry_journal;
goto out;
}
page = grab_cache_page_write_begin(mapping, 0, flags);
if (!page) {
ret = -ENOMEM;
goto out_journal;
}
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
ret = 0;
goto out_release_page;
}
if (!PageUptodate(page)) {
ret = ext4_read_inline_page(inode, page);
if (ret < 0)
goto out_release_page;
}
ret = ext4_journal_get_write_access(handle, iloc.bh);
if (ret)
goto out_release_page;
up_read(&EXT4_I(inode)->xattr_sem);
*pagep = page;
brelse(iloc.bh);
return 1;
out_release_page:
up_read(&EXT4_I(inode)->xattr_sem);
unlock_page(page);
put_page(page);
out_journal:
ext4_journal_stop(handle);
out:
brelse(iloc.bh);
return ret;
}
int ext4_da_write_inline_data_end(struct inode *inode, loff_t pos,
unsigned len, unsigned copied,
struct page *page)
{
int ret;
ret = ext4_write_inline_data_end(inode, pos, len, copied, page);
if (ret < 0) {
unlock_page(page);
put_page(page);
return ret;
}
copied = ret;
/*
* No need to use i_size_read() here, the i_size
* cannot change under us because we hold i_mutex.
*
* But it's important to update i_size while still holding page lock:
* page writeout could otherwise come in and zero beyond i_size.
*/
if (pos+copied > inode->i_size)
i_size_write(inode, pos+copied);
unlock_page(page);
put_page(page);
/*
* Don't mark the inode dirty under page lock. First, it unnecessarily
* makes the holding time of page lock longer. Second, it forces lock
* ordering of page lock and transaction start for journaling
* filesystems.
*/
mark_inode_dirty(inode);
return copied;
}
#ifdef INLINE_DIR_DEBUG
void ext4_show_inline_dir(struct inode *dir, struct buffer_head *bh,
void *inline_start, int inline_size)
{
int offset;
unsigned short de_len;
struct ext4_dir_entry_2 *de = inline_start;
void *dlimit = inline_start + inline_size;
trace_printk("inode %lu\n", dir->i_ino);
offset = 0;
while ((void *)de < dlimit) {
de_len = ext4_rec_len_from_disk(de->rec_len, inline_size);
trace_printk("de: off %u rlen %u name %.*s nlen %u ino %u\n",
offset, de_len, de->name_len, de->name,
de->name_len, le32_to_cpu(de->inode));
if (ext4_check_dir_entry(dir, NULL, de, bh,
inline_start, inline_size, offset))
BUG();
offset += de_len;
de = (struct ext4_dir_entry_2 *) ((char *) de + de_len);
}
}
#else
#define ext4_show_inline_dir(dir, bh, inline_start, inline_size)
#endif
/*
* Add a new entry into a inline dir.
* It will return -ENOSPC if no space is available, and -EIO
* and -EEXIST if directory entry already exists.
*/
static int ext4_add_dirent_to_inline(handle_t *handle,
struct ext4_filename *fname,
struct inode *dir,
struct inode *inode,
struct ext4_iloc *iloc,
void *inline_start, int inline_size)
{
int err;
struct ext4_dir_entry_2 *de;
err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
inline_size, fname, &de);
if (err)
return err;
BUFFER_TRACE(iloc->bh, "get_write_access");
err = ext4_journal_get_write_access(handle, iloc->bh);
if (err)
return err;
ext4_insert_dentry(dir, inode, de, inline_size, fname);
ext4_show_inline_dir(dir, iloc->bh, inline_start, inline_size);
/*
* XXX shouldn't update any times until successful
* completion of syscall, but too many callers depend
* on this.
*
* XXX similarly, too many callers depend on
* ext4_new_inode() setting the times, but error
* recovery deletes the inode, so the worst that can
* happen is that the times are slightly out of date
* and/or different from the directory change time.
*/
dir->i_mtime = dir->i_ctime = current_time(dir);
ext4_update_dx_flag(dir);
inode_inc_iversion(dir);
return 1;
}
static void *ext4_get_inline_xattr_pos(struct inode *inode,
struct ext4_iloc *iloc)
{
struct ext4_xattr_entry *entry;
struct ext4_xattr_ibody_header *header;
BUG_ON(!EXT4_I(inode)->i_inline_off);
header = IHDR(inode, ext4_raw_inode(iloc));
entry = (struct ext4_xattr_entry *)((void *)ext4_raw_inode(iloc) +
EXT4_I(inode)->i_inline_off);
return (void *)IFIRST(header) + le16_to_cpu(entry->e_value_offs);
}
/* Set the final de to cover the whole block. */
static void ext4_update_final_de(void *de_buf, int old_size, int new_size)
{
struct ext4_dir_entry_2 *de, *prev_de;
void *limit;
int de_len;
de = (struct ext4_dir_entry_2 *)de_buf;
if (old_size) {
limit = de_buf + old_size;
do {
prev_de = de;
de_len = ext4_rec_len_from_disk(de->rec_len, old_size);
de_buf += de_len;
de = (struct ext4_dir_entry_2 *)de_buf;
} while (de_buf < limit);
prev_de->rec_len = ext4_rec_len_to_disk(de_len + new_size -
old_size, new_size);
} else {
/* this is just created, so create an empty entry. */
de->inode = 0;
de->rec_len = ext4_rec_len_to_disk(new_size, new_size);
}
}
static int ext4_update_inline_dir(handle_t *handle, struct inode *dir,
struct ext4_iloc *iloc)
{
int ret;
int old_size = EXT4_I(dir)->i_inline_size - EXT4_MIN_INLINE_DATA_SIZE;
int new_size = get_max_inline_xattr_value_size(dir, iloc);
if (new_size - old_size <= ext4_dir_rec_len(1, NULL))
return -ENOSPC;
ret = ext4_update_inline_data(handle, dir,
new_size + EXT4_MIN_INLINE_DATA_SIZE);
if (ret)
return ret;
ext4_update_final_de(ext4_get_inline_xattr_pos(dir, iloc), old_size,
EXT4_I(dir)->i_inline_size -
EXT4_MIN_INLINE_DATA_SIZE);
dir->i_size = EXT4_I(dir)->i_disksize = EXT4_I(dir)->i_inline_size;
return 0;
}
static void ext4_restore_inline_data(handle_t *handle, struct inode *inode,
struct ext4_iloc *iloc,
void *buf, int inline_size)
{
int ret;
ret = ext4_create_inline_data(handle, inode, inline_size);
if (ret) {
ext4_msg(inode->i_sb, KERN_EMERG,
"error restoring inline_data for inode -- potential data loss! (inode %lu, error %d)",
inode->i_ino, ret);
return;
}
ext4_write_inline_data(inode, iloc, buf, 0, inline_size);
ext4_set_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
}
static int ext4_finish_convert_inline_dir(handle_t *handle,
struct inode *inode,
struct buffer_head *dir_block,
void *buf,
int inline_size)
{
int err, csum_size = 0, header_size = 0;
struct ext4_dir_entry_2 *de;
struct ext4_dir_entry_tail *t;
void *target = dir_block->b_data;
/*
* First create "." and ".." and then copy the dir information
* back to the block.
*/
de = (struct ext4_dir_entry_2 *)target;
de = ext4_init_dot_dotdot(inode, de,
inode->i_sb->s_blocksize, csum_size,
le32_to_cpu(((struct ext4_dir_entry_2 *)buf)->inode), 1);
header_size = (void *)de - target;
memcpy((void *)de, buf + EXT4_INLINE_DOTDOT_SIZE,
inline_size - EXT4_INLINE_DOTDOT_SIZE);
if (ext4_has_metadata_csum(inode->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
inode->i_size = inode->i_sb->s_blocksize;
i_size_write(inode, inode->i_sb->s_blocksize);
EXT4_I(inode)->i_disksize = inode->i_sb->s_blocksize;
ext4_update_final_de(dir_block->b_data,
inline_size - EXT4_INLINE_DOTDOT_SIZE + header_size,
inode->i_sb->s_blocksize - csum_size);
if (csum_size) {
t = EXT4_DIRENT_TAIL(dir_block->b_data,
inode->i_sb->s_blocksize);
initialize_dirent_tail(t, inode->i_sb->s_blocksize);
}
set_buffer_uptodate(dir_block);
err = ext4_handle_dirty_dirent_node(handle, inode, dir_block);
if (err)
return err;
set_buffer_verified(dir_block);
return ext4_mark_inode_dirty(handle, inode);
}
static int ext4_convert_inline_data_nolock(handle_t *handle,
struct inode *inode,
struct ext4_iloc *iloc)
{
int error;
void *buf = NULL;
struct buffer_head *data_bh = NULL;
struct ext4_map_blocks map;
int inline_size;
inline_size = ext4_get_inline_size(inode);
buf = kmalloc(inline_size, GFP_NOFS);
if (!buf) {
error = -ENOMEM;
goto out;
}
error = ext4_read_inline_data(inode, buf, inline_size, iloc);
if (error < 0)
goto out;
/*
* Make sure the inline directory entries pass checks before we try to
* convert them, so that we avoid touching stuff that needs fsck.
*/
if (S_ISDIR(inode->i_mode)) {
error = ext4_check_all_de(inode, iloc->bh,
buf + EXT4_INLINE_DOTDOT_SIZE,
inline_size - EXT4_INLINE_DOTDOT_SIZE);
if (error)
goto out;
}
error = ext4_destroy_inline_data_nolock(handle, inode);
if (error)
goto out;
map.m_lblk = 0;
map.m_len = 1;
map.m_flags = 0;
error = ext4_map_blocks(handle, inode, &map, EXT4_GET_BLOCKS_CREATE);
if (error < 0)
goto out_restore;
if (!(map.m_flags & EXT4_MAP_MAPPED)) {
error = -EIO;
goto out_restore;
}
data_bh = sb_getblk(inode->i_sb, map.m_pblk);
if (!data_bh) {
error = -ENOMEM;
goto out_restore;
}
lock_buffer(data_bh);
error = ext4_journal_get_create_access(handle, data_bh);
if (error) {
unlock_buffer(data_bh);
error = -EIO;
goto out_restore;
}
memset(data_bh->b_data, 0, inode->i_sb->s_blocksize);
if (!S_ISDIR(inode->i_mode)) {
memcpy(data_bh->b_data, buf, inline_size);
set_buffer_uptodate(data_bh);
error = ext4_handle_dirty_metadata(handle,
inode, data_bh);
} else {
error = ext4_finish_convert_inline_dir(handle, inode, data_bh,
buf, inline_size);
}
unlock_buffer(data_bh);
out_restore:
if (error)
ext4_restore_inline_data(handle, inode, iloc, buf, inline_size);
out:
brelse(data_bh);
kfree(buf);
return error;
}
/*
* Try to add the new entry to the inline data.
* If succeeds, return 0. If not, extended the inline dir and copied data to
* the new created block.
*/
int ext4_try_add_inline_entry(handle_t *handle, struct ext4_filename *fname,
struct inode *dir, struct inode *inode)
{
int ret, inline_size, no_expand;
void *inline_start;
struct ext4_iloc iloc;
ret = ext4_get_inode_loc(dir, &iloc);
if (ret)
return ret;
ext4_write_lock_xattr(dir, &no_expand);
if (!ext4_has_inline_data(dir))
goto out;
inline_start = (void *)ext4_raw_inode(&iloc)->i_block +
EXT4_INLINE_DOTDOT_SIZE;
inline_size = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DOTDOT_SIZE;
ret = ext4_add_dirent_to_inline(handle, fname, dir, inode, &iloc,
inline_start, inline_size);
if (ret != -ENOSPC)
goto out;
/* check whether it can be inserted to inline xattr space. */
inline_size = EXT4_I(dir)->i_inline_size -
EXT4_MIN_INLINE_DATA_SIZE;
if (!inline_size) {
/* Try to use the xattr space.*/
ret = ext4_update_inline_dir(handle, dir, &iloc);
if (ret && ret != -ENOSPC)
goto out;
inline_size = EXT4_I(dir)->i_inline_size -
EXT4_MIN_INLINE_DATA_SIZE;
}
if (inline_size) {
inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
ret = ext4_add_dirent_to_inline(handle, fname, dir,
inode, &iloc, inline_start,
inline_size);
if (ret != -ENOSPC)
goto out;
}
/*
* The inline space is filled up, so create a new block for it.
* As the extent tree will be created, we have to save the inline
* dir first.
*/
ret = ext4_convert_inline_data_nolock(handle, dir, &iloc);
out:
ext4_write_unlock_xattr(dir, &no_expand);
ext4_mark_inode_dirty(handle, dir);
brelse(iloc.bh);
return ret;
}
/*
* This function fills a red-black tree with information from an
* inlined dir. It returns the number directory entries loaded
* into the tree. If there is an error it is returned in err.
*/
int htree_inlinedir_to_tree(struct file *dir_file,
struct inode *dir, ext4_lblk_t block,
struct dx_hash_info *hinfo,
__u32 start_hash, __u32 start_minor_hash,
int *has_inline_data)
{
int err = 0, count = 0;
unsigned int parent_ino;
int pos;
struct ext4_dir_entry_2 *de;
struct inode *inode = file_inode(dir_file);
int ret, inline_size = 0;
struct ext4_iloc iloc;
void *dir_buf = NULL;
struct ext4_dir_entry_2 fake;
struct fscrypt_str tmp_str;
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
return ret;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
up_read(&EXT4_I(inode)->xattr_sem);
*has_inline_data = 0;
goto out;
}
inline_size = ext4_get_inline_size(inode);
dir_buf = kmalloc(inline_size, GFP_NOFS);
if (!dir_buf) {
ret = -ENOMEM;
up_read(&EXT4_I(inode)->xattr_sem);
goto out;
}
ret = ext4_read_inline_data(inode, dir_buf, inline_size, &iloc);
up_read(&EXT4_I(inode)->xattr_sem);
if (ret < 0)
goto out;
pos = 0;
parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode);
while (pos < inline_size) {
/*
* As inlined dir doesn't store any information about '.' and
* only the inode number of '..' is stored, we have to handle
* them differently.
*/
if (pos == 0) {
fake.inode = cpu_to_le32(inode->i_ino);
fake.name_len = 1;
strcpy(fake.name, ".");
fake.rec_len = ext4_rec_len_to_disk(
ext4_dir_rec_len(fake.name_len, NULL),
inline_size);
ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
de = &fake;
pos = EXT4_INLINE_DOTDOT_OFFSET;
} else if (pos == EXT4_INLINE_DOTDOT_OFFSET) {
fake.inode = cpu_to_le32(parent_ino);
fake.name_len = 2;
strcpy(fake.name, "..");
fake.rec_len = ext4_rec_len_to_disk(
ext4_dir_rec_len(fake.name_len, NULL),
inline_size);
ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
de = &fake;
pos = EXT4_INLINE_DOTDOT_SIZE;
} else {
de = (struct ext4_dir_entry_2 *)(dir_buf + pos);
pos += ext4_rec_len_from_disk(de->rec_len, inline_size);
if (ext4_check_dir_entry(inode, dir_file, de,
iloc.bh, dir_buf,
inline_size, pos)) {
ret = count;
goto out;
}
}
if (ext4_hash_in_dirent(dir)) {
hinfo->hash = EXT4_DIRENT_HASH(de);
hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de);
} else {
ext4fs_dirhash(dir, de->name, de->name_len, hinfo);
}
if ((hinfo->hash < start_hash) ||
((hinfo->hash == start_hash) &&
(hinfo->minor_hash < start_minor_hash)))
continue;
if (de->inode == 0)
continue;
tmp_str.name = de->name;
tmp_str.len = de->name_len;
err = ext4_htree_store_dirent(dir_file, hinfo->hash,
hinfo->minor_hash, de, &tmp_str);
if (err) {
ret = err;
goto out;
}
count++;
}
ret = count;
out:
kfree(dir_buf);
brelse(iloc.bh);
return ret;
}
/*
* So this function is called when the volume is mkfsed with
* dir_index disabled. In order to keep f_pos persistent
* after we convert from an inlined dir to a blocked based,
* we just pretend that we are a normal dir and return the
* offset as if '.' and '..' really take place.
*
*/
int ext4_read_inline_dir(struct file *file,
struct dir_context *ctx,
int *has_inline_data)
{
unsigned int offset, parent_ino;
int i;
struct ext4_dir_entry_2 *de;
struct super_block *sb;
struct inode *inode = file_inode(file);
int ret, inline_size = 0;
struct ext4_iloc iloc;
void *dir_buf = NULL;
int dotdot_offset, dotdot_size, extra_offset, extra_size;
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
return ret;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
up_read(&EXT4_I(inode)->xattr_sem);
*has_inline_data = 0;
goto out;
}
inline_size = ext4_get_inline_size(inode);
dir_buf = kmalloc(inline_size, GFP_NOFS);
if (!dir_buf) {
ret = -ENOMEM;
up_read(&EXT4_I(inode)->xattr_sem);
goto out;
}
ret = ext4_read_inline_data(inode, dir_buf, inline_size, &iloc);
up_read(&EXT4_I(inode)->xattr_sem);
if (ret < 0)
goto out;
ret = 0;
sb = inode->i_sb;
parent_ino = le32_to_cpu(((struct ext4_dir_entry_2 *)dir_buf)->inode);
offset = ctx->pos;
/*
* dotdot_offset and dotdot_size is the real offset and
* size for ".." and "." if the dir is block based while
* the real size for them are only EXT4_INLINE_DOTDOT_SIZE.
* So we will use extra_offset and extra_size to indicate them
* during the inline dir iteration.
*/
dotdot_offset = ext4_dir_rec_len(1, NULL);
dotdot_size = dotdot_offset + ext4_dir_rec_len(2, NULL);
extra_offset = dotdot_size - EXT4_INLINE_DOTDOT_SIZE;
extra_size = extra_offset + inline_size;
/*
* If the version has changed since the last call to
* readdir(2), then we might be pointing to an invalid
* dirent right now. Scan from the start of the inline
* dir to make sure.
*/
if (!inode_eq_iversion(inode, file->f_version)) {
for (i = 0; i < extra_size && i < offset;) {
/*
* "." is with offset 0 and
* ".." is dotdot_offset.
*/
if (!i) {
i = dotdot_offset;
continue;
} else if (i == dotdot_offset) {
i = dotdot_size;
continue;
}
/* for other entry, the real offset in
* the buf has to be tuned accordingly.
*/
de = (struct ext4_dir_entry_2 *)
(dir_buf + i - extra_offset);
/* It's too expensive to do a full
* dirent test each time round this
* loop, but we do have to test at
* least that it is non-zero. A
* failure will be detected in the
* dirent test below. */
if (ext4_rec_len_from_disk(de->rec_len, extra_size)
< ext4_dir_rec_len(1, NULL))
break;
i += ext4_rec_len_from_disk(de->rec_len,
extra_size);
}
offset = i;
ctx->pos = offset;
file->f_version = inode_query_iversion(inode);
}
while (ctx->pos < extra_size) {
if (ctx->pos == 0) {
if (!dir_emit(ctx, ".", 1, inode->i_ino, DT_DIR))
goto out;
ctx->pos = dotdot_offset;
continue;
}
if (ctx->pos == dotdot_offset) {
if (!dir_emit(ctx, "..", 2, parent_ino, DT_DIR))
goto out;
ctx->pos = dotdot_size;
continue;
}
de = (struct ext4_dir_entry_2 *)
(dir_buf + ctx->pos - extra_offset);
if (ext4_check_dir_entry(inode, file, de, iloc.bh, dir_buf,
extra_size, ctx->pos))
goto out;
if (le32_to_cpu(de->inode)) {
if (!dir_emit(ctx, de->name, de->name_len,
le32_to_cpu(de->inode),
get_dtype(sb, de->file_type)))
goto out;
}
ctx->pos += ext4_rec_len_from_disk(de->rec_len, extra_size);
}
out:
kfree(dir_buf);
brelse(iloc.bh);
return ret;
}
struct buffer_head *ext4_get_first_inline_block(struct inode *inode,
struct ext4_dir_entry_2 **parent_de,
int *retval)
{
struct ext4_iloc iloc;
*retval = ext4_get_inode_loc(inode, &iloc);
if (*retval)
return NULL;
*parent_de = (struct ext4_dir_entry_2 *)ext4_raw_inode(&iloc)->i_block;
return iloc.bh;
}
/*
* Try to create the inline data for the new dir.
* If it succeeds, return 0, otherwise return the error.
* In case of ENOSPC, the caller should create the normal disk layout dir.
*/
int ext4_try_create_inline_dir(handle_t *handle, struct inode *parent,
struct inode *inode)
{
int ret, inline_size = EXT4_MIN_INLINE_DATA_SIZE;
struct ext4_iloc iloc;
struct ext4_dir_entry_2 *de;
ret = ext4_get_inode_loc(inode, &iloc);
if (ret)
return ret;
ret = ext4_prepare_inline_data(handle, inode, inline_size);
if (ret)
goto out;
/*
* For inline dir, we only save the inode information for the ".."
* and create a fake dentry to cover the left space.
*/
de = (struct ext4_dir_entry_2 *)ext4_raw_inode(&iloc)->i_block;
de->inode = cpu_to_le32(parent->i_ino);
de = (struct ext4_dir_entry_2 *)((void *)de + EXT4_INLINE_DOTDOT_SIZE);
de->inode = 0;
de->rec_len = ext4_rec_len_to_disk(
inline_size - EXT4_INLINE_DOTDOT_SIZE,
inline_size);
set_nlink(inode, 2);
inode->i_size = EXT4_I(inode)->i_disksize = inline_size;
out:
brelse(iloc.bh);
return ret;
}
struct buffer_head *ext4_find_inline_entry(struct inode *dir,
struct ext4_filename *fname,
struct ext4_dir_entry_2 **res_dir,
int *has_inline_data)
{
struct ext4_xattr_ibody_find is = {
.s = { .not_found = -ENODATA, },
};
struct ext4_xattr_info i = {
.name_index = EXT4_XATTR_INDEX_SYSTEM,
.name = EXT4_XATTR_SYSTEM_DATA,
};
int ret;
void *inline_start;
int inline_size;
ret = ext4_get_inode_loc(dir, &is.iloc);
if (ret)
return ERR_PTR(ret);
down_read(&EXT4_I(dir)->xattr_sem);
ret = ext4_xattr_ibody_find(dir, &i, &is);
if (ret)
goto out;
if (!ext4_has_inline_data(dir)) {
*has_inline_data = 0;
goto out;
}
inline_start = (void *)ext4_raw_inode(&is.iloc)->i_block +
EXT4_INLINE_DOTDOT_SIZE;
inline_size = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DOTDOT_SIZE;
ret = ext4_search_dir(is.iloc.bh, inline_start, inline_size,
dir, fname, 0, res_dir);
if (ret == 1)
goto out_find;
if (ret < 0)
goto out;
if (ext4_get_inline_size(dir) == EXT4_MIN_INLINE_DATA_SIZE)
goto out;
inline_start = ext4_get_inline_xattr_pos(dir, &is.iloc);
inline_size = ext4_get_inline_size(dir) - EXT4_MIN_INLINE_DATA_SIZE;
ret = ext4_search_dir(is.iloc.bh, inline_start, inline_size,
dir, fname, 0, res_dir);
if (ret == 1)
goto out_find;
out:
brelse(is.iloc.bh);
if (ret < 0)
is.iloc.bh = ERR_PTR(ret);
else
is.iloc.bh = NULL;
out_find:
up_read(&EXT4_I(dir)->xattr_sem);
return is.iloc.bh;
}
int ext4_delete_inline_entry(handle_t *handle,
struct inode *dir,
struct ext4_dir_entry_2 *de_del,
struct buffer_head *bh,
int *has_inline_data)
{
int err, inline_size, no_expand;
struct ext4_iloc iloc;
void *inline_start;
err = ext4_get_inode_loc(dir, &iloc);
if (err)
return err;
ext4_write_lock_xattr(dir, &no_expand);
if (!ext4_has_inline_data(dir)) {
*has_inline_data = 0;
goto out;
}
if ((void *)de_del - ((void *)ext4_raw_inode(&iloc)->i_block) <
EXT4_MIN_INLINE_DATA_SIZE) {
inline_start = (void *)ext4_raw_inode(&iloc)->i_block +
EXT4_INLINE_DOTDOT_SIZE;
inline_size = EXT4_MIN_INLINE_DATA_SIZE -
EXT4_INLINE_DOTDOT_SIZE;
} else {
inline_start = ext4_get_inline_xattr_pos(dir, &iloc);
inline_size = ext4_get_inline_size(dir) -
EXT4_MIN_INLINE_DATA_SIZE;
}
BUFFER_TRACE(bh, "get_write_access");
err = ext4_journal_get_write_access(handle, bh);
if (err)
goto out;
err = ext4_generic_delete_entry(handle, dir, de_del, bh,
inline_start, inline_size, 0);
if (err)
goto out;
ext4_show_inline_dir(dir, iloc.bh, inline_start, inline_size);
out:
ext4_write_unlock_xattr(dir, &no_expand);
if (likely(err == 0))
err = ext4_mark_inode_dirty(handle, dir);
brelse(iloc.bh);
if (err != -ENOENT)
ext4_std_error(dir->i_sb, err);
return err;
}
/*
* Get the inline dentry at offset.
*/
static inline struct ext4_dir_entry_2 *
ext4_get_inline_entry(struct inode *inode,
struct ext4_iloc *iloc,
unsigned int offset,
void **inline_start,
int *inline_size)
{
void *inline_pos;
BUG_ON(offset > ext4_get_inline_size(inode));
if (offset < EXT4_MIN_INLINE_DATA_SIZE) {
inline_pos = (void *)ext4_raw_inode(iloc)->i_block;
*inline_size = EXT4_MIN_INLINE_DATA_SIZE;
} else {
inline_pos = ext4_get_inline_xattr_pos(inode, iloc);
offset -= EXT4_MIN_INLINE_DATA_SIZE;
*inline_size = ext4_get_inline_size(inode) -
EXT4_MIN_INLINE_DATA_SIZE;
}
if (inline_start)
*inline_start = inline_pos;
return (struct ext4_dir_entry_2 *)(inline_pos + offset);
}
bool empty_inline_dir(struct inode *dir, int *has_inline_data)
{
int err, inline_size;
struct ext4_iloc iloc;
size_t inline_len;
void *inline_pos;
unsigned int offset;
struct ext4_dir_entry_2 *de;
bool ret = true;
err = ext4_get_inode_loc(dir, &iloc);
if (err) {
EXT4_ERROR_INODE(dir, "error %d getting inode %lu block",
err, dir->i_ino);
return true;
}
down_read(&EXT4_I(dir)->xattr_sem);
if (!ext4_has_inline_data(dir)) {
*has_inline_data = 0;
goto out;
}
de = (struct ext4_dir_entry_2 *)ext4_raw_inode(&iloc)->i_block;
if (!le32_to_cpu(de->inode)) {
ext4_warning(dir->i_sb,
"bad inline directory (dir #%lu) - no `..'",
dir->i_ino);
ret = true;
goto out;
}
inline_len = ext4_get_inline_size(dir);
offset = EXT4_INLINE_DOTDOT_SIZE;
while (offset < inline_len) {
de = ext4_get_inline_entry(dir, &iloc, offset,
&inline_pos, &inline_size);
if (ext4_check_dir_entry(dir, NULL, de,
iloc.bh, inline_pos,
inline_size, offset)) {
ext4_warning(dir->i_sb,
"bad inline directory (dir #%lu) - "
"inode %u, rec_len %u, name_len %d"
"inline size %d",
dir->i_ino, le32_to_cpu(de->inode),
le16_to_cpu(de->rec_len), de->name_len,
inline_size);
ret = true;
goto out;
}
if (le32_to_cpu(de->inode)) {
ret = false;
goto out;
}
offset += ext4_rec_len_from_disk(de->rec_len, inline_size);
}
out:
up_read(&EXT4_I(dir)->xattr_sem);
brelse(iloc.bh);
return ret;
}
int ext4_destroy_inline_data(handle_t *handle, struct inode *inode)
{
int ret, no_expand;
ext4_write_lock_xattr(inode, &no_expand);
ret = ext4_destroy_inline_data_nolock(handle, inode);
ext4_write_unlock_xattr(inode, &no_expand);
return ret;
}
int ext4_inline_data_iomap(struct inode *inode, struct iomap *iomap)
{
__u64 addr;
int error = -EAGAIN;
struct ext4_iloc iloc;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode))
goto out;
error = ext4_get_inode_loc(inode, &iloc);
if (error)
goto out;
addr = (__u64)iloc.bh->b_blocknr << inode->i_sb->s_blocksize_bits;
addr += (char *)ext4_raw_inode(&iloc) - iloc.bh->b_data;
addr += offsetof(struct ext4_inode, i_block);
brelse(iloc.bh);
iomap->addr = addr;
iomap->offset = 0;
iomap->length = min_t(loff_t, ext4_get_inline_size(inode),
i_size_read(inode));
iomap->type = IOMAP_INLINE;
iomap->flags = 0;
out:
up_read(&EXT4_I(inode)->xattr_sem);
return error;
}
int ext4_inline_data_fiemap(struct inode *inode,
struct fiemap_extent_info *fieinfo,
int *has_inline, __u64 start, __u64 len)
{
__u64 physical = 0;
__u64 inline_len;
__u32 flags = FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_NOT_ALIGNED |
FIEMAP_EXTENT_LAST;
int error = 0;
struct ext4_iloc iloc;
down_read(&EXT4_I(inode)->xattr_sem);
if (!ext4_has_inline_data(inode)) {
*has_inline = 0;
goto out;
}
inline_len = min_t(size_t, ext4_get_inline_size(inode),
i_size_read(inode));
if (start >= inline_len)
goto out;
if (start + len < inline_len)
inline_len = start + len;
inline_len -= start;
error = ext4_get_inode_loc(inode, &iloc);
if (error)
goto out;
physical = (__u64)iloc.bh->b_blocknr << inode->i_sb->s_blocksize_bits;
physical += (char *)ext4_raw_inode(&iloc) - iloc.bh->b_data;
physical += offsetof(struct ext4_inode, i_block);
brelse(iloc.bh);
out:
up_read(&EXT4_I(inode)->xattr_sem);
if (physical)
error = fiemap_fill_next_extent(fieinfo, start, physical,
inline_len, flags);
return (error < 0 ? error : 0);
}
int ext4_inline_data_truncate(struct inode *inode, int *has_inline)
{
handle_t *handle;
int inline_size, value_len, needed_blocks, no_expand, err = 0;
size_t i_size;
void *value = NULL;
struct ext4_xattr_ibody_find is = {
.s = { .not_found = -ENODATA, },
};
struct ext4_xattr_info i = {
.name_index = EXT4_XATTR_INDEX_SYSTEM,
.name = EXT4_XATTR_SYSTEM_DATA,
};
needed_blocks = ext4_writepage_trans_blocks(inode);
handle = ext4_journal_start(inode, EXT4_HT_INODE, needed_blocks);
if (IS_ERR(handle))
return PTR_ERR(handle);
ext4_write_lock_xattr(inode, &no_expand);
if (!ext4_has_inline_data(inode)) {
ext4_write_unlock_xattr(inode, &no_expand);
*has_inline = 0;
ext4_journal_stop(handle);
return 0;
}
if ((err = ext4_orphan_add(handle, inode)) != 0)
goto out;
if ((err = ext4_get_inode_loc(inode, &is.iloc)) != 0)
goto out;
down_write(&EXT4_I(inode)->i_data_sem);
i_size = inode->i_size;
inline_size = ext4_get_inline_size(inode);
EXT4_I(inode)->i_disksize = i_size;
if (i_size < inline_size) {
/* Clear the content in the xattr space. */
if (inline_size > EXT4_MIN_INLINE_DATA_SIZE) {
if ((err = ext4_xattr_ibody_find(inode, &i, &is)) != 0)
goto out_error;
BUG_ON(is.s.not_found);
value_len = le32_to_cpu(is.s.here->e_value_size);
value = kmalloc(value_len, GFP_NOFS);
if (!value) {
err = -ENOMEM;
goto out_error;
}
err = ext4_xattr_ibody_get(inode, i.name_index,
i.name, value, value_len);
if (err <= 0)
goto out_error;
i.value = value;
i.value_len = i_size > EXT4_MIN_INLINE_DATA_SIZE ?
i_size - EXT4_MIN_INLINE_DATA_SIZE : 0;
err = ext4_xattr_ibody_set(handle, inode, &i, &is);
if (err)
goto out_error;
}
/* Clear the content within i_blocks. */
if (i_size < EXT4_MIN_INLINE_DATA_SIZE) {
void *p = (void *) ext4_raw_inode(&is.iloc)->i_block;
memset(p + i_size, 0,
EXT4_MIN_INLINE_DATA_SIZE - i_size);
}
EXT4_I(inode)->i_inline_size = i_size <
EXT4_MIN_INLINE_DATA_SIZE ?
EXT4_MIN_INLINE_DATA_SIZE : i_size;
}
out_error:
up_write(&EXT4_I(inode)->i_data_sem);
out:
brelse(is.iloc.bh);
ext4_write_unlock_xattr(inode, &no_expand);
kfree(value);
if (inode->i_nlink)
ext4_orphan_del(handle, inode);
if (err == 0) {
inode->i_mtime = inode->i_ctime = current_time(inode);
err = ext4_mark_inode_dirty(handle, inode);
if (IS_SYNC(inode))
ext4_handle_sync(handle);
}
ext4_journal_stop(handle);
return err;
}
int ext4_convert_inline_data(struct inode *inode)
{
int error, needed_blocks, no_expand;
handle_t *handle;
struct ext4_iloc iloc;
if (!ext4_has_inline_data(inode)) {
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
return 0;
} else if (!ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
/*
* Inode has inline data but EXT4_STATE_MAY_INLINE_DATA is
* cleared. This means we are in the middle of moving of
* inline data to delay allocated block. Just force writeout
* here to finish conversion.
*/
error = filemap_flush(inode->i_mapping);
if (error)
return error;
if (!ext4_has_inline_data(inode))
return 0;
}
needed_blocks = ext4_writepage_trans_blocks(inode);
iloc.bh = NULL;
error = ext4_get_inode_loc(inode, &iloc);
if (error)
return error;
handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE, needed_blocks);
if (IS_ERR(handle)) {
error = PTR_ERR(handle);
goto out_free;
}
ext4_write_lock_xattr(inode, &no_expand);
if (ext4_has_inline_data(inode))
error = ext4_convert_inline_data_nolock(handle, inode, &iloc);
ext4_write_unlock_xattr(inode, &no_expand);
ext4_journal_stop(handle);
out_free:
brelse(iloc.bh);
return error;
}