* 'linux-4.19.y-cip' of https://git.kernel.org/pub/scm/linux/kernel/git/cip/linux-cip: CIP: Bump version suffix to -cip124 after merge from cip/linux-4.19.y-st tree Update localversion-st, tree is up-to-date with 5.4.298. f2fs: fix to do sanity check on ino and xnid squashfs: fix memory leak in squashfs_fill_super pNFS: Handle RPC size limit for layoutcommits wifi: iwlwifi: fw: Fix possible memory leak in iwl_fw_dbg_collect usb: core: usb_submit_urb: downgrade type check udf: Verify partition map count f2fs: fix to avoid panic in f2fs_evict_inode usb: hub: Fix flushing and scheduling of delayed work that tunes runtime pm Revert "drm/dp: Change AUX DPCD probe address from DPCD_REV to LANE0_1_STATUS" net: usb: qmi_wwan: add Telit Cinterion LE910C4-WWX new compositions HID: hid-ntrig: fix unable to handle page fault in ntrig_report_version() HID: asus: fix UAF via HID_CLAIMED_INPUT validation efivarfs: Fix slab-out-of-bounds in efivarfs_d_compare sctp: initialize more fields in sctp_v6_from_sk() net: stmmac: xgmac: Do not enable RX FIFO Overflow interrupts net/mlx5e: Set local Xoff after FW update net: dlink: fix multicast stats being counted incorrectly atm: atmtcp: Prevent arbitrary write in atmtcp_recv_control(). net/atm: remove the atmdev_ops {get, set}sockopt methods Bluetooth: hci_event: Detect if HCI_EV_NUM_COMP_PKTS is unbalanced powerpc/kvm: Fix ifdef to remove build warning net: ipv4: fix regression in local-broadcast routes vhost/net: Protect ubufs with rcu read lock in vhost_net_ubuf_put() scsi: core: sysfs: Correct sysfs attributes access rights ftrace: Fix potential warning in trace_printk_seq during ftrace_dump alloc_fdtable(): change calling conventions. ALSA: usb-audio: Use correct sub-type for UAC3 feature unit validation net/sched: Make cake_enqueue return NET_XMIT_CN when past buffer_limit ipv6: sr: validate HMAC algorithm ID in seg6_hmac_info_add ALSA: usb-audio: Fix size validation in convert_chmap_v3() scsi: qla4xxx: Prevent a potential error pointer dereference usb: xhci: Fix slot_id resource race conflict nfs: fix UAF in direct writes NFS: Fix up commit deadlocks Bluetooth: fix use-after-free in device_for_each_child() selftests: forwarding: tc_actions.sh: add matchall mirror test codel: remove sch->q.qlen check before qdisc_tree_reduce_backlog() sch_qfq: make qfq_qlen_notify() idempotent sch_hfsc: make hfsc_qlen_notify() idempotent sch_drr: make drr_qlen_notify() idempotent btrfs: populate otime when logging an inode item media: venus: hfi: explicitly release IRQ during teardown f2fs: fix to avoid out-of-boundary access in dnode page media: venus: protect against spurious interrupts during probe media: venus: vdec: Clamp param smaller than 1fps and bigger than 240. drm/dp: Change AUX DPCD probe address from DPCD_REV to LANE0_1_STATUS media: rainshadow-cec: fix TOCTOU race condition in rain_interrupt() media: v4l2-ctrls: Don't reset handler's error in v4l2_ctrl_handler_free() ata: Fix SATA_MOBILE_LPM_POLICY description in Kconfig usb: musb: omap2430: fix device leak at unbind NFS: Fix the setting of capabilities when automounting a new filesystem NFS: Fix up handling of outstanding layoutcommit in nfs_update_inode() NFSv4: Fix nfs4_bitmap_copy_adjust() usb: typec: fusb302: cache PD RX state cdc-acm: fix race between initial clearing halt and open USB: cdc-acm: do not log successful probe on later errors nfsd: handle get_client_locked() failure in nfsd4_setclientid_confirm() tracing: Add down_write(trace_event_sem) when adding trace event usb: hub: Don't try to recover devices lost during warm reset. usb: hub: avoid warm port reset during USB3 disconnect x86/mce/amd: Add default names for MCA banks and blocks iio: hid-sensor-prox: Fix incorrect OFFSET calculation mm/zsmalloc: do not pass __GFP_MOVABLE if CONFIG_COMPACTION=n mm/zsmalloc.c: convert to use kmem_cache_zalloc in cache_alloc_zspage() net: usbnet: Fix the wrong netif_carrier_on() call net: usbnet: Avoid potential RCU stall on LINK_CHANGE event PCI/ACPI: Fix runtime PM ref imbalance on Hot-Plug Capable ports ACPI: processor: idle: Check acpi_fetch_acpi_dev() return value kbuild: Add KBUILD_CPPFLAGS to as-option invocation kbuild: add $(CLANG_FLAGS) to KBUILD_CPPFLAGS kbuild: Add CLANG_FLAGS to as-instr mips: Include KBUILD_CPPFLAGS in CHECKFLAGS invocation kbuild: Update assembler calls to use proper flags and language target ARM: 9448/1: Use an absolute path to unified.h in KBUILD_AFLAGS usb: dwc3: Ignore late xferNotReady event to prevent halt timeout USB: storage: Ignore driver CD mode for Realtek multi-mode Wi-Fi dongles usb: storage: realtek_cr: Use correct byte order for bcs->Residue USB: storage: Add unusual-devs entry for Novatek NTK96550-based camera usb: quirks: Add DELAY_INIT quick for another SanDisk 3.2Gen1 Flash Drive iio: proximity: isl29501: fix buffered read on big-endian systems ftrace: Also allocate and copy hash for reading of filter files fpga: zynq_fpga: Fix the wrong usage of dma_map_sgtable() fs/buffer: fix use-after-free when call bh_read() helper drm/amd/display: Fix fractional fb divider in set_pixel_clock_v3 media: venus: Add a check for packet size after reading from shared memory media: ov2659: Fix memory leaks in ov2659_probe() media: usbtv: Lock resolution while streaming media: gspca: Add bounds checking to firmware parser jbd2: prevent softlockup in jbd2_log_do_checkpoint() PCI: endpoint: Fix configfs group removal on driver teardown PCI: endpoint: Fix configfs group list head handling mtd: rawnand: fsmc: Add missing check after DMA map wifi: brcmsmac: Remove const from tbl_ptr parameter in wlc_lcnphy_common_read_table() zynq_fpga: use sgtable-based scatterlist wrappers ata: libata-scsi: Fix ata_to_sense_error() status handling ext4: fix reserved gdt blocks handling in fsmap ext4: fix fsmap end of range reporting with bigalloc ext4: check fast symlink for ea_inode correctly Revert "vgacon: Add check for vc_origin address range in vgacon_scroll()" vt: defkeymap: Map keycodes above 127 to K_HOLE usb: gadget: udc: renesas_usb3: fix device leak at unbind usb: atm: cxacru: Merge cxacru_upload_firmware() into cxacru_heavy_init() m68k: Fix lost column on framebuffer debug console serial: 8250: fix panic due to PSLVERR media: uvcvideo: Do not mark valid metadata as invalid media: uvcvideo: Fix 1-byte out-of-bounds read in uvc_parse_format() btrfs: fix log tree replay failure due to file with 0 links and extents thunderbolt: Fix copy+paste error in match_service_id() misc: rtsx: usb: Ensure mmc child device is active when card is present scsi: lpfc: Remove redundant assignment to avoid memory leak rtc: ds1307: remove clear of oscillator stop flag (OSF) in probe pNFS: Fix uninited ptr deref in block/scsi layout pNFS: Fix disk addr range check in block/scsi layout pNFS: Fix stripe mapping in block/scsi layout ipmi: Fix strcpy source and destination the same kconfig: lxdialog: fix 'space' to (de)select options kconfig: gconf: fix potential memory leak in renderer_edited() kconfig: gconf: avoid hardcoding model2 in on_treeview2_cursor_changed() scsi: aacraid: Stop using PCI_IRQ_AFFINITY scsi: Fix sas_user_scan() to handle wildcard and multi-channel scans kconfig: nconf: Ensure null termination where strncpy is used kconfig: lxdialog: replace strcpy() with strncpy() in inputbox.c PCI: pnv_php: Work around switches with broken presence detection media: uvcvideo: Fix bandwidth issue for Alcor camera media: dvb-frontends: w7090p: fix null-ptr-deref in w7090p_tuner_write_serpar and w7090p_tuner_read_serpar media: dvb-frontends: dib7090p: fix null-ptr-deref in dib7090p_rw_on_apb() media: usb: hdpvr: disable zero-length read messages media: tc358743: Increase FIFO trigger level to 374 media: tc358743: Return an appropriate colorspace from tc358743_set_fmt media: tc358743: Check I2C succeeded during probe pinctrl: stm32: Manage irq affinity settings scsi: mpt3sas: Correctly handle ATA device errors RDMA: hfi1: fix possible divide-by-zero in find_hw_thread_mask() MIPS: Don't crash in stack_top() for tasks without ABI or vDSO jfs: upper bound check of tree index in dbAllocAG jfs: Regular file corruption check jfs: truncate good inode pages when hard link is 0 scsi: bfa: Double-free fix MIPS: vpe-mt: add missing prototypes for vpe_{alloc,start,stop,free} watchdog: dw_wdt: Fix default timeout fs/orangefs: use snprintf() instead of sprintf() scsi: libiscsi: Initialize iscsi_conn->dd_data only if memory is allocated ext4: do not BUG when INLINE_DATA_FL lacks system.data xattr vhost: fail early when __vhost_add_used() fails uapi: in6: restore visibility of most IPv6 socket options net: ncsi: Fix buffer overflow in fetching version id net: dsa: b53: fix b53_imp_vlan_setup for BCM5325 net: vlan: Replace BUG() with WARN_ON_ONCE() in vlan_dev_* stubs wifi: iwlegacy: Check rate_idx range after addition netmem: fix skb_frag_address_safe with unreadable skbs wifi: rtlwifi: fix possible skb memory leak in `_rtl_pci_rx_interrupt()`. wifi: iwlwifi: dvm: fix potential overflow in rs_fill_link_cmd() net: fec: allow disable coalescing (powerpc/512) Fix possible `dma_unmap_single()` on uninitialized pointer s390/stp: Remove udelay from stp_sync_clock() wifi: iwlwifi: mvm: fix scan request validation net: thunderx: Fix format-truncation warning in bgx_acpi_match_id() net: ipv4: fix incorrect MTU in broadcast routes wifi: cfg80211: Fix interface type validation et131x: Add missing check after DMA map be2net: Use correct byte order and format string for TCP seq and ack_seq s390/time: Use monotonic clock in get_cycles() wifi: cfg80211: reject HTC bit for management frames ktest.pl: Prevent recursion of default variable options ASoC: codecs: rt5640: Retry DEVICE_ID verification ALSA: usb-audio: Avoid precedence issues in mixer_quirks macros ALSA: hda/ca0132: Fix buffer overflow in add_tuning_control platform/x86: thinkpad_acpi: Handle KCOV __init vs inline mismatches pm: cpupower: Fix the snapshot-order of tsc,mperf, clock in mperf_stop() ALSA: intel8x0: Fix incorrect codec index usage in mixer for ICH4 ASoC: hdac_hdmi: Rate limit logging on connection and disconnection mmc: rtsx_usb_sdmmc: Fix error-path in sd_set_power_mode() ACPI: processor: fix acpi_object initialization PM: sleep: console: Fix the black screen issue thermal: sysfs: Return ENODATA instead of EAGAIN for reads selftests: tracing: Use mutex_unlock for testing glob filter ARM: tegra: Use I/O memcpy to write to IRAM gpio: tps65912: check the return value of regmap_update_bits() ASoC: soc-dapm: set bias_level if snd_soc_dapm_set_bias_level() was successed cpufreq: Exit governor when failed to start old governor usb: xhci: Avoid showing errors during surprise removal usb: xhci: Set avg_trb_len = 8 for EP0 during Address Device Command usb: xhci: Avoid showing warnings for dying controller selftests/futex: Define SYS_futex on 32-bit architectures with 64-bit time_t usb: xhci: print xhci->xhc_state when queue_command failed securityfs: don't pin dentries twice, once is enough... hfs: fix not erasing deleted b-tree node issue drbd: add missing kref_get in handle_write_conflicts arm64: Handle KCOV __init vs inline mismatches hfsplus: don't use BUG_ON() in hfsplus_create_attributes_file() hfsplus: fix slab-out-of-bounds read in hfsplus_uni2asc() hfsplus: fix slab-out-of-bounds in hfsplus_bnode_read() hfs: fix slab-out-of-bounds in hfs_bnode_read() sctp: linearize cloned gso packets in sctp_rcv netfilter: ctnetlink: fix refcount leak on table dump udp: also consider secpath when evaluating ipsec use for checksumming fs: Prevent file descriptor table allocations exceeding INT_MAX sunvdc: Balance device refcount in vdc_port_mpgroup_check NFSD: detect mismatch of file handle and delegation stateid in OPEN op net: dpaa: fix device leak when querying time stamp info net: gianfar: fix device leak when querying time stamp info netlink: avoid infinite retry looping in netlink_unicast() ALSA: usb-audio: Validate UAC3 cluster segment descriptors ALSA: usb-audio: Validate UAC3 power domain descriptors, too usb: gadget : fix use-after-free in composite_dev_cleanup() MIPS: mm: tlb-r4k: Uniquify TLB entries on init USB: serial: option: add Foxconn T99W709 vsock: Do not allow binding to VMADDR_PORT_ANY net/packet: fix a race in packet_set_ring() and packet_notifier() perf/core: Prevent VMA split of buffer mappings perf/core: Exit early on perf_mmap() fail perf/core: Don't leak AUX buffer refcount on allocation failure pptp: fix pptp_xmit() error path smb: client: let recv_done() cleanup before notifying the callers. benet: fix BUG when creating VFs ipv6: reject malicious packets in ipv6_gso_segment() pptp: ensure minimal skb length in pptp_xmit() netpoll: prevent hanging NAPI when netcons gets enabled NFS: Fix filehandle bounds checking in nfs_fh_to_dentry() pci/hotplug/pnv-php: Wrap warnings in macro pci/hotplug/pnv-php: Improve error msg on power state change failure usb: chipidea: udc: fix sleeping function called from invalid context f2fs: fix to avoid out-of-boundary access in devs.path f2fs: fix to avoid UAF in f2fs_sync_inode_meta() rtc: pcf8563: fix incorrect maximum clock rate handling rtc: hym8563: fix incorrect maximum clock rate handling rtc: ds1307: fix incorrect maximum clock rate handling mtd: rawnand: atmel: set pmecc data setup time mtd: rawnand: atmel: Fix dma_mapping_error() address jfs: fix metapage reference count leak in dbAllocCtl fbdev: imxfb: Check fb_add_videomode to prevent null-ptr-deref crypto: qat - fix seq_file position update in adf_ring_next() dmaengine: nbpfaxi: Add missing check after DMA map dmaengine: mv_xor: Fix missing check after DMA map and missing unmap fs/orangefs: Allow 2 more characters in do_c_string() crypto: img-hash - Fix dma_unmap_sg() nents value scsi: isci: Fix dma_unmap_sg() nents value scsi: mvsas: Fix dma_unmap_sg() nents value scsi: ibmvscsi_tgt: Fix dma_unmap_sg() nents value perf tests bp_account: Fix leaked file descriptor crypto: ccp - Fix crash when rebind ccp device for ccp.ko pinctrl: sunxi: Fix memory leak on krealloc failure power: supply: max14577: Handle NULL pdata when CONFIG_OF is not set clk: davinci: Add NULL check in davinci_lpsc_clk_register() mtd: fix possible integer overflow in erase_xfer() crypto: marvell/cesa - Fix engine load inaccuracy PCI: rockchip-host: Fix "Unexpected Completion" log message vrf: Drop existing dst reference in vrf_ip6_input_dst netfilter: xt_nfacct: don't assume acct name is null-terminated can: kvaser_usb: Assign netdev.dev_port based on device channel index wifi: brcmfmac: fix P2P discovery failure in P2P peer due to missing P2P IE Reapply "wifi: mac80211: Update skb's control block key in ieee80211_tx_dequeue()" mwl8k: Add missing check after DMA map wifi: rtl8xxxu: Fix RX skb size for aggregation disabled net/sched: Restrict conditions for adding duplicating netems to qdisc tree arch: powerpc: defconfig: Drop obsolete CONFIG_NET_CLS_TCINDEX netfilter: nf_tables: adjust lockdep assertions handling drm/amd/pm/powerplay/hwmgr/smu_helper: fix order of mask and value m68k: Don't unregister boot console needlessly tcp: fix tcp_ofo_queue() to avoid including too much DUP SACK range iwlwifi: Add missing check for alloc_ordered_workqueue wifi: iwlwifi: Fix memory leak in iwl_mvm_init() wifi: rtl818x: Kill URBs before clearing tx status queue caif: reduce stack size, again staging: nvec: Fix incorrect null termination of battery manufacturer samples: mei: Fix building on musl libc usb: early: xhci-dbc: Fix early_ioremap leak Revert "vmci: Prevent the dispatching of uninitialized payloads" pps: fix poll support vmci: Prevent the dispatching of uninitialized payloads staging: fbtft: fix potential memory leak in fbtft_framebuffer_alloc() ARM: dts: vfxxx: Correctly use two tuples for timer address ASoC: ops: dynamically allocate struct snd_ctl_elem_value hfsplus: remove mutex_lock check in hfsplus_free_extents ASoC: Intel: fix SND_SOC_SOF dependencies ethernet: intel: fix building with large NR_CPUS usb: phy: mxs: disconnect line when USB charger is attached usb: chipidea: udc: protect usb interrupt enable usb: chipidea: udc: add new API ci_hdrc_gadget_connect comedi: comedi_test: Fix possible deletion of uninitialized timers nilfs2: reject invalid file types when reading inodes i2c: qup: jump out of the loop in case of timeout net/sched: sch_qfq: Avoid triggering might_sleep in atomic context in qfq_delete_class net: appletalk: Fix use-after-free in AARP proxy probe net: appletalk: fix kerneldoc warnings RDMA/core: Rate limit GID cache warning messages usb: hub: fix detection of high tier USB3 devices behind suspended hubs net_sched: sch_sfq: reject invalid perturb period net_sched: sch_sfq: move the limit validation net_sched: sch_sfq: use a temporary work area for validating configuration net_sched: sch_sfq: don't allow 1 packet limit net_sched: sch_sfq: handle bigger packets net_sched: sch_sfq: annotate data-races around q->perturb_period power: supply: bq24190_charger: Fix runtime PM imbalance on error xhci: Disable stream for xHC controller with XHCI_BROKEN_STREAMS virtio-net: ensure the received length does not exceed allocated size usb: dwc3: qcom: Don't leave BCR asserted usb: musb: fix gadget state on disconnect net/sched: Return NULL when htb_lookup_leaf encounters an empty rbtree net: vlan: fix VLAN 0 refcount imbalance of toggling filtering during runtime Bluetooth: L2CAP: Fix attempting to adjust outgoing MTU Bluetooth: SMP: Fix using HCI_ERROR_REMOTE_USER_TERM on timeout Bluetooth: SMP: If an unallowed command is received consider it a failure Bluetooth: Fix null-ptr-deref in l2cap_sock_resume_cb() usb: net: sierra: check for no status endpoint net/sched: sch_qfq: Fix race condition on qfq_aggregate net: emaclite: Fix missing pointer increment in aligned_read() comedi: Fix use of uninitialized data in insn_rw_emulate_bits() comedi: Fix some signed shift left operations comedi: das6402: Fix bit shift out of bounds comedi: das16m1: Fix bit shift out of bounds comedi: aio_iiro_16: Fix bit shift out of bounds comedi: pcl812: Fix bit shift out of bounds iio: adc: max1363: Reorder mode_list[] entries iio: adc: max1363: Fix MAX1363_4X_CHANS/MAX1363_8X_CHANS[] soc: aspeed: lpc-snoop: Don't disable channels that aren't enabled soc: aspeed: lpc-snoop: Cleanup resources in stack-order mmc: sdhci-pci: Quirk for broken command queuing on Intel GLK-based Positivo models memstick: core: Zero initialize id_reg in h_memstick_read_dev_id() isofs: Verify inode mode when loading from disk dmaengine: nbpfaxi: Fix memory corruption in probe() af_packet: fix soft lockup issue caused by tpacket_snd() af_packet: fix the SO_SNDTIMEO constraint not effective on tpacked_snd() phonet/pep: Move call to pn_skb_get_dst_sockaddr() earlier in pep_sock_accept() HID: core: do not bypass hid_hw_raw_request HID: core: ensure __hid_request reserves the report ID as the first byte HID: core: ensure the allocated report buffer can contain the reserved report ID pch_uart: Fix dma_sync_sg_for_device() nents value Input: xpad - set correct controller type for Acer NGR200 i2c: stm32: fix the device used for the DMA map usb: gadget: configfs: Fix OOB read on empty string write USB: serial: ftdi_sio: add support for NDI EMGUIDE GEMINI USB: serial: option: add Foxconn T99W640 USB: serial: option: add Telit Cinterion FE910C04 (ECM) composition dma-mapping: add generic helpers for mapping sgtable objects usb: renesas_usbhs: Flush the notify_hotplug_work gpio: rcar: Use raw_spinlock to protect register access Change-Id: Ia6b8b00918487999c648f298d3550afc7eaaae03 Signed-off-by: bengris32 <bengris32@protonmail.ch>
2118 lines
51 KiB
C
2118 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;
|
|
|
|
if (!is.s.not_found) {
|
|
EXT4_ERROR_INODE(inode, "unexpected inline data xattr");
|
|
error = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
|
|
if (is.s.not_found) {
|
|
EXT4_ERROR_INODE(inode, "missing inline data xattr");
|
|
error = -EFSCORRUPTED;
|
|
goto out;
|
|
}
|
|
|
|
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,
|
|
loff_t 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;
|
|
|
|
if (is.s.not_found) {
|
|
EXT4_ERROR_INODE(inode,
|
|
"missing inline data xattr");
|
|
err = -EFSCORRUPTED;
|
|
goto out_error;
|
|
}
|
|
|
|
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;
|
|
}
|