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