1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27 
28 #include "slurp.h"
29 #include "util.h"
30 
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 
41 /* The dup's are because fclose closes its file descriptor even if the FILE* was acquired with fdopen, and when
42 the control gets back to slurp, it closes the fd (again). It doesn't seem to exist on Amiga OS though, so... */
43 #ifndef HAVE_DUP
44 # define dup(fd) fd
45 #endif
46 
47 /* I hate this... */
48 #ifndef O_BINARY
49 # ifdef O_RAW
50 #  define O_BINARY O_RAW
51 # else
52 #  define O_BINARY 0
53 # endif
54 #endif
55 
56 static void _slurp_closure_free(slurp_t *t)
57 {
58 	free(t->data);
59 }
60 
61 /* --------------------------------------------------------------------- */
62 
63 /* CHUNK is how much memory is allocated at once. Too large a number is a
64  * waste of memory; too small means constantly realloc'ing.
65  *
66  * <mml> also, too large a number might take the OS more than an efficient number of reads to read in one
67  *       hit -- which you could be processing/reallocing while waiting for the next bit
68  * <mml> we had something for some proggy on the server that was sucking data off stdin
69  * <mml> and had our resident c programmer and resident perl programmer competing for the fastest code
70  * <mml> but, the c coder found that after a bunch of test runs with time, 64k worked out the best case
71  * ...
72  * <mml> but, on another system with a different block size, 64 blocks may still be efficient, but 64k
73  *       might not be 64 blocks
74  * (so maybe this should grab the block size from stat() instead...) */
75 #define CHUNK 65536
76 
77 static int _slurp_stdio_pipe(slurp_t * t, int fd)
78 {
79 	int old_errno;
80 	FILE *fp;
81 	uint8_t *read_buf, *realloc_buf;
82 	size_t this_len;
83 	int chunks = 0;
84 
85 	t->data = NULL;
86 	fp = fdopen(dup(fd), "rb");
87 	if (fp == NULL)
88 		return 0;
89 
90 	do {
91 		chunks++;
92 		/* Have to cast away the const... */
93 		realloc_buf = realloc((void *) t->data, CHUNK * chunks);
94 		if (realloc_buf == NULL) {
95 			old_errno = errno;
96 			fclose(fp);
97 			free(t->data);
98 			errno = old_errno;
99 			return 0;
100 		}
101 		t->data = realloc_buf;
102 		read_buf = (void *) (t->data + (CHUNK * (chunks - 1)));
103 		this_len = fread(read_buf, 1, CHUNK, fp);
104 		if (this_len <= 0) {
105 			if (ferror(fp)) {
106 				old_errno = errno;
107 				fclose(fp);
108 				free(t->data);
109 				errno = old_errno;
110 				return 0;
111 			}
112 		}
113 		t->length += this_len;
114 	} while (this_len);
115 	fclose(fp);
116 	t->closure = _slurp_closure_free;
117 	return 1;
118 }
119 
120 static int _slurp_stdio(slurp_t * t, int fd)
121 {
122 	int old_errno;
123 	FILE *fp;
124 	size_t got = 0, need, len;
125 
126 	if (t->length == 0) {
127 		/* Hrmph. Probably a pipe or something... gotta do it the REALLY ugly way. */
128 		return _slurp_stdio_pipe(t, fd);
129 	}
130 
131 	fp = fdopen(dup(fd), "rb");
132 
133 	if (!fp)
134 		return 0;
135 
136 	t->data = (uint8_t *) malloc(t->length);
137 	if (t->data == NULL) {
138 		old_errno = errno;
139 		fclose(fp);
140 		errno = old_errno;
141 		return 0;
142 	}
143 
144 	/* Read the WHOLE thing -- fread might not get it all at once,
145 	 * so keep trying until it returns zero. */
146 	need = t->length;
147 	do {
148 		len = fread(t->data + got, 1, need, fp);
149 		if (len <= 0) {
150 			if (ferror(fp)) {
151 				old_errno = errno;
152 				fclose(fp);
153 				free(t->data);
154 				errno = old_errno;
155 				return 0;
156 			}
157 
158 			if (need > 0) {
159 				/* short file */
160 				need = 0;
161 				t->length = got;
162 			}
163 		} else {
164 			got += len;
165 			need -= len;
166 		}
167 	} while (need > 0);
168 
169 	fclose(fp);
170 	t->closure = _slurp_closure_free;
171 	return 1;
172 }
173 
174 
175 /* --------------------------------------------------------------------- */
176 
177 static slurp_t *_slurp_open(const char *filename, struct stat * buf, size_t size)
178 {
179 	slurp_t *t;
180 	int fd, old_errno;
181 
182 	if (buf && S_ISDIR(buf->st_mode)) {
183 		errno = EISDIR;
184 		return NULL;
185 	}
186 
187 	t = (slurp_t *) mem_alloc(sizeof(slurp_t));
188 	if (t == NULL)
189 		return NULL;
190 	t->pos = 0;
191 
192 	if (strcmp(filename, "-") == 0) {
193 		if (_slurp_stdio(t, STDIN_FILENO))
194 			return t;
195 		free(t);
196 		return NULL;
197 	}
198 
199 	if (size <= 0) {
200 		size = (buf ? buf->st_size : file_size(filename));
201 	}
202 
203 #ifdef WIN32
204 	switch (slurp_win32(t, filename, size)) {
205 	case 0: free(t); return NULL;
206 	case 1: return t;
207 	};
208 #endif
209 
210 #if HAVE_MMAP
211 	switch (slurp_mmap(t, filename, size)) {
212 	case 0: free(t); return NULL;
213 	case 1: return t;
214 	};
215 #endif
216 
217 	fd = open(filename, O_RDONLY | O_BINARY);
218 
219 	if (fd < 0) {
220 		free(t);
221 		return NULL;
222 	}
223 
224 	t->length = size;
225 
226 	if (_slurp_stdio(t, fd)) {
227 		close(fd);
228 		return t;
229 	}
230 
231 	old_errno = errno;
232 	close(fd);
233 	free(t);
234 	errno = old_errno;
235 	return NULL;
236 }
237 
238 slurp_t *slurp(const char *filename, struct stat * buf, size_t size)
239 {
240 	slurp_t *t = _slurp_open(filename, buf, size);
241 	uint8_t *mmdata;
242 	size_t mmlen;
243 
244 	if (!t) {
245 		return NULL;
246 	}
247 
248 	mmdata = t->data;
249 	mmlen = t->length;
250 	if (mmcmp_unpack(&mmdata, &mmlen)) {
251 		// clean up the existing data
252 		if (t->data && t->closure) {
253 			t->closure(t);
254 		}
255 		// and put the new stuff in
256 		t->length = mmlen;
257 		t->data = mmdata;
258 		t->closure = _slurp_closure_free;
259 	}
260 
261 	// TODO re-add PP20 unpacker, possibly also handle other formats?
262 
263 	return t;
264 }
265 
266 
267 void unslurp(slurp_t * t)
268 {
269 	if (!t)
270 		return;
271 	if (t->data && t->closure) {
272 		t->closure(t);
273 	}
274 	free(t);
275 }
276 
277 /* --------------------------------------------------------------------- */
278 
279 int slurp_seek(slurp_t *t, long offset, int whence)
280 {
281 	switch (whence) {
282 	default:
283 	case SEEK_SET:
284 		break;
285 	case SEEK_CUR:
286 		offset += t->pos;
287 		break;
288 	case SEEK_END:
289 		offset += t->length;
290 		break;
291 	}
292 	if (offset < 0 || (size_t) offset > t->length)
293 		return -1;
294 	t->pos = offset;
295 	return 0;
296 }
297 
298 long slurp_tell(slurp_t *t)
299 {
300 	return (long) t->pos;
301 }
302 
303 size_t slurp_read(slurp_t *t, void *ptr, size_t count)
304 {
305 	count = slurp_peek(t, ptr, count);
306 	t->pos += count;
307 	return count;
308 }
309 
310 size_t slurp_peek(slurp_t *t, void *ptr, size_t count)
311 {
312 	size_t bytesleft = t->length - t->pos;
313 	if (count > bytesleft) {
314 		// short read -- fill in any extra bytes with zeroes
315 		size_t tail = count - bytesleft;
316 		count = bytesleft;
317 		memset(ptr + count, 0, tail);
318 	}
319 	if (count)
320 		memcpy(ptr, t->data + t->pos, count);
321 	return count;
322 }
323 
324 int slurp_getc(slurp_t *t)
325 {
326 	return (t->pos < t->length) ? t->data[t->pos++] : EOF;
327 }
328 
329 int slurp_eof(slurp_t *t)
330 {
331 	return t->pos >= t->length;
332 }
333 
334