76406f208e52209411bea901fd1ba91712c6bb0d
[samba.git] / source3 / smbd / statcache.c
1 /* 
2    Unix SMB/CIFS implementation.
3    stat cache code
4    Copyright (C) Andrew Tridgell 1992-2000
5    Copyright (C) Jeremy Allison 1999-2000
6    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
7    
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12    
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17    
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24
25 extern BOOL case_sensitive;
26
27 /****************************************************************************
28  Stat cache code used in unix_convert.
29 *****************************************************************************/
30
31 typedef struct {
32         char *original_path;
33         char *translated_path;
34         size_t translated_path_length;
35         char names[2]; /* This is extended via malloc... */
36 } stat_cache_entry;
37
38 #define INIT_STAT_CACHE_SIZE 512
39 static hash_table stat_cache;
40
41 /**
42  * Add an entry into the stat cache.
43  *
44  * @param full_orig_name       The original name as specified by the client
45  * @param orig_translated_path The name on our filesystem.
46  * 
47  * @note Only the first strlen(orig_translated_path) characters are stored 
48  *       into the cache.  This means that full_orig_name will be internally
49  *       truncated.
50  *
51  */
52
53 void stat_cache_add( const char *full_orig_name, const char *orig_translated_path)
54 {
55         stat_cache_entry *scp;
56         stat_cache_entry *found_scp;
57         char *translated_path;
58         size_t translated_path_length;
59
60         char *original_path;
61         size_t original_path_length;
62
63         hash_element *hash_elem;
64
65         if (!lp_stat_cache())
66                 return;
67
68         /*
69          * Don't cache trivial valid directory entries such as . and ..
70          */
71
72         if((*full_orig_name == '\0') || (full_orig_name[0] == '.' && 
73                                 ((full_orig_name[1] == '\0') ||
74                                  (full_orig_name[1] == '.' && full_orig_name[1] == '\0'))))
75                 return;
76
77         /*
78          * If we are in case insentive mode, we don't need to
79          * store names that need no translation - else, it
80          * would be a waste.
81          */
82
83         if(case_sensitive && (strcmp(full_orig_name, orig_translated_path) == 0))
84                 return;
85
86         /*
87          * Remove any trailing '/' characters from the
88          * translated path.
89          */
90
91         translated_path = strdup(orig_translated_path);
92         if (!translated_path)
93                 return;
94
95         translated_path_length = strlen(translated_path);
96
97         if(translated_path[translated_path_length-1] == '/') {
98                 translated_path[translated_path_length-1] = '\0';
99                 translated_path_length--;
100         }
101
102         if(case_sensitive) {
103                 original_path = strdup(full_orig_name);
104         } else {
105                 original_path = strdup_upper(full_orig_name);
106         }
107
108         if (!original_path) {
109                 SAFE_FREE(translated_path);
110                 return;
111         }
112
113         original_path_length = strlen(original_path);
114
115         if(original_path[original_path_length-1] == '/') {
116                 original_path[original_path_length-1] = '\0';
117                 original_path_length--;
118         }
119
120         if (original_path_length != translated_path_length) {
121                 if (original_path_length < translated_path_length) {
122                         DEBUG(0, ("OOPS - tried to store stat cache entry for weird length paths [%s] %u and [%s] %u)!\n",
123                                                 original_path, original_path_length, translated_path, translated_path_length));
124                         SAFE_FREE(original_path);
125                         SAFE_FREE(translated_path);
126                         return;
127                 }
128
129                 /* we only want to store the first part of original_path,
130                         up to the length of translated_path */
131
132                 original_path[translated_path_length] = '\0';
133                 original_path_length = translated_path_length;
134         }
135
136         /*
137          * Check this name doesn't exist in the cache before we 
138          * add it.
139          */
140
141         if ((hash_elem = hash_lookup(&stat_cache, original_path))) {
142                 found_scp = (stat_cache_entry *)(hash_elem->value);
143                 if (strcmp((found_scp->translated_path), orig_translated_path) == 0) {
144                         /* already in hash table */
145                         SAFE_FREE(original_path);
146                         SAFE_FREE(translated_path);
147                         return;
148                 }
149                 /* hash collision - remove before we re-add */
150                 hash_remove(&stat_cache, hash_elem);
151         }  
152   
153         /*
154          * New entry.
155          */
156   
157         if((scp = (stat_cache_entry *)malloc(sizeof(stat_cache_entry)
158                                                 +original_path_length
159                                                 +translated_path_length)) == NULL) {
160                 DEBUG(0,("stat_cache_add: Out of memory !\n"));
161                 SAFE_FREE(original_path);
162                 SAFE_FREE(translated_path);
163                 return;
164         }
165
166         scp->original_path = scp->names;
167         /* pointer into the structure... */
168         scp->translated_path = scp->names + original_path_length + 1;
169         safe_strcpy(scp->original_path, original_path, original_path_length);
170         safe_strcpy(scp->translated_path, translated_path, translated_path_length);
171         scp->translated_path_length = translated_path_length;
172
173         hash_insert(&stat_cache, (char *)scp, original_path);
174
175         SAFE_FREE(original_path);
176         SAFE_FREE(translated_path);
177
178         DEBUG(5,("stat_cache_add: Added entry %s -> %s\n", scp->original_path, scp->translated_path));
179 }
180
181 /**
182  * Look through the stat cache for an entry
183  *
184  * The hash-table's internals will promote it to the top if found.
185  *
186  * @param conn    A connection struct to do the stat() with.
187  * @param name    The path we are attempting to cache, modified by this routine
188  *                to be correct as far as the cache can tell us
189  * @param dirpath The path as far as the stat cache told us.
190  * @param start   A pointer into name, for where to 'start' in fixing the rest of the name up.
191  * @param psd     A stat buffer, NOT from the cache, but just a side-effect.
192  *
193  * @return True if we translated (and did a scuccessful stat on) the entire name.
194  *
195  */
196
197 BOOL stat_cache_lookup(connection_struct *conn, pstring name, pstring dirpath, 
198                        char **start, SMB_STRUCT_STAT *pst)
199 {
200         stat_cache_entry *scp;
201         char *chk_name;
202         size_t namelen;
203         hash_element *hash_elem;
204         char *sp;
205         BOOL sizechanged = False;
206         unsigned int num_components = 0;
207
208         if (!lp_stat_cache())
209                 return False;
210  
211         namelen = strlen(name);
212
213         *start = name;
214
215         DO_PROFILE_INC(statcache_lookups);
216
217         /*
218          * Don't lookup trivial valid directory entries.
219          */
220         if((*name == '\0') || (name[0] == '.' && 
221                                 ((name[1] == '\0') ||
222                                  (name[1] == '.' && name[1] == '\0'))))
223                 return False;
224
225         if (case_sensitive) {
226                 chk_name = strdup(name);
227                 if (!chk_name) {
228                         DEBUG(0, ("stat_cache_lookup: strdup failed!\n"));
229                         return False;
230                 }
231
232         } else {
233                 chk_name = strdup_upper(name);
234                 if (!chk_name) {
235                         DEBUG(0, ("stat_cache_lookup: strdup_upper failed!\n"));
236                         return False;
237                 }
238
239                 /*
240                  * In some language encodings the length changes
241                  * if we uppercase. We need to treat this differently
242                  * below.
243                  */
244                 if (strlen(chk_name) != namelen)
245                         sizechanged = True;
246         }
247
248         while (1) {
249                 hash_elem = hash_lookup(&stat_cache, chk_name);
250                 if(hash_elem == NULL) {
251                         DEBUG(10,("stat_cache_lookup: lookup failed for name [%s]\n", chk_name ));
252                         /*
253                          * Didn't find it - remove last component for next try.
254                          */
255                         sp = strrchr_m(chk_name, '/');
256                         if (sp) {
257                                 *sp = '\0';
258                                 /*
259                                  * Count the number of times we have done this,
260                                  * we'll need it when reconstructing the string.
261                                  */
262                                 if (sizechanged)
263                                         num_components++;
264
265                         } else {
266                                 /*
267                                  * We reached the end of the name - no match.
268                                  */
269                                 DO_PROFILE_INC(statcache_misses);
270                                 SAFE_FREE(chk_name);
271                                 return False;
272                         }
273                         if((*chk_name == '\0') || (strcmp(chk_name, ".") == 0)
274                                         || (strcmp(chk_name, "..") == 0)) {
275                                 DO_PROFILE_INC(statcache_misses);
276                                 SAFE_FREE(chk_name);
277                                 return False;
278                         }
279                 } else {
280                         scp = (stat_cache_entry *)(hash_elem->value);
281                         DEBUG(10,("stat_cache_lookup: lookup succeeded for name [%s] -> [%s]\n", chk_name, scp->translated_path ));
282                         DO_PROFILE_INC(statcache_hits);
283                         if(SMB_VFS_STAT(conn,scp->translated_path, pst) != 0) {
284                                 /* Discard this entry - it doesn't exist in the filesystem.  */
285                                 hash_remove(&stat_cache, hash_elem);
286                                 SAFE_FREE(chk_name);
287                                 return False;
288                         }
289
290                         if (!sizechanged) {
291                                 memcpy(name, scp->translated_path, MIN(sizeof(pstring)-1, scp->translated_path_length));
292                         } else if (num_components == 0) {
293                                 pstrcpy(name, scp->translated_path);
294                         } else {
295                                 sp = strnrchr_m(name, '/', num_components);
296                                 if (sp) {
297                                         pstring last_component;
298                                         pstrcpy(last_component, sp);
299                                         pstrcpy(name, scp->translated_path);
300                                         pstrcat(name, last_component);
301                                 } else {
302                                         pstrcpy(name, scp->translated_path);
303                                 }
304                         }
305
306                         /* set pointer for 'where to start' on fixing the rest of the name */
307                         *start = &name[scp->translated_path_length];
308                         if(**start == '/')
309                                 ++*start;
310
311                         pstrcpy(dirpath, scp->translated_path);
312                         SAFE_FREE(chk_name);
313                         return (namelen == scp->translated_path_length);
314                 }
315         }
316 }
317
318 /*************************************************************************** **
319  * Initializes or clears the stat cache.
320  *
321  *  Input:  none.
322  *  Output: none.
323  *
324  * ************************************************************************** **
325  */
326 BOOL reset_stat_cache( void )
327 {
328         static BOOL initialised;
329         if (!lp_stat_cache())
330                 return True;
331
332         if (initialised) {
333                 hash_clear(&stat_cache);
334         }
335
336         initialised = hash_table_init( &stat_cache, INIT_STAT_CACHE_SIZE, 
337                                        (compare_function)(strcmp));
338         return initialised;
339 }