Don't count CRs seen by fs_gets against passed-in buffer size
[neverball] / share / fs.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <assert.h>
4 #include <string.h>
5 #include <physfs.h>
6
7 #include "fs.h"
8 #include "dir.h"
9 #include "array.h"
10 #include "common.h"
11
12 /*
13  * This file implements the virtual file system layer.  Most file
14  * system and input/output operations are handled here.  There are
15  * basically two groups of functions here: low-level functions
16  * implemented directly using the PhysicsFS 1.0 API and higher-level
17  * functions implemented using the former group.
18  */
19
20 /* -------------------------------------------------------------------------- */
21
22 struct fs_file
23 {
24     PHYSFS_file *handle;
25 };
26
27 int fs_init(const char *argv0)
28 {
29     if (PHYSFS_init(argv0))
30     {
31         PHYSFS_permitSymbolicLinks(1);
32         return 1;
33     }
34
35     return 0;
36 }
37
38 int fs_quit(void)
39 {
40     return PHYSFS_deinit();
41 }
42
43 const char *fs_error(void)
44 {
45     return PHYSFS_getLastError();
46 }
47
48 /* -------------------------------------------------------------------------- */
49
50 const char *fs_base_dir(void)
51 {
52     return PHYSFS_getBaseDir();
53 }
54
55 int fs_add_path(const char *path)
56 {
57     return PHYSFS_addToSearchPath(path, 0);
58 }
59
60 int fs_set_write_dir(const char *path)
61 {
62     return PHYSFS_setWriteDir(path);
63 }
64
65 const char *fs_get_write_dir(void)
66 {
67     return PHYSFS_getWriteDir();
68 }
69
70 /* -------------------------------------------------------------------------- */
71
72 Array fs_dir_scan(const char *path, int (*filter)(struct dir_item *))
73 {
74     return dir_scan(path, filter, PHYSFS_enumerateFiles, PHYSFS_freeList);
75 }
76
77 void fs_dir_free(Array items)
78 {
79     dir_free(items);
80 }
81
82 /* -------------------------------------------------------------------------- */
83
84 fs_file fs_open(const char *path, const char *mode)
85 {
86     fs_file fh;
87
88     assert((mode[0] == 'r' && !mode[1]) ||
89            (mode[0] == 'w' && (!mode[1] || mode[1] == '+')));
90
91     if ((fh = malloc(sizeof (*fh))))
92     {
93         switch (mode[0])
94         {
95         case 'r':
96             fh->handle = PHYSFS_openRead(path);
97             break;
98
99         case 'w':
100             fh->handle = (mode[1] == '+' ?
101                           PHYSFS_openAppend(path) :
102                           PHYSFS_openWrite(path));
103             break;
104         }
105
106         if (fh->handle)
107         {
108             PHYSFS_setBuffer(fh->handle, 0x2000);
109         }
110         else
111         {
112             free(fh);
113             fh = NULL;
114         }
115     }
116
117     return fh;
118 }
119
120 int fs_close(fs_file fh)
121 {
122     if (PHYSFS_close(fh->handle))
123     {
124         free(fh);
125         return 1;
126     }
127     return 0;
128 }
129
130 /* -------------------------------------------------------------------------- */
131
132 int fs_mkdir(const char *path)
133 {
134     return PHYSFS_mkdir(path);
135 }
136
137 int fs_exists(const char *path)
138 {
139     return PHYSFS_exists(path);
140 }
141
142 int fs_remove(const char *path)
143 {
144     return PHYSFS_delete(path);
145 }
146
147 /* -------------------------------------------------------------------------- */
148
149 int fs_read(void *data, int size, int count, fs_file fh)
150 {
151     return PHYSFS_read(fh->handle, data, size, count);
152 }
153
154 int fs_write(const void *data, int size, int count, fs_file fh)
155 {
156     return PHYSFS_write(fh->handle, data, size, count);
157 }
158
159 int fs_flush(fs_file fh)
160 {
161     return PHYSFS_flush(fh->handle);
162 }
163
164 long fs_tell(fs_file fh)
165 {
166     return PHYSFS_tell(fh->handle);
167 }
168
169 int fs_seek(fs_file fh, long offset, int whence)
170 {
171     PHYSFS_uint64 pos = 0;
172     PHYSFS_sint64 cur = PHYSFS_tell(fh->handle);
173     PHYSFS_sint64 len = PHYSFS_fileLength(fh->handle);
174
175     switch (whence)
176     {
177     case SEEK_SET:
178         pos = offset;
179         break;
180
181     case SEEK_CUR:
182         if (cur < 0)
183             return -1;
184         pos = cur + offset;
185         break;
186
187     case SEEK_END:
188         if (len < 0)
189             return -1;
190         pos = len + offset;
191         break;
192     }
193
194     return PHYSFS_seek(fh->handle, pos);
195 }
196
197 int fs_eof(fs_file fh)
198 {
199     return PHYSFS_eof(fh->handle);
200 }
201
202 int fs_length(fs_file fh)
203 {
204     return PHYSFS_fileLength(fh->handle);
205 }
206
207 /* -------------------------------------------------------------------------- */
208
209 /*
210  * The code below does not use the PhysicsFS API.
211  */
212
213 /* -------------------------------------------------------------------------- */
214
215 static int cmp_dir_items(const void *A, const void *B)
216 {
217     const struct dir_item *a = A, *b = B;
218     return strcmp(a->path, b->path);
219 }
220
221 static int is_archive(struct dir_item *item)
222 {
223     return strcmp(item->path + strlen(item->path) - 4, ".zip") == 0;
224 }
225
226 static void add_archives(const char *path)
227 {
228     Array archives;
229     int i;
230
231     if ((archives = dir_scan(path, is_archive, NULL, NULL)))
232     {
233         array_sort(archives, cmp_dir_items);
234
235         for (i = 0; i < array_len(archives); i++)
236             fs_add_path(DIR_ITEM_GET(archives, i)->path);
237
238         dir_free(archives);
239     }
240 }
241
242 int fs_add_path_with_archives(const char *path)
243 {
244     add_archives(path);
245     return fs_add_path(path);
246 }
247
248 /* -------------------------------------------------------------------------- */
249
250 int fs_rename(const char *src, const char *dst)
251 {
252     const char *write_dir;
253     char *real_src, *real_dst;
254     int rc = 0;
255
256     if ((write_dir = fs_get_write_dir()))
257     {
258         real_src = concat_string(write_dir, "/", src, NULL);
259         real_dst = concat_string(write_dir, "/", dst, NULL);
260
261         rc = file_rename(real_src, real_dst);
262
263         free(real_src);
264         free(real_dst);
265     }
266
267     return rc;
268 }
269
270 /* -------------------------------------------------------------------------- */
271
272 int fs_getc(fs_file fh)
273 {
274     unsigned char c;
275
276     if (fs_read(&c, 1, 1, fh) != 1)
277         return -1;
278
279     return (int) c;
280 }
281
282 int fs_putc(int c, fs_file fh)
283 {
284     unsigned char b = (unsigned char) c;
285
286     if (fs_write(&b, 1, 1, fh) != 1)
287         return -1;
288
289     return b;
290 }
291
292 int fs_puts(const char *src, fs_file fh)
293 {
294     while (*src)
295         if (fs_putc(*src++, fh) < 0)
296             return -1;
297
298     return 0;
299 }
300
301 char *fs_gets(char *dst, int count, fs_file fh)
302 {
303     char *s = dst;
304     int c;
305
306     assert(dst);
307     assert(count > 0);
308
309     if (fs_eof(fh))
310         return NULL;
311
312     while (count > 1 && (c = fs_getc(fh)) >= 0)
313     {
314         count--;
315
316         *s = c;
317
318         /* Keep a newline and break. */
319
320         if (*s == '\n')
321         {
322             s++;
323             break;
324         }
325
326         /* Ignore carriage returns. */
327
328         if (*s == '\r')
329         {
330             count++;
331             s--;
332         }
333
334         s++;
335     }
336
337     *s = '\0';
338
339     return dst;
340 }
341
342 /* -------------------------------------------------------------------------- */
343
344 /*
345  * Write out a multiline string to a file with appropriately converted
346  * linefeed characters.
347  */
348 static int write_lines(const char *start, int length, fs_file fh)
349 {
350 #ifdef _WIN32
351     static const char crlf[] = "\r\n";
352 #else
353     static const char crlf[] = "\n";
354 #endif
355
356     int total_written = 0;
357
358     int datalen;
359     int written;
360     char *lf;
361
362     while (total_written < length)
363     {
364         lf = strchr(start, '\n');
365
366         datalen = lf ? (int) (lf - start) : length - total_written;
367         written = fs_write(start, 1, datalen, fh);
368
369         if (written < 0)
370             break;
371
372         total_written += written;
373
374         if (written < datalen)
375             break;
376
377         if (lf)
378         {
379             if (fs_puts(crlf, fh) < 0)
380                 break;
381
382             total_written += 1;
383             start = lf + 1;
384         }
385     }
386
387     return total_written;
388 }
389
390 /* -------------------------------------------------------------------------- */
391
392 /*
393  * Trying to avoid defining a feature test macro for every platform by
394  * declaring vsnprintf with the C99 signature.  This is probably bad.
395  */
396
397 #include <stdio.h>
398 #include <stdarg.h>
399
400 extern int vsnprintf(char *, size_t, const char *, va_list);
401
402 int fs_printf(fs_file fh, const char *fmt, ...)
403 {
404     char *buff;
405     int len;
406
407     va_list ap;
408
409     va_start(ap, fmt);
410     len = vsnprintf(NULL, 0, fmt, ap) + 1;
411     va_end(ap);
412
413     if ((buff = malloc(len)))
414     {
415         int written;
416
417         va_start(ap, fmt);
418         vsnprintf(buff, len, fmt, ap);
419         va_end(ap);
420
421         /*
422          * HACK.  This assumes fs_printf is always called with the
423          * intention of writing text, and not arbitrary data.
424          */
425
426         written = write_lines(buff, strlen(buff), fh);
427
428         free(buff);
429
430         return written;
431     }
432
433     return 0;
434 }
435
436 /* -------------------------------------------------------------------------- */
437
438 void *fs_load(const char *path, int *datalen)
439 {
440     fs_file fh;
441     void *data;
442
443     data = NULL;
444
445     if ((fh = fs_open(path, "r")))
446     {
447         if ((*datalen = fs_length(fh)) > 0)
448         {
449             if ((data = malloc(*datalen)))
450             {
451                 if (fs_read(data, *datalen, 1, fh) != 1)
452                 {
453                     free(data);
454                     data = NULL;
455                     *datalen = 0;
456                 }
457             }
458         }
459
460         fs_close(fh);
461     }
462
463     return data;
464 }
465
466 /* -------------------------------------------------------------------------- */