/src/ghostpdl/base/gsiorom.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2001-2024 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* %rom% IODevice implementation for a compressed in-memory filesystem */ |
18 | | |
19 | | /* |
20 | | * This file implements a special %rom% IODevice designed for embedded |
21 | | * use. It accesses a compressed filesytem image which may be stored |
22 | | * in literal ROM, or more commonly is just static data linked directly |
23 | | * into the executable. This can be used for storing postscript library |
24 | | * files, fonts, Resources or other data files that Ghostscript needs |
25 | | * to run. |
26 | | */ |
27 | | |
28 | | #include "std.h" |
29 | | #include "stdint_.h" |
30 | | #include "string_.h" |
31 | | #include "gsiorom.h" |
32 | | #include "gx.h" |
33 | | #include "gpcheck.h" |
34 | | #include "gserrors.h" |
35 | | #include "gsstruct.h" |
36 | | #include "gsutil.h" |
37 | | #include "gxiodev.h" |
38 | | #include "stream.h" |
39 | | #include "stat_.h" |
40 | | #include "zlib.h" |
41 | | |
42 | | /* device method prototypes */ |
43 | | static iodev_proc_init(romfs_init); |
44 | | static iodev_proc_finit(romfs_finit); |
45 | | static iodev_proc_open_file(romfs_open_file); |
46 | | static iodev_proc_file_status(romfs_file_status); |
47 | | static iodev_proc_enumerate_files(romfs_enumerate_files_init); |
48 | | static iodev_proc_enumerate_next(romfs_enumerate_next); |
49 | | static iodev_proc_enumerate_close(romfs_enumerate_close); |
50 | | /* close is handled by stream closure */ |
51 | | |
52 | | /* device definition */ |
53 | | const gx_io_device gs_iodev_rom = |
54 | | { |
55 | | "%rom%", "FileSystem", |
56 | | {romfs_init, romfs_finit, iodev_no_open_device, |
57 | | romfs_open_file, |
58 | | iodev_no_fopen, iodev_no_fclose, |
59 | | iodev_no_delete_file, iodev_no_rename_file, |
60 | | romfs_file_status, |
61 | | romfs_enumerate_files_init, romfs_enumerate_next, romfs_enumerate_close, |
62 | | iodev_no_get_params, iodev_no_put_params |
63 | | }, |
64 | | NULL, |
65 | | NULL |
66 | | }; |
67 | | |
68 | | /* internal state for our device */ |
69 | | typedef struct romfs_state_s { |
70 | | int atblock; /* for later when we decompress by blocks */ |
71 | | } romfs_state; |
72 | | |
73 | | gs_private_st_simple(st_romfs_state, struct romfs_state_s, "romfs_state"); |
74 | | |
75 | | typedef struct romfs_file_enum_s { |
76 | | char *pattern; /* pattern pointer */ |
77 | | int list_index; /* next node to visit */ |
78 | | gs_memory_t *memory; /* memory structure used */ |
79 | | } romfs_file_enum; |
80 | | |
81 | | gs_private_st_ptrs1(st_romfs_file_enum, struct romfs_file_enum_s, "romfs_file_enum", |
82 | | romfs_file_enum_enum_ptrs, romfs_file_enum_reloc_ptrs, pattern); |
83 | | |
84 | | static uint32_t get_u32_big_endian(const uint32_t *a); |
85 | | |
86 | | /* coverity[ -tainted_data_return ] */ |
87 | | /* coverity[ -tainted_data_argument : arg-0 ] */ |
88 | | static uint32_t |
89 | | get_u32_big_endian(const uint32_t *a) |
90 | 7.47G | { |
91 | 7.47G | uint32_t v; |
92 | 7.47G | const unsigned char *c=(const unsigned char *)a; |
93 | | |
94 | 7.47G | v = (((uint32_t)c[0])<<24) | (((uint32_t)c[1])<<16) | (((uint32_t)c[2])<<8) | c[3]; |
95 | 7.47G | return v; |
96 | 7.47G | } |
97 | | |
98 | | /* ------ Block streams, potentially compressed (read only) ------ */ |
99 | | |
100 | | /* String stream procedures */ |
101 | | static int |
102 | | s_block_read_available(stream *, gs_offset_t *), |
103 | | s_block_read_seek(stream *, gs_offset_t), |
104 | | s_block_read_close(stream *), |
105 | | s_block_read_process(stream_state *, stream_cursor_read *, |
106 | | stream_cursor_write *, bool); |
107 | | |
108 | | /* Initialize a stream for reading from a collection of blocks */ |
109 | | static void |
110 | | sread_block(register stream *s, const byte *ptr, uint len, const uint32_t *node ) |
111 | 15.4M | { |
112 | 15.4M | static const stream_procs p = { |
113 | 15.4M | s_block_read_available, s_block_read_seek, s_std_read_reset, |
114 | 15.4M | s_std_read_flush, s_block_read_close, s_block_read_process, |
115 | | NULL /* no read_switch */ |
116 | 15.4M | }; |
117 | 15.4M | s_std_init(s, (byte *)ptr, len, &p, s_mode_read + s_mode_seek); |
118 | 15.4M | s->end_status = 0; |
119 | 15.4M | s->file = (gp_file *)node; /* convenient place to put it for %rom% files */ |
120 | 15.4M | s->file_modes = s->modes; |
121 | 15.4M | s->file_offset = 0; |
122 | 15.4M | s->file_limit = S_FILE_LIMIT_MAX; |
123 | 15.4M | } |
124 | | |
125 | | /* Return the number of available bytes */ |
126 | | static int |
127 | | s_block_read_available(stream *s, gs_offset_t *pl) |
128 | 13.6M | { |
129 | 13.6M | uint32_t *node = (uint32_t *)s->file; |
130 | 13.6M | uint32_t filelen = get_u32_big_endian(node) & 0x7fffffff; /* ignore compression bit */ |
131 | | |
132 | 13.6M | *pl = filelen - s->position - (sbufptr(s) - s->cbuf); |
133 | 13.6M | if (*pl == 0 && s->close_at_eod) /* EOF */ |
134 | 5.99k | *pl = -1; |
135 | 13.6M | return 0; |
136 | 13.6M | } |
137 | | |
138 | | /* Seek in a string being read. Return 0 if OK, ERRC if not. */ |
139 | | static int |
140 | | s_block_read_seek(register stream * s, gs_offset_t pos) |
141 | 40.7M | { |
142 | 40.7M | uint32_t *node = (uint32_t *)s->file; |
143 | 40.7M | uint32_t filelen = get_u32_big_endian(node) & 0x7fffffff; /* ignore compression bit */ |
144 | 40.7M | uint end = s->cursor.r.limit - s->cbuf + 1; |
145 | 40.7M | long offset = pos - s->position; |
146 | | |
147 | 40.7M | if (pos < 0 || pos > filelen) |
148 | 5.99k | return ERRC; |
149 | 40.7M | if (offset < 0 || offset > end) { |
150 | | /* Need to pull a different block into the buffer */ |
151 | 16.9M | stream_cursor_write pw; |
152 | | |
153 | | /* buffer stays aligned to blocks */ |
154 | 16.9M | offset = (s->file_offset + pos) % ROMFS_BLOCKSIZE; |
155 | 16.9M | s->position = pos - offset; |
156 | 16.9M | pw.ptr = s->cbuf - 1; |
157 | 16.9M | pw.limit = pw.ptr + s->cbsize; |
158 | 16.9M | s->cursor.r.ptr = s->cursor.r.limit = s->cbuf - 1; |
159 | 16.9M | if ((s->end_status = s_block_read_process((stream_state *)s, NULL, &pw, 0)) == ERRC) |
160 | 0 | return ERRC; |
161 | 16.9M | if (s->end_status == 1) |
162 | 16.9M | s->end_status = 0; |
163 | 16.9M | s->cursor.r.ptr = s->cbuf - 1; |
164 | 16.9M | s->cursor.r.limit = pw.ptr; /* limit of the block just read */ |
165 | 16.9M | } |
166 | | /* Now set the read pointer to the correct place in the buffer */ |
167 | 40.7M | s->cursor.r.ptr = s->cbuf + offset - 1; |
168 | 40.7M | return 0; |
169 | 40.7M | } |
170 | | |
171 | | static int |
172 | | s_block_read_close(stream * s) |
173 | 15.4M | { |
174 | 15.4M | gs_free_object(s->memory, s->cbuf, "file_close(buffer)"); |
175 | 15.4M | s->file = 0; /* disconnect the node */ |
176 | | /* Increment the IDs to prevent further access. */ |
177 | 15.4M | s->read_id = s->write_id = (s->read_id | s->write_id) + 1; |
178 | 15.4M | return 0; |
179 | 15.4M | } |
180 | | |
181 | | static int |
182 | | s_block_read_process(stream_state * st, stream_cursor_read * ignore_pr, |
183 | | stream_cursor_write * pw, bool last) |
184 | 63.8M | { |
185 | 63.8M | int code; |
186 | 63.8M | stream *s = (stream *)st; /* no separate state */ |
187 | 63.8M | uint32_t *node = (uint32_t *)s->file; |
188 | 63.8M | uint max_count = pw->limit - pw->ptr; |
189 | 63.8M | int status = 1; |
190 | 63.8M | int compression = ((get_u32_big_endian(node) & 0x80000000) != 0) ? 1 : 0; |
191 | 63.8M | uint32_t filelen = get_u32_big_endian(node) & 0x7fffffff; /* ignore compression bit */ |
192 | 63.8M | uint32_t blocks = (filelen+ROMFS_BLOCKSIZE-1) / ROMFS_BLOCKSIZE; |
193 | 63.8M | uint32_t iblock = (s->position + s->file_offset + (s->cursor.r.limit + 1 - s->cbuf)) / ROMFS_BLOCKSIZE; |
194 | 63.8M | uint32_t block_length = get_u32_big_endian(node+1+(2*iblock)); |
195 | 63.8M | uint32_t block_offset = get_u32_big_endian(node+2+(2*iblock)); |
196 | 63.8M | unsigned const char *block_data = ((unsigned char *)node) + block_offset; |
197 | 63.8M | int count = iblock < (blocks - 1) ? ROMFS_BLOCKSIZE : filelen - (ROMFS_BLOCKSIZE * iblock); |
198 | | |
199 | 63.8M | if (s->position + (s->cursor.r.limit - s->cbuf + 1) >= filelen || block_data == NULL) |
200 | 1.33M | return EOFC; /* at EOF */ |
201 | 62.5M | if (s->file_limit < S_FILE_LIMIT_MAX) { |
202 | | /* Adjust count for subfile limit */ |
203 | 0 | uint32_t limit_count = s->file_offset + s->file_limit - s->position; |
204 | |
|
205 | 0 | if (count > limit_count) |
206 | 0 | count = limit_count; |
207 | 0 | } |
208 | | /* get the block into the buffer */ |
209 | 62.5M | if (compression) { |
210 | 62.0M | unsigned long buflen = ROMFS_BLOCKSIZE; |
211 | 62.0M | byte *dest = (pw->ptr + 1); /* destination for unpack */ |
212 | 62.0M | int need_copy = false; |
213 | | |
214 | | /* If the dest is not in our buffer, we can only use it if there */ |
215 | | /* is enough space in it */ |
216 | 62.0M | if ((dest < s->cbuf) || (dest >= (s->cbuf + s->cbsize))) { |
217 | | /* the destination is _not_ in our buffer. If the area isn't */ |
218 | | /* big enough we need to ucompress to our buffer, then copy */ |
219 | | /* the data afterward. INVARIANT: if the buffer is outside */ |
220 | | /* the cbuf, then the cbuf must be empty. */ |
221 | 21.4M | if (max_count < count) { |
222 | | #ifdef DEBUG |
223 | | if ((sbufptr(s)) < s->cursor.r.limit) |
224 | | emprintf(s->memory, "cbuf not empty as expected\n."); |
225 | | #endif |
226 | 0 | dest = s->cbuf; |
227 | 0 | need_copy = true; |
228 | 0 | } |
229 | 21.4M | } |
230 | | /* Decompress the data into this block */ |
231 | 62.0M | code = uncompress (dest, &buflen, block_data, block_length); |
232 | 62.0M | if (code != Z_OK || count != buflen) |
233 | 0 | return ERRC; |
234 | 62.0M | if (need_copy) { |
235 | 0 | memcpy(pw->ptr+1, dest, max_count); |
236 | 0 | count = max_count; |
237 | 0 | } |
238 | 62.0M | } else { |
239 | | /* not compressed -- just copy it */ |
240 | 540k | count = block_length; |
241 | 540k | if (count > max_count) |
242 | 0 | count = max_count; |
243 | 540k | memcpy(pw->ptr+1, block_data, count); |
244 | 540k | } |
245 | 62.5M | if (count < 0) |
246 | 0 | count = 0; |
247 | 62.5M | pw->ptr += count; |
248 | 62.5M | process_interrupts(s->memory); |
249 | 62.5M | return status; |
250 | 62.5M | } |
251 | | |
252 | | static int |
253 | | romfs_init(gx_io_device *iodev, gs_memory_t *mem) |
254 | 162k | { |
255 | 162k | romfs_state *state = gs_alloc_struct(mem, romfs_state, &st_romfs_state, |
256 | 162k | "romfs_init(state)"); |
257 | 162k | if (!state) |
258 | 0 | return_error(gs_error_VMerror); |
259 | 162k | iodev->state = state; |
260 | 162k | return 0; |
261 | 162k | } |
262 | | |
263 | | static void |
264 | | romfs_finit(gx_io_device *iodev, gs_memory_t *mem) |
265 | 71.8k | { |
266 | 71.8k | gs_free_object(mem, iodev->state, "romfs_finit"); |
267 | 71.8k | iodev->state = NULL; |
268 | 71.8k | return; |
269 | 71.8k | } |
270 | | |
271 | | static int |
272 | | romfs_open_file(gx_io_device *iodev, const char *fname, uint namelen, |
273 | | const char *access, stream **ps, gs_memory_t *mem) |
274 | 38.0M | { |
275 | 38.0M | extern const uint32_t *gs_romfs[]; |
276 | 38.0M | int code; |
277 | 38.0M | const uint32_t *node_scan = gs_romfs[0], *node = NULL; |
278 | 38.0M | uint32_t filelen, blocks; |
279 | 38.0M | int i; |
280 | 38.0M | char *filename; |
281 | 38.0M | char fmode[4] = "\000\000\000\000"; |
282 | | |
283 | | /* return an empty stream on error */ |
284 | 38.0M | *ps = NULL; |
285 | | |
286 | | /* scan the inodes to find the requested file */ |
287 | 6.53G | for (i=0; node_scan != 0; i++, node_scan = gs_romfs[i]) { |
288 | 6.51G | filelen = get_u32_big_endian(node_scan) & 0x7fffffff; /* ignore compression bit */ |
289 | 6.51G | blocks = (filelen+ROMFS_BLOCKSIZE-1)/ ROMFS_BLOCKSIZE; |
290 | 6.51G | filename = (char *)(&(node_scan[1+(2*blocks)])); |
291 | 6.51G | if ((namelen == strlen(filename)) && |
292 | 6.51G | (strncmp(filename, fname, namelen) == 0)) { |
293 | 15.4M | node = node_scan; |
294 | 15.4M | break; |
295 | 15.4M | } |
296 | 6.51G | } |
297 | | /* inode points to the file (or NULL if not found */ |
298 | 38.0M | if (node == NULL) |
299 | 22.5M | return_error(gs_error_undefinedfilename); |
300 | | |
301 | | /* Initialize a stream for reading this romfs file using a common function */ |
302 | | /* we get a buffer that is larger than what we need for decompression */ |
303 | | /* we need extra space since some filters may leave data in the buffer when */ |
304 | | /* calling 'read_process' */ |
305 | 15.4M | code = file_prepare_stream(fname, namelen, access, ROMFS_BLOCKSIZE+256, ps, fmode, mem); |
306 | 15.4M | if (code < 0) |
307 | 0 | return code; |
308 | 15.4M | (*ps)->modes = s_mode_read; |
309 | 15.4M | sread_block(*ps, (*ps)->cbuf, (*ps)->cbsize, node); |
310 | | /* return success */ |
311 | 15.4M | return 0; |
312 | 15.4M | } |
313 | | |
314 | | static int |
315 | | romfs_file_status(gx_io_device * iodev, const char *fname, struct stat *pstat) |
316 | 3.91M | { |
317 | 3.91M | extern const uint32_t *gs_romfs[]; |
318 | 3.91M | extern const time_t gs_romfs_buildtime; |
319 | 3.91M | const uint32_t *node_scan = gs_romfs[0], *node = NULL; |
320 | 3.91M | uint32_t filelen, blocks; |
321 | 3.91M | int i; |
322 | 3.91M | char *filename; |
323 | 3.91M | uint namelen = strlen(fname); |
324 | | |
325 | | /* a build time of zero indicates we have the "dummy" romfs |
326 | | * used when COMPILE_INITS==0 - returning a specific error here |
327 | | * gives us a quick way to check for that. |
328 | | */ |
329 | 3.91M | if (gs_romfs_buildtime == (time_t)0) { |
330 | 0 | return_error(gs_error_unregistered); |
331 | 0 | } |
332 | | |
333 | 3.91M | memset(pstat, 0, sizeof(struct stat)); |
334 | | /* scan the inodes to find the requested file */ |
335 | 524M | for (i=0; node_scan != 0; i++, node_scan = gs_romfs[i]) { |
336 | 523M | filelen = get_u32_big_endian(node_scan) & 0x7fffffff; /* ignore compression bit */ |
337 | 523M | blocks = (filelen+ROMFS_BLOCKSIZE-1)/ ROMFS_BLOCKSIZE; |
338 | 523M | filename = (char *)(&(node_scan[1+(2*blocks)])); |
339 | 523M | if ((namelen == strlen(filename)) && |
340 | 523M | (strncmp(filename, fname, namelen) == 0)) { |
341 | 2.65M | node = node_scan; |
342 | 2.65M | break; |
343 | 2.65M | } |
344 | 523M | } |
345 | | /* inode points to the file (or NULL if not found */ |
346 | 3.91M | if (node == NULL) |
347 | 1.26M | return_error(gs_error_undefinedfilename); |
348 | | |
349 | | /* fill in the values used by zstatus */ |
350 | 2.65M | pstat->st_size = filelen; |
351 | 2.65M | pstat->st_mtime = gs_romfs_buildtime; |
352 | 2.65M | pstat->st_ctime = gs_romfs_buildtime; |
353 | 2.65M | return 0; /* success */ |
354 | 3.91M | } |
355 | | |
356 | | static file_enum * |
357 | | romfs_enumerate_files_init(gs_memory_t * mem, gx_io_device *iodev, const char *pat, |
358 | | uint patlen) |
359 | 487k | { |
360 | 487k | romfs_file_enum *penum = gs_alloc_struct(mem, romfs_file_enum, &st_romfs_file_enum, |
361 | 487k | "romfs_enumerate_files_init(file_enum)"); |
362 | 487k | if (penum == NULL) |
363 | 0 | return NULL; |
364 | 487k | memset(penum, 0, sizeof(romfs_file_enum)); |
365 | 487k | penum->pattern = (char *)gs_alloc_bytes(mem, patlen+1, "romfs_enumerate_file_init(pattern)"); |
366 | 487k | penum->list_index = 0; /* start at first node */ |
367 | 487k | penum->memory = mem; |
368 | 487k | if (penum->pattern == NULL) { |
369 | 0 | romfs_enumerate_close(mem, (file_enum *) penum); |
370 | 0 | return NULL; |
371 | 0 | } |
372 | 487k | memcpy(penum->pattern, pat, patlen); /* Copy string to buffer */ |
373 | 487k | penum->pattern[patlen]=0; /* Terminate string */ |
374 | | |
375 | 487k | return (file_enum *)penum; |
376 | 487k | } |
377 | | |
378 | | static void |
379 | | romfs_enumerate_close(gs_memory_t * mem, file_enum *pfen) |
380 | 487k | { |
381 | 487k | romfs_file_enum *penum = (romfs_file_enum *)pfen; |
382 | 487k | gs_memory_t *mem2 = penum->memory; |
383 | 487k | (void)mem; |
384 | | |
385 | 487k | if (penum->pattern) |
386 | 487k | gs_free_object(mem2, penum->pattern, "romfs_enum_init(pattern)"); |
387 | 487k | gs_free_object(mem2, penum, "romfs_enum_init(romfs_enum)"); |
388 | 487k | } |
389 | | |
390 | | static uint |
391 | | romfs_enumerate_next(gs_memory_t * mem, file_enum *pfen, char *ptr, uint maxlen) |
392 | 812k | { |
393 | 812k | extern const uint32_t *gs_romfs[]; |
394 | 812k | romfs_file_enum *penum = (romfs_file_enum *)pfen; |
395 | 812k | (void)mem; |
396 | | |
397 | 131M | while (gs_romfs[penum->list_index] != 0) { |
398 | 131M | const uint32_t *node = gs_romfs[penum->list_index]; |
399 | 131M | uint32_t filelen = get_u32_big_endian(node) & 0x7fffffff; /* ignore compression bit */ |
400 | 131M | uint32_t blocks = (filelen+ROMFS_BLOCKSIZE-1)/ ROMFS_BLOCKSIZE; |
401 | 131M | char *filename = (char *)(&(node[1+(2*blocks)])); |
402 | | |
403 | 131M | penum->list_index++; /* bump to next unconditionally */ |
404 | 131M | if (string_match((byte *)filename, strlen(filename), |
405 | 131M | (byte *)penum->pattern, |
406 | 131M | strlen(penum->pattern), 0)) { |
407 | 324k | if (strlen(filename) < maxlen) |
408 | 324k | memcpy(ptr, filename, strlen(filename)); |
409 | 324k | return strlen(filename); /* if > maxlen, caller will detect rangecheck */ |
410 | 324k | } |
411 | 131M | } |
412 | | /* ran off end of list, close the enum */ |
413 | 487k | romfs_enumerate_close(mem, pfen); |
414 | 487k | return ~(uint)0; |
415 | 812k | } |
416 | | |
417 | | int |
418 | | romfs_file_len(gs_memory_t * mem, const char *fname) |
419 | 0 | { |
420 | 0 | extern const uint32_t *gs_romfs[]; |
421 | 0 | extern const time_t gs_romfs_buildtime; |
422 | 0 | const uint32_t *node_scan = gs_romfs[0], *node = NULL; |
423 | 0 | uint32_t filelen, blocks; |
424 | 0 | int i; |
425 | 0 | char *filename; |
426 | 0 | uint namelen = strlen(fname); |
427 | | |
428 | | /* a build time of zero indicates we have the "dummy" romfs |
429 | | * used when COMPILE_INITS==0 - returning a specific error here |
430 | | * gives us a quick way to check for that. |
431 | | */ |
432 | 0 | if (gs_romfs_buildtime == (time_t)0) { |
433 | 0 | return_error(gs_error_unregistered); |
434 | 0 | } |
435 | | |
436 | | /* scan the inodes to find the requested file */ |
437 | 0 | for (i=0; node_scan != 0; i++, node_scan = gs_romfs[i]) { |
438 | 0 | filelen = get_u32_big_endian(node_scan) & 0x7fffffff; /* ignore compression bit */ |
439 | 0 | blocks = (filelen+ROMFS_BLOCKSIZE-1)/ ROMFS_BLOCKSIZE; |
440 | 0 | filename = (char *)(&(node_scan[1+(2*blocks)])); |
441 | 0 | if ((namelen == strlen(filename)) && |
442 | 0 | (strncmp(filename, fname, namelen) == 0)) { |
443 | 0 | node = node_scan; |
444 | 0 | break; |
445 | 0 | } |
446 | 0 | } |
447 | | /* inode points to the file (or NULL if not found */ |
448 | 0 | if (node == NULL) |
449 | 0 | return_error(gs_error_undefinedfilename); |
450 | | |
451 | 0 | return (int)filelen; |
452 | 0 | } |