1 /*
2  * Copyright (C) 2003-2010 Neverball authors
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <assert.h>
18 #include <string.h>
19 #include <errno.h>
20 
21 #include "fs.h"
22 #include "dir.h"
23 #include "array.h"
24 #include "list.h"
25 #include "common.h"
26 
27 /*
28  * This file implements the low-level virtual file system routines
29  * using stdio.
30  */
31 
32 /*---------------------------------------------------------------------------*/
33 
34 struct fs_file_s
35 {
36     FILE *handle;
37 };
38 
39 static char *fs_dir_base;
40 static char *fs_dir_write;
41 static List  fs_path;
42 
fs_init(const char * argv0)43 int fs_init(const char *argv0)
44 {
45     fs_dir_base  = strdup(dir_name(argv0));
46     fs_dir_write = NULL;
47     fs_path      = NULL;
48 
49     return 1;
50 }
51 
fs_quit(void)52 int fs_quit(void)
53 {
54     if (fs_dir_base)
55     {
56         free(fs_dir_base);
57         fs_dir_base = NULL;
58     }
59 
60     if (fs_dir_write)
61     {
62         free(fs_dir_write);
63         fs_dir_write = NULL;
64     }
65 
66     while (fs_path)
67     {
68         free(fs_path->data);
69         fs_path = list_rest(fs_path);
70     }
71 
72     return 1;
73 }
74 
fs_error(void)75 const char *fs_error(void)
76 {
77     return strerror(errno);
78 }
79 
80 /*---------------------------------------------------------------------------*/
81 
fs_base_dir(void)82 const char *fs_base_dir(void)
83 {
84     return fs_dir_base;
85 }
86 
fs_add_path(const char * path)87 int fs_add_path(const char *path)
88 {
89     /* TODO: ZIP archive support. */
90 
91     if (dir_exists(path))
92     {
93         fs_path = list_cons(strdup(path), fs_path);
94         return 1;
95     }
96     return 0;
97 }
98 
fs_set_write_dir(const char * path)99 int fs_set_write_dir(const char *path)
100 {
101     if (dir_exists(path))
102     {
103         if (fs_dir_write)
104         {
105             free(fs_dir_write);
106             fs_dir_write = NULL;
107         }
108 
109         fs_dir_write = strdup(path);
110         return 1;
111     }
112     return 0;
113 }
114 
fs_get_write_dir(void)115 const char *fs_get_write_dir(void)
116 {
117     return fs_dir_write;
118 }
119 
120 /*---------------------------------------------------------------------------*/
121 
add_files(List * items,const char * real)122 static void add_files(List *items, const char *real)
123 {
124     List files, file;
125 
126     if ((files = dir_list_files(real)))
127     {
128         for (file = files; file; file = file->next)
129         {
130             int skip = 0;
131             List p, l;
132 
133             /* "Inspired" by PhysicsFS file enumeration code. */
134 
135             for (p = NULL, l = *items; l; p = l, l = l->next)
136             {
137                 int cmp;
138 
139                 if ((cmp = strcmp(l->data, file->data)) >= 0)
140                 {
141                     skip = (cmp == 0);
142                     break;
143                 }
144             }
145 
146             if (!skip)
147             {
148                 if (p)
149                     p->next = list_cons(file->data, p->next);
150                 else
151                     *items = list_cons(file->data, *items);
152 
153                 /* Take over memory management duties. */
154 
155                 file->data = NULL;
156             }
157         }
158 
159         dir_list_free(files);
160     }
161 }
162 
list_files(const char * path)163 static List list_files(const char *path)
164 {
165     List files = NULL;
166     List p;
167 
168     for (p = fs_path; p; p = p->next)
169     {
170         char *real = path_join(p->data, path);
171         add_files(&files, real);
172         free(real);
173     }
174 
175     return files;
176 }
177 
free_files(List files)178 static void free_files(List files)
179 {
180     while (files)
181     {
182         free(files->data);
183         files = list_rest(files);
184     }
185 }
186 
fs_dir_scan(const char * path,int (* filter)(struct dir_item *))187 Array fs_dir_scan(const char *path, int (*filter)(struct dir_item *))
188 {
189     return dir_scan(path, filter, list_files, free_files);
190 }
191 
fs_dir_free(Array items)192 void fs_dir_free(Array items)
193 {
194     dir_free(items);
195 }
196 
197 /*---------------------------------------------------------------------------*/
198 
real_path(const char * path)199 static char *real_path(const char *path)
200 {
201     char *real = NULL;
202     List p;
203 
204     for (p = fs_path; p; p = p->next)
205     {
206         real = path_join(p->data, path);
207 
208         if (file_exists(real))
209             break;
210 
211         free(real);
212         real = NULL;
213     }
214 
215     return real;
216 }
217 
218 /*---------------------------------------------------------------------------*/
219 
fs_open(const char * path,const char * mode)220 fs_file fs_open(const char *path, const char *mode)
221 {
222     fs_file fh;
223 
224     assert((mode[0] == 'r' && !mode[1]) ||
225            (mode[0] == 'w' && (!mode[1] || mode[1] == '+')));
226 
227     if ((fh = malloc(sizeof (*fh))))
228     {
229         char *real;
230 
231         fh->handle = NULL;
232 
233         switch (mode[0])
234         {
235         case 'r':
236             if ((real = real_path(path)))
237             {
238                 fh->handle = fopen(real, "rb");
239                 free(real);
240             }
241 
242             break;
243 
244         case 'w':
245             if (fs_dir_write)
246             {
247                 real = path_join(fs_dir_write, path);
248 
249                 fh->handle = (mode[1] == '+' ?
250                               fopen(real, "wb") :
251                               fopen(real, "wb+"));
252 
253                 free(real);
254             }
255             break;
256         }
257 
258         if (!fh->handle)
259         {
260             free(fh);
261             fh = NULL;
262         }
263     }
264 
265     return fh;
266 }
267 
fs_close(fs_file fh)268 int fs_close(fs_file fh)
269 {
270     if (fclose(fh->handle))
271     {
272         free(fh);
273         return 1;
274     }
275     return 0;
276 }
277 
278 /*----------------------------------------------------------------------------*/
279 
fs_mkdir(const char * path)280 int fs_mkdir(const char *path)
281 {
282     char *real;
283     int rc;
284 
285     real = path_join(fs_dir_write, path);
286     rc = dir_make(real);
287     free((void *) real);
288 
289     return rc == 0;
290 }
291 
fs_exists(const char * path)292 int fs_exists(const char *path)
293 {
294     char *real;
295 
296     if ((real = real_path(path)))
297     {
298         free(real);
299         return 1;
300     }
301     return 0;
302 }
303 
fs_remove(const char * path)304 int fs_remove(const char *path)
305 {
306     char *real;
307     int rc;
308 
309     real = path_join(fs_dir_write, path);
310     rc = (remove(real) == 0);
311     free(real);
312 
313     return rc;
314 }
315 
316 /*---------------------------------------------------------------------------*/
317 
fs_read(void * data,int size,int count,fs_file fh)318 int fs_read(void *data, int size, int count, fs_file fh)
319 {
320     return fread(data, size, count, fh->handle);
321 }
322 
fs_write(const void * data,int size,int count,fs_file fh)323 int fs_write(const void *data, int size, int count, fs_file fh)
324 {
325     return fwrite(data, size, count, fh->handle);
326 }
327 
fs_flush(fs_file fh)328 int fs_flush(fs_file fh)
329 {
330     return fflush(fh->handle);
331 }
332 
fs_tell(fs_file fh)333 long fs_tell(fs_file fh)
334 {
335     return ftell(fh->handle);
336 }
337 
fs_seek(fs_file fh,long offset,int whence)338 int fs_seek(fs_file fh, long offset, int whence)
339 {
340     return fseek(fh->handle, offset, whence);
341 }
342 
fs_eof(fs_file fh)343 int fs_eof(fs_file fh)
344 {
345     /*
346      * Unlike PhysicsFS, stdio does not register EOF unless we have
347      * actually attempted to read past the end of the file.  Nothing
348      * is done to mitigate this: instead, code that relies on
349      * PhysicsFS behavior should be fixed not to.
350      */
351     return feof(fh->handle);
352 }
353 
fs_length(fs_file fh)354 int fs_length(fs_file fh)
355 {
356     long len, cur = ftell(fh->handle);
357 
358     fseek(fh->handle, 0, SEEK_END);
359     len = ftell(fh->handle);
360     fseek(fh->handle, cur, SEEK_SET);
361 
362     return len;
363 }
364 
365 /*---------------------------------------------------------------------------*/
366