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