Mercurial > hg > sidinfo
view sidlib.c @ 341:fe061ead51cc
Perform character set conversion after item formatting step instead of before it. This should remedy the potential issue of formatting not taking UTF8 multibyte into account, as our formatting unfortunately does not support multibyte encoding. This way the data should stay in ISO-8859-1 format just up to outputting it.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 13 Jan 2020 16:29:47 +0200 |
parents | 629876cc0540 |
children | c2ebcb0f0d62 |
line wrap: on
line source
/* * SIDInfoLib - Way too simplistic PSID/RSID file library * Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2014-2020 Tecnic Software productions (TNSP) */ #include "sidlib.h" #include "th_endian.h" #include "th_string.h" static char *sidlib_strdup_convert(SIDLibChConvCtx *chconv, const char *str) { if (chconv != NULL) return chconv->convert(chconv, str); else return th_strdup(str); } static BOOL sidlib_fread_str(th_ioctx *ctx, SIDLibChConvCtx *chconv, char **str, const size_t len) { char *tmp; if ((*str = tmp = th_malloc(len + 1)) == NULL) { th_io_error(ctx, THERR_MALLOC, "Could not allocate %" PRIu_SIZE_T " bytes for a string.", len); goto err; } if (!thfread_str(ctx, tmp, len)) { th_io_error(ctx, THERR_FREAD, "Could not read %" PRIu_SIZE_T " bytes from file.", len); goto err; } tmp[len] = 0; if (chconv != NULL) { *str = chconv->convert(chconv, tmp); th_free(tmp); } return TRUE; err: th_free(tmp); return FALSE; } static int sidlib_read_hash_data(th_ioctx *ctx, SIDLibPSIDHeader *psid, th_md5state_t *state, const BOOL newSLDB) { int ret = THERR_OK; uint8_t *data = NULL; BOOL first = TRUE; size_t read; if ((data = (uint8_t *) th_malloc(SIDLIB_BUFFER_SIZE)) == NULL) { ret = th_io_error(ctx, THERR_MALLOC, "Error allocating temporary data buffer of %d bytes.", SIDLIB_BUFFER_SIZE); goto exit; } psid->dataSize = 0; do { read = thfread(data, sizeof(uint8_t), SIDLIB_BUFFER_SIZE, ctx); psid->dataSize += read; // If load address is 0 in header and we have the first block, grab it if (first && psid->loadAddress == 0) { if (read < 4) { ret = th_io_error(ctx, THERR_FREAD, "Error reading song data, unexpectedly small file."); goto exit; } // Grab the load address psid->loadAddress = TH_LE16_TO_NATIVE(*(uint16_t *) data); // .. do not include the load address to the hash if NEW SLDB format if (newSLDB) th_md5_append(state, &data[2], read - 2); else th_md5_append(state, data, read); first = FALSE; } else if (read > 0) { // Append data "as is" th_md5_append(state, data, read); } } while (read > 0 && !thfeof(ctx)); exit: th_free(data); return ret; } int sidlib_read_sid_file(th_ioctx *ctx, SIDLibPSIDHeader *psid, const BOOL newSLDB, SIDLibChConvCtx *chconv) { int ret = THERR_OK; th_md5state_t state; off_t hdrStart, hdrEnd; hdrStart = thftell(ctx); // Read PSID header in if (!thfread_str(ctx, (uint8_t *) psid->magic, SIDLIB_PSID_MAGIC_LEN) || !thfread_be16(ctx, &psid->version) || !thfread_be16(ctx, &psid->dataOffset) || !thfread_be16(ctx, &psid->loadAddress) || !thfread_be16(ctx, &psid->initAddress) || !thfread_be16(ctx, &psid->playAddress) || !thfread_be16(ctx, &psid->nSongs) || !thfread_be16(ctx, &psid->startSong) || !thfread_be32(ctx, &psid->speed)) { ret = th_io_error(ctx, ctx->status, "Could not read PSID/RSID header."); goto exit; } psid->magic[SIDLIB_PSID_MAGIC_LEN] = 0; if ((psid->magic[0] != 'R' && psid->magic[0] != 'P') || psid->magic[1] != 'S' || psid->magic[2] != 'I' || psid->magic[3] != 'D' || psid->version < 1 || psid->version > 4) { ret = th_io_error(ctx, THERR_NOT_SUPPORTED, "Not a supported PSID or RSID file."); goto exit; } psid->isRSID = psid->magic[0] == 'R'; if (!sidlib_fread_str(ctx, chconv, &psid->sidName, SIDLIB_PSID_STR_LEN) || !sidlib_fread_str(ctx, chconv, &psid->sidAuthor, SIDLIB_PSID_STR_LEN) || !sidlib_fread_str(ctx, chconv, &psid->sidCopyright, SIDLIB_PSID_STR_LEN)) { ret = th_io_error(ctx, ctx->status, "Error reading SID file header data."); goto exit; } // Check if we need to load PSIDv2NG header ... if (psid->version >= 2) { // Yes, we need to if (!thfread_be16(ctx, &psid->flags) || !thfread_u8(ctx, &psid->startPage) || !thfread_u8(ctx, &psid->pageLength) || !thfread_u8(ctx, &psid->sid2Addr) || !thfread_u8(ctx, &psid->sid3Addr)) { ret = th_io_error(ctx, ctx->status, "Error reading PSID/RSID v2+ extra header data."); goto exit; } } hdrEnd = thftell(ctx); // Initialize MD5-hash calculation th_md5_init(&state); if (newSLDB) { // New Songlengths.md5 style hash calculation: // We just hash the whole file, so seek back to beginning .. thfseek(ctx, hdrStart, SEEK_SET); if ((ret = sidlib_read_hash_data(ctx, psid, &state, FALSE)) != THERR_OK) goto exit; psid->dataSize -= hdrEnd - hdrStart; } else { // "Old" Songlengths.txt style MD5 hash calculation // We need to separately hash data etc. if ((ret = sidlib_read_hash_data(ctx, psid, &state, TRUE)) != THERR_OK) goto exit; // Append header data to hash th_md5_append_le16(&state, psid->initAddress); th_md5_append_le16(&state, psid->playAddress); th_md5_append_le16(&state, psid->nSongs); // Append song speed data to hash uint8_t tmp8 = psid->isRSID ? 60 : 0; for (int index = 0; index < psid->nSongs && index < 32; index++) { if (psid->isRSID) tmp8 = 60; else tmp8 = (psid->speed & (1 << index)) ? 60 : 0; th_md5_append(&state, &tmp8, sizeof(tmp8)); } // Rest of songs (more than 32) for (int index = 32; index < psid->nSongs; index++) th_md5_append(&state, &tmp8, sizeof(tmp8)); // PSIDv2NG specific if (psid->version >= 2) { // REFER TO SIDPLAY HEADERS FOR MORE INFORMATION tmp8 = (psid->flags >> 2) & 3; if (tmp8 == 2) th_md5_append(&state, &tmp8, sizeof(tmp8)); } } // Calculate the hash th_md5_finish(&state, psid->hash); exit: return ret; } int sidlib_read_sid_file_alloc(th_ioctx *ctx, SIDLibPSIDHeader **ppsid, const BOOL newSLDB, SIDLibChConvCtx *chconv) { if ((*ppsid = th_malloc0(sizeof(SIDLibPSIDHeader))) == NULL) return THERR_MALLOC; (*ppsid)->allocated = TRUE; return sidlib_read_sid_file(ctx, *ppsid, newSLDB, chconv); } void sidlib_free_sid_file(SIDLibPSIDHeader *psid) { if (psid != NULL) { th_free(psid->sidName); th_free(psid->sidAuthor); th_free(psid->sidCopyright); if (psid->allocated) th_free(psid); } } const char *sidlib_get_sid_clock_str(const int flags) { switch (flags) { case PSF_CLOCK_UNKNOWN : return "Unknown"; case PSF_CLOCK_PAL : return "PAL 50Hz"; case PSF_CLOCK_NTSC : return "NTSC 60Hz"; case PSF_CLOCK_ANY : return "PAL / NTSC"; default : return "?"; } } const char *sidlib_get_sid_model_str(const int flags) { switch (flags) { case PSF_MODEL_UNKNOWN : return "Unknown"; case PSF_MODEL_MOS6581 : return "MOS6581"; case PSF_MODEL_MOS8580 : return "MOS8580"; case PSF_MODEL_ANY : return "MOS6581 / MOS8580"; default : return "?"; } } // Free memory allocated for given SLDB node // static void sidlib_sldb_node_free(SIDLibSLDBNode *node) { if (node != NULL) { th_free(node->lengths); th_free(node); } } // Parse a time-entry in SLDB format // static int sidlib_sldb_get_value(const char *str, size_t *pos) { int result = 0; while (isdigit(str[*pos])) result = (result * 10) + (str[(*pos)++] - '0'); return result; } static int sidlib_sldb_gettime(const char *str, size_t *pos) { int result; // Check if it starts with a digit if (th_isdigit(str[*pos])) { // Get minutes-field result = sidlib_sldb_get_value(str, pos) * 60; // Check the field separator char if (str[*pos] == ':') { // Get seconds-field (*pos)++; result += sidlib_sldb_get_value(str, pos); } else result = -2; } else result = -1; // Ignore and skip the possible attributes while (str[*pos] && !th_isspace(str[*pos])) (*pos)++; return result; } // Parse one SLDB definition line, return SLDB node // static int sidlib_sldb_parse_entry(th_ioctx *ctx, SIDLibSLDBNode **pnode, const char *line) { int ret = THERR_OK; SIDLibSLDBNode *node; size_t pos, tmpLen, savePos; BOOL isOK; int i; // Allocate new node if ((*pnode = node = (SIDLibSLDBNode *) th_malloc0(sizeof(SIDLibSLDBNode))) == NULL) { ret = th_io_error(ctx, THERR_MALLOC, "Error allocating new SLDB node."); goto exit; } // Get hash value for (pos = 0, i = 0; i < TH_MD5HASH_LENGTH; i++, pos += 2) { unsigned int tmpu; sscanf(&line[pos], "%2x", &tmpu); node->hash[i] = tmpu; } // Get playtimes th_findnext(line, &pos); if (line[pos] != '=') { ret = th_io_error(ctx, THERR_INVALID_DATA, "'=' expected on column #%" PRIu_SIZE_T ".", pos); goto exit; } // First playtime is after '=' savePos = ++pos; tmpLen = strlen(line); // Get number of sub-tune lengths isOK = TRUE; while (pos < tmpLen && isOK) { th_findnext(line, &pos); if (sidlib_sldb_gettime(line, &pos) >= 0) node->nlengths++; else isOK = FALSE; } // Allocate memory for lengths if (node->nlengths == 0) { ret = THERR_OK; goto exit; } if ((node->lengths = (int *) th_calloc(node->nlengths, sizeof(int))) == NULL) { ret = th_io_error(ctx, THERR_MALLOC, "Error allocating SLDB entry length data."); goto exit; } // Read lengths in for (i = 0, pos = savePos, isOK = TRUE; pos < tmpLen && i < node->nlengths && isOK; i++) { int l; th_findnext(line, &pos); l = sidlib_sldb_gettime(line, &pos); if (l >= 0) node->lengths[i] = l; else isOK = FALSE; } return ret; exit: sidlib_sldb_node_free(node); return ret; } int sidlib_sldb_new(SIDLibSLDB **pdbh) { SIDLibSLDB *dbh; if ((dbh = *pdbh = (SIDLibSLDB *) th_malloc0(sizeof(SIDLibSLDB))) == NULL) return THERR_MALLOC; return THERR_OK; } // Read SLDB database to memory // int sidlib_sldb_read(th_ioctx *ctx, SIDLibSLDB *dbh) { int ret = THERR_OK; char *line = NULL; if ((line = th_malloc(SIDLIB_BUFFER_SIZE)) == NULL) { ret = th_io_error(ctx, THERR_MALLOC, "Error allocating temporary data buffer of %d bytes.", SIDLIB_BUFFER_SIZE); goto exit; } while (thfgets(line, SIDLIB_BUFFER_SIZE, ctx) != NULL) { SIDLibSLDBNode *tmnode; size_t pos = 0; ctx->line++; th_findnext(line, &pos); // Check if it is datafield if (th_isxdigit(line[pos])) { // Check the length of the hash int hashLen; for (hashLen = 0; line[pos] && th_isxdigit(line[pos]); hashLen++, pos++); if (hashLen != TH_MD5HASH_LENGTH_CH) { ret = th_io_error(ctx, THERR_INVALID_DATA, "Invalid MD5-hash."); goto exit; } else { // Parse and add node to db if ((ret = sidlib_sldb_parse_entry(ctx, &tmnode, line)) != THERR_OK) goto exit; th_llist_append_node((th_llist_t **) &dbh->nodes, (th_llist_t *) tmnode); } } else if (line[pos] != ';' && line[pos] != '[' && line[pos] != 0) { ret = th_io_error(ctx, THERR_INVALID_DATA, "Invalid line / unexpected data at column #%" PRIu_SIZE_T ".", pos); goto exit; } } exit: th_free(line); return ret; } // Compare two given MD5-hashes. // Return: 0 if equal // negative if hash1 < hash2 // positive if hash1 > hash2 // static int sidlib_sldb_compare_hash(th_md5hash_t hash1, th_md5hash_t hash2) { int i, delta; for (i = delta = 0; i < TH_MD5HASH_LENGTH && !delta; i++) delta = hash1[i] - hash2[i]; return delta; } // Compare two nodes. // We assume here that we never ever get NULL-pointers. static int sidlib_sldb_compare_nodes(const void *node1, const void *node2) { return sidlib_sldb_compare_hash( (*(SIDLibSLDBNode **) node1)->hash, (*(SIDLibSLDBNode **) node2)->hash); } // (Re)create index // int sidlib_sldb_build_index(SIDLibSLDB *dbh) { if (dbh == NULL) return THERR_NULLPTR; // Free old index th_free_r(&(dbh->pindex)); // Get size of db dbh->nnodes = th_llist_length((th_llist_t *) dbh->nodes); // Check number of nodes if (dbh->nnodes > 0) { SIDLibSLDBNode *node; size_t i; // Check number of nodes against overflow if (dbh->nnodes > UINTPTR_MAX / sizeof(SIDLibSTILNode *)) return THERR_BOUNDS; // Allocate memory for index-table dbh->pindex = (SIDLibSLDBNode **) th_malloc(sizeof(SIDLibSLDBNode *) * dbh->nnodes); if (dbh->pindex == NULL) return THERR_MALLOC; // Get node-pointers to table for (i = 0, node = dbh->nodes; node != NULL && i < dbh->nnodes; node = (SIDLibSLDBNode *) node->node.next) dbh->pindex[i++] = node; // Sort the indexes qsort(dbh->pindex, dbh->nnodes, sizeof(SIDLibSLDBNode *), sidlib_sldb_compare_nodes); } return THERR_OK; } // Free a given song-length database // void sidlib_sldb_free(SIDLibSLDB *dbh) { if (dbh != NULL) { th_llist_free_func_node((th_llist_t *) dbh->nodes, (void (*)(th_llist_t *)) sidlib_sldb_node_free); dbh->nodes = NULL; dbh->nnodes = 0; th_free_r(&dbh->pindex); th_free(dbh); } } SIDLibSLDBNode *sidlib_sldb_get_by_hash(SIDLibSLDB *dbh, th_md5hash_t hash) { SIDLibSLDBNode keyItem, *key, **item; memcpy(&keyItem.hash, hash, sizeof(th_md5hash_t)); key = &keyItem; item = bsearch(&key, dbh->pindex, dbh->nnodes, sizeof(dbh->pindex[0]), sidlib_sldb_compare_nodes); return (item != NULL) ? *item : NULL; } /* STIL database handling functions */ int sidlib_stildb_new(SIDLibSTILDB **pdbh) { SIDLibSTILDB *dbh; if ((dbh = *pdbh = (SIDLibSTILDB *) th_malloc0(sizeof(SIDLibSTILDB))) == NULL) return THERR_MALLOC; return THERR_OK; } SIDLibSTILSubTune *sidlib_stildb_find_subtune(SIDLibSTILNode *node, const int nsubtune) { for (size_t n = 0; n < node->nsubtunes; n++) { SIDLibSTILSubTune *subtune = &node->subtunes[n]; if (subtune->tune == nsubtune) return subtune; } return NULL; } static int sidlib_stildb_entry_realloc( SIDLibSTILNode *node, const int nsubtune, const BOOL alloc, const int nfield, const char *fdata, SIDLibChConvCtx *chconv) { SIDLibSTILSubTune *subtune; if (node == NULL) return THERR_NULLPTR; // Get subtune or allocate new if ((subtune = sidlib_stildb_find_subtune(node, nsubtune)) == NULL) { node->subtunes = (SIDLibSTILSubTune *) th_realloc( node->subtunes, (node->nsubtunes + 1) * sizeof(SIDLibSTILSubTune)); if (node->subtunes == NULL) return THERR_MALLOC; subtune = &node->subtunes[node->nsubtunes]; memset(subtune, 0, sizeof(SIDLibSTILSubTune)); subtune->tune = nsubtune; node->nsubtunes++; } // Allocate field space, if needed if (alloc && nfield >= 0 && nfield < STF_LAST) { SIDLibSTILField *field = &subtune->fields[nfield]; if ((field->data = th_realloc(field->data, (field->ndata + 1) * sizeof(char *))) == NULL) return THERR_MALLOC; field->data[field->ndata] = sidlib_strdup_convert(chconv, fdata); field->ndata++; } return THERR_OK; } static void sidlib_stildb_node_free(SIDLibSTILNode *node) { if (node != NULL) { for (size_t nsubtune = 0; nsubtune < node->nsubtunes; nsubtune++) { SIDLibSTILSubTune *subtune = &node->subtunes[nsubtune]; for (int nfield = 0; nfield < STF_LAST; nfield++) { SIDLibSTILField *field = &subtune->fields[nfield]; for (int n = 0; n < field->ndata; n++) th_free(field->data[n]); th_free(field->data); } } th_free(node->subtunes); th_free(node->filename); th_free(node); } } static int sidlib_stildb_node_new(SIDLibSTILNode **pnode, const char *filename, SIDLibChConvCtx *chconv) { // Allocate memory for new node SIDLibSTILNode *node; int res; if ((node = *pnode = (SIDLibSTILNode *) th_malloc0(sizeof(SIDLibSTILNode))) == NULL) return THERR_MALLOC; // Allocate filename and initial space for one subtune // NOTE! Filename is not converted even if chconv != NULL if ((node->filename = th_strdup(filename)) == NULL) return THERR_MALLOC; if ((res = sidlib_stildb_entry_realloc(node, 1, FALSE, -1, NULL, chconv)) != THERR_OK) { sidlib_stildb_node_free(node); return res; } return res; } /* Free a given STIL database */ void sidlib_stildb_free(SIDLibSTILDB *dbh) { if (dbh != NULL) { th_llist_free_func_node((th_llist_t *) dbh->nodes, (void (*)(th_llist_t *)) sidlib_stildb_node_free); dbh->nodes = NULL; dbh->nnodes = 0; th_free_r(&dbh->pindex); th_free(dbh); } } /* Read database (additively) to given db-structure */ enum { PM_EOF, PM_ERROR, PM_IDLE, PM_COMMENT, PM_NEXT, PM_ENTRY, PM_FIELD_NAME, PM_FIELD_DATA, PM_SUBTUNE, PM_LAST }; const char *sidlib_stil_fields[STF_LAST] = { "NAME", "AUTHOR", "TITLE", "INFO", "ARTIST", "COMMENT", // STF_LAST }; typedef struct { size_t lineNum; ssize_t linePos; int ch, prevMode, nextMode, parseMode; BOOL lineStart; } SIDLibSTILParserCtx; static int sidlib_stildb_get_field(const char *name) { for (int n = 0; n < STF_LAST; n++) { if (strcmp(sidlib_stil_fields[n], name) == 0) return n; } return -1; } static void sidlib_stildb_set_parsemode(SIDLibSTILParserCtx *ctx, const int mode) { ctx->prevMode = ctx->parseMode; ctx->parseMode = mode; } static void sidlib_stildb_set_next_parsemode(SIDLibSTILParserCtx *ctx, const int mode) { sidlib_stildb_set_parsemode(ctx, PM_NEXT); ctx->nextMode = mode; } #define VADDCH(ch) if (strPos < SIDLIB_BUFFER_SIZE) { tmpStr[strPos++] = ch; } int sidlib_stildb_read(th_ioctx *fh, SIDLibSTILDB *dbh, SIDLibChConvCtx *chconv) { int ret = THERR_OK, field = -1; SIDLibSTILParserCtx ctx; char *tmpStr = NULL, *fieldName = NULL; size_t strPos; SIDLibSTILNode *entry = NULL; int subtune; if (fh == NULL || dbh == NULL) return THERR_NULLPTR; if ((tmpStr = th_malloc(SIDLIB_BUFFER_SIZE + 1)) == NULL) return THERR_MALLOC; // Initialize values memset(&ctx, 0, sizeof(ctx)); ctx.nextMode = ctx.prevMode = ctx.parseMode = PM_IDLE; ctx.ch = -1; ctx.lineStart = TRUE; strPos = 0; // Parse the STIL database while (ctx.parseMode != PM_EOF && ctx.parseMode != PM_ERROR) { if (ctx.ch == -1) { // Get next character switch (ctx.ch = thfgetc(fh)) { case '\n': fh->line++; ctx.linePos = -1; break; default: if (ctx.linePos < 0) { ctx.lineStart = TRUE; ctx.linePos = 0; } else ctx.linePos++; } } switch (ctx.parseMode) { case PM_COMMENT: // Comment parsing mode if (ctx.ch == '\n') { // End of line, end of comment sidlib_stildb_set_parsemode(&ctx, ctx.prevMode); } ctx.ch = -1; break; case PM_NEXT: case PM_IDLE: // Normal parsing mode if (ctx.ch == EOF) { sidlib_stildb_set_parsemode(&ctx, PM_EOF); } else if (ctx.ch == '#') { sidlib_stildb_set_parsemode(&ctx, PM_COMMENT); ctx.ch = -1; } else if (th_iscrlf(ctx.ch) || th_isspace(ctx.ch)) { ctx.ch = -1; } else if (ctx.parseMode == PM_IDLE) { // PM_IDLE strPos = 0; if (ctx.ch == '/') { sidlib_stildb_set_parsemode(&ctx, PM_ENTRY); } else if (ctx.ch == '(') { sidlib_stildb_set_parsemode(&ctx, PM_SUBTUNE); ctx.ch = -1; ctx.lineStart = TRUE; } else if (th_isalpha(ctx.ch)) { sidlib_stildb_set_parsemode(&ctx, PM_FIELD_NAME); } else { // Error! Invalid character found ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c'.", ctx.ch); goto out; } } else { // PM_NEXT - Next item found sidlib_stildb_set_parsemode(&ctx, ctx.nextMode); } break; case PM_ENTRY: if (ctx.ch == EOF || th_iscrlf(ctx.ch) || th_isspace(ctx.ch)) { // A new file / entry, allocate and append tmpStr[strPos] = 0; if ((ret = sidlib_stildb_node_new(&entry, tmpStr, chconv)) != THERR_OK) goto out; th_llist_append_node((th_llist_t **) &dbh->nodes, (th_llist_t *) entry); // Default subtune is 0, for "main tune" information subtune = 0; sidlib_stildb_set_parsemode(&ctx, PM_IDLE); } else { VADDCH(ctx.ch) else { ret = th_io_error(fh, THERR_INVALID_DATA, "STIL entry filename too long."); goto out; } ctx.ch = -1; } break; case PM_SUBTUNE: if (ctx.ch == EOF || ctx.ch == ')') { BOOL neg = FALSE; // Subtune indicator end tmpStr[strPos] = 0; if (!th_get_int(tmpStr, (unsigned int *) &subtune, &neg) || neg) { ret = th_io_error(fh, THERR_INVALID_DATA, "Entry '%s' subtune indicator '%s' is not a valid integer.", entry->filename, tmpStr); goto out; } if (subtune <= 0 || subtune > 128) { th_io_error(fh, THERR_INVALID_DATA, "Entry '%s' subtune number #%d is out of range/invalid.", entry->filename, subtune); subtune = -1; } sidlib_stildb_set_parsemode(&ctx, PM_IDLE); } else if (th_isdigit(ctx.ch)) { if (ctx.lineStart) goto sub_unexpected; VADDCH(ctx.ch) else { ret = th_io_error(fh, THERR_INVALID_DATA, "Subtune indicator too long."); goto out; } } else if ((ctx.ch == '#' && strPos > 0) || (ctx.ch != '#' && !th_isspace(ctx.ch))) { sub_unexpected: ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c' in subtune indicator.", ctx.ch); goto out; } ctx.ch = -1; ctx.lineStart = FALSE; break; case PM_FIELD_NAME: if (ctx.ch == EOF || ctx.ch == ':' || th_iscrlf(ctx.ch) || th_isspace(ctx.ch)) { tmpStr[strPos] = 0; th_pstr_cpy(&fieldName, tmpStr); field = sidlib_stildb_get_field(tmpStr); if (entry == NULL) { ret = th_io_error(fh, THERR_INVALID_DATA, "No STIL entry allocated, but field '%s' found.", fieldName); goto out; } sidlib_stildb_set_next_parsemode(&ctx, PM_FIELD_DATA); ctx.lineStart = FALSE; strPos = 0; } else { VADDCH(ctx.ch) else { ret = th_io_error(fh, THERR_INVALID_DATA, "Field name too long."); goto out; } } ctx.ch = -1; break; case PM_FIELD_DATA: if (ctx.lineStart && th_isspace(ctx.ch) && ctx.linePos < 8) { ctx.ch = -1; } else if (th_iscrlf(ctx.ch)) { ctx.lineStart = TRUE; ctx.ch = -1; } else if (ctx.ch == EOF || (ctx.lineStart && ctx.linePos < 8)) { // Field done tmpStr[strPos] = 0; if (field >= 0 && subtune >= 0) { // Supported field, add it if ((ret = sidlib_stildb_entry_realloc( entry, subtune, TRUE, field, tmpStr, chconv)) != THERR_OK) { ret = th_io_error(fh, THERR_MALLOC, "Could not allocate memory for field '%s'.", fieldName); goto out; } } sidlib_stildb_set_parsemode(&ctx, PM_IDLE); } else { ctx.lineStart = FALSE; VADDCH(ctx.ch) else { ret = th_io_error(fh, THERR_INVALID_DATA, "Field '%s' data too long.", fieldName); goto out; } ctx.ch = - 1; } break; } } out: th_free(tmpStr); th_free(fieldName); if (ret == THERR_OK && ctx.parseMode == PM_ERROR) ret = THERR_INVALID_DATA; return ret; } /* Compare two nodes */ static int sidlib_stildb_compare_subtunes(const void *node1, const void *node2) { return ((SIDLibSTILSubTune *) node1)->tune - ((SIDLibSTILSubTune *) node2)->tune; } static int sidlib_stildb_compare_nodes(const void *node1, const void *node2) { // We assume here that we never ever get NULL-pointers or similar return strcmp( (*(SIDLibSTILNode * *) node1)->filename, (*(SIDLibSTILNode * *) node2)->filename); } /* (Re)create index */ int sidlib_stildb_build_index(SIDLibSTILDB *dbh) { if (dbh == NULL) return THERR_NULLPTR; // Free old index th_free_r(&(dbh->pindex)); // Get size of db dbh->nnodes = th_llist_length((th_llist_t *) dbh->nodes); // Check number of nodes if (dbh->nnodes > 0) { SIDLibSTILNode *node; size_t i; // Check number of nodes against overflow if (dbh->nnodes > UINTPTR_MAX / sizeof(SIDLibSTILNode *)) return THERR_BOUNDS; // Allocate memory for index-table dbh->pindex = (SIDLibSTILNode **) th_malloc(sizeof(SIDLibSTILNode *) * dbh->nnodes); if (dbh->pindex == NULL) return THERR_MALLOC; // Get node-pointers to table for (i = 0, node = dbh->nodes; node != NULL && i < dbh->nnodes; node = (SIDLibSTILNode *) node->node.next) { dbh->pindex[i++] = node; // Sort the subtunes qsort(node->subtunes, node->nsubtunes, sizeof(SIDLibSTILSubTune), sidlib_stildb_compare_subtunes); } // Sort the indexes qsort(dbh->pindex, dbh->nnodes, sizeof(SIDLibSTILNode *), sidlib_stildb_compare_nodes); } return THERR_OK; } /* Get STIL information node from database */ SIDLibSTILNode *sidlib_stildb_get_node(SIDLibSTILDB *dbh, const char *filename) { SIDLibSTILNode keyItem, *key, **item; keyItem.filename = (char *) filename; key = &keyItem; item = bsearch(&key, dbh->pindex, dbh->nnodes, sizeof(SIDLibSTILNode *), sidlib_stildb_compare_nodes); return item != NULL ? *item : NULL; }