1 /*
2 * ============================================================================
3 * Title: File I/O Routines
4 * Author: J. Zbiciak
5 * ============================================================================
6 * This module contains routines for reading/writing files, including ROM
7 * images, CFG files, etc.
8 *
9 * Currently, these routines operate on LZFILE*'s rather than on filenames,
10 * since I'd like these to be able to work in structured files someday.
11 * (eg. so I can read a ROM image out of an archive, or such.)
12 * ============================================================================
13 * FILE_READ_ROM16 -- Reads a 16-bit big-endian ROM image.
14 * FILE_READ_ROM8P2 -- Reads a 10-bit ROM image in 8 plus 2 format
15 * FILE_READ_ROM10 -- Reads an 8-bit ROM image (eg. GROM).
16 * FILE_PARSE_CFG -- Parses a CFG file and returns a linked list of
17 * configuration actions to be handled by the
18 * machine configuration engine.
19 * ============================================================================
20 */
21
22
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #include "config.h"
27 #include "lzoe/lzoe.h"
28 #include "file.h"
29
30 char *exe_path;
31
32
33 /* ======================================================================== */
34 /* FILE_READ_ROM16 -- Reads a 16-bit ROM image up to 64K x 16. */
35 /* */
36 /* Leaves file pointer pointing at end of ROM image */
37 /* if read is successful. Returns 0 on success, -1 */
38 /* of failure. */
39 /* ======================================================================== */
file_read_rom16(LZFILE * f,int len,uint16_t img[])40 int file_read_rom16 (LZFILE *f, int len, uint16_t img[])
41 {
42 int r;
43
44 /* -------------------------------------------------------------------- */
45 /* Sanity check: To all the arguments make sense? */
46 /* -------------------------------------------------------------------- */
47 if (!f || !img || len < 0)
48 {
49 fprintf(stderr, "file_read_rom16: Bad parameters!\n"
50 " %p, %10d, %p\n",
51 (void *)f, len, (void *)img);
52 exit(1);
53 }
54
55 /* -------------------------------------------------------------------- */
56 /* Read in the ROM image. */
57 /* -------------------------------------------------------------------- */
58 len = lzoe_fread((void*) img, 2, len, f);
59
60
61 /* -------------------------------------------------------------------- */
62 /* Bring the ROM image into the host endian. */
63 /* -------------------------------------------------------------------- */
64 for (r = 0; r < len; r++)
65 img[r] = be_to_host_16(img[r]);
66
67 return len;
68 }
69
70 /* ======================================================================== */
71 /* FILE_READ_ROM8P2 -- Reads a 10-bit ROM image up to 64K x 16 in packed */
72 /* 8 plus 2 format. The first 'len' bytes are */
73 /* the 8 LSB's of the ROM's decles. The next */
74 /* 'len / 4' bytes hold the 2 MSBs, packed in little- */
75 /* endian order. This format is used by the VOL1, */
76 /* VOL2 resource files, and is included for */
77 /* completeness. */
78 /* */
79 /* Leaves file pointer pointing at end of ROM image */
80 /* if read is successful. Returns 0 on success, -1 */
81 /* of failure. */
82 /* ======================================================================== */
file_read_rom8p2(LZFILE * f,int len,uint16_t img[])83 int file_read_rom8p2 (LZFILE *f, int len, uint16_t img[])
84 {
85 int r, blk8sz, blk2sz, blk8, blk2, shl;
86 uint8_t *tmp;
87
88 /* -------------------------------------------------------------------- */
89 /* Sanity check: To all the arguments make sense? */
90 /* -------------------------------------------------------------------- */
91 if (!f || !img || len < 0)
92 {
93 fprintf(stderr, "file_read_rom8p2: Bad parameters!\n"
94 " %p, %10d, %p\n",
95 (void *)f, len, (void *)img);
96 exit(1);
97 }
98
99 /* -------------------------------------------------------------------- */
100 /* Calculate the sizes of the 8-bit and 2-bit sections, being careful */
101 /* to round the decle count up to handle non-multiple-of-4 images. */
102 /* -------------------------------------------------------------------- */
103 blk8sz = len;
104 blk2sz = (len + 3) >> 2;
105
106 /* -------------------------------------------------------------------- */
107 /* Read in the ROM image to a temporary storage buffer for unpacking. */
108 /* -------------------------------------------------------------------- */
109 tmp = CALLOC(uint8_t, blk8sz + blk2sz);
110
111 if (!tmp)
112 {
113 fprintf(stderr, "file_read_rom8p2: Out of memory.\n");
114 exit(1);
115 }
116
117 r = lzoe_fread(tmp, 1, blk8sz + blk2sz, f);
118
119 if (r != blk8sz + blk2sz)
120 {
121 fprintf(stderr, "file_read_rom8p2: Error reading ROM image.\n");
122 perror("fread()");
123
124 free(tmp);
125 return -1;
126 }
127
128 /* -------------------------------------------------------------------- */
129 /* Unpack the ROM image into the user's buffer. */
130 /* -------------------------------------------------------------------- */
131 for (blk8 = 0, blk2 = blk8sz; blk8 < blk8sz; blk8++)
132 {
133 shl = 8 - ((blk8 & 3) << 1);
134
135 img[blk8] = tmp[blk8] | (0x0300 & (tmp[blk2] << shl));
136
137 if ((blk8 & 3) == 3) blk2++;
138 }
139
140 free(tmp);
141
142 return len;
143 }
144
145
146 /* ======================================================================== */
147 /* FILE_READ_ROM8 -- Reads an 8-bit ROM image up to 64K x 16. */
148 /* */
149 /* Leaves file pointer pointing at end of ROM image */
150 /* if read is successful. Returns 0 on success, -1 */
151 /* of failure. */
152 /* ======================================================================== */
file_read_rom8(LZFILE * f,int len,uint16_t img[])153 int file_read_rom8 (LZFILE *f, int len, uint16_t img[])
154 {
155 int r;
156 uint16_t packed;
157
158 /* -------------------------------------------------------------------- */
159 /* Sanity check: To all the arguments make sense? */
160 /* -------------------------------------------------------------------- */
161 if (!f || !img || len < 0)
162 {
163 fprintf(stderr, "file_read_rom8: Bad parameters!\n"
164 " %p, %10d, %p\n",
165 (void *)f, len, (void *)img);
166 exit(1);
167 }
168
169 /* -------------------------------------------------------------------- */
170 /* Read in the ROM image. */
171 /* -------------------------------------------------------------------- */
172 len = lzoe_fread((void*) img, 1, len, f);
173 if (len < 1) return len;
174
175 /* -------------------------------------------------------------------- */
176 /* Unpack the ROM image. */
177 /* -------------------------------------------------------------------- */
178 len = len + (len % 2); /* Round length up to an even value */
179
180 for (r = len - 2; r >= 0; r -= 2)
181 {
182 packed = host_to_le_16(img[r >> 1]);
183
184 img[r + 1] = packed >> 8;
185 img[r + 0] = packed & 0xFF;
186 }
187
188 return len;
189 }
190
191 /* ======================================================================== */
192 /* FILE_LENGTH -- Returns the length of an open file */
193 /* ======================================================================== */
file_length(LZFILE * f)194 long file_length(LZFILE *f)
195 {
196 long here, end;
197
198 here = lzoe_ftell(f); lzoe_fseek(f, 0, SEEK_END);
199 end = lzoe_ftell(f); lzoe_fseek(f, here, SEEK_SET);
200
201 return end;
202 }
203
204 /* ======================================================================== */
205 /* FILE_EXISTS -- Determines if a given file exists. */
206 /* ======================================================================== */
file_exists(const char * pathname)207 int file_exists
208 (
209 const char *pathname
210 )
211 {
212 #ifndef NO_LZO
213 return lzoe_exists(pathname);
214 #else
215 /* -------------------------------------------------------------------- */
216 /* NOTE: access() isn't portable, so fall back to fopen() if needed. */
217 /* -------------------------------------------------------------------- */
218 #ifndef NO_ACCESS
219 return access(pathname, R_OK|F_OK) != -1;
220 #else
221 FILE *f = fopen(pathname, "r");
222
223 if (f)
224 fclose(f);
225
226 return f != NULL;
227 #endif
228 #endif
229 }
230
231 /* ======================================================================== */
232 /* IS_ABSOLUTE_PATH -- Returns non-zero if the path is absolute. */
233 /* ======================================================================== */
is_absolute_path(const char * fname)234 int is_absolute_path(const char *fname)
235 {
236 if (fname[0] == PATH_SEP)
237 return 1;
238
239 if ( has_lzoe_prefix( fname ) )
240 return 1;
241
242 #ifdef WIN32
243 /* Look for a drive letter */
244 if (isalpha(fname[0]) && fname[1] == ':' && fname[2] == PATH_SEP)
245 return 1;
246 #endif
247
248 #if defined(__AMIGAOS4__) || defined(WII)
249 /* Look for a prefix of the form "VOL:". Allow everything but the
250 * path separator to appear before a ":". */
251 {
252 const char *s;
253
254 s = fname;
255 while (*s)
256 {
257 if (*s == PATH_SEP)
258 break;
259
260 if (*s == ':')
261 return 1;
262
263 s++;
264 }
265 }
266 #endif
267
268 return 0;
269 }
270
271 /* ======================================================================== */
272 /* PATH_FOPEN -- Wrapper on fopen() that searches down a path. */
273 /* Warning: Don't use this with mode = "w" or "wb". */
274 /* ======================================================================== */
path_fopen(path_t * path,const char * fname,const char * mode)275 LZFILE *path_fopen(path_t *path, const char *fname, const char *mode)
276 {
277 int f_len, b_len;
278 char *buf;
279 LZFILE *f;
280
281 /* -------------------------------------------------------------------- */
282 /* If the path is empty, or the filename specifies an absolute path, */
283 /* just do a bare fopen. */
284 /* -------------------------------------------------------------------- */
285 if (!path || is_absolute_path(fname))
286 return lzoe_fopen(fname, mode);
287
288 /* -------------------------------------------------------------------- */
289 /* Dynamically allocate string buffer to avoid overflows. */
290 /* -------------------------------------------------------------------- */
291 f_len = strlen(fname);
292 b_len = f_len * 2 + 2;
293 buf = CALLOC(char, b_len);
294
295 /* -------------------------------------------------------------------- */
296 /* Check all the path elements. */
297 /* -------------------------------------------------------------------- */
298 while (path)
299 {
300 if (b_len < f_len + path->p_len + 2)
301 {
302 b_len = 2 * (f_len + path->p_len) + 2;
303 buf = REALLOC(buf, char, b_len);
304 }
305
306 strcpy(buf, path->name);
307 buf[path->p_len] = PATH_SEP;
308 strcpy(buf + path->p_len + 1, fname);
309
310 if ((f = lzoe_fopen(buf, mode)) != NULL)
311 {
312 free(buf);
313 return f;
314 }
315
316 path = path->next;
317 }
318
319 /* -------------------------------------------------------------------- */
320 /* Didn't find it? Give up. */
321 /* -------------------------------------------------------------------- */
322 free(buf);
323
324 return NULL;
325 }
326
327 /* ======================================================================== */
328 /* EXISTS_IN_PATH -- Looks for file along the given path, returns the */
329 /* full path if it finds it and it's readable. */
330 /* ======================================================================== */
exists_in_path(path_t * path,const char * fname)331 char *exists_in_path(path_t *path, const char *fname)
332 {
333 int f_len, b_len;
334 char *buf;
335
336 /* -------------------------------------------------------------------- */
337 /* If the path is empty, just look in CWD. */
338 /* -------------------------------------------------------------------- */
339 if (!path || is_absolute_path(fname))
340 {
341 if (file_exists(fname))
342 return strdup(fname);
343 else
344 return NULL;
345 }
346
347 /* -------------------------------------------------------------------- */
348 /* Dynamically allocate string buffer to avoid overflows. */
349 /* -------------------------------------------------------------------- */
350 f_len = strlen(fname);
351 b_len = f_len * 2 + 2;
352 buf = CALLOC(char, b_len);
353
354 /* -------------------------------------------------------------------- */
355 /* Check all the path elements. */
356 /* -------------------------------------------------------------------- */
357 while (path)
358 {
359 if (b_len < f_len + path->p_len + 2)
360 {
361 b_len = 2 * (f_len + path->p_len) + 2;
362 buf = REALLOC(buf, char, b_len);
363 }
364
365 strcpy(buf, path->name);
366 buf[path->p_len] = PATH_SEP;
367 strcpy(buf + path->p_len + 1, fname);
368
369 if (file_exists(buf))
370 return buf;
371
372 path = path->next;
373 }
374
375 /* -------------------------------------------------------------------- */
376 /* Didn't find it? Give up. */
377 /* -------------------------------------------------------------------- */
378 free(buf);
379
380 return NULL;
381 }
382
383 /* ======================================================================== */
384 /* APPEND_PATH -- Given an existing path, add a new path on the end. */
385 /* Sure, this will be slow on ridiculously long paths. */
386 /* ======================================================================== */
append_path(path_t * path,const char * fname)387 path_t *append_path(path_t *path, const char *fname)
388 {
389 path_t *head = path, **node;
390 char *local_fname;
391
392 if (exe_path && fname[0] == '=')
393 {
394 int l;
395 char *n;
396
397 l = strlen(exe_path) + strlen(fname) + 1;
398
399 if (!(n = CALLOC(char, l + 1)))
400 {
401 fprintf(stderr, "out of memory\n");
402 exit(1);
403 }
404
405 snprintf(n, l+1, "%s%c%s", exe_path, PATH_SEP, fname + 1);
406
407 local_fname = n;
408 } else
409 {
410 local_fname = strdup(fname);
411 }
412
413 for (node = &head; *node; node = &(*node)->next)
414 if (!strcmp((*node)->name, local_fname))
415 {
416 free(local_fname);
417 return head;
418 }
419
420 *node = CALLOC(path_t, 1);
421 (*node)->p_len = strlen(local_fname);
422 (*node)->name = local_fname;
423
424 return head;
425 }
426
427 /* ======================================================================== */
428 /* PARSE_PATH_STRING */
429 /* ======================================================================== */
parse_path_string(path_t * path,const char * pstr)430 path_t *parse_path_string(path_t *path, const char *pstr)
431 {
432 char *str, *p;
433
434 if (!pstr || !strlen(pstr))
435 return path;
436
437 /* get writeable local copy for strtok */
438 str = strdup(pstr);
439
440 p = strtok(str, PATH_COMPONENT_SEP);
441 while (p)
442 {
443 path = append_path(path, p);
444 p = strtok(NULL, PATH_COMPONENT_SEP);
445 }
446
447 free(str); /* dump writeable local copy */
448 return path;
449 }
450
451 /* ======================================================================== */
452 /* FREE_PATH */
453 /* ======================================================================== */
free_path(path_t * path)454 void free_path(path_t *path)
455 {
456 path_t *next;
457
458 if (!path)
459 return;
460
461 for (; path; path = next)
462 {
463 next = path->next;
464 CONDFREE(path->name);
465 free(path);
466 }
467 }
468
469 /* ======================================================================== */
470 /* DUMP_SEARCH_PATH */
471 /* ======================================================================== */
dump_search_path(path_t * path)472 void dump_search_path(path_t *path)
473 {
474 fprintf(stderr, "\nSearch path:\n");
475
476 while (path)
477 {
478 fprintf(stderr, " %s\n", path->name);
479 path = path->next;
480 }
481
482 fprintf(stderr, "\n");
483 }
484
485 /* ======================================================================== */
486 /* MAKE_ABSOLUTE_PATH */
487 /* */
488 /* Given a notion of "current working directory", try to make an absolute */
489 /* a path string. Always returns a freshly allocated string that must be */
490 /* freed by the caller. */
491 /* ======================================================================== */
make_absolute_path(const char * cwd,const char * path)492 char *make_absolute_path(const char *cwd, const char *path)
493 {
494 char *new_path;
495 int c_len = strlen(cwd);
496 int p_len = strlen(path);
497
498 if (!is_absolute_path(path))
499 {
500 new_path = (char *)malloc(c_len + p_len + 2);
501
502 memcpy(new_path, cwd, c_len);
503 memcpy(new_path + c_len + 1, path, p_len);
504
505 new_path[c_len ] = PATH_SEP;
506 new_path[c_len + p_len + 1] = 0;
507 } else
508 {
509 new_path = strdup(path);
510 }
511
512 return new_path;
513 }
514
515 #ifdef PLAT_MACOS
516 # include <mach-o/dyld.h>
517 #endif
518
519 #define MAX_CWD_PATH (65535)
520
521 #if defined(HAS_READLINK) && defined(HAS_LSTAT)
522 /* ======================================================================== */
523 /* RESOLVE_LINK -- If path is a symbolic link, return the real */
524 /* path. Otherwise just return the path as-is. */
525 /* Allocates fresh storage for the return value. */
526 /* Returns NULL on error. */
527 /* ======================================================================== */
resolve_link(const char * path)528 LOCAL char *resolve_link( const char *path )
529 {
530 char *rl_buf;
531 struct stat s;
532 ssize_t rl_len;
533
534 if ( lstat( path, &s ) != 0 )
535 return NULL;
536
537 // Not a link, so just return a copy as-is.
538 if ( !S_ISLNK(s.st_mode) )
539 return strdup(path);
540
541 // Readlink needs us to pass it a buffer
542 rl_buf = CALLOC(char, MAX_CWD_PATH);
543 if ( !rl_buf )
544 return NULL;
545
546 rl_len = readlink(path, rl_buf, MAX_CWD_PATH);
547
548 if ( rl_len < 0 )
549 {
550 free(rl_buf);
551 return NULL;
552 }
553
554 // readlink explicitly does NOT terminate with a NUL. ?!?
555 rl_buf[rl_len] = 0;
556
557 // Return a path trimmed to size. If realloc fails, it'll return NULL,
558 // so we have that going for us...
559 return REALLOC(rl_buf, char, rl_len + 1);
560 }
561 #endif
562
563 /* ======================================================================== */
564 /* GET_EXE_DIR -- Get directory containing this executable. */
565 /* Returns pointer to allocated storage. Will */
566 /* return NULL if it can't determine directory. */
567 /* ======================================================================== */
get_exe_dir(const char * const argv0)568 char *get_exe_dir(const char *const argv0)
569 {
570 char *old_exe_path;
571 char *new_exe_path;
572 char *s;
573 #ifdef WIN32
574 const int argv0_len = strlen(argv0);
575 #endif
576
577 /* -------------------------------------------------------------------- */
578 /* Disable exe_path for now, as we reuse the PATH routines above that */
579 /* do magic things if it's set. */
580 /* -------------------------------------------------------------------- */
581 old_exe_path = exe_path;
582 exe_path = NULL;
583
584 /* -------------------------------------------------------------------- */
585 /* MacOS X: _NSGetExecutablePath supposedly gets *a* path to our */
586 /* executable. Then, readlink() should get us the executable itself. */
587 /* -------------------------------------------------------------------- */
588 #ifdef PLAT_MACOS
589 {
590 char *gep_buf;
591 uint32_t gep_buf_size = MAX_CWD_PATH - 1;
592
593 gep_buf = CALLOC(char, MAX_CWD_PATH);
594 if (!gep_buf)
595 goto macosx_fail;
596
597 // Try to get the executable path
598 if ( _NSGetExecutablePath(gep_buf, &gep_buf_size) != 0 )
599 goto macosx_fail;
600
601 // Documentation doesn't say whether path is NUL terminated, to add
602 // a NUL terminator just to be sure.
603 gep_buf[gep_buf_size] = 0;
604
605 // Resolve the symlinks (if any)
606 new_exe_path = resolve_link(gep_buf);
607
608 if (new_exe_path)
609 {
610 free(gep_buf);
611 goto got_exe_path;
612 }
613
614 macosx_fail:
615 CONDFREE(gep_buf);
616 }
617 #endif
618
619 /* -------------------------------------------------------------------- */
620 /* If the system has a /proc/self/exe or similar symlink to the exe, */
621 /* then try to use that. */
622 /* -------------------------------------------------------------------- */
623 #ifdef PROC_SELF_EXE
624 new_exe_path = resolve_link(PROC_SELF_EXE);
625 if ( new_exe_path )
626 goto got_exe_path;
627 #endif
628
629 /* -------------------------------------------------------------------- */
630 /* Classical heuristic: If argv[0] is set, try the following: */
631 /* */
632 /* -- If it starts with PATH_SEP, assume it's an absolute path to */
633 /* the executable. (If it's Windows, look for drive letter & ':') */
634 /* */
635 /* -- If it contains PATH_SEP, assume it's a relative path to the */
636 /* executable. Concatenate it to CWD to get full path. */
637 /* */
638 /* -- Otherwise, examine $PATH looking for PATH/argv0. */
639 /* -------------------------------------------------------------------- */
640 if (argv0[0] == PATH_SEP)
641 {
642 new_exe_path = strdup(argv0);
643 goto got_exe_path;
644 }
645
646 #ifdef WIN32
647 if (argv0_len > 3 && isalpha(argv0[0]) && argv0[1] == ':')
648 {
649 /* ---------------------------------------------------------------- */
650 /* Common case: C:\path\to\jzintv.exe */
651 /* ---------------------------------------------------------------- */
652 if (argv0[2] == PATH_SEP)
653 {
654 new_exe_path = strdup(argv0);
655 goto got_exe_path;
656 }
657
658 /* ---------------------------------------------------------------- */
659 /* Ugh: C:foo.exe is relative to CWD on a specific drive that */
660 /* isn't necessarily the current drive. Let's just fail for now, */
661 /* as remaining code doesn't expect this abomination. */
662 /* ---------------------------------------------------------------- */
663 goto fail;
664 }
665 #endif
666
667 #ifndef NO_GETCWD
668 {
669 char *cwd_buf = NULL;
670 if (strchr(argv0, PATH_SEP))
671 {
672 cwd_buf = CALLOC(char, MAX_CWD_PATH);
673 if (!cwd_buf)
674 goto cwd_fail;
675
676 char *cwd = getcwd(cwd_buf, MAX_CWD_PATH - 1);
677 if (!cwd)
678 goto cwd_fail;
679
680 new_exe_path = make_absolute_path(cwd, argv0);
681 free(cwd_buf);
682 goto got_exe_path;
683 }
684 cwd_fail:
685 CONDFREE(cwd_buf);
686 }
687 #endif
688
689 #ifndef NO_PATH_CHECK
690 {
691 path_t *path = NULL;
692 const char *pstr = getenv("PATH");
693 char *found = NULL;
694
695 if (!pstr)
696 goto path_fail;
697
698 path = parse_path_string(NULL, pstr);
699 if (!path)
700 goto path_fail;
701
702 found = exists_in_path(path, argv0);
703 if (found)
704 {
705 free_path(path);
706 new_exe_path = found;
707 goto got_exe_path;
708 }
709 path_fail:
710 free_path(path);
711 CONDFREE(found);
712 }
713 #endif
714
715 fail:
716 /* -------------------------------------------------------------------- */
717 /* Done borrowing exe_path; restore it. */
718 /* -------------------------------------------------------------------- */
719 exe_path = old_exe_path;
720
721 #ifdef WIN32
722 /* -------------------------------------------------------------------- */
723 /* If the argv0 filename didn't end in ".exe", add it and try again. */
724 /* -------------------------------------------------------------------- */
725 if (argv0_len > 5 && stricmp(argv0 + argv0_len - 4, ".exe") != 0)
726 {
727 char *with_exe = malloc(argv0_len + 5);
728 if (with_exe)
729 {
730 memcpy(with_exe, argv0, argv0_len);
731 memcpy(with_exe + argv0_len, ".exe", 5);
732 new_exe_path = get_exe_dir(with_exe);
733 free(with_exe);
734 return new_exe_path;
735 }
736 }
737 #endif
738
739 /* -------------------------------------------------------------------- */
740 /* Return failure. Bummer, dude. */
741 /* -------------------------------------------------------------------- */
742 return NULL;
743
744 got_exe_path:
745 /* -------------------------------------------------------------------- */
746 /* Done borrowing exe_path; restore it. */
747 /* -------------------------------------------------------------------- */
748 exe_path = old_exe_path;
749
750 /* -------------------------------------------------------------------- */
751 /* Strip off the executable name, keeping only the path. */
752 /* Possible late failure mode: We can't find PATH_SEP. */
753 /* -------------------------------------------------------------------- */
754 s = strrchr(new_exe_path, PATH_SEP);
755 if (!s)
756 {
757 free(new_exe_path);
758 goto fail;
759 }
760 *s = 0;
761
762 return new_exe_path;
763 }
764
765 /* ======================================================================== */
766 /* LOAD_TEXT_FILE */
767 /* */
768 /* Loads a text file into a line-oriented structure. Attempts to deal */
769 /* with DOS (CR+LF), Mac (CR) and UNIX (LF) newline styles. Strips off */
770 /* newlines in resulting structure. */
771 /* */
772 /* The text_file structure itself contains a pointer to the file body, */
773 /* along with an array of offsets into that file. Using offsets saves */
774 /* RAM on machines where sizeof(char *) > sizeof(uint32_t). */
775 /* ======================================================================== */
load_text_file(LZFILE * f,int ts)776 text_file *load_text_file(LZFILE *f, int ts)
777 {
778 long len;
779 char *body;
780 size_t r;
781 text_file *tf;
782 uint32_t *l_ptr, idx, l_start;
783 int lines = 0, l_alloc, got_line = 0;
784
785 /* -------------------------------------------------------------------- */
786 /* Allocate one large buffer for the entire file. */
787 /* -------------------------------------------------------------------- */
788 len = file_length(f);
789
790 if (len > 1 << 30 || len < 1)
791 return NULL;
792
793 if (!(body = (char *)malloc(len)))
794 return NULL;
795
796 /* -------------------------------------------------------------------- */
797 /* Slurp in the entire file. */
798 /* -------------------------------------------------------------------- */
799 r = lzoe_fread(body, 1, len, f);
800 if ((int)r < len)
801 {
802 free(body);
803 return NULL;
804 }
805
806 /* -------------------------------------------------------------------- */
807 /* Convert tabs to spaces if asked to do so. */
808 /* -------------------------------------------------------------------- */
809 if (ts > 0)
810 {
811 int new_len = len;
812 int col = 0;
813 char *os, *ns;
814
815 /* Initial scan: Count up how much white space we'll add. */
816 if (ts > 1)
817 for (idx = 0; idx < (uint32_t)len; idx++)
818 {
819 int ch = body[idx];
820
821 if (ch == '\t')
822 {
823 int tab;
824
825 tab = ts - (col % ts);
826 col += tab;
827 new_len += tab - 1;
828 } else
829 {
830 col++;
831 }
832
833 if (ch == '\012' || ch == '\015')
834 col = 0;
835 }
836
837 /* Next, if the file got larger, reallocate the buffer and slide */
838 /* it to the end so we can do this in-place. */
839 if (new_len > len)
840 {
841 body = REALLOC(body, char, new_len);
842 if (!body)
843 return NULL;
844
845 os = body + new_len - len;
846 ns = body;
847 memmove(os, ns, len);
848 } else
849 {
850 os = ns = body;
851 }
852
853 /* Now expand-in-place. */
854 col = 0;
855 for (idx = 0; idx < (uint32_t)len; idx++)
856 {
857 int ch = *os++;
858
859 if (ch == '\t')
860 {
861 int tab;
862
863 tab = ts - (col % ts);
864 col += tab;
865
866 while (tab-- > 0)
867 *ns++ = ' ';
868 } else
869 {
870 col++;
871 *ns++ = ch;
872 }
873
874 if (ch == '\012' || ch == '\015')
875 col = 0;
876
877 assert(ns <= os);
878 }
879
880 len = new_len;
881 }
882
883 /* -------------------------------------------------------------------- */
884 /* Start with an initial line-pointer buffer. */
885 /* -------------------------------------------------------------------- */
886 l_alloc = 4096;
887 l_ptr = (uint32_t *)malloc(sizeof(uint32_t) * l_alloc);
888
889
890 /* -------------------------------------------------------------------- */
891 /* Find the line breaks and poke in NULs. */
892 /* -------------------------------------------------------------------- */
893 l_start = 0;
894 idx = 0;
895 while (idx < (uint32_t)len)
896 {
897 if (body[idx] == '\012') /* LF: Assume it's UNIX */
898 {
899 body[idx] = 0;
900 got_line = 1;
901 }
902
903 if (body[idx] == '\015') /* CR: Could be DOS or Mac */
904 {
905 body[idx] = 0;
906 if (body[idx + 1] == '\012') /* Skip LF immediately after CR */
907 idx++;
908 got_line = 1;
909 }
910
911 idx++;
912
913 if (got_line)
914 {
915 if (lines >= l_alloc)
916 {
917 l_alloc <<= 1;
918 l_ptr = REALLOC(l_ptr, uint32_t, l_alloc);
919 }
920 l_ptr[lines++] = l_start;
921 l_start = idx;
922 got_line = 0;
923 }
924 }
925
926 /* -------------------------------------------------------------------- */
927 /* Finally, construct the text_file structure. */
928 /* -------------------------------------------------------------------- */
929 tf = CALLOC(text_file, 1);
930 tf->body = body;
931 tf->line = l_ptr;
932 tf->lines = lines;
933
934 return tf;
935 }
936
937 /* ======================================================================== */
938 /* OPEN_UNIQUE_FILENAME */
939 /* Given a specification, generate a unique filename and open the file. */
940 /* ======================================================================== */
open_unique_filename(unique_filename_t * spec)941 FILE *open_unique_filename(unique_filename_t *spec)
942 {
943 uint32_t wrap_thresh = 1;
944 int attempts = 0, i;
945 FILE *f = NULL;
946
947 if (!spec->f_name)
948 {
949 /* Assume the index could never be more than ~10 digits */
950 uint32_t alloc = strlen(spec->prefix) + strlen(spec->suffix) + 12;
951 spec->f_name = CALLOC(char, alloc);
952 spec->alloc = alloc;
953 }
954
955 if (spec->num_digits < 1)
956 spec->num_digits = 4;
957
958 for (i = 0; i < spec->num_digits; i++)
959 wrap_thresh *= 10;
960
961 do
962 {
963 spec->last_idx++;
964
965 if (spec->last_idx >= wrap_thresh)
966 {
967 spec->num_digits++;
968 wrap_thresh *= 10;
969 }
970
971 snprintf(spec->f_name, spec->alloc, "%s%.*d%s",
972 spec->prefix, spec->num_digits, spec->last_idx, spec->suffix);
973
974 if (!file_exists(spec->f_name))
975 {
976 attempts++;
977 f = fopen(spec->f_name, "wb");
978 }
979 } while (!f && attempts < 10 && spec->last_idx != 0);
980
981 return f;
982 }
983
984 /* ======================================================================== */
985 /* This program is free software; you can redistribute it and/or modify */
986 /* it under the terms of the GNU General Public License as published by */
987 /* the Free Software Foundation; either version 2 of the License, or */
988 /* (at your option) any later version. */
989 /* */
990 /* This program is distributed in the hope that it will be useful, */
991 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
992 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU */
993 /* General Public License for more details. */
994 /* */
995 /* You should have received a copy of the GNU General Public License along */
996 /* with this program; if not, write to the Free Software Foundation, Inc., */
997 /* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
998 /* ======================================================================== */
999 /* Copyright (c) 1998-2020, Joseph Zbiciak */
1000 /* ======================================================================== */
1001