1 /*
2 	quakeio.c
3 
4 	(description)
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 	Copyright (C) 1999,2000  contributors of the QuakeForge project
8 	Please see the file "AUTHORS" for a list of contributors
9 
10 	This program is free software; you can redistribute it and/or
11 	modify it under the terms of the GNU General Public License
12 	as published by the Free Software Foundation; either version 2
13 	of the License, or (at your option) any later version.
14 
15 	This program is distributed in the hope that it will be useful,
16 	but WITHOUT ANY WARRANTY; without even the implied warranty of
17 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 
19 	See the GNU General Public License for more details.
20 
21 	You should have received a copy of the GNU General Public License
22 	along with this program; if not, write to:
23 
24 		Free Software Foundation, Inc.
25 		59 Temple Place - Suite 330
26 		Boston, MA  02111-1307, USA
27 
28 */
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32 
33 #ifdef HAVE_ZLIB
34 # include <zlib.h>
35 #endif
36 #ifdef HAVE_STRING_H
37 # include <string.h>
38 #endif
39 #ifdef HAVE_STRINGS_H
40 # include <strings.h>
41 #endif
42 #ifdef HAVE_UNISTD_H
43 # include <unistd.h>
44 #endif
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 
49 #if defined(_WIN32) && defined(HAVE_MALLOC_H)
50 #include <malloc.h>
51 #endif
52 
53 #ifdef HAVE_IO_H
54 #include <io.h>
55 #endif
56 
57 #include <limits.h>
58 #include <stdarg.h>
59 #include <stdlib.h>
60 #include <errno.h>
61 
62 #include "qfalloca.h"
63 
64 #include "QF/dstring.h"
65 #include "QF/qendian.h"
66 #include "QF/quakefs.h"
67 #include "QF/quakeio.h"
68 
69 #ifdef _WIN32
70 # ifndef __BORLANDC__
71 #  define setmode _setmode
72 #  define O_BINARY _O_BINARY
73 # endif
74 #endif
75 
76 #define QF_ZIP	1
77 #define QF_READ	2
78 
79 struct QFile_s {
80 	FILE *file;
81 #ifdef HAVE_ZLIB
82 	gzFile gzfile;
83 #endif
84 	off_t size;
85 	off_t start;
86 	off_t pos;
87 	int   c;
88 	int   sub;
89 };
90 
91 
92 VISIBLE int
Qrename(const char * old_path,const char * new_path)93 Qrename (const char *old_path, const char *new_path)
94 {
95 	return rename (old_path, new_path);
96 }
97 
98 VISIBLE int
Qremove(const char * path)99 Qremove (const char *path)
100 {
101 	return remove (path);
102 }
103 
104 VISIBLE int
Qfilesize(QFile * file)105 Qfilesize (QFile *file)
106 {
107 	return file->size;
108 }
109 
110 static int
check_file(int fd,int offs,int len,int * zip)111 check_file (int fd, int offs, int len, int *zip)
112 {
113 	unsigned char id[2], len_bytes[4];
114 
115 	if (offs < 0 || len < 0) {
116 		// normal file
117 		offs = 0;
118 		len = lseek (fd, 0, SEEK_END);
119 		lseek (fd, 0, SEEK_SET);
120 	}
121 	if (zip && *zip) {
122 		int         r;
123 
124 		lseek (fd, offs, SEEK_SET);
125 		r = read (fd, id, 2);
126 		if (r == 2 && id[0] == 0x1f && id[1] == 0x8b && len >= 6 &&
127 			lseek (fd, offs + len - 4, SEEK_SET) >= 0 &&
128 			read (fd, len_bytes, 4) == 4) {
129 			len = ((len_bytes[3] << 24)
130 				   | (len_bytes[2] << 16)
131 				   | (len_bytes[1] << 8)
132 				   | (len_bytes[0]));
133 		} else {
134 			*zip = 0;
135 		}
136 	}
137 	lseek (fd, offs, SEEK_SET);
138 	return len;
139 }
140 
141 static int
file_mode(const char * mode,char * out)142 file_mode (const char *mode, char *out)
143 {
144 	int         flags = 0;
145 	char       *p;
146 
147 	for (p = out; *mode; mode++) {
148 		if (*mode == 'z') {
149 			flags |= QF_ZIP;
150 			continue;
151 		}
152 		if (*mode == 'r' || ((*mode == 'w' || *mode == 'a') && *mode == '+')) {
153 			flags |= QF_READ;
154 		}
155 #ifndef HAVE_ZLIB
156 		if (strchr ("0123456789fh", *mode)) {
157 			continue;
158 		}
159 #endif
160 		if (p)
161 			*p++ = *mode;
162 	}
163 	if (p)
164 		*p = 0;
165 	return flags;
166 }
167 
168 VISIBLE QFile *
Qopen(const char * path,const char * mode)169 Qopen (const char *path, const char *mode)
170 {
171 	QFile      *file;
172 	char       *m;
173 	int         flags, reading, zip;
174 	int         size = -1;
175 
176 	m = alloca (strlen (mode) + 1);
177 	flags = file_mode (mode, m);
178 
179 	zip = flags & QF_ZIP;
180 	reading = flags & QF_READ;
181 
182 	if (reading) {
183 		int         fd = open (path, O_RDONLY);
184 		if (fd != -1) {
185 			size = check_file (fd, -1, -1, &zip);
186 			close (fd);
187 		}
188 	}
189 
190 	file = calloc (sizeof (*file), 1);
191 	if (!file)
192 		return 0;
193 	file->size = size;
194 #ifdef HAVE_ZLIB
195 	if (zip) {
196 		file->gzfile = gzopen (path, m);
197 		if (!file->gzfile) {
198 			free (file);
199 			return 0;
200 		}
201 	} else
202 #endif
203 	{
204 		file->file = fopen (path, m);
205 		if (!file->file) {
206 			free (file);
207 			return 0;
208 		}
209 	}
210 	file->c = -1;
211 	return file;
212 }
213 
214 VISIBLE QFile *
Qdopen(int fd,const char * mode)215 Qdopen (int fd, const char *mode)
216 {
217 	QFile      *file;
218 	char       *m, *p;
219 #ifdef HAVE_ZLIB
220 	int         zip = 0;
221 #endif
222 	int         len = strlen (mode);
223 
224 	m = alloca (len + 1);
225 #ifdef _WIN32
226 	setmode (fd, O_BINARY);
227 #endif
228 	for (p = m; *mode && p - m < len; mode++) {
229 		if (*mode == 'z') {
230 #ifdef HAVE_ZLIB
231 			zip = 1;
232 #endif
233 			continue;
234 		}
235 		*p++ = *mode;
236 	}
237 
238 	*p = 0;
239 
240 	file = calloc (sizeof (*file), 1);
241 	if (!file)
242 		return 0;
243 #ifdef HAVE_ZLIB
244 	if (zip) {
245 		file->gzfile = gzdopen (fd, m);
246 		if (!file->gzfile) {
247 			free (file);
248 			return 0;
249 		}
250 	} else
251 #endif
252 	{
253 		file->file = fdopen (fd, m);
254 		if (!file->file) {
255 			free (file);
256 			return 0;
257 		}
258 	}
259 	file->c = -1;
260 	return file;
261 }
262 
263 VISIBLE QFile *
Qfopen(FILE * file,const char * mode)264 Qfopen (FILE *file, const char *mode)
265 {
266 	QFile      *qfile;
267 	int         flags = file_mode (mode, 0);
268 
269 	if (!file)
270 		return 0;
271 	qfile = calloc (sizeof (*qfile), 1);
272 	if (!qfile)
273 		return 0;
274 	qfile->file = file;
275 	if (flags & QF_READ)
276 		qfile->size = check_file (fileno (file), -1, -1, 0);
277 	qfile->c = -1;
278 	return qfile;
279 }
280 
281 VISIBLE QFile *
Qsubopen(const char * path,int offs,int len,int zip)282 Qsubopen (const char *path, int offs, int len, int zip)
283 {
284 	int         fd = open (path, O_RDONLY);
285 	QFile      *file;
286 
287 	if (fd == -1)
288 		return 0;
289 #ifdef _WIN32
290 	setmode (fd, O_BINARY);
291 #endif
292 
293 	len = check_file (fd, offs, len, &zip);
294 	file = Qdopen (fd, zip ? "rbz" : "rb");
295 	file->size = len;
296 	file->start = offs;
297 	file->sub = 1;
298 	return file;
299 }
300 
301 VISIBLE void
Qclose(QFile * file)302 Qclose (QFile *file)
303 {
304 	if (file->file)
305 		fclose (file->file);
306 #ifdef HAVE_ZLIB
307 	else
308 		gzclose (file->gzfile);
309 #endif
310 	free (file);
311 }
312 
313 VISIBLE int
Qread(QFile * file,void * buf,int count)314 Qread (QFile *file, void *buf, int count)
315 {
316 	int         offs = 0;
317 	int         ret;
318 
319 	if (file->c != -1) {
320 		char       *b = buf;
321 		*b++ = file->c;
322 		buf = b;
323 		offs = 1;
324 		file->c = -1;
325 		count--;
326 		if (!count)
327 			return 1;
328 	}
329 	if (file->sub) {
330 		// sub-files are always opened in binary mode, so we don't need to
331 		// worry about character translation messing up count/pos. Normal
332 		// files can be left to the operating system to take care of EOF.
333 		if (file->pos + count > file->size)
334 			count = file->size - file->pos;
335 		if (count < 0)
336 			return -1;
337 		if (!count)
338 			return 0;
339 	}
340 	if (file->file)
341 		ret = fread (buf, 1, count, file->file);
342 	else
343 #ifdef HAVE_ZLIB
344 		ret = gzread (file->gzfile, buf, count);
345 #else
346 		return -1;
347 #endif
348 	if (file->sub)
349 		file->pos += ret;
350 	return ret == -1 ? ret : ret + offs;
351 }
352 
353 VISIBLE int
Qwrite(QFile * file,const void * buf,int count)354 Qwrite (QFile *file, const void *buf, int count)
355 {
356 	if (file->sub)		// can't write to a sub-file
357 		return -1;
358 	if (file->file)
359 		return fwrite (buf, 1, count, file->file);
360 #ifdef HAVE_ZLIB
361 	else
362 		return gzwrite (file->gzfile, (const voidp)buf, count);
363 #else
364 	return -1;
365 #endif
366 }
367 
368 VISIBLE int
Qprintf(QFile * file,const char * fmt,...)369 Qprintf (QFile *file, const char *fmt, ...)
370 {
371 	va_list     args;
372 	int         ret = -1;
373 
374 	if (file->sub)		// can't write to a sub-file
375 		return -1;
376 	va_start (args, fmt);
377 	if (file->file)
378 		ret = vfprintf (file->file, fmt, args);
379 #ifdef HAVE_ZLIB
380 	else {
381 		static dstring_t *buf;
382 
383 		if (!buf)
384 			buf = dstring_new ();
385 
386 		va_start (args, fmt);
387 		dvsprintf (buf, fmt, args);
388 		va_end (args);
389 		ret = strlen (buf->str);
390 		if (ret > 0)
391 			ret = gzwrite (file->gzfile, buf, (unsigned) ret);
392 	}
393 #endif
394 	va_end (args);
395 	return ret;
396 }
397 
398 VISIBLE int
Qputs(QFile * file,const char * buf)399 Qputs (QFile *file, const char *buf)
400 {
401 	if (file->sub)		// can't write to a sub-file
402 		return -1;
403 	if (file->file)
404 		return fputs (buf, file->file);
405 #ifdef HAVE_ZLIB
406 	else
407 		return gzputs (file->gzfile, buf);
408 #else
409 	return 0;
410 #endif
411 }
412 
413 VISIBLE char *
Qgets(QFile * file,char * buf,int count)414 Qgets (QFile *file, char *buf, int count)
415 {
416 	char       *ret = buf;
417 	char        c;
418 
419 	while (buf - ret < count - 1) {
420 		c = Qgetc (file);
421 		if (c < 0)
422 			break;
423 		*buf++ = c;
424 		if (c == '\n')
425 			break;
426 	}
427 	if (buf == ret)
428 		return 0;
429 
430 	*buf++ = 0;
431 	return ret;
432 }
433 
434 VISIBLE int
Qgetc(QFile * file)435 Qgetc (QFile *file)
436 {
437 	if (file->c != -1) {
438 		int         c = file->c;
439 		file->c = -1;
440 		return c;
441 	}
442 	if (file->sub) {
443 		if (file->pos >= file->size)
444 			return EOF;
445 		file->pos++;
446 	}
447 	if (file->file)
448 		return fgetc (file->file);
449 #ifdef HAVE_ZLIB
450 	else
451 		return gzgetc (file->gzfile);
452 #else
453 	return -1;
454 #endif
455 }
456 
457 VISIBLE int
Qputc(QFile * file,int c)458 Qputc (QFile *file, int c)
459 {
460 	if (file->sub)		// can't write to a sub-file
461 		return -1;
462 	if (file->file)
463 		return fputc (c, file->file);
464 #ifdef HAVE_ZLIB
465 	else
466 		return gzputc (file->gzfile, c);
467 #else
468 	return -1;
469 #endif
470 }
471 
472 VISIBLE int
Qungetc(QFile * file,int c)473 Qungetc (QFile *file, int c)
474 {
475 	if (file->c == -1)
476 		file->c = (byte) c;
477 	return c;
478 }
479 
480 VISIBLE int
Qseek(QFile * file,long offset,int whence)481 Qseek (QFile *file, long offset, int whence)
482 {
483 	int         res;
484 
485 	file->c = -1;
486 	if (file->file) {
487 		switch (whence) {
488 			case SEEK_SET:
489 				res = fseek (file->file, file->start + offset, whence);
490 				break;
491 			case SEEK_CUR:
492 				res = fseek (file->file, offset, whence);
493 				break;
494 			case SEEK_END:
495 				if (file->size == -1) {
496 					// we don't know the size (due to writing) so punt and
497 					// pass on the request as-is
498 					res = fseek (file->file, offset, SEEK_END);
499 				} else {
500 					res = fseek (file->file,
501 								 file->start + file->size - offset, SEEK_SET);
502 				}
503 				break;
504 			default:
505 				errno = EINVAL;
506 				return -1;
507 		}
508 		if (res != -1)
509 			res = ftell (file->file) - file->start;
510 		if (file->sub)
511 			file->pos = res;
512 		return res;
513 	}
514 #ifdef HAVE_ZLIB
515 	else {
516 		// libz seems to keep track of the true start position itself
517 		// doesn't support SEEK_END, though
518 		res = gzseek (file->gzfile, offset, whence);
519 		if (file->sub)
520 			file->pos = res;
521 		return res;
522 	}
523 #else
524 	return -1;
525 #endif
526 }
527 
528 VISIBLE long
Qtell(QFile * file)529 Qtell (QFile *file)
530 {
531 	int         offs;
532 	int         ret;
533 
534 	offs =  (file->c != -1) ? 1 : 0;
535 	if (file->file)
536 		ret = ftell (file->file) - file->start;
537 	else
538 #ifdef HAVE_ZLIB
539 		ret = gztell (file->gzfile);	//FIXME does gztell do the right thing?
540 #else
541 		return -1;
542 #endif
543 	if (file->sub)
544 		file->pos = ret;
545 	return ret == -1 ? ret : ret - offs;
546 }
547 
548 VISIBLE int
Qflush(QFile * file)549 Qflush (QFile *file)
550 {
551 	if (file->file)
552 		return fflush (file->file);
553 #ifdef HAVE_ZLIB
554 	else
555 		return gzflush (file->gzfile, Z_SYNC_FLUSH);
556 #else
557 	return -1;
558 #endif
559 }
560 
561 VISIBLE int
Qeof(QFile * file)562 Qeof (QFile *file)
563 {
564 	if (file->c != -1)
565 		return 0;
566 	if (file->sub)
567 		return file->pos >= file->size;
568 	if (file->file)
569 		return feof (file->file);
570 #ifdef HAVE_ZLIB
571 	else
572 		return gzeof (file->gzfile);
573 #else
574 	return -1;
575 #endif
576 }
577 
578 /*
579 	Qgetline
580 
581 	Dynamic length version of Qgets. Do NOT free the buffer.
582 */
583 VISIBLE const char *
Qgetline(QFile * file)584 Qgetline (QFile *file)
585 {
586 	static int  size = 256;
587 	static char *buf = 0;
588 	int         len;
589 
590 	if (!buf) {
591 		buf = malloc (size);
592 		if (!buf)
593 			return 0;
594 	}
595 
596 	if (!Qgets (file, buf, size))
597 		return 0;
598 
599 	len = strlen (buf);
600 	while (len && buf[len - 1] != '\n') {
601 		char       *t = realloc (buf, size + 256);
602 
603 		if (!t)
604 			return 0;
605 		buf = t;
606 		size += 256;
607 		if (!Qgets (file, buf + len, size - len))
608 			break;
609 		len = strlen (buf);
610 	}
611 	return buf;
612 }
613