1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *      File I/O.
12  *
13  *      See LICENSE.txt for copyright information.
14  */
15 
16 #include "allegro5/allegro.h"
17 
18 /* enable large file support in gcc/glibc */
19 #if defined ALLEGRO_HAVE_FTELLO && defined ALLEGRO_HAVE_FSEEKO
20 #ifndef _LARGEFILE_SOURCE
21    #define _LARGEFILE_SOURCE
22 #endif
23 #ifndef _LARGEFILE_SOURCE64
24    #define _LARGEFILE_SOURCE64
25 #endif
26 #ifndef _FILE_OFFSET_BITS
27    #define _FILE_OFFSET_BITS 64
28 #endif
29 #endif
30 
31 #include <stdio.h>
32 
33 #include "allegro5/internal/aintern.h"
34 #include "allegro5/internal/aintern_file.h"
35 #include "allegro5/internal/aintern_wunicode.h"
36 #include ALLEGRO_INTERNAL_HEADER
37 
38 #ifdef ALLEGRO_HAVE_SYS_STAT_H
39 #include <sys/stat.h>
40 #endif
41 
42 ALLEGRO_DEBUG_CHANNEL("stdio")
43 
44 /* forward declaration */
45 const struct ALLEGRO_FILE_INTERFACE _al_file_interface_stdio;
46 
47 #ifndef PATH_MAX
48 #define PATH_MAX 4096
49 #endif
50 
51 
52 typedef struct
53 {
54    FILE *fp;
55    int errnum;
56    char errmsg[80];
57 } USERDATA;
58 
59 
get_userdata(ALLEGRO_FILE * f)60 static USERDATA *get_userdata(ALLEGRO_FILE *f)
61 {
62    if (f)
63       return al_get_file_userdata(f);
64    else
65       return NULL;
66 }
67 
68 
69 /* Function: al_fopen_fd
70  */
al_fopen_fd(int fd,const char * mode)71 ALLEGRO_FILE *al_fopen_fd(int fd, const char *mode)
72 {
73    ALLEGRO_FILE *f;
74    USERDATA *userdata;
75    FILE *fp;
76 
77    userdata = al_malloc(sizeof(USERDATA));
78    if (!userdata)
79       return NULL;
80 
81    /* The fd should remain open if this function fails in any way,
82     * so delay the fdopen() call to last.
83     */
84    userdata->fp = NULL;
85    userdata->errnum = 0;
86 
87    f = al_create_file_handle(&_al_file_interface_stdio, userdata);
88    if (!f) {
89       al_free(userdata);
90       return NULL;
91    }
92 
93    fp = fdopen(fd, mode);
94    if (!fp) {
95       al_set_errno(errno);
96       al_fclose(f);
97       return NULL;
98    }
99 
100    userdata->fp = fp;
101    return f;
102 }
103 
104 
file_stdio_fopen(const char * path,const char * mode)105 static void *file_stdio_fopen(const char *path, const char *mode)
106 {
107    FILE *fp;
108    USERDATA *userdata;
109 
110    ALLEGRO_DEBUG("opening %s %s\n", path, mode);
111 
112 #ifdef ALLEGRO_WINDOWS
113    {
114       wchar_t *wpath = _al_win_utf8_to_utf16(path);
115       wchar_t *wmode = _al_win_utf8_to_utf16(mode);
116       fp = _wfopen(wpath, wmode);
117       al_free(wpath);
118       al_free(wmode);
119    }
120 #else
121    fp = fopen(path, mode);
122 #endif
123 
124    if (!fp) {
125       al_set_errno(errno);
126       return NULL;
127    }
128 
129    userdata = al_malloc(sizeof(USERDATA));
130    if (!userdata) {
131       fclose(fp);
132       return NULL;
133    }
134 
135    userdata->fp = fp;
136    userdata->errnum = 0;
137 
138    return userdata;
139 }
140 
141 
file_stdio_fclose(ALLEGRO_FILE * f)142 static bool file_stdio_fclose(ALLEGRO_FILE *f)
143 {
144    USERDATA *userdata = get_userdata(f);
145    bool ret;
146 
147    if (userdata->fp == NULL) {
148       /* This can happen in the middle of al_fopen_fd. */
149       ret = true;
150    }
151    else if (fclose(userdata->fp) == 0) {
152       ret = true;
153    }
154    else {
155       al_set_errno(errno);
156       ret = false;
157    }
158 
159    al_free(userdata);
160 
161    return ret;
162 }
163 
164 
file_stdio_fread(ALLEGRO_FILE * f,void * ptr,size_t size)165 static size_t file_stdio_fread(ALLEGRO_FILE *f, void *ptr, size_t size)
166 {
167    USERDATA *userdata = get_userdata(f);
168 
169    if (size == 1) {
170       /* Optimise common case. */
171       int c = fgetc(userdata->fp);
172       if (c == EOF) {
173          userdata->errnum = errno;
174          al_set_errno(errno);
175          return 0;
176       }
177       *((char *)ptr) = (char)c;
178       return 1;
179    }
180    else {
181       size_t ret = fread(ptr, 1, size, userdata->fp);
182       if (ret < size) {
183          userdata->errnum = errno;
184          al_set_errno(errno);
185       }
186       return ret;
187    }
188 }
189 
190 
file_stdio_fwrite(ALLEGRO_FILE * f,const void * ptr,size_t size)191 static size_t file_stdio_fwrite(ALLEGRO_FILE *f, const void *ptr, size_t size)
192 {
193    USERDATA *userdata = get_userdata(f);
194    size_t ret;
195 
196    ret = fwrite(ptr, 1, size, userdata->fp);
197    if (ret < size) {
198       userdata->errnum = errno;
199       al_set_errno(errno);
200    }
201 
202    return ret;
203 }
204 
205 
file_stdio_fflush(ALLEGRO_FILE * f)206 static bool file_stdio_fflush(ALLEGRO_FILE *f)
207 {
208    USERDATA *userdata = get_userdata(f);
209 
210    if (fflush(userdata->fp) == EOF) {
211       userdata->errnum = errno;
212       al_set_errno(errno);
213       return false;
214    }
215 
216    return true;
217 }
218 
219 
file_stdio_ftell(ALLEGRO_FILE * f)220 static int64_t file_stdio_ftell(ALLEGRO_FILE *f)
221 {
222    USERDATA *userdata = get_userdata(f);
223    int64_t ret;
224 
225 #if defined(ALLEGRO_HAVE_FTELLO)
226    ret = ftello(userdata->fp);
227 #elif defined(ALLEGRO_HAVE_FTELLI64)
228    ret = _ftelli64(userdata->fp);
229 #else
230    ret = ftell(userdata->fp);
231 #endif
232    if (ret == -1) {
233       userdata->errnum = errno;
234       al_set_errno(errno);
235    }
236 
237    return ret;
238 }
239 
240 
file_stdio_fseek(ALLEGRO_FILE * f,int64_t offset,int whence)241 static bool file_stdio_fseek(ALLEGRO_FILE *f, int64_t offset,
242    int whence)
243 {
244    USERDATA *userdata = get_userdata(f);
245    int rc;
246 
247    switch (whence) {
248       case ALLEGRO_SEEK_SET: whence = SEEK_SET; break;
249       case ALLEGRO_SEEK_CUR: whence = SEEK_CUR; break;
250       case ALLEGRO_SEEK_END: whence = SEEK_END; break;
251    }
252 
253 #if defined(ALLEGRO_HAVE_FSEEKO)
254    rc = fseeko(userdata->fp, offset, whence);
255 #elif defined(ALLEGRO_HAVE_FSEEKI64)
256    rc = _fseeki64(userdata->fp, offset, whence);
257 #else
258    rc = fseek(userdata->fp, offset, whence);
259 #endif
260 
261    if (rc == -1) {
262       userdata->errnum = errno;
263       al_set_errno(errno);
264       return false;
265    }
266 
267    return true;
268 }
269 
270 
file_stdio_feof(ALLEGRO_FILE * f)271 static bool file_stdio_feof(ALLEGRO_FILE *f)
272 {
273    USERDATA *userdata = get_userdata(f);
274 
275    return feof(userdata->fp);
276 }
277 
278 
file_stdio_ferror(ALLEGRO_FILE * f)279 static int file_stdio_ferror(ALLEGRO_FILE *f)
280 {
281    USERDATA *userdata = get_userdata(f);
282 
283    return ferror(userdata->fp);
284 }
285 
286 
file_stdio_ferrmsg(ALLEGRO_FILE * f)287 static const char *file_stdio_ferrmsg(ALLEGRO_FILE *f)
288 {
289    USERDATA *userdata = get_userdata(f);
290 
291    if (userdata->errnum == 0)
292       return "";
293 
294    /* Note: at this time MinGW has neither strerror_r nor strerror_s. */
295 #if defined(ALLEGRO_HAVE_STRERROR_R)
296    {
297       int rc = strerror_r(userdata->errnum, userdata->errmsg,
298          sizeof(userdata->errmsg));
299       if (rc == 0) {
300          return userdata->errmsg;
301       }
302    }
303 #endif
304 
305 #if defined(ALLEGRO_HAVE_STRERROR_S)
306    {
307       errno_t rc = strerror_s(userdata->errmsg, sizeof(userdata->errmsg),
308          userdata->errnum);
309       if (rc == 0) {
310          return userdata->errmsg;
311       }
312    }
313 #endif
314 
315    return "";
316 }
317 
318 
file_stdio_fclearerr(ALLEGRO_FILE * f)319 static void file_stdio_fclearerr(ALLEGRO_FILE *f)
320 {
321    USERDATA *userdata = get_userdata(f);
322 
323    clearerr(userdata->fp);
324 }
325 
326 
file_stdio_fungetc(ALLEGRO_FILE * f,int c)327 static int file_stdio_fungetc(ALLEGRO_FILE *f, int c)
328 {
329    USERDATA *userdata = get_userdata(f);
330    int rc;
331 
332    rc = ungetc(c, userdata->fp);
333    if (rc == EOF) {
334       userdata->errnum = errno;
335       al_set_errno(errno);
336    }
337 
338    return rc;
339 }
340 
341 
file_stdio_fsize(ALLEGRO_FILE * f)342 static off_t file_stdio_fsize(ALLEGRO_FILE *f)
343 {
344    int64_t old_pos;
345    int64_t new_pos;
346 
347    old_pos = file_stdio_ftell(f);
348    if (old_pos == -1)
349       return -1;
350 
351    if (!file_stdio_fseek(f, 0, ALLEGRO_SEEK_END))
352       return -1;
353 
354    new_pos = file_stdio_ftell(f);
355    if (new_pos == -1)
356       return -1;
357 
358    if (!file_stdio_fseek(f, old_pos, ALLEGRO_SEEK_SET))
359       return -1;
360 
361    return new_pos;
362 }
363 
364 
365 const struct ALLEGRO_FILE_INTERFACE _al_file_interface_stdio =
366 {
367    file_stdio_fopen,
368    file_stdio_fclose,
369    file_stdio_fread,
370    file_stdio_fwrite,
371    file_stdio_fflush,
372    file_stdio_ftell,
373    file_stdio_fseek,
374    file_stdio_feof,
375    file_stdio_ferror,
376    file_stdio_ferrmsg,
377    file_stdio_fclearerr,
378    file_stdio_fungetc,
379    file_stdio_fsize
380 };
381 
382 
383 /* Function: al_set_standard_file_interface
384  */
al_set_standard_file_interface(void)385 void al_set_standard_file_interface(void)
386 {
387    al_set_new_file_interface(&_al_file_interface_stdio);
388 }
389 
390 
391 #define MAX_MKTEMP_TRIES 1000
392 
mktemp_replace_XX(const char * template,char * dst)393 static void mktemp_replace_XX(const char *template, char *dst)
394 {
395    static const char chars[] =
396       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
397    size_t len = strlen(template);
398    unsigned i;
399 
400    for (i=0; i<len; i++) {
401       if (template[i] != 'X') {
402          dst[i] = template[i];
403       }
404       else {
405          /* -1 to avoid the NUL terminator. */
406          dst[i] = chars[_al_rand() % (sizeof(chars) - 1)];
407       }
408    }
409 
410    dst[i] = '\0';
411 }
412 
413 
make_temp_file(const char * template,char * temp_filename,ALLEGRO_PATH * path)414 static ALLEGRO_FILE *make_temp_file(const char *template, char *temp_filename,
415    ALLEGRO_PATH *path)
416 {
417    ALLEGRO_FILE *f;
418    int fd;
419    int i;
420 
421    /* Note: the path should be absolute.  The user is likely to want to remove
422     * the file later.  If we return a relative path, the user might change the
423     * working directory in the mean time and then try to remove the wrong file.
424     * Mostly likely there won't be any such file, but the temporary file
425     * wouldn't be removed.
426     */
427 
428    for (i=0; i<MAX_MKTEMP_TRIES; ++i) {
429       mktemp_replace_XX(template, temp_filename);
430       al_set_path_filename(path, temp_filename);
431 
432 #ifndef ALLEGRO_MSVC
433       fd = open(al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP),
434          O_EXCL | O_CREAT | O_RDWR, S_IRWXU);
435 #else
436       fd = open(al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP),
437          O_EXCL | O_CREAT | O_RDWR, _S_IWRITE | _S_IREAD);
438 #endif
439 
440       if (fd != -1)
441          break;
442    }
443 
444    if (fd == -1) {
445       al_set_errno(errno);
446       return NULL;
447    }
448 
449    f = al_fopen_fd(fd, "rb+");
450    if (!f) {
451       al_set_errno(errno);
452       close(fd);
453       unlink(al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP));
454       return NULL;
455    }
456 
457    return f;
458 }
459 
460 
461 /* Function: al_make_temp_file
462  */
al_make_temp_file(const char * template,ALLEGRO_PATH ** ret_path)463 ALLEGRO_FILE *al_make_temp_file(const char *template, ALLEGRO_PATH **ret_path)
464 {
465    char *temp_filename;
466    ALLEGRO_PATH *path;
467    ALLEGRO_FILE *f;
468 
469    temp_filename = al_malloc(strlen(template) + 1);
470    path = al_get_standard_path(ALLEGRO_TEMP_PATH);
471 
472    if (temp_filename && path)
473       f = make_temp_file(template, temp_filename, path);
474    else
475       f = NULL;
476 
477    al_free(temp_filename);
478 
479    if (f && ret_path)
480       *ret_path = path;
481    else
482       al_destroy_path(path);
483 
484    return f;
485 }
486 
487 
488 /* vim: set sts=3 sw=3 et: */
489