2000-04-30 15:04:28 +04:00
/*
2002-01-30 09:08:46 +03:00
Unix SMB / CIFS implementation .
2000-04-30 15:04:28 +04:00
stat cache code
Copyright ( C ) Andrew Tridgell 1992 - 2000
2004-08-25 05:04:02 +04:00
Copyright ( C ) Jeremy Allison 1999 - 2004
2003-03-18 01:23:51 +03:00
Copyright ( C ) Andrew Bartlett < abartlet @ samba . org > 2003
2007-07-12 02:39:11 +04:00
Copyright ( C ) Volker Lendecke 2007
2000-04-30 15:04:28 +04:00
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
2007-07-09 23:25:36 +04:00
the Free Software Foundation ; either version 3 of the License , or
2000-04-30 15:04:28 +04:00
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
2007-07-10 04:52:41 +04:00
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2000-04-30 15:04:28 +04:00
*/
# include "includes.h"
/****************************************************************************
Stat cache code used in unix_convert .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2004-08-25 02:48:49 +04:00
static TDB_CONTEXT * tdb_stat_cache ;
2000-04-30 15:04:28 +04:00
2003-03-18 01:23:51 +03:00
/**
* Add an entry into the stat cache .
*
* @ param full_orig_name The original name as specified by the client
* @ param orig_translated_path The name on our filesystem .
*
* @ note Only the first strlen ( orig_translated_path ) characters are stored
* into the cache . This means that full_orig_name will be internally
* truncated .
*
*/
2000-04-30 15:04:28 +04:00
2007-07-12 02:39:11 +04:00
void stat_cache_add ( const char * full_orig_name , const char * translated_path , BOOL case_sensitive )
2000-04-30 15:04:28 +04:00
{
2003-07-02 04:08:29 +04:00
size_t translated_path_length ;
2004-08-25 02:48:49 +04:00
TDB_DATA data_val ;
2003-07-02 04:08:29 +04:00
char * original_path ;
size_t original_path_length ;
2005-04-08 00:59:37 +04:00
size_t sc_size = lp_max_stat_cache_size ( ) ;
2003-07-02 04:08:29 +04:00
if ( ! lp_stat_cache ( ) )
return ;
2006-07-11 22:01:26 +04:00
if ( sc_size & & ( tdb_map_size ( tdb_stat_cache ) > sc_size * 1024 ) ) {
2005-04-08 00:59:37 +04:00
reset_stat_cache ( ) ;
}
2004-08-25 02:48:49 +04:00
ZERO_STRUCT ( data_val ) ;
2003-07-02 04:08:29 +04:00
/*
2003-09-05 23:59:55 +04:00
* Don ' t cache trivial valid directory entries such as . and . .
2003-07-02 04:08:29 +04:00
*/
2007-07-12 02:39:11 +04:00
if ( ( * full_orig_name = = ' \0 ' )
| | ISDOT ( full_orig_name ) | | ISDOTDOT ( full_orig_name ) ) {
2003-07-02 04:08:29 +04:00
return ;
2007-07-12 02:39:11 +04:00
}
2003-07-02 04:08:29 +04:00
/*
* If we are in case insentive mode , we don ' t need to
* store names that need no translation - else , it
* would be a waste .
*/
2007-07-12 02:39:11 +04:00
if ( case_sensitive & & ( strcmp ( full_orig_name , translated_path ) = = 0 ) )
2003-07-02 04:08:29 +04:00
return ;
/*
* Remove any trailing ' / ' characters from the
* translated path .
*/
2007-07-12 02:39:11 +04:00
/*
* To save a strdup we don ' t necessarily 0 - terminate the translated
* path in the tdb . Instead , we do it directly after the tdb_fetch in
* stat_cache_lookup .
*/
2003-07-02 04:08:29 +04:00
translated_path_length = strlen ( translated_path ) ;
if ( translated_path [ translated_path_length - 1 ] = = ' / ' ) {
translated_path_length - - ;
}
2003-07-27 07:40:45 +04:00
if ( case_sensitive ) {
2004-12-07 21:25:53 +03:00
original_path = SMB_STRDUP ( full_orig_name ) ;
2003-07-27 07:40:45 +04:00
} else {
original_path = strdup_upper ( full_orig_name ) ;
}
2003-07-02 04:08:29 +04:00
if ( ! original_path ) {
return ;
}
original_path_length = strlen ( original_path ) ;
if ( original_path [ original_path_length - 1 ] = = ' / ' ) {
original_path [ original_path_length - 1 ] = ' \0 ' ;
original_path_length - - ;
}
if ( original_path_length ! = translated_path_length ) {
if ( original_path_length < translated_path_length ) {
2003-11-03 17:34:25 +03:00
DEBUG ( 0 , ( " OOPS - tried to store stat cache entry for weird length paths [%s] %lu and [%s] %lu)! \n " ,
original_path , ( unsigned long ) original_path_length , translated_path , ( unsigned long ) translated_path_length ) ) ;
2003-07-02 04:08:29 +04:00
SAFE_FREE ( original_path ) ;
return ;
}
2004-08-25 02:48:49 +04:00
/* we only want to index by the first part of original_path,
2003-07-02 04:08:29 +04:00
up to the length of translated_path */
original_path [ translated_path_length ] = ' \0 ' ;
original_path_length = translated_path_length ;
}
/*
2004-08-25 02:48:49 +04:00
* New entry or replace old entry .
2003-07-02 04:08:29 +04:00
*/
2003-03-18 01:23:51 +03:00
2004-08-25 02:48:49 +04:00
data_val . dsize = translated_path_length + 1 ;
2007-03-29 13:35:51 +04:00
data_val . dptr = ( uint8 * ) translated_path ;
2003-07-02 04:08:29 +04:00
2004-08-25 02:48:49 +04:00
if ( tdb_store_bystring ( tdb_stat_cache , original_path , data_val , TDB_REPLACE ) ! = 0 ) {
DEBUG ( 0 , ( " stat_cache_add: Error storing entry %s -> %s \n " , original_path , translated_path ) ) ;
} else {
2005-09-13 01:47:42 +04:00
DEBUG ( 5 , ( " stat_cache_add: Added entry (%lx:size%x) %s -> %s \n " ,
( unsigned long ) data_val . dptr , ( unsigned int ) data_val . dsize , original_path , translated_path ) ) ;
2004-08-25 02:48:49 +04:00
}
2003-07-02 04:08:29 +04:00
SAFE_FREE ( original_path ) ;
2000-04-30 15:04:28 +04:00
}
2003-03-18 01:23:51 +03:00
/**
* Look through the stat cache for an entry
*
* @ param conn A connection struct to do the stat ( ) with .
* @ param name The path we are attempting to cache , modified by this routine
2007-07-12 02:39:11 +04:00
* to be correct as far as the cache can tell us . We assume that
* it is a malloc ' ed string , we free it if necessary .
2003-03-18 01:23:51 +03:00
* @ param dirpath The path as far as the stat cache told us .
* @ param start A pointer into name , for where to ' start ' in fixing the rest of the name up .
* @ param psd A stat buffer , NOT from the cache , but just a side - effect .
*
* @ return True if we translated ( and did a scuccessful stat on ) the entire name .
*
*/
2000-04-30 15:04:28 +04:00
2007-07-12 02:39:11 +04:00
BOOL stat_cache_lookup ( connection_struct * conn , char * * pname , char * * dirpath ,
2000-04-30 15:04:28 +04:00
char * * start , SMB_STRUCT_STAT * pst )
{
2003-07-27 07:40:45 +04:00
char * chk_name ;
2003-07-02 04:08:29 +04:00
size_t namelen ;
2003-07-03 00:01:51 +04:00
BOOL sizechanged = False ;
unsigned int num_components = 0 ;
2007-07-07 13:57:27 +04:00
char * translated_path ;
size_t translated_path_length ;
TDB_DATA data_val ;
2007-07-12 02:39:11 +04:00
char * name ;
2003-07-02 04:08:29 +04:00
if ( ! lp_stat_cache ( ) )
return False ;
2000-04-30 15:04:28 +04:00
2007-07-12 02:39:11 +04:00
name = * pname ;
2003-07-02 04:08:29 +04:00
namelen = strlen ( name ) ;
* start = name ;
DO_PROFILE_INC ( statcache_lookups ) ;
/*
* Don ' t lookup trivial valid directory entries .
*/
2007-07-12 02:39:11 +04:00
if ( ( * name = = ' \0 ' ) | | ISDOT ( name ) | | ISDOTDOT ( name ) ) {
2003-07-02 04:08:29 +04:00
return False ;
2007-07-12 02:39:11 +04:00
}
2003-07-02 04:08:29 +04:00
2004-05-07 22:37:47 +04:00
if ( conn - > case_sensitive ) {
2004-12-07 21:25:53 +03:00
chk_name = SMB_STRDUP ( name ) ;
2003-07-27 07:40:45 +04:00
if ( ! chk_name ) {
DEBUG ( 0 , ( " stat_cache_lookup: strdup failed! \n " ) ) ;
return False ;
}
} else {
chk_name = strdup_upper ( name ) ;
if ( ! chk_name ) {
DEBUG ( 0 , ( " stat_cache_lookup: strdup_upper failed! \n " ) ) ;
return False ;
}
2003-07-03 00:01:51 +04:00
/*
* In some language encodings the length changes
* if we uppercase . We need to treat this differently
* below .
*/
if ( strlen ( chk_name ) ! = namelen )
sizechanged = True ;
}
2003-07-02 04:08:29 +04:00
while ( 1 ) {
2004-08-25 02:48:49 +04:00
char * sp ;
data_val = tdb_fetch_bystring ( tdb_stat_cache , chk_name ) ;
2007-07-07 13:57:27 +04:00
if ( data_val . dptr ! = NULL & & data_val . dsize ! = 0 ) {
break ;
}
DEBUG ( 10 , ( " stat_cache_lookup: lookup failed for name [%s] \n " , chk_name ) ) ;
/*
* Didn ' t find it - remove last component for next try .
*/
if ( ! ( sp = strrchr_m ( chk_name , ' / ' ) ) ) {
2003-07-02 04:08:29 +04:00
/*
2007-07-07 13:57:27 +04:00
* We reached the end of the name - no match .
2003-07-02 04:08:29 +04:00
*/
2007-07-07 13:57:27 +04:00
DO_PROFILE_INC ( statcache_misses ) ;
SAFE_FREE ( chk_name ) ;
return False ;
}
* sp = ' \0 ' ;
/*
* Count the number of times we have done this , we ' ll
* need it when reconstructing the string .
*/
if ( sizechanged )
num_components + + ;
if ( ( * chk_name = = ' \0 ' )
| | ISDOT ( chk_name ) | | ISDOTDOT ( chk_name ) ) {
DO_PROFILE_INC ( statcache_misses ) ;
2003-07-27 07:40:45 +04:00
SAFE_FREE ( chk_name ) ;
2007-07-07 13:57:27 +04:00
return False ;
}
}
translated_path = ( char * ) data_val . dptr ;
translated_path_length = data_val . dsize - 1 ;
2007-07-12 02:39:11 +04:00
/*
* In stat_cache_add we did not necessarily 0 - terminate the translated
* path . Do it here , where we do have a freshly malloc ' ed blob .
*/
translated_path [ translated_path_length ] = ' \0 ' ;
2007-07-07 13:57:27 +04:00
DEBUG ( 10 , ( " stat_cache_lookup: lookup succeeded for name [%s] "
" -> [%s] \n " , chk_name , translated_path ) ) ;
DO_PROFILE_INC ( statcache_hits ) ;
if ( SMB_VFS_STAT ( conn , translated_path , pst ) ! = 0 ) {
/* Discard this entry - it doesn't exist in the filesystem. */
tdb_delete_bystring ( tdb_stat_cache , chk_name ) ;
SAFE_FREE ( chk_name ) ;
SAFE_FREE ( data_val . dptr ) ;
return False ;
}
if ( ! sizechanged ) {
memcpy ( name , translated_path ,
2007-07-12 02:39:11 +04:00
MIN ( namelen , translated_path_length ) ) ;
}
else {
if ( num_components = = 0 ) {
name = SMB_STRNDUP ( translated_path ,
translated_path_length ) ;
2007-07-07 13:57:27 +04:00
} else {
2007-07-12 02:39:11 +04:00
char * sp ;
sp = strnrchr_m ( name , ' / ' , num_components ) ;
if ( sp ) {
asprintf ( & name , " %.*s%s " ,
( int ) translated_path_length ,
translated_path , sp ) ;
} else {
name = SMB_STRNDUP ( translated_path ,
translated_path_length ) ;
}
2003-07-02 04:08:29 +04:00
}
2007-07-12 02:39:11 +04:00
if ( name = = NULL ) {
/*
* TODO : Get us out of here with a real error message
*/
smb_panic ( " malloc failed " ) ;
}
SAFE_FREE ( * pname ) ;
* pname = name ;
2003-07-02 04:08:29 +04:00
}
2007-07-07 13:57:27 +04:00
2007-07-12 02:39:11 +04:00
2007-07-07 13:57:27 +04:00
/* set pointer for 'where to start' on fixing the rest of the name */
* start = & name [ translated_path_length ] ;
if ( * * start = = ' / ' )
+ + * start ;
2007-07-12 02:39:11 +04:00
* dirpath = translated_path ;
2007-07-07 13:57:27 +04:00
SAFE_FREE ( chk_name ) ;
return ( namelen = = translated_path_length ) ;
2000-04-30 15:04:28 +04:00
}
2007-01-20 00:46:12 +03:00
/***************************************************************************
Tell all smbd ' s to delete an entry .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void send_stat_cache_delete_message ( const char * name )
{
# ifdef DEVELOPER
2007-05-15 19:49:55 +04:00
message_send_all ( smbd_messaging_context ( ) ,
MSG_SMB_STAT_CACHE_DELETE ,
2007-01-20 00:46:12 +03:00
name ,
strlen ( name ) + 1 ,
NULL ) ;
# endif
}
/***************************************************************************
Delete an entry .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void stat_cache_delete ( const char * name )
{
char * lname = strdup_upper ( name ) ;
if ( ! lname ) {
return ;
}
DEBUG ( 10 , ( " stat_cache_delete: deleting name [%s] -> %s \n " ,
lname , name ) ) ;
tdb_delete_bystring ( tdb_stat_cache , lname ) ;
SAFE_FREE ( lname ) ;
}
2004-08-25 05:04:02 +04:00
/***************************************************************
Compute a hash value based on a string key value .
The function returns the bucket index number for the hashed key .
JRA . Use a djb - algorithm hash for speed .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2004-08-25 02:48:49 +04:00
2006-07-11 22:01:26 +04:00
unsigned int fast_string_hash ( TDB_DATA * key )
2004-08-25 02:48:49 +04:00
{
2006-07-11 22:01:26 +04:00
unsigned int n = 0 ;
2004-08-25 02:48:49 +04:00
const char * p ;
2007-03-29 13:35:51 +04:00
for ( p = ( const char * ) key - > dptr ; * p ! = ' \0 ' ; p + + ) {
2006-07-11 22:01:26 +04:00
n = ( ( n < < 5 ) + n ) ^ ( unsigned int ) ( * p ) ;
2004-08-25 02:48:49 +04:00
}
return n ;
}
2004-08-25 05:04:02 +04:00
/***************************************************************************
Initializes or clears the stat cache .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2000-04-30 15:04:28 +04:00
BOOL reset_stat_cache ( void )
{
2003-07-02 04:08:29 +04:00
if ( ! lp_stat_cache ( ) )
return True ;
2000-05-04 10:28:38 +04:00
2004-08-25 02:48:49 +04:00
if ( tdb_stat_cache ) {
tdb_close ( tdb_stat_cache ) ;
2000-05-02 11:10:54 +04:00
}
2000-10-05 02:37:33 +04:00
2004-08-25 05:04:02 +04:00
/* Create the in-memory tdb using our custom hash function. */
2004-08-31 02:25:36 +04:00
tdb_stat_cache = tdb_open_ex ( " statcache " , 1031 , TDB_INTERNAL ,
2004-08-31 00:19:40 +04:00
( O_RDWR | O_CREAT ) , 0644 , NULL , fast_string_hash ) ;
2004-08-25 05:04:02 +04:00
2004-08-25 02:48:49 +04:00
if ( ! tdb_stat_cache )
return False ;
return True ;
2003-07-02 04:08:29 +04:00
}