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