// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org> * Copyright (C) 2019 Samsung Electronics Co., Ltd. */ #include <linux/fs.h>#include <linux/filelock.h>#include <linux/slab.h>#include <linux/vmalloc.h>#include <linux/kthread.h>#include <linux/freezer.h>#include <linux/dcache.h> #include "glob.h"#include "vfs_cache.h"#include "oplock.h"#include "vfs.h"#include "connection.h"#include "misc.h"#include "mgmt/tree_connect.h"#include "mgmt/user_session.h"#include "mgmt/user_config.h"#include "smb_common.h"#include "server.h"#include "smb2pdu.h" #define S_DEL_PENDING 1#define S_DEL_ON_CLS 2#define S_DEL_ON_CLS_STREAM 8 static unsigned int inode_hash_mask __read_mostly;static unsigned int inode_hash_shift __read_mostly;static struct hlist_head *inode_hashtable __read_mostly;static DEFINE_RWLOCK(inode_hash_lock); static struct ksmbd_file_table global_ft;static atomic_long_t fd_limit;static struct kmem_cache *filp_cache; static int ksmbd_mark_fp_closed(struct ksmbd_file *fp); #define OPLOCK_NONE 0#define OPLOCK_EXCLUSIVE 1#define OPLOCK_BATCH 2#define OPLOCK_READ 3 /* level 2 oplock */ #ifdef CONFIG_PROC_FS static const struct ksmbd_const_name ksmbd_lease_const_names[] = { {le32_to_cpu(SMB2_LEASE_NONE_LE), "LEASE_NONE"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE), "LEASE_R"}, {le32_to_cpu(SMB2_LEASE_HANDLE_CACHING_LE), "LEASE_H"}, {le32_to_cpu(SMB2_LEASE_WRITE_CACHING_LE), "LEASE_W"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE), "LEASE_RH"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE), "LEASE_RW"}, {le32_to_cpu(SMB2_LEASE_HANDLE_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE), "LEASE_WH"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE), "LEASE_RWH"},}; static const struct ksmbd_const_name ksmbd_oplock_const_names[] = { {SMB2_OPLOCK_LEVEL_NONE, "OPLOCK_NONE"}, {SMB2_OPLOCK_LEVEL_II, "OPLOCK_II"}, {SMB2_OPLOCK_LEVEL_EXCLUSIVE, "OPLOCK_EXCLUSIVE"}, {SMB2_OPLOCK_LEVEL_BATCH, "OPLOCK_BATCH"},}; static const struct ksmbd_const_name ksmbd_file_state_names[] = { {FP_NEW, "new"}, {FP_INITED, "open"}, {FP_CLOSED, "closed"},}; #define KSMBD_PROC_FILE_DURABLE BIT(0)#define KSMBD_PROC_FILE_PERSISTENT BIT(1)#define KSMBD_PROC_FILE_RESILIENT BIT(2)#define KSMBD_PROC_FILE_DELETE_ON_CLOSE BIT(3)#define KSMBD_PROC_FILE_STREAM BIT(4)#define KSMBD_PROC_FILE_POSIX BIT(5)#define KSMBD_PROC_FILE_ATTRIB_ONLY BIT(6) static const struct ksmbd_const_name ksmbd_file_flag_names[] = { {KSMBD_PROC_FILE_DURABLE, "durable"}, {KSMBD_PROC_FILE_PERSISTENT, "persistent"}, {KSMBD_PROC_FILE_RESILIENT, "resilient"}, {KSMBD_PROC_FILE_DELETE_ON_CLOSE, "delete-on-close"}, {KSMBD_PROC_FILE_STREAM, "stream"}, {KSMBD_PROC_FILE_POSIX, "posix"}, {KSMBD_PROC_FILE_ATTRIB_ONLY, "attrib-only"},}; static unsigned int ksmbd_proc_file_flags(struct ksmbd_file *fp){ unsigned int flags = 0; if (fp->is_durable) flags |= KSMBD_PROC_FILE_DURABLE; if (fp->is_persistent) flags |= KSMBD_PROC_FILE_PERSISTENT; if (fp->is_resilient) flags |= KSMBD_PROC_FILE_RESILIENT; if (fp->coption & FILE_DELETE_ON_CLOSE_LE) flags |= KSMBD_PROC_FILE_DELETE_ON_CLOSE; if (fp->stream.name) flags |= KSMBD_PROC_FILE_STREAM; if (fp->is_posix_ctxt) flags |= KSMBD_PROC_FILE_POSIX; if (fp->attrib_only) flags |= KSMBD_PROC_FILE_ATTRIB_ONLY; return flags;} static int proc_show_files(struct seq_file *m, void *v){ struct ksmbd_file *fp = NULL; unsigned int id; struct oplock_info *opinfo; read_lock(&global_ft.lock); idr_for_each_entry(global_ft.idr, fp, id) { seq_printf(m, "tree_id:\t0x%x\n", fp->tcon ? fp->tcon->id : 0); seq_printf(m, "persistent_id:\t0x%llx\n", fp->persistent_id); seq_printf(m, "volatile_id:\t0x%llx\n", fp->volatile_id); seq_printf(m, "refcount:\t%d\n", atomic_read(&fp->refcount)); rcu_read_lock(); opinfo = rcu_dereference(fp->f_opinfo); if (opinfo) { const struct ksmbd_const_name *const_names; const char *name; int count; unsigned int level; if (opinfo->is_lease) { const_names = ksmbd_lease_const_names; count = ARRAY_SIZE(ksmbd_lease_const_names); level = le32_to_cpu(opinfo->o_lease->state); } else { const_names = ksmbd_oplock_const_names; count = ARRAY_SIZE(ksmbd_oplock_const_names); level = opinfo->level; } rcu_read_unlock(); name = ksmbd_proc_const_name(const_names, count, level); if (name) seq_printf(m, "oplock:\t%s\n", name); else seq_printf(m, "oplock:\t0x%x\n", level); } else { rcu_read_unlock(); seq_puts(m, "oplock:\tnone\n"); } seq_printf(m, "state:\t%s\n", ksmbd_proc_const_name(ksmbd_file_state_names, ARRAY_SIZE(ksmbd_file_state_names), fp->f_state)); seq_printf(m, "durable_timeout:\t%u\n", fp->durable_timeout); seq_printf(m, "create_options:\t0x%08x\n", le32_to_cpu(fp->coption)); seq_printf(m, "desired_access:\t0x%08x\n", le32_to_cpu(fp->daccess)); seq_printf(m, "share_access:\t0x%08x\n", le32_to_cpu(fp->saccess)); seq_puts(m, "flags:\t"); ksmbd_proc_show_flag_names(m, ksmbd_file_flag_names, ARRAY_SIZE(ksmbd_file_flag_names), ksmbd_proc_file_flags(fp)); seq_printf(m, "\nname:\t%s\n\n", fp->filp->f_path.dentry->d_name.name); } read_unlock(&global_ft.lock); return 0;} static int create_proc_files(void){ if (!ksmbd_proc_create("files", proc_show_files, NULL)) return -ENOMEM; return 0;}#elsestatic int create_proc_files(void) { return 0; }#endif static bool durable_scavenger_running;static DEFINE_MUTEX(durable_scavenger_lock);static wait_queue_head_t dh_wq; bool ksmbd_durable_scavenger_active(void){ bool active; mutex_lock(&durable_scavenger_lock); active = durable_scavenger_running; mutex_unlock(&durable_scavenger_lock); return active;} void ksmbd_set_fd_limit(unsigned long limit){ limit = min(limit, get_max_files()); atomic_long_set(&fd_limit, limit);} static bool fd_limit_depleted(void){ long v = atomic_long_dec_return(&fd_limit); if (v >= 0) return false; atomic_long_inc(&fd_limit); return true;} static void fd_limit_close(void){ atomic_long_inc(&fd_limit);} /* * INODE hash */ static unsigned long inode_hash(struct super_block *sb, unsigned long hashval){ unsigned long tmp; tmp = (hashval * (unsigned long)sb) ^ (GOLDEN_RATIO_PRIME + hashval) / L1_CACHE_BYTES; tmp = tmp ^ ((tmp ^ GOLDEN_RATIO_PRIME) >> inode_hash_shift); return tmp & inode_hash_mask;} static struct ksmbd_inode *__ksmbd_inode_lookup(struct dentry *de){ struct hlist_head *head = inode_hashtable + inode_hash(d_inode(de)->i_sb, (unsigned long)de); struct ksmbd_inode *ci = NULL, *ret_ci = NULL; hlist_for_each_entry(ci, head, m_hash) { if (ci->m_de == de) { if (atomic_inc_not_zero(&ci->m_count)) ret_ci = ci; break; } } return ret_ci;} static struct ksmbd_inode *ksmbd_inode_lookup(struct ksmbd_file *fp){ return __ksmbd_inode_lookup(fp->filp->f_path.dentry);} struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d){ struct ksmbd_inode *ci; read_lock(&inode_hash_lock); ci = __ksmbd_inode_lookup(d); read_unlock(&inode_hash_lock); return ci;} int ksmbd_query_inode_status(struct dentry *dentry){ struct ksmbd_inode *ci; int ret = KSMBD_INODE_STATUS_UNKNOWN; read_lock(&inode_hash_lock); ci = __ksmbd_inode_lookup(dentry); read_unlock(&inode_hash_lock); if (!ci) return ret; down_read(&ci->m_lock); if (ci->m_flags & S_DEL_PENDING) ret = KSMBD_INODE_STATUS_PENDING_DELETE; else ret = KSMBD_INODE_STATUS_OK; up_read(&ci->m_lock); ksmbd_inode_put(ci); return ret;} bool ksmbd_inode_pending_delete(struct ksmbd_file *fp){ struct ksmbd_inode *ci = fp->f_ci; int ret; down_read(&ci->m_lock); ret = (ci->m_flags & S_DEL_PENDING); up_read(&ci->m_lock); if (ret || !ksmbd_stream_fd(fp)) return ret; spin_lock(&fp->f_lock); ret = fp->stream_del_pending; spin_unlock(&fp->f_lock); return ret;} void ksmbd_set_inode_pending_delete(struct ksmbd_file *fp){ struct ksmbd_inode *ci = fp->f_ci; down_write(&ci->m_lock); ci->m_flags |= S_DEL_PENDING; up_write(&ci->m_lock);} void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp){ struct ksmbd_inode *ci = fp->f_ci; down_write(&ci->m_lock); ci->m_flags &= ~S_DEL_PENDING; up_write(&ci->m_lock);} bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp){ struct ksmbd_file *prev_fp; struct ksmbd_inode *ci = fp->f_ci; bool ret = false; if (ksmbd_stream_fd(fp)) return false; down_read(&ci->m_lock); list_for_each_entry(prev_fp, &ci->m_fp_list, node) { if (prev_fp == fp || !ksmbd_stream_fd(prev_fp)) continue; if (file_inode(fp->filp) != file_inode(prev_fp->filp)) continue; if (!(prev_fp->saccess & FILE_SHARE_DELETE_LE)) { ret = true; break; } } up_read(&ci->m_lock); return ret;} void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp, int file_info){ struct ksmbd_inode *ci = fp->f_ci; down_write(&ci->m_lock); if (ksmbd_stream_fd(fp)) ci->m_flags |= S_DEL_ON_CLS_STREAM; else ci->m_flags |= S_DEL_ON_CLS; up_write(&ci->m_lock);} /* * FileDispositionInformation (SET_INFO) on a stream handle must only * mark the stream for deletion, not the whole file -- otherwise * deleting a single alternate data stream (e.g. AFP_AfpInfo) deletes * the entire file's data along with it. * * This is tracked on fp itself (stream_del_pending), not the shared * ksmbd_inode: the inode-wide S_DEL_ON_CLS_STREAM flag used by * ksmbd_fd_set_delete_on_close() can't record *which* stream should be * deleted, so if a different stream handle on the same file closed * first, it would delete the wrong stream. */void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp){ if (ksmbd_stream_fd(fp)) { spin_lock(&fp->f_lock); fp->stream_del_pending = true; spin_unlock(&fp->f_lock); } else { ksmbd_set_inode_pending_delete(fp); }} void ksmbd_fd_clear_delete_pending(struct ksmbd_file *fp){ if (ksmbd_stream_fd(fp)) { spin_lock(&fp->f_lock); fp->stream_del_pending = false; spin_unlock(&fp->f_lock); } else { ksmbd_clear_inode_pending_delete(fp); }} static void ksmbd_inode_hash(struct ksmbd_inode *ci){ struct hlist_head *b = inode_hashtable + inode_hash(d_inode(ci->m_de)->i_sb, (unsigned long)ci->m_de); hlist_add_head(&ci->m_hash, b);} static void ksmbd_inode_unhash(struct ksmbd_inode *ci){ write_lock(&inode_hash_lock); hlist_del_init(&ci->m_hash); write_unlock(&inode_hash_lock);} static int ksmbd_inode_init(struct ksmbd_inode *ci, struct ksmbd_file *fp){ atomic_set(&ci->m_count, 1); atomic_set(&ci->op_count, 0); atomic_set(&ci->sop_count, 0); ci->m_flags = 0; ci->m_fattr = 0; INIT_LIST_HEAD(&ci->m_fp_list); INIT_LIST_HEAD(&ci->m_op_list); init_rwsem(&ci->m_lock); ci->m_de = fp->filp->f_path.dentry; return 0;} static struct ksmbd_inode *ksmbd_inode_get(struct ksmbd_file *fp){ struct ksmbd_inode *ci, *tmpci; int rc; read_lock(&inode_hash_lock); ci = ksmbd_inode_lookup(fp); read_unlock(&inode_hash_lock); if (ci) return ci; ci = kmalloc_obj(struct ksmbd_inode, KSMBD_DEFAULT_GFP); if (!ci) return NULL; rc = ksmbd_inode_init(ci, fp); if (rc) { pr_err("inode initialized failed\n"); kfree(ci); return NULL; } write_lock(&inode_hash_lock); tmpci = ksmbd_inode_lookup(fp); if (!tmpci) { ksmbd_inode_hash(ci); } else { kfree(ci); ci = tmpci; } write_unlock(&inode_hash_lock); return ci;} static void ksmbd_inode_free(struct ksmbd_inode *ci){ ksmbd_inode_unhash(ci); kfree(ci);} void ksmbd_inode_put(struct ksmbd_inode *ci){ if (atomic_dec_and_test(&ci->m_count)) ksmbd_inode_free(ci);} int __init ksmbd_inode_hash_init(void){ unsigned int loop; unsigned long numentries = 16384; unsigned long bucketsize = sizeof(struct hlist_head); unsigned long size; inode_hash_shift = ilog2(numentries); inode_hash_mask = (1 << inode_hash_shift) - 1; size = bucketsize << inode_hash_shift; /* init master fp hash table */ inode_hashtable = vmalloc(size); if (!inode_hashtable) return -ENOMEM; for (loop = 0; loop < (1U << inode_hash_shift); loop++) INIT_HLIST_HEAD(&inode_hashtable[loop]); return 0;} Lines 1–500 of 2008. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org> * Copyright (C) 2019 Samsung Electronics Co., Ltd. */ #include <linux/fs.h> #include <linux/filelock.h> #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/kthread.h> #include <linux/freezer.h> #include <linux/dcache.h> #include "glob.h" #include "vfs_cache.h" #include "oplock.h" #include "vfs.h" #include "connection.h" #include "misc.h" #include "mgmt/tree_connect.h" #include "mgmt/user_session.h" #include "mgmt/user_config.h" #include "smb_common.h" #include "server.h" #include "smb2pdu.h" #define S_DEL_PENDING 1 #define S_DEL_ON_CLS 2 #define S_DEL_ON_CLS_STREAM 8 static unsigned int inode_hash_mask __read_mostly; static unsigned int inode_hash_shift __read_mostly; static struct hlist_head *inode_hashtable __read_mostly; static DEFINE_RWLOCK(inode_hash_lock); static struct ksmbd_file_table global_ft; static atomic_long_t fd_limit; static struct kmem_cache *filp_cache; static int ksmbd_mark_fp_closed(struct ksmbd_file *fp); #define OPLOCK_NONE 0 #define OPLOCK_EXCLUSIVE 1 #define OPLOCK_BATCH 2 #define OPLOCK_READ 3 /* level 2 oplock */ #ifdef CONFIG_PROC_FS static const struct ksmbd_const_name ksmbd_lease_const_names[] = { {le32_to_cpu(SMB2_LEASE_NONE_LE), "LEASE_NONE"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE), "LEASE_R"}, {le32_to_cpu(SMB2_LEASE_HANDLE_CACHING_LE), "LEASE_H"}, {le32_to_cpu(SMB2_LEASE_WRITE_CACHING_LE), "LEASE_W"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE), "LEASE_RH"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE), "LEASE_RW"}, {le32_to_cpu(SMB2_LEASE_HANDLE_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE), "LEASE_WH"}, {le32_to_cpu(SMB2_LEASE_READ_CACHING_LE | SMB2_LEASE_HANDLE_CACHING_LE | SMB2_LEASE_WRITE_CACHING_LE), "LEASE_RWH"}, }; static const struct ksmbd_const_name ksmbd_oplock_const_names[] = { {SMB2_OPLOCK_LEVEL_NONE, "OPLOCK_NONE"}, {SMB2_OPLOCK_LEVEL_II, "OPLOCK_II"}, {SMB2_OPLOCK_LEVEL_EXCLUSIVE, "OPLOCK_EXCLUSIVE"}, {SMB2_OPLOCK_LEVEL_BATCH, "OPLOCK_BATCH"}, }; static const struct ksmbd_const_name ksmbd_file_state_names[] = { {FP_NEW, "new"}, {FP_INITED, "open"}, {FP_CLOSED, "closed"}, }; #define KSMBD_PROC_FILE_DURABLE BIT(0) #define KSMBD_PROC_FILE_PERSISTENT BIT(1) #define KSMBD_PROC_FILE_RESILIENT BIT(2) #define KSMBD_PROC_FILE_DELETE_ON_CLOSE BIT(3) #define KSMBD_PROC_FILE_STREAM BIT(4) #define KSMBD_PROC_FILE_POSIX BIT(5) #define KSMBD_PROC_FILE_ATTRIB_ONLY BIT(6) static const struct ksmbd_const_name ksmbd_file_flag_names[] = { {KSMBD_PROC_FILE_DURABLE, "durable"}, {KSMBD_PROC_FILE_PERSISTENT, "persistent"}, {KSMBD_PROC_FILE_RESILIENT, "resilient"}, {KSMBD_PROC_FILE_DELETE_ON_CLOSE, "delete-on-close"}, {KSMBD_PROC_FILE_STREAM, "stream"}, {KSMBD_PROC_FILE_POSIX, "posix"}, {KSMBD_PROC_FILE_ATTRIB_ONLY, "attrib-only"}, }; static unsigned int ksmbd_proc_file_flags(struct ksmbd_file *fp) { unsigned int flags = 0; if (fp->is_durable) flags |= KSMBD_PROC_FILE_DURABLE; if (fp->is_persistent) flags |= KSMBD_PROC_FILE_PERSISTENT; if (fp->is_resilient) flags |= KSMBD_PROC_FILE_RESILIENT; if (fp->coption & FILE_DELETE_ON_CLOSE_LE) flags |= KSMBD_PROC_FILE_DELETE_ON_CLOSE; if (fp->stream.name) flags |= KSMBD_PROC_FILE_STREAM; if (fp->is_posix_ctxt) flags |= KSMBD_PROC_FILE_POSIX; if (fp->attrib_only) flags |= KSMBD_PROC_FILE_ATTRIB_ONLY; return flags; } static int proc_show_files(struct seq_file *m, void *v) { struct ksmbd_file *fp = NULL; unsigned int id; struct oplock_info *opinfo; read_lock(&global_ft.lock); idr_for_each_entry(global_ft.idr, fp, id) { seq_printf(m, "tree_id:\t0x%x\n", fp->tcon ? fp->tcon->id : 0); seq_printf(m, "persistent_id:\t0x%llx\n", fp->persistent_id); seq_printf(m, "volatile_id:\t0x%llx\n", fp->volatile_id); seq_printf(m, "refcount:\t%d\n", atomic_read(&fp->refcount)); rcu_read_lock(); opinfo = rcu_dereference(fp->f_opinfo); if (opinfo) { const struct ksmbd_const_name *const_names; const char *name; int count; unsigned int level; if (opinfo->is_lease) { const_names = ksmbd_lease_const_names; count = ARRAY_SIZE(ksmbd_lease_const_names); level = le32_to_cpu(opinfo->o_lease->state); } else { const_names = ksmbd_oplock_const_names; count = ARRAY_SIZE(ksmbd_oplock_const_names); level = opinfo->level; } rcu_read_unlock(); name = ksmbd_proc_const_name(const_names, count, level); if (name) seq_printf(m, "oplock:\t%s\n", name); else seq_printf(m, "oplock:\t0x%x\n", level); } else { rcu_read_unlock(); seq_puts(m, "oplock:\tnone\n"); } seq_printf(m, "state:\t%s\n", ksmbd_proc_const_name(ksmbd_file_state_names, ARRAY_SIZE(ksmbd_file_state_names), fp->f_state)); seq_printf(m, "durable_timeout:\t%u\n", fp->durable_timeout); seq_printf(m, "create_options:\t0x%08x\n", le32_to_cpu(fp->coption)); seq_printf(m, "desired_access:\t0x%08x\n", le32_to_cpu(fp->daccess)); seq_printf(m, "share_access:\t0x%08x\n", le32_to_cpu(fp->saccess)); seq_puts(m, "flags:\t"); ksmbd_proc_show_flag_names(m, ksmbd_file_flag_names, ARRAY_SIZE(ksmbd_file_flag_names), ksmbd_proc_file_flags(fp)); seq_printf(m, "\nname:\t%s\n\n", fp->filp->f_path.dentry->d_name.name); } read_unlock(&global_ft.lock); return 0; } static int create_proc_files(void) { if (!ksmbd_proc_create("files", proc_show_files, NULL)) return -ENOMEM; return 0; } #else static int create_proc_files(void) { return 0; } #endif static bool durable_scavenger_running; static DEFINE_MUTEX(durable_scavenger_lock); static wait_queue_head_t dh_wq; bool ksmbd_durable_scavenger_active(void) { bool active; mutex_lock(&durable_scavenger_lock); active = durable_scavenger_running; mutex_unlock(&durable_scavenger_lock); return active; } void ksmbd_set_fd_limit(unsigned long limit) { limit = min(limit, get_max_files()); atomic_long_set(&fd_limit, limit); } static bool fd_limit_depleted(void) { long v = atomic_long_dec_return(&fd_limit); if (v >= 0) return false; atomic_long_inc(&fd_limit); return true; } static void fd_limit_close(void) { atomic_long_inc(&fd_limit); } /* * INODE hash */ static unsigned long inode_hash(struct super_block *sb, unsigned long hashval) { unsigned long tmp; tmp = (hashval * (unsigned long)sb) ^ (GOLDEN_RATIO_PRIME + hashval) / L1_CACHE_BYTES; tmp = tmp ^ ((tmp ^ GOLDEN_RATIO_PRIME) >> inode_hash_shift); return tmp & inode_hash_mask; } static struct ksmbd_inode *__ksmbd_inode_lookup(struct dentry *de) { struct hlist_head *head = inode_hashtable + inode_hash(d_inode(de)->i_sb, (unsigned long)de); struct ksmbd_inode *ci = NULL, *ret_ci = NULL; hlist_for_each_entry(ci, head, m_hash) { if (ci->m_de == de) { if (atomic_inc_not_zero(&ci->m_count)) ret_ci = ci; break; } } return ret_ci; } static struct ksmbd_inode *ksmbd_inode_lookup(struct ksmbd_file *fp) { return __ksmbd_inode_lookup(fp->filp->f_path.dentry); } struct ksmbd_inode *ksmbd_inode_lookup_lock(struct dentry *d) { struct ksmbd_inode *ci; read_lock(&inode_hash_lock); ci = __ksmbd_inode_lookup(d); read_unlock(&inode_hash_lock); return ci; } int ksmbd_query_inode_status(struct dentry *dentry) { struct ksmbd_inode *ci; int ret = KSMBD_INODE_STATUS_UNKNOWN; read_lock(&inode_hash_lock); ci = __ksmbd_inode_lookup(dentry); read_unlock(&inode_hash_lock); if (!ci) return ret; down_read(&ci->m_lock); if (ci->m_flags & S_DEL_PENDING) ret = KSMBD_INODE_STATUS_PENDING_DELETE; else ret = KSMBD_INODE_STATUS_OK; up_read(&ci->m_lock); ksmbd_inode_put(ci); return ret; } bool ksmbd_inode_pending_delete(struct ksmbd_file *fp) { struct ksmbd_inode *ci = fp->f_ci; int ret; down_read(&ci->m_lock); ret = (ci->m_flags & S_DEL_PENDING); up_read(&ci->m_lock); if (ret || !ksmbd_stream_fd(fp)) return ret; spin_lock(&fp->f_lock); ret = fp->stream_del_pending; spin_unlock(&fp->f_lock); return ret; } void ksmbd_set_inode_pending_delete(struct ksmbd_file *fp) { struct ksmbd_inode *ci = fp->f_ci; down_write(&ci->m_lock); ci->m_flags |= S_DEL_PENDING; up_write(&ci->m_lock); } void ksmbd_clear_inode_pending_delete(struct ksmbd_file *fp) { struct ksmbd_inode *ci = fp->f_ci; down_write(&ci->m_lock); ci->m_flags &= ~S_DEL_PENDING; up_write(&ci->m_lock); } bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp) { struct ksmbd_file *prev_fp; struct ksmbd_inode *ci = fp->f_ci; bool ret = false; if (ksmbd_stream_fd(fp)) return false; down_read(&ci->m_lock); list_for_each_entry(prev_fp, &ci->m_fp_list, node) { if (prev_fp == fp || !ksmbd_stream_fd(prev_fp)) continue; if (file_inode(fp->filp) != file_inode(prev_fp->filp)) continue; if (!(prev_fp->saccess & FILE_SHARE_DELETE_LE)) { ret = true; break; } } up_read(&ci->m_lock); return ret; } void ksmbd_fd_set_delete_on_close(struct ksmbd_file *fp, int file_info) { struct ksmbd_inode *ci = fp->f_ci; down_write(&ci->m_lock); if (ksmbd_stream_fd(fp)) ci->m_flags |= S_DEL_ON_CLS_STREAM; else ci->m_flags |= S_DEL_ON_CLS; up_write(&ci->m_lock); } /* * FileDispositionInformation (SET_INFO) on a stream handle must only * mark the stream for deletion, not the whole file -- otherwise * deleting a single alternate data stream (e.g. AFP_AfpInfo) deletes * the entire file's data along with it. * * This is tracked on fp itself (stream_del_pending), not the shared * ksmbd_inode: the inode-wide S_DEL_ON_CLS_STREAM flag used by * ksmbd_fd_set_delete_on_close() can't record *which* stream should be * deleted, so if a different stream handle on the same file closed * first, it would delete the wrong stream. */ void ksmbd_fd_set_delete_pending(struct ksmbd_file *fp) { if (ksmbd_stream_fd(fp)) { spin_lock(&fp->f_lock); fp->stream_del_pending = true; spin_unlock(&fp->f_lock); } else { ksmbd_set_inode_pending_delete(fp); } } void ksmbd_fd_clear_delete_pending(struct ksmbd_file *fp) { if (ksmbd_stream_fd(fp)) { spin_lock(&fp->f_lock); fp->stream_del_pending = false; spin_unlock(&fp->f_lock); } else { ksmbd_clear_inode_pending_delete(fp); } } static void ksmbd_inode_hash(struct ksmbd_inode *ci) { struct hlist_head *b = inode_hashtable + inode_hash(d_inode(ci->m_de)->i_sb, (unsigned long)ci->m_de); hlist_add_head(&ci->m_hash, b); } static void ksmbd_inode_unhash(struct ksmbd_inode *ci) { write_lock(&inode_hash_lock); hlist_del_init(&ci->m_hash); write_unlock(&inode_hash_lock); } static int ksmbd_inode_init(struct ksmbd_inode *ci, struct ksmbd_file *fp) { atomic_set(&ci->m_count, 1); atomic_set(&ci->op_count, 0); atomic_set(&ci->sop_count, 0); ci->m_flags = 0; ci->m_fattr = 0; INIT_LIST_HEAD(&ci->m_fp_list); INIT_LIST_HEAD(&ci->m_op_list); init_rwsem(&ci->m_lock); ci->m_de = fp->filp->f_path.dentry; return 0; } static struct ksmbd_inode *ksmbd_inode_get(struct ksmbd_file *fp) { struct ksmbd_inode *ci, *tmpci; int rc; read_lock(&inode_hash_lock); ci = ksmbd_inode_lookup(fp); read_unlock(&inode_hash_lock); if (ci) return ci; ci = kmalloc_obj(struct ksmbd_inode, KSMBD_DEFAULT_GFP); if (!ci) return NULL; rc = ksmbd_inode_init(ci, fp); if (rc) { pr_err("inode initialized failed\n"); kfree(ci); return NULL; } write_lock(&inode_hash_lock); tmpci = ksmbd_inode_lookup(fp); if (!tmpci) { ksmbd_inode_hash(ci); } else { kfree(ci); ci = tmpci; } write_unlock(&inode_hash_lock); return ci; } static void ksmbd_inode_free(struct ksmbd_inode *ci) { ksmbd_inode_unhash(ci); kfree(ci); } void ksmbd_inode_put(struct ksmbd_inode *ci) { if (atomic_dec_and_test(&ci->m_count)) ksmbd_inode_free(ci); } int __init ksmbd_inode_hash_init(void) { unsigned int loop; unsigned long numentries = 16384; unsigned long bucketsize = sizeof(struct hlist_head); unsigned long size; inode_hash_shift = ilog2(numentries); inode_hash_mask = (1 << inode_hash_shift) - 1; size = bucketsize << inode_hash_shift; /* init master fp hash table */ inode_hashtable = vmalloc(size); if (!inode_hashtable) return -ENOMEM; for (loop = 0; loop < (1U << inode_hash_shift); loop++) INIT_HLIST_HEAD(&inode_hashtable[loop]); return 0; }