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 
20 #include "fs.h"
21 #include "dir.h"
22 #include "array.h"
23 #include "common.h"
24 
25 /*
26  * This file implements the high-level virtual file system layer
27  * routines.
28  */
29 
30 /*---------------------------------------------------------------------------*/
31 
cmp_dir_items(const void * A,const void * B)32 static int cmp_dir_items(const void *A, const void *B)
33 {
34     const struct dir_item *a = A, *b = B;
35     return strcmp(a->path, b->path);
36 }
37 
is_archive(struct dir_item * item)38 static int is_archive(struct dir_item *item)
39 {
40     return (str_ends_with(item->path, ".zip") ||
41             str_ends_with(item->path, ".pk3"));
42 }
43 
add_archives(const char * path)44 static void add_archives(const char *path)
45 {
46     Array archives;
47     int i;
48 
49     if ((archives = dir_scan(path, is_archive, NULL, NULL)))
50     {
51         array_sort(archives, cmp_dir_items);
52 
53         for (i = 0; i < array_len(archives); i++)
54             fs_add_path(DIR_ITEM_GET(archives, i)->path);
55 
56         dir_free(archives);
57     }
58 }
59 
fs_add_path_with_archives(const char * path)60 int fs_add_path_with_archives(const char *path)
61 {
62     add_archives(path);
63     return fs_add_path(path);
64 }
65 
66 /*---------------------------------------------------------------------------*/
67 
fs_rename(const char * src,const char * dst)68 int fs_rename(const char *src, const char *dst)
69 {
70     const char *write_dir;
71     char *real_src, *real_dst;
72     int rc = 0;
73 
74     if ((write_dir = fs_get_write_dir()))
75     {
76         real_src = concat_string(write_dir, "/", src, NULL);
77         real_dst = concat_string(write_dir, "/", dst, NULL);
78 
79         rc = file_rename(real_src, real_dst);
80 
81         free(real_src);
82         free(real_dst);
83     }
84 
85     return rc;
86 }
87 
88 /*---------------------------------------------------------------------------*/
89 
fs_getc(fs_file fh)90 int fs_getc(fs_file fh)
91 {
92     unsigned char c;
93 
94     if (fs_read(&c, 1, 1, fh) != 1)
95         return -1;
96 
97     return (int) c;
98 }
99 
fs_putc(int c,fs_file fh)100 int fs_putc(int c, fs_file fh)
101 {
102     unsigned char b = (unsigned char) c;
103 
104     if (fs_write(&b, 1, 1, fh) != 1)
105         return -1;
106 
107     return b;
108 }
109 
fs_puts(const char * src,fs_file fh)110 int fs_puts(const char *src, fs_file fh)
111 {
112     while (*src)
113         if (fs_putc(*src++, fh) < 0)
114             return -1;
115 
116     return 0;
117 }
118 
fs_gets(char * dst,int count,fs_file fh)119 char *fs_gets(char *dst, int count, fs_file fh)
120 {
121     char *s = dst;
122     int c;
123 
124     assert(dst);
125     assert(count > 0);
126 
127     if (fs_eof(fh))
128         return NULL;
129 
130     while (count > 1)
131         if ((c = fs_getc(fh)) >= 0)
132         {
133             count--;
134 
135             *s = c;
136 
137             /* Keep a newline and break. */
138 
139             if (*s == '\n')
140             {
141                 s++;
142                 break;
143             }
144 
145             /* Ignore carriage returns. */
146 
147             if (*s == '\r')
148             {
149                 count++;
150                 s--;
151             }
152 
153             s++;
154         }
155         else if (s == dst)
156             return NULL;
157         else
158             break;
159 
160     *s = '\0';
161 
162     return dst;
163 }
164 
165 /*---------------------------------------------------------------------------*/
166 
167 /*
168  * Write out a multiline string to a file with appropriately converted
169  * linefeed characters.
170  */
write_lines(const char * start,int length,fs_file fh)171 static int write_lines(const char *start, int length, fs_file fh)
172 {
173 #ifdef _WIN32
174     static const char crlf[] = "\r\n";
175 #else
176     static const char crlf[] = "\n";
177 #endif
178 
179     int total_written = 0;
180 
181     int datalen;
182     int written;
183     char *lf;
184 
185     while (total_written < length)
186     {
187         lf = strchr(start, '\n');
188 
189         datalen = lf ? (int) (lf - start) : length - total_written;
190         written = fs_write(start, 1, datalen, fh);
191 
192         if (written < 0)
193             break;
194 
195         total_written += written;
196 
197         if (written < datalen)
198             break;
199 
200         if (lf)
201         {
202             if (fs_puts(crlf, fh) < 0)
203                 break;
204 
205             total_written += 1;
206             start = lf + 1;
207         }
208     }
209 
210     return total_written;
211 }
212 
213 /*---------------------------------------------------------------------------*/
214 
fs_printf(fs_file fh,const char * fmt,...)215 int fs_printf(fs_file fh, const char *fmt, ...)
216 {
217     char *buff;
218     int len;
219 
220     va_list ap;
221 
222     va_start(ap, fmt);
223     len = 1 + vsnprintf(NULL, 0, fmt, ap);
224     va_end(ap);
225 
226     if ((buff = malloc(len)))
227     {
228         int written;
229 
230         va_start(ap, fmt);
231         vsnprintf(buff, len, fmt, ap);
232         va_end(ap);
233 
234         /*
235          * HACK.  This assumes fs_printf is always called with the
236          * intention of writing text, and not arbitrary data.
237          */
238 
239         written = write_lines(buff, strlen(buff), fh);
240 
241         free(buff);
242 
243         return written;
244     }
245 
246     return 0;
247 }
248 
249 /*---------------------------------------------------------------------------*/
250 
fs_load(const char * path,int * datalen)251 void *fs_load(const char *path, int *datalen)
252 {
253     fs_file fh;
254     void *data;
255 
256     data = NULL;
257 
258     if ((fh = fs_open(path, "r")))
259     {
260         if ((*datalen = fs_length(fh)) > 0)
261         {
262             if ((data = malloc(*datalen)))
263             {
264                 if (fs_read(data, *datalen, 1, fh) != 1)
265                 {
266                     free(data);
267                     data = NULL;
268                     *datalen = 0;
269                 }
270             }
271         }
272 
273         fs_close(fh);
274     }
275 
276     return data;
277 }
278 
279 /*---------------------------------------------------------------------------*/
280 
281 /*
282  * Convert a system path into a VFS path.
283  */
fs_resolve(const char * system)284 const char *fs_resolve(const char *system)
285 {
286     static char path[MAXSTR];
287 
288     const char *p;
289 
290     /*
291      * PhysicsFS will claim a file doesn't exist if its path uses a
292      * directory separator other than a forward slash, even if that
293      * separator is valid for the system. We'll oblige.
294      */
295 
296     SAFECPY(path, system);
297 
298     path_normalize(path);
299 
300     if (fs_exists(path))
301         return path;
302 
303     /* Chop off directories until we have a match. */
304 
305     p = path;
306 
307     while ((p = path_next_sep(p)))
308     {
309         /* Skip separator. */
310 
311         p += 1;
312 
313         if (fs_exists(p))
314             return p;
315     }
316 
317     return NULL;
318 }
319 
320 /*---------------------------------------------------------------------------*/
321