ext4l: bring in extents_status, fsync, and hash
Copy extents_status.c, extents_status.h, fsync.c, and hash.c from Linux v6.18 fs/ext4 directory. - extents_status: extent status tree for tracking extent state - fsync: file synchronization operations - hash: directory entry hashing for htree directories Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2306
fs/ext4l/extents_status.c
Normal file
2306
fs/ext4l/extents_status.c
Normal file
File diff suppressed because it is too large
Load Diff
254
fs/ext4l/extents_status.h
Normal file
254
fs/ext4l/extents_status.h
Normal file
@@ -0,0 +1,254 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* fs/ext4/extents_status.h
|
||||
*
|
||||
* Written by Yongqiang Yang <xiaoqiangnk@gmail.com>
|
||||
* Modified by
|
||||
* Allison Henderson <achender@linux.vnet.ibm.com>
|
||||
* Zheng Liu <wenqing.lz@taobao.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _EXT4_EXTENTS_STATUS_H
|
||||
#define _EXT4_EXTENTS_STATUS_H
|
||||
|
||||
/*
|
||||
* Turn on ES_DEBUG__ to get lots of info about extent status operations.
|
||||
*/
|
||||
#ifdef ES_DEBUG__
|
||||
#define es_debug(fmt, ...) printk(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define es_debug(fmt, ...) no_printk(fmt, ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* With ES_AGGRESSIVE_TEST defined, the result of es caching will be
|
||||
* checked with old map_block's result.
|
||||
*/
|
||||
#define ES_AGGRESSIVE_TEST__
|
||||
|
||||
/*
|
||||
* These flags live in the high bits of extent_status.es_pblk
|
||||
*/
|
||||
enum {
|
||||
ES_WRITTEN_B,
|
||||
ES_UNWRITTEN_B,
|
||||
ES_DELAYED_B,
|
||||
ES_HOLE_B,
|
||||
ES_REFERENCED_B,
|
||||
ES_FLAGS
|
||||
};
|
||||
|
||||
#define ES_SHIFT (sizeof(ext4_fsblk_t)*8 - ES_FLAGS)
|
||||
#define ES_MASK (~((ext4_fsblk_t)0) << ES_SHIFT)
|
||||
|
||||
/*
|
||||
* Besides EXTENT_STATUS_REFERENCED, all these extent type masks
|
||||
* are exclusive, only one type can be set at a time.
|
||||
*/
|
||||
#define EXTENT_STATUS_WRITTEN (1 << ES_WRITTEN_B)
|
||||
#define EXTENT_STATUS_UNWRITTEN (1 << ES_UNWRITTEN_B)
|
||||
#define EXTENT_STATUS_DELAYED (1 << ES_DELAYED_B)
|
||||
#define EXTENT_STATUS_HOLE (1 << ES_HOLE_B)
|
||||
#define EXTENT_STATUS_REFERENCED (1 << ES_REFERENCED_B)
|
||||
|
||||
#define ES_TYPE_MASK ((ext4_fsblk_t)(EXTENT_STATUS_WRITTEN | \
|
||||
EXTENT_STATUS_UNWRITTEN | \
|
||||
EXTENT_STATUS_DELAYED | \
|
||||
EXTENT_STATUS_HOLE))
|
||||
|
||||
#define ES_TYPE_VALID(type) ((type) && !((type) & ((type) - 1)))
|
||||
|
||||
struct ext4_sb_info;
|
||||
struct ext4_extent;
|
||||
|
||||
struct extent_status {
|
||||
struct rb_node rb_node;
|
||||
ext4_lblk_t es_lblk; /* first logical block extent covers */
|
||||
ext4_lblk_t es_len; /* length of extent in block */
|
||||
ext4_fsblk_t es_pblk; /* first physical block */
|
||||
};
|
||||
|
||||
struct ext4_es_tree {
|
||||
struct rb_root root;
|
||||
struct extent_status *cache_es; /* recently accessed extent */
|
||||
};
|
||||
|
||||
struct ext4_es_stats {
|
||||
unsigned long es_stats_shrunk;
|
||||
struct percpu_counter es_stats_cache_hits;
|
||||
struct percpu_counter es_stats_cache_misses;
|
||||
u64 es_stats_scan_time;
|
||||
u64 es_stats_max_scan_time;
|
||||
struct percpu_counter es_stats_all_cnt;
|
||||
struct percpu_counter es_stats_shk_cnt;
|
||||
};
|
||||
|
||||
/*
|
||||
* Pending cluster reservations for bigalloc file systems
|
||||
*
|
||||
* A cluster with a pending reservation is a logical cluster shared by at
|
||||
* least one extent in the extents status tree with delayed and unwritten
|
||||
* status and at least one other written or unwritten extent. The
|
||||
* reservation is said to be pending because a cluster reservation would
|
||||
* have to be taken in the event all blocks in the cluster shared with
|
||||
* written or unwritten extents were deleted while the delayed and
|
||||
* unwritten blocks remained.
|
||||
*
|
||||
* The set of pending cluster reservations is an auxiliary data structure
|
||||
* used with the extents status tree to implement reserved cluster/block
|
||||
* accounting for bigalloc file systems. The set is kept in memory and
|
||||
* records all pending cluster reservations.
|
||||
*
|
||||
* Its primary function is to avoid the need to read extents from the
|
||||
* disk when invalidating pages as a result of a truncate, punch hole, or
|
||||
* collapse range operation. Page invalidation requires a decrease in the
|
||||
* reserved cluster count if it results in the removal of all delayed
|
||||
* and unwritten extents (blocks) from a cluster that is not shared with a
|
||||
* written or unwritten extent, and no decrease otherwise. Determining
|
||||
* whether the cluster is shared can be done by searching for a pending
|
||||
* reservation on it.
|
||||
*
|
||||
* Secondarily, it provides a potentially faster method for determining
|
||||
* whether the reserved cluster count should be increased when a physical
|
||||
* cluster is deallocated as a result of a truncate, punch hole, or
|
||||
* collapse range operation. The necessary information is also present
|
||||
* in the extents status tree, but might be more rapidly accessed in
|
||||
* the pending reservation set in many cases due to smaller size.
|
||||
*
|
||||
* The pending cluster reservation set is implemented as a red-black tree
|
||||
* with the goal of minimizing per page search time overhead.
|
||||
*/
|
||||
|
||||
struct pending_reservation {
|
||||
struct rb_node rb_node;
|
||||
ext4_lblk_t lclu;
|
||||
};
|
||||
|
||||
struct ext4_pending_tree {
|
||||
struct rb_root root;
|
||||
};
|
||||
|
||||
extern int __init ext4_init_es(void);
|
||||
extern void ext4_exit_es(void);
|
||||
extern void ext4_es_init_tree(struct ext4_es_tree *tree);
|
||||
|
||||
extern void ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
|
||||
ext4_lblk_t len, ext4_fsblk_t pblk,
|
||||
unsigned int status,
|
||||
bool delalloc_reserve_used);
|
||||
extern void ext4_es_cache_extent(struct inode *inode, ext4_lblk_t lblk,
|
||||
ext4_lblk_t len, ext4_fsblk_t pblk,
|
||||
unsigned int status);
|
||||
extern void ext4_es_remove_extent(struct inode *inode, ext4_lblk_t lblk,
|
||||
ext4_lblk_t len);
|
||||
extern void ext4_es_find_extent_range(struct inode *inode,
|
||||
int (*match_fn)(struct extent_status *es),
|
||||
ext4_lblk_t lblk, ext4_lblk_t end,
|
||||
struct extent_status *es);
|
||||
extern int ext4_es_lookup_extent(struct inode *inode, ext4_lblk_t lblk,
|
||||
ext4_lblk_t *next_lblk,
|
||||
struct extent_status *es);
|
||||
extern bool ext4_es_scan_range(struct inode *inode,
|
||||
int (*matching_fn)(struct extent_status *es),
|
||||
ext4_lblk_t lblk, ext4_lblk_t end);
|
||||
extern bool ext4_es_scan_clu(struct inode *inode,
|
||||
int (*matching_fn)(struct extent_status *es),
|
||||
ext4_lblk_t lblk);
|
||||
|
||||
static inline unsigned int ext4_es_status(struct extent_status *es)
|
||||
{
|
||||
return es->es_pblk >> ES_SHIFT;
|
||||
}
|
||||
|
||||
static inline unsigned int ext4_es_type(struct extent_status *es)
|
||||
{
|
||||
return (es->es_pblk >> ES_SHIFT) & ES_TYPE_MASK;
|
||||
}
|
||||
|
||||
static inline int ext4_es_is_written(struct extent_status *es)
|
||||
{
|
||||
return (ext4_es_type(es) & EXTENT_STATUS_WRITTEN) != 0;
|
||||
}
|
||||
|
||||
static inline int ext4_es_is_unwritten(struct extent_status *es)
|
||||
{
|
||||
return (ext4_es_type(es) & EXTENT_STATUS_UNWRITTEN) != 0;
|
||||
}
|
||||
|
||||
static inline int ext4_es_is_delayed(struct extent_status *es)
|
||||
{
|
||||
return (ext4_es_type(es) & EXTENT_STATUS_DELAYED) != 0;
|
||||
}
|
||||
|
||||
static inline int ext4_es_is_hole(struct extent_status *es)
|
||||
{
|
||||
return (ext4_es_type(es) & EXTENT_STATUS_HOLE) != 0;
|
||||
}
|
||||
|
||||
static inline int ext4_es_is_mapped(struct extent_status *es)
|
||||
{
|
||||
return (ext4_es_is_written(es) || ext4_es_is_unwritten(es));
|
||||
}
|
||||
|
||||
static inline void ext4_es_set_referenced(struct extent_status *es)
|
||||
{
|
||||
es->es_pblk |= ((ext4_fsblk_t)EXTENT_STATUS_REFERENCED) << ES_SHIFT;
|
||||
}
|
||||
|
||||
static inline void ext4_es_clear_referenced(struct extent_status *es)
|
||||
{
|
||||
es->es_pblk &= ~(((ext4_fsblk_t)EXTENT_STATUS_REFERENCED) << ES_SHIFT);
|
||||
}
|
||||
|
||||
static inline int ext4_es_is_referenced(struct extent_status *es)
|
||||
{
|
||||
return (ext4_es_status(es) & EXTENT_STATUS_REFERENCED) != 0;
|
||||
}
|
||||
|
||||
static inline ext4_fsblk_t ext4_es_pblock(struct extent_status *es)
|
||||
{
|
||||
return es->es_pblk & ~ES_MASK;
|
||||
}
|
||||
|
||||
static inline ext4_fsblk_t ext4_es_show_pblock(struct extent_status *es)
|
||||
{
|
||||
ext4_fsblk_t pblock = ext4_es_pblock(es);
|
||||
return pblock == ~ES_MASK ? 0 : pblock;
|
||||
}
|
||||
|
||||
static inline void ext4_es_store_pblock(struct extent_status *es,
|
||||
ext4_fsblk_t pb)
|
||||
{
|
||||
ext4_fsblk_t block;
|
||||
|
||||
block = (pb & ~ES_MASK) | (es->es_pblk & ES_MASK);
|
||||
es->es_pblk = block;
|
||||
}
|
||||
|
||||
static inline void ext4_es_store_pblock_status(struct extent_status *es,
|
||||
ext4_fsblk_t pb,
|
||||
unsigned int status)
|
||||
{
|
||||
WARN_ON_ONCE(!ES_TYPE_VALID(status & ES_TYPE_MASK));
|
||||
|
||||
es->es_pblk = (((ext4_fsblk_t)status << ES_SHIFT) & ES_MASK) |
|
||||
(pb & ~ES_MASK);
|
||||
}
|
||||
|
||||
extern int ext4_es_register_shrinker(struct ext4_sb_info *sbi);
|
||||
extern void ext4_es_unregister_shrinker(struct ext4_sb_info *sbi);
|
||||
|
||||
extern int ext4_seq_es_shrinker_info_show(struct seq_file *seq, void *v);
|
||||
|
||||
extern int __init ext4_init_pending(void);
|
||||
extern void ext4_exit_pending(void);
|
||||
extern void ext4_init_pending_tree(struct ext4_pending_tree *tree);
|
||||
extern void ext4_remove_pending(struct inode *inode, ext4_lblk_t lblk);
|
||||
extern bool ext4_is_pending(struct inode *inode, ext4_lblk_t lblk);
|
||||
extern void ext4_es_insert_delayed_extent(struct inode *inode, ext4_lblk_t lblk,
|
||||
ext4_lblk_t len, bool lclu_allocated,
|
||||
bool end_allocated);
|
||||
extern void ext4_clear_inode_es(struct inode *inode);
|
||||
|
||||
#endif /* _EXT4_EXTENTS_STATUS_H */
|
||||
177
fs/ext4l/fsync.c
Normal file
177
fs/ext4l/fsync.c
Normal file
@@ -0,0 +1,177 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* linux/fs/ext4/fsync.c
|
||||
*
|
||||
* Copyright (C) 1993 Stephen Tweedie (sct@redhat.com)
|
||||
* from
|
||||
* Copyright (C) 1992 Remy Card (card@masi.ibp.fr)
|
||||
* Laboratoire MASI - Institut Blaise Pascal
|
||||
* Universite Pierre et Marie Curie (Paris VI)
|
||||
* from
|
||||
* linux/fs/minix/truncate.c Copyright (C) 1991, 1992 Linus Torvalds
|
||||
*
|
||||
* ext4fs fsync primitive
|
||||
*
|
||||
* Big-endian to little-endian byte-swapping/bitmaps by
|
||||
* David S. Miller (davem@caip.rutgers.edu), 1995
|
||||
*
|
||||
* Removed unnecessary code duplication for little endian machines
|
||||
* and excessive __inline__s.
|
||||
* Andi Kleen, 1997
|
||||
*
|
||||
* Major simplications and cleanup - we only need to do the metadata, because
|
||||
* we can depend on generic_block_fdatasync() to sync the data blocks.
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/buffer_head.h>
|
||||
|
||||
#include "ext4.h"
|
||||
#include "ext4_jbd2.h"
|
||||
|
||||
#include <trace/events/ext4.h>
|
||||
|
||||
/*
|
||||
* If we're not journaling and this is a just-created file, we have to
|
||||
* sync our parent directory (if it was freshly created) since
|
||||
* otherwise it will only be written by writeback, leaving a huge
|
||||
* window during which a crash may lose the file. This may apply for
|
||||
* the parent directory's parent as well, and so on recursively, if
|
||||
* they are also freshly created.
|
||||
*/
|
||||
static int ext4_sync_parent(struct inode *inode)
|
||||
{
|
||||
struct dentry *dentry, *next;
|
||||
int ret = 0;
|
||||
|
||||
if (!ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY))
|
||||
return 0;
|
||||
dentry = d_find_any_alias(inode);
|
||||
if (!dentry)
|
||||
return 0;
|
||||
while (ext4_test_inode_state(inode, EXT4_STATE_NEWENTRY)) {
|
||||
ext4_clear_inode_state(inode, EXT4_STATE_NEWENTRY);
|
||||
|
||||
next = dget_parent(dentry);
|
||||
dput(dentry);
|
||||
dentry = next;
|
||||
inode = dentry->d_inode;
|
||||
|
||||
/*
|
||||
* The directory inode may have gone through rmdir by now. But
|
||||
* the inode itself and its blocks are still allocated (we hold
|
||||
* a reference to the inode via its dentry), so it didn't go
|
||||
* through ext4_evict_inode()) and so we are safe to flush
|
||||
* metadata blocks and the inode.
|
||||
*/
|
||||
ret = sync_mapping_buffers(inode->i_mapping);
|
||||
if (ret)
|
||||
break;
|
||||
ret = sync_inode_metadata(inode, 1);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
dput(dentry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ext4_fsync_nojournal(struct file *file, loff_t start, loff_t end,
|
||||
int datasync, bool *needs_barrier)
|
||||
{
|
||||
struct inode *inode = file->f_inode;
|
||||
int ret;
|
||||
|
||||
ret = generic_buffers_fsync_noflush(file, start, end, datasync);
|
||||
if (!ret)
|
||||
ret = ext4_sync_parent(inode);
|
||||
if (test_opt(inode->i_sb, BARRIER))
|
||||
*needs_barrier = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ext4_fsync_journal(struct inode *inode, bool datasync,
|
||||
bool *needs_barrier)
|
||||
{
|
||||
struct ext4_inode_info *ei = EXT4_I(inode);
|
||||
journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
|
||||
tid_t commit_tid = datasync ? ei->i_datasync_tid : ei->i_sync_tid;
|
||||
|
||||
/*
|
||||
* Fastcommit does not really support fsync on directories or other
|
||||
* special files. Force a full commit.
|
||||
*/
|
||||
if (!S_ISREG(inode->i_mode))
|
||||
return ext4_force_commit(inode->i_sb);
|
||||
|
||||
if (journal->j_flags & JBD2_BARRIER &&
|
||||
!jbd2_trans_will_send_data_barrier(journal, commit_tid))
|
||||
*needs_barrier = true;
|
||||
|
||||
return ext4_fc_commit(journal, commit_tid);
|
||||
}
|
||||
|
||||
/*
|
||||
* akpm: A new design for ext4_sync_file().
|
||||
*
|
||||
* This is only called from sys_fsync(), sys_fdatasync() and sys_msync().
|
||||
* There cannot be a transaction open by this task.
|
||||
* Another task could have dirtied this inode. Its data can be in any
|
||||
* state in the journalling system.
|
||||
*
|
||||
* What we do is just kick off a commit and wait on it. This will snapshot the
|
||||
* inode to disk.
|
||||
*/
|
||||
int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
|
||||
{
|
||||
int ret = 0, err;
|
||||
bool needs_barrier = false;
|
||||
struct inode *inode = file->f_mapping->host;
|
||||
|
||||
ret = ext4_emergency_state(inode->i_sb);
|
||||
if (unlikely(ret))
|
||||
return ret;
|
||||
|
||||
ASSERT(ext4_journal_current_handle() == NULL);
|
||||
|
||||
trace_ext4_sync_file_enter(file, datasync);
|
||||
|
||||
if (sb_rdonly(inode->i_sb))
|
||||
goto out;
|
||||
|
||||
if (!EXT4_SB(inode->i_sb)->s_journal) {
|
||||
ret = ext4_fsync_nojournal(file, start, end, datasync,
|
||||
&needs_barrier);
|
||||
if (needs_barrier)
|
||||
goto issue_flush;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = file_write_and_wait_range(file, start, end);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* The caller's filemap_fdatawrite()/wait will sync the data.
|
||||
* Metadata is in the journal, we wait for proper transaction to
|
||||
* commit here.
|
||||
*/
|
||||
ret = ext4_fsync_journal(inode, datasync, &needs_barrier);
|
||||
|
||||
issue_flush:
|
||||
if (needs_barrier) {
|
||||
err = blkdev_issue_flush(inode->i_sb->s_bdev);
|
||||
if (!ret)
|
||||
ret = err;
|
||||
}
|
||||
out:
|
||||
err = file_check_and_advance_wb_err(file);
|
||||
if (ret == 0)
|
||||
ret = err;
|
||||
trace_ext4_sync_file_exit(inode, ret);
|
||||
return ret;
|
||||
}
|
||||
323
fs/ext4l/hash.c
Normal file
323
fs/ext4l/hash.c
Normal file
@@ -0,0 +1,323 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* linux/fs/ext4/hash.c
|
||||
*
|
||||
* Copyright (C) 2002 by Theodore Ts'o
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/unicode.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/bitops.h>
|
||||
#include "ext4.h"
|
||||
|
||||
#define DELTA 0x9E3779B9
|
||||
|
||||
static void TEA_transform(__u32 buf[4], __u32 const in[])
|
||||
{
|
||||
__u32 sum = 0;
|
||||
__u32 b0 = buf[0], b1 = buf[1];
|
||||
__u32 a = in[0], b = in[1], c = in[2], d = in[3];
|
||||
int n = 16;
|
||||
|
||||
do {
|
||||
sum += DELTA;
|
||||
b0 += ((b1 << 4)+a) ^ (b1+sum) ^ ((b1 >> 5)+b);
|
||||
b1 += ((b0 << 4)+c) ^ (b0+sum) ^ ((b0 >> 5)+d);
|
||||
} while (--n);
|
||||
|
||||
buf[0] += b0;
|
||||
buf[1] += b1;
|
||||
}
|
||||
|
||||
/* F, G and H are basic MD4 functions: selection, majority, parity */
|
||||
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
|
||||
#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z)))
|
||||
#define H(x, y, z) ((x) ^ (y) ^ (z))
|
||||
|
||||
/*
|
||||
* The generic round function. The application is so specific that
|
||||
* we don't bother protecting all the arguments with parens, as is generally
|
||||
* good macro practice, in favor of extra legibility.
|
||||
* Rotation is separate from addition to prevent recomputation
|
||||
*/
|
||||
#define ROUND(f, a, b, c, d, x, s) \
|
||||
(a += f(b, c, d) + x, a = rol32(a, s))
|
||||
#define K1 0
|
||||
#define K2 013240474631UL
|
||||
#define K3 015666365641UL
|
||||
|
||||
/*
|
||||
* Basic cut-down MD4 transform. Returns only 32 bits of result.
|
||||
*/
|
||||
static __u32 half_md4_transform(__u32 buf[4], __u32 const in[8])
|
||||
{
|
||||
__u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
|
||||
|
||||
/* Round 1 */
|
||||
ROUND(F, a, b, c, d, in[0] + K1, 3);
|
||||
ROUND(F, d, a, b, c, in[1] + K1, 7);
|
||||
ROUND(F, c, d, a, b, in[2] + K1, 11);
|
||||
ROUND(F, b, c, d, a, in[3] + K1, 19);
|
||||
ROUND(F, a, b, c, d, in[4] + K1, 3);
|
||||
ROUND(F, d, a, b, c, in[5] + K1, 7);
|
||||
ROUND(F, c, d, a, b, in[6] + K1, 11);
|
||||
ROUND(F, b, c, d, a, in[7] + K1, 19);
|
||||
|
||||
/* Round 2 */
|
||||
ROUND(G, a, b, c, d, in[1] + K2, 3);
|
||||
ROUND(G, d, a, b, c, in[3] + K2, 5);
|
||||
ROUND(G, c, d, a, b, in[5] + K2, 9);
|
||||
ROUND(G, b, c, d, a, in[7] + K2, 13);
|
||||
ROUND(G, a, b, c, d, in[0] + K2, 3);
|
||||
ROUND(G, d, a, b, c, in[2] + K2, 5);
|
||||
ROUND(G, c, d, a, b, in[4] + K2, 9);
|
||||
ROUND(G, b, c, d, a, in[6] + K2, 13);
|
||||
|
||||
/* Round 3 */
|
||||
ROUND(H, a, b, c, d, in[3] + K3, 3);
|
||||
ROUND(H, d, a, b, c, in[7] + K3, 9);
|
||||
ROUND(H, c, d, a, b, in[2] + K3, 11);
|
||||
ROUND(H, b, c, d, a, in[6] + K3, 15);
|
||||
ROUND(H, a, b, c, d, in[1] + K3, 3);
|
||||
ROUND(H, d, a, b, c, in[5] + K3, 9);
|
||||
ROUND(H, c, d, a, b, in[0] + K3, 11);
|
||||
ROUND(H, b, c, d, a, in[4] + K3, 15);
|
||||
|
||||
buf[0] += a;
|
||||
buf[1] += b;
|
||||
buf[2] += c;
|
||||
buf[3] += d;
|
||||
|
||||
return buf[1]; /* "most hashed" word */
|
||||
}
|
||||
#undef ROUND
|
||||
#undef K1
|
||||
#undef K2
|
||||
#undef K3
|
||||
#undef F
|
||||
#undef G
|
||||
#undef H
|
||||
|
||||
/* The old legacy hash */
|
||||
static __u32 dx_hack_hash_unsigned(const char *name, int len)
|
||||
{
|
||||
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
|
||||
const unsigned char *ucp = (const unsigned char *) name;
|
||||
|
||||
while (len--) {
|
||||
hash = hash1 + (hash0 ^ (((int) *ucp++) * 7152373));
|
||||
|
||||
if (hash & 0x80000000)
|
||||
hash -= 0x7fffffff;
|
||||
hash1 = hash0;
|
||||
hash0 = hash;
|
||||
}
|
||||
return hash0 << 1;
|
||||
}
|
||||
|
||||
static __u32 dx_hack_hash_signed(const char *name, int len)
|
||||
{
|
||||
__u32 hash, hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
|
||||
const signed char *scp = (const signed char *) name;
|
||||
|
||||
while (len--) {
|
||||
hash = hash1 + (hash0 ^ (((int) *scp++) * 7152373));
|
||||
|
||||
if (hash & 0x80000000)
|
||||
hash -= 0x7fffffff;
|
||||
hash1 = hash0;
|
||||
hash0 = hash;
|
||||
}
|
||||
return hash0 << 1;
|
||||
}
|
||||
|
||||
static void str2hashbuf_signed(const char *msg, int len, __u32 *buf, int num)
|
||||
{
|
||||
__u32 pad, val;
|
||||
int i;
|
||||
const signed char *scp = (const signed char *) msg;
|
||||
|
||||
pad = (__u32)len | ((__u32)len << 8);
|
||||
pad |= pad << 16;
|
||||
|
||||
val = pad;
|
||||
if (len > num*4)
|
||||
len = num * 4;
|
||||
for (i = 0; i < len; i++) {
|
||||
val = ((int) scp[i]) + (val << 8);
|
||||
if ((i % 4) == 3) {
|
||||
*buf++ = val;
|
||||
val = pad;
|
||||
num--;
|
||||
}
|
||||
}
|
||||
if (--num >= 0)
|
||||
*buf++ = val;
|
||||
while (--num >= 0)
|
||||
*buf++ = pad;
|
||||
}
|
||||
|
||||
static void str2hashbuf_unsigned(const char *msg, int len, __u32 *buf, int num)
|
||||
{
|
||||
__u32 pad, val;
|
||||
int i;
|
||||
const unsigned char *ucp = (const unsigned char *) msg;
|
||||
|
||||
pad = (__u32)len | ((__u32)len << 8);
|
||||
pad |= pad << 16;
|
||||
|
||||
val = pad;
|
||||
if (len > num*4)
|
||||
len = num * 4;
|
||||
for (i = 0; i < len; i++) {
|
||||
val = ((int) ucp[i]) + (val << 8);
|
||||
if ((i % 4) == 3) {
|
||||
*buf++ = val;
|
||||
val = pad;
|
||||
num--;
|
||||
}
|
||||
}
|
||||
if (--num >= 0)
|
||||
*buf++ = val;
|
||||
while (--num >= 0)
|
||||
*buf++ = pad;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the hash of a filename. If len is 0 and name is NULL, then
|
||||
* this function can be used to test whether or not a hash version is
|
||||
* supported.
|
||||
*
|
||||
* The seed is an 4 longword (32 bits) "secret" which can be used to
|
||||
* uniquify a hash. If the seed is all zero's, then some default seed
|
||||
* may be used.
|
||||
*
|
||||
* A particular hash version specifies whether or not the seed is
|
||||
* represented, and whether or not the returned hash is 32 bits or 64
|
||||
* bits. 32 bit hashes will return 0 for the minor hash.
|
||||
*/
|
||||
static int __ext4fs_dirhash(const struct inode *dir, const char *name, int len,
|
||||
struct dx_hash_info *hinfo)
|
||||
{
|
||||
__u32 hash;
|
||||
__u32 minor_hash = 0;
|
||||
const char *p;
|
||||
int i;
|
||||
__u32 in[8], buf[4];
|
||||
void (*str2hashbuf)(const char *, int, __u32 *, int) =
|
||||
str2hashbuf_signed;
|
||||
|
||||
/* Initialize the default seed for the hash checksum functions */
|
||||
buf[0] = 0x67452301;
|
||||
buf[1] = 0xefcdab89;
|
||||
buf[2] = 0x98badcfe;
|
||||
buf[3] = 0x10325476;
|
||||
|
||||
/* Check to see if the seed is all zero's */
|
||||
if (hinfo->seed) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (hinfo->seed[i]) {
|
||||
memcpy(buf, hinfo->seed, sizeof(buf));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (hinfo->hash_version) {
|
||||
case DX_HASH_LEGACY_UNSIGNED:
|
||||
hash = dx_hack_hash_unsigned(name, len);
|
||||
break;
|
||||
case DX_HASH_LEGACY:
|
||||
hash = dx_hack_hash_signed(name, len);
|
||||
break;
|
||||
case DX_HASH_HALF_MD4_UNSIGNED:
|
||||
str2hashbuf = str2hashbuf_unsigned;
|
||||
fallthrough;
|
||||
case DX_HASH_HALF_MD4:
|
||||
p = name;
|
||||
while (len > 0) {
|
||||
(*str2hashbuf)(p, len, in, 8);
|
||||
half_md4_transform(buf, in);
|
||||
len -= 32;
|
||||
p += 32;
|
||||
}
|
||||
minor_hash = buf[2];
|
||||
hash = buf[1];
|
||||
break;
|
||||
case DX_HASH_TEA_UNSIGNED:
|
||||
str2hashbuf = str2hashbuf_unsigned;
|
||||
fallthrough;
|
||||
case DX_HASH_TEA:
|
||||
p = name;
|
||||
while (len > 0) {
|
||||
(*str2hashbuf)(p, len, in, 4);
|
||||
TEA_transform(buf, in);
|
||||
len -= 16;
|
||||
p += 16;
|
||||
}
|
||||
hash = buf[0];
|
||||
minor_hash = buf[1];
|
||||
break;
|
||||
case DX_HASH_SIPHASH:
|
||||
{
|
||||
struct qstr qname = QSTR_INIT(name, len);
|
||||
__u64 combined_hash;
|
||||
|
||||
if (fscrypt_has_encryption_key(dir)) {
|
||||
combined_hash = fscrypt_fname_siphash(dir, &qname);
|
||||
} else {
|
||||
ext4_warning_inode(dir, "Siphash requires key");
|
||||
return -1;
|
||||
}
|
||||
|
||||
hash = (__u32)(combined_hash >> 32);
|
||||
minor_hash = (__u32)combined_hash;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
hinfo->hash = 0;
|
||||
hinfo->minor_hash = 0;
|
||||
ext4_warning(dir->i_sb,
|
||||
"invalid/unsupported hash tree version %u",
|
||||
hinfo->hash_version);
|
||||
return -EINVAL;
|
||||
}
|
||||
hash = hash & ~1;
|
||||
if (hash == (EXT4_HTREE_EOF_32BIT << 1))
|
||||
hash = (EXT4_HTREE_EOF_32BIT - 1) << 1;
|
||||
hinfo->hash = hash;
|
||||
hinfo->minor_hash = minor_hash;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ext4fs_dirhash(const struct inode *dir, const char *name, int len,
|
||||
struct dx_hash_info *hinfo)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_UNICODE)
|
||||
const struct unicode_map *um = dir->i_sb->s_encoding;
|
||||
int r, dlen;
|
||||
unsigned char *buff;
|
||||
struct qstr qstr = {.name = name, .len = len };
|
||||
|
||||
if (len && IS_CASEFOLDED(dir) &&
|
||||
(!IS_ENCRYPTED(dir) || fscrypt_has_encryption_key(dir))) {
|
||||
buff = kzalloc(PATH_MAX, GFP_KERNEL);
|
||||
if (!buff)
|
||||
return -ENOMEM;
|
||||
|
||||
dlen = utf8_casefold(um, &qstr, buff, PATH_MAX);
|
||||
if (dlen < 0) {
|
||||
kfree(buff);
|
||||
goto opaque_seq;
|
||||
}
|
||||
|
||||
r = __ext4fs_dirhash(dir, buff, dlen, hinfo);
|
||||
|
||||
kfree(buff);
|
||||
return r;
|
||||
}
|
||||
opaque_seq:
|
||||
#endif
|
||||
return __ext4fs_dirhash(dir, name, len, hinfo);
|
||||
}
|
||||
Reference in New Issue
Block a user