1 /* vim: set et ts=4 sw=4 sts=4 fdm=marker syntax=c.doxygen: */
2 
3 /** \file   src/lib/base.c
4  * \brief   Base library code
5  *
6  * \author  Bas Wassink <b.wassink@ziggo.nl>
7  */
8 
9 /*
10  *  HVSClib - a library to work with High Voltage SID Collection files
11  *  Copyright (C) 2018  Bas Wassink <b.wassink@ziggo.nl>
12  *
13  *  This program is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU General Public License as published by
15  *  the Free Software Foundation; either version 2 of the License, or
16  *  (at your option) any later version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License along
24  *  with this program; if not, write to the Free Software Foundation, Inc.,
25  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.*
26  */
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <inttypes.h>
31 #include <string.h>
32 #include <limits.h>
33 #include <errno.h>
34 #include <ctype.h>
35 
36 #include "hvsc.h"
37 
38 #include "hvsc_defs.h"
39 
40 #include "base.h"
41 
42 /** \brief  Size of chunks to read in hvsc_read_file()
43  */
44 #define READFILE_BLOCK_SIZE  65536
45 
46 
47 /** \brief  Size of chunks to read int hvsc_text_file_read()
48  */
49 #define READFILE_LINE_SIZE  1024
50 
51 
52 /** \brief  Error messages
53  */
54 static const char *hvsc_err_messages[HVSC_ERR_CODE_COUNT] = {
55     "OK",
56     "out of memory error",
57     "I/O error",
58     "file too large error",
59     "libgcrypt error",
60     "malformed timestamp",
61     "object not found",
62     "invalid data or operation",
63 };
64 
65 
66 /** \brief  List of field indentifiers
67  *
68  * \see hvsc_stil_field_type_t
69  */
70 static const char *field_identifiers[] = {
71     " ARTIST:",
72     " AUTHOR:",
73     "    BUG:",     /* XXX: only used in BUGlist.txt */
74     "COMMENT:",
75     "   NAME:",
76     "  TITLE:",
77     NULL
78 };
79 
80 
81 /** \brief  List of field identifier display string for dumping
82  *
83  * This makes it more clear to distinguish parser errors (ie NAME: showing up
84  * in a field text)
85  */
86 static const char *field_displays[] = {
87     " {artist}",
88     " {author}",
89     "    {bug}",     /* XXX: only used in BUGlist.txt */
90     "{comment}",
91     "   {name}",
92     "  {title}",
93     NULL
94 };
95 
96 
97 /** \brief  Error message to return for invalid error codes
98  */
99 static const char *invalid_err_msg = "<unknown error code>";
100 
101 
102 /** \brief  Error code for the library
103  */
104 int hvsc_errno;
105 
106 
107 /** \brief  Absolute path to the HVSC root directory
108  */
109 char *hvsc_root_path;
110 
111 /** \brief  Absolute path to the SLDB file
112  */
113 char *hvsc_sldb_path;
114 
115 /** \brief  Absolute path to the STIL file
116  */
117 char *hvsc_stil_path;
118 
119 /** \brief  Absolute path to the BUGlist file
120  */
121 char *hvsc_bugs_path;
122 
123 
124 /** \brief  Get error message for errno \a n
125  *
126  * \param[in]   n   error code
127  *
128  * \return  error message
129  */
hvsc_strerror(int n)130 const char *hvsc_strerror(int n)
131 {
132     if (n < 0 || n >= HVSC_ERR_CODE_COUNT) {
133         return invalid_err_msg;
134     }
135     return hvsc_err_messages[n];
136 }
137 
138 
139 /** \brief  Print error message on `stderr` optionally with a \a prefix
140  *
141  * Prints error code and message on `stderr`, and when an I/O error was
142  * encountered, the C-library's errno and strerror() will also be printed.
143  *
144  * \param[in]   prefix  optional prefix
145  */
hvsc_perror(const char * prefix)146 void hvsc_perror(const char *prefix)
147 {
148     /* display prefix? */
149     if (prefix != NULL && *prefix != '\0') {
150         fprintf(stderr, "%s: ", prefix);
151     }
152 
153     switch (hvsc_errno) {
154 
155         case HVSC_ERR_IO:
156             /* I/O error */
157             fprintf(stderr, "%d: %s (%d: %s)\n",
158                     hvsc_errno, hvsc_strerror(hvsc_errno),
159                     errno, strerror(errno));
160             break;
161 
162         default:
163             fprintf(stderr, "%d: %s\n", hvsc_errno, hvsc_strerror(hvsc_errno));
164             break;
165     }
166 }
167 
168 
169 /** \brief  Initialize text file handle
170  *
171  * \param[in,out]   handle  text file handle
172  */
hvsc_text_file_init_handle(hvsc_text_file_t * handle)173 void hvsc_text_file_init_handle(hvsc_text_file_t *handle)
174 {
175     handle->fp = NULL;
176     handle->path = NULL;
177     handle->lineno = 0;
178     handle->linelen = 0;
179     handle->buffer = NULL;
180     handle->buflen = 0;
181 }
182 
183 
184 /** \brief  Open text file \a path for reading
185  *
186  * \param[in]       path    path to file
187  * \param[in,out]   handle  file handle, must be allocated by the caller
188  *
189  * \return  bool
190  */
hvsc_text_file_open(const char * path,hvsc_text_file_t * handle)191 int hvsc_text_file_open(const char *path, hvsc_text_file_t *handle)
192 {
193     printf("%s(): opening '%s'\n", __func__, path);
194     hvsc_text_file_init_handle(handle);
195 
196     handle->fp = fopen(path, "rb");
197     if (handle->fp == NULL) {
198         hvsc_errno = HVSC_ERR_IO;
199         return 0;
200     }
201     handle->path = hvsc_strdup(path);
202     if (handle->path == NULL) {
203         fclose(handle->fp);
204         return 0;
205     }
206 
207     handle->lineno = 0;
208 
209     handle->buffer = malloc(READFILE_LINE_SIZE);
210     if (handle->buffer == NULL) {
211         hvsc_errno = HVSC_ERR_OOM;
212         free(handle->path);
213         fclose(handle->fp);
214         return 0;
215     }
216     handle->buflen = READFILE_LINE_SIZE;
217 
218     return 1;
219 }
220 
221 
222 /** \brief  Close text file via \a handle
223  *
224  * Cleans up memory used by the members of \a handle, but not \a handle itself
225  *
226  * \param[in,out]   handle  text file handle
227  */
hvsc_text_file_close(hvsc_text_file_t * handle)228 void hvsc_text_file_close(hvsc_text_file_t *handle)
229 {
230     if (handle->path != NULL) {
231         free(handle->path);
232         handle->path = NULL;
233     }
234     if (handle->buffer != NULL) {
235         free(handle->buffer);
236         handle->buffer = NULL;
237     }
238     if (handle->fp != NULL) {
239         fclose(handle->fp);
240         handle->fp = NULL;
241     }
242 }
243 
244 
245 /** \brief  Read a line from a text file
246  *
247  * \param[in,out]   handle  text file handle
248  *
249  * \return  pointer to current line or `NULL` on failure
250  */
hvsc_text_file_read(hvsc_text_file_t * handle)251 const char *hvsc_text_file_read(hvsc_text_file_t *handle)
252 {
253     size_t i = 0;
254 
255     while (1) {
256         int ch;
257 
258         /* resize buffer? */
259         if (i == handle->buflen - 1) {
260             /* resize buffer */
261 #ifdef HVSC_BEBUG
262             printf("RESIZING BUFFER TO %lu, lineno %ld\n",
263                     (unsigned long)(handle->buflen  * 2), handle->lineno);
264 #endif
265             char *tmp = realloc(handle->buffer, handle->buflen * 2);
266             if (tmp == NULL) {
267                 hvsc_errno = HVSC_ERR_OOM;
268                 return NULL;
269             }
270             handle->buffer = tmp;
271             handle->buflen *= 2;
272         }
273 
274         ch = fgetc(handle->fp);
275         if (ch == EOF) {
276             if (feof(handle->fp)) {
277                 /* OK, proper EOF */
278                 handle->buffer[i] = '\0';
279                 if (i == 0) {
280                     return NULL;
281                 } else {
282                     return handle->buffer;
283                 }
284             } else {
285                 hvsc_errno = HVSC_ERR_IO;
286                 return NULL;
287             }
288         }
289 
290         if (ch == '\n') {
291             /* Unix EOL, strip */
292             handle->buffer[i] = '\0';
293             /* Strip Windows CR */
294             if (i > 0 && handle->buffer[i - 1] == '\r') {
295                 handle->buffer[--i] = '\0';
296             }
297             handle->lineno++;
298             handle->linelen = i;
299             return handle->buffer;
300         }
301 
302         handle->buffer[i++] = (char)ch;
303     }
304     return handle->buffer;
305 }
306 
307 
308 /** @brief  Read data from \a path into \a dest, allocating memory
309  *
310  * This function reads data from \a path, (re)allocating memory as required.
311  * The pointer to the result is stored in \a dest. If this function fails for
312  * some reason (file not found, out of memory), -1 is returned and all memory
313  * used by this function is freed.
314  *
315  * READFILE_BLOCK_SIZE bytes are read at a time, and whenever memory runs out,
316  * it is doubled in size.
317  *
318  * @note:   Since this function returns `long`, it can only be used for files
319  *          up to 2GB. Should be enough for C64 related files.
320  *
321  * Example:
322  * @code{.c}
323  *
324  *  unsigned char *data;
325  *  int result;
326  *
327  *  if ((result = hvsc_read_file(&data, "Commando.sid")) < 0) {
328  *      fprintf(stderr, "oeps!\n");
329  *  } else {
330  *      printf("OK, read %ld bytes\n", result);
331  *      free(data);
332  *  }
333  * @endcode
334  *
335  * @param   dest    destination of data
336  * @param   path    path to file
337  *
338  * @return  number of bytes read, or -1 on failure
339  */
hvsc_read_file(uint8_t ** dest,const char * path)340 long hvsc_read_file(uint8_t **dest, const char *path)
341 {
342     uint8_t *data;
343     uint8_t *tmp;
344     FILE *fd;
345     size_t offset = 0;
346     size_t size = READFILE_BLOCK_SIZE;
347     size_t result;
348 
349     fd = fopen(path, "rb");
350     if (fd == NULL) {
351         hvsc_errno = HVSC_ERR_IO;
352         return -1;
353     }
354 
355     data = malloc(READFILE_BLOCK_SIZE);
356     if (data == NULL) {
357         return -1;
358     }
359 
360     /* keep reading chunks until EOF */
361     while (1) {
362         /* need to resize? */
363         if (offset == size) {
364             /* yup */
365 
366             /* check limit */
367             if (size == (size_t)LONG_MAX + 1) {
368                 hvsc_errno = HVSC_ERR_FILE_TOO_LARGE;
369                 free(data);
370                 fclose(fd);
371                 return -1;
372             }
373 
374             tmp = realloc(data, size * 2);
375             if (tmp == NULL) {
376                 fclose(fd);
377                 free(data);
378                 return -1;
379             }
380             data = tmp;
381             size *= 2;
382         }
383         result = fread(data + offset, 1, READFILE_BLOCK_SIZE, fd);
384         if (result < READFILE_BLOCK_SIZE) {
385             if (feof(fd)) {
386                 /* OK: EOF */
387                 /* try to realloc to minimum size required */
388                 tmp = realloc(data, offset + result);
389                 if (tmp != NULL) {
390                     /* OK, no worries if it fails, the C standard guarantees
391                      * the original data is still intact */
392                     data = tmp;
393                 }
394                 *dest = data;
395                 fclose(fd);
396                 return (long)(offset + result);
397             } else {
398                 /* IO error */
399                 hvsc_errno = HVSC_ERR_IO;
400                 free(data);
401                 *dest = NULL;
402                 fclose(fd);
403                 return -1;
404             }
405         }
406         offset += READFILE_BLOCK_SIZE;
407     }
408     /* shouldn't get here */
409 }
410 
411 
412 /** \brief  Copy at most \a n chars of \a s
413  *
414  * This function appends a nul-byte after \a n bytes.
415  *
416  * \param[in]   s   string to copy
417  * \param[in]   n   maximum number of chars to copy
418  *
419  * \return  heap-allocated, nul-terminated copy of \a n bytes of \a s, or
420  *          `NULL` on failure
421  */
hvsc_strndup(const char * s,size_t n)422 char *hvsc_strndup(const char *s, size_t n)
423 {
424     char *t = calloc(n + 1, 1);
425 
426     if (t == NULL) {
427         hvsc_errno = HVSC_ERR_OOM;
428         return NULL;
429     }
430 
431     strncpy(t, s, n);
432     return t;
433 }
434 
435 
436 
437 /** \brief  Create heap-allocated copy of \a s
438  *
439  * \param[in]   s   string to copy
440  *
441  * \return  copy of \a s or `NULL` on error
442  */
hvsc_strdup(const char * s)443 char *hvsc_strdup(const char *s)
444 {
445     char *t;
446     size_t len = strlen(s);
447 
448     t = malloc(len + 1);
449     if (t == NULL) {
450         hvsc_errno = HVSC_ERR_OOM;
451         return NULL;
452     }
453     memcpy(t, s, len + 1);
454     return t;
455 }
456 
457 
458 /** \brief  Join paths \a p1 and \a p2
459  *
460  * Concatenates \a p1 and \a p2, putting a path separator between them. \a p1
461  * is expected to not contain a trailing separator and \a p2 is expected to
462  * not start with a path separator.
463  *
464  * \param[in]   p1  first path
465  * \param[in]   p2  second path
466  *
467  * \todo    Make more flexible (handle leading/trailing separators
468  * \todo    Handle Windows/DOS paths
469  *
470  * \return  heap-allocated string
471  */
hvsc_paths_join(const char * p1,const char * p2)472 char *hvsc_paths_join(const char *p1, const char *p2)
473 {
474     char *result;
475     size_t len1;
476     size_t len2;
477 
478     if (p1 == NULL || p2 == NULL) {
479         return NULL;
480     }
481 
482     len1 = strlen(p1);
483     len2 = strlen(p2);
484 
485     result = malloc(len1 + len2 + 2);   /* +2 for / and '\0' */
486     if (result == NULL) {
487         hvsc_errno = HVSC_ERR_OOM;
488         return NULL;
489     }
490 
491     memcpy(result, p1, len1);
492 #if defined(_WIN32) || defined(_WIN64)
493     *(result + len1) = '\\';
494 #else
495     *(result + len1) = '/';
496 #endif
497     memcpy(result + len1 + 1, p2, len2 + 1);    /* add p2 including '\0' */
498 
499     return result;
500 }
501 
502 
503 /** \brief  Set the path to HVSC root, SLDB and STIL
504  *
505  * \param[in]   path    path to HVSC root directory
506  *
507  * \return  bool
508  */
hvsc_set_paths(const char * path)509 int hvsc_set_paths(const char *path)
510 {
511     /* set HVSC root path */
512     hvsc_root_path = hvsc_strdup(path);
513     if (hvsc_root_path == NULL) {
514         return 0;
515     }
516 
517     /* set SLDB path */
518     hvsc_sldb_path = hvsc_paths_join(hvsc_root_path, HVSC_SLDB_FILE);
519     if (hvsc_sldb_path == NULL) {
520         free(hvsc_root_path);
521         hvsc_root_path = NULL;
522         return 0;
523     }
524 
525     /* set STIL path */
526     hvsc_stil_path = hvsc_paths_join(hvsc_root_path, HVSC_STIL_FILE);
527     if (hvsc_stil_path == NULL) {
528         free(hvsc_root_path);
529         free(hvsc_sldb_path);
530         hvsc_root_path = NULL;
531         hvsc_sldb_path = NULL;
532         return 0;
533     }
534 
535     /* set BUGlist path */
536     hvsc_bugs_path = hvsc_paths_join(hvsc_root_path, HVSC_BUGS_FILE);
537     if (hvsc_bugs_path == NULL) {
538         free(hvsc_root_path);
539         free(hvsc_sldb_path);
540         free(hvsc_stil_path);
541         hvsc_root_path = NULL;
542         hvsc_sldb_path = NULL;
543         hvsc_stil_path = NULL;
544         return 0;
545     }
546 
547     hvsc_dbg("HVSC root = %s\n", hvsc_root_path);
548     hvsc_dbg("HVSC sldb = %s\n", hvsc_sldb_path);
549     hvsc_dbg("HVSC stil = %s\n", hvsc_stil_path);
550     hvsc_dbg("HVSC bugs = %s\n", hvsc_bugs_path);
551     return 1;
552 }
553 
554 
555 /** \brief  Free memory used by the HSVC paths
556  */
hvsc_free_paths(void)557 void hvsc_free_paths(void)
558 {
559     if (hvsc_root_path != NULL) {
560         free(hvsc_root_path);
561         hvsc_root_path = NULL;
562     }
563     if (hvsc_sldb_path != NULL) {
564         free(hvsc_sldb_path);
565         hvsc_sldb_path = NULL;
566     }
567     if (hvsc_stil_path != NULL) {
568         free(hvsc_stil_path);
569         hvsc_stil_path = NULL;
570     }
571     if (hvsc_bugs_path != NULL) {
572         free(hvsc_bugs_path);
573         hvsc_bugs_path = NULL;
574     }
575 }
576 
577 
578 /** \brief  Strip the HSVC root path from \a path
579  *
580  * \param[in]   path    path to a PSID file inside the HVSC
581  *
582  * \return  heap-allocated path with the HVSC root stripped, or a heap-allocated
583  *          copy of \a path if the HVSC root wasn't present. Returns `NULL` on
584  *          memory allocation failure.
585  */
hvsc_path_strip_root(const char * path)586 char *hvsc_path_strip_root(const char *path)
587 {
588     size_t plen = strlen(path);             /* length of path */
589     size_t rlen = strlen(hvsc_root_path);   /* length of HSVC root path */
590     char *result;
591 
592     if (plen <= rlen) {
593         return hvsc_strdup(path);
594     }
595 
596     if (memcmp(path, hvsc_root_path, rlen) == 0) {
597         /* got HSVC root path */
598         result = malloc(plen - rlen + 1);
599         if (result == NULL) {
600             hvsc_errno = HVSC_ERR_OOM;
601             return NULL;
602         }
603         memcpy(result, path + rlen, plen - rlen + 1);
604         return result;
605     }
606 
607     return hvsc_strdup(path);
608 }
609 
610 
611 /** \brief  Translate all backslashes into forward slashes
612  *
613  * Since entries in the SLDB, STIL and BUGlist are listed with forward slashes,
614  * on Windows we'll need to fix the directory separators to allow strcmp() to
615  * work.
616  *
617  * \param[in,out]   path    pathname to fix
618  */
hvsc_path_fix_separators(char * path)619 void hvsc_path_fix_separators(char *path)
620 {
621     while (*path != '\0') {
622         if (*path == '\\') {
623             *path = '/';
624         }
625         path++;
626     }
627 }
628 
629 
630 /** \brief  Check if \a s contains only whitespace
631  *
632  * \param[in]   s   string to check
633  *
634  * \return  bool
635  */
hvsc_string_is_empty(const char * s)636 int hvsc_string_is_empty(const char *s)
637 {
638     while (*s != '\0' && isspace((int)*s)) {
639         s++;
640     }
641     return *s == '\0';
642 }
643 
644 
645 /** \brief  Check if \a s is a comment
646  *
647  * Checks if the first non-whitespace token in \a s is a '#', indicating a
648  * comment.
649  *
650  * \param[in]   s   string to check
651  *
652  * \return  bool
653  */
hvsc_string_is_comment(const char * s)654 int hvsc_string_is_comment(const char *s)
655 {
656     /* ignore leading whitespace (not strictly required) */
657     while (*s != '\0' && isspace((int)*s)) {
658         s++;
659     }
660     return *s == '#';
661 }
662 
663 
664 /** \brief  Parse string \a p for a timestamp and return number of seconds
665  *
666  * Parse a timestamp in the format HH:MM, return number of seconds.
667  *
668  * \param[in]   t       timestamp
669  * \param[out]  endptr  object to store pointer to first non-timestamp char
670  *
671  * \return  time in seconds or -1 on error
672  */
hvsc_parse_simple_timestamp(char * t,char ** endptr)673 long hvsc_parse_simple_timestamp(char *t, char **endptr)
674 {
675     long m = 0;
676     long s = 0;
677 
678     /* minutes */
679     while (isdigit((int)*t)) {
680         m = m * 10 + *t - '0';
681         t++;
682     }
683     if (*t != ':') {
684         /* error */
685         *endptr = t;
686         hvsc_errno = HVSC_ERR_TIMESTAMP;
687         return -1;
688     }
689 
690     /* seconds */
691     t++;
692     while (isdigit((int)*t)) {
693         s = s * 10 + *t - '0';
694         t++;
695         if (s > 59) {
696             hvsc_errno = HVSC_ERR_TIMESTAMP;
697             return -1;
698         }
699     }
700 
701     /* done */
702     *endptr = t;
703     return m * 60 + s;
704 }
705 
706 
707 
708 /** \brief  Determine is \a s hold a field identifier
709  *
710  * Checks against a list of know field identifiers.
711  *
712  * \param[in]   s   string to parse
713  *
714  * \return  field type or -1 (HVSC_FIELD_INVALID) when not found
715  *
716  * \note    returning -1 does not indicate failure, just that \a s doesn't
717  *          contain a field indentifier (ie normal text for a comment or so)
718  */
hvsc_get_field_type(const char * s)719 int hvsc_get_field_type(const char *s)
720 {
721     int i = 0;
722 
723     while (field_identifiers[i] != NULL) {
724         int result = strncmp(s, field_identifiers[i], 8);
725         if (result == 0) {
726             return i;   /* got it */
727         }
728         i++;
729     }
730     return HVSC_FIELD_INVALID;
731 }
732 
733 
734 /** \brief  Get display string for field \a type
735  *
736  * \param[in]   type    field type
737  *
738  * \return  string
739  */
hvsc_get_field_display(int type)740 const char *hvsc_get_field_display(int type)
741 {
742     if (type < 0 || type >= HVSC_FIELD_TYPE_COUNT) {
743         return "<invalid>";
744     }
745     return field_displays[type];
746 }
747 
748 
749 /** \brief  Get a 16-bit big endian unsigned integer from \a src
750  *
751  * \param[out]  dest    object to store result
752  * \param[in]   src     source data
753  */
hvsc_get_word_be(uint16_t * dest,const uint8_t * src)754 void hvsc_get_word_be(uint16_t *dest, const uint8_t *src)
755 {
756     *dest = (uint16_t)((src[0] << 8) + src[1]);
757 }
758 
759 
760 /** \brief  Get a 16-bit little endian unsigned integer from \a src
761  *
762  * \param[out]  dest    object to store result
763  * \param[in]   src     source data
764  */
hvsc_get_word_le(uint16_t * dest,const uint8_t * src)765 void hvsc_get_word_le(uint16_t *dest, const uint8_t *src)
766 {
767     *dest = (uint16_t)((src[1] << 8) + src[0]);
768 }
769 
770 
771 
772 /** \brief  Get a 32-bit big endian unsigned integer from \a src
773  *
774  * \param[out]  dest    object to store result
775  * \param[in]   src     source data
776  */
hvsc_get_longword_be(uint32_t * dest,const uint8_t * src)777 void hvsc_get_longword_be(uint32_t *dest, const uint8_t *src)
778 {
779     *dest = (uint32_t)((src[0] << 24) + (src[1] << 16) + (src[2] << 8) + src[3]);
780 }
781