/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 by Miika Pekkarinen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /* * TagCache API * * ----------x---------x------------------x----- * | | | External * +---------------x-------+ | TagCache | Libraries * | Modification routines | | Core | * +-x---------x-----------+ | | * | (R/W) | | | | * | +------x-------------x-+ +-------------x-----+ | * | | x==x Filters & clauses | | * | | Search routines | +-------------------+ | * | | x============================x DirCache * | +-x--------------------+ | (optional) * | | (R) | * | | +-------------------------------+ +---------+ | * | | | DB Commit (sort,unique,index) | | | | * | | +-x--------------------------x--+ | Control | | * | | | (R/W) | (R) | Thread | | * | | | +----------------------+ | | | | * | | | | TagCache DB Builder | | +---------+ | * | | | +-x-------------x------+ | | * | | | | (R) | (W) | | * | | | | +--x--------x---------+ | * | | | | | Temporary Commit DB | | * | | | | +---------------------+ | * +-x----x-------x--+ | * | TagCache RAM DB x==\(W) +-----------------+ | * +-----------------+ \===x | | * | | | | (R) | Ram DB Loader x============x DirCache * +-x----x---x---x---+ /==x | | (optional) * | Tagcache Disk DB x==/ +-----------------+ | * +------------------+ | * */ /*#define LOGF_ENABLE*/ #include #include #include #ifdef APPLICATION #include /* readlink() */ #include /* PATH_MAX */ #endif #include "config.h" #include "ata_idle_notify.h" #include "thread.h" #include "kernel.h" #include "system.h" #include "logf.h" #include "string-extra.h" #include "usb.h" #include "metadata.h" #include "tagcache.h" #include "core_alloc.h" #include "crc32.h" #include "misc.h" #include "settings.h" #include "dir.h" #include "pathfuncs.h" #include "structec.h" #include "debug.h" #include "dircache.h" #ifndef __PCTOOL__ #include "lang.h" #include "eeprom_settings.h" #endif #ifdef __PCTOOL__ #define yield() do { } while(0) #define sim_sleep(timeout) do { } while(0) #define do_timed_yield() do { } while(0) #endif #ifndef __PCTOOL__ /* Tag Cache thread. */ static struct event_queue tagcache_queue SHAREDBSS_ATTR; static long tagcache_stack[(DEFAULT_STACK_SIZE + 0x4000)/sizeof(long)]; static const char tagcache_thread_name[] = "tagcache"; #endif /* buffer size for all the (stack allocated & static) buffers handling tc data */ #define TAGCACHE_BUFSZ (TAG_MAXLEN+32) /* Previous path when scanning directory tree recursively. */ static char curpath[TAGCACHE_BUFSZ]; /* Used when removing duplicates. */ static char *tempbuf; /* Allocated when needed. */ static long tempbufidx; /* Current location in buffer. */ static size_t tempbuf_size; /* Buffer size (TEMPBUF_SIZE). */ static long tempbuf_left; /* Buffer space left. */ static long tempbuf_pos; #ifndef __PCTOOL__ static int tempbuf_handle; #endif #define SORTED_TAGS_COUNT 8 #define TAGCACHE_IS_UNIQUE(tag) (BIT_N(tag) & TAGCACHE_UNIQUE_TAGS) #define TAGCACHE_IS_SORTED(tag) (BIT_N(tag) & TAGCACHE_SORTED_TAGS) #define TAGCACHE_IS_NUMERIC_OR_NONUNIQUE(tag) \ (BIT_N(tag) & (TAGCACHE_NUMERIC_TAGS | ~TAGCACHE_UNIQUE_TAGS)) /* Tags we want to get sorted (loaded to the tempbuf). */ #define TAGCACHE_SORTED_TAGS ((1LU << tag_artist) | (1LU << tag_album) | \ (1LU << tag_genre) | (1LU << tag_composer) | (1LU << tag_comment) | \ (1LU << tag_albumartist) | (1LU << tag_grouping) | (1LU << tag_title)) /* Uniqued tags (we can use these tags with filters and conditional clauses). */ #define TAGCACHE_UNIQUE_TAGS ((1LU << tag_artist) | (1LU << tag_album) | \ (1LU << tag_genre) | (1LU << tag_composer) | (1LU << tag_comment) | \ (1LU << tag_albumartist) | (1LU << tag_grouping)) /* String presentation of the tags defined in tagcache.h. Must be in correct order! */ static const char *tags_str[] = { "artist", "album", "genre", "title", "filename", "composer", "comment", "albumartist", "grouping", "year", "discnumber", "tracknumber", "bitrate", "length", "playcount", "rating", "playtime", "lastplayed", "commitid", "mtime", "lastelapsed", "lastoffset" }; /* Status information of the tagcache. */ static struct tagcache_stat tc_stat; /* Queue commands. */ enum tagcache_queue { Q_STOP_SCAN = 0, Q_START_SCAN, Q_IMPORT_CHANGELOG, Q_UPDATE, Q_REBUILD, /* Internal tagcache command queue. */ CMD_UPDATE_MASTER_HEADER, CMD_UPDATE_NUMERIC, }; struct tagcache_command_entry { int32_t command; int32_t idx_id; int32_t tag; int32_t data; }; #ifndef __PCTOOL__ static struct tagcache_command_entry command_queue[TAGCACHE_COMMAND_QUEUE_LENGTH]; static volatile int command_queue_widx = 0; static volatile int command_queue_ridx = 0; static struct mutex command_queue_mutex SHAREDBSS_ATTR; #endif /* Tag database structures. */ /* Variable-length tag entry in tag files. */ struct tagfile_entry { int32_t tag_length; /* Length of the data in bytes including '\0' */ int32_t idx_id; /* Corresponding entry location in index file of not unique tags */ char tag_data[0]; /* Begin of the tag data */ }; /* Fixed-size tag entry in master db index. */ struct index_entry { int32_t tag_seek[TAG_COUNT]; /* Location of tag data or numeric tag data */ int32_t flag; /* Status flags */ }; /* Header is the same in every file. */ struct tagcache_header { int32_t magic; /* Header version number */ int32_t datasize; /* Data size in bytes */ int32_t entry_count; /* Number of entries in this file */ }; struct master_header { struct tagcache_header tch; int32_t serial; /* Increasing counting number */ int32_t commitid; /* Number of commits so far */ int32_t dirty; }; /* For the endianess correction */ static const char * const tagfile_entry_ec = "ll"; /** Note: This should be (1 + TAG_COUNT) amount of l's. */ static const char * const index_entry_ec = "lllllllllllllllllllllll"; static const char * const tagcache_header_ec = "lll"; static const char * const master_header_ec = "llllll"; static struct master_header current_tcmh; #ifdef HAVE_TC_RAMCACHE #define TC_ALIGN_PTR(p, type, gap_out_p) \ ({ typeof (p) __p = (p); \ typeof (p) __palgn = ALIGN_UP(__p, __alignof__(type)); \ *(gap_out_p) = (char *)__palgn - (char *)__p; \ __palgn; }) #define IF_TCRCDC(...) IF_DIRCACHE(__VA_ARGS__) #ifdef HAVE_DIRCACHE #define tcrc_dcfrefs \ ((struct dircache_fileref *)(tcramcache.hdr->tags[tag_filename] + \ sizeof (struct tagcache_header))) #endif /* HAVE_DIRCACHE */ /* Header is created when loading database to ram. */ struct ramcache_header { char *tags[TAG_COUNT]; /* Tag file content (dcfrefs if tag_filename) */ int entry_count[TAG_COUNT]; /* Number of entries in the indices. */ struct index_entry indices[0]; /* Master index file content */ }; #ifdef HAVE_EEPROM_SETTINGS struct statefile_header { int32_t magic; /* Statefile version number */ struct master_header mh; /* Header from the master index */ struct ramcache_header *hdr; /* Old load address of hdr for relocation */ struct tagcache_stat tc_stat; }; #endif /* HAVE_EEPROM_SETTINGS */ /* In-RAM ramcache structure (not persisted) */ static struct tcramcache { struct ramcache_header *hdr; /* allocated ramcache_header */ int handle; /* buffer handle */ int move_lock; } tcramcache; static inline void tcrc_buffer_lock(void) { tcramcache.move_lock++; } static inline void tcrc_buffer_unlock(void) { tcramcache.move_lock--; } #else /* ndef HAVE_TC_RAMCACHE */ #define IF_TCRCDC(...) #endif /* HAVE_TC_RAMCACHE */ /** * Full tag entries stored in a temporary file waiting * for commit to the cache. */ struct temp_file_entry { long tag_offset[TAG_COUNT]; short tag_length[TAG_COUNT]; long flag; long data_length; }; struct tempbuf_id_list { long id; struct tempbuf_id_list *next; }; struct tempbuf_searchidx { long idx_id; char *str; int seek; struct tempbuf_id_list idlist; }; /* Lookup buffer for fixing messed up index while after sorting. */ static long commit_entry_count; static long lookup_buffer_depth; static struct tempbuf_searchidx **lookup; /* Used when building the temporary file. */ static int cachefd = -1, filenametag_fd; static int total_entry_count = 0; static int data_size = 0; static int processed_dir_count; /* Thread safe locking */ static volatile int write_lock; static volatile int read_lock; static bool delete_entry(long idx_id); static inline void str_setlen(char *buf, size_t len) { buf[len] = '\0'; } static void allocate_tempbuf(void) { /* Yeah, malloc would be really nice now :) */ size_t size; tempbuf_size = 0; #ifdef __PCTOOL__ size = 32*1024*1024; tempbuf = malloc(size); if (tempbuf) tempbuf_size = size; #else /* !__PCTOOL__ */ tempbuf_handle = core_alloc_maximum("tc tempbuf", &size, NULL); if (tempbuf_handle > 0) { tempbuf = core_get_data(tempbuf_handle); tempbuf_size = size; } #endif /* __PCTOOL__ */ } static void free_tempbuf(void) { if (tempbuf_size == 0) return ; #ifdef __PCTOOL__ free(tempbuf); #else tempbuf_handle = core_free(tempbuf_handle); #endif tempbuf = NULL; tempbuf_size = 0; } const char* tagcache_tag_to_str(int tag) { return tags_str[tag]; } /* Helper functions for the two most read/write data structure: tagfile_entry and index_entry */ static ssize_t ecread_tagfile_entry(int fd, struct tagfile_entry *buf) { return ecread(fd, buf, 1, tagfile_entry_ec, tc_stat.econ); } enum e_ecread_errors {e_SUCCESS = 0, e_ENTRY_SIZEMISMATCH = 1, e_TAG_TOOLONG, e_TAG_SIZEMISMATCH}; static enum e_ecread_errors ecread_tagfile_entry_and_tag (int fd, struct tagfile_entry *tfe, char* buf, int bufsz) { long tag_length; str_setlen(buf, 0); if (ecread_tagfile_entry(fd, tfe)!= sizeof(struct tagfile_entry)) { return e_ENTRY_SIZEMISMATCH; } tag_length = tfe->tag_length; if (tag_length >= bufsz) return e_TAG_TOOLONG; if (read(fd, buf, tag_length) != tag_length) return e_TAG_SIZEMISMATCH; str_setlen(buf, tag_length); return e_SUCCESS; } static ssize_t ecread_index_entry(int fd, struct index_entry *buf) { return ecread(fd, buf, 1, index_entry_ec, tc_stat.econ); } static ssize_t ecwrite_index_entry(int fd, struct index_entry *buf) { return ecwrite(fd, buf, 1, index_entry_ec, tc_stat.econ); } static int open_tag_fd(struct tagcache_header *hdr, int tag, bool write) { int fd; char buf[MAX_PATH]; const int bufsz = sizeof(buf); int rc; if (TAGCACHE_IS_NUMERIC(tag) || tag < 0 || tag >= TAG_COUNT) return -1; snprintf(buf, bufsz, TAGCACHE_FILE_INDEX, tag); fd = open(buf, write ? O_RDWR : O_RDONLY); if (fd < 0) { logf("tag file open failed: tag=%d write=%d file=%s", tag, write, buf); tc_stat.ready = false; return fd; } /* Check the header. */ rc = ecread(fd, hdr, 1, tagcache_header_ec, tc_stat.econ); if (hdr->magic != TAGCACHE_MAGIC || rc != sizeof(struct tagcache_header)) { logf("header error"); tc_stat.ready = false; close(fd); return -2; } return fd; } static int open_master_fd(struct master_header *hdr, bool write) { int fd; int rc; fd = open(TAGCACHE_FILE_MASTER, write ? O_RDWR : O_RDONLY); if (fd < 0) { logf("master file open failed for R/W"); tc_stat.ready = false; return fd; } tc_stat.econ = false; /* Check the header. */ rc = read(fd, hdr, sizeof(struct master_header)); if (hdr->tch.magic == TAGCACHE_MAGIC && rc == sizeof(struct master_header)) { /* Success. */ return fd; } /* Trying to read again, this time with endianess correction enabled. */ lseek(fd, 0, SEEK_SET); rc = ecread(fd, hdr, 1, master_header_ec, true); if (hdr->tch.magic != TAGCACHE_MAGIC || rc != sizeof(struct master_header)) { logf("header error"); tc_stat.ready = false; close(fd); return -2; } tc_stat.econ = true; return fd; } #ifndef __PCTOOL__ static bool do_timed_yield(void) { /* Sorting can lock up for quite a while, so yield occasionally */ static long wakeup_tick = 0; if (TIME_AFTER(current_tick, wakeup_tick)) { yield(); wakeup_tick = current_tick + (HZ/50); return true; } return false; } #endif /* __PCTOOL__ */ #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) /* find the ramcache entry corresponding to the file indicated by * filename and dc (it's corresponding dircache id). */ static long find_entry_ram(const char *filename) { static long last_pos = 0; struct dircache_fileref dcfref; /* Check if tagcache is loaded into ram. */ if (!tc_stat.ramcache) return -1; if (dircache_search(DCS_CACHED_PATH | DCS_UPDATE_FILEREF, &dcfref, filename) <= 0) { logf("tagcache: file not found."); return -1; } /* Search references */ int end_pos = current_tcmh.tch.entry_count; while (1) { for (int i = last_pos; i < end_pos; i++) { do_timed_yield(); if (!(tcramcache.hdr->indices[i].flag & FLAG_DIRCACHE)) continue; int cmp = dircache_fileref_cmp(&tcrc_dcfrefs[i], &dcfref); if (cmp < 3) continue; last_pos = MAX(0, i - 3); return i; } if (last_pos == 0) { last_pos = MAX(0, end_pos - 3); break; } end_pos = last_pos; last_pos = 0; } return -1; } #endif /* defined (HAVE_TC_RAMCACHE) && defined (HAVE_DIRCACHE) */ static long find_entry_disk(const char *filename_raw, bool localfd) { struct tagcache_header tch; static long last_pos = -1; long pos_history[POS_HISTORY_COUNT]; long pos_history_idx = 0; bool found = false; struct tagfile_entry tfe; int fd; char buf[TAGCACHE_BUFSZ]; const long bufsz = sizeof(buf); int i; int pos = -1; const char *filename = filename_raw; #ifdef APPLICATION char pathbuf[PATH_MAX]; /* Note: Don't use MAX_PATH here, it's too small */ if (realpath(filename, pathbuf) == pathbuf) filename = pathbuf; #endif /* APPLICATION */ if (!tc_stat.ready) return -2; fd = filenametag_fd; if (fd < 0 || localfd) { last_pos = -1; if ( (fd = open_tag_fd(&tch, tag_filename, false)) < 0) return -1; } check_again: if (last_pos > 0) lseek(fd, last_pos, SEEK_SET); else lseek(fd, sizeof(struct tagcache_header), SEEK_SET); while (true) { pos = lseek(fd, 0, SEEK_CUR); for (i = pos_history_idx-1; i >= 0; i--) pos_history[i+1] = pos_history[i]; pos_history[0] = pos; int res = ecread_tagfile_entry_and_tag(fd, &tfe, buf, bufsz); if (res == e_SUCCESS) {;;} else if (res == e_ENTRY_SIZEMISMATCH) break; else { switch (res) { case e_TAG_TOOLONG: logf("too long tag #1"); close(fd); if (!localfd) filenametag_fd = -1; last_pos = -1; return -2; case e_TAG_SIZEMISMATCH: logf("read error #2"); close(fd); if (!localfd) filenametag_fd = -1; last_pos = -1; return -3; default: break; } } if (!strcmp(filename, buf)) { last_pos = pos_history[pos_history_idx]; found = true; break ; } if (pos_history_idx < POS_HISTORY_COUNT - 1) pos_history_idx++; } /* Not found? */ if (!found) { if (last_pos > 0) { last_pos = -1; logf("seek again"); goto check_again; } if (fd != filenametag_fd || localfd) close(fd); return -4; } if (fd != filenametag_fd || localfd) close(fd); return tfe.idx_id; } static int find_index(const char *filename) { long idx_id = -1; #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) idx_id = find_entry_ram(filename); #endif if (idx_id < 0) idx_id = find_entry_disk(filename, true); return idx_id; } bool tagcache_find_index(struct tagcache_search *tcs, const char *filename) { int idx_id; if (!tc_stat.ready) return false; idx_id = find_index(filename); if (idx_id < 0) return false; if (!tagcache_search(tcs, tag_filename)) return false; tcs->entry_count = 0; tcs->idx_id = idx_id; return true; } static bool get_index(int masterfd, int idxid, struct index_entry *idx, bool use_ram) { bool localfd = false; if (idxid < 0) { logf("Incorrect idxid: %d", idxid); return false; } #ifdef HAVE_TC_RAMCACHE if (tc_stat.ramcache && use_ram) { if (tcramcache.hdr->indices[idxid].flag & FLAG_DELETED) return false; *idx = tcramcache.hdr->indices[idxid]; return true; } #endif /* HAVE_TC_RAMCACHE */ if (masterfd < 0) { struct master_header tcmh; localfd = true; masterfd = open_master_fd(&tcmh, false); if (masterfd < 0) return false; } lseek(masterfd, idxid * sizeof(struct index_entry) + sizeof(struct master_header), SEEK_SET); if (ecread_index_entry(masterfd, idx) != sizeof(struct index_entry)) { logf("read error #3"); if (localfd) close(masterfd); return false; } if (localfd) close(masterfd); if (idx->flag & FLAG_DELETED) return false; return true; (void)use_ram; } #ifndef __PCTOOL__ static bool write_index(int masterfd, int idxid, struct index_entry *idx) { /* We need to exclude all memory only flags & tags when writing to disk. */ if (idx->flag & FLAG_DIRCACHE) { logf("memory only flags!"); return false; } #ifdef HAVE_TC_RAMCACHE /* Only update numeric data. Writing the whole index to RAM by memcpy * destroys dircache pointers! */ if (tc_stat.ramcache) { struct index_entry *idx_ram = &tcramcache.hdr->indices[idxid]; for (int tag = 0; tag < TAG_COUNT; tag++) { if (TAGCACHE_IS_NUMERIC(tag)) { idx_ram->tag_seek[tag] = idx->tag_seek[tag]; } } /* Don't touch the dircache flag or attributes. */ idx_ram->flag = (idx->flag & 0x0000ffff) | (idx_ram->flag & (0xffff0000 | FLAG_DIRCACHE)); } #endif /* HAVE_TC_RAMCACHE */ lseek(masterfd, idxid * sizeof(struct index_entry) + sizeof(struct master_header), SEEK_SET); if (ecwrite_index_entry(masterfd, idx) != sizeof(struct index_entry)) { logf("write error #3"); logf("idxid: %d", idxid); return false; } return true; } #endif /* !__PCTOOL__ */ static bool open_files(struct tagcache_search *tcs, int tag) { if (tcs->idxfd[tag] < 0) { char fn[MAX_PATH]; snprintf(fn, sizeof fn, TAGCACHE_FILE_INDEX, tag); tcs->idxfd[tag] = open(fn, O_RDONLY); } if (tcs->idxfd[tag] < 0) { logf("File not open!"); return false; } return true; } static bool retrieve(struct tagcache_search *tcs, IF_DIRCACHE(int idx_id,) struct index_entry *idx, int tag, char *buf, long bufsz) { struct tagfile_entry tfe; long seek; str_setlen(buf, 0); if (TAGCACHE_IS_NUMERIC(tag)) return false; seek = idx->tag_seek[tag]; if (seek < 0) { logf("Retrieve failed"); return false; } #ifdef HAVE_TC_RAMCACHE if (tcs->ramsearch) { #ifdef HAVE_DIRCACHE if (tag == tag_filename && (idx->flag & FLAG_DIRCACHE)) { if (dircache_get_fileref_path(&tcrc_dcfrefs[idx_id], buf, bufsz) >= 0) return true; } else #endif /* HAVE_DIRCACHE */ if (tag != tag_filename) { struct tagfile_entry *ep = (struct tagfile_entry *)&tcramcache.hdr->tags[tag][seek]; strlcpy(buf, ep->tag_data, bufsz); return true; } } #endif /* HAVE_TC_RAMCACHE */ if (!open_files(tcs, tag)) return false; lseek(tcs->idxfd[tag], seek, SEEK_SET); switch (ecread_tagfile_entry_and_tag(tcs->idxfd[tag], &tfe, buf, bufsz)) { case e_SUCCESS: break; case e_ENTRY_SIZEMISMATCH: logf("read error #5"); return false; case e_TAG_TOOLONG: logf("too long tag #5"); return false; case e_TAG_SIZEMISMATCH: logf("read error #6"); return false; default: logf("unknown_error"); break;; } return true; } #define COMMAND_QUEUE_IS_EMPTY (command_queue_ridx == command_queue_widx) static long find_tag(int tag, int idx_id, const struct index_entry *idx) { #ifndef __PCTOOL__ if (! COMMAND_QUEUE_IS_EMPTY && TAGCACHE_IS_NUMERIC(tag)) { /* Attempt to find tag data through store-to-load forwarding in command queue */ long result = -1; mutex_lock(&command_queue_mutex); int ridx = command_queue_widx; while (ridx != command_queue_ridx) { if (--ridx < 0) ridx = TAGCACHE_COMMAND_QUEUE_LENGTH - 1; if (command_queue[ridx].command == CMD_UPDATE_NUMERIC && command_queue[ridx].idx_id == idx_id && command_queue[ridx].tag == tag) { result = command_queue[ridx].data; break; } } mutex_unlock(&command_queue_mutex); if (result >= 0) { logf("find_tag: " "Recovered tag %d value %lX from write queue", tag, result); return result; } } #else (void)idx_id; #endif return idx->tag_seek[tag]; } static inline long sec_in_ms(long ms) { return (ms/1000) % 60; } static inline long min_in_ms(long ms) { return (ms/1000) / 60; } static long check_virtual_tags(int tag, int idx_id, const struct index_entry *idx) { long data = 0; switch (tag) { case tag_virt_length_sec: data = sec_in_ms(find_tag(tag_length, idx_id, idx)); break; case tag_virt_length_min: data = min_in_ms(find_tag(tag_length, idx_id, idx)); break; case tag_virt_playtime_sec: data = sec_in_ms(find_tag(tag_playtime, idx_id, idx)); break; case tag_virt_playtime_min: data = min_in_ms(find_tag(tag_playtime, idx_id, idx)); break; case tag_virt_autoscore: if (find_tag(tag_length, idx_id, idx) == 0 || find_tag(tag_playcount, idx_id, idx) == 0) { data = 0; } else { /* A straight calculus gives: autoscore = 100 * playtime / length / playcout (1) Now, consider the euclidian division of playtime by length: playtime = alpha * length + beta With: 0 <= beta < length Now, (1) becomes: autoscore = 100 * (alpha / playcout + beta / length / playcount) Both terms should be small enough to avoid any overflow */ long playtime = find_tag(tag_playtime, idx_id, idx); long length = find_tag(tag_length, idx_id, idx); long playcount = find_tag(tag_playcount, idx_id, idx); data = 100 * (playtime / length) + (100 * (playtime % length)) / length; data /= playcount; } break; /* How many commits before the file has been added to the DB. */ case tag_virt_entryage: data = current_tcmh.commitid - find_tag(tag_commitid, idx_id, idx) - 1; break; case tag_virt_basename: tag = tag_filename; /* return filename; caller handles basename */ /* FALLTHRU */ default: data = find_tag(tag, idx_id, idx); } return data; } long tagcache_get_numeric(const struct tagcache_search *tcs, int tag) { struct index_entry idx; if (!tc_stat.ready) return false; if (!TAGCACHE_IS_NUMERIC(tag)) return -1; if (!get_index(tcs->masterfd, tcs->idx_id, &idx, true)) return -2; return check_virtual_tags(tag, tcs->idx_id, &idx); } inline static bool str_ends_with(const char *str1, const char *str2) { int str_len = strlen(str1); int clause_len = strlen(str2); if (clause_len > str_len) return false; return !strcasecmp(&str1[str_len - clause_len], str2); } inline static bool str_oneof(const char *str, const char *list) { const char *sep; int l, len = strlen(str); while (*list) { sep = strchr(list, '|'); l = sep ? (intptr_t)sep - (intptr_t)list : (int)strlen(list); if ((l==len) && !strncasecmp(str, list, len)) return true; list += sep ? l + 1 : l; } return false; } static bool check_against_clause(long numeric, const char *str, const struct tagcache_search_clause *clause) { if (clause->numeric) { switch (clause->type) { case clause_is: return numeric == clause->numeric_data; case clause_is_not: return numeric != clause->numeric_data; case clause_gt: return numeric > clause->numeric_data; case clause_gteq: return numeric >= clause->numeric_data; case clause_lt: return numeric < clause->numeric_data; case clause_lteq: return numeric <= clause->numeric_data; default: logf("Incorrect numeric tag: %d", clause->type); } } else { switch (clause->type) { case clause_is: return !strcasecmp(clause->str, str); case clause_is_not: return strcasecmp(clause->str, str); case clause_gt: return 0>strcasecmp(clause->str, str); case clause_gteq: return 0>=strcasecmp(clause->str, str); case clause_lt: return 0str, str); case clause_lteq: return 0<=strcasecmp(clause->str, str); case clause_contains: return (strcasestr(str, clause->str) != NULL); case clause_not_contains: return (strcasestr(str, clause->str) == NULL); case clause_begins_with: return (strcasestr(str, clause->str) == str); case clause_not_begins_with: return (strcasestr(str, clause->str) != str); case clause_ends_with: return str_ends_with(str, clause->str); case clause_not_ends_with: return !str_ends_with(str, clause->str); case clause_oneof: return str_oneof(str, clause->str); default: logf("Incorrect tag: %d", clause->type); } } return false; } static bool check_clauses(struct tagcache_search *tcs, struct index_entry *idx, struct tagcache_search_clause **clauses, int count) { int i; /* Go through all conditional clauses. */ for (i = 0; i < count; i++) { int seek; char buf[256]; const int bufsz = sizeof(buf); char *str = buf; struct tagcache_search_clause *clause = clauses[i]; if (clause->type == clause_logical_or) break; /* all conditions before logical-or satisfied -- stop processing clauses */ seek = check_virtual_tags(clause->tag, tcs->idx_id, idx); #ifdef HAVE_TC_RAMCACHE if (tcs->ramsearch) { struct tagfile_entry *tfe; if (!TAGCACHE_IS_NUMERIC(clause->tag)) { if (clause->tag == tag_filename || clause->tag == tag_virt_basename) { retrieve(tcs, IF_DIRCACHE(tcs->idx_id,) idx, tag_filename, buf, bufsz); } else { tfe = (struct tagfile_entry *) &tcramcache.hdr->tags[clause->tag][seek]; /* str points to movable data, but no locking required here, * as no yield() is following */ str = tfe->tag_data; } } } else #endif /* HAVE_TC_RAMCACHE */ { struct tagfile_entry tfe; if (!TAGCACHE_IS_NUMERIC(clause->tag)) { int tag = clause->tag; if (tag == tag_virt_basename) tag = tag_filename; int fd = tcs->idxfd[tag]; lseek(fd, seek, SEEK_SET); switch (ecread_tagfile_entry_and_tag(fd, &tfe, str, bufsz)) { case e_SUCCESS: break; case e_ENTRY_SIZEMISMATCH: logf("read error #15"); return false; case e_TAG_TOOLONG: logf("too long tag #6"); return false; case e_TAG_SIZEMISMATCH: logf("read error #16"); return false; default: logf("unknown_error"); break;; } /* Check if entry has been deleted. */ if (str[0] == '\0') return false; } } if (clause->tag == tag_virt_basename) { char *basename = strrchr(str, '/'); if (basename) str = basename + 1; } if (!check_against_clause(seek, str, clause)) { /* Clause failed -- try finding a logical-or clause */ while (++i < count) { if (clauses[i]->type == clause_logical_or) break; } if (i < count) /* Found logical-or? */ continue; /* Check clauses after logical-or */ return false; } } return true; } bool tagcache_check_clauses(struct tagcache_search *tcs, struct tagcache_search_clause **clause, int count) { struct index_entry idx; if (count == 0) return true; if (!get_index(tcs->masterfd, tcs->idx_id, &idx, true)) return false; return check_clauses(tcs, &idx, clause, count); } static bool add_uniqbuf(struct tagcache_search *tcs, unsigned long id) { int i; /* If uniq buffer is not defined we must return true for search to work. */ if (tcs->unique_list == NULL || (!TAGCACHE_IS_UNIQUE(tcs->type) && !TAGCACHE_IS_NUMERIC(tcs->type))) { return true; } for (i = 0; i < tcs->unique_list_count; i++) { /* Return false if entry is found. */ if (tcs->unique_list[i] == id) return false; } if (tcs->unique_list_count < tcs->unique_list_capacity) { tcs->unique_list[i] = id; tcs->unique_list_count++; } return true; } static bool build_lookup_list(struct tagcache_search *tcs) { struct index_entry entry; int i, j; tcs->seek_list_count = 0; #ifdef HAVE_TC_RAMCACHE if (tcs->ramsearch) { tcrc_buffer_lock(); /* lock because below makes a pointer to movable data */ for (i = tcs->seek_pos; i < current_tcmh.tch.entry_count; i++) { struct tagcache_seeklist_entry *seeklist; /* idx points to movable data, don't yield or reload */ struct index_entry *idx = &tcramcache.hdr->indices[i]; if (tcs->seek_list_count == SEEK_LIST_SIZE) break ; /* Skip deleted files. */ if (idx->flag & FLAG_DELETED) continue; /* Go through all filters.. */ for (j = 0; j < tcs->filter_count; j++) { if (idx->tag_seek[tcs->filter_tag[j]] != tcs->filter_seek[j]) { break ; } } if (j < tcs->filter_count) continue ; /* Check for conditions. */ if (!check_clauses(tcs, idx, tcs->clause, tcs->clause_count)) continue; /* Add to the seek list if not already in uniq buffer (doesn't yield)*/ if (!add_uniqbuf(tcs, idx->tag_seek[tcs->type])) continue; /* Lets add it. */ seeklist = &tcs->seeklist[tcs->seek_list_count]; seeklist->seek = idx->tag_seek[tcs->type]; seeklist->flag = idx->flag; seeklist->idx_id = i; tcs->seek_list_count++; } tcrc_buffer_unlock(); tcs->seek_pos = i; return tcs->seek_list_count > 0; } #endif /* HAVE_TC_RAMCACHE */ if (tcs->masterfd < 0) { struct master_header tcmh; tcs->masterfd = open_master_fd(&tcmh, false); } lseek(tcs->masterfd, tcs->seek_pos * sizeof(struct index_entry) + sizeof(struct master_header), SEEK_SET); while (ecread_index_entry(tcs->masterfd, &entry) == sizeof(struct index_entry)) { struct tagcache_seeklist_entry *seeklist; if (tcs->seek_list_count == SEEK_LIST_SIZE) break ; i = tcs->seek_pos; tcs->seek_pos++; /* Check if entry has been deleted. */ if (entry.flag & FLAG_DELETED) continue; /* Go through all filters.. */ for (j = 0; j < tcs->filter_count; j++) { if (entry.tag_seek[tcs->filter_tag[j]] != tcs->filter_seek[j]) break ; } if (j < tcs->filter_count) continue ; /* Check for conditions. */ if (!check_clauses(tcs, &entry, tcs->clause, tcs->clause_count)) continue; /* Add to the seek list if not already in uniq buffer. */ if (!add_uniqbuf(tcs, entry.tag_seek[tcs->type])) continue; /* Lets add it. */ seeklist = &tcs->seeklist[tcs->seek_list_count]; seeklist->seek = entry.tag_seek[tcs->type]; seeklist->flag = entry.flag; seeklist->idx_id = i; tcs->seek_list_count++; yield(); } return tcs->seek_list_count > 0; } static void remove_files(void) { int i; char buf[MAX_PATH]; const int bufsz = sizeof(buf); tc_stat.ready = false; tc_stat.ramcache = false; tc_stat.econ = false; remove(TAGCACHE_FILE_MASTER); for (i = 0; i < TAG_COUNT; i++) { if (TAGCACHE_IS_NUMERIC(i)) continue; snprintf(buf, bufsz, TAGCACHE_FILE_INDEX, i); remove(buf); } } static bool check_all_headers(void) { struct master_header myhdr; struct tagcache_header tch; int tag; int fd; if ( (fd = open_master_fd(&myhdr, false)) < 0) return false; close(fd); if (myhdr.dirty) { logf("tagcache is dirty!"); return false; } memcpy(¤t_tcmh, &myhdr, sizeof(struct master_header)); for (tag = 0; tag < TAG_COUNT; tag++) { if (TAGCACHE_IS_NUMERIC(tag)) continue; if ( (fd = open_tag_fd(&tch, tag, false)) < 0) return false; close(fd); } return true; } bool tagcache_search(struct tagcache_search *tcs, int tag) { struct tagcache_header tag_hdr; struct master_header master_hdr; int i; while (read_lock) sleep(1); memset(tcs, 0, sizeof(struct tagcache_search)); if (tc_stat.commit_step > 0 || !tc_stat.ready) return false; tcs->position = sizeof(struct tagcache_header); tcs->type = tag; tcs->seek_pos = 0; tcs->list_position = 0; tcs->seek_list_count = 0; tcs->filter_count = 0; tcs->masterfd = -1; for (i = 0; i < TAG_COUNT; i++) tcs->idxfd[i] = -1; #ifndef HAVE_TC_RAMCACHE tcs->ramsearch = false; #else tcs->ramsearch = tc_stat.ramcache; if (tcs->ramsearch) { tcs->entry_count = tcramcache.hdr->entry_count[tcs->type]; } else #endif { /* Always open as R/W so we can pass tcs to functions that modify data also * without failing. */ tcs->masterfd = open_master_fd(&master_hdr, true); if (tcs->masterfd < 0) return false; if (!TAGCACHE_IS_NUMERIC(tcs->type)) { tcs->idxfd[tcs->type] = open_tag_fd(&tag_hdr, tcs->type, false); if (tcs->idxfd[tcs->type] < 0) return false; tcs->entry_count = tag_hdr.entry_count; } else { tcs->entry_count = master_hdr.tch.entry_count; } } tcs->valid = true; tcs->initialized = true; write_lock++; return true; } void tagcache_search_set_uniqbuf(struct tagcache_search *tcs, void *buffer, long length) { tcs->unique_list = (unsigned long *)buffer; tcs->unique_list_capacity = length / sizeof(*tcs->unique_list); tcs->unique_list_count = 0; } bool tagcache_search_add_filter(struct tagcache_search *tcs, int tag, int seek) { if (tcs->filter_count == TAGCACHE_MAX_FILTERS) return false; if (TAGCACHE_IS_NUMERIC_OR_NONUNIQUE(tag)) return false; tcs->filter_tag[tcs->filter_count] = tag; tcs->filter_seek[tcs->filter_count] = seek; tcs->filter_count++; return true; } bool tagcache_search_add_clause(struct tagcache_search *tcs, struct tagcache_search_clause *clause) { int i; if (tcs->clause_count >= TAGCACHE_MAX_CLAUSES) { logf("Too many clauses"); return false; } if (clause->type != clause_logical_or) { /* Check if there is already a similar filter in present (filters are * much faster than clauses). */ for (i = 0; i < tcs->filter_count; i++) { if (tcs->filter_tag[i] == clause->tag) return true; } if (!TAGCACHE_IS_NUMERIC(clause->tag) && tcs->idxfd[clause->tag] < 0) { char buf[MAX_PATH]; const int bufsz = sizeof(buf); snprintf(buf, bufsz, TAGCACHE_FILE_INDEX, clause->tag); tcs->idxfd[clause->tag] = open(buf, O_RDONLY); } } tcs->clause[tcs->clause_count] = clause; tcs->clause_count++; return true; } static bool get_next(struct tagcache_search *tcs) { /* WARNING pointers into buf are used in outside functions */ static char buf[TAGCACHE_BUFSZ]; const int bufsz = sizeof(buf); struct tagfile_entry entry; #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) long flag = 0; #endif if (!tcs->valid || !tc_stat.ready) return false; if (tcs->idxfd[tcs->type] < 0 && !TAGCACHE_IS_NUMERIC(tcs->type) #ifdef HAVE_TC_RAMCACHE && !tcs->ramsearch #endif ) return false; /* Relative fetch. */ if (tcs->filter_count > 0 || tcs->clause_count > 0 || TAGCACHE_IS_NUMERIC(tcs->type) #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) /* We need to retrieve flag status for dircache. */ || (tcs->ramsearch && tcs->type == tag_filename) #endif ) { struct tagcache_seeklist_entry *seeklist; /* Check for end of list. */ if (tcs->list_position == tcs->seek_list_count) { tcs->list_position = 0; /* Try to fetch more. */ if (!build_lookup_list(tcs)) { tcs->valid = false; return false; } } seeklist = &tcs->seeklist[tcs->list_position]; #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) flag = seeklist->flag; #endif tcs->position = seeklist->seek; tcs->idx_id = seeklist->idx_id; tcs->list_position++; } else { if (tcs->entry_count == 0) { tcs->valid = false; return false; } tcs->entry_count--; } tcs->result_seek = tcs->position; if (TAGCACHE_IS_NUMERIC(tcs->type)) { snprintf(buf, bufsz, "%ld", tcs->position); tcs->result = buf; tcs->result_len = strlen(buf) + 1; return true; } /* Direct fetch. */ #ifdef HAVE_TC_RAMCACHE if (tcs->ramsearch) { #ifdef HAVE_DIRCACHE if (tcs->type == tag_filename && (flag & FLAG_DIRCACHE)) { ssize_t len = dircache_get_fileref_path(&tcrc_dcfrefs[tcs->idx_id], buf, sizeof (buf)); if (len >= 0) { tcs->result_len = len + 1; tcs->result = buf; tcs->ramresult = false; return true; } /* else do it the hard way */ } #endif /* HAVE_DIRCACHE */ if (tcs->type != tag_filename) { struct tagfile_entry *ep; ep = (struct tagfile_entry *)&tcramcache.hdr->tags[tcs->type][tcs->position]; /* don't return ep->tag_data directly as it may move */ tcs->result_len = strlcpy(buf, ep->tag_data, sizeof(buf)) + 1; tcs->result = buf; tcs->idx_id = ep->idx_id; tcs->ramresult = false; /* was true before we copied to buf too */ /* Increase position for the next run. This may get overwritten. */ tcs->position += sizeof(struct tagfile_entry) + ep->tag_length; return true; } } #endif /* HAVE_TC_RAMCACHE */ if (!open_files(tcs, tcs->type)) { tcs->valid = false; return false; } /* Seek stream to the correct position and continue to direct fetch. */ lseek(tcs->idxfd[tcs->type], tcs->position, SEEK_SET); switch (ecread_tagfile_entry_and_tag(tcs->idxfd[tcs->type], &entry, buf, bufsz)) { case e_SUCCESS: break; case e_ENTRY_SIZEMISMATCH: logf("read error #5"); tcs->valid = false; return false; case e_TAG_TOOLONG: tcs->valid = false; logf("too long tag #2"); logf("P:%lX/%lX", tcs->position, entry.tag_length); return false; case e_TAG_SIZEMISMATCH: tcs->valid = false; logf("read error #4"); return false; default: logf("unknown_error"); break;; } /** Update the position for the next read (this may be overridden if filters or clauses are being used). */ tcs->position += sizeof(struct tagfile_entry) + entry.tag_length; str_setlen(buf, entry.tag_length); tcs->result = buf; tcs->result_len = strlen(tcs->result) + 1; tcs->idx_id = entry.idx_id; tcs->ramresult = false; return true; } bool tagcache_get_next(struct tagcache_search *tcs) { while (get_next(tcs)) { if (tcs->result_len > 1) return true; } return false; } bool tagcache_retrieve(struct tagcache_search *tcs, int idxid, int tag, char *buf, long size) { struct index_entry idx; *buf = '\0'; if (!get_index(tcs->masterfd, idxid, &idx, true)) return false; return retrieve(tcs, IF_DIRCACHE(idxid,) &idx, tag, buf, size); } static bool update_master_header(void) { struct master_header myhdr; int fd; if (!tc_stat.ready) return false; if ( (fd = open_master_fd(&myhdr, true)) < 0) return false; myhdr.serial = current_tcmh.serial; myhdr.commitid = current_tcmh.commitid; myhdr.dirty = current_tcmh.dirty; /* Write it back */ lseek(fd, 0, SEEK_SET); ecwrite(fd, &myhdr, 1, master_header_ec, tc_stat.econ); close(fd); return true; } void tagcache_search_finish(struct tagcache_search *tcs) { int i; if (!tcs->initialized) return; if (tcs->masterfd >= 0) { close(tcs->masterfd); tcs->masterfd = -1; } for (i = 0; i < TAG_COUNT; i++) { if (tcs->idxfd[i] >= 0) { close(tcs->idxfd[i]); tcs->idxfd[i] = -1; } } tcs->ramsearch = false; tcs->valid = false; tcs->initialized = 0; if (write_lock > 0) write_lock--; } #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) static struct tagfile_entry *get_tag(const struct index_entry *entry, int tag) { return (struct tagfile_entry *)&tcramcache.hdr->tags[tag][entry->tag_seek[tag]]; } static long get_tag_numeric(const struct index_entry *entry, int tag, int idx_id) { return check_virtual_tags(tag, idx_id, entry); } static char* get_tag_string(const struct index_entry *entry, int tag) { char* s = get_tag(entry, tag)->tag_data; return strcmp(s, UNTAGGED) ? s : NULL; } bool tagcache_fill_tags(struct mp3entry *id3, const char *filename) { struct index_entry *entry; int idx_id; if (!tc_stat.ready || !tc_stat.ramcache) return false; /* Find the corresponding entry in tagcache. */ idx_id = find_entry_ram(filename); if (idx_id < 0) return false; entry = &tcramcache.hdr->indices[idx_id]; memset(id3, 0, sizeof(struct mp3entry)); char* buf = id3->id3v2buf; ssize_t remaining = sizeof(id3->id3v2buf); /* this macro sets id3 strings by copying to the id3v2buf */ #define SET(x, y) do \ { \ if (remaining > 0) \ { \ x = NULL; /* initialize with null if tag doesn't exist */ \ char* src = get_tag_string(entry, y); \ if (src) \ { \ x = buf; \ size_t len = strlcpy(buf, src, remaining) +1; \ buf += len; remaining -= len; \ } \ } \ } while(0) SET(id3->title, tag_title); SET(id3->artist, tag_artist); SET(id3->album, tag_album); SET(id3->genre_string, tag_genre); SET(id3->composer, tag_composer); SET(id3->comment, tag_comment); SET(id3->albumartist, tag_albumartist); SET(id3->grouping, tag_grouping); id3->length = get_tag_numeric(entry, tag_length, idx_id); id3->playcount = get_tag_numeric(entry, tag_playcount, idx_id); id3->rating = get_tag_numeric(entry, tag_rating, idx_id); id3->lastplayed = get_tag_numeric(entry, tag_lastplayed, idx_id); id3->score = get_tag_numeric(entry, tag_virt_autoscore, idx_id) / 10; id3->year = get_tag_numeric(entry, tag_year, idx_id); id3->discnum = get_tag_numeric(entry, tag_discnumber, idx_id); id3->tracknum = get_tag_numeric(entry, tag_tracknumber, idx_id); id3->bitrate = get_tag_numeric(entry, tag_bitrate, idx_id); if (id3->bitrate == 0) id3->bitrate = 1; if (global_settings.autoresume_enable) { id3->elapsed = get_tag_numeric(entry, tag_lastelapsed, idx_id); logf("tagcache_fill_tags: Set elapsed for %s to %lX\n", id3->title, id3->elapsed); id3->offset = get_tag_numeric(entry, tag_lastoffset, idx_id); logf("tagcache_fill_tags: Set offset for %s to %lX\n", id3->title, id3->offset); } return true; } #endif /* defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) */ static inline void write_item(const char *item) { int len = strlen(item) + 1; data_size += len; write(cachefd, item, len); } static int check_if_empty(char **tag) { int length; if (*tag == NULL || **tag == '\0') { *tag = UNTAGGED; return sizeof(UNTAGGED); /* Tag length */ } length = strlen(*tag); if (length > TAG_MAXLEN) { logf("over length tag: %s", *tag); length = TAG_MAXLEN; str_setlen((*tag), length); } return length + 1; } /* GCC 3.4.6 for Coldfire can choose to inline this function. Not a good * idea, as it uses lots of stack and is called from a recursive function * (check_dir). */ static void NO_INLINE add_tagcache(char *path, unsigned long mtime) { #define ADD_TAG(entry, tag, data) \ /* Adding tag */ \ entry.tag_length[tag] = check_if_empty(data); \ entry.tag_offset[tag] = offset; \ offset += entry.tag_length[tag] struct mp3entry id3; struct temp_file_entry entry; bool ret; int fd; int idx_id = -1; char tracknumfix[3]; int offset = 0; int path_length = strlen(path); bool has_albumartist; bool has_grouping; #ifdef SIMULATOR /* Crude logging for the sim - to aid in debugging */ int logfd = open(ROCKBOX_DIR "/database.log", O_WRONLY | O_APPEND | O_CREAT, 0666); if (logfd >= 0) { write(logfd, path, strlen(path)); write(logfd, "\n", 1); close(logfd); } #endif /* SIMULATOR */ if (cachefd < 0) return ; /* Check for overlength file path. */ if (path_length > TAG_MAXLEN) { /* Path can't be shortened. */ logf("Too long path: %s", path); return ; } /* Check if the file is supported. */ if (probe_file_format(path) == AFMT_UNKNOWN) return ; /* Check if the file is already cached. */ #if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) idx_id = find_entry_ram(path); #endif /* Be sure the entry doesn't exist. */ if (filenametag_fd >= 0 && idx_id < 0) idx_id = find_entry_disk(path, false); /* Check if file has been modified. */ if (idx_id >= 0) { struct index_entry idx; /* TODO: Mark that the index exists (for fast reverse scan) */ //found_idx[idx_id/8] |= idx_id%8; if (!get_index(-1, idx_id, &idx, true)) { logf("failed to retrieve index entry"); return ; } if ((unsigned long)idx.tag_seek[tag_mtime] == mtime) { /* No changes to file. */ return ; } /* Metadata might have been changed. Delete the entry. */ logf("Re-adding: %s", path); if (!delete_entry(idx_id)) { logf("delete_entry failed: %d", idx_id); return ; } } fd = open(path, O_RDONLY); if (fd < 0) { logf("open fail: %s", path); return ; } memset(&id3, 0, sizeof(struct mp3entry)); memset(&entry, 0, sizeof(struct temp_file_entry)); memset(&tracknumfix, 0, sizeof(tracknumfix)); ret = get_metadata(&id3, fd, path); close(fd); if (!ret) return ; logf("-> %s", path); if (id3.tracknum <= 0) /* Track number missing? */ { id3.tracknum = -1; } /* Numeric tags */ entry.tag_offset[tag_year] = id3.year; entry.tag_offset[tag_discnumber] = id3.discnum; entry.tag_offset[tag_tracknumber] = id3.tracknum; entry.tag_offset[tag_length] = id3.length; entry.tag_offset[tag_bitrate] = id3.bitrate; entry.tag_offset[tag_mtime] = mtime; /* String tags. */ has_albumartist = id3.albumartist != NULL && strlen(id3.albumartist) > 0; has_grouping = id3.grouping != NULL && strlen(id3.grouping) > 0; ADD_TAG(entry, tag_filename, &path); ADD_TAG(entry, tag_title, &id3.title); ADD_TAG(entry, tag_artist, &id3.artist); ADD_TAG(entry, tag_album, &id3.album); ADD_TAG(entry, tag_genre, &id3.genre_string); ADD_TAG(entry, tag_composer, &id3.composer); ADD_TAG(entry, tag_comment, &id3.comment); if (has_albumartist) { ADD_TAG(entry, tag_albumartist, &id3.albumartist); } else { ADD_TAG(entry, tag_albumartist, &id3.artist); } if (has_grouping) { ADD_TAG(entry, tag_grouping, &id3.grouping); } else { ADD_TAG(entry, tag_grouping, &id3.title); } entry.data_length = offset; /* Write the header */ write(cachefd, &entry, sizeof(struct temp_file_entry)); /* And tags also... Correct order is critical */ write_item(path); write_item(id3.title); write_item(id3.artist); write_item(id3.album); write_item(id3.genre_string); write_item(id3.composer); write_item(id3.comment); if (has_albumartist) { write_item(id3.albumartist); } else { write_item(id3.artist); } if (has_grouping) { write_item(id3.grouping); } else { write_item(id3.title); } total_entry_count++; #undef ADD_TAG } static bool tempbuf_insert(char *str, int id, int idx_id, bool unique) { struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; int len = strlen(str)+1; int i; unsigned crc32; unsigned *crcbuf = (unsigned *)&tempbuf[tempbuf_size-4]; char buf[TAGCACHE_BUFSZ]; const int bufsz = sizeof(buf); for (i = 0; str[i] != '\0' && i < bufsz-1; i++) buf[i] = tolower(str[i]); crc32 = crc_32(buf, i, 0xffffffff); if (unique) { /* Check if the crc does not exist -> entry does not exist for sure. */ for (i = 0; i < tempbufidx; i++) { if (crcbuf[-i] != crc32) continue; if (!strcasecmp(str, index[i].str)) { if (id < 0 || id >= lookup_buffer_depth) { logf("lookup buf overf.: %d", id); return false; } lookup[id] = &index[i]; return true; } } } /* Insert to CRC buffer. */ crcbuf[-tempbufidx] = crc32; tempbuf_left -= 4; /* Insert it to the buffer. */ tempbuf_left -= len; if (tempbuf_left - 4 < 0 || tempbufidx >= commit_entry_count-1) return false; if (id >= lookup_buffer_depth) { logf("lookup buf overf. #2: %d", id); return false; } if (id >= 0) { lookup[id] = &index[tempbufidx]; index[tempbufidx].idlist.id = id; } else index[tempbufidx].idlist.id = -1; index[tempbufidx].idlist.next = NULL; index[tempbufidx].idx_id = idx_id; index[tempbufidx].seek = -1; index[tempbufidx].str = &tempbuf[tempbuf_pos]; memcpy(index[tempbufidx].str, str, len); tempbuf_pos += len; tempbufidx++; return true; } static int compare(const void *p1, const void *p2) { do_timed_yield(); struct tempbuf_searchidx *e1 = (struct tempbuf_searchidx *)p1; struct tempbuf_searchidx *e2 = (struct tempbuf_searchidx *)p2; if (strcmp(e1->str, UNTAGGED) == 0) { if (strcmp(e2->str, UNTAGGED) == 0) return 0; return -1; } else if (strcmp(e2->str, UNTAGGED) == 0) return 1; return strncasecmp(e1->str, e2->str, TAG_MAXLEN); } static int tempbuf_sort(int fd) { struct tempbuf_searchidx *index = (struct tempbuf_searchidx *)tempbuf; struct tagfile_entry fe; int i; int length; /* Generate reverse lookup entries. */ for (i = 0; i < lookup_buffer_depth; i++) { struct tempbuf_id_list *idlist; if (!lookup[i]) continue; if (lookup[i]->idlist.id == i) continue; idlist = &lookup[i]->idlist; while (idlist->next != NULL) idlist = idlist->next; tempbuf_left -= sizeof(struct tempbuf_id_list); if (tempbuf_left - 4 < 0) return -1; idlist->next = (struct tempbuf_id_list *)&tempbuf[tempbuf_pos]; if (tempbuf_pos & 0x03) { tempbuf_pos = (tempbuf_pos & ~0x03) + 0x04; tempbuf_left -= 3; idlist->next = (struct tempbuf_id_list *)&tempbuf[tempbuf_pos]; } tempbuf_pos += sizeof(struct tempbuf_id_list); idlist = idlist->next; idlist->id = i; idlist->next = NULL; do_timed_yield(); } qsort(index, tempbufidx, sizeof(struct tempbuf_searchidx), compare); memset(lookup, 0, lookup_buffer_depth * sizeof(struct tempbuf_searchidx **)); for (i = 0; i < tempbufidx; i++) { struct tempbuf_id_list *idlist = &index[i].idlist; /* Fix the lookup list. */ while (idlist != NULL) { if (idlist->id >= 0) lookup[idlist->id] = &index[i]; idlist = idlist->next; } index[i].seek = lseek(fd, 0, SEEK_CUR); length = strlen(index[i].str) + 1; fe.tag_length = length; fe.idx_id = index[i].idx_id; /* Check the chunk alignment. */ if ((fe.tag_length + sizeof(struct tagfile_entry)) % TAGFILE_ENTRY_CHUNK_LENGTH) { fe.tag_length += TAGFILE_ENTRY_CHUNK_LENGTH - ((fe.tag_length + sizeof(struct tagfile_entry)) % TAGFILE_ENTRY_CHUNK_LENGTH); } #ifdef TAGCACHE_STRICT_ALIGN /* Make sure the entry is long aligned. */ if (index[i].seek & 0x03) { logf("tempbuf_sort: alignment error!"); return -3; } #endif if (ecwrite(fd, &fe, 1, tagfile_entry_ec, tc_stat.econ) != sizeof(struct tagfile_entry)) { logf("tempbuf_sort: write error #1"); return -1; } if (write(fd, index[i].str, length) != length) { logf("tempbuf_sort: write error #2"); return -2; } /* Write some padding. */ if (fe.tag_length - length > 0) write(fd, "XXXXXXXX", fe.tag_length - length); } return i; } inline static struct tempbuf_searchidx* tempbuf_locate(int id) { if (id < 0 || id >= lookup_buffer_depth) return NULL; return lookup[id]; } inline static int tempbuf_find_location(int id) { struct tempbuf_searchidx *entry; entry = tempbuf_locate(id); if (entry == NULL) return -1; return entry->seek; } static bool build_numeric_indices(struct tagcache_header *h, int tmpfd) { struct master_header tcmh; struct index_entry idx; int masterfd; int masterfd_pos; struct temp_file_entry *entrybuf = (struct temp_file_entry *)tempbuf; int max_entries; int entries_processed = 0; int i, j; char buf[TAGCACHE_BUFSZ]; const int bufsz = sizeof(buf); max_entries = tempbuf_size / sizeof(struct temp_file_entry) - 1; logf("Building numeric indices..."); lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET); if ( (masterfd = open_master_fd(&tcmh, true)) < 0) return false; masterfd_pos = lseek(masterfd, tcmh.tch.entry_count * sizeof(struct index_entry), SEEK_CUR); if (masterfd_pos < 0) { logf("we can't append!"); close(masterfd); return false; } while (entries_processed < h->entry_count) { int count = MIN(h->entry_count - entries_processed, max_entries); /* Read in as many entries as possible. */ for (i = 0; i < count; i++) { struct temp_file_entry *tfe = &entrybuf[i]; int datastart; /* Read in numeric data. */ if (read(tmpfd, tfe, sizeof(struct temp_file_entry)) != sizeof(struct temp_file_entry)) { logf("read fail #1"); close(masterfd); return false; } datastart = lseek(tmpfd, 0, SEEK_CUR); /** * Read string data from the following tags: * - tag_filename * - tag_artist * - tag_album * - tag_title * * A crc32 hash is calculated from the read data * and stored back to the data offset field kept in memory. */ #define tmpdb_read_string_tag(tag) \ lseek(tmpfd, tfe->tag_offset[tag], SEEK_CUR); \ if ((unsigned long)tfe->tag_length[tag] > (unsigned long)bufsz) \ { \ logf("read fail: buffer overflow"); \ close(masterfd); \ return false; \ } \ \ if (read(tmpfd, buf, tfe->tag_length[tag]) != \ tfe->tag_length[tag]) \ { \ logf("read fail #2"); \ close(masterfd); \ return false; \ } \ str_setlen(buf, tfe->tag_length[tag]); \ \ tfe->tag_offset[tag] = crc_32(buf, strlen(buf), 0xffffffff); \ lseek(tmpfd, datastart, SEEK_SET) tmpdb_read_string_tag(tag_filename); tmpdb_read_string_tag(tag_artist); tmpdb_read_string_tag(tag_album); tmpdb_read_string_tag(tag_title); /* Seek to the end of the string data. */ lseek(tmpfd, tfe->data_length, SEEK_CUR); } /* Backup the master index position. */ masterfd_pos = lseek(masterfd, 0, SEEK_CUR); lseek(masterfd, sizeof(struct master_header), SEEK_SET); /* Check if we can resurrect some deleted runtime statistics data. */ for (i = 0; i < tcmh.tch.entry_count; i++) { /* Read the index entry. */ if (ecread_index_entry(masterfd, &idx) != sizeof(struct index_entry)) { logf("read fail #3"); close(masterfd); return false; } /** * Skip unless the entry is marked as being deleted * or the data has already been resurrected. */ if (!(idx.flag & FLAG_DELETED) || (idx.flag & FLAG_RESURRECTED)) continue; /* Now try to match the entry. */ /** * To succesfully match a song, the following conditions * must apply: * * For numeric fields: tag_length * - Full identical match is required * * If tag_filename matches, no further checking necessary. * * For string hashes: tag_artist, tag_album, tag_title * - All three of these must match */ for (j = 0; j < count; j++) { struct temp_file_entry *tfe = &entrybuf[j]; /* Try to match numeric fields first. */ if (tfe->tag_offset[tag_length] != idx.tag_seek[tag_length]) continue; /* Now it's time to do the hash matching. */ if (tfe->tag_offset[tag_filename] != idx.tag_seek[tag_filename]) { int match_count = 0; /* No filename match, check if we can match two other tags. */ #define tmpdb_match(tag) \ if (tfe->tag_offset[tag] == idx.tag_seek[tag]) \ match_count++ tmpdb_match(tag_artist); tmpdb_match(tag_album); tmpdb_match(tag_title); if (match_count < 3) { /* Still no match found, give up. */ continue; } } /* A match found, now copy & resurrect the statistical data. */ #define tmpdb_copy_tag(tag) \ tfe->tag_offset[tag] = idx.tag_seek[tag] tmpdb_copy_tag(tag_playcount); tmpdb_copy_tag(tag_rating); tmpdb_copy_tag(tag_playtime); tmpdb_copy_tag(tag_lastplayed); tmpdb_copy_tag(tag_commitid); tmpdb_copy_tag(tag_lastelapsed); tmpdb_copy_tag(tag_lastoffset); /* Avoid processing this entry again. */ idx.flag |= FLAG_RESURRECTED; lseek(masterfd, -(off_t)sizeof(struct index_entry), SEEK_CUR); if (ecwrite_index_entry(masterfd, &idx) != sizeof(struct index_entry)) { logf("masterfd writeback fail #1"); close(masterfd); return false; } logf("Entry resurrected"); } } /* Restore the master index position. */ lseek(masterfd, masterfd_pos, SEEK_SET); /* Commit the data to the index. */ for (i = 0; i < count; i++) { int loc = lseek(masterfd, 0, SEEK_CUR); if (ecread_index_entry(masterfd, &idx) != sizeof(struct index_entry)) { logf("read fail #3"); close(masterfd); return false; } for (j = 0; j < TAG_COUNT; j++) { if (!TAGCACHE_IS_NUMERIC(j)) continue; idx.tag_seek[j] = entrybuf[i].tag_offset[j]; } idx.flag = entrybuf[i].flag; if (idx.tag_seek[tag_commitid]) { /* Data has been resurrected. */ idx.flag |= FLAG_DIRTYNUM; } else if (tc_stat.ready && current_tcmh.commitid > 0) { idx.tag_seek[tag_commitid] = current_tcmh.commitid; idx.flag |= FLAG_DIRTYNUM; } /* Write back the updated index. */ lseek(masterfd, loc, SEEK_SET); if (ecwrite_index_entry(masterfd, &idx) != sizeof(struct index_entry)) { logf("write fail"); close(masterfd); return false; } } entries_processed += count; logf("%d/%ld entries processed", entries_processed, h->entry_count); } close(masterfd); return true; } /** * Return values: * > 0 success * == 0 temporary failure * < 0 fatal error */ static int build_index(int index_type, struct tagcache_header *h, int tmpfd) { int i; struct tagcache_header tch; struct master_header tcmh; struct index_entry idxbuf[IDX_BUF_DEPTH]; int idxbuf_pos; char buf[TAGCACHE_BUFSZ]; const long bufsz = sizeof(buf); int fd = -1, masterfd; bool error = false; int init; int masterfd_pos; logf("Building index: %d", index_type); /* Check the number of entries we need to allocate ram for. */ commit_entry_count = h->entry_count + 1; masterfd = open_master_fd(&tcmh, false); if (masterfd >= 0) { commit_entry_count += tcmh.tch.entry_count; close(masterfd); } else remove_files(); /* Just to be sure we are clean. */ /* Open the index file, which contains the tag names. */ fd = open_tag_fd(&tch, index_type, true); if (fd >= 0) { logf("tch.datasize=%ld", tch.datasize); lookup_buffer_depth = 1 + /* First part */ commit_entry_count + /* Second part */ (tch.datasize / TAGFILE_ENTRY_CHUNK_LENGTH); } else { lookup_buffer_depth = 1 + /* First part */ commit_entry_count + /* Second part */ 0; } logf("lookup_buffer_depth=%ld", lookup_buffer_depth); logf("commit_entry_count=%ld", commit_entry_count); /* Allocate buffer for all index entries from both old and new * tag files. */ tempbufidx = 0; tempbuf_pos = commit_entry_count * sizeof(struct tempbuf_searchidx); /* Allocate lookup buffer. The first portion of commit_entry_count * contains the new tags in the temporary file and the second * part for locating entries already in the db. * * New tags Old tags * +---------+---------------------------+ * | index | position/ENTRY_CHUNK_SIZE | lookup buffer * +---------+---------------------------+ * * Old tags are inserted to a temporary buffer with position: * tempbuf_insert(position/ENTRY_CHUNK_SIZE, ...); * And new tags with index: * tempbuf_insert(idx, ...); * * The buffer is sorted and written into tag file: * tempbuf_sort(...); * leaving master index locations messed up. * * That is fixed using the lookup buffer for old tags: * new_seek = tempbuf_find_location(old_seek, ...); * and for new tags: * new_seek = tempbuf_find_location(idx); */ lookup = (struct tempbuf_searchidx **)&tempbuf[tempbuf_pos]; tempbuf_pos += lookup_buffer_depth * sizeof(void **); memset(lookup, 0, lookup_buffer_depth * sizeof(void **)); /* And calculate the remaining data space used mainly for storing * tag data (strings). */ tempbuf_left = tempbuf_size - tempbuf_pos - 8; if (tempbuf_left - TAGFILE_ENTRY_AVG_LENGTH * commit_entry_count < 0) { logf("Buffer way too small!"); close(fd); return 0; } if (fd >= 0) { /** * If tag file contains unique tags (sorted index), we will load * it entirely into memory so we can resort it later for use with * chunked browsing. */ if (TAGCACHE_IS_SORTED(index_type)) { logf("loading tags..."); for (i = 0; i < tch.entry_count; i++) { struct tagfile_entry entry; int loc = lseek(fd, 0, SEEK_CUR); bool ret; switch (ecread_tagfile_entry_and_tag(fd, &entry, buf, bufsz)) { case e_SUCCESS: break; case e_ENTRY_SIZEMISMATCH: logf("read error #7"); close(fd); return -2; case e_TAG_TOOLONG: logf("too long tag #3"); close(fd); return -2; case e_TAG_SIZEMISMATCH: logf("read error #8"); close(fd); return -2; default: logf("unknown_error"); break;; } /* Skip deleted entries. */ if (buf[0] == '\0') continue; /** * Save the tag and tag id in the memory buffer. Tag id * is saved so we can later reindex the master lookup * table when the index gets resorted. */ ret = tempbuf_insert(buf, loc/TAGFILE_ENTRY_CHUNK_LENGTH + commit_entry_count, entry.idx_id, TAGCACHE_IS_UNIQUE(index_type)); if (!ret) { close(fd); return -3; } do_timed_yield(); } logf("done"); } else tempbufidx = tch.entry_count; } else { /** * Creating new index file to store the tags. No need to preload * anything whether the index type is sorted or not. */ snprintf(buf, bufsz, TAGCACHE_FILE_INDEX, index_type); fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { logf("%s open fail", buf); return -2; } tch.magic = TAGCACHE_MAGIC; tch.entry_count = 0; tch.datasize = 0; if (ecwrite(fd, &tch, 1, tagcache_header_ec, tc_stat.econ) != sizeof(struct tagcache_header)) { logf("header write failed"); close(fd); return -2; } } /* Loading the tag lookup file as "master file". */ logf("Loading index file"); masterfd = open(TAGCACHE_FILE_MASTER, O_RDWR); if (masterfd < 0) { logf("Creating new DB"); masterfd = open(TAGCACHE_FILE_MASTER, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (masterfd < 0) { logf("Failure to create index file (%s)", TAGCACHE_FILE_MASTER); close(fd); return -2; } /* Write the header (write real values later). */ memset(&tcmh, 0, sizeof(struct master_header)); tcmh.tch = *h; tcmh.tch.entry_count = 0; tcmh.tch.datasize = 0; tcmh.dirty = true; ecwrite(masterfd, &tcmh, 1, master_header_ec, tc_stat.econ); init = true; masterfd_pos = lseek(masterfd, 0, SEEK_CUR); } else { /** * Master file already exists so we need to process the current * file first. */ init = false; if (ecread(masterfd, &tcmh, 1, master_header_ec, tc_stat.econ) != sizeof(struct master_header) || tcmh.tch.magic != TAGCACHE_MAGIC) { logf("header error"); close(fd); close(masterfd); return -2; } /** * If we reach end of the master file, we need to expand it to * hold new tags. If the current index is not sorted, we can * simply append new data to end of the file. * However, if the index is sorted, we need to update all tag * pointers in the master file for the current index. */ masterfd_pos = lseek(masterfd, tcmh.tch.entry_count * sizeof(struct index_entry), SEEK_CUR); if (masterfd_pos == filesize(masterfd)) { logf("appending..."); init = true; } } /** * Load new unique tags in memory to be sorted later and added * to the master lookup file. */ if (TAGCACHE_IS_SORTED(index_type)) { lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET); /* h is the header of the temporary file containing new tags. */ logf("inserting new tags..."); for (i = 0; i < h->entry_count; i++) { struct temp_file_entry entry; if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) != sizeof(struct temp_file_entry)) { logf("read fail #3"); error = true; goto error_exit; } /* Read data. */ if (entry.tag_length[index_type] >= (long)bufsz) { logf("too long entry!"); error = true; goto error_exit; } lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR); if (read(tmpfd, buf, entry.tag_length[index_type]) != entry.tag_length[index_type]) { logf("read fail #4"); error = true; goto error_exit; } str_setlen(buf, entry.tag_length[index_type]); if (TAGCACHE_IS_UNIQUE(index_type)) error = !tempbuf_insert(buf, i, -1, true); else error = !tempbuf_insert(buf, i, tcmh.tch.entry_count + i, false); if (error) { logf("insert error"); goto error_exit; } /* Skip to next. */ lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] - entry.tag_length[index_type], SEEK_CUR); do_timed_yield(); } logf("done"); /* Sort the buffer data and write it to the index file. */ lseek(fd, sizeof(struct tagcache_header), SEEK_SET); /** * We need to truncate the index file now. There can be junk left * at the end of file (however, we _should_ always follow the * entry_count and don't crash with that). */ ftruncate(fd, lseek(fd, 0, SEEK_CUR)); i = tempbuf_sort(fd); if (i < 0) goto error_exit; logf("sorted %d tags", i); /** * Now update all indexes in the master lookup file. */ logf("updating indices..."); lseek(masterfd, sizeof(struct master_header), SEEK_SET); for (i = 0; i < tcmh.tch.entry_count; i += idxbuf_pos) { int j; int loc = lseek(masterfd, 0, SEEK_CUR); idxbuf_pos = MIN(tcmh.tch.entry_count - i, IDX_BUF_DEPTH); if (ecread(masterfd, idxbuf, idxbuf_pos, index_entry_ec, tc_stat.econ) != (int)sizeof(struct index_entry)*idxbuf_pos) { logf("read fail #5"); error = true; goto error_exit ; } lseek(masterfd, loc, SEEK_SET); for (j = 0; j < idxbuf_pos; j++) { if (idxbuf[j].flag & FLAG_DELETED) { /* We can just ignore deleted entries. */ // idxbuf[j].tag_seek[index_type] = 0; continue; } idxbuf[j].tag_seek[index_type] = tempbuf_find_location( idxbuf[j].tag_seek[index_type]/TAGFILE_ENTRY_CHUNK_LENGTH + commit_entry_count); if (idxbuf[j].tag_seek[index_type] < 0) { logf("update error: %ld/%d/%ld", idxbuf[j].flag, i+j, tcmh.tch.entry_count); error = true; goto error_exit; } do_timed_yield(); } /* Write back the updated index. */ if (ecwrite(masterfd, idxbuf, idxbuf_pos, index_entry_ec, tc_stat.econ) != (int)sizeof(struct index_entry)*idxbuf_pos) { logf("write fail"); error = true; goto error_exit; } } logf("done"); } /** * Walk through the temporary file containing the new tags. */ // build_normal_index(h, tmpfd, masterfd, idx); logf("updating new indices..."); lseek(masterfd, masterfd_pos, SEEK_SET); lseek(tmpfd, sizeof(struct tagcache_header), SEEK_SET); lseek(fd, 0, SEEK_END); for (i = 0; i < h->entry_count; i += idxbuf_pos) { int j; idxbuf_pos = MIN(h->entry_count - i, IDX_BUF_DEPTH); if (init) { memset(idxbuf, 0, sizeof(struct index_entry)*IDX_BUF_DEPTH); } else { int loc = lseek(masterfd, 0, SEEK_CUR); if (ecread(masterfd, idxbuf, idxbuf_pos, index_entry_ec, tc_stat.econ) != (int)sizeof(struct index_entry)*idxbuf_pos) { logf("read fail #6"); error = true; break ; } lseek(masterfd, loc, SEEK_SET); } /* Read entry headers. */ for (j = 0; j < idxbuf_pos; j++) { if (!TAGCACHE_IS_SORTED(index_type)) { struct temp_file_entry entry; struct tagfile_entry fe; if (read(tmpfd, &entry, sizeof(struct temp_file_entry)) != sizeof(struct temp_file_entry)) { logf("read fail #7"); error = true; break ; } /* Read data. */ if (entry.tag_length[index_type] >= (int)sizeof(buf)) { logf("too long entry!"); logf("length=%d", entry.tag_length[index_type]); logf("pos=0x%02lx", lseek(tmpfd, 0, SEEK_CUR)); error = true; break ; } lseek(tmpfd, entry.tag_offset[index_type], SEEK_CUR); if (read(tmpfd, buf, entry.tag_length[index_type]) != entry.tag_length[index_type]) { logf("read fail #8"); logf("offset=0x%02lx", entry.tag_offset[index_type]); logf("length=0x%02x", entry.tag_length[index_type]); error = true; break ; } /* Write to index file. */ idxbuf[j].tag_seek[index_type] = lseek(fd, 0, SEEK_CUR); fe.tag_length = entry.tag_length[index_type]; fe.idx_id = tcmh.tch.entry_count + i + j; ecwrite(fd, &fe, 1, tagfile_entry_ec, tc_stat.econ); write(fd, buf, fe.tag_length); tempbufidx++; /* Skip to next. */ lseek(tmpfd, entry.data_length - entry.tag_offset[index_type] - entry.tag_length[index_type], SEEK_CUR); } else { /* Locate the correct entry from the sorted array. */ idxbuf[j].tag_seek[index_type] = tempbuf_find_location(i + j); if (idxbuf[j].tag_seek[index_type] < 0) { logf("entry not found (%d)", j); error = true; break ; } } } /* Write index. */ if (ecwrite(masterfd, idxbuf, idxbuf_pos, index_entry_ec, tc_stat.econ) != (int)sizeof(struct index_entry)*idxbuf_pos) { logf("tagcache: write fail #4"); error = true; break ; } do_timed_yield(); } logf("done"); /* Finally write the header. */ tch.magic = TAGCACHE_MAGIC; tch.entry_count = tempbufidx; tch.datasize = lseek(fd, 0, SEEK_END) - sizeof(struct tagcache_header); lseek(fd, 0, SEEK_SET); ecwrite(fd, &tch, 1, tagcache_header_ec, tc_stat.econ); if (index_type != tag_filename) h->datasize += tch.datasize; logf("s:%d/%ld/%ld", index_type, tch.datasize, h->datasize); error_exit: close(fd); close(masterfd); if (error) return -2; return 1; } static bool commit(void) { struct tagcache_header tch; struct master_header tcmh; int i, len, rc; int tmpfd; int masterfd; #ifdef HAVE_DIRCACHE bool dircache_buffer_stolen = false; #endif #ifdef HAVE_TC_RAMCACHE bool ramcache_buffer_stolen = false; #endif logf("committing tagcache"); while (write_lock) sleep(1); tmpfd = open(TAGCACHE_FILE_TEMP, O_RDONLY); if (tmpfd < 0) { logf("nothing to commit"); return true; } /* Load the header. */ len = sizeof(struct tagcache_header); rc = read(tmpfd, &tch, len); if (tch.magic != TAGCACHE_MAGIC || rc != len) { logf("incorrect tmpheader"); close(tmpfd); remove(TAGCACHE_FILE_TEMP); return false; } if (tch.entry_count == 0) logf("nothing to commit"); /* Fully initialize existing headers (if any) before going further. */ tc_stat.ready = check_all_headers(); #ifdef HAVE_EEPROM_SETTINGS remove(TAGCACHE_STATEFILE); #endif /* At first be sure to unload the ramcache! */ #ifdef HAVE_TC_RAMCACHE tc_stat.ramcache = false; #endif /* Beyond here, jump to commit_error to undo locks and restore dircache */ rc = false; read_lock++; /* Try to steal every buffer we can :) */ #ifdef HAVE_DIRCACHE if (tempbuf_size == 0) { /* Suspend dircache to free its allocation. */ dircache_free_buffer(); dircache_buffer_stolen = true; allocate_tempbuf(); } #endif /* HAVE_DIRCACHE */ #ifdef HAVE_TC_RAMCACHE if (tempbuf_size == 0 && tc_stat.ramcache_allocated > 0) { tcrc_buffer_lock(); tempbuf = (char *)(tcramcache.hdr + 1); tempbuf_size = tc_stat.ramcache_allocated - sizeof(struct ramcache_header) - 128; tempbuf_size &= ~0x03; ramcache_buffer_stolen = true; } #endif /* HAVE_TC_RAMCACHE */ /* And finally fail if there are no buffers available. */ if (tempbuf_size == 0) { logf("delaying commit until next boot"); tc_stat.commit_delayed = true; close(tmpfd); goto commit_error; } logf("commit %ld entries...", tch.entry_count); /* Mark DB dirty so it will stay disabled if commit fails. */ current_tcmh.dirty = true; update_master_header(); /* Now create the index files. */ tc_stat.commit_step = 0; tch.datasize = 0; tc_stat.commit_delayed = false; for (i = 0; i < TAG_COUNT; i++) { int ret; if (TAGCACHE_IS_NUMERIC(i)) continue; tc_stat.commit_step++; ret = build_index(i, &tch, tmpfd); if (ret <= 0) { close(tmpfd); logf("tagcache failed init"); if (ret == 0) tc_stat.commit_delayed = true; tc_stat.commit_step = 0; goto commit_error; } } if (!build_numeric_indices(&tch, tmpfd)) { logf("Failure to commit numeric indices"); close(tmpfd); tc_stat.commit_step = 0; goto commit_error; } close(tmpfd); tc_stat.commit_step = 0; /* Update the master index headers. */ if ( (masterfd = open_master_fd(&tcmh, true)) < 0) goto commit_error; remove(TAGCACHE_FILE_TEMP); tcmh.tch.entry_count += tch.entry_count; tcmh.tch.datasize = sizeof(struct master_header) + sizeof(struct index_entry) * tcmh.tch.entry_count + tch.datasize; tcmh.dirty = false; tcmh.commitid++; lseek(masterfd, 0, SEEK_SET); ecwrite(masterfd, &tcmh, 1, master_header_ec, tc_stat.econ); close(masterfd); logf("tagcache committed"); tc_stat.ready = check_all_headers(); tc_stat.readyvalid = true; #ifdef HAVE_TC_RAMCACHE if (ramcache_buffer_stolen) { tempbuf = NULL; tempbuf_size = 0; ramcache_buffer_stolen = false; tcrc_buffer_unlock(); } /* Reload tagcache. */ if (tc_stat.ramcache_allocated > 0) tagcache_start_scan(); #endif /* HAVE_TC_RAMCACHE */ rc = true; commit_error: #ifdef HAVE_TC_RAMCACHE if (ramcache_buffer_stolen) { tempbuf = NULL; tempbuf_size = 0; tcrc_buffer_unlock(); } #endif /* HAVE_TC_RAMCACHE */ read_lock--; #ifdef HAVE_DIRCACHE /* Resume the dircache, if we stole the buffer. */ if (dircache_buffer_stolen) { free_tempbuf(); dircache_resume(); } #endif /* HAVE_DIRCACHE */ return rc; } #ifndef __PCTOOL__ static bool modify_numeric_entry(int masterfd, int idx_id, int tag, long data) { struct index_entry idx; if (!tc_stat.ready) return false; if (!TAGCACHE_IS_NUMERIC(tag)) return false; if (!get_index(masterfd, idx_id, &idx, false)) return false; idx.tag_seek[tag] = data; idx.flag |= FLAG_DIRTYNUM; return write_index(masterfd, idx_id, &idx); } #if 0 bool tagcache_modify_numeric_entry(struct tagcache_search *tcs, int tag, long data) { struct master_header myhdr; if (tcs->masterfd < 0) { if ( (tcs->masterfd = open_master_fd(&myhdr, true)) < 0) return false; } return modify_numeric_entry(tcs->masterfd, tcs->idx_id, tag, data); } #endif static bool command_queue_is_full(void) { int next; next = command_queue_widx + 1; if (next >= TAGCACHE_COMMAND_QUEUE_LENGTH) next = 0; return (next == command_queue_ridx); } static void command_queue_sync_callback(void) { struct master_header myhdr; int masterfd; mutex_lock(&command_queue_mutex); if ( (masterfd = open_master_fd(&myhdr, true)) < 0) return; while (command_queue_ridx != command_queue_widx) { struct tagcache_command_entry *ce = &command_queue[command_queue_ridx]; switch (ce->command) { case CMD_UPDATE_MASTER_HEADER: { close(masterfd); update_master_header(); /* Re-open the masterfd. */ if ( (masterfd = open_master_fd(&myhdr, true)) < 0) return; break; } case CMD_UPDATE_NUMERIC: { modify_numeric_entry(masterfd, ce->idx_id, ce->tag, ce->data); break; } } if (++command_queue_ridx >= TAGCACHE_COMMAND_QUEUE_LENGTH) command_queue_ridx = 0; } close(masterfd); tc_stat.queue_length = 0; mutex_unlock(&command_queue_mutex); } static void run_command_queue(bool force) { if (COMMAND_QUEUE_IS_EMPTY) return; if (force || command_queue_is_full()) command_queue_sync_callback(); else register_storage_idle_func(command_queue_sync_callback); } static void queue_command(int cmd, long idx_id, int tag, long data) { while (1) { int next; mutex_lock(&command_queue_mutex); next = command_queue_widx + 1; if (next >= TAGCACHE_COMMAND_QUEUE_LENGTH) next = 0; /* Make sure queue is not full. */ if (next != command_queue_ridx) { struct tagcache_command_entry *ce = &command_queue[command_queue_widx]; ce->command = cmd; ce->idx_id = idx_id; ce->tag = tag; ce->data = data; command_queue_widx = next; tc_stat.queue_length++; mutex_unlock(&command_queue_mutex); break; } /* Queue is full, try again later... */ mutex_unlock(&command_queue_mutex); sleep(1); } } long tagcache_increase_serial(void) { long old; if (!tc_stat.ready) return -2; while (read_lock) sleep(1); old = current_tcmh.serial++; queue_command(CMD_UPDATE_MASTER_HEADER, 0, 0, 0); return old; } void tagcache_update_numeric(int idx_id, int tag, long data) { queue_command(CMD_UPDATE_NUMERIC, idx_id, tag, data); } #endif /* !__PCTOOL__ */ static bool write_tag(int fd, const char *tagstr, const char *datastr) { char buf[512]; const int bufsz = sizeof(buf); int i; snprintf(buf, bufsz, "%s=\"", tagstr); for (i = strlen(buf); i < (long)sizeof(buf)-4; i++) { if (*datastr == '\0') break; if (*datastr == '"' || *datastr == '\\') buf[i++] = '\\'; else if (*datastr == '\n') { buf[i++] = '\\'; buf[i] = 'n'; continue; } buf[i] = *(datastr++); } str_setlen(buf, bufsz - 1); strlcpy(&buf[i], "\" ", (bufsz - i - 1)); write(fd, buf, i + 2); return true; } #ifndef __PCTOOL__ static bool read_tag(char *dest, long size, const char *src, const char *tagstr) { int pos; char current_tag[32]; while (*src != '\0') { /* Skip all whitespace */ while (*src == ' ') src++; if (*src == '\0') break; pos = 0; /* Read in tag name */ while (*src != '=' && *src != ' ') { current_tag[pos] = *src; src++; pos++; if (*src == '\0' || pos >= (int) sizeof(current_tag)) return false; } str_setlen(current_tag, pos); /* Read in tag data */ /* Find the start. */ while (*src != '"' && *src != '\0') src++; if (*src == '\0' || *(++src) == '\0') return false; /* Read the data. */ for (pos = 0; pos < size; pos++) { if (*src == '\0') break; if (*src == '\\') { src++; if (*src == 'n') dest[pos] = '\n'; else dest[pos] = *src; src++; continue; } if (*src == '\0') break; if (*src == '"') { src++; break; } dest[pos] = *(src++); } str_setlen(dest, pos); if (!strcasecmp(tagstr, current_tag)) return true; } return false; } static int parse_changelog_line(int line_n, char *buf, void *parameters) { struct index_entry idx; char tag_data[TAGCACHE_BUFSZ]; int idx_id; long masterfd = (long)(intptr_t)parameters; const int import_tags[] = { tag_playcount, tag_rating, tag_playtime, tag_lastplayed, tag_commitid, tag_lastelapsed, tag_lastoffset }; int i; (void)line_n; if (*buf == '#') return 0; /* logf("%d/%s", line_n, buf); */ if (!read_tag(tag_data, sizeof tag_data, buf, "filename")) { logf("%d/filename missing", line_n); logf("-> %s", buf); return 0; } idx_id = find_index(tag_data); if (idx_id < 0) { logf("%d/entry not found", line_n); return 0; } if (!get_index(masterfd, idx_id, &idx, false)) { logf("%d/failed to retrieve index entry", line_n); return 0; } /* Stop if tag has already been modified. */ if (idx.flag & FLAG_DIRTYNUM) return 0; logf("%d/import: %s", line_n, tag_data); idx.flag |= FLAG_DIRTYNUM; for (i = 0; i < (long)(sizeof(import_tags)/sizeof(import_tags[0])); i++) { int data; if (!read_tag(tag_data, sizeof tag_data, buf, tagcache_tag_to_str(import_tags[i]))) { continue; } data = atoi(tag_data); if (data < 0) continue; idx.tag_seek[import_tags[i]] = data; if (import_tags[i] == tag_lastplayed && data >= current_tcmh.serial) current_tcmh.serial = data + 1; else if (import_tags[i] == tag_commitid && data >= current_tcmh.commitid) current_tcmh.commitid = data + 1; } return write_index(masterfd, idx_id, &idx) ? 0 : -5; } bool tagcache_import_changelog(void) { struct master_header myhdr; struct tagcache_header tch; int clfd; long masterfd; char buf[2048]; const int bufsz = sizeof(buf); if (!tc_stat.ready) return false; while (read_lock) sleep(1); clfd = open(TAGCACHE_FILE_CHANGELOG, O_RDONLY); if (clfd < 0) { logf("failure to open changelog"); return false; } if ( (masterfd = open_master_fd(&myhdr, true)) < 0) { close(clfd); return false; } write_lock++; filenametag_fd = open_tag_fd(&tch, tag_filename, false); fast_readline(clfd, buf, bufsz, (void *)(intptr_t)masterfd, parse_changelog_line); close(clfd); close(masterfd); if (filenametag_fd >= 0) { close(filenametag_fd); filenametag_fd = -1; } write_lock--; update_master_header(); return true; } #endif /* !__PCTOOL__ */ bool tagcache_create_changelog(struct tagcache_search *tcs) { struct master_header myhdr; struct index_entry idx; char buf[TAGCACHE_BUFSZ]; const int bufsz = sizeof(buf); char temp[32]; int clfd; int i, j; if (!tc_stat.ready) return false; if (!tagcache_search(tcs, tag_filename)) return false; /* Initialize the changelog */ clfd = open(TAGCACHE_FILE_CHANGELOG, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (clfd < 0) { logf("failure to open changelog"); return false; } if (tcs->masterfd < 0) { if ( (tcs->masterfd = open_master_fd(&myhdr, false)) < 0) { close(clfd); return false; } } else { lseek(tcs->masterfd, 0, SEEK_SET); ecread(tcs->masterfd, &myhdr, 1, master_header_ec, tc_stat.econ); } write(clfd, "## Changelog version 1\n", 23); for (i = 0; i < myhdr.tch.entry_count; i++) { if (ecread_index_entry(tcs->masterfd, &idx) != sizeof(struct index_entry)) { logf("read error #9"); tagcache_search_finish(tcs); close(clfd); return false; } /* Skip until the entry found has been modified. */ if (! (idx.flag & FLAG_DIRTYNUM) ) continue; /* Skip deleted entries too. */ if (idx.flag & FLAG_DELETED) continue; /* Now retrieve all tags. */ for (j = 0; j < TAG_COUNT; j++) { if (TAGCACHE_IS_NUMERIC(j)) { snprintf(temp, sizeof temp, "%d", (int)idx.tag_seek[j]); write_tag(clfd, tagcache_tag_to_str(j), temp); continue; } tcs->type = j; tagcache_retrieve(tcs, i, tcs->type, buf, bufsz); write_tag(clfd, tagcache_tag_to_str(j), buf); } write(clfd, "\n", 1); do_timed_yield(); } close(clfd); tagcache_search_finish(tcs); return true; } static bool delete_entry(long idx_id) { int fd = -1; int masterfd = -1; int tag, i; struct index_entry idx, myidx; struct master_header myhdr; char buf[TAGCACHE_BUFSZ]; const int bufsz = sizeof(buf); int in_use[TAG_COUNT]; logf("delete_entry(): %ld", idx_id); #ifdef HAVE_TC_RAMCACHE /* At first mark the entry removed from ram cache. */ if (tc_stat.ramcache) tcramcache.hdr->indices[idx_id].flag |= FLAG_DELETED; #endif if ( (masterfd = open_master_fd(&myhdr, true) ) < 0) return false; lseek(masterfd, idx_id * sizeof(struct index_entry), SEEK_CUR); if (ecread_index_entry(masterfd, &myidx) != sizeof(struct index_entry)) { logf("delete_entry(): read error"); goto cleanup; } if (myidx.flag & FLAG_DELETED) { logf("delete_entry(): already deleted!"); goto cleanup; } myidx.flag |= FLAG_DELETED; lseek(masterfd, -(off_t)sizeof(struct index_entry), SEEK_CUR); if (ecwrite_index_entry(masterfd, &myidx) != sizeof(struct index_entry)) { logf("delete_entry(): write_error #1"); goto cleanup; } /* Now check which tags are no longer in use (if any) */ for (tag = 0; tag < TAG_COUNT; tag++) in_use[tag] = 0; lseek(masterfd, sizeof(struct master_header), SEEK_SET); for (i = 0; i < myhdr.tch.entry_count; i++) { struct index_entry *idxp; #ifdef HAVE_TC_RAMCACHE /* Use RAM DB if available for greater speed */ if (tc_stat.ramcache) idxp = &tcramcache.hdr->indices[i]; else #endif { if (ecread_index_entry(masterfd, &idx) != sizeof(struct index_entry)) { logf("delete_entry(): read error #2"); goto cleanup; } idxp = &idx; } if (idxp->flag & FLAG_DELETED) continue; for (tag = 0; tag < TAG_COUNT; tag++) { if (TAGCACHE_IS_NUMERIC(tag)) continue; if (idxp->tag_seek[tag] == myidx.tag_seek[tag]) in_use[tag]++; } } /* Now delete all tags no longer in use. */ for (tag = 0; tag < TAG_COUNT; tag++) { struct tagcache_header tch; int oldseek = myidx.tag_seek[tag]; if (TAGCACHE_IS_NUMERIC(tag)) continue; /** * Replace tag seek with a hash value of the field string data. * That way runtime statistics of moved or altered files can be * resurrected. */ #ifdef HAVE_TC_RAMCACHE if (tc_stat.ramcache && tag != tag_filename) { struct tagfile_entry *tfe; int32_t *seek = &tcramcache.hdr->indices[idx_id].tag_seek[tag]; tfe = (struct tagfile_entry *)&tcramcache.hdr->tags[tag][*seek]; tcrc_buffer_lock(); /* protect tfe and seek if crc_32() yield()s */ *seek = crc_32(tfe->tag_data, strlen(tfe->tag_data), 0xffffffff); tcrc_buffer_unlock(); myidx.tag_seek[tag] = *seek; } else #endif /* HAVE_TC_RAMCACHE */ { struct tagfile_entry tfe; /* Open the index file, which contains the tag names. */ if ((fd = open_tag_fd(&tch, tag, true)) < 0) goto cleanup; /* Skip the header block */ lseek(fd, myidx.tag_seek[tag], SEEK_SET); switch (ecread_tagfile_entry_and_tag(fd, &tfe, buf, bufsz)) { case e_SUCCESS: break; case e_ENTRY_SIZEMISMATCH: logf("delete_entry(): read error #3"); goto cleanup; case e_TAG_TOOLONG: logf("too long tag #4"); goto cleanup; case e_TAG_SIZEMISMATCH: logf("delete_entry(): read error #3"); goto cleanup; default: logf("unknown_error"); break;; } myidx.tag_seek[tag] = crc_32(buf, strlen(buf), 0xffffffff); } if (in_use[tag]) { logf("in use: %d/%d", tag, in_use[tag]); if (fd >= 0) { close(fd); fd = -1; } continue; } #ifdef HAVE_TC_RAMCACHE /* Delete from ram. */ if (tc_stat.ramcache && tag != tag_filename) { struct tagfile_entry *tagentry = (struct tagfile_entry *)&tcramcache.hdr->tags[tag][oldseek]; str_setlen(tagentry->tag_data, 0); } #endif /* HAVE_TC_RAMCACHE */ /* Open the index file, which contains the tag names. */ if (fd < 0) { if ((fd = open_tag_fd(&tch, tag, true)) < 0) goto cleanup; } /* Skip the header block */ lseek(fd, oldseek + sizeof(struct tagfile_entry), SEEK_SET); /* Debug, print 10 first characters of the tag read(fd, buf, 10); buf[10]='\0'; logf("TAG:%s", buf); lseek(fd, -10, SEEK_CUR); */ /* Write first data byte in tag as \0 */ write(fd, "", 1); /* Now tag data has been removed */ close(fd); fd = -1; } /* Write index entry back into master index. */ lseek(masterfd, sizeof(struct master_header) + (idx_id * sizeof(struct index_entry)), SEEK_SET); if (ecwrite_index_entry(masterfd, &myidx) != sizeof(struct index_entry)) { logf("delete_entry(): write_error #2"); goto cleanup; } close(masterfd); return true; cleanup: if (fd >= 0) close(fd); if (masterfd >= 0) close(masterfd); return false; } /** * Returns true if there is an event waiting in the queue * that requires the current operation to be aborted. */ static bool check_event_queue(void) { #ifndef __PCTOOL__ struct queue_event ev; if(!queue_peek(&tagcache_queue, &ev)) return false; switch (ev.id) { case Q_STOP_SCAN: case SYS_POWEROFF: case SYS_USB_CONNECTED: return true; } #endif /* __PCTOOL__ */ return false; } #ifdef HAVE_TC_RAMCACHE static void fix_ramcache(void* old_addr, void* new_addr) { ptrdiff_t offpos = new_addr - old_addr; for (int i = 0; i < TAG_COUNT; i++) tcramcache.hdr->tags[i] += offpos; } static int move_cb(int handle, void* current, void* new) { if (tcramcache.move_lock > 0) return BUFLIB_CB_CANNOT_MOVE; fix_ramcache(current, new); tcramcache.hdr = new; return BUFLIB_CB_OK; (void)handle; } static struct buflib_callbacks ops = { .move_callback = move_cb, .shrink_callback = NULL, }; static bool allocate_tagcache(void) { tc_stat.ramcache_allocated = 0; tcramcache.handle = 0; tcramcache.hdr = NULL; /* Load the header. */ struct master_header tcmh; int fd = open_master_fd(&tcmh, false); if (fd < 0) return false; close(fd); /** * Now calculate the required cache size plus * some extra space for alignment fixes. */ size_t alloc_size = tcmh.tch.datasize + 256 + TAGCACHE_RESERVE + sizeof(struct ramcache_header) + TAG_COUNT*sizeof(void *); #ifdef HAVE_DIRCACHE alloc_size += tcmh.tch.entry_count*sizeof(struct dircache_fileref); #endif int handle = core_alloc_ex("tc ramcache", alloc_size, &ops); if (handle <= 0) return false; tcramcache.handle = handle; tcramcache.hdr = core_get_data(handle); tc_stat.ramcache_allocated = alloc_size; memset(tcramcache.hdr, 0, sizeof(struct ramcache_header)); memcpy(¤t_tcmh, &tcmh, sizeof current_tcmh); logf("tagcache: %d bytes allocated.", tc_stat.ramcache_allocated); return true; } #ifdef HAVE_EEPROM_SETTINGS static bool tagcache_dumpload(void) { struct statefile_header shdr; int fd, rc, handle; tcramcache.handle = 0; tcramcache.hdr = NULL; fd = open(TAGCACHE_STATEFILE, O_RDONLY); if (fd < 0) { logf("no tagcache statedump"); return false; } /* Check the statefile memory placement */ rc = read(fd, &shdr, sizeof(struct statefile_header)); if (rc != sizeof(struct statefile_header) || shdr.magic != TAGCACHE_STATEFILE_MAGIC || shdr.mh.tch.magic != TAGCACHE_MAGIC) { logf("incorrect statefile"); close(fd); return false; } /* Lets allocate real memory and load it */ handle = core_alloc_ex("tc ramcache", shdr.tc_stat.ramcache_allocated, &ops); if (handle <= 0) { logf("alloc failure"); return false; } tcrc_buffer_lock(); tcramcache.handle = handle; tcramcache.hdr = core_get_data(handle); rc = read(fd, tcramcache.hdr, shdr.tc_stat.ramcache_allocated); tcrc_buffer_unlock(); close(fd); if (rc != shdr.tc_stat.ramcache_allocated) { logf("read failure!"); core_free(handle); return false; } tc_stat = shdr.tc_stat; /* Now fix the pointers */ fix_ramcache(shdr.hdr, tcramcache.hdr); /* Load the tagcache master header (should match the actual DB file header). */ memcpy(¤t_tcmh, &shdr.mh, sizeof current_tcmh); return true; } static bool tagcache_dumpsave(void) { struct statefile_header shdr; int fd; if (!tc_stat.ramcache) return false; fd = open(TAGCACHE_STATEFILE, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { logf("failed to create a statedump"); return false; } /* Create the header */ shdr.magic = TAGCACHE_STATEFILE_MAGIC; shdr.hdr = tcramcache.hdr; memcpy(&shdr.mh, ¤t_tcmh, sizeof current_tcmh); memcpy(&shdr.tc_stat, &tc_stat, sizeof tc_stat); write(fd, &shdr, sizeof shdr); /* And dump the data too */ tcrc_buffer_lock(); write(fd, tcramcache.hdr, tc_stat.ramcache_allocated); tcrc_buffer_unlock(); close(fd); return true; } #endif /* HAVE_EEPROM_SETTINGS */ static bool load_tagcache(void) { /* DEBUG: After tagcache commit and dircache rebuild, hdr-sturcture * may become corrupt. */ bool const auto_update = global_settings.tagcache_autoupdate; bool ok = false; ssize_t bytesleft = tc_stat.ramcache_allocated - sizeof(struct ramcache_header); int fd; #ifdef HAVE_DIRCACHE /* Wait for any in-progress dircache build to complete */ dircache_wait(); #endif /* HAVE_DIRCACHE */ logf("loading tagcache to ram..."); tcrc_buffer_lock(); /* lock for the rest of the scan, simpler to handle */ fd = open(TAGCACHE_FILE_MASTER, O_RDONLY); if (fd < 0) { logf("tagcache open failed"); goto failure; } struct master_header tcmh; if (ecread(fd, &tcmh, 1, master_header_ec, tc_stat.econ) != sizeof(struct master_header) || tcmh.tch.magic != TAGCACHE_MAGIC) { logf("incorrect header"); goto failure; } /* Master header copy should already match, this can be redundant to do. */ current_tcmh = tcmh; /* Load the master index table. */ for (int i = 0; i < tcmh.tch.entry_count; i++) { bytesleft -= sizeof(struct index_entry); if (bytesleft < 0) { logf("too big tagcache."); goto failure; } int rc = ecread_index_entry(fd, &tcramcache.hdr->indices[i]); if (rc != sizeof (struct index_entry)) { logf("read error #10"); goto failure; } } close(fd); fd = -1; /* Load the tags right after the index entries */ char *p = (char *)&tcramcache.hdr->indices[tcmh.tch.entry_count]; for (int tag = 0; tag < TAG_COUNT; tag++) { ssize_t rc; if (TAGCACHE_IS_NUMERIC(tag)) continue; p = TC_ALIGN_PTR(p, struct tagcache_header, &rc); bytesleft -= rc; if (bytesleft < (ssize_t)sizeof(struct tagcache_header)) { logf("Too big tagcache #10.5"); goto failure; } tcramcache.hdr->tags[tag] = p; /* Load the header */ struct tagcache_header *tch = (struct tagcache_header *)p; p += sizeof(struct tagcache_header); bytesleft -= sizeof (struct tagcache_header); fd = open_tag_fd(tch, tag, false); if (rc < 0) goto failure; /* Load the entries for this tag */ for (tcramcache.hdr->entry_count[tag] = 0; tcramcache.hdr->entry_count[tag] < tch->entry_count; tcramcache.hdr->entry_count[tag]++) { /* Abort if we got a critical event in queue */ if (do_timed_yield() && check_event_queue()) goto failure; p = TC_ALIGN_PTR(p, struct tagfile_entry, &rc); bytesleft -= rc; if (bytesleft < (ssize_t)sizeof(struct tagfile_entry)) { logf("Too big tagcache #10.75"); goto failure; } struct tagfile_entry *fe = (struct tagfile_entry *)p; off_t pos = lseek(fd, 0, SEEK_CUR); /* Load the header for the tag itself */ if (ecread_tagfile_entry(fd, fe) != sizeof(struct tagfile_entry)) { /* End of lookup table. */ logf("read error #11"); goto failure; } int idx_id = fe->idx_id; /* dircache reference clobbers *fe */ struct index_entry *idx = &tcramcache.hdr->indices[idx_id]; if (idx_id != -1 || tag == tag_filename) /* filename NOT optional */ { if (idx_id < 0 || idx_id >= tcmh.tch.entry_count) { logf("corrupt tagfile entry:tag=%d:idxid=%d", tag, idx_id); goto failure; } if (idx->tag_seek[tag] != pos) { logf("corrupt data structures!:"); logf(" tag_seek[%d]=%ld:pos=%ld", tag, idx->tag_seek[tag], pos); goto failure; } } /* We have a special handling for the filename tags; neither the paths nor the entry headers are stored; only the tagcache header and dircache references are. */ if (tag == tag_filename) { #ifdef HAVE_DIRCACHE if (idx->flag & FLAG_DIRCACHE) { /* This flag must not be used yet. */ logf("internal error!"); goto failure; } p += sizeof (struct dircache_fileref); bytesleft -= sizeof (struct dircache_fileref); #endif /* HAVE_DIRCACHE */ char filename[TAGCACHE_BUFSZ]; if (fe->tag_length >= (long)sizeof(filename)-1) { read(fd, filename, 10); str_setlen(filename, 10); logf("TAG:%s", filename); logf("too long filename"); goto failure; } if ((idx->flag & FLAG_DELETED) IFN_DIRCACHE( || !auto_update )) { /* seek over tag data instead of reading */ if (lseek(fd, fe->tag_length, SEEK_CUR) < 0) { logf("read error #11.5"); goto failure; } continue; } if (read(fd, filename, fe->tag_length) != fe->tag_length) { logf("read error #12"); goto failure; } #ifdef HAVE_DIRCACHE /* If auto updating, check storage too, otherwise simply attempt to load cache references */ unsigned int searchflag = auto_update ? DCS_STORAGE_PATH : DCS_CACHED_PATH; int rc = dircache_search(searchflag | DCS_UPDATE_FILEREF, &tcrc_dcfrefs[idx_id], filename); if (rc > 0) /* in cache and we have fileref */ idx->flag |= FLAG_DIRCACHE; else if (rc == 0) /* not in cache but okay */ ; else if (auto_update) #else /* ndef HAVE_DIRCACHE */ /* Check if path is no longer valid */ if (!file_exists(filename)) #endif /* HAVE_DIRCACHE */ { logf("Entry no longer valid."); logf("-> %s", filename); delete_entry(idx_id); } continue; } bytesleft -= sizeof(struct tagfile_entry) + fe->tag_length; if (bytesleft < 0) { logf("too big tagcache #2"); logf("tl: %ld", fe->tag_length); logf("bl: %ld", bytesleft); goto failure; } p = fe->tag_data; rc = read(fd, p, fe->tag_length); p += rc; if (rc != fe->tag_length) { logf("read error #13"); logf("rc=0x%04x", (unsigned int)rc); // 0x431 logf("len=0x%04lx", fe->tag_length); // 0x4000 logf("pos=0x%04lx", lseek(fd, 0, SEEK_CUR)); // 0x433 logf("tag=0x%02x", tag); // 0x00 goto failure; } } #ifdef HAVE_DIRCACHE if (tag == tag_filename) p = (char *)&tcrc_dcfrefs[tcmh.tch.entry_count]; #endif /* HAVE_DIRCACHE */ close(fd); } tc_stat.ramcache_used = tc_stat.ramcache_allocated - bytesleft; logf("tagcache loaded into ram!"); logf("utilization: %d%%", 100*tc_stat.ramcache_used / tc_stat.ramcache_allocated); ok = true; failure: if (fd >= 0) close(fd); tcrc_buffer_unlock(); return ok; } #endif /* HAVE_TC_RAMCACHE */ static bool check_deleted_files(void) { int fd; char buf[TAGCACHE_BUFSZ]; const int bufsz = sizeof(buf); struct tagfile_entry tfe; logf("reverse scan..."); snprintf(buf, bufsz, TAGCACHE_FILE_INDEX, tag_filename); fd = open(buf, O_RDONLY); if (fd < 0) { logf("%s open fail", buf); return false; } lseek(fd, sizeof(struct tagcache_header), SEEK_SET); while (ecread_tagfile_entry(fd, &tfe) == sizeof(struct tagfile_entry) && !check_event_queue()) { if (tfe.tag_length >= (long)bufsz-1) { logf("too long tag"); close(fd); return false; } if (read(fd, buf, tfe.tag_length) != tfe.tag_length) { logf("read error #14"); close(fd); return false; } str_setlen(buf, tfe.tag_length); /* Check if the file has already deleted from the db. */ if (*buf == '\0') continue; /* Now check if the file exists. */ if (!file_exists(buf)) { logf("Entry no longer valid."); logf("-> %s / %ld", buf, tfe.tag_length); delete_entry(tfe.idx_id); } do_timed_yield(); } close(fd); logf("done"); return true; } /* Note that this function must not be inlined, otherwise the whole point * of having the code in a separate function is lost. */ static void NO_INLINE check_ignore(const char *dirname, int *ignore, int *unignore) { char newpath[MAX_PATH]; const int bufsz = sizeof(newpath); /* check for a database.ignore file */ snprintf(newpath, bufsz, "%s/database.ignore", dirname); *ignore = file_exists(newpath); /* check for a database.unignore file */ snprintf(newpath, bufsz, "%s/database.unignore", dirname); *unignore = file_exists(newpath); } /* max roots on native. on application more can be added via malloc() */ #define MAX_STATIC_ROOTS 12 static struct search_roots_ll { const char *path; struct search_roots_ll * next; } roots_ll[MAX_STATIC_ROOTS]; /* check if the path is already included in the search roots, by the * means that the path itself or one of its parents folders is in the list */ static bool search_root_exists(const char *path) { struct search_roots_ll *this; for(this = &roots_ll[0]; this; this = this->next) { size_t root_len = strlen(this->path); /* check if the link target is inside of an existing search root * don't add if target is inside, we'll scan it later */ if (!strncmp(this->path, path, root_len)) return true; } return false; } #ifdef APPLICATION /* * This adds a path to the search roots, possibly during traveling through * the filesystem. It only adds if the path is not inside an already existing * search root. * * Returns true if it added the path to the search roots * * Windows 2000 and greater supports symlinks, but they don't provide * realpath() or readlink(), and symlinks are rarely used on them so * ignore this for windows for now **/ static bool add_search_root(const char *name) { (void)name; #ifndef WIN32 struct search_roots_ll *this, *prev = NULL; char target[MAX_PATH]; const int target_bufsz = sizeof(target); /* Okay, realpath() is almost completely broken on android * * It doesn't accept NULL for resolved_name to dynamically allocate * the resulting path; and it assumes resolved_name to be PATH_MAX * (actually MAXPATHLEN, but it's the same [as of 2.3]) long * and blindly writes to the end if it * * therefore use sufficiently large static storage here * Note that PATH_MAX != MAX_PATH **/ static char abs_target[PATH_MAX]; ssize_t len; len = readlink(name, target, target_bufsz-1); if (len < 0) return false; str_setlen(target, len); if (realpath(target, abs_target) == NULL) return false; if (search_root_exists(abs_target)) return false; /* get the end of the list */ for(this = &roots_ll[0]; this; prev = this, this = this->next); if (prev) { size_t len = strlen(abs_target) + 1; /* count \0 */ this = malloc(sizeof(struct search_roots_ll) + len ); if (!this || len > MIN(PATH_MAX, MAX_PATH)) { logf("Error at adding a search root: %s", this ? "path too long":"OOM"); free(this); prev->next = NULL; return false; } this->path = ((char*)this) + sizeof(struct search_roots_ll); strcpy((char*)this->path, abs_target); /* ok to cast const away here */ this->next = NULL; prev->next = this; logf("Added %s to the search roots\n", abs_target); return true; } #endif return false; } static int free_search_root_single(struct search_roots_ll * start) { if (start < &roots_ll[0] && start >= &roots_ll[MAX_STATIC_ROOTS]) { free(start->next); return sizeof(struct search_roots_ll); } return 0; } static int free_search_roots(struct search_roots_ll * start) { int ret = 0; if (start->next) { ret += free_search_root_single(start->next); } return ret; } #else /* native, simulator */ #define add_search_root(a) do {} while(0) #define free_search_roots(a) do {} while(0) #endif static bool check_dir(const char *dirname, int add_files) { int success = false; DIR *dir = opendir(dirname); if (!dir) { logf("tagcache: opendir(%s) failed", dirname); return false; } /* check for a database.ignore and database.unignore */ int ignore, unignore; check_ignore(dirname, &ignore, &unignore); /* don't do anything if both ignore and unignore are there */ if (ignore != unignore) add_files = unignore; /* Recursively scan the dir. */ while (!check_event_queue()) { struct dirent *entry = readdir(dir); if (entry == NULL) { success = true; break; } if (is_dotdir_name(entry->d_name)) continue; struct dirinfo info = dir_get_info(dir, entry); size_t len = strlen(curpath); path_append(&curpath[len-1], PA_SEP_HARD, entry->d_name, sizeof (curpath) - len); processed_dir_count++; if (info.attribute & ATTR_DIRECTORY) { #ifndef SIMULATOR /* don't follow symlinks to dirs, but try to add it as a search root * this makes able to avoid looping in recursive symlinks */ if (info.attribute & ATTR_LINK) add_search_root(curpath); else #endif /* SIMULATOR */ check_dir(curpath, add_files); } else if (add_files) { tc_stat.curentry = curpath; /* Add a new entry to the temporary db file. */ add_tagcache(curpath, info.mtime); /* Wait until current path for debug screen is read and unset. */ while (tc_stat.syncscreen && tc_stat.curentry != NULL) yield(); tc_stat.curentry = NULL; } str_setlen(curpath, len); } closedir(dir); return success; } void tagcache_screensync_event(void) { tc_stat.curentry = NULL; } void tagcache_screensync_enable(bool state) { tc_stat.syncscreen = state; } #ifndef __PCTOOL__ /* this is called by the database tool to not pull in global_settings */ static #endif void do_tagcache_build(const char *path[]) { struct tagcache_header header; bool ret; str_setlen(curpath, 0); data_size = 0; total_entry_count = 0; processed_dir_count = 0; #ifdef HAVE_DIRCACHE dircache_wait(); #endif logf("updating tagcache"); cachefd = open(TAGCACHE_FILE_TEMP, O_RDONLY); if (cachefd >= 0) { logf("skipping, cache already waiting for commit"); close(cachefd); return ; } cachefd = open(TAGCACHE_FILE_TEMP, O_RDWR | O_CREAT | O_TRUNC, 0666); if (cachefd < 0) { logf("master file open failed: %s", TAGCACHE_FILE_TEMP); return ; } filenametag_fd = open_tag_fd(&header, tag_filename, false); cpu_boost(true); logf("Scanning files..."); /* Scan for new files. */ memset(&header, 0, sizeof(struct tagcache_header)); write(cachefd, &header, sizeof(struct tagcache_header)); ret = true; roots_ll[0].path = path[0]; roots_ll[0].next = NULL; /* i is for the path vector, j for the roots_ll array * path can be skipped , but root_ll entries can't */ for(int i = 1, j = 1; path[i] && j < MAX_STATIC_ROOTS; i++) { if (search_root_exists(path[i])) /* skip this path */ continue; roots_ll[j].path = path[i]; roots_ll[j-1].next = &roots_ll[j]; j++; } struct search_roots_ll * this; /* check_dir might add new roots */ for(this = &roots_ll[0]; this; this = this->next) { strlcpy(curpath, this->path, sizeof(curpath)); ret = ret && check_dir(this->path, true); } free_search_roots(&roots_ll[0]); /* Write the header. */ header.magic = TAGCACHE_MAGIC; header.datasize = data_size; header.entry_count = total_entry_count; lseek(cachefd, 0, SEEK_SET); write(cachefd, &header, sizeof(struct tagcache_header)); close(cachefd); if (filenametag_fd >= 0) { close(filenametag_fd); filenametag_fd = -1; } if (!ret) { logf("Aborted."); cpu_boost(false); return ; } /* Commit changes to the database. */ #ifdef __PCTOOL__ allocate_tempbuf(); #endif if (commit()) { logf("tagcache built!"); } #ifdef __PCTOOL__ free_tempbuf(); #endif #ifdef HAVE_TC_RAMCACHE if (tcramcache.hdr) { /* Import runtime statistics if we just initialized the db. */ if (current_tcmh.serial == 0) queue_post(&tagcache_queue, Q_IMPORT_CHANGELOG, 0); } #endif cpu_boost(false); } #ifndef __PCTOOL__ void tagcache_build(void) { char *vect[MAX_STATIC_ROOTS + 1]; /* +1 to ensure NULL sentinel */ char str[sizeof(global_settings.tagcache_scan_paths)]; strlcpy(str, global_settings.tagcache_scan_paths, sizeof(str)); int res = split_string(str, ':', vect, MAX_STATIC_ROOTS); vect[res] = NULL; do_tagcache_build((const char**)vect); } #endif /* __PCTOOL__ */ #ifdef HAVE_TC_RAMCACHE static void load_ramcache(void) { if (!tcramcache.hdr) return ; cpu_boost(true); /* At first we should load the cache (if exists). */ tc_stat.ramcache = load_tagcache(); if (!tc_stat.ramcache) { /* If loading failed, it must indicate some problem with the db * so disable it entirely to prevent further issues. */ tc_stat.ready = false; tcramcache.hdr = NULL; int handle = tcramcache.handle; tcramcache.handle = 0; core_free(handle); } cpu_boost(false); } void tagcache_unload_ramcache(void) { tc_stat.ramcache = false; /* Just to make sure there is no statefile present. */ // remove(TAGCACHE_STATEFILE); } #endif /* HAVE_TC_RAMCACHE */ #ifndef __PCTOOL__ static void tagcache_thread(void) { struct queue_event ev; bool check_done = false; /* If the previous cache build/update was interrupted, commit * the changes first in foreground. */ cpu_boost(true); allocate_tempbuf(); commit(); free_tempbuf(); #ifdef HAVE_TC_RAMCACHE #ifdef HAVE_EEPROM_SETTINGS if (firmware_settings.initialized && firmware_settings.disk_clean && global_settings.tagcache_ram) { check_done = tagcache_dumpload(); } remove(TAGCACHE_STATEFILE); #endif /* HAVE_EEPROM_SETTINGS */ /* Allocate space for the tagcache if found on disk. */ if (global_settings.tagcache_ram && !tc_stat.ramcache) allocate_tagcache(); #endif /* HAVE_TC_RAMCACHE */ cpu_boost(false); tc_stat.initialized = true; /* Don't delay bootup with the header check but do it on background. */ if (!tc_stat.ready) { sleep(HZ); tc_stat.ready = check_all_headers(); tc_stat.readyvalid = true; } while (1) { run_command_queue(false); queue_wait_w_tmo(&tagcache_queue, &ev, HZ); switch (ev.id) { case Q_IMPORT_CHANGELOG: tagcache_import_changelog(); break; case Q_REBUILD: remove_files(); remove(TAGCACHE_FILE_TEMP); tagcache_build(); break; case Q_UPDATE: tagcache_build(); #ifdef HAVE_TC_RAMCACHE load_ramcache(); #endif check_deleted_files(); break ; case Q_START_SCAN: check_done = false; case SYS_TIMEOUT: if (check_done || !tc_stat.ready) break ; #ifdef HAVE_TC_RAMCACHE if (!tc_stat.ramcache && global_settings.tagcache_ram) { load_ramcache(); if (tc_stat.ramcache && global_settings.tagcache_autoupdate) tagcache_build(); } else #endif /* HAVE_RC_RAMCACHE */ if (global_settings.tagcache_autoupdate) { tagcache_build(); /* This will be very slow unless dircache is enabled or target is flash based, but do it anyway for consistency. */ check_deleted_files(); } logf("tagcache check done"); check_done = true; break ; case Q_STOP_SCAN: break ; case SYS_POWEROFF: break ; case SYS_USB_CONNECTED: logf("USB: TagCache"); usb_acknowledge(SYS_USB_CONNECTED_ACK); usb_wait_for_disconnect(&tagcache_queue); break ; } } } bool tagcache_prepare_shutdown(void) { if (tagcache_get_commit_step() > 0) return false; tagcache_stop_scan(); while (read_lock || write_lock) sleep(1); return true; } void tagcache_shutdown(void) { /* Flush the command queue. */ run_command_queue(true); #if defined(HAVE_EEPROM_SETTINGS) && defined(HAVE_TC_RAMCACHE) if (tc_stat.ramcache) tagcache_dumpsave(); #endif } static int get_progress(void) { int total_count = -1; #ifdef HAVE_DIRCACHE struct dircache_info dcinfo; dircache_get_info(&dcinfo); if (dcinfo.status != DIRCACHE_IDLE) { total_count = dcinfo.entry_count; } else #endif /* HAVE_DIRCACHE */ #ifdef HAVE_TC_RAMCACHE { if (tcramcache.hdr && tc_stat.ramcache) total_count = current_tcmh.tch.entry_count; } #endif /* HAVE_TC_RAMCACHE */ if (total_count < 0) return -1; else if (total_count == 0) return 0; else if (processed_dir_count > total_count) return 100; else return processed_dir_count * 100 / total_count; } struct tagcache_stat* tagcache_get_stat(void) { tc_stat.progress = get_progress(); tc_stat.processed_entries = processed_dir_count; return &tc_stat; } void tagcache_start_scan(void) { queue_post(&tagcache_queue, Q_START_SCAN, 0); } bool tagcache_update(void) { if (!tc_stat.ready) return false; queue_post(&tagcache_queue, Q_UPDATE, 0); return false; } bool tagcache_rebuild() { queue_post(&tagcache_queue, Q_REBUILD, 0); return false; } void tagcache_stop_scan(void) { queue_post(&tagcache_queue, Q_STOP_SCAN, 0); } #endif /* !__PCTOOL__ */ void tagcache_init(void) { memset(&tc_stat, 0, sizeof(struct tagcache_stat)); memset(¤t_tcmh, 0, sizeof(struct master_header)); filenametag_fd = -1; write_lock = read_lock = 0; #ifndef __PCTOOL__ mutex_init(&command_queue_mutex); queue_init(&tagcache_queue, true); create_thread(tagcache_thread, tagcache_stack, sizeof(tagcache_stack), 0, tagcache_thread_name IF_PRIO(, PRIORITY_BACKGROUND) IF_COP(, CPU)); #else tc_stat.initialized = true; allocate_tempbuf(); commit(); free_tempbuf(); tc_stat.ready = check_all_headers(); #endif } #ifdef __PCTOOL__ void tagcache_reverse_scan(void) { logf("Checking for deleted files"); check_deleted_files(); } #endif bool tagcache_is_initialized(void) { return tc_stat.initialized; } bool tagcache_is_fully_initialized(void) { return tc_stat.readyvalid; } bool tagcache_is_usable(void) { return tc_stat.initialized && tc_stat.ready; } int tagcache_get_commit_step(void) { return tc_stat.commit_step; } int tagcache_get_max_commit_step(void) { return (int)(SORTED_TAGS_COUNT)+1; }