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