summaryrefslogtreecommitdiffstats
path: root/fs/smb/client/misc.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /fs/smb/client/misc.c
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'fs/smb/client/misc.c')
-rw-r--r--fs/smb/client/misc.c1112
1 files changed, 1112 insertions, 0 deletions
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
new file mode 100644
index 000000000..46e1382e8
--- /dev/null
+++ b/fs/smb/client/misc.c
@@ -0,0 +1,1112 @@
+// SPDX-License-Identifier: LGPL-2.1
+/*
+ *
+ * Copyright (C) International Business Machines Corp., 2002,2008
+ * Author(s): Steve French (sfrench@us.ibm.com)
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/mempool.h>
+#include <linux/vmalloc.h>
+#include "cifsglob.h"
+#include "cifsproto.h"
+#include "cifs_debug.h"
+#include "smberr.h"
+#include "nterr.h"
+#include "cifs_unicode.h"
+#include "smb2pdu.h"
+#include "smb2proto.h"
+#include "smb1proto.h"
+#include "cifsfs.h"
+#ifdef CONFIG_CIFS_DFS_UPCALL
+#include "dns_resolve.h"
+#include "dfs_cache.h"
+#include "dfs.h"
+#endif
+#include "fs_context.h"
+#include "cached_dir.h"
+
+struct tcon_list {
+ struct list_head entry;
+ struct cifs_tcon *tcon;
+};
+
+/* The xid serves as a useful identifier for each incoming vfs request,
+ in a similar way to the mid which is useful to track each sent smb,
+ and CurrentXid can also provide a running counter (although it
+ will eventually wrap past zero) of the total vfs operations handled
+ since the cifs fs was mounted */
+
+unsigned int
+_get_xid(void)
+{
+ unsigned int xid;
+
+ spin_lock(&GlobalMid_Lock);
+ GlobalTotalActiveXid++;
+
+ /* keep high water mark for number of simultaneous ops in filesystem */
+ if (GlobalTotalActiveXid > GlobalMaxActiveXid)
+ GlobalMaxActiveXid = GlobalTotalActiveXid;
+ if (GlobalTotalActiveXid > 65000)
+ cifs_dbg(FYI, "warning: more than 65000 requests active\n");
+ xid = GlobalCurrentXid++;
+ spin_unlock(&GlobalMid_Lock);
+ return xid;
+}
+
+void
+_free_xid(unsigned int xid)
+{
+ spin_lock(&GlobalMid_Lock);
+ /* if (GlobalTotalActiveXid == 0)
+ BUG(); */
+ GlobalTotalActiveXid--;
+ spin_unlock(&GlobalMid_Lock);
+}
+
+struct cifs_ses *
+sesInfoAlloc(void)
+{
+ struct cifs_ses *ret_buf;
+
+ ret_buf = kzalloc_obj(struct cifs_ses);
+ if (ret_buf) {
+ atomic_inc(&sesInfoAllocCount);
+ spin_lock_init(&ret_buf->ses_lock);
+ ret_buf->ses_status = SES_NEW;
+ ++ret_buf->ses_count;
+ INIT_LIST_HEAD(&ret_buf->smb_ses_list);
+ INIT_LIST_HEAD(&ret_buf->tcon_list);
+ mutex_init(&ret_buf->session_mutex);
+ spin_lock_init(&ret_buf->iface_lock);
+ INIT_LIST_HEAD(&ret_buf->iface_list);
+ spin_lock_init(&ret_buf->chan_lock);
+ }
+ return ret_buf;
+}
+
+void
+sesInfoFree(struct cifs_ses *buf_to_free)
+{
+ struct cifs_server_iface *iface = NULL, *niface = NULL;
+
+ if (buf_to_free == NULL) {
+ cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n");
+ return;
+ }
+
+ unload_nls(buf_to_free->local_nls);
+ atomic_dec(&sesInfoAllocCount);
+ kfree(buf_to_free->serverOS);
+ kfree(buf_to_free->serverDomain);
+ kfree(buf_to_free->serverNOS);
+ kfree_sensitive(buf_to_free->password);
+ kfree_sensitive(buf_to_free->password2);
+ kfree(buf_to_free->user_name);
+ kfree(buf_to_free->domainName);
+ kfree(buf_to_free->dns_dom);
+ kfree_sensitive(buf_to_free->auth_key.response);
+ spin_lock(&buf_to_free->iface_lock);
+ list_for_each_entry_safe(iface, niface, &buf_to_free->iface_list,
+ iface_head)
+ kref_put(&iface->refcount, release_iface);
+ spin_unlock(&buf_to_free->iface_lock);
+ kfree_sensitive(buf_to_free);
+}
+
+struct cifs_tcon *
+tcon_info_alloc(bool dir_leases_enabled, enum smb3_tcon_ref_trace trace)
+{
+ struct cifs_tcon *ret_buf;
+ static atomic_t tcon_debug_id;
+
+ ret_buf = kzalloc_obj(*ret_buf);
+ if (!ret_buf)
+ return NULL;
+
+ if (dir_leases_enabled == true) {
+ ret_buf->cfids = init_cached_dirs();
+ if (!ret_buf->cfids) {
+ kfree(ret_buf);
+ return NULL;
+ }
+ }
+ /* else ret_buf->cfids is already set to NULL above */
+
+ atomic_inc(&tconInfoAllocCount);
+ ret_buf->status = TID_NEW;
+ ret_buf->debug_id = atomic_inc_return(&tcon_debug_id);
+ ret_buf->tc_count = 1;
+ spin_lock_init(&ret_buf->tc_lock);
+ INIT_LIST_HEAD(&ret_buf->openFileList);
+ INIT_LIST_HEAD(&ret_buf->tcon_list);
+ INIT_LIST_HEAD(&ret_buf->cifs_sb_list);
+ spin_lock_init(&ret_buf->open_file_lock);
+ spin_lock_init(&ret_buf->stat_lock);
+ spin_lock_init(&ret_buf->sb_list_lock);
+ atomic_set(&ret_buf->num_local_opens, 0);
+ atomic_set(&ret_buf->num_remote_opens, 0);
+ ret_buf->stats_from_time = ktime_get_real_seconds();
+#ifdef CONFIG_CIFS_FSCACHE
+ mutex_init(&ret_buf->fscache_lock);
+#endif
+ trace_smb3_tcon_ref(ret_buf->debug_id, ret_buf->tc_count, trace);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+ INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
+#endif
+ INIT_LIST_HEAD(&ret_buf->pending_opens);
+ INIT_DELAYED_WORK(&ret_buf->query_interfaces,
+ smb2_query_server_interfaces);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+ INIT_DELAYED_WORK(&ret_buf->dfs_cache_work, dfs_cache_refresh);
+#endif
+
+ return ret_buf;
+}
+
+void
+tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
+{
+ if (tcon == NULL) {
+ cifs_dbg(FYI, "Null buffer passed to tconInfoFree\n");
+ return;
+ }
+ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count, trace);
+ free_cached_dirs(tcon->cfids);
+ atomic_dec(&tconInfoAllocCount);
+ kfree(tcon->nativeFileSystem);
+ kfree_sensitive(tcon->password);
+ kfree(tcon->origin_fullpath);
+ kfree(tcon);
+}
+
+void *
+cifs_buf_get(void)
+{
+ void *ret_buf = NULL;
+ /*
+ * SMB2 header is bigger than CIFS one - no problems to clean some
+ * more bytes for CIFS.
+ */
+ size_t buf_size = sizeof(struct smb2_hdr);
+
+ /*
+ * We could use negotiated size instead of max_msgsize -
+ * but it may be more efficient to always alloc same size
+ * albeit slightly larger than necessary and maxbuffersize
+ * defaults to this and can not be bigger.
+ */
+ ret_buf = mempool_alloc(cifs_req_poolp, GFP_NOFS);
+
+ /* clear the first few header bytes */
+ /* for most paths, more is cleared in header_assemble */
+ memset(ret_buf, 0, buf_size + 3);
+ atomic_inc(&buf_alloc_count);
+#ifdef CONFIG_CIFS_STATS2
+ atomic_inc(&total_buf_alloc_count);
+#endif /* CONFIG_CIFS_STATS2 */
+
+ return ret_buf;
+}
+
+void
+cifs_buf_release(void *buf_to_free)
+{
+ if (buf_to_free == NULL) {
+ /* cifs_dbg(FYI, "Null buffer passed to cifs_buf_release\n");*/
+ return;
+ }
+ mempool_free(buf_to_free, cifs_req_poolp);
+
+ atomic_dec(&buf_alloc_count);
+ return;
+}
+
+void *
+cifs_small_buf_get(void)
+{
+ void *ret_buf = NULL;
+
+/* We could use negotiated size instead of max_msgsize -
+ but it may be more efficient to always alloc same size
+ albeit slightly larger than necessary and maxbuffersize
+ defaults to this and can not be bigger */
+ ret_buf = mempool_alloc(cifs_sm_req_poolp, GFP_NOFS);
+ /* No need to clear memory here, cleared in header assemble */
+ atomic_inc(&small_buf_alloc_count);
+#ifdef CONFIG_CIFS_STATS2
+ atomic_inc(&total_small_buf_alloc_count);
+#endif /* CONFIG_CIFS_STATS2 */
+
+ return ret_buf;
+}
+
+void
+cifs_small_buf_release(void *buf_to_free)
+{
+
+ if (buf_to_free == NULL) {
+ cifs_dbg(FYI, "Null buffer passed to cifs_small_buf_release\n");
+ return;
+ }
+ mempool_free(buf_to_free, cifs_sm_req_poolp);
+
+ atomic_dec(&small_buf_alloc_count);
+ return;
+}
+
+void
+free_rsp_buf(int resp_buftype, void *rsp)
+{
+ if (resp_buftype == CIFS_SMALL_BUFFER)
+ cifs_small_buf_release(rsp);
+ else if (resp_buftype == CIFS_LARGE_BUFFER)
+ cifs_buf_release(rsp);
+}
+
+void
+dump_smb(void *buf, int smb_buf_length)
+{
+ if (traceSMB == 0)
+ return;
+
+ print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_NONE, 8, 2, buf,
+ smb_buf_length, true);
+}
+
+void
+cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb, const char *reason, int rc)
+{
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
+
+ if (sbflags & CIFS_MOUNT_SERVER_INUM) {
+ struct cifs_tcon *tcon = NULL;
+
+ if (cifs_sb->master_tlink)
+ tcon = cifs_sb_master_tcon(cifs_sb);
+
+ atomic_andnot(CIFS_MOUNT_SERVER_INUM, &cifs_sb->mnt_cifs_flags);
+ cifs_sb->mnt_cifs_serverino_autodisabled = true;
+ if (rc)
+ cifs_dbg(VFS, "%s: %d\n", reason, rc);
+ else
+ cifs_dbg(VFS, "%s\n", reason);
+ cifs_dbg(VFS, "Autodisabling the use of server inode numbers on %s\n",
+ tcon ? tcon->tree_name : "new server");
+ cifs_dbg(VFS, "The server doesn't seem to support them properly or the files might be on different servers (DFS)\n");
+ cifs_dbg(VFS, "Hardlinks will not be recognized on this mount. Consider mounting with the \"noserverino\" option to silence this message.\n");
+
+ }
+}
+
+void cifs_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock)
+{
+ oplock &= 0xF;
+
+ if (oplock == OPLOCK_EXCLUSIVE) {
+ cinode->oplock = CIFS_CACHE_WRITE_FLG | CIFS_CACHE_READ_FLG;
+ cifs_dbg(FYI, "Exclusive Oplock granted on inode %p\n",
+ &cinode->netfs.inode);
+ } else if (oplock == OPLOCK_READ) {
+ cinode->oplock = CIFS_CACHE_READ_FLG;
+ cifs_dbg(FYI, "Level II Oplock granted on inode %p\n",
+ &cinode->netfs.inode);
+ } else
+ cinode->oplock = 0;
+}
+
+/*
+ * We wait for oplock breaks to be processed before we attempt to perform
+ * writes.
+ */
+int cifs_get_writer(struct cifsInodeInfo *cinode)
+{
+ int rc;
+
+start:
+ rc = wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_OPLOCK_BREAK,
+ TASK_KILLABLE);
+ if (rc)
+ return rc;
+
+ spin_lock(&cinode->writers_lock);
+ if (!cinode->writers)
+ set_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
+ cinode->writers++;
+ /* Check to see if we have started servicing an oplock break */
+ if (test_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags)) {
+ cinode->writers--;
+ if (cinode->writers == 0) {
+ clear_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
+ wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS);
+ }
+ spin_unlock(&cinode->writers_lock);
+ goto start;
+ }
+ spin_unlock(&cinode->writers_lock);
+ return 0;
+}
+
+void cifs_put_writer(struct cifsInodeInfo *cinode)
+{
+ spin_lock(&cinode->writers_lock);
+ cinode->writers--;
+ if (cinode->writers == 0) {
+ clear_bit(CIFS_INODE_PENDING_WRITERS, &cinode->flags);
+ wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS);
+ }
+ spin_unlock(&cinode->writers_lock);
+}
+
+/**
+ * cifs_queue_oplock_break - queue the oplock break handler for cfile
+ * @cfile: The file to break the oplock on
+ *
+ * This function is called from the demultiplex thread when it
+ * receives an oplock break for @cfile.
+ *
+ * Assumes the tcon->open_file_lock is held.
+ * Assumes cfile->file_info_lock is NOT held.
+ */
+void cifs_queue_oplock_break(struct cifsFileInfo *cfile)
+{
+ /*
+ * Bump the handle refcount now while we hold the
+ * open_file_lock to enforce the validity of it for the oplock
+ * break handler. The matching put is done at the end of the
+ * handler.
+ */
+ cifsFileInfo_get(cfile);
+
+ queue_work(cifsoplockd_wq, &cfile->oplock_break);
+}
+
+void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
+{
+ clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
+ wake_up_bit(&cinode->flags, CIFS_INODE_PENDING_OPLOCK_BREAK);
+}
+
+bool
+backup_cred(struct cifs_sb_info *cifs_sb)
+{
+ unsigned int sbflags = cifs_sb_flags(cifs_sb);
+
+ if (sbflags & CIFS_MOUNT_CIFS_BACKUPUID) {
+ if (uid_eq(cifs_sb->ctx->backupuid, current_fsuid()))
+ return true;
+ }
+ if (sbflags & CIFS_MOUNT_CIFS_BACKUPGID) {
+ if (in_group_p(cifs_sb->ctx->backupgid))
+ return true;
+ }
+
+ return false;
+}
+
+void
+cifs_del_pending_open(struct cifs_pending_open *open)
+{
+ spin_lock(&tlink_tcon(open->tlink)->open_file_lock);
+ list_del(&open->olist);
+ spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
+}
+
+void
+cifs_add_pending_open_locked(struct cifs_fid *fid, struct tcon_link *tlink,
+ struct cifs_pending_open *open)
+{
+ memcpy(open->lease_key, fid->lease_key, SMB2_LEASE_KEY_SIZE);
+ open->oplock = CIFS_OPLOCK_NO_CHANGE;
+ open->tlink = tlink;
+ fid->pending_open = open;
+ list_add_tail(&open->olist, &tlink_tcon(tlink)->pending_opens);
+}
+
+void
+cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink,
+ struct cifs_pending_open *open)
+{
+ spin_lock(&tlink_tcon(tlink)->open_file_lock);
+ cifs_add_pending_open_locked(fid, tlink, open);
+ spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
+}
+
+/*
+ * Critical section which runs after acquiring deferred_lock.
+ * As there is no reference count on cifs_deferred_close, pdclose
+ * should not be used outside deferred_lock.
+ */
+bool
+cifs_is_deferred_close(struct cifsFileInfo *cfile, struct cifs_deferred_close **pdclose)
+{
+ struct cifs_deferred_close *dclose;
+
+ list_for_each_entry(dclose, &CIFS_I(d_inode(cfile->dentry))->deferred_closes, dlist) {
+ if ((dclose->netfid == cfile->fid.netfid) &&
+ (dclose->persistent_fid == cfile->fid.persistent_fid) &&
+ (dclose->volatile_fid == cfile->fid.volatile_fid)) {
+ *pdclose = dclose;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Critical section which runs after acquiring deferred_lock.
+ */
+void
+cifs_add_deferred_close(struct cifsFileInfo *cfile, struct cifs_deferred_close *dclose)
+{
+ bool is_deferred = false;
+ struct cifs_deferred_close *pdclose;
+
+ is_deferred = cifs_is_deferred_close(cfile, &pdclose);
+ if (is_deferred) {
+ kfree(dclose);
+ return;
+ }
+
+ dclose->tlink = cfile->tlink;
+ dclose->netfid = cfile->fid.netfid;
+ dclose->persistent_fid = cfile->fid.persistent_fid;
+ dclose->volatile_fid = cfile->fid.volatile_fid;
+ list_add_tail(&dclose->dlist, &CIFS_I(d_inode(cfile->dentry))->deferred_closes);
+}
+
+/*
+ * Critical section which runs after acquiring deferred_lock.
+ */
+void
+cifs_del_deferred_close(struct cifsFileInfo *cfile)
+{
+ bool is_deferred = false;
+ struct cifs_deferred_close *dclose;
+
+ is_deferred = cifs_is_deferred_close(cfile, &dclose);
+ if (!is_deferred)
+ return;
+ list_del(&dclose->dlist);
+ kfree(dclose);
+}
+
+void
+cifs_close_deferred_file(struct cifsInodeInfo *cifs_inode)
+{
+ struct cifsFileInfo *cfile = NULL, *failed_cfile = NULL;
+ struct file_list *tmp_list, *tmp_next_list;
+ LIST_HEAD(file_head);
+
+ if (cifs_inode == NULL)
+ return;
+
+ spin_lock(&cifs_inode->open_file_lock);
+ list_for_each_entry(cfile, &cifs_inode->openFileList, flist) {
+ if (delayed_work_pending(&cfile->deferred)) {
+ if (cancel_delayed_work(&cfile->deferred)) {
+ spin_lock(&cifs_inode->deferred_lock);
+ cifs_del_deferred_close(cfile);
+ spin_unlock(&cifs_inode->deferred_lock);
+
+ tmp_list = kmalloc_obj(struct file_list,
+ GFP_ATOMIC);
+ if (tmp_list == NULL) {
+ failed_cfile = cfile;
+ break;
+ }
+ tmp_list->cfile = cfile;
+ list_add_tail(&tmp_list->list, &file_head);
+ }
+ }
+ }
+ spin_unlock(&cifs_inode->open_file_lock);
+
+ if (failed_cfile)
+ _cifsFileInfo_put(failed_cfile, false, false);
+
+ list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
+ _cifsFileInfo_put(tmp_list->cfile, false, false);
+ list_del(&tmp_list->list);
+ kfree(tmp_list);
+ }
+}
+
+void
+cifs_close_all_deferred_files(struct cifs_tcon *tcon)
+{
+ struct cifsFileInfo *cfile, *failed_cfile = NULL;
+ struct file_list *tmp_list, *tmp_next_list;
+ LIST_HEAD(file_head);
+
+ spin_lock(&tcon->open_file_lock);
+ list_for_each_entry(cfile, &tcon->openFileList, tlist) {
+ if (delayed_work_pending(&cfile->deferred)) {
+ if (cancel_delayed_work(&cfile->deferred)) {
+ spin_lock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+ cifs_del_deferred_close(cfile);
+ spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+
+ tmp_list = kmalloc_obj(struct file_list,
+ GFP_ATOMIC);
+ if (tmp_list == NULL) {
+ failed_cfile = cfile;
+ break;
+ }
+ tmp_list->cfile = cfile;
+ list_add_tail(&tmp_list->list, &file_head);
+ }
+ }
+ }
+ spin_unlock(&tcon->open_file_lock);
+
+ if (failed_cfile)
+ _cifsFileInfo_put(failed_cfile, true, false);
+
+ list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
+ _cifsFileInfo_put(tmp_list->cfile, true, false);
+ list_del(&tmp_list->list);
+ kfree(tmp_list);
+ }
+}
+
+void cifs_close_all_deferred_files_sb(struct cifs_sb_info *cifs_sb)
+{
+ struct rb_root *root = &cifs_sb->tlink_tree;
+ struct rb_node *node;
+ struct cifs_tcon *tcon;
+ struct tcon_link *tlink;
+ struct tcon_list *tmp_list, *q;
+ LIST_HEAD(tcon_head);
+
+ spin_lock(&cifs_sb->tlink_tree_lock);
+ for (node = rb_first(root); node; node = rb_next(node)) {
+ tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+ tcon = tlink_tcon(tlink);
+ if (IS_ERR(tcon))
+ continue;
+ tmp_list = kmalloc_obj(struct tcon_list, GFP_ATOMIC);
+ if (tmp_list == NULL)
+ break;
+ tmp_list->tcon = tcon;
+ /* Take a reference on tcon to prevent it from being freed */
+ spin_lock(&tcon->tc_lock);
+ ++tcon->tc_count;
+ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
+ netfs_trace_tcon_ref_get_close_defer_files);
+ spin_unlock(&tcon->tc_lock);
+ list_add_tail(&tmp_list->entry, &tcon_head);
+ }
+ spin_unlock(&cifs_sb->tlink_tree_lock);
+
+ list_for_each_entry_safe(tmp_list, q, &tcon_head, entry) {
+ cifs_close_all_deferred_files(tmp_list->tcon);
+ list_del(&tmp_list->entry);
+ cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_close_defer_files);
+ kfree(tmp_list);
+ }
+}
+
+void cifs_close_deferred_file_under_dentry(struct cifs_tcon *tcon,
+ struct dentry *dentry)
+{
+ struct file_list *tmp_list, *tmp_next_list;
+ struct cifsFileInfo *cfile, *failed_cfile = NULL;
+ LIST_HEAD(file_head);
+
+ spin_lock(&tcon->open_file_lock);
+ list_for_each_entry(cfile, &tcon->openFileList, tlist) {
+ if ((cfile->dentry == dentry) &&
+ delayed_work_pending(&cfile->deferred) &&
+ cancel_delayed_work(&cfile->deferred)) {
+ spin_lock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+ cifs_del_deferred_close(cfile);
+ spin_unlock(&CIFS_I(d_inode(cfile->dentry))->deferred_lock);
+
+ tmp_list = kmalloc_obj(struct file_list, GFP_ATOMIC);
+ if (tmp_list == NULL) {
+ failed_cfile = cfile;
+ break;
+ }
+ tmp_list->cfile = cfile;
+ list_add_tail(&tmp_list->list, &file_head);
+ }
+ }
+ spin_unlock(&tcon->open_file_lock);
+
+ if (failed_cfile)
+ _cifsFileInfo_put(failed_cfile, true, false);
+
+ list_for_each_entry_safe(tmp_list, tmp_next_list, &file_head, list) {
+ _cifsFileInfo_put(tmp_list->cfile, true, false);
+ list_del(&tmp_list->list);
+ kfree(tmp_list);
+ }
+}
+
+/*
+ * If a dentry has been deleted, all corresponding open handles should know that
+ * so that we do not defer close them.
+ */
+void cifs_mark_open_handles_for_deleted_file(struct inode *inode,
+ const char *path)
+{
+ struct cifsFileInfo *cfile;
+ void *page;
+ const char *full_path;
+ struct cifsInodeInfo *cinode = CIFS_I(inode);
+
+ page = alloc_dentry_path();
+ spin_lock(&cinode->open_file_lock);
+
+ /*
+ * note: we need to construct path from dentry and compare only if the
+ * inode has any hardlinks. When number of hardlinks is 1, we can just
+ * mark all open handles since they are going to be from the same file.
+ */
+ if (inode->i_nlink > 1) {
+ list_for_each_entry(cfile, &cinode->openFileList, flist) {
+ full_path = build_path_from_dentry(cfile->dentry, page);
+ if (!IS_ERR(full_path) && strcmp(full_path, path) == 0)
+ cfile->status_file_deleted = true;
+ }
+ } else {
+ list_for_each_entry(cfile, &cinode->openFileList, flist)
+ cfile->status_file_deleted = true;
+ }
+ spin_unlock(&cinode->open_file_lock);
+ free_dentry_path(page);
+}
+
+/* parses DFS referral V3 structure
+ * caller is responsible for freeing target_nodes
+ * returns:
+ * - on success - 0
+ * - on failure - errno
+ */
+int
+parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
+ unsigned int *num_of_nodes,
+ struct dfs_info3_param **target_nodes,
+ const struct nls_table *nls_codepage, int remap,
+ const char *searchName, bool is_unicode)
+{
+ int i, rc = 0;
+ char *data_end;
+ struct dfs_referral_level_3 *ref;
+ unsigned int path_consumed;
+ size_t search_name_len;
+
+ if (rsp_size < sizeof(*rsp)) {
+ cifs_dbg(VFS | ONCE,
+ "%s: header is malformed (size is %u, must be %zu)\n",
+ __func__, rsp_size, sizeof(*rsp));
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ *num_of_nodes = le16_to_cpu(rsp->NumberOfReferrals);
+
+ if (*num_of_nodes < 1) {
+ cifs_dbg(VFS | ONCE, "%s: [path=%s] num_referrals must be at least > 0, but we got %d\n",
+ __func__, searchName, *num_of_nodes);
+ rc = -ENOENT;
+ goto parse_DFS_referrals_exit;
+ }
+
+ if (sizeof(*rsp) + *num_of_nodes * sizeof(REFERRAL3) > rsp_size) {
+ cifs_dbg(VFS | ONCE,
+ "%s: malformed buffer (size is %u, must be at least %zu)\n",
+ __func__, rsp_size,
+ sizeof(*rsp) + *num_of_nodes * sizeof(REFERRAL3));
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ ref = (struct dfs_referral_level_3 *) &(rsp->referrals);
+ if (ref->VersionNumber != cpu_to_le16(3)) {
+ cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
+ le16_to_cpu(ref->VersionNumber));
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ /* get the upper boundary of the resp buffer */
+ data_end = (char *)rsp + rsp_size;
+
+ cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
+ *num_of_nodes, le32_to_cpu(rsp->DFSFlags));
+
+ *target_nodes = kzalloc_objs(struct dfs_info3_param, *num_of_nodes);
+ if (*target_nodes == NULL) {
+ rc = -ENOMEM;
+ goto parse_DFS_referrals_exit;
+ }
+ search_name_len = strlen(searchName);
+
+ /* collect necessary data from referrals */
+ for (i = 0; i < *num_of_nodes; i++) {
+ char *temp;
+ int max_len;
+ struct dfs_info3_param *node = (*target_nodes)+i;
+
+ node->flags = le32_to_cpu(rsp->DFSFlags);
+ path_consumed = le16_to_cpu(rsp->PathConsumed);
+ if (is_unicode) {
+ size_t search_name_utf16_len = search_name_len * 2 + 2;
+ __le16 *tmp;
+
+ if (path_consumed > search_name_utf16_len) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ tmp = kmalloc(search_name_utf16_len, GFP_KERNEL);
+ if (!tmp) {
+ rc = -ENOMEM;
+ goto parse_DFS_referrals_exit;
+ }
+ cifsConvertToUTF16((__le16 *)tmp, searchName,
+ PATH_MAX, nls_codepage, remap);
+ node->path_consumed = cifs_utf16_bytes(tmp, path_consumed,
+ nls_codepage);
+ kfree(tmp);
+ } else {
+ if (path_consumed > search_name_len) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+
+ node->path_consumed = path_consumed;
+ }
+
+ node->server_type = le16_to_cpu(ref->ServerType);
+ node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
+
+ /* copy DfsPath */
+ if (le16_to_cpu(ref->DfsPathOffset) > data_end - (char *)ref) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+ temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset);
+ max_len = data_end - temp;
+ node->path_name = cifs_strndup_from_utf16(temp, max_len,
+ is_unicode, nls_codepage);
+ if (!node->path_name) {
+ rc = -ENOMEM;
+ goto parse_DFS_referrals_exit;
+ }
+
+ /* copy link target UNC */
+ if (le16_to_cpu(ref->NetworkAddressOffset) > data_end - (char *)ref) {
+ rc = -EINVAL;
+ goto parse_DFS_referrals_exit;
+ }
+ temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
+ max_len = data_end - temp;
+ node->node_name = cifs_strndup_from_utf16(temp, max_len,
+ is_unicode, nls_codepage);
+ if (!node->node_name) {
+ rc = -ENOMEM;
+ goto parse_DFS_referrals_exit;
+ }
+
+ node->ttl = le32_to_cpu(ref->TimeToLive);
+
+ ref++;
+ }
+
+parse_DFS_referrals_exit:
+ if (rc) {
+ free_dfs_info_array(*target_nodes, *num_of_nodes);
+ *target_nodes = NULL;
+ *num_of_nodes = 0;
+ }
+ return rc;
+}
+
+void extract_unc_hostname(const char *unc, const char **h, size_t *len)
+{
+ const char *end;
+
+ /* skip initial slashes */
+ while (*unc && (*unc == '\\' || *unc == '/'))
+ unc++;
+
+ end = unc;
+
+ while (*end && !(*end == '\\' || *end == '/'))
+ end++;
+
+ *h = unc;
+ *len = end - unc;
+}
+
+/**
+ * copy_path_name - copy src path to dst, possibly truncating
+ * @dst: The destination buffer
+ * @src: The source name
+ *
+ * returns number of bytes written (including trailing nul)
+ */
+int copy_path_name(char *dst, const char *src)
+{
+ int name_len;
+
+ /*
+ * PATH_MAX includes nul, so if strlen(src) >= PATH_MAX it
+ * will truncate and strlen(dst) will be PATH_MAX-1
+ */
+ name_len = strscpy(dst, src, PATH_MAX);
+ if (WARN_ON_ONCE(name_len < 0))
+ name_len = PATH_MAX-1;
+
+ /* we count the trailing nul */
+ name_len++;
+ return name_len;
+}
+
+struct super_cb_data {
+ void *data;
+ struct super_block *sb;
+};
+
+static void tcon_super_cb(struct super_block *sb, void *arg)
+{
+ struct super_cb_data *sd = arg;
+ struct cifs_sb_info *cifs_sb;
+ struct cifs_tcon *t1 = sd->data, *t2;
+
+ if (sd->sb)
+ return;
+
+ cifs_sb = CIFS_SB(sb);
+ t2 = cifs_sb_master_tcon(cifs_sb);
+
+ spin_lock(&t2->tc_lock);
+ if ((t1->ses == t2->ses ||
+ t1->ses->dfs_root_ses == t2->ses->dfs_root_ses) &&
+ t1->ses->server == t2->ses->server &&
+ t2->origin_fullpath &&
+ dfs_src_pathname_equal(t2->origin_fullpath, t1->origin_fullpath))
+ sd->sb = sb;
+ spin_unlock(&t2->tc_lock);
+}
+
+static struct super_block *__cifs_get_super(void (*f)(struct super_block *, void *),
+ void *data)
+{
+ struct super_cb_data sd = {
+ .data = data,
+ .sb = NULL,
+ };
+ struct file_system_type **fs_type = (struct file_system_type *[]) {
+ &cifs_fs_type, &smb3_fs_type, NULL,
+ };
+
+ for (; *fs_type; fs_type++) {
+ iterate_supers_type(*fs_type, f, &sd);
+ if (sd.sb) {
+ /*
+ * Grab an active reference in order to prevent automounts (DFS links)
+ * of expiring and then freeing up our cifs superblock pointer while
+ * we're doing failover.
+ */
+ cifs_sb_active(sd.sb);
+ return sd.sb;
+ }
+ }
+ pr_warn_once("%s: could not find dfs superblock\n", __func__);
+ return ERR_PTR(-EINVAL);
+}
+
+static void __cifs_put_super(struct super_block *sb)
+{
+ if (!IS_ERR_OR_NULL(sb))
+ cifs_sb_deactive(sb);
+}
+
+struct super_block *cifs_get_dfs_tcon_super(struct cifs_tcon *tcon)
+{
+ spin_lock(&tcon->tc_lock);
+ if (!tcon->origin_fullpath) {
+ spin_unlock(&tcon->tc_lock);
+ return ERR_PTR(-ENOENT);
+ }
+ spin_unlock(&tcon->tc_lock);
+ return __cifs_get_super(tcon_super_cb, tcon);
+}
+
+void cifs_put_tcp_super(struct super_block *sb)
+{
+ __cifs_put_super(sb);
+}
+
+#ifdef CONFIG_CIFS_DFS_UPCALL
+int match_target_ip(struct TCP_Server_Info *server,
+ const char *host, size_t hostlen,
+ bool *result)
+{
+ struct sockaddr_storage ss;
+ int rc;
+
+ cifs_dbg(FYI, "%s: hostname=%.*s\n", __func__, (int)hostlen, host);
+
+ *result = false;
+
+ rc = dns_resolve_name(server->dns_dom, host, hostlen,
+ (struct sockaddr *)&ss);
+ if (rc < 0)
+ return rc;
+
+ spin_lock(&server->srv_lock);
+ *result = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
+ spin_unlock(&server->srv_lock);
+ cifs_dbg(FYI, "%s: ip addresses matched: %s\n", __func__, str_yes_no(*result));
+ return 0;
+}
+
+int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
+{
+ int rc;
+
+ kfree(cifs_sb->prepath);
+ cifs_sb->prepath = NULL;
+
+ if (prefix && *prefix) {
+ cifs_sb->prepath = cifs_sanitize_prepath(prefix, GFP_ATOMIC);
+ if (IS_ERR(cifs_sb->prepath)) {
+ rc = PTR_ERR(cifs_sb->prepath);
+ cifs_sb->prepath = NULL;
+ return rc;
+ }
+ if (cifs_sb->prepath)
+ convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
+ }
+
+ atomic_or(CIFS_MOUNT_USE_PREFIX_PATH, &cifs_sb->mnt_cifs_flags);
+ return 0;
+}
+
+/*
+ * Handle weird Windows SMB server behaviour. It responds with
+ * STATUS_OBJECT_NAME_INVALID code to SMB2 QUERY_INFO request for
+ * "\<server>\<dfsname>\<linkpath>" DFS reference, where <dfsname> contains
+ * non-ASCII unicode symbols.
+ */
+int cifs_inval_name_dfs_link_error(const unsigned int xid,
+ struct cifs_tcon *tcon,
+ struct cifs_sb_info *cifs_sb,
+ const char *full_path,
+ bool *islink)
+{
+ struct TCP_Server_Info *server = tcon->ses->server;
+ struct cifs_ses *ses = tcon->ses;
+ size_t len;
+ char *path;
+ char *ref_path;
+
+ *islink = false;
+
+ /*
+ * Fast path - skip check when @full_path doesn't have a prefix path to
+ * look up or tcon is not DFS.
+ */
+ if (strlen(full_path) < 2 || !cifs_sb ||
+ (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_NO_DFS) ||
+ !is_tcon_dfs(tcon))
+ return 0;
+
+ spin_lock(&server->srv_lock);
+ if (!server->leaf_fullpath) {
+ spin_unlock(&server->srv_lock);
+ return 0;
+ }
+ spin_unlock(&server->srv_lock);
+
+ /*
+ * Slow path - tcon is DFS and @full_path has prefix path, so attempt
+ * to get a referral to figure out whether it is an DFS link.
+ */
+ len = strnlen(tcon->tree_name, MAX_TREE_SIZE + 1) + strlen(full_path) + 1;
+ path = kmalloc(len, GFP_KERNEL);
+ if (!path)
+ return -ENOMEM;
+
+ scnprintf(path, len, "%s%s", tcon->tree_name, full_path);
+ ref_path = dfs_cache_canonical_path(path + 1, cifs_sb->local_nls,
+ cifs_remap(cifs_sb));
+ kfree(path);
+
+ if (IS_ERR(ref_path)) {
+ if (PTR_ERR(ref_path) != -EINVAL)
+ return PTR_ERR(ref_path);
+ } else {
+ struct dfs_info3_param *refs = NULL;
+ int num_refs = 0;
+
+ /*
+ * XXX: we are not using dfs_cache_find() here because we might
+ * end up filling all the DFS cache and thus potentially
+ * removing cached DFS targets that the client would eventually
+ * need during failover.
+ */
+ ses = CIFS_DFS_ROOT_SES(ses);
+ if (ses->server->ops->get_dfs_refer &&
+ !ses->server->ops->get_dfs_refer(xid, ses, ref_path, &refs,
+ &num_refs, cifs_sb->local_nls,
+ cifs_remap(cifs_sb)))
+ *islink = refs[0].server_type == DFS_TYPE_LINK;
+ free_dfs_info_array(refs, num_refs);
+ kfree(ref_path);
+ }
+ return 0;
+}
+#endif
+
+int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry)
+{
+ int timeout = 10;
+ int rc;
+
+ spin_lock(&server->srv_lock);
+ if (server->tcpStatus != CifsNeedReconnect) {
+ spin_unlock(&server->srv_lock);
+ return 0;
+ }
+ timeout *= server->nr_targets;
+ spin_unlock(&server->srv_lock);
+
+ /*
+ * Give demultiplex thread up to 10 seconds to each target available for
+ * reconnect -- should be greater than cifs socket timeout which is 7
+ * seconds.
+ *
+ * On "soft" mounts we wait once. Hard mounts keep retrying until
+ * process is killed or server comes back on-line.
+ */
+ do {
+ rc = wait_event_interruptible_timeout(server->response_q,
+ (server->tcpStatus != CifsNeedReconnect),
+ timeout * HZ);
+ if (rc < 0) {
+ cifs_dbg(FYI, "%s: aborting reconnect due to received signal\n",
+ __func__);
+ return -ERESTARTSYS;
+ }
+
+ /* are we still trying to reconnect? */
+ spin_lock(&server->srv_lock);
+ if (server->tcpStatus != CifsNeedReconnect) {
+ spin_unlock(&server->srv_lock);
+ return 0;
+ }
+ spin_unlock(&server->srv_lock);
+ } while (retry);
+
+ cifs_dbg(FYI, "%s: gave up waiting on reconnect\n", __func__);
+ return -EHOSTDOWN;
+}