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