2022-08-10 22:00:08 -05:00
// SPDX-License-Identifier: GPL-2.0
/*
* Functions to handle the cached directory entries
*
* Copyright ( c ) 2022 , Ronnie Sahlberg < lsahlber @ redhat . com >
*/
2022-10-12 06:13:03 -05:00
# include <linux/namei.h>
2022-08-10 22:00:08 -05:00
# include "cifsglob.h"
# include "cifsproto.h"
# include "cifs_debug.h"
# include "smb2proto.h"
# include "cached_dir.h"
2022-10-06 00:14:31 -05:00
static struct cached_fid * init_cached_dir ( const char * path ) ;
static void free_cached_dir ( struct cached_fid * cfid ) ;
static struct cached_fid * find_or_create_cached_dir ( struct cached_fids * cfids ,
const char * path ,
bool lookup_only )
{
struct cached_fid * cfid ;
spin_lock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry ( cfid , & cfids - > entries , entry ) {
if ( ! strcmp ( cfid - > path , path ) ) {
/*
* If it doesn ' t have a lease it is either not yet
* fully cached or it may be in the process of
* being deleted due to a lease break .
*/
if ( ! cfid - > has_lease ) {
spin_unlock ( & cfids - > cfid_list_lock ) ;
return NULL ;
}
kref_get ( & cfid - > refcount ) ;
spin_unlock ( & cfids - > cfid_list_lock ) ;
return cfid ;
}
}
if ( lookup_only ) {
spin_unlock ( & cfids - > cfid_list_lock ) ;
return NULL ;
}
if ( cfids - > num_entries > = MAX_CACHED_FIDS ) {
spin_unlock ( & cfids - > cfid_list_lock ) ;
return NULL ;
}
cfid = init_cached_dir ( path ) ;
if ( cfid = = NULL ) {
spin_unlock ( & cfids - > cfid_list_lock ) ;
return NULL ;
}
cfid - > cfids = cfids ;
cfids - > num_entries + + ;
list_add ( & cfid - > entry , & cfids - > entries ) ;
cfid - > on_list = true ;
kref_get ( & cfid - > refcount ) ;
spin_unlock ( & cfids - > cfid_list_lock ) ;
return cfid ;
}
2022-08-31 12:49:44 +10:00
2022-10-12 06:13:03 -05:00
static struct dentry *
path_to_dentry ( struct cifs_sb_info * cifs_sb , const char * path )
{
struct dentry * dentry ;
const char * s , * p ;
char sep ;
sep = CIFS_DIR_SEP ( cifs_sb ) ;
dentry = dget ( cifs_sb - > root ) ;
s = path ;
do {
struct inode * dir = d_inode ( dentry ) ;
struct dentry * child ;
if ( ! S_ISDIR ( dir - > i_mode ) ) {
dput ( dentry ) ;
dentry = ERR_PTR ( - ENOTDIR ) ;
break ;
}
/* skip separators */
while ( * s = = sep )
s + + ;
if ( ! * s )
break ;
p = s + + ;
/* next separator */
while ( * s & & * s ! = sep )
s + + ;
child = lookup_positive_unlocked ( p , dentry , s - p ) ;
dput ( dentry ) ;
dentry = child ;
} while ( ! IS_ERR ( dentry ) ) ;
return dentry ;
}
2022-08-10 22:00:08 -05:00
/*
* Open the and cache a directory handle .
* If error then * cfid is not initialized .
*/
int open_cached_dir ( unsigned int xid , struct cifs_tcon * tcon ,
2022-08-11 19:51:18 -05:00
const char * path ,
struct cifs_sb_info * cifs_sb ,
bool lookup_only , struct cached_fid * * ret_cfid )
2022-08-10 22:00:08 -05:00
{
struct cifs_ses * ses ;
struct TCP_Server_Info * server ;
struct cifs_open_parms oparms ;
struct smb2_create_rsp * o_rsp = NULL ;
struct smb2_query_info_rsp * qi_rsp = NULL ;
int resp_buftype [ 2 ] ;
struct smb_rqst rqst [ 2 ] ;
struct kvec rsp_iov [ 2 ] ;
struct kvec open_iov [ SMB2_CREATE_IOV_SIZE ] ;
struct kvec qi_iov [ 1 ] ;
int rc , flags = 0 ;
2022-10-06 00:14:31 -05:00
__le16 * utf16_path = NULL ;
2022-08-10 22:00:08 -05:00
u8 oplock = SMB2_OPLOCK_LEVEL_II ;
struct cifs_fid * pfid ;
2022-10-06 00:14:31 -05:00
struct dentry * dentry = NULL ;
2022-08-11 19:04:29 -05:00
struct cached_fid * cfid ;
2022-10-06 00:14:31 -05:00
struct cached_fids * cfids ;
2022-08-10 22:00:08 -05:00
2022-10-06 00:14:31 -05:00
if ( tcon = = NULL | | tcon - > cfids = = NULL | | tcon - > nohandlecache | |
2022-08-10 22:00:08 -05:00
is_smb1_server ( tcon - > ses - > server ) )
return - EOPNOTSUPP ;
ses = tcon - > ses ;
server = ses - > server ;
2022-10-06 00:14:31 -05:00
cfids = tcon - > cfids ;
if ( ! server - > ops - > new_lease_key )
return - EIO ;
2022-08-10 22:00:08 -05:00
if ( cifs_sb - > root = = NULL )
return - ENOENT ;
2022-10-06 00:14:31 -05:00
utf16_path = cifs_convert_path_to_utf16 ( path , cifs_sb ) ;
if ( ! utf16_path )
2022-08-31 12:49:44 +10:00
return - ENOMEM ;
2022-10-06 00:14:31 -05:00
cfid = find_or_create_cached_dir ( cfids , path , lookup_only ) ;
if ( cfid = = NULL ) {
kfree ( utf16_path ) ;
return - ENOENT ;
}
/*
* At this point we either have a lease already and we can just
* return it . If not we are guaranteed to be the only thread accessing
* this cfid .
*/
if ( cfid - > has_lease ) {
2022-08-11 19:04:29 -05:00
* ret_cfid = cfid ;
2022-10-06 00:14:31 -05:00
kfree ( utf16_path ) ;
2022-08-10 22:00:08 -05:00
return 0 ;
}
/*
* We do not hold the lock for the open because in case
2022-10-06 00:14:31 -05:00
* SMB2_open needs to reconnect .
* This is safe because no other thread will be able to get a ref
* to the cfid until we have finished opening the file and ( possibly )
* acquired a lease .
2022-08-10 22:00:08 -05:00
*/
if ( smb3_encryption_required ( tcon ) )
flags | = CIFS_TRANSFORM_REQ ;
2022-08-11 19:04:29 -05:00
pfid = & cfid - > fid ;
2022-08-10 22:00:08 -05:00
server - > ops - > new_lease_key ( pfid ) ;
memset ( rqst , 0 , sizeof ( rqst ) ) ;
resp_buftype [ 0 ] = resp_buftype [ 1 ] = CIFS_NO_BUFFER ;
memset ( rsp_iov , 0 , sizeof ( rsp_iov ) ) ;
/* Open */
memset ( & open_iov , 0 , sizeof ( open_iov ) ) ;
rqst [ 0 ] . rq_iov = open_iov ;
rqst [ 0 ] . rq_nvec = SMB2_CREATE_IOV_SIZE ;
oparms . tcon = tcon ;
oparms . create_options = cifs_create_options ( cifs_sb , CREATE_NOT_FILE ) ;
oparms . desired_access = FILE_READ_ATTRIBUTES ;
oparms . disposition = FILE_OPEN ;
oparms . fid = pfid ;
oparms . reconnect = false ;
rc = SMB2_open_init ( tcon , server ,
2022-10-06 00:14:31 -05:00
& rqst [ 0 ] , & oplock , & oparms , utf16_path ) ;
2022-08-10 22:00:08 -05:00
if ( rc )
goto oshr_free ;
smb2_set_next_command ( tcon , & rqst [ 0 ] ) ;
memset ( & qi_iov , 0 , sizeof ( qi_iov ) ) ;
rqst [ 1 ] . rq_iov = qi_iov ;
rqst [ 1 ] . rq_nvec = 1 ;
rc = SMB2_query_info_init ( tcon , server ,
& rqst [ 1 ] , COMPOUND_FID ,
COMPOUND_FID , FILE_ALL_INFORMATION ,
SMB2_O_INFO_FILE , 0 ,
sizeof ( struct smb2_file_all_info ) +
PATH_MAX * 2 , 0 , NULL ) ;
if ( rc )
goto oshr_free ;
smb2_set_related ( & rqst [ 1 ] ) ;
rc = compound_send_recv ( xid , ses , server ,
flags , 2 , rqst ,
resp_buftype , rsp_iov ) ;
if ( rc ) {
if ( rc = = - EREMCHG ) {
tcon - > need_reconnect = true ;
pr_warn_once ( " server share %s deleted \n " ,
2022-09-21 14:05:53 -05:00
tcon - > tree_name ) ;
2022-08-10 22:00:08 -05:00
}
2022-10-06 00:14:31 -05:00
goto oshr_free ;
2022-08-10 22:00:08 -05:00
}
atomic_inc ( & tcon - > num_remote_opens ) ;
o_rsp = ( struct smb2_create_rsp * ) rsp_iov [ 0 ] . iov_base ;
oparms . fid - > persistent_fid = o_rsp - > PersistentFileId ;
oparms . fid - > volatile_fid = o_rsp - > VolatileFileId ;
# ifdef CONFIG_CIFS_DEBUG2
oparms . fid - > mid = le64_to_cpu ( o_rsp - > hdr . MessageId ) ;
# endif /* CIFS_DEBUG2 */
2022-10-06 00:14:31 -05:00
if ( o_rsp - > OplockLevel ! = SMB2_OPLOCK_LEVEL_LEASE )
goto oshr_free ;
smb2_parse_contexts ( server , o_rsp ,
& oparms . fid - > epoch ,
oparms . fid - > lease_key , & oplock ,
NULL , NULL ) ;
2022-08-10 22:00:08 -05:00
qi_rsp = ( struct smb2_query_info_rsp * ) rsp_iov [ 1 ] . iov_base ;
if ( le32_to_cpu ( qi_rsp - > OutputBufferLength ) < sizeof ( struct smb2_file_all_info ) )
2022-10-06 00:14:31 -05:00
goto oshr_free ;
2022-08-10 22:00:08 -05:00
if ( ! smb2_validate_and_copy_iov (
le16_to_cpu ( qi_rsp - > OutputBufferOffset ) ,
sizeof ( struct smb2_file_all_info ) ,
& rsp_iov [ 1 ] , sizeof ( struct smb2_file_all_info ) ,
2022-08-11 19:04:29 -05:00
( char * ) & cfid - > file_all_info ) )
cfid - > file_all_info_is_valid = true ;
2022-10-12 06:13:03 -05:00
if ( ! path [ 0 ] )
dentry = dget ( cifs_sb - > root ) ;
else {
dentry = path_to_dentry ( cifs_sb , path ) ;
if ( IS_ERR ( dentry ) )
goto oshr_free ;
}
cfid - > dentry = dentry ;
cfid - > tcon = tcon ;
2022-08-11 19:04:29 -05:00
cfid - > time = jiffies ;
2022-10-06 00:14:31 -05:00
cfid - > is_open = true ;
cfid - > has_lease = true ;
2022-08-10 22:00:08 -05:00
oshr_free :
2022-10-06 00:14:31 -05:00
kfree ( utf16_path ) ;
2022-08-10 22:00:08 -05:00
SMB2_open_free ( & rqst [ 0 ] ) ;
SMB2_query_info_free ( & rqst [ 1 ] ) ;
free_rsp_buf ( resp_buftype [ 0 ] , rsp_iov [ 0 ] . iov_base ) ;
free_rsp_buf ( resp_buftype [ 1 ] , rsp_iov [ 1 ] . iov_base ) ;
2022-10-06 00:14:31 -05:00
spin_lock ( & cfids - > cfid_list_lock ) ;
if ( ! cfid - > has_lease ) {
if ( cfid - > on_list ) {
list_del ( & cfid - > entry ) ;
cfid - > on_list = false ;
cfids - > num_entries - - ;
}
rc = - ENOENT ;
}
spin_unlock ( & cfids - > cfid_list_lock ) ;
if ( rc ) {
free_cached_dir ( cfid ) ;
cfid = NULL ;
}
2022-08-10 22:00:08 -05:00
if ( rc = = 0 )
2022-08-11 19:04:29 -05:00
* ret_cfid = cfid ;
2022-08-10 22:00:08 -05:00
return rc ;
}
int open_cached_dir_by_dentry ( struct cifs_tcon * tcon ,
struct dentry * dentry ,
2022-08-11 19:04:29 -05:00
struct cached_fid * * ret_cfid )
2022-08-10 22:00:08 -05:00
{
2022-08-11 19:04:29 -05:00
struct cached_fid * cfid ;
2022-10-06 00:14:31 -05:00
struct cached_fids * cfids = tcon - > cfids ;
2022-08-11 19:04:29 -05:00
2022-10-06 00:14:31 -05:00
if ( cfids = = NULL )
2022-08-31 12:49:44 +10:00
return - ENOENT ;
2022-08-11 19:04:29 -05:00
2022-10-06 00:14:31 -05:00
spin_lock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry ( cfid , & cfids - > entries , entry ) {
if ( dentry & & cfid - > dentry = = dentry ) {
cifs_dbg ( FYI , " found a cached root file handle by dentry \n " ) ;
kref_get ( & cfid - > refcount ) ;
* ret_cfid = cfid ;
spin_unlock ( & cfids - > cfid_list_lock ) ;
return 0 ;
}
2022-08-10 22:00:08 -05:00
}
2022-10-06 00:14:31 -05:00
spin_unlock ( & cfids - > cfid_list_lock ) ;
2022-08-10 22:00:08 -05:00
return - ENOENT ;
}
static void
smb2_close_cached_fid ( struct kref * ref )
{
struct cached_fid * cfid = container_of ( ref , struct cached_fid ,
refcount ) ;
2022-10-06 00:14:31 -05:00
spin_lock ( & cfid - > cfids - > cfid_list_lock ) ;
if ( cfid - > on_list ) {
list_del ( & cfid - > entry ) ;
cfid - > on_list = false ;
cfid - > cfids - > num_entries - - ;
2022-08-10 22:00:08 -05:00
}
2022-10-06 00:14:31 -05:00
spin_unlock ( & cfid - > cfids - > cfid_list_lock ) ;
2022-08-10 22:00:08 -05:00
2022-10-06 00:14:31 -05:00
dput ( cfid - > dentry ) ;
cfid - > dentry = NULL ;
if ( cfid - > is_open ) {
SMB2_close ( 0 , cfid - > tcon , cfid - > fid . persistent_fid ,
cfid - > fid . volatile_fid ) ;
2022-08-10 22:00:08 -05:00
}
2022-10-06 00:14:31 -05:00
free_cached_dir ( cfid ) ;
2022-08-10 22:00:08 -05:00
}
void close_cached_dir ( struct cached_fid * cfid )
{
kref_put ( & cfid - > refcount , smb2_close_cached_fid ) ;
}
/*
* Called from cifs_kill_sb when we unmount a share
*/
void close_all_cached_dirs ( struct cifs_sb_info * cifs_sb )
{
struct rb_root * root = & cifs_sb - > tlink_tree ;
struct rb_node * node ;
struct cached_fid * cfid ;
struct cifs_tcon * tcon ;
struct tcon_link * tlink ;
2022-10-06 00:14:31 -05:00
struct cached_fids * cfids ;
2022-08-10 22:00:08 -05:00
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 ;
2022-10-06 00:14:31 -05:00
cfids = tcon - > cfids ;
if ( cfids = = NULL )
2022-08-31 12:49:44 +10:00
continue ;
2022-10-06 00:14:31 -05:00
list_for_each_entry ( cfid , & cfids - > entries , entry ) {
2022-08-10 22:00:08 -05:00
dput ( cfid - > dentry ) ;
cfid - > dentry = NULL ;
}
}
}
/*
2022-10-06 00:14:31 -05:00
* Invalidate all cached dirs when a TCON has been reset
2022-08-10 22:00:08 -05:00
* due to a session loss .
*/
void invalidate_all_cached_dirs ( struct cifs_tcon * tcon )
{
2022-10-06 00:14:31 -05:00
struct cached_fids * cfids = tcon - > cfids ;
struct cached_fid * cfid , * q ;
struct list_head entry ;
INIT_LIST_HEAD ( & entry ) ;
spin_lock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry_safe ( cfid , q , & cfids - > entries , entry ) {
list_del ( & cfid - > entry ) ;
list_add ( & cfid - > entry , & entry ) ;
cfids - > num_entries - - ;
cfid - > is_open = false ;
/* To prevent race with smb2_cached_lease_break() */
kref_get ( & cfid - > refcount ) ;
}
spin_unlock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry_safe ( cfid , q , & entry , entry ) {
cfid - > on_list = false ;
list_del ( & cfid - > entry ) ;
cancel_work_sync ( & cfid - > lease_break ) ;
if ( cfid - > has_lease ) {
/*
* We lease was never cancelled from the server so we
* need to drop the reference .
*/
spin_lock ( & cfids - > cfid_list_lock ) ;
cfid - > has_lease = false ;
spin_unlock ( & cfids - > cfid_list_lock ) ;
kref_put ( & cfid - > refcount , smb2_close_cached_fid ) ;
}
/* Drop the extra reference opened above*/
kref_put ( & cfid - > refcount , smb2_close_cached_fid ) ;
}
2022-08-10 22:00:08 -05:00
}
static void
smb2_cached_lease_break ( struct work_struct * work )
{
struct cached_fid * cfid = container_of ( work ,
struct cached_fid , lease_break ) ;
2022-10-06 00:14:31 -05:00
spin_lock ( & cfid - > cfids - > cfid_list_lock ) ;
cfid - > has_lease = false ;
spin_unlock ( & cfid - > cfids - > cfid_list_lock ) ;
kref_put ( & cfid - > refcount , smb2_close_cached_fid ) ;
2022-08-10 22:00:08 -05:00
}
int cached_dir_lease_break ( struct cifs_tcon * tcon , __u8 lease_key [ 16 ] )
{
2022-10-06 00:14:31 -05:00
struct cached_fids * cfids = tcon - > cfids ;
struct cached_fid * cfid ;
2022-08-31 12:49:44 +10:00
2022-10-06 00:14:31 -05:00
if ( cfids = = NULL )
2022-08-31 12:49:44 +10:00
return false ;
2022-08-31 12:49:42 +10:00
2022-10-06 00:14:31 -05:00
spin_lock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry ( cfid , & cfids - > entries , entry ) {
if ( cfid - > has_lease & &
! memcmp ( lease_key ,
cfid - > fid . lease_key ,
SMB2_LEASE_KEY_SIZE ) ) {
cfid - > time = 0 ;
/*
* We found a lease remove it from the list
* so no threads can access it .
*/
list_del ( & cfid - > entry ) ;
cfid - > on_list = false ;
cfids - > num_entries - - ;
queue_work ( cifsiod_wq ,
& cfid - > lease_break ) ;
spin_unlock ( & cfids - > cfid_list_lock ) ;
return true ;
}
2022-08-10 22:00:08 -05:00
}
2022-10-06 00:14:31 -05:00
spin_unlock ( & cfids - > cfid_list_lock ) ;
2022-08-10 22:00:08 -05:00
return false ;
}
2022-08-11 19:04:29 -05:00
2022-10-06 00:14:31 -05:00
static struct cached_fid * init_cached_dir ( const char * path )
2022-08-31 12:49:44 +10:00
{
struct cached_fid * cfid ;
2022-10-06 00:14:31 -05:00
cfid = kzalloc ( sizeof ( * cfid ) , GFP_ATOMIC ) ;
2022-08-31 12:49:44 +10:00
if ( ! cfid )
return NULL ;
2022-10-06 00:14:31 -05:00
cfid - > path = kstrdup ( path , GFP_ATOMIC ) ;
2022-08-31 12:49:44 +10:00
if ( ! cfid - > path ) {
kfree ( cfid ) ;
return NULL ;
}
2022-10-06 00:14:31 -05:00
INIT_WORK ( & cfid - > lease_break , smb2_cached_lease_break ) ;
INIT_LIST_HEAD ( & cfid - > entry ) ;
2022-08-31 12:49:44 +10:00
INIT_LIST_HEAD ( & cfid - > dirents . entries ) ;
mutex_init ( & cfid - > dirents . de_mutex ) ;
2022-10-06 00:14:31 -05:00
spin_lock_init ( & cfid - > fid_lock ) ;
kref_init ( & cfid - > refcount ) ;
2022-08-31 12:49:44 +10:00
return cfid ;
}
2022-10-06 00:14:31 -05:00
static void free_cached_dir ( struct cached_fid * cfid )
2022-08-31 12:49:44 +10:00
{
2022-10-06 00:14:31 -05:00
struct cached_dirent * dirent , * q ;
dput ( cfid - > dentry ) ;
cfid - > dentry = NULL ;
/*
* Delete all cached dirent names
*/
list_for_each_entry_safe ( dirent , q , & cfid - > dirents . entries , entry ) {
list_del ( & dirent - > entry ) ;
kfree ( dirent - > name ) ;
kfree ( dirent ) ;
}
2022-08-31 12:49:44 +10:00
kfree ( cfid - > path ) ;
cfid - > path = NULL ;
kfree ( cfid ) ;
}
2022-08-31 12:49:42 +10:00
struct cached_fids * init_cached_dirs ( void )
2022-08-11 19:04:29 -05:00
{
2022-08-31 12:49:42 +10:00
struct cached_fids * cfids ;
2022-08-11 19:04:29 -05:00
2022-08-31 12:49:42 +10:00
cfids = kzalloc ( sizeof ( * cfids ) , GFP_KERNEL ) ;
if ( ! cfids )
2022-08-11 19:04:29 -05:00
return NULL ;
2022-10-06 00:14:31 -05:00
spin_lock_init ( & cfids - > cfid_list_lock ) ;
INIT_LIST_HEAD ( & cfids - > entries ) ;
2022-08-31 12:49:42 +10:00
return cfids ;
2022-08-11 19:04:29 -05:00
}
2022-10-06 00:14:31 -05:00
/*
* Called from tconInfoFree when we are tearing down the tcon .
* There are no active users or open files / directories at this point .
*/
2022-08-31 12:49:42 +10:00
void free_cached_dirs ( struct cached_fids * cfids )
2022-08-11 19:04:29 -05:00
{
2022-10-06 00:14:31 -05:00
struct cached_fid * cfid , * q ;
struct list_head entry ;
INIT_LIST_HEAD ( & entry ) ;
spin_lock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry_safe ( cfid , q , & cfids - > entries , entry ) {
cfid - > on_list = false ;
cfid - > is_open = false ;
list_del ( & cfid - > entry ) ;
list_add ( & cfid - > entry , & entry ) ;
2022-08-31 12:49:44 +10:00
}
2022-10-06 00:14:31 -05:00
spin_unlock ( & cfids - > cfid_list_lock ) ;
list_for_each_entry_safe ( cfid , q , & entry , entry ) {
list_del ( & cfid - > entry ) ;
free_cached_dir ( cfid ) ;
}
2022-08-31 12:49:42 +10:00
kfree ( cfids ) ;
2022-08-11 19:04:29 -05:00
}