1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      Helper routines to make file.c work on Unix (POSIX) platforms.
12  *
13  *      By Michael Bukin.
14  *
15  *      See readme.txt for copyright information.
16  */
17 
18 /* libc should use 64-bit for file sizes when possible */
19 #define _FILE_OFFSET_BITS 64
20 
21 #include <stdio.h>
22 #include <string.h>
23 
24 #include "allegro.h"
25 #include "allegro/internal/aintern.h"
26 
27 #ifdef ALLEGRO_HAVE_SYS_STAT_H
28 #include <sys/stat.h>
29 #endif
30 
31 #ifdef ALLEGRO_HAVE_DIRENT_H
32    #include <sys/types.h>
33    #include <dirent.h>
34    #define NAMLEN(dirent) (strlen((dirent)->d_name))
35 #else
36    /* Apparently all new systems have `dirent.h'. */
37    #error ALLEGRO_HAVE_DIRENT_H not defined
38 #endif
39 
40 #ifdef ALLEGRO_HAVE_SYS_TIME_H
41   #include <sys/time.h>
42 #endif
43 #ifdef ALLEGRO_HAVE_TIME_H
44   #include <time.h>
45 #endif
46 
47 #define PREFIX_I "al-unix INFO: "
48 
49 #define PREFIX_I "al-unix INFO: "
50 
51 
52 /* _al_file_isok:
53  *  Helper function to check if it is safe to access a file on a floppy
54  *  drive.
55  */
_al_file_isok(AL_CONST char * filename)56 int _al_file_isok(AL_CONST char *filename)
57 {
58    return TRUE;
59 }
60 
61 
62 
63 /* _al_file_size_ex:
64  *  Measures the size of the specified file.
65  */
_al_file_size_ex(AL_CONST char * filename)66 uint64_t _al_file_size_ex(AL_CONST char *filename)
67 {
68    struct stat s;
69    char tmp[1024];
70 
71    if (stat(uconvert(filename, U_CURRENT, tmp, U_UTF8, sizeof(tmp)), &s) != 0) {
72       *allegro_errno = errno;
73       return 0;
74    }
75 
76    return s.st_size;
77 }
78 
79 
80 
81 /* _al_file_time:
82  *  Returns the timestamp of the specified file.
83  */
_al_file_time(AL_CONST char * filename)84 time_t _al_file_time(AL_CONST char *filename)
85 {
86    struct stat s;
87    char tmp[1024];
88 
89    if (stat(uconvert(filename, U_CURRENT, tmp, U_UTF8, sizeof(tmp)), &s) != 0) {
90       *allegro_errno = errno;
91       return 0;
92    }
93 
94    return s.st_mtime;
95 }
96 
97 
98 
99 /* ff_get_filename:
100  *  When passed a completely specified file path, this returns a pointer
101  *  to the filename portion.
102  */
ff_get_filename(AL_CONST char * path)103 static char *ff_get_filename(AL_CONST char *path)
104 {
105    char *p = (char*)path + strlen(path);
106 
107    while ((p > path) && (*(p - 1) != '/'))
108       p--;
109 
110    return p;
111 }
112 
113 
114 
115 /* ff_put_backslash:
116  *  If the last character of the filename is not a /, this routine will
117  *  concatenate a / on to it.
118  */
ff_put_backslash(char * filename,int size)119 static void ff_put_backslash(char *filename, int size)
120 {
121    int len = strlen(filename);
122 
123    if ((len > 0) && (len < (size - 1)) && (filename[len - 1] != '/')) {
124       filename[len] = '/';
125       filename[len + 1] = 0;
126    }
127 }
128 
129 
130 
131 #define FF_MATCH_TRY 0
132 #define FF_MATCH_ONE 1
133 #define FF_MATCH_ANY 2
134 
135 
136 struct FF_MATCH_DATA
137 {
138    int type;
139    AL_CONST char *s1;
140    AL_CONST char *s2;
141 };
142 
143 
144 
145 /* ff_match:
146  *  Matches two strings ('*' matches any number of characters,
147  *  '?' matches any character).
148  */
ff_match(AL_CONST char * s1,AL_CONST char * s2)149 static int ff_match(AL_CONST char *s1, AL_CONST char *s2)
150 {
151    static unsigned int size = 0;
152    static struct FF_MATCH_DATA *data = NULL;
153    AL_CONST char *s1end;
154    int index, c1, c2;
155 
156    /* handle NULL arguments */
157    if ((!s1) && (!s2)) {
158       if (data) {
159          _AL_FREE(data);
160          data = NULL;
161       }
162 
163       return 0;
164    }
165 
166    s1end = s1 + strlen(s1);
167 
168    /* allocate larger working area if necessary */
169    if (data && (size < strlen(s2))) {
170       _AL_FREE(data);
171       data = NULL;
172    }
173 
174    if (!data) {
175       size = strlen(s2);
176       data = _AL_MALLOC(sizeof(struct FF_MATCH_DATA) * size * 2 + 1);
177       if (!data)
178          return 0;
179    }
180 
181    index = 0;
182    data[0].s1 = s1;
183    data[0].s2 = s2;
184    data[0].type = FF_MATCH_TRY;
185 
186    while (index >= 0) {
187       s1 = data[index].s1;
188       s2 = data[index].s2;
189       c1 = *s1;
190       c2 = *s2;
191 
192       switch (data[index].type) {
193 
194       case FF_MATCH_TRY:
195          if (c2 == 0) {
196             /* pattern exhausted */
197             if (c1 == 0)
198                return 1;
199             else
200                index--;
201          }
202          else if (c1 == 0) {
203             /* string exhausted */
204             while (*s2 == '*')
205                s2++;
206             if (*s2 == 0)
207                return 1;
208             else
209                index--;
210          }
211          else if (c2 == '*') {
212             /* try to match the rest of pattern with empty string */
213             data[index++].type = FF_MATCH_ANY;
214             data[index].s1 = s1end;
215             data[index].s2 = s2 + 1;
216             data[index].type = FF_MATCH_TRY;
217          }
218          else if ((c2 == '?') || (c1 == c2)) {
219             /* try to match the rest */
220             data[index++].type = FF_MATCH_ONE;
221             data[index].s1 = s1 + 1;
222             data[index].s2 = s2 + 1;
223             data[index].type = FF_MATCH_TRY;
224          }
225          else
226             index--;
227          break;
228 
229       case FF_MATCH_ONE:
230          /* the rest of string did not match, try earlier */
231          index--;
232          break;
233 
234       case FF_MATCH_ANY:
235          /* rest of string did not match, try add more chars to string tail */
236          if (--data[index + 1].s1 >= s1) {
237             data[index + 1].type = FF_MATCH_TRY;
238             index++;
239          }
240          else
241             index--;
242          break;
243 
244       default:
245          /* this is a bird? This is a plane? No it's a bug!!! */
246          return 0;
247       }
248    }
249 
250    return 0;
251 }
252 
253 
254 
255 /* ff_get_attrib:
256  *  Builds up the attribute list of the file pointed to by name and s.
257  */
ff_get_attrib(AL_CONST char * name,struct stat * s)258 static int ff_get_attrib(AL_CONST char *name, struct stat *s)
259 {
260    int attrib = 0;
261    uid_t euid = geteuid();
262 
263    if (euid != 0) {
264       if (s->st_uid == euid) {
265 	 if ((s->st_mode & S_IWUSR) == 0)
266 	    attrib |= FA_RDONLY;
267       }
268       else if (s->st_gid == getegid()) {
269 	 if ((s->st_mode & S_IWGRP) == 0)
270 	    attrib |= FA_RDONLY;
271       }
272       else if ((s->st_mode & S_IWOTH) == 0) {
273 	 attrib |= FA_RDONLY;
274       }
275    }
276 
277    if (S_ISDIR(s->st_mode))
278       attrib |= FA_DIREC;
279 
280    if ((name[0] == '.') && ((name[1] != '.') || (name[2] != '\0')))
281       attrib |= FA_HIDDEN;
282 
283    return attrib;
284 }
285 
286 
287 
288 /* structure for use by the directory scanning routines */
289 #define FF_MAXPATHLEN 1024
290 
291 struct FF_DATA
292 {
293    DIR *dir;
294    char dirname[FF_MAXPATHLEN];
295    char pattern[FF_MAXPATHLEN];
296    int attrib;
297    uint64_t size;
298 };
299 
300 
301 
302 /* al_findfirst:
303  *  Initiates a directory search.
304  */
al_findfirst(AL_CONST char * pattern,struct al_ffblk * info,int attrib)305 int al_findfirst(AL_CONST char *pattern, struct al_ffblk *info, int attrib)
306 {
307    struct FF_DATA *ff_data;
308    struct stat s;
309    int actual_attrib;
310    char tmp[1024];
311    char *p;
312 
313    /* allocate ff_data structure */
314    ff_data = _AL_MALLOC(sizeof(struct FF_DATA));
315    if (!ff_data) {
316       *allegro_errno = ENOMEM;
317       return -1;
318    }
319 
320    memset(ff_data, 0, sizeof *ff_data);
321    info->ff_data = (void *) ff_data;
322 
323    /* if the pattern contains no wildcard, we use stat() */
324    if (!ustrpbrk(pattern, uconvert("?*", U_ASCII, tmp, U_CURRENT, sizeof(tmp)))) {
325       /* start the search */
326       errno = *allegro_errno = 0;
327 
328       if (stat(uconvert(pattern, U_CURRENT, tmp, U_UTF8, sizeof(tmp)), &s) == 0) {
329          /* get file attributes */
330          actual_attrib = ff_get_attrib(ff_get_filename(uconvert(pattern, U_CURRENT, tmp, U_UTF8, sizeof(tmp))), &s);
331 
332          /* does it match ? */
333          if ((actual_attrib & ~attrib) == 0) {
334             info->attrib = actual_attrib;
335             info->time = s.st_mtime;
336             info->size = s.st_size; /* overflows at 2GB */
337             ff_data->size = s.st_size;
338             ustrzcpy(info->name, sizeof(info->name), get_filename(pattern));
339             return 0;
340          }
341       }
342 
343        _AL_FREE(ff_data);
344       info->ff_data = NULL;
345       *allegro_errno = (errno ? errno : ENOENT);
346       return -1;
347    }
348 
349    ff_data->attrib = attrib;
350 
351    do_uconvert(pattern, U_CURRENT, ff_data->dirname, U_UTF8, sizeof(ff_data->dirname));
352    p = ff_get_filename(ff_data->dirname);
353    _al_sane_strncpy(ff_data->pattern, p, sizeof(ff_data->pattern));
354    if (p == ff_data->dirname)
355       _al_sane_strncpy(ff_data->dirname, "./", FF_MAXPATHLEN);
356    else
357       *p = 0;
358 
359    /* nasty bodge, but gives better compatibility with DOS programs */
360    if (strcmp(ff_data->pattern, "*.*") == 0)
361       _al_sane_strncpy(ff_data->pattern, "*", FF_MAXPATHLEN);
362 
363    /* start the search */
364    errno = *allegro_errno = 0;
365 
366    ff_data->dir = opendir(ff_data->dirname);
367 
368    if (!ff_data->dir) {
369       *allegro_errno = (errno ? errno : ENOENT);
370       _AL_FREE(ff_data);
371       info->ff_data = NULL;
372       return -1;
373    }
374 
375    if (al_findnext(info) != 0) {
376       al_findclose(info);
377       return -1;
378    }
379 
380    return 0;
381 }
382 
383 
384 
385 /* al_findnext:
386  *  Retrieves the next file from a directory search.
387  */
al_findnext(struct al_ffblk * info)388 int al_findnext(struct al_ffblk *info)
389 {
390    char tempname[FF_MAXPATHLEN];
391    char filename[FF_MAXPATHLEN];
392    int attrib;
393    struct dirent *entry;
394    struct stat s;
395    struct FF_DATA *ff_data = (struct FF_DATA *) info->ff_data;
396 
397    ASSERT(ff_data);
398 
399    /* if the pattern contained no wildcard */
400    if (!ff_data->dir)
401       return -1;
402 
403    while (TRUE) {
404       /* read directory entry */
405       entry = readdir(ff_data->dir);
406       if (!entry) {
407          *allegro_errno = (errno ? errno : ENOENT);
408          return -1;
409       }
410 
411       /* try to match file name with pattern */
412       tempname[0] = 0;
413       if (NAMLEN(entry) >= sizeof(tempname))
414          strncat(tempname, entry->d_name, sizeof(tempname) - 1);
415       else
416          strncat(tempname, entry->d_name, NAMLEN(entry));
417 
418       if (ff_match(tempname, ff_data->pattern)) {
419          _al_sane_strncpy(filename, ff_data->dirname, FF_MAXPATHLEN);
420          ff_put_backslash(filename, sizeof(filename));
421          strncat(filename, tempname, sizeof(filename) - strlen(filename) - 1);
422 
423          /* get file attributes */
424          if (stat(filename, &s) == 0) {
425             attrib = ff_get_attrib(tempname, &s);
426 
427             /* does it match ? */
428             if ((attrib & ~ff_data->attrib) == 0)
429                break;
430          }
431          else {
432             /* evil! but no other way to avoid exiting for_each_file() */
433             *allegro_errno = 0;
434          }
435       }
436    }
437 
438    info->attrib = attrib;
439    info->time = s.st_mtime;
440    info->size = s.st_size; /* overflows at 2GB */
441    ff_data->size = s.st_size;
442 
443    do_uconvert(tempname, U_UTF8, info->name, U_CURRENT, sizeof(info->name));
444 
445    return 0;
446 }
447 
448 
449 
450 /* al_findclose:
451  *  Cleans up after a directory search.
452  */
al_findclose(struct al_ffblk * info)453 void al_findclose(struct al_ffblk *info)
454 {
455    struct FF_DATA *ff_data = (struct FF_DATA *) info->ff_data;
456 
457    if (ff_data) {
458       if (ff_data->dir) {
459          closedir(ff_data->dir);
460       }
461       _AL_FREE(ff_data);
462       info->ff_data = NULL;
463 
464       /* to avoid leaking memory */
465       ff_match(NULL, NULL);
466    }
467 }
468 
469 
470 
471 /* _al_getdcwd:
472  *  Returns the current directory on the specified drive.
473  */
_al_getdcwd(int drive,char * buf,int size)474 void _al_getdcwd(int drive, char *buf, int size)
475 {
476    char tmp[1024];
477 
478    if (getcwd(tmp, sizeof(tmp)))
479       do_uconvert(tmp, U_UTF8, buf, U_CURRENT, size);
480    else
481       usetc(buf, 0);
482 }
483 
484 
485 
486 /* _al_ffblk_get_size:
487  *  Returns the size out of an _al_ffblk structure.
488  */
al_ffblk_get_size(struct al_ffblk * info)489 uint64_t al_ffblk_get_size(struct al_ffblk *info)
490 {
491    struct FF_DATA *ff_data;
492    ASSERT(info);
493    ff_data = (struct FF_DATA *) info->ff_data;
494    ASSERT(ff_data);
495    return ff_data->size;
496 }
497 
498 
499 
500 /* _al_detect_filename_encoding:
501  *  Platform specific function to detect the filename encoding. This is called
502  *  after setting a system driver, and even if this driver is SYSTEM_NONE.
503  */
_al_detect_filename_encoding(void)504 void _al_detect_filename_encoding(void)
505 {
506    char const *encoding = "unknown";
507    char *locale = getenv("LC_ALL");
508 
509    if (!locale || !locale[0]) {
510       locale = getenv("LC_CTYPE");
511       if (!locale || !locale[0])
512          locale = getenv("LANG");
513    }
514 
515    if (locale) {
516       if (strstr(locale, "utf8") ||
517           strstr(locale, "UTF-8") ||
518           strstr(locale, "utf-8") ||
519           strstr(locale, "UTF8")) {
520          /* Note: UTF8 is default anyway. */
521          set_filename_encoding(U_UTF8);
522          encoding = "UTF8";
523       }
524       /* TODO: detect other encodings, and support them in Allegro */
525    }
526 
527    TRACE(PREFIX_I "Assumed libc encoding is %s.\n", encoding);
528 }
529