1 /* $Id$
2  *  Provides case insensitive file name matching on UNIX filesystems.
3  *  Written at 1999 by Tobias Ernst and released to the public domain.
4  *
5  *
6  * HUSKYLIB: common defines, types and functions for HUSKY
7  *
8  * This is part of The HUSKY Fidonet Software project:
9  * see http://husky.sourceforge.net for details
10  *
11  *
12  * HUSKYLIB is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * HUSKYLIB is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; see file COPYING. If not, write to the
24  * Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25  *
26  * See also http://www.gnu.org, license may be found here.
27  */
28 
29 
30 /* standard headers */
31 #include <ctype.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <assert.h>
37 #include <ctype.h>
38 #include <sys/types.h>
39 
40 
41 /* huskylib: compiler.h */
42 #include <compiler.h>
43 
44 /* compiler-dependent headers */
45 #ifdef HAS_UNISTD_H
46 #   include <unistd.h>
47 #endif
48 
49 #ifdef HAS_IO_H
50 #   include <io.h>
51 #endif
52 
53 #ifdef HAS_DIRENT_H
54 #  include <dirent.h>
55 #endif
56 
57 #ifdef HAS_SYS_PARAMS_H
58 #   include <sys/params.h>
59 #endif
60 
61 #ifdef HAS_STRINGS_H
62 #   include <strings.h>
63 #endif
64 
65 /* huskylib headers */
66 #define DLLEXPORT
67 #include <huskylib.h>
68 
69 
70 /***  Declarations & defines  ***********************************************/
71 
72 /*  ATTENTION: The adaptcase routine builds an internal cache which never
73  *  expires. If you ever add files to or remove files to a subdirectory and
74  *  later want to have adaptcase map this particular file name properly,
75  *  you must call adaptcase_refresh_dir() with the subdirectory path name
76  *  as argument!
77  */
78 void adaptcase_refresh_dir(const char *directory);
79 void adaptcase(char *);
80 
81 /***  Implementation  *******************************************************/
82 
83 #ifdef __UNIX__
84 
85 /* The adaptcase routine behaves as follows: It assumes that pathname
86    is a path name which may contain multiple dashes, and it assumes
87    that you run on a case sensitive file system but want / must to
88    match the path name insensitively. adaptcase takes every path
89    element out of pathname and uses findfirst to check if it
90    exists. If it exists, the path element is replaced by the exact
91    spelling as used by the file system. If it does not exist, it is
92    converted to lowercase.  This allows you to make you program deal
93    with things like mounts of DOS file systems under unix
94 
95    Return value is 1 if the file exists and 0 if not.
96 
97    Attention: Do not ever try to understand this code. I had to do
98    heavy caching and other optimizations in this routine in order to
99    reduce that startup time of msged to a reasonable value. (The
100    problem is that opendir / readdir is very slow ...). If you ever
101    have to fix something in this routine, you'd better rewrite it from
102    scratch.
103 
104  *  ATTENTION: The adaptcase routine builds an internal cache which never
105  *  expires. If you ever add files to or remove files to a subdirectory and
106  *  later want to have adaptcase map this particular file name properly,
107  *  you must call adaptcase_refresh_dir() with the subdirectory path name
108  *  as argument!
109 
110 */
111 
112 /* the cache will take  about 30 * 4192 + 30 * 512 * 4 bytes in this
113    configuration, i.e. 180K */
114 
115 #define adaptcase_cachesize 30
116 #define rawcache_stepsize 4192
117 #define cacheindex_stepsize 512
118 
119 struct adaptcase_cache_entry
120 {
121     char *query;
122     char *result;
123     char *raw_cache;
124     size_t *cache_index;
125     size_t n;
126 };
127 static int adaptcase_cache_position = -1;
128 static struct adaptcase_cache_entry adaptcase_cache[adaptcase_cachesize];
129 
130 static char *current_cache;
cache_sort_cmp(const void * a,const void * b)131 static int cache_sort_cmp(const void *a, const void *b)
132 {
133     return strcasecmp(current_cache+(*((const size_t *)a)),
134                    current_cache+(*((const size_t *)b)));
135 }
136 
cache_find_cmp(const void * a,const void * b)137 static int cache_find_cmp(const void *a, const void *b)
138 {
139     return strcasecmp((const char *)a, current_cache+(*((const size_t *)b)));
140 }
141 
142 /* #define TRACECACHE */
143 
144 #ifdef __BSD__
145 #define DIRENTLEN(x) ((x)->d_namlen)
146 #else
147 #define DIRENTLEN(x) (strlen((x)->d_name))
148 #endif
149 
adaptcase_refresh_dir(const char * directory)150 void adaptcase_refresh_dir(const char *directory)
151 {
152     int k = strlen(directory), l;
153 
154     if (k && directory[k-1] == '/')
155     {
156         k--;
157     }
158 
159     if (k != 0)
160     {
161         l = adaptcase_cache_position;
162         do
163         {
164             if (adaptcase_cache[l].query != NULL)
165             {
166                 if ((!memcmp(adaptcase_cache[l].query,directory,k)) &&
167                     (adaptcase_cache[l].query[k] == '\0'))
168                 {
169                     nfree(adaptcase_cache[l].query);
170                     nfree(adaptcase_cache[l].result);
171                     nfree(adaptcase_cache[l].raw_cache);
172                 }
173             }
174 
175             l = (l == 0) ? (adaptcase_cachesize - 1) : (l - 1);
176         } while (l != adaptcase_cache_position);
177     }
178 }
179 
adaptcase(char * pathname)180 void adaptcase(char *pathname)
181 {
182     int l, found=1, addresult=0;
183     size_t i, j, k, n, *m, raw_high, rawmax, nmax;
184     char buf[FILENAME_MAX + 1];
185     DIR *dirp = NULL;
186     struct dirent *dp;
187     char c;
188 
189 #ifdef TRACECACHE
190     FILE *ftrc;
191 #endif
192 
193     if (!*pathname)
194         return;
195 #ifdef TRACECACHE
196     ftrc = fopen ("trace.log", "a");
197     fprintf(ftrc, "--Query: %s\n", pathname);
198 #endif
199 
200     if (adaptcase_cache_position == -1)
201     {
202         /* initialise the cache */
203         memset(adaptcase_cache, 0, adaptcase_cachesize *
204                sizeof(struct adaptcase_cache_entry));
205         adaptcase_cache_position = 0;
206     }
207 
208     k = strlen(pathname);
209 
210     if (k > 2)
211     {
212         for (k = k - 2; k>0 && pathname[k] != '/'; k--) {};
213     }
214     else
215     {
216         k = 0;
217     }
218 
219     j = 0; i = 0;
220 
221 
222 start_over:
223 
224     if (k != 0)
225     {
226         l = adaptcase_cache_position;
227         do
228         {
229             if (adaptcase_cache[l].query != NULL)
230             {
231                 if ((!memcmp(adaptcase_cache[l].query,pathname,k)) &&
232                     (adaptcase_cache[l].query[k] == '\0'))
233                 {
234                     /* cache hit for the directory */
235 #ifdef TRACECACHE
236                     fprintf (ftrc, "Cache hit for Dir: %s\n",
237                              adaptcase_cache[l].result);
238 #endif
239                     memcpy(buf, adaptcase_cache[l].result, k);
240                     buf[k] = '/';
241                     current_cache=adaptcase_cache[l].raw_cache;
242                     m = bsearch(pathname + k + 1,
243                                 adaptcase_cache[l].cache_index,
244                                 adaptcase_cache[l].n,
245                                 sizeof(size_t),
246                                 cache_find_cmp);
247                     if (m == 0)
248                     {
249 #ifdef TRACECACHE
250                         fprintf (ftrc, "Cache miss for file.\n");
251 #endif
252 
253                         /* file does not exist - convert to lower c. */
254                         for (n = k + 1; pathname[n-1]; n++)
255                         {
256                             buf[n] = tolower(pathname[n]);
257                         }
258                         memcpy(pathname, buf, n-1);
259 #ifdef TRACECACHE
260                         fprintf(ftrc, "Return: %s\n", pathname);
261                         fclose(ftrc);
262 #endif
263                         return;
264                     }
265                     else
266                     {
267 #ifdef TRACECACHE
268                         fprintf (ftrc, "Cache hit for file: %s\n",
269                                  adaptcase_cache[l].raw_cache+(*m));
270 #endif
271 
272                         /* file does exist = cache hit for the file */
273                         for (n = k + 1; pathname[n-1]; n++)
274                         {
275                             buf[n] =
276                                 adaptcase_cache[l].raw_cache[(*m) + n - k - 1];
277                         }
278                         assert(buf[n-1] == '\0');
279                         memcpy(pathname, buf, n-1);
280 #ifdef TRACECACHE
281                         fprintf(ftrc, "Return: %s\n", pathname);
282                         fclose(ftrc);
283 #endif
284                         return;
285                     }
286                 }
287             }
288             l = (l == 0) ? adaptcase_cachesize - 1 : l - 1;
289         } while (l != adaptcase_cache_position);
290 
291 #ifdef TRACECACHE
292         fprintf (ftrc, "Cache miss for directory.\n");
293 #endif
294 
295 
296         /* no hit for the directory */
297         addresult = 1;
298     }
299 
300 
301     while (pathname[i])
302     {
303         if (pathname[i] == '/')
304         {
305             buf[i] = pathname[i];
306             if (addresult && i == k)
307             {
308                 goto add_to_cache;
309             }
310 cache_failure:
311             i++;
312             buf[i]='\0';
313             dirp = opendir(buf);
314 #ifdef TRACECACHE
315             if (dirp == NULL)
316             {
317                 fprintf (ftrc, "Error opening directory %s\n", buf);
318             }
319 #endif
320         }
321         else
322         {
323             assert(i==0);
324             dirp = opendir("./");
325 #ifdef TRACECACHE
326             if (dirp == NULL)
327             {
328                 fprintf (ftrc, "Error opening directory ./\n");
329             }
330 #endif
331         }
332 
333         j = i;
334         for (; pathname[i] && pathname[i]!='/'; i++)
335             buf[i] = pathname[i];
336         buf[i] = '\0';
337         found = 0;
338 
339         if (dirp != NULL)
340         {
341             while ((dp = readdir(dirp)) != NULL)
342             {
343                 if (!strcasecmp(dp->d_name, buf + j))
344                 {
345                     /* file exists, take over it's name */
346 
347                     assert((i - j) == DIRENTLEN(dp));
348                     memcpy(buf + j, dp->d_name, DIRENTLEN(dp) + 1);
349                     closedir(dirp);
350                     dirp = NULL;
351                     found = 1;
352                     break;
353                 }
354             }
355         }
356         if (!found)
357         {
358             /* file does not exist - so the rest is brand new and
359                should be converted to lower case */
360 
361             for (i = j; pathname[i]; i++)
362                 buf[i] = tolower(pathname[i]);
363             buf[i] = '\0';
364             if (dirp != NULL)
365             {
366                 closedir(dirp);
367             }
368             dirp = NULL;
369             break;
370         }
371     }
372     assert(strlen(pathname) == strlen(buf));
373 
374 add_to_cache:
375     while (addresult)
376     {
377         l = adaptcase_cache_position;
378         l = (l == adaptcase_cachesize - 1) ? 0 : l + 1;
379 
380         nfree(adaptcase_cache[l].query);
381         nfree(adaptcase_cache[l].result);
382         nfree(adaptcase_cache[l].raw_cache);
383 
384         if ( (adaptcase_cache[l].query = malloc(k + 1)) == NULL ||
385              (adaptcase_cache[l].result = malloc(k + 1)) == NULL ||
386              (adaptcase_cache[l].raw_cache =  malloc(rawmax = rawcache_stepsize)) == NULL ||
387              (adaptcase_cache[l].cache_index = malloc((nmax = cacheindex_stepsize) * sizeof(size_t))) == NULL )
388         {
389             goto cache_error;
390         }
391 
392         adaptcase_cache[l].n = 0;
393         raw_high = 0;
394 
395         c = buf[k]; buf[k] = '\0';
396         if ((dirp = opendir(buf)) == NULL)
397         {
398             buf[k] = c;
399             goto cache_error;
400         }
401         buf[k] = c;
402 
403         while ((dp = readdir(dirp)) != NULL)
404         {
405             if (raw_high + DIRENTLEN(dp) + 1 > rawmax)
406             {
407                 if ((adaptcase_cache[l].raw_cache =
408                      realloc(adaptcase_cache[l].raw_cache,
409                              rawmax+=rawcache_stepsize)) == NULL)
410                 {
411                     goto cache_error;
412                 }
413             }
414 
415             if (adaptcase_cache[l].n == nmax - 1)
416             {
417                 if ((adaptcase_cache[l].cache_index =
418                      realloc(adaptcase_cache[l].cache_index,
419                              (nmax+=cacheindex_stepsize) *
420                              sizeof(size_t))) == NULL)
421                 {
422                     goto cache_error;
423                 }
424             }
425 
426             memcpy (adaptcase_cache[l].raw_cache + raw_high,
427                     dp->d_name, DIRENTLEN(dp) + 1);
428             adaptcase_cache[l].cache_index[adaptcase_cache[l].n++] = raw_high;
429             raw_high += DIRENTLEN(dp) + 1;
430         }
431         closedir(dirp);
432         current_cache=adaptcase_cache[l].raw_cache;
433         qsort(adaptcase_cache[l].cache_index, adaptcase_cache[l].n,
434               sizeof(size_t), cache_sort_cmp);
435 
436         memcpy(adaptcase_cache[l].query, pathname, k);
437         adaptcase_cache[l].query[k] = '\0';
438         memcpy(adaptcase_cache[l].result, buf, k);
439         adaptcase_cache[l].result[k] = '\0';
440 
441         adaptcase_cache_position = l;
442 
443 #ifdef TRACECACHE
444         fprintf  (ftrc, "Successfully added cache entry.\n");
445 #endif
446         goto start_over;
447 
448     cache_error:
449 
450         nfree(adaptcase_cache[l].query);
451         nfree(adaptcase_cache[l].result);
452         nfree(adaptcase_cache[l].raw_cache);
453         nfree(adaptcase_cache[l].cache_index);
454 
455             if (dirp != NULL)
456         {
457             closedir(dirp);
458         }
459 #ifdef TRACECACHE
460         fprintf  (ftrc, "Error in building cache entry.\n");
461 #endif
462         addresult = 0;
463         goto cache_failure;
464     }
465 
466 #ifdef TRACECACHE
467     fprintf(ftrc, "Return: %s\n", pathname);
468     fclose(ftrc);
469 #endif
470     strcpy(pathname, buf);
471     return;
472 }
473 
474 
475 #else
476 
477 /* Not UNIX - Just convert the file to lower case, it will work because
478    the FS is case insensitive */
479 
adaptcase(char * str)480 void adaptcase (char *str)
481 {
482     unused(str);
483 }
484 
adaptcase_refresh_dir(const char * directory)485 void adaptcase_refresh_dir(const char *directory)
486 {
487     unused(directory);
488 }
489 #endif
490 
491 
492 #if defined (TEST)
main(int argc,char ** argv)493 int main(int argc, char **argv)
494 {
495     char cmdbuf[64];
496     char fnbuf[128];
497 
498     do
499     {
500         printf ("(L)ookup, (I)nvalidate, (Q)uit? "); fflush(stdout);
501         gets(cmdbuf);
502         switch (*cmdbuf)
503         {
504         case 'q':
505         case 'Q':
506             return 0;
507         case 'I':
508         case 'i':
509             gets(fnbuf);
510             adaptcase_refresh_dir(fnbuf);
511             break;
512         case 'L':
513         case 'l':
514             gets(fnbuf);
515             adaptcase(fnbuf);
516             printf ("%s\n", fnbuf);
517             break;
518         }
519     } while (1);
520 }
521 #endif
522 
523 
524