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