/********************************************************************\ Name: ODB.C Created by: Stefan Ritt Contents: MIDAS online database functions $Id$ \********************************************************************/ #undef NDEBUG // midas required assert() to be always enabled /**dox***************************************************************/ /** @file odb.c The Online Database file */ /** @defgroup odbfunctionc ODB Functions (db_xxx) */ /**dox***************************************************************/ /** @addtogroup odbfunctionc * * @{ */ /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS #include "midas.h" #include "msystem.h" #include "mxml.h" #include "git-revision.h" #include "mstrlcpy.h" #include #include #include #include "mjson.h" #define CHECK_OPEN_RECORD 1 /*------------------------------------------------------------------*/ /********************************************************************\ * * * db_xxx - Database Functions * * * \********************************************************************/ /* Globals */ #ifdef LOCAL_ROUTINES static DATABASE *_database; static INT _database_entries = 0; #endif static RECORD_LIST *_record_list; static INT _record_list_entries = 0; static WATCH_LIST *_watch_list; static INT _watch_list_entries = 0; INT db_save_xml_key(HNDLE hDB, HNDLE hKey, INT level, MXML_WRITER * writer); /*------------------------------------------------------------------*/ #ifdef LOCAL_ROUTINES typedef struct db_err_msg_struct db_err_msg; static void db_msg(db_err_msg** msg, INT message_type, const char *filename, INT line, const char *routine, const char *format, ...) MATTRPRINTF(6,7); static void db_print_msg(const db_err_msg* msg); static void db_flush_msg(db_err_msg** msg); static INT db_find_key_locked(const DATABASE_HEADER *pheader, HNDLE hKey, const char *key_name, HNDLE * subhKey, db_err_msg** msg); static const KEY* db_find_pkey_locked(const DATABASE_HEADER *pheader, const KEY* pkey, const char *key_name, int *pstatus, db_err_msg** msg); static std::string db_get_path_locked(const DATABASE_HEADER* pheader, HNDLE hKey); static std::string db_get_path_locked(const DATABASE_HEADER* pheader, const KEY *pkey); static int db_scan_tree_locked(const DATABASE_HEADER* pheader, const KEY* pkey, int level, int(*callback) (const DATABASE_HEADER*, const KEY*, int, void*, db_err_msg**), void *info, db_err_msg** msg); static int db_set_mode_wlocked(DATABASE_HEADER*,KEY*,WORD mode,int recurse,db_err_msg**); static const KEY* db_resolve_link_locked(const DATABASE_HEADER*, const KEY*,int *pstatus, db_err_msg**); static int db_notify_clients_locked(const DATABASE_HEADER* pheader, HNDLE hDB, HNDLE hKeyMod, int index, BOOL bWalk, db_err_msg** msg); static int db_create_key_wlocked(DATABASE_HEADER* pheader, KEY* parentKey, const char *key_name, DWORD type, KEY** pnewkey, db_err_msg** msg); static int db_set_value_wlocked(DATABASE_HEADER* pheader, HNDLE hDB, KEY* pkey_root, const char *key_name, const void *data, INT data_size, INT num_values, DWORD type, db_err_msg** msg); static INT db_get_data_locked(DATABASE_HEADER* pheader, const KEY* pkey, int idx, void *data, INT * buf_size, DWORD type, db_err_msg** msg); static INT db_set_data_wlocked(DATABASE_HEADER* pheader, KEY* pkey, const void *data, INT data_size, INT num_values, DWORD type, const char* caller, db_err_msg** msg); static INT db_set_data_index_wlocked(DATABASE_HEADER* pheader, KEY* pkey, int idx, const void *data, INT data_size, DWORD type, const char* caller, db_err_msg** msg); static INT db_check_set_data_locked(DATABASE_HEADER* pheader, const KEY* pkey, const void *data, INT data_size, INT num_values, DWORD type, const char* caller, db_err_msg** msg); static INT db_check_set_data_index_locked(DATABASE_HEADER* pheader, const KEY* pkey, int idx, const void *data, INT data_size, DWORD type, const char* caller, db_err_msg** msg); static int db_remove_open_record_wlocked(DATABASE* pdb, DATABASE_HEADER* pheader, HNDLE hKey); #endif // LOCAL_ROUTINES /*------------------------------------------------------------------*/ /********************************************************************\ * * * db_msg_xxx error message handling * * * \********************************************************************/ #ifdef LOCAL_ROUTINES struct db_err_msg_struct { db_err_msg *next = NULL; int message_type = 0; std::string filename; int line = 0; std::string routine; std::string text; }; static db_err_msg* _last_error_message = NULL; // for debuging core dumps void db_print_msg(const db_err_msg* msg) { while (msg != NULL) { printf("db_err_msg: %p, next %p, type %d, file \'%s:%d\', function \'%s\': %s\n", msg, msg->next, msg->message_type, msg->filename.c_str(), msg->line, msg->routine.c_str(), msg->text.c_str()); msg = msg->next; } } void db_msg(db_err_msg** msgp, INT message_type, const char *filename, INT line, const char *routine, const char *format, ...) { if (!msgp) return; va_list argptr; char message[1000]; /* print argument list into message */ va_start(argptr, format); vsnprintf(message, sizeof(message)-1, format, argptr); va_end(argptr); message[sizeof(message)-1] = 0; // ensure string is NUL-terminated db_err_msg* msg = new db_err_msg; msg->next = NULL; msg->message_type = message_type; msg->filename = filename; msg->line = line; msg->routine = routine; msg->text = message; _last_error_message = msg; //printf("new message:\n"); //db_print_msg(msg); if (*msgp == NULL) { *msgp = msg; return; } // append new message to the end of the list db_err_msg *m = (*msgp); while (m->next != NULL) { m = m->next; } assert(m->next == NULL); m->next = msg; //printf("Message list with added new message:\n"); //db_print_msg(*msgp); return; } void db_flush_msg(db_err_msg** msgp) { db_err_msg *msg = *msgp; *msgp = NULL; if (/* DISABLES CODE */ (0)) { printf("db_flush_msg: %p\n", msg); db_print_msg(msg); } while (msg != NULL) { cm_msg(msg->message_type, msg->filename.c_str(), msg->line, msg->routine.c_str(), "%s", msg->text.c_str()); db_err_msg* next = msg->next; msg->message_type = 0; msg->next = NULL; delete msg; msg = next; } } #endif // LOCAL_ROUTINES /*------------------------------------------------------------------*/ #ifdef LOCAL_ROUTINES /********************************************************************\ * * * Shared Memory Allocation * * * \********************************************************************/ static bool db_validate_key_offset(const DATABASE_HEADER * pheader, int offset); /*------------------------------------------------------------------*/ static void *malloc_key(DATABASE_HEADER * pheader, INT size, const char* caller) { FREE_DESCRIP *pfree, *pfound, *pprev = NULL; if (size == 0) return NULL; /* quadword alignment for alpha CPU */ size = ALIGN8(size); //printf("malloc_key(%d) from [%s]\n", size, caller); if (!db_validate_key_offset(pheader, pheader->first_free_key)) { return NULL; } /* search for free block */ pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); //printf("try free block %p size %d, next %d\n", pfree, pfree->size, pfree->next_free); while (pfree->size < size && pfree->next_free) { if (!db_validate_key_offset(pheader, pfree->next_free)) { return NULL; } pprev = pfree; pfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); //printf("pfree %p size %d next_free %d\n", pfree, pfree->size, pfree->next_free); } //printf("found free block %p size %d\n", pfree, pfree->size); /* return if not enough memory */ if (pfree->size < size) return 0; pfound = pfree; /* if found block is first in list, correct pheader */ if (pfree == (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key)) { if (size < pfree->size) { /* free block is only used partially */ pheader->first_free_key += size; pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); pfree->size = pfound->size - size; pfree->next_free = pfound->next_free; } else { /* free block is used totally */ pheader->first_free_key = pfree->next_free; } } else { /* check if free block is used totally */ if (pfound->size - size < (int) sizeof(FREE_DESCRIP)) { /* skip block totally */ pprev->next_free = pfound->next_free; } else { /* decrease free block */ pfree = (FREE_DESCRIP *) ((char *) pfound + size); pfree->size = pfound->size - size; pfree->next_free = pfound->next_free; pprev->next_free = (POINTER_T) pfree - (POINTER_T) pheader; } } assert((void*)pfound != (void*)pheader); memset(pfound, 0, size); return pfound; } /*------------------------------------------------------------------*/ static void free_key(DATABASE_HEADER * pheader, void *address, INT size) { FREE_DESCRIP *pfree, *pprev, *pnext; if (size == 0) return; assert(address != pheader); /* quadword alignment for alpha CPU */ size = ALIGN8(size); pfree = (FREE_DESCRIP *) address; pprev = NULL; /* clear current block */ memset(address, 0, size); /* if key comes before first free block, adjust pheader */ if ((POINTER_T) address - (POINTER_T) pheader < pheader->first_free_key) { pfree->size = size; pfree->next_free = pheader->first_free_key; pheader->first_free_key = (POINTER_T) address - (POINTER_T) pheader; } else { /* find last free block before current block */ pprev = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); while (pprev->next_free < (POINTER_T) address - (POINTER_T) pheader) { if (pprev->next_free <= 0) { cm_msg(MERROR, "free_key", "database is corrupted: pprev=%p, pprev->next_free=%d", pprev, pprev->next_free); return; } pprev = (FREE_DESCRIP *) ((char *) pheader + pprev->next_free); } pfree->size = size; pfree->next_free = pprev->next_free; pprev->next_free = (POINTER_T) pfree - (POINTER_T) pheader; } /* try to melt adjacent free blocks after current block */ pnext = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); if ((POINTER_T) pnext == (POINTER_T) pfree + pfree->size) { pfree->size += pnext->size; pfree->next_free = pnext->next_free; memset(pnext, 0, pnext->size); } /* try to melt adjacent free blocks before current block */ if (pprev && pprev->next_free == (POINTER_T) pprev - (POINTER_T) pheader + pprev->size) { pprev->size += pfree->size; pprev->next_free = pfree->next_free; memset(pfree, 0, pfree->size); } } static int validate_free_data(DATABASE_HEADER * pheader, int free_data) { if (free_data <= 0) return 0; if (free_data < (int)sizeof(DATABASE_HEADER)) { //printf("validate_free_data: failed: %d is inside the database header 0..%d\n", free_data, (int)sizeof(DATABASE_HEADER)); return 0; } if (free_data < (int)sizeof(DATABASE_HEADER) + pheader->key_size) { //printf("validate_free_data: failed: %d is inside key space %d..%d\n", free_data, (int)sizeof(DATABASE_HEADER), (int)sizeof(DATABASE_HEADER) + pheader->key_size); return 0; } if (free_data > (int)sizeof(DATABASE_HEADER) + pheader->key_size + pheader->data_size) { //printf("validate_free_data: failed: %d is beyound end of odb %d+%d+%d = %d\n", free_data, (int)sizeof(DATABASE_HEADER), pheader->key_size, pheader->data_size, (int)sizeof(DATABASE_HEADER) + pheader->key_size + pheader->data_size); return 0; } return 1; } /*------------------------------------------------------------------*/ static void *malloc_data(DATABASE_HEADER * pheader, INT size) { if (size == 0) return NULL; assert(size > 0); /* quadword alignment for alpha CPU */ size = ALIGN8(size); /* smallest allocation size is 8 bytes to make sure we can always create a new FREE_DESCRIP in free_data() */ assert(size >= (int)sizeof(FREE_DESCRIP)); if (!validate_free_data(pheader, pheader->first_free_data)) { return NULL; } /* search for free block */ FREE_DESCRIP *pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); FREE_DESCRIP *pprev = NULL; FREE_DESCRIP *pfound = NULL; while (1) { //printf("malloc_data: pprev %p, pfree %p, next %d, size %d, want %d\n", pprev, pfree, pfree->next_free, pfree->size, size); if (pfree->size >= size) { // we will use this block pfound = pfree; break; } if (!pfree->next_free) { // no more free blocks return NULL; } if (!validate_free_data(pheader, pfree->next_free)) { // next_free is invalid //printf("malloc_data: pprev %p, pfree %p, next %d, size %d, next is invalid\n", pprev, pfree, pfree->next_free, pfree->size); return NULL; } pprev = pfree; pfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); } //printf("malloc_data: pprev %p, pfound %p, size %d, want %d\n", pprev, pfound, pfound->size, size); assert(pfound != NULL); assert(size <= pfound->size); /* if found block is first in list, correct pheader */ if (!pprev) { if (size < pfree->size) { /* free block is only used partially */ pheader->first_free_data += size; pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); pfree->size = pfound->size - size; pfree->next_free = pfound->next_free; } else { /* free block is used totally */ pheader->first_free_data = pfree->next_free; } } else { /* check if free block is used totally */ if (pfound->size - size < (int) sizeof(FREE_DESCRIP)) { /* delete this free block from the free blocks chain */ pprev->next_free = pfound->next_free; } else { /* decrease free block */ pfree = (FREE_DESCRIP *) ((char *) pfound + size); pfree->size = pfound->size - size; pfree->next_free = pfound->next_free; pprev->next_free = (POINTER_T) pfree - (POINTER_T) pheader; } } assert((void*)pfound != (void*)pheader); /* zero memeory */ memset(pfound, 0, size); return pfound; } /*------------------------------------------------------------------*/ static int free_data(DATABASE_HEADER * pheader, void *address, INT size, const char* caller) { if (size == 0) return DB_SUCCESS; assert(address != pheader); /* quadword alignment for alpha CPU */ size = ALIGN8(size); /* smallest allocation size is 8 bytes to make sure we can always create a new FREE_DESCRIP in free_data() */ assert(size >= (int)sizeof(FREE_DESCRIP)); FREE_DESCRIP *pprev = NULL; FREE_DESCRIP *pfree = (FREE_DESCRIP *) address; int pfree_offset = (POINTER_T) address - (POINTER_T) pheader; /* clear current block */ memset(address, 0, size); if (pheader->first_free_data == 0) { /* if free list is empty, create the first free block, adjust pheader */ pfree->size = size; pfree->next_free = 0; pheader->first_free_data = pfree_offset; /* nothing else to do */ return DB_SUCCESS; } else if ((POINTER_T) address - (POINTER_T) pheader < pheader->first_free_data) { /* if data comes before first free block, create new free block, adjust pheader */ pfree->size = size; pfree->next_free = pheader->first_free_data; pheader->first_free_data = pfree_offset; /* maybe merge next free block into the new free block */ //printf("free_data: created new first free block, maybe merge with old first free block\n"); } else { /* find last free block before current block */ pprev = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); while (pprev->next_free < pfree_offset) { if (pprev->next_free == 0) { /* add new free block at the end of the chain of free blocks */ //printf("free_data: adding new free block at the very end\n"); break; } if (!validate_free_data(pheader, pprev->next_free)) { cm_msg(MERROR, "free_data", "database is corrupted: pprev=%p, pprev->next_free=%d in free_data(%p,%p,%d) from %s", pprev, pprev->next_free, pheader, address, size, caller); return DB_CORRUPTED; } pprev = (FREE_DESCRIP *) ((char *) pheader + pprev->next_free); } pfree->size = size; pfree->next_free = pprev->next_free; pprev->next_free = pfree_offset; } /* try to melt adjacent free blocks after current block */ FREE_DESCRIP *pnext = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); if ((POINTER_T) pnext == (POINTER_T) pfree + pfree->size) { //printf("free_data: merging first and second free block\n"); pfree->size += pnext->size; pfree->next_free = pnext->next_free; memset(pnext, 0, pnext->size); } /* try to melt adjacent free blocks before current block */ if (pprev && pprev->next_free == (POINTER_T) pprev - (POINTER_T) pheader + pprev->size) { //printf("free_data: merging pprev and pfree\n"); pprev->size += pfree->size; pprev->next_free = pfree->next_free; memset(pfree, 0, pfree->size); } return DB_SUCCESS; } /*------------------------------------------------------------------*/ static void *realloc_data(DATABASE_HEADER * pheader, void *address, INT old_size, INT new_size, const char* caller) { void *tmp = NULL; if (old_size) { int status; tmp = malloc(old_size); if (tmp == NULL) { cm_msg(MERROR, "realloc_data", "cannot malloc(%d), called from %s", old_size, caller); return NULL; } memcpy(tmp, address, old_size); status = free_data(pheader, address, old_size, caller); if (status != DB_SUCCESS) { free(tmp); cm_msg(MERROR, "realloc_data", "cannot free_data(%p, %d), called from %s", address, old_size, caller); return NULL; } } void *pnew = malloc_data(pheader, new_size); if (!pnew) { if (tmp) free(tmp); cm_msg(MERROR, "realloc_data", "cannot malloc_data(%d), called from %s", new_size, caller); return NULL; } if (old_size) { memcpy(pnew, tmp, old_size < new_size ? old_size : new_size); free(tmp); } return pnew; } #endif // LOCAL_ROUTINES /*------------------------------------------------------------------*/ char *strcomb(const char **list) /* convert list of strings into single string to be used by db_paste() */ { INT i, j; static char *str = NULL; /* counter number of chars */ for (i = 0, j = 0; list[i]; i++) j += strlen(list[i]) + 1; j += 1; if (str == NULL) str = (char *) malloc(j); else str = (char *) realloc(str, j); str[0] = 0; for (i = 0; list[i]; i++) { strcat(str, list[i]); strcat(str, "\n"); } return str; } /*------------------------------------------------------------------*/ std::string strcomb1(const char **list) /* convert list of strings into single string to be used by db_paste() */ { std::string s; for (int i = 0; list[i]; i++) { s += list[i]; s += "\n"; } return s; } /*------------------------------------------------------------------*/ struct print_key_info_buf { int alloc_size; int used; char* buf; }; static void add_to_buf(struct print_key_info_buf* buf, const char* s) { int len = strlen(s); if (buf->used + len + 10 > buf->alloc_size) { int new_size = 1024 + 2*buf->alloc_size + len; //printf("realloc %d->%d, used %d, adding %d\n", buf->alloc_size, new_size, buf->used, len); buf->buf = (char*)realloc(buf->buf, new_size); assert(buf->buf != NULL); buf->alloc_size = new_size; } memcpy(buf->buf + buf->used, s, len); buf->used += len; buf->buf[buf->used] = 0; // zero-terminate the string } #ifdef LOCAL_ROUTINES static INT print_key_info(HNDLE hDB, HNDLE hKey, KEY * pkey, INT level, void *info) { struct print_key_info_buf* buf = (struct print_key_info_buf*)info; int i; char str[256]; sprintf(str, "%08X %08X %08X ", (int) (hKey - sizeof(DATABASE_HEADER)), (int) (pkey->data - sizeof(DATABASE_HEADER)), (int) pkey->total_size); assert(strlen(str)+10 < sizeof(str)); for (i = 0; i < level; i++) strcat(str, " "); assert(strlen(str)+10 < sizeof(str)); strcat(str, pkey->name); strcat(str, "\n"); assert(strlen(str)+10 < sizeof(str)); //printf("str [%s]\n", str); add_to_buf(buf, str); return SUCCESS; } static bool db_validate_data_offset(const DATABASE_HEADER * pheader, int offset); INT db_show_mem(HNDLE hDB, char **result, BOOL verbose) { INT total_size_key, total_size_data; struct print_key_info_buf buf; buf.buf = NULL; buf.used = 0; buf.alloc_size = 0; db_lock_database(hDB); DATABASE_HEADER *pheader = _database[hDB - 1].database_header; char str[256]; sprintf(str, "Database header size is 0x%04X, all following values are offset by this!\n", (int)sizeof(DATABASE_HEADER)); add_to_buf(&buf, str); sprintf(str, "Key area 0x00000000 - 0x%08X, size %d bytes\n", pheader->key_size - 1, pheader->key_size); add_to_buf(&buf, str); sprintf(str, "Data area 0x%08X - 0x%08X, size %d bytes\n\n", pheader->key_size, pheader->key_size + pheader->data_size, pheader->data_size); add_to_buf(&buf, str); add_to_buf(&buf, "Keylist:\n"); add_to_buf(&buf, "--------\n"); total_size_key = 0; if (!db_validate_key_offset(pheader, pheader->first_free_key)) { add_to_buf(&buf, "ODB is corrupted: pheader->first_free_key is invalid\n"); db_unlock_database(hDB); if (result) { *result = buf.buf; } else { free(buf.buf); } return DB_CORRUPTED; } FREE_DESCRIP *pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); while ((POINTER_T) pfree != (POINTER_T) pheader) { total_size_key += pfree->size; sprintf(str, "Free block at 0x%08X, size 0x%08X, next 0x%08X\n", (int) ((POINTER_T) pfree - (POINTER_T) pheader - sizeof(DATABASE_HEADER)), pfree->size, pfree->next_free ? (int) (pfree->next_free - sizeof(DATABASE_HEADER)) : 0); add_to_buf(&buf, str); if (!db_validate_key_offset(pheader, pfree->next_free)) { add_to_buf(&buf, "ODB is corrupted: next_free is invalid!"); break; } pfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); } sprintf(str, "\nFree Key area: %d bytes out of %d bytes\n", total_size_key, pheader->key_size); add_to_buf(&buf, str); add_to_buf(&buf, "\nData:\n"); add_to_buf(&buf, "-----\n"); total_size_data = 0; if (!db_validate_data_offset(pheader, pheader->first_free_data)) { add_to_buf(&buf, "ODB is corrupted: pheader->first_free_data is invalid\n"); db_unlock_database(hDB); if (result) { *result = buf.buf; } else { free(buf.buf); } return DB_CORRUPTED; } pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); while ((POINTER_T) pfree != (POINTER_T) pheader) { total_size_data += pfree->size; sprintf(str, "Free block at 0x%08X, size 0x%08X, next 0x%08X\n", (int) ((POINTER_T) pfree - (POINTER_T) pheader - sizeof(DATABASE_HEADER)), pfree->size, pfree->next_free ? (int) (pfree->next_free - sizeof(DATABASE_HEADER)) : 0); add_to_buf(&buf, str); if (!db_validate_data_offset(pheader, pfree->next_free)) { add_to_buf(&buf, "ODB is corrupted: next_free is invalid!"); break; } pfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); } sprintf(str, "\nFree Data area: %d bytes out of %d bytes\n", total_size_data, pheader->data_size); add_to_buf(&buf, str); sprintf(str, "\nFree: %1d (%1.1lf%%) keylist, %1d (%1.1lf%%) data\n", total_size_key, 100 * (double) total_size_key / pheader->key_size, total_size_data, 100 * (double) total_size_data / pheader->data_size); add_to_buf(&buf, str); if (verbose) { add_to_buf(&buf, "\n\n"); add_to_buf(&buf, "Key Data Size\n"); add_to_buf(&buf, "----------------------------\n"); db_scan_tree(hDB, pheader->root_key, 0, print_key_info, &buf); } sprintf(str, "\nTotal ODB size: %d (0x%08X) Bytes, %lg MiB\n", pheader->key_size + pheader->data_size, pheader->key_size + pheader->data_size, ((pheader->key_size + pheader->data_size) / 1E6)); add_to_buf(&buf, str); db_unlock_database(hDB); if (result) { *result = buf.buf; } else { free(buf.buf); } return DB_SUCCESS; } INT db_get_free_mem(HNDLE hDB, INT *key_size, INT *data_size) { DATABASE_HEADER *pheader; FREE_DESCRIP *pfree; *data_size = 0; *key_size = 0; db_lock_database(hDB); pheader = _database[hDB - 1].database_header; pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); while ((POINTER_T) pfree != (POINTER_T) pheader) { *key_size += pfree->size; pfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); } *data_size = 0; pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); while ((POINTER_T) pfree != (POINTER_T) pheader) { *data_size += pfree->size; pfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); } db_unlock_database(hDB); return DB_SUCCESS; } #endif // LOCAL_ROUTINES // Method to check if a given string is valid UTF-8. Returns 1 if it is. // This method was taken from stackoverflow user Christoph, specifically // http://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c static bool is_utf8(const char * string) { if (!string) return false; return ss_is_valid_utf8(string); } #ifdef LOCAL_ROUTINES /*------------------------------------------------------------------*/ static int db_validate_name(const char* name, int maybe_path, const char* caller_name, db_err_msg** msg) { //printf("db_validate_name [%s] length %d, maybe_path %d from %s\n", name, (int)strlen(name), maybe_path, caller_name); if (name == NULL) { db_msg(msg, MERROR, "db_validate_name", "Invalid name passed to %s: should not be NULL", caller_name); return DB_INVALID_NAME; } size_t len = strlen(name); if (len < 1) { db_msg(msg, MERROR, "db_validate_name", "Invalid name passed to %s: should not be an empty string", caller_name); return DB_INVALID_NAME; } if (strchr(name, '[')) { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: should not contain \"[\"", name, caller_name); return DB_INVALID_NAME; } if (name[0] == ' ') { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: should not start with a space", name, caller_name); return DB_INVALID_NAME; } if (name[len-1] == ' ') { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: should not end with a space", name, caller_name); return DB_INVALID_NAME; } if (strchr(name, ']')) { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: should not contain \"[\"", name, caller_name); return DB_INVALID_NAME; } if (!is_utf8(name)) { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: invalid unicode UTF-8 encoding", name, caller_name); return DB_INVALID_NAME; } if (!maybe_path) { if (strchr(name, '/')) { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: should not contain \"/\"", name, caller_name); return DB_INVALID_NAME; } if (strlen(name) >= NAME_LENGTH) { db_msg(msg, MERROR, "db_validate_name", "Invalid name \"%s\" passed to %s: length %d should be less than %d bytes", name, caller_name, (int)strlen(name), NAME_LENGTH); return DB_INVALID_NAME; } } //if (strcmp(name, "test")==0) //return DB_INVALID_NAME; return DB_SUCCESS; } /*------------------------------------------------------------------*/ static bool db_validate_key_offset(const DATABASE_HEADER * pheader, int offset) /* check if key offset lies in valid range */ { if (offset != 0 && offset < (int) sizeof(DATABASE_HEADER)) return false; if (offset > (int) sizeof(DATABASE_HEADER) + pheader->key_size) return false; return true; } static bool db_validate_data_offset(const DATABASE_HEADER * pheader, int offset) /* check if data offset lies in valid range */ { if (offset != 0 && offset < (int) sizeof(DATABASE_HEADER)) return false; if (offset > (int) sizeof(DATABASE_HEADER) + pheader->key_size + pheader->data_size) return false; return true; } static bool db_validate_hkey(const DATABASE_HEADER * pheader, HNDLE hKey) { if (hKey == 0) { cm_msg(MERROR, "db_validate_hkey", "Error: invalid zero hkey %d", hKey); return false; } if (!db_validate_key_offset(pheader, hKey)) { cm_msg(MERROR, "db_validate_hkey", "Error: invalid hkey %d", hKey); return false; } return true; } static const KEY* db_get_pkey(const DATABASE_HEADER* pheader, HNDLE hKey, int* pstatus, const char* caller, db_err_msg **msg) { BOOL hKey_is_root_key = FALSE; if (!hKey) { hKey_is_root_key = TRUE; hKey = pheader->root_key; } /* check if hKey argument is correct */ if (hKey == 0) { if (pstatus) *pstatus = DB_INVALID_HANDLE; return NULL; } /* check if hKey argument is correct */ if (!db_validate_key_offset(pheader, hKey)) { if (pstatus) *pstatus = DB_INVALID_HANDLE; return NULL; } const KEY* pkey = (const KEY *) ((char *) pheader + hKey); if (pkey->type < 1 || pkey->type >= TID_LAST) { DWORD tid = pkey->type; if (hKey_is_root_key) { db_msg(msg, MERROR, caller, "db_get_pkey: root_key hkey %d invalid key type %d, database root directory is corrupted", hKey, tid); if (pstatus) *pstatus = DB_CORRUPTED; return NULL; } else { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "db_get_pkey: hkey %d path \"%s\" invalid key type %d", hKey, path.c_str(), tid); } if (pstatus) *pstatus = DB_NO_KEY; return NULL; } if (pkey->name[0] == 0) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "db_get_pkey: hkey %d path \"%s\" invalid name \"%s\" is empty", hKey, path.c_str(), pkey->name); if (pstatus) *pstatus = DB_NO_KEY; return NULL; } return pkey; } static const KEYLIST* db_get_pkeylist(const DATABASE_HEADER* pheader, HNDLE hKey, const KEY* pkey, const char* caller, db_err_msg **msg, bool kludge_repair = false) { if (pkey->type != TID_KEY) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "db_get_pkeylist: hkey %d path \"%s\" unexpected call to db_get_pkeylist(), not a subdirectory, pkey->type %d", hKey, path.c_str(), pkey->type); return NULL; } if (!hKey) { hKey = pheader->root_key; } if (!db_validate_data_offset(pheader, pkey->data)) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "hkey %d path \"%s\" invalid pkey->data %d", hKey, path.c_str(), pkey->data); return NULL; } const KEYLIST *pkeylist = (const KEYLIST *) ((char *) pheader + pkey->data); if (pkeylist->parent != hKey) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "hkey %d path \"%s\" invalid pkeylist->parent %d should be hkey %d", hKey, path.c_str(), pkeylist->parent, hKey); return NULL; } if (pkeylist->first_key == 0 && pkeylist->num_keys != 0) { if (!kludge_repair) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "hkey %d path \"%s\" invalid pkeylist->first_key %d should be non zero for num_keys %d", hKey, path.c_str(), pkeylist->first_key, pkeylist->num_keys); return NULL; } // FIXME: this repair should be done in db_validate_and_repair_key() std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, caller, "hkey %d path \"%s\" repaired invalid num_keys %d when pkeylist->first_key is zero", hKey, path.c_str(), pkeylist->num_keys); ((KEYLIST*)pkeylist)->num_keys = 0; } return pkeylist; } static HNDLE db_pkey_to_hkey(const DATABASE_HEADER* pheader, const KEY* pkey) { return (POINTER_T) pkey - (POINTER_T) pheader; } static const KEY* db_get_parent(const DATABASE_HEADER* pheader, const KEY* pkey, int* pstatus, const char* caller, db_err_msg **msg) { if (pkey->parent_keylist == 0) { // they asked for the parent of "/", return "/" return db_get_pkey(pheader, pheader->root_key, pstatus, caller, msg); } if (!db_validate_data_offset(pheader, pkey->parent_keylist)) { db_msg(msg, MERROR, caller, "hkey %d path \"%s\" invalid pkey->parent %d", db_pkey_to_hkey(pheader, pkey), db_get_path_locked(pheader, pkey).c_str(), pkey->parent_keylist); if (pstatus) *pstatus = DB_CORRUPTED; return NULL; } const KEYLIST *pkeylist = (const KEYLIST *) ((char *) pheader + pkey->parent_keylist); if (pkeylist->first_key == 0 && pkeylist->num_keys != 0) { db_msg(msg, MERROR, caller, "hkey %d path \"%s\" invalid parent pkeylist->first_key %d should be non zero for num_keys %d", db_pkey_to_hkey(pheader, pkey), db_get_path_locked(pheader, pkey).c_str(), pkeylist->first_key, pkeylist->num_keys); if (pstatus) *pstatus = DB_CORRUPTED; return NULL; } return db_get_pkey(pheader, pkeylist->parent, pstatus, caller, msg); } static const KEY* db_enum_first_locked(const DATABASE_HEADER *pheader, const KEY* pkey, db_err_msg **msg) { HNDLE hKey = db_pkey_to_hkey(pheader, pkey); if (pkey->type != TID_KEY) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, "db_enum_first_locked", "hkey %d path \"%s\" tid %d is not a directory", hKey, path.c_str(), pkey->type); return NULL; } const KEYLIST *pkeylist = db_get_pkeylist(pheader, hKey, pkey, "db_find_key", msg); if (!pkeylist) { // error return NULL; } if (pkeylist->num_keys == 0) { // empty directory return NULL; } if (pkeylist->first_key == 0) { // empty directory return NULL; } return db_get_pkey(pheader, pkeylist->first_key, NULL, "db_enum_first_locked", msg); } static const KEY* db_enum_next_locked(const DATABASE_HEADER *pheader, const KEY* pdir, const KEY* pkey, db_err_msg **msg) { if (pkey->next_key == 0) return NULL; return db_get_pkey(pheader, pkey->next_key, NULL, "db_enum_next_locked", msg); } static const KEY* db_resolve_link_locked(const DATABASE_HEADER* pheader, const KEY* pkey, int* pstatus, db_err_msg** msg) { if (pkey->type != TID_LINK) return pkey; // FIXME: need to validate pkey->data if (*((char *) pheader + pkey->data) == '/') { return db_find_pkey_locked(pheader, NULL, (char*)pheader + pkey->data, pstatus, msg); } else { return db_find_pkey_locked(pheader, pkey, (char*)pheader + pkey->data, pstatus, msg); } } /* static void db_print_pkey(const DATABASE_HEADER * pheader, const KEY* pkey, int recurse = 0, const char *path = NULL, HNDLE parenthkeylist = 0) { HNDLE hkey = db_pkey_to_hkey(pheader, pkey); std::string xpath; if (path == NULL) { xpath = db_get_path_locked(pheader, hkey); path = xpath.c_str(); } printf("path \"%s\", parenthkey %d, hkey %d, name \"%s\", type %d, parent %d, data %d, total_size %d", path, parenthkeylist, hkey, pkey->name, pkey->type, pkey->parent_keylist, pkey->data, pkey->total_size); if (pkey->type != TID_KEY) { printf("\n"); } else { const KEYLIST *pkeylist = db_get_pkeylist(pheader, hkey, pkey, "db_validate_key", NULL); if (pkeylist) { printf(", pkeylist parent %d, num_keys %d, first_key %d", pkeylist->parent, pkeylist->num_keys, pkeylist->first_key); printf("\n"); } } } static void db_print_hkey(const DATABASE_HEADER * pheader, HNDLE hkey, int recurse = 0, const char *path = NULL, HNDLE parenthkeylist = 0) { const KEY *pkey = db_get_pkey(pheader, hkey, NULL, "db_print_key", NULL); if (!pkey) { return; } db_print_pkey(pheader, pkey, recurse, path, parenthkeylist); } */ static bool db_validate_and_repair_key_wlocked(DATABASE_HEADER * pheader, int recurse, const char *path, HNDLE parenthkeylist, HNDLE hkey, KEY * pkey, db_err_msg **msg) { int status; bool flag = true; //printf("path \"%s\", parenthkey %d, hkey %d, pkey->name \"%s\", type %d\n", path, parenthkeylist, hkey, pkey->name, pkey->type); //std::string xpath = db_get_path_locked(pheader, hkey); //if (xpath != path) // printf("hkey %d, path \"%s\" vs \"%s\"\n", hkey, path, xpath.c_str()); //db_print_key(pheader, 0, path, parenthkeylist, hkey); if (hkey==0 || !db_validate_key_offset(pheader, hkey)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid hkey", hkey, path); return false; } /* check key type */ if (pkey->type <= 0 || pkey->type >= TID_LAST) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", name \"%s\", invalid key type %d", hkey, path, pkey->name, pkey->type); return false; } /* check key name */ status = db_validate_name(pkey->name, FALSE, "db_validate_key", msg); if (status != DB_SUCCESS) { char newname[NAME_LENGTH]; sprintf(newname, "%p", pkey); db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\": invalid name \"%s\" replaced with \"%s\"", hkey, path, pkey->name, newname); mstrlcpy(pkey->name, newname, sizeof(pkey->name)); flag = false; //return false; } /* check parent */ if (pkey->parent_keylist != parenthkeylist) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", name \"%s\", invalid parent_keylist %d should be %d", hkey, path, pkey->name, pkey->parent_keylist, parenthkeylist); return false; } if (!db_validate_data_offset(pheader, pkey->data)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid data offset 0x%08X is invalid", hkey, path, pkey->data - (int)sizeof(DATABASE_HEADER)); return false; } /* check key sizes */ if ((pkey->total_size < 0) || (pkey->total_size > pheader->data_size)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid pkey->total_size %d", hkey, path, pkey->total_size); return false; } if ((pkey->item_size < 0) || (pkey->item_size > pheader->data_size)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid pkey->item_size: %d", hkey, path, pkey->item_size); return false; } if ((pkey->num_values < 0) || (pkey->num_values > pheader->data_size)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid pkey->num_values: %d", hkey, path, pkey->num_values); return false; } /* check and correct key size */ if (pkey->total_size != pkey->item_size * pkey->num_values) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", corrected pkey->total_size from %d to %d*%d=%d", hkey, path, pkey->total_size, pkey->item_size, pkey->num_values, pkey->item_size * pkey->num_values); pkey->total_size = pkey->item_size * pkey->num_values; flag = false; } /* check and correct key size */ if (pkey->data == 0 && pkey->total_size != 0) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", pkey->data is zero, corrected pkey->num_values %d and pkey->total_size %d to be zero, should be zero", hkey, path, pkey->num_values, pkey->total_size); pkey->num_values = 0; pkey->total_size = 0; flag = false; } if (pkey->type == TID_STRING || pkey->type == TID_LINK) { const char* s = (char*)pheader + pkey->data; if (!is_utf8(s)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", string value is not valid UTF-8", hkey, path); //flag = false; } } /* check for empty link */ if (pkey->type == TID_LINK) { // minimum symlink length is 3 bytes: // one byte "/" // one byte odb entry name // one byte "\0" if (pkey->total_size <= 2) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_LINK is an empty link", hkey, path); flag = false; //return false; } } /* check for too long link */ if (pkey->type == TID_LINK) { if (pkey->total_size >= MAX_ODB_PATH) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_LINK length %d exceeds MAX_ODB_PATH %d", hkey, path, pkey->total_size, MAX_ODB_PATH); flag = false; //return false; } } /* check for link loop */ if (pkey->type == TID_LINK) { const char* link = (char*)pheader + pkey->data; int link_len = strlen(link); int path_len = strlen(path); if (link_len == path_len) { // check for link to itself if (equal_ustring(link, path)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_LINK to \"%s\" is a link to itself", hkey, path, link); flag = false; //return false; } } else if (link_len < path_len) { // check for link to the "path" subdirectory char tmp[MAX_ODB_PATH]; memcpy(tmp, path, link_len); tmp[link_len] = 0; if (equal_ustring(link, tmp) && path[link_len] == DIR_SEPARATOR) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_LINK to \"%s\" is a loop", hkey, path, link); flag = false; //return false; } } } /* check access mode */ if ((pkey->access_mode & ~(MODE_READ | MODE_WRITE | MODE_DELETE | MODE_EXCLUSIVE | MODE_ALLOC))) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid pkey->access_mode %d", hkey, path, pkey->access_mode); flag = false; //return false; } /* check if access time is in the future */ if (pkey->last_written > 0 && ((DWORD)pkey->last_written > ss_time() + 3600)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid pkey->last_written time %d", hkey, path, pkey->last_written); flag = false; //return false; } if (pkey->type == TID_KEY) { bool pkeylist_ok = true; // FIXME: notice the kludged repair of pkeylist! K.O. const KEYLIST *pkeylist = db_get_pkeylist(pheader, hkey, pkey, "db_validate_key", msg, true); if (!pkeylist) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", invalid pkey->data %d", hkey, path, pkey->data); flag = false; } else { if (pkeylist->parent != hkey) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_KEY invalid pkeylist->parent %d is not hkey %d", hkey, path, pkeylist->parent, hkey); flag = false; pkeylist_ok = false; } if (pkeylist->num_keys < 0 || pkeylist->num_keys > pheader->key_size) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_KEY invalid pkeylist->num_keys %d", hkey, path, pkeylist->num_keys); flag = false; pkeylist_ok = false; } if (pkeylist->num_keys == 0 && pkeylist->first_key == 0) { // empty key } else if (pkeylist->first_key == 0 || !db_validate_key_offset(pheader, pkeylist->first_key)) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_KEY invalid pkeylist->first_key %d", hkey, path, pkeylist->first_key); flag = false; pkeylist_ok = false; } if (pkeylist_ok) { //printf("hkey %d, path \"%s\", pkey->data %d, pkeylist parent %d, num_keys %d, first_key %d: ", hkey, path, pkey->data, pkeylist->parent, pkeylist->num_keys, pkeylist->first_key); HNDLE subhkey = pkeylist->first_key; int count = 0; while (subhkey != 0) { KEY* subpkey = (KEY*)db_get_pkey(pheader, subhkey, NULL, "db_validate_key", msg); if (!subpkey) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", TID_KEY invalid subhkey %d", hkey, path, subhkey); pkeylist_ok = false; flag = false; break; } std::string buf; buf += path; buf += "/"; buf += subpkey->name; //printf("pkey %p, next %d, name [%s], path %s\n", subpkey, subpkey->next_key, subpkey->name, buf.c_str()); if (recurse) { flag &= db_validate_and_repair_key_wlocked(pheader, recurse + 1, buf.c_str(), pkey->data, subhkey, subpkey, msg); } count++; subhkey = subpkey->next_key; } if (count != pkeylist->num_keys) { db_msg(msg, MERROR, "db_validate_key", "hkey %d, path \"%s\", repaired TID_KEY mismatch of pkeylist->num_keys %d against key chain length %d", hkey, path, pkeylist->num_keys, count); ((KEYLIST*)pkeylist)->num_keys = count; flag = false; pkeylist_ok = false; } } } } return flag; } /*------------------------------------------------------------------*/ DATABASE_CLIENT* db_get_my_client_locked(DATABASE* pdb) { assert(pdb); assert(pdb->database_header); DATABASE_HEADER *pheader = pdb->database_header; int idx = pdb->client_index; if (idx < 0 || idx >= pheader->max_client_index || idx >= MAX_CLIENTS) { cm_msg(MERROR, "db_get_my_client_locked", "My client index %d in ODB is invalid: out of range 0..%d. Maybe this client was removed by a timeout, see midas.log. Cannot continue, aborting...", idx, pheader->max_client_index-1); abort(); } DATABASE_CLIENT* pclient = &pheader->client[idx]; // safe to dereference on "idx" if (pclient->name[0] == 0) { cm_msg(MERROR, "db_get_my_client_locked", "My client index %d in ODB is invalid: client name is blank. Maybe this client was removed by a timeout, see midas.log. Cannot continue, aborting...", idx); abort(); } int pid = getpid(); if (pclient->pid != pid) { cm_msg(MERROR, "db_get_my_client_locked", "My client index %d in ODB is invalid: pid mismatch, my pid is %d, but ODB has %d. Maybe this client was removed by a timeout, see midas.log. Cannot continue, aborting...", idx, pid, pclient->pid); abort(); } //printf("db_get_my_client_locked: idx %d, name [%s], pid %d\n", idx, pclient->name, pid); return pclient; } /*------------------------------------------------------------------*/ static void db_validate_sizes() { /* validate size of data structures (miscompiled, 32/64 bit mismatch, etc */ if (0) { #define S(x) printf("assert(sizeof(%-20s) == %6d);\n", #x, (int)sizeof(x)) // basic data types S(char *); S(char); S(int); S(long int); S(float); S(double); S(BOOL); S(WORD); S(DWORD); S(INT); S(POINTER_T); S(midas_thread_t); // data buffers S(EVENT_REQUEST); S(BUFFER_CLIENT); S(BUFFER_HEADER); // history files S(HIST_RECORD); S(DEF_RECORD); S(INDEX_RECORD); S(TAG); // ODB shared memory structures S(KEY); S(KEYLIST); S(OPEN_RECORD); S(DATABASE_CLIENT); S(DATABASE_HEADER); // misc structures S(EVENT_HEADER); S(RUNINFO); S(EQUIPMENT_INFO); S(EQUIPMENT_STATS); S(BANK_HEADER); S(BANK); S(BANK32); S(ANA_OUTPUT_INFO); S(PROGRAM_INFO); S(ALARM_CLASS); S(ALARM); //S(CHN_SETTINGS); //S(CHN_STATISTICS); #undef S } #if 0 EQUIPMENT_INFO eq; printf("EQUIPMENT_INFO offset of event_id: %d\n", (int)((char*)&eq.event_id - (char*)&eq)); printf("EQUIPMENT_INFO offset of eq_type: %d\n", (int)((char*)&eq.eq_type - (char*)&eq)); printf("EQUIPMENT_INFO offset of event_limit: %d\n", (int)((char*)&eq.event_limit - (char*)&eq)); printf("EQUIPMENT_INFO offset of num_subevents: %d\n", (int)((char*)&eq.num_subevents - (char*)&eq)); printf("EQUIPMENT_INFO offset of status: %d\n", (int)((char*)&eq.status - (char*)&eq)); printf("EQUIPMENT_INFO offset of hidden: %d\n", (int)((char*)&eq.hidden - (char*)&eq)); #endif assert(sizeof(UINT8) == 1); assert(sizeof(INT8) == 1); assert(sizeof(UINT16) == 2); assert(sizeof(INT16) == 2); assert(sizeof(UINT32) == 4); assert(sizeof(INT32) == 4); assert(sizeof(UINT64) == 8); assert(sizeof(INT64) == 8); #ifdef OS_LINUX assert(sizeof(EVENT_REQUEST) == 16); // ODB v3 assert(sizeof(BUFFER_CLIENT) == 256); assert(sizeof(BUFFER_HEADER) == 16444); assert(sizeof(HIST_RECORD) == 20); assert(sizeof(DEF_RECORD) == 40); assert(sizeof(INDEX_RECORD) == 12); assert(sizeof(TAG) == 40); assert(sizeof(KEY) == 68); assert(sizeof(KEYLIST) == 12); assert(sizeof(OPEN_RECORD) == 8); assert(sizeof(DATABASE_CLIENT) == 2112); assert(sizeof(DATABASE_HEADER) == 135232); assert(sizeof(EVENT_HEADER) == 16); //assert(sizeof(EQUIPMENT_INFO) == 696); has been moved to dynamic checking inside mhttpd.c assert(sizeof(EQUIPMENT_STATS) == 24); assert(sizeof(BANK_HEADER) == 8); assert(sizeof(BANK) == 8); assert(sizeof(BANK32) == 12); assert(sizeof(ANA_OUTPUT_INFO) == 792); assert(sizeof(PROGRAM_INFO) == 316); assert(sizeof(ALARM_CLASS) == 348); assert(sizeof(ALARM) == 452); //assert(sizeof(CHN_SETTINGS) == 648); // ODB v3 //assert(sizeof(CHN_STATISTICS) == 56); // ODB v3 #endif } typedef struct { DATABASE_HEADER * pheader; int max_keys; int num_keys; HNDLE* hkeys; int* counts; int* modes; int num_modified; } UPDATE_OPEN_RECORDS; static int db_update_open_record_wlocked(const DATABASE_HEADER* xpheader, const KEY* xpkey, int level, void* voidp, db_err_msg **msg) { int found = 0; int count = 0; int status; int k; UPDATE_OPEN_RECORDS *uorp = (UPDATE_OPEN_RECORDS *)voidp; KEY* pkey = (KEY*)xpkey; // drop "const": we already have "allow_write" HNDLE hKey = db_pkey_to_hkey(uorp->pheader, pkey); for (k=0; knum_keys; k++) if (uorp->hkeys[k] == hKey) { found = 1; count = uorp->counts[k]; break; } if (pkey->notify_count == 0 && !found) return DB_SUCCESS; // no open record here std::string path = db_get_path_locked(uorp->pheader, hKey); if (path == "") { db_msg(msg, MINFO, "db_update_open_record", "Invalid hKey %d", hKey); return DB_SUCCESS; } //if (!db_validate_hkey(uorp->pheader, hKey)) { // cm_msg(MINFO, "db_update_open_record", "Invalid hKey %d", hKey); // return DB_SUCCESS; //} // //KEY* pkey = (KEY *) ((char *) uorp->pheader + hKey); //printf("path [%s], type %d, notify_count %d\n", path, pkey->type, pkey->notify_count); // extra check: are we looking at the same key? //assert(xkey->notify_count == pkey->notify_count); #if 0 printf("%s, notify_count %d, found %d, our count %d\n", path, pkey->notify_count, found, count); #endif if (pkey->notify_count==0 && found) { db_msg(msg, MINFO, "db_update_open_record", "Added missing open record flag to \"%s\"", path.c_str()); pkey->notify_count = count; uorp->num_modified++; return DB_SUCCESS; } if (pkey->notify_count!=0 && !found) { db_msg(msg, MINFO, "db_update_open_record", "Removed open record flag from \"%s\"", path.c_str()); pkey->notify_count = 0; uorp->num_modified++; if (pkey->access_mode | MODE_EXCLUSIVE) { status = db_set_mode_wlocked(uorp->pheader, pkey, (WORD) (pkey->access_mode & ~MODE_EXCLUSIVE), 1, msg); if (status != DB_SUCCESS) { db_msg(msg, MERROR, "db_update_open_record", "Cannot remove exclusive access mode from \"%s\", db_set_mode() status %d", path.c_str(), status); return DB_SUCCESS; } db_msg(msg, MINFO, "db_update_open_record", "Removed exclusive access mode from \"%s\"", path.c_str()); } return DB_SUCCESS; } if (pkey->notify_count != uorp->counts[k]) { db_msg(msg, MINFO, "db_update_open_record", "Updated notify_count of \"%s\" from %d to %d", path.c_str(), pkey->notify_count, count); pkey->notify_count = count; uorp->num_modified++; return DB_SUCCESS; } return DB_SUCCESS; } static int db_validate_open_records_wlocked(DATABASE_HEADER* pheader, db_err_msg** msg) { int status = DB_SUCCESS; UPDATE_OPEN_RECORDS uor; int i, j, k; uor.max_keys = MAX_CLIENTS*MAX_OPEN_RECORDS; uor.num_keys = 0; uor.hkeys = (HNDLE*)calloc(uor.max_keys, sizeof(HNDLE)); uor.counts = (int*)calloc(uor.max_keys, sizeof(int)); uor.modes = (int*)calloc(uor.max_keys, sizeof(int)); uor.num_modified = 0; assert(uor.hkeys != NULL); assert(uor.counts != NULL); assert(uor.modes != NULL); uor.pheader = pheader; for (i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT* pclient = &pheader->client[i]; for (j = 0; j < pclient->max_index; j++) if (pclient->open_record[j].handle) { int found = 0; for (k=0; kopen_record[j].handle) { uor.counts[k]++; found = 1; break; } } if (!found) { uor.hkeys[uor.num_keys] = pclient->open_record[j].handle; uor.counts[uor.num_keys] = 1; uor.modes[uor.num_keys] = pclient->open_record[j].access_mode; uor.num_keys++; } } } #if 0 for (i=0; ifirst_free_key)) { db_msg(msg, MERROR, "db_validate_db", "Error: database corruption, invalid pheader->first_free_key 0x%08X", pheader->first_free_key - (int)sizeof(DATABASE_HEADER)); return false; } pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); while ((POINTER_T) pfree != (POINTER_T) pheader) { if (pfree->next_free != 0 && !db_validate_key_offset(pheader, pfree->next_free)) { db_msg(msg, MERROR, "db_validate_db", "Warning: database corruption, invalid key area next_free 0x%08X", pfree->next_free - (int)sizeof(DATABASE_HEADER)); flag = false; break; } total_size_key += pfree->size; FREE_DESCRIP *nextpfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); if (pfree->next_free != 0 && nextpfree == pfree) { db_msg(msg, MERROR, "db_validate_db", "Warning: database corruption, key area next_free 0x%08X is same as current free %p, truncating the free list", pfree->next_free, pfree - (int)sizeof(DATABASE_HEADER)); pfree->next_free = 0; flag = false; break; //return false; } pfree = nextpfree; } ratio = ((double) (pheader->key_size - total_size_key)) / ((double) pheader->key_size); if (ratio > 0.9) db_msg(msg, MERROR, "db_validate_db", "Warning: database key area is %.0f%% full", ratio * 100.0); if (total_size_key > pheader->key_size) { db_msg(msg, MERROR, "db_validate_db", "Error: database corruption, total_key_size 0x%08X bigger than pheader->key_size 0x%08X", total_size_key, pheader->key_size); flag = false; } /* validate the data free list */ if (!db_validate_data_offset(pheader, pheader->first_free_data)) { db_msg(msg, MERROR, "db_validate_db", "Error: database corruption, invalid pheader->first_free_data 0x%08X", pheader->first_free_data - (int)sizeof(DATABASE_HEADER)); return false; } //printf("pheader %p, first_free_data %d, key size %d, data size %d\n", pheader, pheader->first_free_data, pheader->key_size, pheader->data_size); pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); while ((POINTER_T) pfree != (POINTER_T) pheader) { if (pfree->next_free != 0 && !db_validate_data_offset(pheader, pfree->next_free)) { db_msg(msg, MERROR, "db_validate_db", "Warning: database corruption, invalid data area next_free 0x%08X", pfree->next_free - (int)sizeof(DATABASE_HEADER)); flag = false; break; //return false; } total_size_data += pfree->size; FREE_DESCRIP *nextpfree = (FREE_DESCRIP *) ((char *) pheader + pfree->next_free); if (pfree->next_free != 0 && nextpfree == pfree) { db_msg(msg, MERROR, "db_validate_db", "Warning: database corruption, data area next_free 0x%08X is same as current free %p, truncating the free list", pfree->next_free, pfree - (int)sizeof(DATABASE_HEADER)); pfree->next_free = 0; flag = false; break; //return false; } pfree = nextpfree; } ratio = ((double) (pheader->data_size - total_size_data)) / ((double) pheader->data_size); if (ratio > 0.9) db_msg(msg, MERROR, "db_validate_db", "Warning: database data area is %.0f%% full", ratio * 100.0); if (total_size_data > pheader->data_size) { db_msg(msg, MERROR, "db_validate_db", "Error: database corruption, total_size_data 0x%08X bigger than pheader->data_size 0x%08X", total_size_key, pheader->data_size); flag = false; //return false; } /* validate the tree of keys, starting from the root key */ if (!db_validate_key_offset(pheader, pheader->root_key)) { db_msg(msg, MERROR, "db_validate_db", "Error: database corruption, pheader->root_key 0x%08X is invalid", pheader->root_key - (int)sizeof(DATABASE_HEADER)); return false; } flag &= db_validate_and_repair_key_wlocked(pheader, 1, "", 0, pheader->root_key, (KEY *) ((char *) pheader + pheader->root_key), msg); if (!flag) { db_msg(msg, MERROR, "db_validate_db", "Error: ODB corruption detected, see previous messages"); } return flag; } #endif // LOCAL_ROUTINES /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Open an online database @param database_name Database name. @param database_size Initial size of database if not existing @param client_name Name of this application @param hDB ODB handle obtained via cm_get_experiment_database(). @return DB_SUCCESS, DB_CREATED, DB_INVALID_NAME, DB_NO_MEMORY, DB_MEMSIZE_MISMATCH, DB_NO_SEMAPHORE, DB_INVALID_PARAM, RPC_NET_ERROR */ INT db_open_database(const char *xdatabase_name, INT database_size, HNDLE * hDB, const char *client_name) { if (rpc_is_remote()) return rpc_call(RPC_DB_OPEN_DATABASE, xdatabase_name, database_size, hDB, client_name); #ifdef LOCAL_ROUTINES { INT i, status; HNDLE handle; DATABASE_CLIENT *pclient; BOOL shm_created; DATABASE_HEADER *pheader; KEY *pkey; KEYLIST *pkeylist; FREE_DESCRIP *pfree; BOOL call_watchdog; DWORD timeout; char database_name[NAME_LENGTH]; /* restrict name length */ mstrlcpy(database_name, xdatabase_name, NAME_LENGTH); int odb_size_limit = 100*1000*1000; if (database_size < 0 || database_size > odb_size_limit) { cm_msg(MERROR, "db_open_database", "invalid database size: %d bytes. ODB size limit is %d bytes", database_size, odb_size_limit); return DB_INVALID_PARAM; } if (strlen(client_name) >= NAME_LENGTH) { cm_msg(MERROR, "db_open_database", "client name \'%s\' is longer than %d characters", client_name, NAME_LENGTH-1); return DB_INVALID_PARAM; } if (strchr(client_name, '/') != NULL) { cm_msg(MERROR, "db_open_database", "client name \'%s\' should not contain the slash \'/\' character", client_name); return DB_INVALID_PARAM; } /* allocate new space for the new database descriptor */ if (_database_entries == 0) { _database = (DATABASE *) malloc(sizeof(DATABASE)); memset(_database, 0, sizeof(DATABASE)); if (_database == NULL) { *hDB = 0; return DB_NO_MEMORY; } _database_entries = 1; i = 0; } else { /* check if database already open */ for (i = 0; i < _database_entries; i++) if (_database[i].attached && equal_ustring(_database[i].name, database_name)) { /* check if database belongs to this thread */ *hDB = i + 1; return DB_SUCCESS; } /* check for a deleted entry */ for (i = 0; i < _database_entries; i++) if (!_database[i].attached) break; /* if not found, create new one */ if (i == _database_entries) { _database = (DATABASE *) realloc(_database, sizeof(DATABASE) * (_database_entries + 1)); memset(&_database[_database_entries], 0, sizeof(DATABASE)); _database_entries++; if (_database == NULL) { _database_entries--; *hDB = 0; return DB_NO_MEMORY; } } } handle = (HNDLE) i; /* open shared memory region */ void* shm_adr = NULL; size_t shm_size = 0; HNDLE shm_handle; status = ss_shm_open(database_name, sizeof(DATABASE_HEADER) + 2 * ALIGN8(database_size / 2), &shm_adr, &shm_size, &shm_handle, TRUE); if (status == SS_NO_MEMORY || status == SS_FILE_ERROR) { *hDB = 0; return DB_INVALID_NAME; } _database[handle].shm_adr = shm_adr; _database[handle].shm_size = shm_size; _database[handle].shm_handle = shm_handle; _database[handle].database_header = (DATABASE_HEADER *) shm_adr; /* shortcut to header */ pheader = _database[handle].database_header; /* save name */ strcpy(_database[handle].name, database_name); shm_created = (status == SS_CREATED); /* clear memeory for debugging */ /* memset(pheader, 0, sizeof(DATABASE_HEADER) + 2*ALIGN8(database_size/2)); */ if (shm_created && pheader->name[0] == 0) { /* setup header info if database was created */ memset(pheader, 0, sizeof(DATABASE_HEADER) + 2 * ALIGN8(database_size / 2)); strcpy(pheader->name, database_name); pheader->version = DATABASE_VERSION; pheader->key_size = ALIGN8(database_size / 2); pheader->data_size = ALIGN8(database_size / 2); pheader->root_key = sizeof(DATABASE_HEADER); pheader->first_free_key = sizeof(DATABASE_HEADER); pheader->first_free_data = sizeof(DATABASE_HEADER) + pheader->key_size; /* set up free list */ pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_key); pfree->size = pheader->key_size; pfree->next_free = 0; pfree = (FREE_DESCRIP *) ((char *) pheader + pheader->first_free_data); pfree->size = pheader->data_size; pfree->next_free = 0; /* create root key */ pkey = (KEY *) malloc_key(pheader, sizeof(KEY), "db_open_database_A"); assert(pkey); /* set key properties */ pkey->type = TID_KEY; pkey->num_values = 1; pkey->access_mode = MODE_READ | MODE_WRITE | MODE_DELETE; strcpy(pkey->name, "root"); pkey->parent_keylist = 0; /* create keylist */ pkeylist = (KEYLIST *) malloc_key(pheader, sizeof(KEYLIST), "db_open_database_B"); assert(pkeylist); /* store keylist in data field */ pkey->data = (POINTER_T) pkeylist - (POINTER_T) pheader; pkey->item_size = sizeof(KEYLIST); pkey->total_size = sizeof(KEYLIST); pkeylist->parent = (POINTER_T) pkey - (POINTER_T) pheader; pkeylist->num_keys = 0; pkeylist->first_key = 0; } /* check database version */ if (pheader->version != DATABASE_VERSION) { cm_msg(MERROR, "db_open_database", "Different database format: Shared memory is %d, program is %d", pheader->version, DATABASE_VERSION); return DB_VERSION_MISMATCH; } /* check database size vs shared memory size */ if (_database[handle].shm_size < (int)sizeof(DATABASE_HEADER) + pheader->key_size + pheader->data_size) { cm_msg(MERROR, "db_open_database", "Invalid database, shared memory size %d is smaller than database size %d (header: %d, key area: %d, data area: %d). Delete this shared memory (odbedit -R), create a new odb (odbinit) and reload it from the last odb save file.", _database[handle].shm_size, (int)sizeof(DATABASE_HEADER) + pheader->key_size + pheader->data_size, (int)sizeof(DATABASE_HEADER), pheader->key_size, pheader->data_size); return DB_VERSION_MISMATCH; } /* check root key */ if (!db_validate_key_offset(pheader, pheader->root_key)) { cm_msg(MERROR, "db_open_database", "Invalid, incompatible or corrupted database: root key offset %d is invalid", pheader->root_key); return DB_VERSION_MISMATCH; } else { pkey = (KEY*)((char*)pheader + pheader->root_key); if (pkey->type != TID_KEY) { cm_msg(MERROR, "db_open_database", "Invalid, incompatible or corrupted database: root key type %d is not TID_KEY", pkey->type); return DB_VERSION_MISMATCH; } if (strcmp(pkey->name, "root") != 0) { cm_msg(MERROR, "db_open_database", "Invalid, incompatible or corrupted database: root key name \"%s\" is not \"root\"", pkey->name); return DB_VERSION_MISMATCH; } // what if we are connecting to an incompatible ODB? // A call to db_validate_and_repair_key() maybe will // corrupt it here. But we have no choice, // if we skip it here and continue, // db_validate_and_repair_db() will call it later anyway... K.O. db_err_msg* msg = NULL; bool ok = db_validate_and_repair_key_wlocked(pheader, 0, "", 0, pheader->root_key, pkey, &msg); if (msg) db_flush_msg(&msg); if (!ok) { cm_msg(MERROR, "db_open_database", "Invalid, incompatible or corrupted database: root key is invalid"); return DB_VERSION_MISMATCH; } } /* set default mutex and semaphore timeout */ _database[handle].timeout = 10000; /* create mutexes for the database */ status = ss_mutex_create(&_database[handle].mutex, TRUE); if (status != SS_SUCCESS && status != SS_CREATED) { *hDB = 0; return DB_NO_SEMAPHORE; } /* create semaphore for the database */ status = ss_semaphore_create(database_name, &(_database[handle].semaphore)); if (status != SS_SUCCESS && status != SS_CREATED) { *hDB = 0; return DB_NO_SEMAPHORE; } _database[handle].lock_cnt = 0; _database[handle].protect = FALSE; _database[handle].protect_read = FALSE; _database[handle].protect_write = FALSE; /* first lock database */ status = db_lock_database(handle + 1); if (status != DB_SUCCESS) return status; /* we have the database locked, without write protection */ /* Now we have a DATABASE_HEADER, so let's setup a CLIENT structure in that database. The information there can also be seen by other processes. */ /* update the client count */ int num_clients = 0; int max_client_index = 0; for (i = 0; i < MAX_CLIENTS; i++) { if (pheader->client[i].pid == 0) continue; num_clients++; max_client_index = i + 1; } pheader->num_clients = num_clients; pheader->max_client_index = max_client_index; /*fprintf(stderr,"num_clients: %d, max_client: %d\n",pheader->num_clients,pheader->max_client_index); */ /* remove dead clients */ for (i = 0; i < MAX_CLIENTS; i++) { if (pheader->client[i].pid == 0) continue; if (!ss_pid_exists(pheader->client[i].pid)) { char client_name_tmp[NAME_LENGTH]; int client_pid; mstrlcpy(client_name_tmp, pheader->client[i].name, sizeof(client_name_tmp)); client_pid = pheader->client[i].pid; // removed: /* decrement notify_count for open records and clear exclusive mode */ // open records are corrected later, by db_validate_open_records() /* clear entry from client structure in database header */ memset(&(pheader->client[i]), 0, sizeof(DATABASE_CLIENT)); cm_msg(MERROR, "db_open_database", "Removed ODB client \'%s\', index %d because process pid %d does not exists", client_name_tmp, i, client_pid); } } /* Look for empty client slot */ for (i = 0; i < MAX_CLIENTS; i++) if (pheader->client[i].pid == 0) break; if (i == MAX_CLIENTS) { db_unlock_database(handle + 1); *hDB = 0; cm_msg(MERROR, "db_open_database", "maximum number of clients exceeded"); return DB_NO_SLOT; } /* store slot index in _database structure */ _database[handle].client_index = i; /* Save the index of the last client of that database so that later only the clients 0..max_client_index-1 have to be searched through. */ pheader->num_clients++; if (i + 1 > pheader->max_client_index) pheader->max_client_index = i + 1; /* setup database header and client structure */ pclient = &pheader->client[i]; memset(pclient, 0, sizeof(DATABASE_CLIENT)); mstrlcpy(pclient->name, client_name, sizeof(pclient->name)); pclient->pid = ss_getpid(); pclient->num_open_records = 0; ss_suspend_get_odb_port(&pclient->port); pclient->last_activity = ss_millitime(); cm_get_watchdog_params(&call_watchdog, &timeout); pclient->watchdog_timeout = timeout; /* check ODB for corruption */ db_err_msg* msg = NULL; bool ok = db_validate_and_repair_db_wlocked(pheader, &msg); if (msg) db_flush_msg(&msg); if (!ok) { /* do not treat corrupted odb as a fatal error- allow the user to preceed at own risk- the database is already corrupted, so no further harm can possibly be made. */ /* db_unlock_database(handle + 1); *hDB = 0; return DB_CORRUPTED; */ } /* setup _database entry */ _database[handle].database_data = _database[handle].database_header + 1; _database[handle].attached = TRUE; _database[handle].protect = FALSE; _database[handle].protect_read = FALSE; _database[handle].protect_write = FALSE; *hDB = (handle + 1); status = db_validate_open_records_wlocked(pheader, &msg); if (status != DB_SUCCESS) { db_unlock_database(handle + 1); if (msg) db_flush_msg(&msg); cm_msg(MERROR, "db_open_database", "Error: db_validate_open_records() status %d", status); return status; } db_unlock_database(handle + 1); if (msg) db_flush_msg(&msg); if (shm_created) return DB_CREATED; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Close a database @param hDB ODB handle obtained via cm_get_experiment_database(). @return DB_SUCCESS, DB_INVALID_HANDLE, RPC_NET_ERROR */ INT db_close_database(HNDLE hDB) { if (rpc_is_remote()) return rpc_call(RPC_DB_CLOSE_DATABASE, hDB); #ifdef LOCAL_ROUTINES else { INT destroy_flag, i, j; char xname[256]; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_close_database", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } /* flush database to disk */ db_flush_database(hDB); /* first lock database */ db_lock_database(hDB); DATABASE* pdb = &_database[hDB - 1]; if (!pdb->attached) { db_unlock_database(hDB); cm_msg(MERROR, "db_close_database", "database not attached"); return DB_INVALID_HANDLE; } DATABASE_HEADER *pheader = pdb->database_header; DATABASE_CLIENT *pclient = db_get_my_client_locked(pdb); db_allow_write_locked(&_database[hDB-1], "db_close_database"); /* close all open records */ for (i = 0; i < pclient->max_index; i++) if (pclient->open_record[i].handle) db_remove_open_record_wlocked(pdb, pheader, pclient->open_record[i].handle); /* mark entry in _database as empty */ pdb->attached = FALSE; /* clear entry from client structure in database header */ memset(pclient, 0, sizeof(DATABASE_CLIENT)); /* calculate new max_client_index entry */ for (i = MAX_CLIENTS - 1; i >= 0; i--) if (pheader->client[i].pid != 0) break; pheader->max_client_index = i + 1; /* count new number of clients */ for (i = MAX_CLIENTS - 1, j = 0; i >= 0; i--) if (pheader->client[i].pid != 0) j++; pheader->num_clients = j; destroy_flag = (pheader->num_clients == 0); /* flush shared memory to disk before it gets deleted */ if (destroy_flag) ss_shm_flush(pheader->name, pdb->shm_adr, pdb->shm_size, pdb->shm_handle, true); mstrlcpy(xname, pheader->name, sizeof(xname)); /* unmap shared memory, delete it if we are the last */ ss_shm_close(xname, pdb->shm_adr, pdb->shm_size, pdb->shm_handle, destroy_flag); pheader = NULL; // after ss_shm_close(), pheader points nowhere pdb->database_header = NULL; // ditto /* unlock database */ db_unlock_database(hDB); /* delete semaphore */ ss_semaphore_delete(pdb->semaphore, destroy_flag); /* delete mutex */ ss_mutex_delete(pdb->mutex); /* update _database_entries */ if (hDB == _database_entries) _database_entries--; if (_database_entries > 0) _database = (DATABASE *) realloc(_database, sizeof(DATABASE) * (_database_entries)); else { free(_database); _database = NULL; } /* if we are the last one, also delete other semaphores */ if (destroy_flag) { //extern INT _semaphore_elog, _semaphore_alarm, _semaphore_history, _semaphore_msg; extern INT _semaphore_elog, _semaphore_alarm, _semaphore_history; if (_semaphore_elog) ss_semaphore_delete(_semaphore_elog, TRUE); if (_semaphore_alarm) ss_semaphore_delete(_semaphore_alarm, TRUE); if (_semaphore_history) ss_semaphore_delete(_semaphore_history, TRUE); //if (_semaphore_msg) // ss_semaphore_delete(_semaphore_msg, TRUE); } } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_flush_database(HNDLE hDB) /********************************************************************\ Routine: db_flush_database Purpose: Flushes the shared memory of a database to its disk file. Input: HNDLE hDB Handle to the database, which is used as an index to the _database array. Output: none Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid RPC_NET_ERROR Network error \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_FLUSH_DATABASE, hDB); #ifdef LOCAL_ROUTINES else { int status, size; uint32_t flush_period = 60; uint32_t last_flush = 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_flush_database", "invalid database handle"); return DB_INVALID_HANDLE; } /* create keys if not present */ size = sizeof(flush_period); db_get_value(hDB, 0, "/System/Flush/Flush period", &flush_period, &size, TID_UINT32, true); size = sizeof(last_flush); db_get_value(hDB, 0, "/System/Flush/Last flush", &last_flush, &size, TID_UINT32, true); HNDLE hkey; status = db_find_key(hDB, 0, "/System/Flush/Last flush", &hkey); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_flush_database", "Cannot obtain key /System/Flush/Last flush"); return DB_INVALID_HANDLE; } db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; if (!_database[hDB - 1].attached) { db_unlock_database(hDB); cm_msg(MERROR, "db_flush_database", "invalid database handle"); return DB_INVALID_HANDLE; } db_err_msg *msg = nullptr; const KEY *pkey = db_get_pkey(pheader, hkey, &status, "db_flush_database", &msg); if (!pkey) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return CM_NO_CLIENT; } size = sizeof(last_flush); status = db_get_data_locked(pheader, pkey, 0, &last_flush, &size, TID_UINT32, &msg); /* only flush if period has expired */ if (ss_time() > last_flush + flush_period) { db_allow_write_locked(pdb, "db_flush_database"); /* update last flush time in ODB */ last_flush = ss_time(); db_set_value_wlocked(pheader, hDB, 0, "/System/Flush/Last flush", &last_flush, sizeof(last_flush), 1, TID_UINT32, &msg); /* flush shared memory to disk */ ss_shm_flush(pheader->name, _database[hDB - 1].shm_adr, _database[hDB - 1].shm_size, _database[hDB - 1].shm_handle, false); } db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_close_all_databases(void) /********************************************************************\ Routine: db_close_all_databases Purpose: Close all open databases and open records Input: none Output: none Function value: DB_SUCCESS Successful completion \********************************************************************/ { INT status; if (rpc_is_remote()) { status = rpc_call(RPC_DB_CLOSE_ALL_DATABASES); if (status != DB_SUCCESS) return status; } db_close_all_records(); db_unwatch_all(); #ifdef LOCAL_ROUTINES { INT i; for (i = _database_entries; i > 0; i--) db_close_database(i); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_set_client_name(HNDLE hDB, const char *client_name) /********************************************************************\ Routine: db_set_client_name Purpose: Set client name for a database. Used by cm_connect_experiment if a client name is duplicate and changed. Input: INT hDB Handle to database char *client_name Name of this application Output: Function value: DB_SUCCESS Successful completion RPC_NET_ERROR Network error \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_CLIENT_NAME, hDB, client_name); #ifdef LOCAL_ROUTINES { if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_client_name", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_CLIENT *pclient = db_get_my_client_locked(pdb); mstrlcpy(pclient->name, client_name, sizeof(pclient->name)); db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Lock a database for exclusive access via system semaphore calls. @param hDB Handle to the database to lock @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TIMEOUT */ INT db_lock_database(HNDLE hDB) { #ifdef LOCAL_ROUTINES int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_lock_database", "invalid database handle %d, aborting...", hDB); abort(); return DB_INVALID_HANDLE; } /* obtain access mutex in multi-thread applications */ status = ss_mutex_wait_for(_database[hDB - 1].mutex, _database[hDB - 1].timeout); if (status != SS_SUCCESS) { cm_msg(MERROR, "db_lock_database", "cannot lock ODB mutex, timeout %d ms, status %d, aborting...", _database[hDB - 1].timeout, status); abort(); } /* protect this function against recursive call from signal handlers */ if (_database[hDB - 1].inside_lock_unlock) { fprintf(stderr, "db_lock_database: Detected recursive call to db_{lock,unlock}_database() while already inside db_{lock,unlock}_database(). Maybe this is a call from a signal handler. Cannot continue, aborting...\n"); abort(); } _database[hDB - 1].inside_lock_unlock = 1; //static int x = 0; //x++; //if (x > 5000) { // printf("inside db_lock_database(), press Ctrl-C now!\n"); // sleep(5); //} // test recursive locking // static int out=0; // out++; // printf("HERE %d!\n", out); // if (out>10) abort(); // db_lock_database(hDB); // printf("OUT %d!\n", out); if (_database[hDB - 1].lock_cnt == 0) { _database[hDB - 1].lock_cnt = 1; /* wait max. 5 minutes for semaphore (required if locking process is being debugged) */ status = ss_semaphore_wait_for(_database[hDB - 1].semaphore, _database[hDB - 1].timeout); if (status == SS_TIMEOUT) { cm_msg(MERROR, "db_lock_database", "cannot lock ODB semaphore, timeout %d ms, aborting...", _database[hDB - 1].timeout); //exit(1); // exit() calls ataxit() handlers, including midas disconnect experiment which will crash because ODB locking is hosed. cannot continue, must abort(). K.O. 13-OCT-2024. abort(); } if (status != SS_SUCCESS) { cm_msg(MERROR, "db_lock_database", "cannot lock ODB semaphore, timeout %d ms, ss_semaphore_wait_for() status %d, aborting...", _database[hDB - 1].timeout, status); abort(); } } else { _database[hDB - 1].lock_cnt++; // we have already the lock (recursive call), so just increase counter } #ifdef CHECK_LOCK_COUNT { char str[256]; sprintf(str, "db_lock_database, lock_cnt=%d", _database[hDB - 1].lock_cnt); ss_stack_history_entry(str); } #endif if (_database[hDB - 1].protect) { if (_database[hDB - 1].database_header == NULL) { int status; assert(!_database[hDB - 1].protect_read); assert(!_database[hDB - 1].protect_write); status = ss_shm_unprotect(_database[hDB - 1].shm_handle, &_database[hDB - 1].shm_adr, _database[hDB - 1].shm_size, TRUE, FALSE, "db_lock_database"); if (status != SS_SUCCESS) { cm_msg(MERROR, "db_lock_database", "cannot lock ODB, ss_shm_unprotect(TRUE,FALSE) failed with status %d, aborting...", status); cm_msg_flush_buffer(); abort(); } _database[hDB - 1].database_header = (DATABASE_HEADER *) _database[hDB - 1].shm_adr; _database[hDB - 1].protect_read = TRUE; _database[hDB - 1].protect_write = FALSE; } } _database[hDB - 1].inside_lock_unlock = 0; #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } #ifdef LOCAL_ROUTINES INT db_allow_write_locked(DATABASE* p, const char* caller_name) { assert(p); if (p->protect && !p->protect_write) { int status; assert(p->lock_cnt > 0); assert(p->database_header != NULL); assert(p->protect_read); status = ss_shm_unprotect(p->shm_handle, &p->shm_adr, p->shm_size, TRUE, TRUE, caller_name); if (status != SS_SUCCESS) { cm_msg(MERROR, "db_allow_write_locked", "cannot write to ODB, ss_shm_unprotect(TRUE,TRUE) failed with status %d, aborting...", status); cm_msg_flush_buffer(); abort(); } p->database_header = (DATABASE_HEADER *) p->shm_adr; p->protect_read = TRUE; p->protect_write = TRUE; } return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ /********************************************************************/ /** Unlock a database via system semaphore calls. @param hDB Handle to the database to unlock @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_unlock_database(HNDLE hDB) { #ifdef LOCAL_ROUTINES if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_unlock_database", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } #ifdef CHECK_LOCK_COUNT { char str[256]; sprintf(str, "db_unlock_database, lock_cnt=%d", _database[hDB - 1].lock_cnt); ss_stack_history_entry(str); } #endif /* protect this function against recursive call from signal handlers */ if (_database[hDB - 1].inside_lock_unlock) { fprintf(stderr, "db_unlock_database: Detected recursive call to db_{lock,unlock}_database() while already inside db_{lock,unlock}_database(). Maybe this is a call from a signal handler. Cannot continue, aborting...\n"); abort(); } _database[hDB - 1].inside_lock_unlock = 1; //static int x = 0; //x++; //if (x > 5000) { // printf("inside db_unlock_database(), press Ctrl-C now!\n"); // sleep(5); //} if (_database[hDB - 1].lock_cnt == 1) { ss_semaphore_release(_database[hDB - 1].semaphore); if (_database[hDB - 1].protect && _database[hDB - 1].database_header) { int status; assert(_database[hDB - 1].protect_read); assert(_database[hDB - 1].database_header); _database[hDB - 1].database_header = NULL; status = ss_shm_protect(_database[hDB - 1].shm_handle, _database[hDB - 1].shm_adr, _database[hDB - 1].shm_size); if (status != SS_SUCCESS) { cm_msg(MERROR, "db_unlock_database", "cannot unlock ODB, ss_shm_protect() failed with status %d, aborting...", status); cm_msg_flush_buffer(); abort(); } _database[hDB - 1].protect_read = FALSE; _database[hDB - 1].protect_write = FALSE; } } assert(_database[hDB - 1].lock_cnt > 0); _database[hDB - 1].lock_cnt--; _database[hDB - 1].inside_lock_unlock = 0; /* release mutex for multi-thread applications */ ss_mutex_release(_database[hDB - 1].mutex); #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ #if 0 INT db_get_lock_cnt(HNDLE hDB) { #ifdef LOCAL_ROUTINES /* return zero if no ODB is open or we run remotely */ if (_database_entries == 0) return 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_lock_cnt", "invalid database handle %d, aborting...", hDB); fprintf(stderr, "db_get_lock_cnt: invalid database handle %d, aborting...\n", hDB); abort(); return DB_INVALID_HANDLE; } return _database[hDB - 1].lock_cnt; #else return 0; #endif } #endif INT db_set_lock_timeout(HNDLE hDB, int timeout_millisec) { #ifdef LOCAL_ROUTINES /* return zero if no ODB is open or we run remotely */ if (_database_entries == 0) return 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_lock_timeout", "invalid database handle %d, aborting...", hDB); fprintf(stderr, "db_set_lock_timeout: invalid database handle %d, aborting...\n", hDB); abort(); return DB_INVALID_HANDLE; } if (timeout_millisec > 0) { _database[hDB - 1].timeout = timeout_millisec; } return _database[hDB - 1].timeout; #else return 0; #endif } #ifdef LOCAL_ROUTINES /** Update last activity time */ INT db_update_last_activity(DWORD millitime) { bool found = false; int pid = ss_getpid(); for (int i = 0; i < _database_entries; i++) { if (_database[i].attached) { db_lock_database(i + 1); db_allow_write_locked(&_database[i], "db_update_last_activity"); assert(_database[i].database_header); /* update the last_activity entry to show that we are alive */ for (int j=0; j<_database[i].database_header->max_client_index; j++) { DATABASE_CLIENT* pdbclient = _database[i].database_header->client + j; //printf("client %d pid %d vs our pid %d\n", j, pdbclient->pid, pid); if (pdbclient->pid == pid) { pdbclient->last_activity = millitime; found = true; } } db_unlock_database(i + 1); } } if (!found) { cm_msg(MERROR, "db_update_last_activity", "Did not find this client in any database. Maybe this client was removed by a timeout, see midas.log. Cannot continue, aborting..."); abort(); } return DB_SUCCESS; } #endif // LOCAL_ROUTINES #ifdef LOCAL_ROUTINES static void db_delete_client_wlocked(DATABASE_HEADER* pheader, int jclient, db_err_msg** msg) { DATABASE_CLIENT* pdbclient = &pheader->client[jclient]; /* decrement notify_count for open records and clear exclusive mode */ int k; for (k = 0; k < pdbclient->max_index; k++) if (pdbclient->open_record[k].handle) { KEY* pkey = (KEY *) ((char *) pheader + pdbclient->open_record[k].handle); if (pkey->notify_count > 0) pkey->notify_count--; if (pdbclient->open_record[k].access_mode & MODE_WRITE) db_set_mode_wlocked(pheader, pkey, (WORD) (pkey->access_mode & ~MODE_EXCLUSIVE), 2, msg); } /* clear entry from client structure in buffer header */ memset(pdbclient, 0, sizeof(DATABASE_CLIENT)); /* calculate new max_client_index entry */ for (k = MAX_CLIENTS - 1; k >= 0; k--) if (pheader->client[k].pid != 0) break; pheader->max_client_index = k + 1; /* count new number of clients */ int nc; for (k = MAX_CLIENTS - 1, nc = 0; k >= 0; k--) if (pheader->client[k].pid != 0) nc++; pheader->num_clients = nc; } #endif #ifdef LOCAL_ROUTINES static int db_delete_client_info_wlocked(HNDLE hDB, DATABASE_HEADER* pheader, int pid, db_err_msg** msg) { if (!pid) pid = ss_getpid(); char str[256]; int status = 0; sprintf(str, "System/Clients/%0d", pid); KEY* pkey = (KEY*)db_find_pkey_locked(pheader, NULL, str, &status, msg); if (!pkey) { return status; } status = db_set_mode_wlocked(pheader, pkey, MODE_READ | MODE_WRITE | MODE_DELETE, 2, msg); HNDLE hKey = db_pkey_to_hkey(pheader, pkey); status = db_delete_key1(hDB, hKey, 1, TRUE); int32_t data = 0; db_set_value_wlocked(pheader, hDB, 0, "/System/Client Notify", &data, sizeof(data), 1, TID_INT32, msg); return status; } #endif /********************************************************************/ /** Delete client info from database @param hDB Database handle @param pid PID of entry to delete, zero for this process. @return CM_SUCCESS */ int db_delete_client_info(HNDLE hDB, int pid) { #ifdef LOCAL_ROUTINES if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_delete_client_info", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_delete_client_info", "invalid database handle"); return DB_INVALID_HANDLE; } /* lock database */ db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; db_allow_write_locked(pdb, "db_delete_client_info"); db_err_msg* msg = NULL; int status = db_delete_client_info_wlocked(hDB, pheader, pid, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; #else return DB_SUCCESS; #endif } void db_cleanup(const char *who, DWORD actual_time, BOOL wrong_interval) { #ifdef LOCAL_ROUTINES int i; /* check online databases */ for (i = 0; i < _database_entries; i++) { DATABASE *pdb = &_database[i]; if (!pdb->attached) // empty slot continue; db_lock_database(i + 1); db_allow_write_locked(pdb, "db_cleanup"); DATABASE_HEADER* pheader = pdb->database_header; DATABASE_CLIENT* pclient = db_get_my_client_locked(pdb); /* update the last_activity entry to show that we are alive */ pclient->last_activity = actual_time; /* don't check other clients if interval is stange */ if (wrong_interval) { db_unlock_database(i + 1); continue; } db_err_msg *msg = NULL; /* now check other clients */ int j; for (j = 0; j < pheader->max_client_index; j++) { DATABASE_CLIENT *pdbclient = &pheader->client[j]; int client_pid = pdbclient->pid; if (client_pid == 0) // empty slot continue; if (!ss_pid_exists(client_pid)) { db_msg(&msg, MINFO, "db_cleanup", "Client \'%s\' on database \'%s\' pid %d does not exist and db_cleanup called by %s removed it", pdbclient->name, pheader->name, client_pid, who); db_delete_client_wlocked(pheader, j, &msg); db_delete_client_info_wlocked(i+1, pheader, client_pid, &msg); continue; } /* now make again the check with the buffer locked */ actual_time = ss_millitime(); if ((pdbclient->watchdog_timeout && actual_time > pdbclient->last_activity && actual_time - pdbclient->last_activity > pdbclient->watchdog_timeout) ) { db_msg(&msg, MINFO, "db_cleanup", "Client \'%s\' on database \'%s\' pid %d timed out and db_cleanup called by %s removed it (idle %1.1lfs,TO %1.0lfs)", pdbclient->name, pheader->name, client_pid, who, (actual_time - pdbclient->last_activity) / 1000.0, pdbclient->watchdog_timeout / 1000.0); db_delete_client_wlocked(pheader, j, &msg); db_delete_client_info_wlocked(i+1, pheader, client_pid, &msg); } } db_unlock_database(i + 1); if (msg) db_flush_msg(&msg); } #endif } #ifdef LOCAL_ROUTINES void db_cleanup2(const char* client_name, int ignore_timeout, DWORD actual_time, const char *who) { /* check online databases */ int i; for (i = 0; i < _database_entries; i++) { if (_database[i].attached) { /* update the last_activity entry to show that we are alive */ db_lock_database(i + 1); db_err_msg *msg = NULL; DATABASE* pdb = &_database[i]; db_allow_write_locked(pdb, "db_cleanup2"); DWORD now = ss_millitime(); DATABASE_HEADER* pheader = pdb->database_header; DATABASE_CLIENT* pclient = db_get_my_client_locked(pdb); pclient->last_activity = now; /* now check other clients */ int j; for (j = 0; j < pheader->max_client_index; j++) { DATABASE_CLIENT* pdbclient = &pheader->client[j]; if (j == pdb->client_index) // do not check ourselves continue; if (!pdbclient->pid) // empty slot continue; if ((client_name == NULL || client_name[0] == 0 || strncmp(pdbclient->name, client_name, strlen(client_name)) == 0)) { int client_pid = pdbclient->pid; if (!ss_pid_exists(client_pid)) { db_msg(&msg, MINFO, "db_cleanup2", "Client \'%s\' on database \'%s\' pid %d does not exist and db_cleanup2 called by %s removed it", pdbclient->name, pheader->name, client_pid, who); db_delete_client_wlocked(pheader, j, &msg); db_delete_client_info_wlocked(i+1, pheader, client_pid, &msg); /* go again though whole list */ j = 0; continue; } DWORD interval; if (ignore_timeout) interval = 2 * WATCHDOG_INTERVAL; else interval = pdbclient->watchdog_timeout; /* If client process has no activity, clear its buffer entry. */ if ((interval > 0 && now - pdbclient->last_activity > interval)) { db_msg(&msg, MINFO, "db_cleanup2", "Client \'%s\' on database \'%s\' timed out and db_cleanup2 called by %s removed it (idle %1.1lfs,TO %1.0lfs)", pdbclient->name, pheader->name, who, (now - pdbclient->last_activity) / 1000.0, interval / 1000.0); db_delete_client_wlocked(pheader, j, &msg); db_delete_client_info_wlocked(i+1, pheader, client_pid, &msg); /* go again though whole list */ j = 0; continue; } } } db_unlock_database(i + 1); if (msg) db_flush_msg(&msg); } } } void db_set_watchdog_params(DWORD timeout) { /* set watchdog flag of all open databases */ for (int i = _database_entries; i > 0; i--) { db_lock_database(i); DATABASE* pdb = &_database[i - 1]; if (!pdb->attached) { db_unlock_database(i); continue; } DATABASE_CLIENT *pclient = db_get_my_client_locked(pdb); db_allow_write_locked(pdb, "db_set_watchdog_params"); /* clear entry from client structure in buffer header */ pclient->watchdog_timeout = timeout; /* show activity */ pclient->last_activity = ss_millitime(); db_unlock_database(i); } } /********************************************************************/ /** Return watchdog information about specific client @param hDB ODB handle @param client_name ODB client name @param timeout Timeout for this application in seconds @param last Last time watchdog was called in msec @return CM_SUCCESS, CM_NO_CLIENT, DB_INVALID_HANDLE */ INT db_get_watchdog_info(HNDLE hDB, const char *client_name, DWORD * timeout, DWORD * last) { if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_watchdog_info", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_watchdog_info", "invalid database handle"); return DB_INVALID_HANDLE; } /* lock database */ db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; /* find client */ for (int i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT *pclient = &pheader->client[i]; if (pclient->pid && equal_ustring(pclient->name, client_name)) { *timeout = pclient->watchdog_timeout; *last = ss_millitime() - pclient->last_activity; db_unlock_database(hDB); return DB_SUCCESS; } } *timeout = *last = 0; db_unlock_database(hDB); return CM_NO_CLIENT; } /********************************************************************/ /** Check if a client with a /system/client/xxx entry has a valid entry in the ODB client table. If not, remove that client from the /system/client tree. @param hDB Handle to online database @param hKeyClient Handle to client key @return CM_SUCCESS, CM_NO_CLIENT */ INT db_check_client(HNDLE hDB, HNDLE hKeyClient) { if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_check_client", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_check_client", "invalid database handle"); return DB_INVALID_HANDLE; } int status; db_lock_database(hDB); db_err_msg* msg = NULL; DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; const KEY* pkey = db_get_pkey(pheader, hKeyClient, &status, "db_check_client", &msg); if (!pkey) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return CM_NO_CLIENT; } int client_pid = atoi(pkey->name); const KEY* pkey_name = db_find_pkey_locked(pheader, pkey, "Name", &status, &msg); if (!pkey_name) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return CM_NO_CLIENT; } char name[256]; name[0] = 0; int size = sizeof(name); status = db_get_data_locked(pheader, pkey_name, 0, name, &size, TID_STRING, &msg); //fprintf(stderr, "db_check_client: hkey %d, status %d, pid %d, name \'%s\'\n", hKeyClient, status, client_pid, name); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return CM_NO_CLIENT; } bool dead = false; bool found = false; /* loop through clients */ for (int i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT *pclient = &pheader->client[i]; if (pclient->pid == client_pid) { found = true; break; } } if (found) { /* check that the client is still running: PID still exists */ if (!ss_pid_exists(client_pid)) { dead = true; } } status = DB_SUCCESS; if (!found || dead) { /* client not found : delete ODB stucture */ db_allow_write_locked(pdb, "db_check_client"); status = db_delete_client_info_wlocked(hDB, pheader, client_pid, &msg); if (status != DB_SUCCESS) db_msg(&msg, MERROR, "db_check_client", "Cannot delete client info for client \'%s\', pid %d, db_delete_client_info() status %d", name, client_pid, status); else if (!found) db_msg(&msg, MINFO, "db_check_client", "Deleted entry \'/System/Clients/%d\' for client \'%s\' because it is not connected to ODB", client_pid, name); else if (dead) db_msg(&msg, MINFO, "db_check_client", "Deleted entry \'/System/Clients/%d\' for client \'%s\' because process pid %d does not exists", client_pid, name, client_pid); status = CM_NO_CLIENT; } db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif // LOCAL_ROUTINES /********************************************************************/ /** Protect a database for read/write access outside of the \b db_xxx functions @param hDB ODB handle obtained via cm_get_experiment_database(). @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_protect_database(HNDLE hDB) { if (rpc_is_remote()) return DB_SUCCESS; #ifdef LOCAL_ROUTINES if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_protect_database", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } _database[hDB - 1].protect = TRUE; ss_shm_protect(_database[hDB - 1].shm_handle, _database[hDB - 1].database_header, _database[hDB - 1].shm_size); _database[hDB - 1].database_header = NULL; #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*---- helper routines ---------------------------------------------*/ const char *extract_key(const char *key_list, char *key_name, int key_name_length) { int i = 0; if (*key_list == '/') key_list++; while (*key_list && *key_list != '/' && ++i < key_name_length) *key_name++ = *key_list++; *key_name = 0; return key_list; } BOOL equal_ustring(const char *str1, const char *str2) { if (str1 == NULL && str2 != NULL) return FALSE; if (str1 != NULL && str2 == NULL) return FALSE; if (str1 == NULL && str2 == NULL) return TRUE; if (strlen(str1) != strlen(str2)) return FALSE; while (*str1) if (toupper(*str1++) != toupper(*str2++)) return FALSE; if (*str2) return FALSE; return TRUE; } BOOL ends_with_ustring(const char *str, const char *suffix) { int len_str = strlen(str); int len_suffix = strlen(suffix); // suffix is longer than the string if (len_suffix > len_str) return FALSE; return equal_ustring(str + len_str - len_suffix, suffix); } //utility function to match string against wildcard pattern BOOL strmatch(char* pattern, char* str){ switch(*pattern){ case 0: if(*str == 0){ //end of pattern return true; }else return false; case '*': { int i=0; // check for end of the string if(pattern[1] == 0) return true; // loop on all possible matches while(str[i] != 0) if(strmatch(pattern + 1, str+(i++))) return true; return false; } case '?': if(*str == 0){ //end of string return false; } else { return strmatch(pattern+1, str+1); } default: if(*str == 0){ //end of string return false; } else if(toupper(*str) == toupper(*pattern)){ //recursion return strmatch(pattern+1, str+1); } else { return false; } } } //utility function to extract array indexes void strarrayindex(char* odbpath, int* index1, int* index2){ char* pc; *index1 = *index2 = 0; if (odbpath[strlen(odbpath) - 1] == ']') { if (strchr(odbpath, '[')) { if (*(strchr(odbpath, '[') + 1) == '*') *index1 = -1; else if (strchr((strchr(odbpath, '[') + 1), '.') || strchr((strchr(odbpath, '[') + 1), '-')) { *index1 = atoi(strchr(odbpath, '[') + 1); pc = strchr(odbpath, '[') + 1; while (*pc != '.' && *pc != '-') pc++; while (*pc == '.' || *pc == '-') pc++; *index2 = atoi(pc); } else *index1 = atoi(strchr(odbpath, '[') + 1); } //remove everything after bracket *strchr(odbpath, '[') = 0; } } /********************************************************************/ /** Create a new key in a database @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Key handle to start with, 0 for root @param key_name Name of key in the form "/key/key/key" @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types) @return DB_SUCCESS, DB_INVALID_HANDLE, DB_INVALID_PARAM, DB_FULL, DB_KEY_EXIST, DB_NO_ACCESS */ INT db_create_key(HNDLE hDB, HNDLE hKey, const char *key_name, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_CREATE_KEY, hDB, hKey, key_name, type); #ifdef LOCAL_ROUTINES { int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_create_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_create_key", "invalid database handle"); return DB_INVALID_HANDLE; } /* lock database */ db_lock_database(hDB); DATABASE_HEADER* pheader = _database[hDB - 1].database_header; db_err_msg *msg = NULL; KEY* pkey = (KEY*)db_get_pkey(pheader, hKey, &status, "db_create_key", &msg); if (!pkey) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return DB_INVALID_HANDLE; } db_allow_write_locked(&_database[hDB-1], "db_create_key"); KEY* newpkey = NULL; status = db_create_key_wlocked(pheader, pkey, key_name, type, &newpkey, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } #ifdef LOCAL_ROUTINES int db_create_key_wlocked(DATABASE_HEADER* pheader, KEY* parentKey, const char *key_name, DWORD type, KEY** pnewkey, db_err_msg** msg) { int status; if (pnewkey) *pnewkey = NULL; if (parentKey== NULL) { parentKey = (KEY*) db_get_pkey(pheader, pheader->root_key, &status, "db_create_key_wlocked", msg); if (!parentKey) { return status; } } status = db_validate_name(key_name, TRUE, "db_create_key_wlocked", msg); if (status != DB_SUCCESS) { return status; } /* check type */ if (type <= 0 || type >= TID_LAST) { db_msg(msg, MERROR, "db_create_key", "invalid key type %d to create \'%s\' in \'%s\'", type, key_name, db_get_path_locked(pheader, parentKey).c_str()); return DB_INVALID_PARAM; } const KEY* pdir = parentKey; if (pdir->type != TID_KEY) { db_msg(msg, MERROR, "db_create_key", "cannot create \'%s\' in \'%s\' tid is %d, not a directory", key_name, db_get_path_locked(pheader, pdir).c_str(), pdir->type); return DB_NO_KEY; } KEY* pcreated = NULL; const char* pkey_name = key_name; do { // key name is limited to NAME_LENGTH, but buffer has to be slightly longer // to prevent truncation before db_validate_name checks for correct length. K.O. char name[NAME_LENGTH+100]; /* extract single key from key_name */ pkey_name = extract_key(pkey_name, name, sizeof(name)); status = db_validate_name(name, FALSE, "db_create_key", msg); if (status != DB_SUCCESS) { return status; } /* do not allow empty names, like '/dir/dir//dir/' */ if (name[0] == 0) { return DB_INVALID_PARAM; } /* check if parent or current directory */ if (strcmp(name, "..") == 0) { pdir = db_get_parent(pheader, pdir, &status, "db_create_key", msg); if (!pdir) { return status; } continue; } if (strcmp(name, ".") == 0) continue; KEY* pprev = NULL; const KEY* pitem = db_enum_first_locked(pheader, pdir, msg); while (pitem) { if (equal_ustring(name, pitem->name)) { break; } pprev = (KEY*)pitem; pitem = db_enum_next_locked(pheader, pdir, pitem, msg); } if (!pitem) { /* not found: create new key */ /* check parent for write access */ const KEY* pkeyparent = db_get_parent(pheader, pdir, &status, "db_create_key", msg); if (!pkeyparent) { return status; } if (!(pkeyparent->access_mode & MODE_WRITE) || (pkeyparent->access_mode & MODE_EXCLUSIVE)) { return DB_NO_ACCESS; } KEYLIST* pkeylist = (KEYLIST*)db_get_pkeylist(pheader, db_pkey_to_hkey(pheader, pdir), pdir, "db_create_key_wlocked", msg); if (!pkeylist) { return DB_CORRUPTED; } pkeylist->num_keys++; if (*pkey_name == '/' || type == TID_KEY) { /* create new key with keylist */ KEY* pkey = (KEY *) malloc_key(pheader, sizeof(KEY), "db_create_key_subdir"); if (pkey == NULL) { if (pkeylist->num_keys > 0) pkeylist->num_keys--; db_msg(msg, MERROR, "db_create_key", "online database full while creating \'%s\' in \'%s\'", key_name, db_get_path_locked(pheader, parentKey).c_str()); return DB_FULL; } /* append key to key list */ if (pprev) pprev->next_key = (POINTER_T) pkey - (POINTER_T) pheader; else pkeylist->first_key = (POINTER_T) pkey - (POINTER_T) pheader; /* set key properties */ pkey->type = TID_KEY; pkey->num_values = 1; pkey->access_mode = MODE_READ | MODE_WRITE | MODE_DELETE; mstrlcpy(pkey->name, name, sizeof(pkey->name)); pkey->parent_keylist = (POINTER_T) pkeylist - (POINTER_T) pheader; /* find space for new keylist */ pkeylist = (KEYLIST *) malloc_key(pheader, sizeof(KEYLIST), "db_create_key_B"); if (pkeylist == NULL) { db_msg(msg, MERROR, "db_create_key", "online database full while creating \'%s\' in \'%s'", key_name, db_get_path_locked(pheader, parentKey).c_str()); return DB_FULL; } /* store keylist in data field */ pkey->data = (POINTER_T) pkeylist - (POINTER_T) pheader; pkey->item_size = sizeof(KEYLIST); pkey->total_size = sizeof(KEYLIST); pkeylist->parent = (POINTER_T) pkey - (POINTER_T) pheader; pkeylist->num_keys = 0; pkeylist->first_key = 0; pcreated = pkey; pdir = pkey; // descend into newly created subdirectory } else { /* create new key with data */ KEY* pkey = (KEY *) malloc_key(pheader, sizeof(KEY), "db_create_key_data"); if (pkey == NULL) { if (pkeylist->num_keys > 0) pkeylist->num_keys--; db_msg(msg, MERROR, "db_create_key", "online database full while creating \'%s\' in \'%s\'", key_name, db_get_path_locked(pheader, parentKey).c_str()); return DB_FULL; } /* append key to key list */ if (pprev) pprev->next_key = (POINTER_T) pkey - (POINTER_T) pheader; else pkeylist->first_key = (POINTER_T) pkey - (POINTER_T) pheader; pkey->type = type; pkey->num_values = 1; pkey->access_mode = MODE_READ | MODE_WRITE | MODE_DELETE; mstrlcpy(pkey->name, name, sizeof(pkey->name)); pkey->parent_keylist = (POINTER_T) pkeylist - (POINTER_T) pheader; /* allocate data */ pkey->item_size = rpc_tid_size(type); if (pkey->item_size > 0) { void* data = malloc_data(pheader, pkey->item_size); if (data == NULL) { pkey->total_size = 0; db_msg(msg, MERROR, "db_create_key", "online database full while creating \'%s\' in \'%s\'", key_name, db_get_path_locked(pheader, parentKey).c_str()); return DB_FULL; } pkey->total_size = pkey->item_size; pkey->data = (POINTER_T)data - (POINTER_T)pheader; } else { /* first data is empty */ pkey->item_size = 0; pkey->total_size = 0; pkey->data = 0; } pcreated = pkey; } } else { /* key found: descend */ /* resolve links */ if (pitem->type == TID_LINK && pkey_name[0]) { pdir = db_resolve_link_locked(pheader, pitem, &status, msg); if (!pdir) { return status; } continue; } if (!(*pkey_name == '/')) { if (pitem->type != type) { db_msg(msg, MERROR, "db_create_key", "object of type %d already exists at \"%s\" while creating \'%s\' of type %d in \'%s\'", pitem->type, db_get_path_locked(pheader, pitem).c_str(), key_name, type, db_get_path_locked(pheader, parentKey).c_str()); return DB_TYPE_MISMATCH; } //db_print_pkey(pheader, pitem); if (pnewkey) *pnewkey = (KEY*)pitem; return DB_KEY_EXIST; } if (pitem->type != TID_KEY) { db_msg(msg, MERROR, "db_create_key", "path element \"%s\" in \"%s\" is not a subdirectory at \"%s\" while creating \'%s\' in \'%s\'", name, key_name, db_get_path_locked(pheader, pitem).c_str(), key_name, db_get_path_locked(pheader, parentKey).c_str()); return DB_TYPE_MISMATCH; } pdir = pitem; } } while (*pkey_name == '/'); assert(pcreated != NULL); if (pnewkey) *pnewkey = pcreated; return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ /********************************************************************/ /** Create a link to a key or set the destination of and existing link. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Key handle to start with, 0 for root @param link_name Name of key in the form "/key/key/key" @param destination Destination of link in the form "/key/key/key" @return DB_SUCCESS, DB_INVALID_HANDLE, DB_FULL, DB_KEY_EXIST, DB_NO_ACCESS, DB_INVALID_NAME */ INT db_create_link(HNDLE hDB, HNDLE hKey, const char *link_name, const char *destination) { HNDLE hkey; int status; if (rpc_is_remote()) return rpc_call(RPC_DB_CREATE_LINK, hDB, hKey, link_name, destination); if (destination == NULL) { cm_msg(MERROR, "db_create_link", "link destination name is NULL"); return DB_INVALID_NAME; } if (destination[0] != '/') { cm_msg(MERROR, "db_create_link", "link destination name \'%s\' should start with \'/\', relative links are forbidden", destination); return DB_INVALID_NAME; } if (strlen(destination) < 1) { cm_msg(MERROR, "db_create_link", "link destination name \'%s\' is too short", destination); return DB_INVALID_NAME; } if ((destination[0] == '/') && (destination[1] == 0)) { cm_msg(MERROR, "db_create_link", "links to \"/\" are forbidden"); return DB_INVALID_NAME; } /* check if destination exists */ status = db_find_key(hDB, hKey, destination, &hkey); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_create_link", "Link destination \"%s\" does not exist", destination); return DB_NO_KEY; } //printf("db_create_link: [%s] hkey %d\n", destination, hkey); /* check if link already exists */ status = db_find_link(hDB, hKey, link_name, &hkey); if (status != DB_SUCCESS) { // create new link status = db_set_value(hDB, hKey, link_name, destination, strlen(destination) + 1, 1, TID_LINK); } else { // check if key is TID_LINK KEY key; db_get_key(hDB, hkey, &key); if (key.type != TID_LINK) { cm_msg(MERROR, "db_create_link", "Existing key \"%s\" is not a link", link_name); return DB_KEY_EXIST; } // modify existing link status = db_set_link_data(hDB, hkey, destination, strlen(destination)+1, 1, TID_LINK); } return status; } /********************************************************************/ /** Delete a subtree, using level information (only called internally by db_delete_key()) @internal @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Key handle to start with, 0 for root @param level Recursion level, must be zero when @param follow_links Follow links when TRUE called from a user routine @return DB_SUCCESS, DB_INVALID_HANDLE, DB_OPEN_RECORD */ INT db_delete_key1(HNDLE hDB, HNDLE hKey, INT level, BOOL follow_links) { #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEYLIST *pkeylist; KEY *pkey, *pnext_key, *pkey_tmp; HNDLE hKeyLink; BOOL deny_delete; INT status; BOOL locked = FALSE; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_delete_key1", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_delete_key1", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_delete_key1", "invalid key handle"); return DB_INVALID_HANDLE; } /* lock database at the top level */ if (level == 0) { db_lock_database(hDB); locked = TRUE; } pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { if (locked) db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check if someone has opened key or parent */ if (level == 0) do { #ifdef CHECK_OPEN_RECORD if (pkey->notify_count) { if (locked) db_unlock_database(hDB); return DB_OPEN_RECORD; } #endif if (pkey->parent_keylist == 0) break; pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); } while (TRUE); pkey = (KEY *) ((char *) pheader + hKey); pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); deny_delete = FALSE; /* first recures subtree for keys */ if (pkey->type == TID_KEY && pkeylist->first_key) { pkey = (KEY *) ((char *) pheader + pkeylist->first_key); do { pnext_key = (KEY *) (POINTER_T) pkey->next_key; // FIXME: what is this casting of hKey to pointer? status = db_delete_key1(hDB, (POINTER_T) pkey - (POINTER_T) pheader, level + 1, follow_links); if (status == DB_NO_ACCESS) deny_delete = TRUE; if (pnext_key) { // FIXME: validate pnext_key pkey = (KEY *) ((char *) pheader + (POINTER_T) pnext_key); } } while (pnext_key); } /* follow links if requested */ if (pkey->type == TID_LINK && follow_links) { status = db_find_key1(hDB, 0, (char *) pheader + pkey->data, &hKeyLink); if (status == DB_SUCCESS && follow_links < 100) db_delete_key1(hDB, hKeyLink, level + 1, follow_links + 1); if (follow_links == 100) cm_msg(MERROR, "db_delete_key1", "try to delete cyclic link"); } /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { if (locked) db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* return if key was already deleted by cyclic link */ if (pkey->parent_keylist == 0) { if (locked) db_unlock_database(hDB); return DB_SUCCESS; } /* now delete key */ if (hKey != pheader->root_key) { if (!(pkey->access_mode & MODE_DELETE) || deny_delete) { if (locked) db_unlock_database(hDB); return DB_NO_ACCESS; } #ifdef CHECK_OPEN_RECORD if (pkey->notify_count) { if (locked) db_unlock_database(hDB); return DB_OPEN_RECORD; } #endif db_allow_write_locked(&_database[hDB - 1], "db_delete_key1"); /* delete key data */ if (pkey->type == TID_KEY) free_key(pheader, (char *) pheader + pkey->data, pkey->total_size); else free_data(pheader, (char *) pheader + pkey->data, pkey->total_size, "db_delete_key1"); /* unlink key from list */ pnext_key = (KEY *) (POINTER_T) pkey->next_key; pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); if ((KEY *) ((char *) pheader + pkeylist->first_key) == pkey) { /* key is first in list */ pkeylist->first_key = (POINTER_T) pnext_key; } else { /* find predecessor */ pkey_tmp = (KEY *) ((char *) pheader + pkeylist->first_key); while ((KEY *) ((char *) pheader + pkey_tmp->next_key) != pkey) pkey_tmp = (KEY *) ((char *) pheader + pkey_tmp->next_key); pkey_tmp->next_key = (POINTER_T) pnext_key; } /* delete key */ free_key(pheader, pkey, sizeof(KEY)); pkeylist->num_keys--; } if (locked) db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Delete a subtree in a database starting from a key (including this key). \code ... status = db_find_link(hDB, 0, str, &hkey); if (status != DB_SUCCESS) { cm_msg(MINFO,"my_delete"," "Cannot find key %s", str); return; } status = db_delete_key(hDB, hkey, FALSE); if (status != DB_SUCCESS) { cm_msg(MERROR,"my_delete"," "Cannot delete key %s", str); return; } ... \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey for key where search starts, zero for root. @param follow_links Follow links when TRUE. @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_ACCESS, DB_OPEN_RECORD */ INT db_delete_key(HNDLE hDB, HNDLE hKey, BOOL follow_links) { if (rpc_is_remote()) return rpc_call(RPC_DB_DELETE_KEY, hDB, hKey, follow_links); return db_delete_key1(hDB, hKey, 0, follow_links); } #ifdef LOCAL_ROUTINES static const KEY* db_find_pkey_locked(const DATABASE_HEADER *pheader, const KEY* pkey, const char *key_name, int *pstatus, db_err_msg **msg) { if (pkey == NULL) { pkey = db_get_pkey(pheader, pheader->root_key, pstatus, "db_find_key", msg); if (!pkey) { return NULL; } } HNDLE hKey = db_pkey_to_hkey(pheader, pkey); if (pkey->type != TID_KEY) { DWORD tid = pkey->type; std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, "db_find_key", "hkey %d path \"%s\" tid %d is not a directory, looking for \"%s\"", hKey, path.c_str(), tid, key_name); if (pstatus) *pstatus = DB_NO_KEY; return NULL; } if (key_name[0] == 0 || strcmp(key_name, "/") == 0) { if (!(pkey->access_mode & MODE_READ)) { if (pstatus) *pstatus = DB_NO_ACCESS; return NULL; } return pkey; } const KEYLIST *pkeylist = db_get_pkeylist(pheader, hKey, pkey, "db_find_key", msg); if (!pkeylist) { if (pstatus) *pstatus = DB_CORRUPTED; return NULL; } HNDLE last_good_hkey = hKey; const char *pkey_name = key_name; do { assert(pkeylist!=NULL); // should never happen! char str[MAX_ODB_PATH]; /* extract single subkey from key_name */ pkey_name = extract_key(pkey_name, str, sizeof(str)); /* strip trailing '[n]' */ if (strchr(str, '[') && str[strlen(str) - 1] == ']') *strchr(str, '[') = 0; /* check if parent or current directory */ if (strcmp(str, "..") == 0) { if (pkey->parent_keylist) { pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); } continue; } if (strcmp(str, ".") == 0) continue; last_good_hkey = hKey; hKey = pkeylist->first_key; if (hKey == 0) { // empty subdirectory if (pstatus) *pstatus = DB_NO_KEY; return NULL; } int i; for (i = 0; i < pkeylist->num_keys; i++) { pkey = db_get_pkey(pheader, hKey, pstatus, "db_find_key", msg); if (!pkey) { std::string path = db_get_path_locked(pheader, last_good_hkey); db_msg(msg, MERROR, "db_find_key", "hkey %d path \"%s\" invalid subdirectory entry hkey %d, looking for \"%s\"", last_good_hkey, path.c_str(), hKey, key_name); return NULL; } if (!db_validate_key_offset(pheader, pkey->next_key)) { std::string path = db_get_path_locked(pheader, hKey); db_msg(msg, MERROR, "db_find_key", "hkey %d path \"%s\" invalid next_key %d, looking for \"%s\"", hKey, path.c_str(), pkey->next_key, key_name); if (pstatus) *pstatus = DB_CORRUPTED; return NULL; } if (equal_ustring(str, pkey->name)) break; if (pkey->next_key == 0) { if (pstatus) *pstatus = DB_NO_KEY; return NULL; } hKey = pkey->next_key; } if (i == pkeylist->num_keys) { if (pstatus) *pstatus = DB_NO_KEY; return NULL; } /* resolve links */ if (pkey->type == TID_LINK) { /* copy destination, strip '/' */ mstrlcpy(str, (char *) pheader + pkey->data, sizeof(str)); if (str[strlen(str) - 1] == '/') str[strlen(str) - 1] = 0; /* if link is pointer to array index, return link instead of destination */ if (str[strlen(str) - 1] == ']') break; /* append rest of key name if existing */ if (pkey_name[0]) { mstrlcat(str, pkey_name, sizeof(str)); return db_find_pkey_locked(pheader, NULL, str, pstatus, msg); } else { /* if last key in chain is a link, return its destination */ int status = 0; const KEY *plink = db_find_pkey_locked(pheader, NULL, str, &status, msg); if (pstatus) { if (status == DB_NO_KEY) *pstatus = DB_INVALID_LINK; else *pstatus = status; } return plink; } } /* key found: check if last in chain */ if (*pkey_name == '/') { if (pkey->type != TID_KEY) { if (pstatus) *pstatus = DB_NO_KEY; return NULL; } } if (pkey->type == TID_KEY) { /* descend one level */ pkeylist = db_get_pkeylist(pheader, hKey, pkey, "db_find_key", msg); if (!pkeylist) { if (pstatus) *pstatus = DB_CORRUPTED; return NULL; } } else { pkeylist = NULL; } } while (*pkey_name == '/' && *(pkey_name + 1)); return pkey; } static int db_find_key_locked(const DATABASE_HEADER *pheader, HNDLE hKey, const char *key_name, HNDLE *subhKey, db_err_msg **msg) { int status; const KEY* pkey = db_get_pkey(pheader, hKey, &status, "db_find_key", msg); if (!pkey) { if (subhKey) *subhKey = 0; return status; } const KEY* plink = db_find_pkey_locked(pheader, pkey, key_name, &status, msg); if (!plink) { *subhKey = 0; return status; } *subhKey = db_pkey_to_hkey(pheader, plink); return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ /********************************************************************/ /** Returns key handle for a key with a specific name. Keys can be accessed by their name including the directory or by a handle. A key handle is an internal offset to the shared memory where the ODB lives and allows a much faster access to a key than via its name. The function db_find_key() must be used to convert a key name to a handle. Most other database functions use this key handle in various operations. \code HNDLE hkey, hsubkey; // use full name, start from root db_find_key(hDB, 0, "/Runinfo/Run number", &hkey); // start from subdirectory db_find_key(hDB, 0, "/Runinfo", &hkey); db_find_key(hdb, hkey, "Run number", &hsubkey); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param key_name Name of key to search, can contain directories. @param subhKey Returned handle of key, zero if key cannot be found. @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_ACCESS, DB_NO_KEY */ INT db_find_key(HNDLE hDB, HNDLE hKey, const char *key_name, HNDLE * subhKey) { if (rpc_is_remote()) return rpc_call(RPC_DB_FIND_KEY, hDB, hKey, key_name, subhKey); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; INT status; *subhKey = 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_find_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_find_key", "invalid database handle"); return DB_INVALID_HANDLE; } db_err_msg *msg = NULL; db_lock_database(hDB); pheader = _database[hDB - 1].database_header; status = db_find_key_locked(pheader, hKey, key_name, subhKey, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_find_key1(HNDLE hDB, HNDLE hKey, const char *key_name, HNDLE * subhKey) /********************************************************************\ Routine: db_find_key1 Purpose: Same as db_find_key, but without DB locking Input: HNDLE bufer_handle Handle to the database HNDLE hKey Key handle to start the search char *key_name Name of key in the form "/key/key/key" Output: INT *handle Key handle Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_KEY Key doesn't exist DB_NO_ACCESS No access to read key \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_FIND_KEY, hDB, hKey, key_name, subhKey); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEYLIST *pkeylist; KEY *pkey; const char *pkey_name; INT i; *subhKey = 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_find_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_find_key", "invalid database handle"); return DB_INVALID_HANDLE; } pheader = _database[hDB - 1].database_header; if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if (pkey->type != TID_KEY) { cm_msg(MERROR, "db_find_key", "key has no subkeys"); *subhKey = 0; return DB_NO_KEY; } pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); if (key_name[0] == 0 || strcmp(key_name, "/") == 0) { if (!(pkey->access_mode & MODE_READ)) { *subhKey = 0; return DB_NO_ACCESS; } *subhKey = (POINTER_T) pkey - (POINTER_T) pheader; return DB_SUCCESS; } pkey_name = key_name; do { char str[MAX_ODB_PATH]; /* extract single subkey from key_name */ pkey_name = extract_key(pkey_name, str, sizeof(str)); /* check if parent or current directory */ if (strcmp(str, "..") == 0) { if (pkey->parent_keylist) { pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); } continue; } if (strcmp(str, ".") == 0) continue; /* check if key is in keylist */ // FIXME: validate pkeylist->first_key pkey = (KEY *) ((char *) pheader + pkeylist->first_key); for (i = 0; i < pkeylist->num_keys; i++) { if (equal_ustring(str, pkey->name)) break; // FIXME: validate pkey->next_key pkey = (KEY *) ((char *) pheader + pkey->next_key); } if (i == pkeylist->num_keys) { *subhKey = 0; return DB_NO_KEY; } /* resolve links */ if (pkey->type == TID_LINK) { /* copy destination, strip '/' */ strcpy(str, (char *) pheader + pkey->data); if (str[strlen(str) - 1] == '/') str[strlen(str) - 1] = 0; /* append rest of key name if existing */ if (pkey_name[0]) { strcat(str, pkey_name); return db_find_key1(hDB, 0, str, subhKey); } else { /* if last key in chain is a link, return its destination */ return db_find_link1(hDB, 0, str, subhKey); } } /* key found: check if last in chain */ if (*pkey_name == '/') { if (pkey->type != TID_KEY) { *subhKey = 0; return DB_NO_KEY; } } /* descend one level */ pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); } while (*pkey_name == '/' && *(pkey_name + 1)); *subhKey = (POINTER_T) pkey - (POINTER_T) pheader; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_find_link(HNDLE hDB, HNDLE hKey, const char *key_name, HNDLE * subhKey) /********************************************************************\ Routine: db_find_link Purpose: Find a key or link by name and return its handle (internal address). The only difference of this routine compared with db_find_key is that if the LAST key in the chain is a link, it is NOT evaluated. Links not being the last in the chain are evaluated. Input: HNDLE bufer_handle Handle to the database HNDLE hKey Key handle to start the search char *key_name Name of key in the form "/key/key/key" Output: INT *handle Key handle Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_KEY Key doesn't exist DB_NO_ACCESS No access to read key \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_FIND_LINK, hDB, hKey, key_name, subhKey); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEYLIST *pkeylist; KEY *pkey; const char *pkey_name; INT i; *subhKey = 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_find_link", "Invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_find_link", "invalid database handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if (pkey->type != TID_KEY) { db_unlock_database(hDB); cm_msg(MERROR, "db_find_link", "key has no subkeys"); return DB_NO_KEY; } pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); if (key_name[0] == 0 || strcmp(key_name, "/") == 0) { if (!(pkey->access_mode & MODE_READ)) { *subhKey = 0; db_unlock_database(hDB); return DB_NO_ACCESS; } *subhKey = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); return DB_SUCCESS; } pkey_name = key_name; do { char str[MAX_ODB_PATH]; /* extract single subkey from key_name */ pkey_name = extract_key(pkey_name, str, sizeof(str)); /* check if parent or current directory */ if (strcmp(str, "..") == 0) { if (pkey->parent_keylist) { pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); } continue; } if (strcmp(str, ".") == 0) continue; /* check if key is in keylist */ // FIXME: validate pkeylist->first_key pkey = (KEY *) ((char *) pheader + pkeylist->first_key); for (i = 0; i < pkeylist->num_keys; i++) { if (!db_validate_key_offset(pheader, pkey->next_key)) { int pkey_next_key = pkey->next_key; db_unlock_database(hDB); cm_msg(MERROR, "db_find_link", "Warning: database corruption, key \"%s\", next_key 0x%08X is invalid", key_name, pkey_next_key - (int)sizeof(DATABASE_HEADER)); *subhKey = 0; return DB_CORRUPTED; } if (equal_ustring(str, pkey->name)) break; pkey = (KEY *) ((char *) pheader + pkey->next_key); // FIXME: pkey->next_key could be zero } if (i == pkeylist->num_keys) { *subhKey = 0; db_unlock_database(hDB); return DB_NO_KEY; } /* resolve links if not last in chain */ if (pkey->type == TID_LINK && *pkey_name == '/') { /* copy destination, strip '/' */ strcpy(str, (char *) pheader + pkey->data); if (str[strlen(str) - 1] == '/') str[strlen(str) - 1] = 0; /* append rest of key name */ strcat(str, pkey_name); db_unlock_database(hDB); return db_find_link(hDB, 0, str, subhKey); } /* key found: check if last in chain */ if ((*pkey_name == '/')) { if (pkey->type != TID_KEY) { *subhKey = 0; db_unlock_database(hDB); return DB_NO_KEY; } } /* descend one level */ pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); } while (*pkey_name == '/' && *(pkey_name + 1)); *subhKey = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_find_link1(HNDLE hDB, HNDLE hKey, const char *key_name, HNDLE * subhKey) /********************************************************************\ Routine: db_find_link1 Purpose: Same ad db_find_link, but without DB locking Input: HNDLE bufer_handle Handle to the database HNDLE hKey Key handle to start the search char *key_name Name of key in the form "/key/key/key" Output: INT *handle Key handle Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_KEY Key doesn't exist DB_NO_ACCESS No access to read key \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_FIND_LINK, hDB, hKey, key_name, subhKey); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEYLIST *pkeylist; KEY *pkey; const char *pkey_name; INT i; *subhKey = 0; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_find_link", "Invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_find_link", "invalid database handle"); return DB_INVALID_HANDLE; } pheader = _database[hDB - 1].database_header; if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if (pkey->type != TID_KEY) { cm_msg(MERROR, "db_find_link", "key has no subkeys"); return DB_NO_KEY; } pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); if (key_name[0] == 0 || strcmp(key_name, "/") == 0) { if (!(pkey->access_mode & MODE_READ)) { *subhKey = 0; return DB_NO_ACCESS; } *subhKey = (POINTER_T) pkey - (POINTER_T) pheader; return DB_SUCCESS; } pkey_name = key_name; do { char str[MAX_ODB_PATH]; /* extract single subkey from key_name */ pkey_name = extract_key(pkey_name, str, sizeof(str)); /* check if parent or current directory */ if (strcmp(str, "..") == 0) { if (pkey->parent_keylist) { pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); } continue; } if (strcmp(str, ".") == 0) continue; /* check if key is in keylist */ // FIXME: validate pkeylist->first_key pkey = (KEY *) ((char *) pheader + pkeylist->first_key); for (i = 0; i < pkeylist->num_keys; i++) { if (!db_validate_key_offset(pheader, pkey->next_key)) { cm_msg(MERROR, "db_find_link1", "Warning: database corruption, key \"%s\", next_key 0x%08X is invalid", key_name, pkey->next_key - (int)sizeof(DATABASE_HEADER)); *subhKey = 0; return DB_CORRUPTED; } if (equal_ustring(str, pkey->name)) break; pkey = (KEY *) ((char *) pheader + pkey->next_key); // FIXME: pkey->next_key could be zero } if (i == pkeylist->num_keys) { *subhKey = 0; return DB_NO_KEY; } /* resolve links if not last in chain */ if (pkey->type == TID_LINK && *pkey_name == '/') { /* copy destination, strip '/' */ strcpy(str, (char *) pheader + pkey->data); if (str[strlen(str) - 1] == '/') str[strlen(str) - 1] = 0; /* append rest of key name */ strcat(str, pkey_name); return db_find_link1(hDB, 0, str, subhKey); } /* key found: check if last in chain */ if ((*pkey_name == '/')) { if (pkey->type != TID_KEY) { *subhKey = 0; return DB_NO_KEY; } } /* descend one level */ pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); } while (*pkey_name == '/' && *(pkey_name + 1)); *subhKey = (POINTER_T) pkey - (POINTER_T) pheader; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_find_keys(HNDLE hDB, HNDLE hKeyRoot, char* odbpath, std::vector &hKeyVector) /********************************************************************\ Routine: db_find_keys Purpose: finds all ODB keys matching an odb path '*' and '?' wildcard expansion available Input: HNDLE hDB Handle to the database HNDLE hKeyRoot Key handle to start the search char *odbpath Pattern of keys in the form "/key/key/key", can include '*' and '?' Output: std::vector &hKeyVector Key handle Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_KEY No access to any key DB_NO_ACCESS No access to a read key \********************************************************************/ { HNDLE hKey, hSubKey=0; KEY key; INT status; char *pattern = NULL; char *subkeypath = NULL; char *parentkeypath = NULL; char localpath[256]; //make work on a local copy od odbpath to allow recursion strcpy(localpath, odbpath); parentkeypath = localpath; char *wildcard = strpbrk(localpath, "*?"); if(wildcard){ //wildcard found subkeypath = strchr(wildcard, '/'); if(subkeypath){ //truncate the string at slash *subkeypath = 0; subkeypath++; } parentkeypath = strrchr(localpath, '/'); if(parentkeypath){ //truncate there too *parentkeypath = 0; pattern = parentkeypath+1; if((parentkeypath-1) == localpath){ //path starts with '/', no parent path parentkeypath = NULL; } else { parentkeypath = localpath; } } else { //wildcard at top level, start with pattern pattern = localpath; parentkeypath = NULL; } } //if available search for parent key path if(parentkeypath){ status = db_find_key(hDB, hKeyRoot, parentkeypath, &hKey); if (status != DB_SUCCESS) return status; } else { hKey = hKeyRoot; } //if a pattern is found if(pattern){ //try match all subkeys status = DB_NO_KEY; for (int i=0 ; ; i++) { db_enum_key(hDB, hKey, i, &hSubKey); if (!hSubKey) break; // end of list reached db_get_key(hDB, hSubKey, &key); if(strmatch(pattern, key.name)){ //match if(!subkeypath){ //found hKeyVector.push_back(hSubKey); } else if (key.type == TID_KEY){ //recurse with hSubKey as root key and subkeypath as path int subkeystatus = db_find_keys(hDB, hSubKey, subkeypath, hKeyVector); if (subkeystatus != DB_NO_KEY) status = subkeystatus; if (status != DB_SUCCESS && status != DB_NO_KEY) break; } } } return status; } else { //no pattern: hKey matches! db_get_key(hDB, hKey, &key); hKeyVector.push_back(hKey); return DB_SUCCESS; } } /*------------------------------------------------------------------*/ INT db_get_parent(HNDLE hDB, HNDLE hKey, HNDLE * parenthKey) /********************************************************************\ Routine: db_get_parent Purpose: return an handle to the parent key Input: HNDLE bufer_handle Handle to the database HNDLE hKey Key handle of the key Output: INT *handle Parent key handle Function value: DB_SUCCESS Successful completion \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_PARENT, hDB, hKey, parenthKey); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; const KEY *pkey; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_parent", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_parent", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_parent", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; pkey = (const KEY *) ((char *) pheader + hKey); /* find parent key */ const KEYLIST *pkeylist = (const KEYLIST *) ((char *) pheader + pkey->parent_keylist); if (!db_validate_hkey(pheader, pkeylist->parent)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (const KEY *) ((char *) pheader + pkeylist->parent); *parenthKey = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); } #endif return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_scan_tree(HNDLE hDB, HNDLE hKey, INT level, INT(*callback) (HNDLE, HNDLE, KEY *, INT, void *), void *info) /********************************************************************\ Routine: db_scan_tree Purpose: Scan a subtree recursively and call 'callback' for each key Input: HNDLE hDB Handle to the database HNDLE hKeyRoot Key to start scan from, 0 for root INT level Recursion level void *callback Callback routine called with params: hDB Copy of hDB hKey Copy of hKey key Key associated with hKey INT Recursion level info Copy of *info void *info Optional data copied to callback routine Output: implicit via callback Function value: DB_SUCCESS Successful completion \********************************************************************/ { HNDLE hSubkey; KEY key; INT i, status; status = db_get_link(hDB, hKey, &key); if (status != DB_SUCCESS) return status; status = callback(hDB, hKey, &key, level, info); if (status == 0) return status; if (key.type == TID_KEY) { for (i = 0;; i++) { db_enum_link(hDB, hKey, i, &hSubkey); if (!hSubkey) break; db_scan_tree(hDB, hSubkey, level + 1, callback, info); } } return DB_SUCCESS; } #ifdef LOCAL_ROUTINES int db_scan_tree_locked(const DATABASE_HEADER* pheader, const KEY* pkey, int level, int(*callback) (const DATABASE_HEADER* pheader, const KEY *, int, void *, db_err_msg** msg), void *info, db_err_msg** msg) { assert(pkey != NULL); assert(level < MAX_ODB_PATH); int status = callback(pheader, pkey, level, info, msg); if (status == 0) return status; if (pkey->type == TID_KEY) { const KEY* subkey = db_enum_first_locked(pheader, pkey, msg); while (subkey != NULL) { db_scan_tree_locked(pheader, subkey, level + 1, callback, info, msg); subkey = db_enum_next_locked(pheader, pkey, subkey, msg); } } return DB_SUCCESS; } #endif // LOCAL_ROUTINES /*------------------------------------------------------------------*/ INT db_scan_tree_link(HNDLE hDB, HNDLE hKey, INT level, void (*callback) (HNDLE, HNDLE, KEY *, INT, void *), void *info) /********************************************************************\ Routine: db_scan_tree_link Purpose: Scan a subtree recursively and call 'callback' for each key. Similar to db_scan_tree but without following links. Input: HNDLE hDB Handle to the database HNDLE hKeyRoot Key to start scan from, 0 for root INT level Recursion level void *callback Callback routine called with params: hDB Copy of hDB hKey Copy of hKey key Key associated with hKey INT Recursion level info Copy of *info void *info Optional data copied to callback routine Output: implicit via callback Function value: DB_SUCCESS Successful completion \********************************************************************/ { HNDLE hSubkey; KEY key; INT i, status; status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; callback(hDB, hKey, &key, level, info); if (key.type == TID_KEY) { for (i = 0;; i++) { db_enum_link(hDB, hKey, i, &hSubkey); if (!hSubkey) break; db_scan_tree_link(hDB, hSubkey, level + 1, callback, info); } } return DB_SUCCESS; } #ifdef LOCAL_ROUTINES /*------------------------------------------------------------------*/ static std::string db_get_path_locked(const DATABASE_HEADER* pheader, const KEY* pkey) { std::string path = ""; while (1) { //printf("db_get_path_locked: hkey %d, pkey name \"%s\", type %d, parent %d, path \"%s\"\n", hKey, pkey->name, pkey->type, pkey->parent_keylist, path.c_str()); /* check key type */ if (pkey->type <= 0 || pkey->type >= TID_LAST) { char buf[256]; sprintf(buf, "(INVALID_KEY_TYPE_%d)", pkey->type); std::string xpath; xpath += buf; if (path.length() > 0) { xpath += "/"; xpath += path; } return xpath; } /* add key name in front of path */ std::string str = path; path = ""; if (pkey->name[0] == 0) { path += "(EMPTY_NAME)"; } else { path += pkey->name; } if (str.length() > 0) path += "/"; path += str; if (!pkey->parent_keylist) { return path; } if (!db_validate_data_offset(pheader, pkey->parent_keylist)) { return "(INVALID_PARENT_KEYLIST)/" + path; } /* find parent key */ const KEYLIST* pkeylist = (const KEYLIST *) ((char *) pheader + pkey->parent_keylist); if (pkeylist->parent == pheader->root_key) { return "/" + path; } if (pkeylist->parent == 0) { return "(NULL_PARENT)/" + path; } if (!db_validate_key_offset(pheader, pkeylist->parent)) { return "(INVALID_PARENT)/" + path; } pkey = (const KEY *) ((char *) pheader + pkeylist->parent); }; /* NOT REACHED */ } /*------------------------------------------------------------------*/ static std::string db_get_path_locked(const DATABASE_HEADER* pheader, HNDLE hKey) { //printf("db_get_path_locked: hkey %d\n", hKey); if (!hKey) hKey = pheader->root_key; if (hKey == pheader->root_key) { return "/"; } /* check if hKey argument is correct */ if (hKey == 0) { return "(ZERO_HKEY)"; } /* check if hKey argument is correct */ if (!db_validate_key_offset(pheader, hKey)) { return "(INVALID_HKEY)"; } const KEY* pkey = (const KEY *) ((char *) pheader + hKey); return db_get_path_locked(pheader, pkey); } #endif /* LOCAL_ROUTINES */ /*------------------------------------------------------------------*/ INT db_get_path(HNDLE hDB, HNDLE hKey, char *path, INT buf_size) /********************************************************************\ Routine: db_get_path Purpose: Get full path of a key Input: HNDLE hDB Handle to the database HNDLE hKey Key handle INT buf_size Maximum size of path buffer (including trailing zero) Output: char path[buf_size] Path string Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_MEMORY path buffer is to small to contain full string \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_PATH, hDB, hKey, path, buf_size); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_path", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_path", "invalid database handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; std::string xpath = db_get_path_locked(pheader, hKey); db_unlock_database(hDB); mstrlcpy(path, xpath.c_str(), buf_size); return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ std::string db_get_path(HNDLE hDB, HNDLE hKey) /********************************************************************\ Routine: db_get_path Purpose: Get full path of a key Input: HNDLE hDB Handle to the database HNDLE hKey Key handle Function value: Path string \********************************************************************/ { if (rpc_is_remote()) { char path[MAX_ODB_PATH]; int status = rpc_call(RPC_DB_GET_PATH, hDB, hKey, path, sizeof(path)); if (status != DB_SUCCESS) { sprintf(path, "(RPC_DB_GET_PATH status %d)", status); } return path; } #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_path", "invalid database handle"); return "(DB_INVALID_HANDLE)"; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_path", "invalid database handle"); return "(DB_INVALID_HANDLE)"; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; std::string xpath = db_get_path_locked(pheader, hKey); db_unlock_database(hDB); return xpath; } #endif /* LOCAL_ROUTINES */ return "(no LOCAL_ROUTINES)"; } #ifdef LOCAL_ROUTINES /*------------------------------------------------------------------*/ static int db_find_open_records(HNDLE hDB, HNDLE hKey, KEY * key, INT level, void *xresult) { /* check if this key has notify count set */ if (key->notify_count) { db_lock_database(hDB); DATABASE_HEADER *pheader = _database[hDB - 1].database_header; std::string path = db_get_path_locked(pheader, hKey); std::string line = msprintf("%s open %d times by", path.c_str(), key->notify_count); //printf("path [%s] key.name [%s]\n", path, key->name); int count = 0; for (int i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT *pclient = &pheader->client[i]; for (int j = 0; j < pclient->max_index; j++) if (pclient->open_record[j].handle == hKey) { count++; line += " \""; line += pclient->name; line += "\""; //sprintf(line + strlen(line), ", handle %d, mode %d ", pclient->open_record[j].handle, pclient->open_record[j].access_mode); } } if (count < 1) { line += " a deleted client"; } line += "\n"; std::string *result = (std::string*)xresult; *result = line; db_unlock_database(hDB); } return DB_SUCCESS; } static int db_fix_open_records(HNDLE hDB, HNDLE hKey, KEY * key, INT level, void *xresult) { std::string *result = (std::string*)xresult; /* check if this key has notify count set */ if (key->notify_count) { db_lock_database(hDB); DATABASE_HEADER *pheader = _database[hDB - 1].database_header; db_allow_write_locked(&_database[hDB - 1], "db_fix_open_records"); int i; for (i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT *pclient = &pheader->client[i]; int j; for (j = 0; j < pclient->max_index; j++) if (pclient->open_record[j].handle == hKey) break; if (j < pclient->max_index) break; } if (i == pheader->max_client_index) { /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_SUCCESS; } /* reset notify count */ KEY *pkey = (KEY *) ((char *) pheader + hKey); pkey->notify_count = 0; std::string path = db_get_path_locked(pheader, hKey); *result += path; *result += " fixed\n"; } db_unlock_database(hDB); } return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ INT db_get_open_records(HNDLE hDB, HNDLE hKey, char *str, INT buf_size, BOOL fix) /********************************************************************\ Routine: db_get_open_records Purpose: Return a string with all open records Input: HNDLE hDB Handle to the database HNDLE hKey Key to start search from, 0 for root INT buf_size Size of string INT fix If TRUE, fix records which are open but have no client belonging to it. Output: char *str Result string. Individual records are separated with new lines. Function value: DB_SUCCESS Successful completion \********************************************************************/ { str[0] = 0; if (rpc_is_remote()) return rpc_call(RPC_DB_GET_OPEN_RECORDS, hDB, hKey, str, buf_size); std::string result; #ifdef LOCAL_ROUTINES if (fix) db_scan_tree(hDB, hKey, 0, db_fix_open_records, &result); // FIXME: should use db_scan_tree_wlocked() else db_scan_tree(hDB, hKey, 0, db_find_open_records, &result); // FIXME: should use db_scan_tree_locked() #endif mstrlcpy(str, result.c_str(), buf_size); return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Set value of a single key. The function sets a single value or a whole array to a ODB key. Since the data buffer is of type void, no type checking can be performed by the compiler. Therefore the type has to be explicitly supplied, which is checked against the type stored in the ODB. key_name can contain the full path of a key (like: "/Equipment/Trigger/Settings/Level1") while hkey is zero which refers to the root, or hkey can refer to a sub-directory (like /Equipment/Trigger) and key_name is interpreted relative to that directory like "Settings/Level1". \code INT level1; db_set_value(hDB, 0, "/Equipment/Trigger/Settings/Level1", &level1, sizeof(level1), 1, TID_INT32); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKeyRoot Handle for key where search starts, zero for root. @param key_name Name of key to search, can contain directories. @param data Address of data. @param data_size Size of data (in bytes). @param num_values Number of data elements. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types) @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_ACCESS, DB_TYPE_MISMATCH */ INT db_set_value(HNDLE hDB, HNDLE hKeyRoot, const char *key_name, const void *data, INT data_size, INT num_values, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_VALUE, hDB, hKeyRoot, key_name, data, data_size, num_values, type); #ifdef LOCAL_ROUTINES { INT status; if (num_values == 0) return DB_INVALID_PARAM; db_lock_database(hDB); DATABASE_HEADER* pheader = _database[hDB - 1].database_header; db_allow_write_locked(&_database[hDB-1], "db_set_value"); db_err_msg* msg = NULL; KEY* pkey_root = (KEY*)db_get_pkey(pheader, hKeyRoot, &status, "db_set_value", &msg); if (pkey_root) { status = db_set_value_wlocked(pheader, hDB, pkey_root, key_name, data, data_size, num_values, type, &msg); } db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } #ifdef LOCAL_ROUTINES static int db_set_value_wlocked(DATABASE_HEADER* pheader, HNDLE hDB, KEY* pkey_root, const char *key_name, const void *data, INT data_size, INT num_values, DWORD type, db_err_msg** msg) { INT status; if (num_values == 0) return DB_INVALID_PARAM; KEY* pkey = (KEY*)db_find_pkey_locked(pheader, pkey_root, key_name, &status, msg); if (!pkey) { status = db_create_key_wlocked(pheader, pkey_root, key_name, type, &pkey, msg); if (status != DB_SUCCESS && status != DB_CREATED) return status; } if (data_size == 0) { db_msg(msg, MERROR, "db_set_value", "zero data size not allowed"); return DB_TYPE_MISMATCH; } if (type != TID_STRING && type != TID_LINK && data_size != rpc_tid_size(type) * num_values) { db_msg(msg, MERROR, "db_set_value", "\"%s\" data_size %d does not match tid %d size %d times num_values %d", key_name, data_size, type, rpc_tid_size(type), num_values); return DB_TYPE_MISMATCH; } status = db_check_set_data_locked(pheader, pkey, data, data_size, num_values, type, "db_set_value", msg); if (status != DB_SUCCESS) return status; status = db_set_data_wlocked(pheader, pkey, data, data_size, num_values, type, "db_set_value", msg); if (status != DB_SUCCESS) return status; db_notify_clients_locked(pheader, hDB, db_pkey_to_hkey(pheader, pkey), -1, TRUE, msg); return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ /********************************************************************/ /** Set single value of an array. The function sets a single value of an ODB key which is an array. key_name can contain the full path of a key (like: "/Equipment/Trigger/Settings/Level1") while hkey is zero which refers to the root, or hkey can refer to a sub-directory (like /Equipment/Trigger) and key_name is interpreted relative to that directory like "Settings/Level1". \code INT level1; db_set_value_index(hDB, 0, "/Equipment/Trigger/Settings/Level1", &level1, sizeof(level1), 15, TID_INT32, FALSE); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKeyRoot Handle for key where search starts, zero for root. @param key_name Name of key to search, can contain directories. @param data Address of data. @param data_size Size of data (in bytes). @param index Array index of value. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types) @param truncate Truncate array to current index if TRUE @return \ */ INT db_set_value_index(HNDLE hDB, HNDLE hKeyRoot, const char *key_name, const void *data, INT data_size, INT idx, DWORD type, BOOL trunc) { int status; HNDLE hkey; status = db_find_key(hDB, hKeyRoot, key_name, &hkey); if (!hkey) { status = db_create_key(hDB, hKeyRoot, key_name, type); status = db_find_key(hDB, hKeyRoot, key_name, &hkey); if (status != DB_SUCCESS) return status; } else if (trunc) { status = db_set_num_values(hDB, hkey, idx + 1); if (status != DB_SUCCESS) return status; } return db_set_data_index(hDB, hkey, data, data_size, idx, type); } /********************************************************************/ /** Get value of a single key. The function returns single values or whole arrays which are contained in an ODB key. Since the data buffer is of type void, no type checking can be performed by the compiler. Therefore the type has to be explicitly supplied, which is checked against the type stored in the ODB. key_name can contain the full path of a key (like: "/Equipment/Trigger/Settings/Level1") while hkey is zero which refers to the root, or hkey can refer to a sub-directory (like: /Equipment/Trigger) and key_name is interpreted relative to that directory like "Settings/Level1". \code INT level1, size; size = sizeof(level1); db_get_value(hDB, 0, "/Equipment/Trigger/Settings/Level1", &level1, &size, TID_INT32, 0); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKeyRoot Handle for key where search starts, zero for root. @param key_name Name of key to search, can contain directories. @param data Address of data. @param buf_size Maximum buffer size on input, number of written bytes on return. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types) @param create If TRUE, create key if not existing @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_ACCESS, DB_TYPE_MISMATCH, DB_TRUNCATED, DB_NO_KEY */ INT db_get_value(HNDLE hDB, HNDLE hKeyRoot, const char *key_name, void *data, INT * buf_size, DWORD type, BOOL create) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_VALUE, hDB, hKeyRoot, key_name, data, buf_size, type, create); #ifdef LOCAL_ROUTINES { INT status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_value", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_value", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } /* check if key name contains index */ char keyname[MAX_ODB_PATH]; mstrlcpy(keyname, key_name, sizeof(keyname)); int idx = -1; if (strchr(keyname, '[') && strchr(keyname, ']')) { char* p; for (p = strchr(keyname, '[') + 1; *p && *p != ']'; p++) if (!isdigit(*p)) break; if (*p && *p == ']') { idx = atoi(strchr(keyname, '[') + 1); *strchr(keyname, '[') = 0; } } /* now lock database */ db_lock_database(hDB); DATABASE_HEADER* pheader = _database[hDB - 1].database_header; db_err_msg* msg = NULL; const KEY* pkey_root = db_get_pkey(pheader, hKeyRoot, &status, "db_get_value", &msg); if (!pkey_root) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } const KEY* pkey = db_find_pkey_locked(pheader, pkey_root, keyname, &status, &msg); if (!pkey) { if (create) { db_allow_write_locked(&_database[hDB-1], "db_get_value"); /* set default value if key was created */ /* get string size from data size */ int size; if (type == TID_STRING || type == TID_LINK) size = *buf_size; else size = rpc_tid_size(type); status = db_set_value_wlocked(pheader, hDB, (KEY*)pkey_root, keyname, data, *buf_size, *buf_size / size, type, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } else { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return DB_NO_KEY; } } status = db_get_data_locked(pheader, pkey, idx, data, buf_size, type, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } #ifdef LOCAL_ROUTINES static INT db_get_data_locked(DATABASE_HEADER* pheader, const KEY* pkey, int idx, void *data, INT * buf_size, DWORD type, db_err_msg** msg) { /* check for correct type */ if (pkey->type != (type)) { db_msg(msg, MERROR, "db_get_data_locked", "odb entry \"%s\" is of type %s, not %s", db_get_path_locked(pheader, pkey).c_str(), rpc_tid_name(pkey->type), rpc_tid_name(type)); return DB_TYPE_MISMATCH; } /* check for correct type */ if (pkey->type == TID_KEY) { db_msg(msg, MERROR, "db_get_data_locked", "odb entry \"%s\" is of type %s, cannot contain data", db_get_path_locked(pheader, pkey).c_str(), rpc_tid_name(pkey->type)); return DB_TYPE_MISMATCH; } /* check for read access */ if (!(pkey->access_mode & MODE_READ)) { db_msg(msg, MERROR, "db_get_data_locked", "odb entry \"%s\" has no read access", db_get_path_locked(pheader, pkey).c_str()); return DB_NO_ACCESS; } /* check if buffer is too small */ if ((idx == -1 && pkey->num_values * pkey->item_size > *buf_size) || (idx != -1 && pkey->item_size > *buf_size)) { memcpy(data, (char *) pheader + pkey->data, *buf_size); db_msg(msg, MERROR, "db_get_data_locked", "odb entry \"%s\" data truncated, size is %d (%d*%d), buffer size is only %d", db_get_path_locked(pheader, pkey).c_str(), pkey->num_values * pkey->item_size, pkey->num_values, pkey->item_size, *buf_size); return DB_TRUNCATED; } /* check if index in boundaries */ if (idx != -1 && idx >= pkey->num_values) { cm_msg(MERROR, "db_get_data_locked", "odb entry \"%s\" index %d is out of valid range 0..%d", db_get_path_locked(pheader, pkey).c_str(), idx, pkey->num_values-1); return DB_INVALID_PARAM; } /* copy key data */ if (idx == -1) { memcpy(data, (char *) pheader + pkey->data, pkey->num_values * pkey->item_size); *buf_size = pkey->num_values * pkey->item_size; } else { memcpy(data, (char *) pheader + pkey->data + idx * pkey->item_size, pkey->item_size); *buf_size = pkey->item_size; } return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ /********************************************************************/ /** Enumerate subkeys from a key, follow links. hkey must correspond to a valid ODB directory. The index is usually incremented in a loop until the last key is reached. Information about the sub-keys can be obtained with db_get_key(). If a returned key is of type TID_KEY, it contains itself sub-keys. To scan a whole ODB sub-tree, the function db_scan_tree() can be used. \code INT i; HNDLE hkey, hsubkey; KEY key; db_find_key(hdb, 0, "/Runinfo", &hkey); for (i=0 ; ; i++) { db_enum_key(hdb, hkey, i, &hsubkey); if (!hSubkey) break; // end of list reached // print key name db_get_key(hdb, hkey, &key); printf("%s\n", key.name); } \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param idx Subkey index, sould be initially 0, then incremented in each call until *subhKey becomes zero and the function returns DB_NO_MORE_SUBKEYS @param subkey_handle Handle of subkey which can be used in db_get_key() and db_get_data() @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_MORE_SUBKEYS */ INT db_enum_key(HNDLE hDB, HNDLE hKey, INT idx, HNDLE * subkey_handle) { if (rpc_is_remote()) return rpc_call(RPC_DB_ENUM_KEY, hDB, hKey, idx, subkey_handle); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; INT i; char str[256]; HNDLE parent; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_enum_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_enum_key", "invalid database handle"); return DB_INVALID_HANDLE; } *subkey_handle = 0; /* first lock database */ db_lock_database(hDB); pheader = _database[hDB - 1].database_header; if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } db_err_msg *msg = NULL; int status; const KEY* pkey = db_get_pkey(pheader, hKey, &status, "db_enum_key", &msg); if (!pkey) { db_unlock_database(hDB); db_flush_msg(&msg); return DB_NO_MORE_SUBKEYS; } if (pkey->type != TID_KEY) { db_unlock_database(hDB); return DB_NO_MORE_SUBKEYS; } const KEYLIST* pkeylist = db_get_pkeylist(pheader, hKey, pkey, "db_enum_key", &msg); if (!pkeylist) { db_unlock_database(hDB); db_flush_msg(&msg); return DB_NO_MORE_SUBKEYS; } if (idx >= pkeylist->num_keys) { db_unlock_database(hDB); return DB_NO_MORE_SUBKEYS; } pkey = db_get_pkey(pheader, pkeylist->first_key, &status, "db_enum_key", &msg); if (!pkey) { std::string path = db_get_path_locked(pheader, hKey); HNDLE xfirst_key = pkeylist->first_key; db_unlock_database(hDB); if (msg) db_flush_msg(&msg); cm_msg(MERROR, "db_enum_key", "hkey %d path \"%s\" invalid first_key %d", hKey, path.c_str(), xfirst_key); return DB_NO_MORE_SUBKEYS; } for (i = 0; i < idx; i++) { if (pkey->next_key == 0) { std::string path = db_get_path_locked(pheader, hKey); db_unlock_database(hDB); cm_msg(MERROR, "db_enum_key", "hkey %d path \"%s\" unexpected end of key list at index %d", hKey, path.c_str(), i); return DB_NO_MORE_SUBKEYS; } pkey = db_get_pkey(pheader, pkey->next_key, &status, "db_enum_key", &msg); if (!pkey) { std::string path = db_get_path_locked(pheader, hKey); db_unlock_database(hDB); db_flush_msg(&msg); cm_msg(MERROR, "db_enum_key", "hkey %d path \"%s\" invalid key list at index %d, next_key %d", hKey, path.c_str(), i, pkey->next_key); return DB_NO_MORE_SUBKEYS; } } /* resolve links */ if (pkey->type == TID_LINK) { strcpy(str, (char *) pheader + pkey->data); /* no not resolve if link to array index */ if (strlen(str) > 0 && str[strlen(str) - 1] == ']') { *subkey_handle = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); return DB_SUCCESS; } if (*str == '/') { /* absolute path */ db_unlock_database(hDB); return db_find_key(hDB, 0, str, subkey_handle); } else { /* relative path */ if (pkey->parent_keylist) { pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); parent = pkeylist->parent; db_unlock_database(hDB); return db_find_key(hDB, parent, str, subkey_handle); } else { db_unlock_database(hDB); return db_find_key(hDB, 0, str, subkey_handle); } } } *subkey_handle = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_enum_link(HNDLE hDB, HNDLE hKey, INT idx, HNDLE * subkey_handle) /********************************************************************\ Routine: db_enum_link Purpose: Enumerate subkeys from a key, don't follow links Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key to enumerate, zero for the root key INT idx Subkey index, sould be initially 0, then incremented in each call until *subhKey becomes zero and the function returns DB_NO_MORE_SUBKEYS Output: HNDLE *subkey_handle Handle of subkey which can be used in db_get_key and db_get_data Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_MORE_SUBKEYS Last subkey reached \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_ENUM_LINK, hDB, hKey, idx, subkey_handle); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEYLIST *pkeylist; KEY *pkey; INT i; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_enum_link", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_enum_link", "invalid database handle"); return DB_INVALID_HANDLE; } *subkey_handle = 0; /* first lock database */ db_lock_database(hDB); pheader = _database[hDB - 1].database_header; if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if (pkey->type != TID_KEY) { db_unlock_database(hDB); return DB_NO_MORE_SUBKEYS; } pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); if (idx >= pkeylist->num_keys) { db_unlock_database(hDB); return DB_NO_MORE_SUBKEYS; } // FIXME: validate pkeylist->first_key pkey = (KEY *) ((char *) pheader + pkeylist->first_key); for (i = 0; i < idx; i++) { // FIXME: validate pkey->next_key pkey = (KEY *) ((char *) pheader + pkey->next_key); } *subkey_handle = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_get_next_link(HNDLE hDB, HNDLE hKey, HNDLE * subkey_handle) /********************************************************************\ Routine: db_get_next_link Purpose: Get next key in ODB after hKey Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key to enumerate, zero for the root key Output: HNDLE *subkey_handle Handle of subkey which can be used in db_get_key and db_get_data Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_MORE_SUBKEYS Last subkey reached \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_NEXT_LINK, hDB, hKey, subkey_handle); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEYLIST *pkeylist; KEY *pkey; INT descent; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_enum_link", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_enum_link", "invalid database handle"); return DB_INVALID_HANDLE; } *subkey_handle = 0; /* first lock database */ db_lock_database(hDB); pheader = _database[hDB - 1].database_header; if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); descent = TRUE; do { if (pkey->type != TID_KEY || !descent) { if (pkey->next_key) { /* key has next key, return it */ // FIXME: validate pkey->next_key pkey = (KEY *) ((char *) pheader + pkey->next_key); if (pkey->type != TID_KEY) { *subkey_handle = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); return DB_SUCCESS; } /* key has subkeys, so descent on the next iteration */ descent = TRUE; } else { if (pkey->parent_keylist == 0) { /* return if we are back to the root key */ db_unlock_database(hDB); return DB_NO_MORE_SUBKEYS; } /* key is last in list, traverse up */ pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); descent = FALSE; } } else { if (descent) { /* find first subkey */ pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); if (pkeylist->num_keys == 0) { /* if key has no subkeys, look for next key on this level */ descent = FALSE; } else { /* get first subkey */ // FIXME: validate pkeylist->first_key pkey = (KEY *) ((char *) pheader + pkeylist->first_key); if (pkey->type != TID_KEY) { *subkey_handle = (POINTER_T) pkey - (POINTER_T) pheader; db_unlock_database(hDB); return DB_SUCCESS; } } } } } while (TRUE); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ #ifdef LOCAL_ROUTINES static INT db_get_key_locked(const DATABASE_HEADER* pheader, HNDLE hKey, KEY * key, db_err_msg** msg) { if (!hKey) hKey = pheader->root_key; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { return DB_INVALID_HANDLE; } const KEY* pkey = (const KEY *) ((char *) pheader + hKey); if (pkey->type < 1 || pkey->type >= TID_LAST) { int pkey_type = pkey->type; db_msg(msg, MERROR, "db_get_key", "hkey %d invalid key type %d", hKey, pkey_type); return DB_INVALID_HANDLE; } /* check for link to array index */ if (pkey->type == TID_LINK) { char link_name[MAX_ODB_PATH]; mstrlcpy(link_name, (char *) pheader + pkey->data, sizeof(link_name)); if (strlen(link_name) > 0 && link_name[strlen(link_name) - 1] == ']') { if (strchr(link_name, '[') == NULL) return DB_INVALID_LINK; HNDLE hkeylink; if (db_find_key_locked(pheader, 0, link_name, &hkeylink, msg) != DB_SUCCESS) return DB_INVALID_LINK; int status = db_get_key_locked(pheader, hkeylink, key, msg); key->num_values = 1; // fake number of values return status; } } memcpy(key, pkey, sizeof(KEY)); return DB_SUCCESS; } #endif /* LOCAL_ROUTINES */ /********************************************************************/ /** Get key structure from a handle. KEY structure has following format: \code typedef struct { DWORD type; // TID_xxx type INT num_values; // number of values char name[NAME_LENGTH]; // name of variable INT data; // Address of variable (offset) INT total_size; // Total size of data block INT item_size; // Size of single data item WORD access_mode; // Access mode WORD notify_count; // Notify counter INT next_key; // Address of next key INT parent_keylist; // keylist to which this key belongs INT last_written; // Time of last write action } KEY; \endcode Most of these values are used for internal purposes, the values which are of public interest are type, name, num_values, item_size and total_size. For keys which contain a single value, num_values equals to one and total_size equals to item_size. For keys which contain an array of strings (TID_STRING), item_size equals to the length of one string. \code KEY key; HNDLE hkey; db_find_key(hDB, 0, "/Runinfo/Run number", &hkey); db_get_key(hDB, hkey, &key); printf("The run number is of type %s\n", rpc_tid_name(key.type)); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. If Key is a link to an array element, this link is resolved. In this case function returns the key of the link destination and num_values is set to 1. @param key Pointer to KEY stucture. @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_get_key(HNDLE hDB, HNDLE hKey, KEY * key) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_KEY, hDB, hKey, key); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER) && hKey != 0) { cm_msg(MERROR, "db_get_key", "invalid key handle"); return DB_INVALID_HANDLE; } db_err_msg *msg = NULL; db_lock_database(hDB); pheader = _database[hDB - 1].database_header; status = db_get_key_locked(pheader, hKey, key, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Same as db_get_key, but it does not follow links @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param key Pointer to KEY stucture. @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_get_link(HNDLE hDB, HNDLE hKey, KEY * key) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_LINK, hDB, hKey, key); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_link", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_link", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER) && hKey != 0) { cm_msg(MERROR, "db_get_link", "invalid key handle"); return DB_INVALID_HANDLE; } db_err_msg *msg = NULL; db_lock_database(hDB); pheader = _database[hDB - 1].database_header; int status = DB_SUCCESS; const KEY* pkey = db_get_pkey(pheader, hKey, &status, "db_get_link", &msg); if (pkey) { memcpy(key, pkey, sizeof(KEY)); } else { memset(key, 0, sizeof(KEY)); //abort(); } db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Get time when key was last modified @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle of key to operate on @param delta Seconds since last update @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_get_key_time(HNDLE hDB, HNDLE hKey, DWORD * delta) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_KEY_TIME, hDB, hKey, delta); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_key", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); *delta = ss_time() - pkey->last_written; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Get key info (separate values instead of structure) @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle of key to operate on @param name Key name @param name_size Size of the give name (done with sizeof()) @param type Key type (see @ref Midas_Data_Types). @param num_values Number of values in key. @param item_size Size of individual key value (used for strings) @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_get_key_info(HNDLE hDB, HNDLE hKey, char *name, INT name_size, INT * type, INT * num_values, INT * item_size) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_KEY_INFO, hDB, hKey, name, name_size, type, num_values, item_size); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; KEYLIST *pkeylist; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_key_info", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_key_info", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_key_info", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if ((INT) strlen(pkey->name) + 1 > name_size) { /* truncate name */ memcpy(name, pkey->name, name_size - 1); name[name_size] = 0; } else strcpy(name, pkey->name); /* convert "root" to "/" */ if (strcmp(name, "root") == 0) strcpy(name, "/"); *type = pkey->type; *num_values = pkey->num_values; *item_size = pkey->item_size; if (pkey->type == TID_KEY) { pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); *num_values = pkeylist->num_keys; } db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_rename_key(HNDLE hDB, HNDLE hKey, const char *name) /********************************************************************\ Routine: db_get_key Purpose: Rename a key Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key char *name New key name Output: Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_INVALID_NAME Key name contains '/' \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_RENAME_KEY, hDB, hKey, name); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_rename_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_rename_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_rename_key", "invalid key handle"); return DB_INVALID_HANDLE; } db_err_msg* msg = NULL; status = db_validate_name(name, FALSE, "db_rename_key", &msg); if (msg) db_flush_msg(&msg); if (status != DB_SUCCESS) return status; if (name == NULL) { cm_msg(MERROR, "db_rename_key", "key name is NULL"); return DB_INVALID_NAME; } if (strlen(name) < 1) { cm_msg(MERROR, "db_rename_key", "key name is too short"); return DB_INVALID_NAME; } if (strchr(name, '/')) { cm_msg(MERROR, "db_rename_key", "key name may not contain \"/\""); return DB_INVALID_NAME; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if (!pkey->type) { int pkey_type = pkey->type; db_unlock_database(hDB); cm_msg(MERROR, "db_rename_key", "hkey %d invalid key type %d", hKey, pkey_type); return DB_INVALID_HANDLE; } db_allow_write_locked(&_database[hDB - 1], "db_rename_key"); mstrlcpy(pkey->name, name, NAME_LENGTH); db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_reorder_key(HNDLE hDB, HNDLE hKey, INT idx) /********************************************************************\ Routine: db_reorder_key Purpose: Reorder key so that key hKey apprears at position 'index' in keylist (or at bottom if index<0) Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key INT idx New positio of key in keylist Output: Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_NO_ACCESS Key is locked for write DB_OPEN_RECORD Key, subkey or parent key is open \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_REORDER_KEY, hDB, hKey, idx); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey, *pnext_key, *pkey_tmp; KEYLIST *pkeylist; INT i; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_rename_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_rename_key", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_rename_key", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); if (!pkey->type) { int pkey_type = pkey->type; db_unlock_database(hDB); cm_msg(MERROR, "db_reorder_key", "hkey %d invalid key type %d", hKey, pkey_type); return DB_INVALID_HANDLE; } if (!(pkey->access_mode & MODE_WRITE)) { db_unlock_database(hDB); return DB_NO_ACCESS; } /* check if someone has opened key or parent */ do { #ifdef CHECK_OPEN_RECORD if (pkey->notify_count) { db_unlock_database(hDB); return DB_OPEN_RECORD; } #endif if (pkey->parent_keylist == 0) break; pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); // FIXME: validate pkeylist->parent pkey = (KEY *) ((char *) pheader + pkeylist->parent); } while (TRUE); db_allow_write_locked(&_database[hDB - 1], "db_reorder_key"); pkey = (KEY *) ((char *) pheader + hKey); // NB: hKey is already validated pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); /* first remove key from list */ pnext_key = (KEY *) (POINTER_T) pkey->next_key; // FIXME: what is this pointer cast? if ((KEY *) ((char *) pheader + pkeylist->first_key) == pkey) { /* key is first in list */ pkeylist->first_key = (POINTER_T) pnext_key; } else { /* find predecessor */ // FIXME: validate pkeylist->first_key pkey_tmp = (KEY *) ((char *) pheader + pkeylist->first_key); while ((KEY *) ((char *) pheader + pkey_tmp->next_key) != pkey) { // FIXME: validate pkey_tmp->next_key pkey_tmp = (KEY *) ((char *) pheader + pkey_tmp->next_key); } pkey_tmp->next_key = (POINTER_T) pnext_key; } /* add key to list at proper index */ // FIXME: validate pkeylist->first_key pkey_tmp = (KEY *) ((char *) pheader + pkeylist->first_key); if (idx < 0 || idx >= pkeylist->num_keys - 1) { /* add at bottom */ /* find last key */ for (i = 0; i < pkeylist->num_keys - 2; i++) { // FIXME: validate pkey_tmp->next_key pkey_tmp = (KEY *) ((char *) pheader + pkey_tmp->next_key); } pkey_tmp->next_key = (POINTER_T) pkey - (POINTER_T) pheader; pkey->next_key = 0; } else { if (idx == 0) { /* add at top */ pkey->next_key = pkeylist->first_key; pkeylist->first_key = (POINTER_T) pkey - (POINTER_T) pheader; } else { /* add at position index */ for (i = 0; i < idx - 1; i++) { // FIXME: validate pkey_tmp->next_key pkey_tmp = (KEY *) ((char *) pheader + pkey_tmp->next_key); } pkey->next_key = pkey_tmp->next_key; pkey_tmp->next_key = (POINTER_T) pkey - (POINTER_T) pheader; } } db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Get key data from a handle The function returns single values or whole arrays which are contained in an ODB key. Since the data buffer is of type void, no type checking can be performed by the compiler. Therefore the type has to be explicitly supplied, which is checked against the type stored in the ODB. \code HNLDE hkey; INT run_number, size; // get key handle for run number db_find_key(hDB, 0, "/Runinfo/Run number", &hkey); // return run number size = sizeof(run_number); db_get_data(hDB, hkey, &run_number, &size,TID_INT32); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to the return data. @param buf_size Size of data buffer. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TRUNCATED, DB_TYPE_MISMATCH */ INT db_get_data(HNDLE hDB, HNDLE hKey, void *data, INT * buf_size, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_DATA, hDB, hKey, data, buf_size, type); #ifdef LOCAL_ROUTINES { int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_data", "Invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_data", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); DATABASE_HEADER* pheader = _database[hDB - 1].database_header; db_err_msg* msg = NULL; const KEY* pkey = db_get_pkey(pheader, hKey, &status, "db_get_data", &msg); if (!pkey) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } /* check for read access */ if (!(pkey->access_mode & MODE_READ)) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return DB_NO_ACCESS; } /* follow links to array index */ if (pkey->type == TID_LINK) { std::string link_name = (char *) pheader + pkey->data; if (link_name.length() > 0 && link_name.back() == ']') { size_t pos = link_name.rfind("["); if (pos == std::string::npos) { db_msg(&msg, MERROR, "db_get_data", "missing \"[\" in symlink to array element \"%s\" in \"%s\"", link_name.c_str(), db_get_path_locked(pheader, pkey).c_str()); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return DB_INVALID_LINK; } int idx = atoi(link_name.c_str()+pos+1); link_name.resize(pos); //printf("link name [%s] idx %d\n", link_name.c_str(), idx); // relative symlinks did not work in the old db_get_data(), make sure they do not work now. K.O. if (link_name[0] != '/') { db_msg(&msg, MERROR, "db_get_data", "symlink \"%s\" should start with \"/\" in \"%s\"", link_name.c_str(), db_get_path_locked(pheader, pkey).c_str()); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return DB_INVALID_LINK; } const KEY* pkey = db_find_pkey_locked(pheader, NULL, link_name.c_str(), &status, &msg); if (!pkey) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } //printf("db_get_data [%s] type [%s] idx %d\n", db_get_path_locked(pheader, pkey).c_str(), rpc_tid_name(type), idx); status = db_get_data_locked(pheader, pkey, idx, data, buf_size, type, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } } //printf("db_get_data [%s] type [%s]\n", db_get_path_locked(pheader, pkey).c_str(), rpc_tid_name(type)); status = db_get_data_locked(pheader, pkey, -1, data, buf_size, type, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Same as db_get_data, but do not follow a link to an array index @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to the return data. @param buf_size Size of data buffer. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TRUNCATED, DB_TYPE_MISMATCH */ INT db_get_link_data(HNDLE hDB, HNDLE hKey, void *data, INT * buf_size, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_LINK_DATA, hDB, hKey, data, buf_size, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_data", "Invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_data", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for read access */ if (!(pkey->access_mode & MODE_READ)) { db_unlock_database(hDB); return DB_NO_ACCESS; } if (!pkey->type) { int pkey_type = pkey->type; db_unlock_database(hDB); cm_msg(MERROR, "db_get_data", "hkey %d invalid key type %d", hKey, pkey_type); return DB_INVALID_HANDLE; } if (pkey->type != type) { int pkey_type = pkey->type; char pkey_name[NAME_LENGTH]; mstrlcpy(pkey_name, pkey->name, sizeof(pkey_name)); db_unlock_database(hDB); cm_msg(MERROR, "db_get_data", "\"%s\" is of type %s, not %s", pkey_name, rpc_tid_name(pkey_type), rpc_tid_name(type)); return DB_TYPE_MISMATCH; } /* keys cannot contain data */ if (pkey->type == TID_KEY) { db_unlock_database(hDB); cm_msg(MERROR, "db_get_data", "Key cannot contain data"); return DB_TYPE_MISMATCH; } /* check if key has data */ if (pkey->data == 0) { memset(data, 0, *buf_size); *buf_size = 0; db_unlock_database(hDB); return DB_SUCCESS; } /* check if buffer is too small */ if (pkey->num_values * pkey->item_size > *buf_size) { int pkey_size = pkey->num_values * pkey->item_size; memcpy(data, (char *) pheader + pkey->data, *buf_size); db_unlock_database(hDB); std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_get_data", "data for key \"%s\" truncated from %d to %d bytes", path.c_str(), pkey_size, *buf_size); return DB_TRUNCATED; } /* copy key data */ memcpy(data, (char *) pheader + pkey->data, pkey->num_values * pkey->item_size); *buf_size = pkey->num_values * pkey->item_size; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_get_data1(HNDLE hDB, HNDLE hKey, void *data, INT * buf_size, DWORD type, INT * num_values) /********************************************************************\ Routine: db_get_data1 Purpose: Get key data from a handle, return number of values Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key INT *buf_size Size of data buffer DWORD type Type of data Output: void *data Key data INT *buf_size Size of key data INT *num_values Number of values Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_TRUNCATED Return buffer is smaller than key data DB_TYPE_MISMATCH Type mismatch \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_DATA1, hDB, hKey, data, buf_size, type, num_values); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_data", "Invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_data", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for read access */ if (!(pkey->access_mode & MODE_READ)) { db_unlock_database(hDB); return DB_NO_ACCESS; } if (!pkey->type) { int pkey_type = pkey->type; db_unlock_database(hDB); cm_msg(MERROR, "db_get_data", "hkey %d invalid key type %d", hKey, pkey_type); return DB_INVALID_HANDLE; } if (pkey->type != type) { int pkey_type = pkey->type; char pkey_name[NAME_LENGTH]; mstrlcpy(pkey_name, pkey->name, sizeof(pkey_name)); db_unlock_database(hDB); cm_msg(MERROR, "db_get_data", "\"%s\" is of type %s, not %s", pkey_name, rpc_tid_name(pkey_type), rpc_tid_name(type)); return DB_TYPE_MISMATCH; } /* keys cannot contain data */ if (pkey->type == TID_KEY) { db_unlock_database(hDB); cm_msg(MERROR, "db_get_data", "Key cannot contain data"); return DB_TYPE_MISMATCH; } /* check if key has data */ if (pkey->data == 0) { memset(data, 0, *buf_size); *buf_size = 0; db_unlock_database(hDB); return DB_SUCCESS; } /* check if buffer is too small */ if (pkey->num_values * pkey->item_size > *buf_size) { int pkey_size = pkey->num_values * pkey->item_size; memcpy(data, (char *) pheader + pkey->data, *buf_size); db_unlock_database(hDB); std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_get_data", "data for key \"%s\" truncated from %d to %d bytes", path.c_str(), pkey_size, *buf_size); return DB_TRUNCATED; } /* copy key data */ memcpy(data, (char *) pheader + pkey->data, pkey->num_values * pkey->item_size); *buf_size = pkey->num_values * pkey->item_size; *num_values = pkey->num_values; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** returns a single value of keys containing arrays of values. The function returns a single value of keys containing arrays of values. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Size of data buffer. @param buf_size Return size of the record. @param idx Index of array [0..n-1]. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TRUNCATED, DB_OUT_OF_RANGE */ INT db_get_data_index(HNDLE hDB, HNDLE hKey, void *data, INT * buf_size, INT idx, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_GET_DATA_INDEX, hDB, hKey, data, buf_size, idx, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_get_data", "Invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_get_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_get_data", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for read access */ if (!(pkey->access_mode & MODE_READ)) { db_unlock_database(hDB); return DB_NO_ACCESS; } if (!pkey->type) { int pkey_type = pkey->type; db_unlock_database(hDB); cm_msg(MERROR, "db_get_data_index", "hkey %d invalid key type %d", hKey, pkey_type); return DB_INVALID_HANDLE; } if (pkey->type != type) { int pkey_type = pkey->type; char pkey_name[NAME_LENGTH]; mstrlcpy(pkey_name, pkey->name, sizeof(pkey_name)); db_unlock_database(hDB); cm_msg(MERROR, "db_get_data_index", "\"%s\" is of type %s, not %s", pkey_name, rpc_tid_name(pkey_type), rpc_tid_name(type)); return DB_TYPE_MISMATCH; } /* keys cannot contain data */ if (pkey->type == TID_KEY) { db_unlock_database(hDB); cm_msg(MERROR, "db_get_data_index", "Key cannot contain data"); return DB_TYPE_MISMATCH; } /* check if key has data */ if (pkey->data == 0) { memset(data, 0, *buf_size); *buf_size = 0; db_unlock_database(hDB); return DB_SUCCESS; } /* check if index in range */ if (idx < 0 || idx >= pkey->num_values) { int pkey_num_values = pkey->num_values; memset(data, 0, *buf_size); db_unlock_database(hDB); std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_get_data_index", "index (%d) exceeds array length (%d) for key \"%s\"", idx, pkey_num_values, path.c_str()); return DB_OUT_OF_RANGE; } /* check if buffer is too small */ if (pkey->item_size > *buf_size) { int pkey_size = pkey->item_size; /* copy data */ memcpy(data, (char *) pheader + pkey->data + idx * pkey->item_size, *buf_size); db_unlock_database(hDB); std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_get_data_index", "data for key \"%s\" truncated from %d to %d bytes", path.c_str(), pkey_size, *buf_size); return DB_TRUNCATED; } /* copy key data */ memcpy(data, (char *) pheader + pkey->data + idx * pkey->item_size, pkey->item_size); *buf_size = pkey->item_size; db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } #ifdef LOCAL_ROUTINES /********************************************************************/ /** Set key data, adjust number of values if previous data has different size. @param pkey Key to change @param idx Data index to change, "-1" means the whole array of data @param data Buffer from which data gets copied to. @param data_size Size of data buffer. @param num_values Number of data values (for arrays). @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_FULL */ static INT db_set_data_wlocked(DATABASE_HEADER* pheader, KEY* pkey, const void *data, INT data_size, INT num_values, DWORD type, const char* caller, db_err_msg** msg) { /* if no buf_size given (Java!), calculate it */ if (data_size == 0) data_size = pkey->item_size * num_values; /* resize data size if necessary */ if (pkey->total_size != data_size) { // FIXME: validate pkey->data! pkey->data = (POINTER_T) realloc_data(pheader, (char *) pheader + pkey->data, pkey->total_size, data_size, caller); if (pkey->data == 0) { pkey->total_size = 0; db_msg(msg, MERROR, caller, "Cannot reallocate \"%s\" with new size %d bytes, online database full", db_get_path_locked(pheader, pkey).c_str(), data_size); return DB_FULL; } pkey->data -= (POINTER_T) pheader; pkey->total_size = data_size; } /* set number of values */ pkey->num_values = num_values; if (type == TID_STRING || type == TID_LINK) pkey->item_size = data_size / num_values; else pkey->item_size = rpc_tid_size(type); if ((type == TID_STRING || type == TID_LINK) && pkey->num_values == 1) { /* copy string up to NUL termination */ mstrlcpy((char *) pheader + pkey->data, (const char*)data, data_size); } else { /* copy data */ memcpy((char *) pheader + pkey->data, data, data_size); } /* update time */ pkey->last_written = ss_time(); return DB_SUCCESS; } static INT db_set_data_index_wlocked(DATABASE_HEADER* pheader, KEY* pkey, int idx, const void *data, INT data_size, DWORD type, const char* caller, db_err_msg** msg) { /* increase key size if necessary */ if (idx >= pkey->num_values || pkey->item_size == 0) { // FIXME: validate pkey->data pkey->data = (POINTER_T) realloc_data(pheader, (char *) pheader + pkey->data, pkey->total_size, data_size * (idx + 1), caller); if (pkey->data == 0) { pkey->total_size = 0; pkey->num_values = 0; db_msg(msg, MERROR, caller, "Cannot reallocate \"%s\" with new num_values %d and new size %d bytes, online database full", db_get_path_locked(pheader, pkey).c_str(), idx + 1, data_size * (idx + 1)); return DB_FULL; } pkey->data -= (POINTER_T) pheader; if (!pkey->item_size) pkey->item_size = data_size; pkey->total_size = data_size * (idx + 1); pkey->num_values = idx + 1; } #if 0 /* cut strings which are too long */ if ((type == TID_STRING || type == TID_LINK) && (int) strlen((char *) data) + 1 > pkey->item_size) *((char *) data + pkey->item_size - 1) = 0; /* copy data */ memcpy((char *) pheader + pkey->data + idx * pkey->item_size, data, pkey->item_size); #endif if ((type == TID_STRING || type == TID_LINK)) { /* cut strings which are too long */ mstrlcpy((char *) pheader + pkey->data + idx * pkey->item_size, (char*)data, pkey->item_size); } else { /* copy data */ memcpy((char *) pheader + pkey->data + idx * pkey->item_size, data, pkey->item_size); } /* update time */ pkey->last_written = ss_time(); return DB_SUCCESS; } static INT db_check_set_data_locked(DATABASE_HEADER* pheader, const KEY* pkey, const void *data, INT data_size, INT num_values, DWORD type, const char* caller, db_err_msg** msg) { /* check for write access */ if (!(pkey->access_mode & MODE_WRITE) || (pkey->access_mode & MODE_EXCLUSIVE)) { return DB_NO_ACCESS; } if (pkey->type != type) { db_msg(msg, MERROR, caller, "\"%s\" is of type %s, not %s", db_get_path_locked(pheader, pkey).c_str(), rpc_tid_name(pkey->type), rpc_tid_name(type)); return DB_TYPE_MISMATCH; } /* keys cannot contain data */ if (pkey->type == TID_KEY) { db_msg(msg, MERROR, caller, "\"%s\" of type TID_KEY cannot contain data", db_get_path_locked(pheader, pkey).c_str()); return DB_TYPE_MISMATCH; } if (type == TID_STRING || type == TID_LINK) { if (num_values > 1) { int item_size = pkey->item_size; if (data_size > 0 && num_values > 0) item_size = data_size/num_values; //printf("db_check_set_data for %s: utf8 check for odb \"%s\" string array size %d, item size %d\n", caller, db_get_path_locked(pheader, pkey).c_str(), num_values, item_size); for (int i=0; iaccess_mode & MODE_WRITE) || (pkey->access_mode & MODE_EXCLUSIVE)) { return DB_NO_ACCESS; } if (pkey->type != type) { db_msg(msg, MERROR, caller, "\"%s\" is of type %s, not %s", db_get_path_locked(pheader, pkey).c_str(), rpc_tid_name(pkey->type), rpc_tid_name(type)); return DB_TYPE_MISMATCH; } /* keys cannot contain data */ if (pkey->type == TID_KEY) { db_msg(msg, MERROR, "db_set_data_index", "\"%s\" of type TID_KEY cannot contain data", db_get_path_locked(pheader, pkey).c_str()); return DB_TYPE_MISMATCH; } /* check utf-8 encoding */ if (pkey->type == TID_STRING || pkey->type == TID_LINK) { //printf("db_check_set_data_index for %s: utf8 check for odb \"%s\" value \"%s\"\n", caller, db_get_path_locked(pheader, pkey).c_str(), data); const char* value = (const char*)data; if (!is_utf8(value)) { db_msg(msg, MERROR, "db_set_data_index", "\"%s\" index %d set to invalid UTF-8 Unicode string value \"%s\"", db_get_path_locked(pheader, pkey).c_str(), idx, value); // just a warning for now. K.O. //return DB_TYPE_MISMATCH; } } /* check for valid idx */ if (idx < 0) { db_msg(msg, MERROR, caller, "\%s\" given invalid index %d", db_get_path_locked(pheader, pkey).c_str(), idx); return DB_INVALID_PARAM; } /* check for valid array element size: if new element size is different from existing size, ODB becomes corrupted */ if (pkey->item_size != 0 && data_size != pkey->item_size) { db_msg(msg, MERROR, caller, "\"%s\" invalid element data size %d, expected %d", db_get_path_locked(pheader, pkey).c_str(), data_size, pkey->item_size); return DB_TYPE_MISMATCH; } return DB_SUCCESS; } #endif // LOCAL_ROUTINES /********************************************************************/ /** Set key data from a handle. Adjust number of values if previous data has different size. \code HNLDE hkey; INT run_number; // get key handle for run number db_find_key(hDB, 0, "/Runinfo/Run number", &hkey); // set run number db_set_data(hDB, hkey, &run_number, sizeof(run_number),TID_INT32); \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Buffer from which data gets copied to. @param buf_size Size of data buffer. @param num_values Number of data values (for arrays). @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TRUNCATED */ INT db_set_data(HNDLE hDB, HNDLE hKey, const void *data, INT buf_size, INT num_values, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_DATA, hDB, hKey, data, buf_size, num_values, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; HNDLE hkeylink; int link_idx; char link_name[256]; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_data", "invalid key handle"); return DB_INVALID_HANDLE; } if (num_values == 0) return DB_INVALID_PARAM; db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for write access */ if (!(pkey->access_mode & MODE_WRITE) || (pkey->access_mode & MODE_EXCLUSIVE)) { db_unlock_database(hDB); return DB_NO_ACCESS; } /* check for link to array index */ if (pkey->type == TID_LINK) { mstrlcpy(link_name, (char *) pheader + pkey->data, sizeof(link_name)); if (strlen(link_name) > 0 && link_name[strlen(link_name) - 1] == ']') { db_unlock_database(hDB); if (strchr(link_name, '[') == NULL) return DB_INVALID_LINK; link_idx = atoi(strchr(link_name, '[') + 1); *strchr(link_name, '[') = 0; if (db_find_key(hDB, 0, link_name, &hkeylink) != DB_SUCCESS) return DB_INVALID_LINK; return db_set_data_index(hDB, hkeylink, data, buf_size, link_idx, type); } } status = db_check_set_data_locked(pheader, pkey, data, buf_size, num_values, type, "db_set_data", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_allow_write_locked(&_database[hDB-1], "db_set_data"); status = db_set_data_wlocked(pheader, pkey, data, buf_size, num_values, type, "db_set_data", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_notify_clients_locked(pheader, hDB, hKey, -1, TRUE, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } INT db_set_data1(HNDLE hDB, HNDLE hKey, const void *data, INT buf_size, INT num_values, DWORD type) /* Same as db_set_data(), but do not notify hot-linked clients */ { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_DATA1, hDB, hKey, data, buf_size, num_values, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; HNDLE hkeylink; int link_idx; char link_name[256]; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_data1", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_data1", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_data1", "invalid key handle"); return DB_INVALID_HANDLE; } if (num_values == 0) return DB_INVALID_PARAM; db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for write access */ if (!(pkey->access_mode & MODE_WRITE) || (pkey->access_mode & MODE_EXCLUSIVE)) { db_unlock_database(hDB); return DB_NO_ACCESS; } /* check for link to array index */ if (pkey->type == TID_LINK) { mstrlcpy(link_name, (char *) pheader + pkey->data, sizeof(link_name)); if (strlen(link_name) > 0 && link_name[strlen(link_name) - 1] == ']') { db_unlock_database(hDB); if (strchr(link_name, '[') == NULL) return DB_INVALID_LINK; link_idx = atoi(strchr(link_name, '[') + 1); *strchr(link_name, '[') = 0; if (db_find_key(hDB, 0, link_name, &hkeylink) != DB_SUCCESS) return DB_INVALID_LINK; return db_set_data_index1(hDB, hkeylink, data, buf_size, link_idx, type, FALSE); } } status = db_check_set_data_locked(pheader, pkey, data, buf_size, num_values, type, "db_set_data1", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_allow_write_locked(&_database[hDB - 1], "db_set_data1"); status = db_set_data_wlocked(pheader, pkey, data, buf_size, num_values, type, "db_set_data1", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Same as db_set_data, but it does not follow a link to an array index @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Buffer from which data gets copied to. @param buf_size Size of data buffer. @param num_values Number of data values (for arrays). @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TRUNCATED */ INT db_set_link_data(HNDLE hDB, HNDLE hKey, const void *data, INT buf_size, INT num_values, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_LINK_DATA, hDB, hKey, data, buf_size, num_values, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_data", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_data", "invalid key handle"); return DB_INVALID_HANDLE; } if (num_values == 0) return DB_INVALID_PARAM; db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); status = db_check_set_data_locked(pheader, pkey, data, buf_size, num_values, type, "db_set_link_data", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_allow_write_locked(&_database[hDB - 1], "db_set_link_data"); status = db_set_data_wlocked(pheader, pkey, data, buf_size, num_values, type, "db_set_link_data", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_notify_clients_locked(pheader, hDB, hKey, -1, TRUE, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_set_num_values(HNDLE hDB, HNDLE hKey, INT num_values) /********************************************************************\ Routine: db_set_num_values Purpose: Set number of values in a key. Extend with zeros or truncate. Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key INT num_values Number of data values Output: none Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_NUM_VALUES, hDB, hKey, num_values); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; INT new_size; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_num_values", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_num_values", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_num_values", "invalid key handle"); return DB_INVALID_HANDLE; } if (num_values <= 0) { cm_msg(MERROR, "db_set_num_values", "invalid num_values %d", num_values); return DB_INVALID_PARAM; } if (num_values == 0) return DB_INVALID_PARAM; db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for write access */ if (!(pkey->access_mode & MODE_WRITE) || (pkey->access_mode & MODE_EXCLUSIVE)) { db_unlock_database(hDB); return DB_NO_ACCESS; } /* keys cannot contain data */ if (pkey->type == TID_KEY) { db_unlock_database(hDB); cm_msg(MERROR, "db_set_num_values", "Key cannot contain data"); return DB_TYPE_MISMATCH; } if (pkey->total_size != pkey->item_size * pkey->num_values) { db_unlock_database(hDB); cm_msg(MERROR, "db_set_num_values", "Corrupted key"); return DB_CORRUPTED; } if (pkey->item_size == 0) { db_unlock_database(hDB); cm_msg(MERROR, "db_set_num_values", "Cannot resize array with item_size equal to zero"); return DB_INVALID_PARAM; } db_allow_write_locked(&_database[hDB - 1], "db_set_num_values"); /* resize data size if necessary */ if (pkey->num_values != num_values) { new_size = pkey->item_size * num_values; pkey->data = (POINTER_T) realloc_data(pheader, (char *) pheader + pkey->data, pkey->total_size, new_size, "db_set_num_values"); if (pkey->data == 0) { pkey->total_size = 0; pkey->num_values = 0; db_msg(&msg, MERROR, "db_set_num_values", "Cannot resize \"%s\" with num_values %d and new size %d bytes, online database full", db_get_path_locked(pheader, pkey).c_str(), num_values, new_size); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return DB_FULL; } pkey->data -= (POINTER_T) pheader; pkey->total_size = new_size; pkey->num_values = num_values; } /* update time */ pkey->last_written = ss_time(); db_notify_clients_locked(pheader, hDB, hKey, -1, TRUE, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Set key data for a key which contains an array of values. This function sets individual values of a key containing an array. If the index is larger than the array size, the array is extended and the intermediate values are set to zero. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to single value of data. @param data_size @param idx Size of single data element. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_ACCESS, DB_TYPE_MISMATCH */ INT db_set_data_index(HNDLE hDB, HNDLE hKey, const void *data, INT data_size, INT idx, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_DATA_INDEX, hDB, hKey, data, data_size, idx, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; char link_name[256]; int link_idx; HNDLE hkeylink; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_data_index", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_data_index", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_data_index", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); /* check for write access */ if (!(pkey->access_mode & MODE_WRITE) || (pkey->access_mode & MODE_EXCLUSIVE)) { db_unlock_database(hDB); return DB_NO_ACCESS; } /* check for link to array index */ if (pkey->type == TID_LINK) { mstrlcpy(link_name, (char *) pheader + pkey->data, sizeof(link_name)); if (strlen(link_name) > 0 && link_name[strlen(link_name) - 1] == ']') { db_unlock_database(hDB); if (strchr(link_name, '[') == NULL) return DB_INVALID_LINK; link_idx = atoi(strchr(link_name, '[') + 1); *strchr(link_name, '[') = 0; if (db_find_key(hDB, 0, link_name, &hkeylink) != DB_SUCCESS) return DB_INVALID_LINK; return db_set_data_index(hDB, hkeylink, data, data_size, link_idx, type); } } status = db_check_set_data_index_locked(pheader, pkey, idx, data, data_size, type, "db_set_data_index", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_allow_write_locked(&_database[hDB-1], "db_set_data_index"); status = db_set_data_index_wlocked(pheader, pkey, idx, data, data_size, type, "db_set_data_index", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_notify_clients_locked(pheader, hDB, hKey, idx, TRUE, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Same as db_set_data_index, but does not follow links. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to single value of data. @param data_size @param idx Size of single data element. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_ACCESS, DB_TYPE_MISMATCH */ INT db_set_link_data_index(HNDLE hDB, HNDLE hKey, const void *data, INT data_size, INT idx, DWORD type) { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_LINK_DATA_INDEX, hDB, hKey, data, data_size, idx, type); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_link_data_index", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_link_data_index", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_link_data_index", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); status = db_check_set_data_index_locked(pheader, pkey, idx, data, data_size, type, "db_set_link_data_index", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_allow_write_locked(&_database[hDB - 1], "db_set_link_data_index"); status = db_set_data_index_wlocked(pheader, pkey, idx, data, data_size, type, "db_set_link_data_index", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_notify_clients_locked(pheader, hDB, hKey, idx, TRUE, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_set_data_index1(HNDLE hDB, HNDLE hKey, const void *data, INT data_size, INT idx, DWORD type, BOOL bNotify) /********************************************************************\ Routine: db_set_data_index1 Purpose: Set key data for a key which contains an array of values. Optionally notify clients which have key open. Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key to enumerate void *data Pointer to single value of data INT data_size Size of single data element INT idx Index of array to change [0..n-1] DWORD type Type of data BOOL bNotify If TRUE, notify clients Output: none Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid DB_TYPE_MISMATCH Key was created with different type DB_NO_ACCESS No write access \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_DATA_INDEX1, hDB, hKey, data, data_size, idx, type, bNotify); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; KEY *pkey; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_data_index1", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_data_index1", "invalid database handle"); return DB_INVALID_HANDLE; } if (hKey < (int) sizeof(DATABASE_HEADER)) { cm_msg(MERROR, "db_set_data_index1", "invalid key handle"); return DB_INVALID_HANDLE; } db_lock_database(hDB); db_err_msg* msg = NULL; pheader = _database[hDB - 1].database_header; /* check if hKey argument is correct */ if (!db_validate_hkey(pheader, hKey)) { db_unlock_database(hDB); return DB_INVALID_HANDLE; } pkey = (KEY *) ((char *) pheader + hKey); status = db_check_set_data_index_locked(pheader, pkey, idx, data, data_size, type, "db_set_data_index1", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } db_allow_write_locked(&_database[hDB - 1], "db_set_data_index1"); status = db_set_data_index_wlocked(pheader, pkey, idx, data, data_size, type, "db_set_data_index1", &msg); if (status != DB_SUCCESS) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } if (bNotify) db_notify_clients_locked(pheader, hDB, hKey, idx, TRUE, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*----------------------------------------------------------------------------*/ INT db_merge_data(HNDLE hDB, HNDLE hKeyRoot, const char *name, void *data, INT data_size, INT num_values, INT type) /********************************************************************\ Routine: db_merge_data Purpose: Merge an array with an ODB array. If the ODB array doesn't exist, create it and fill it with the array. If it exists, load it in the array. Adjust ODB array size if necessary. Input: HNDLE hDB Handle to the database HNDLE hKeyRoot Key handle to start with, 0 for root cha *name Key name relative to hKeyRoot void *data Pointer to data array INT data_size Size of data array INT num_values Number of values in array DWORD type Type of data Output: none Function value: \********************************************************************/ { HNDLE hKey; INT status, old_size; if (num_values == 0) return DB_INVALID_PARAM; status = db_find_key(hDB, hKeyRoot, name, &hKey); if (status != DB_SUCCESS) { db_create_key(hDB, hKeyRoot, name, type); status = db_find_key(hDB, hKeyRoot, name, &hKey); if (status != DB_SUCCESS) return status; status = db_set_data(hDB, hKey, data, data_size, num_values, type); } else { old_size = data_size; db_get_data(hDB, hKey, data, &old_size, type); status = db_set_data(hDB, hKey, data, data_size, num_values, type); } return status; } #ifdef LOCAL_ROUTINES /*------------------------------------------------------------------*/ static int db_set_mode_wlocked(DATABASE_HEADER *pheader, KEY *pkey, WORD mode, int recurse, db_err_msg** msg) /********************************************************************\ Routine: db_set_mode_wlocked() Purpose: Set access mode of key Input: pheader Database pkey Key DWORD mode Access mode, any or'ed combination of MODE_READ, MODE_WRITE, MODE_EXCLUSIVE and MODE_DELETE recurse Value of 0: do not recurse subtree, value of 1: recurse subtree, becomes recurse level Function value: DB_SUCCESS Successful completion \********************************************************************/ { /* resolve links */ if (pkey->type == TID_LINK) { int status; pkey = (KEY*)db_resolve_link_locked(pheader, pkey, &status, msg); if (!pkey) { return status; } } if (pkey->type == TID_KEY && recurse) { // drop "const" from KEY* we are permitted to write to ODB! KEY* psubkey = (KEY*)db_enum_first_locked(pheader, pkey, msg); while (psubkey) { db_set_mode_wlocked(pheader, psubkey, mode, recurse+1, msg); psubkey = (KEY*)db_enum_next_locked(pheader, pkey, psubkey, msg); } } /* now set mode */ pkey->access_mode = mode; return DB_SUCCESS; } #endif /*------------------------------------------------------------------*/ INT db_set_mode(HNDLE hDB, HNDLE hKey, WORD mode, BOOL recurse) /********************************************************************\ Routine: db_set_mode Purpose: Set access mode of key Input: HNDLE hDB Handle to the database HNDLE hKey Key handle DWORD mode Access mode, any or'ed combination of MODE_READ, MODE_WRITE, MODE_EXCLUSIVE and MODE_DELETE BOOL recurse Value of 0 (FALSE): do not recurse subtree, value of 1 (TRUE): recurse subtree, value of 2: recurse subtree, assume database is locked by caller. Output: none Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_SET_MODE, hDB, hKey, mode, recurse); #ifdef LOCAL_ROUTINES { DATABASE_HEADER *pheader; BOOL locked = FALSE; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_set_mode", "invalid database handle"); return DB_INVALID_HANDLE; } if (!_database[hDB - 1].attached) { cm_msg(MERROR, "db_set_mode", "invalid database handle"); return DB_INVALID_HANDLE; } if (recurse < 2) { db_lock_database(hDB); locked = TRUE; } pheader = _database[hDB - 1].database_header; db_err_msg* msg = NULL; int status = 0; KEY *pkey = (KEY*)db_get_pkey(pheader, hKey, &status, "db_set_mode", &msg); if (!pkey) { if (locked) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } } db_allow_write_locked(&_database[hDB-1], "db_set_mode"); status = db_set_mode_wlocked(pheader, pkey, mode, recurse, &msg); if (locked) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Load a branch of a database from an .ODB file. This function is used by the ODBEdit command load. For a description of the ASCII format, see db_copy(). Data can be loaded relative to the root of the ODB (hkey equal zero) or relative to a certain key. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKeyRoot Handle for key where search starts, zero for root. @param filename Filename of .ODB file. @param bRemote If TRUE, the file is loaded by the server process on the back-end, if FALSE, it is loaded from the current process @return DB_SUCCESS, DB_INVALID_HANDLE, DB_FILE_ERROR */ INT db_load(HNDLE hDB, HNDLE hKeyRoot, const char *filename, BOOL bRemote) { struct stat stat_buf; INT hfile, size, n, i, status; char *buffer; if (rpc_is_remote() && bRemote) return rpc_call(RPC_DB_LOAD, hDB, hKeyRoot, filename); /* open file */ hfile = open(filename, O_RDONLY | O_TEXT, 0644); if (hfile == -1) { cm_msg(MERROR, "db_load", "file \"%s\" not found", filename); return DB_FILE_ERROR; } /* allocate buffer with file size */ fstat(hfile, &stat_buf); size = stat_buf.st_size; buffer = (char *) malloc(size + 1); if (buffer == NULL) { cm_msg(MERROR, "db_load", "cannot allocate ODB load buffer"); close(hfile); return DB_NO_MEMORY; } n = 0; do { i = read(hfile, buffer + n, size - n); if (i <= 0) break; n += i; } while (TRUE); buffer[n] = 0; if (strncmp(buffer, " *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, line); buffer += strlen(line); *buffer_size -= strlen(line); /* copy multiple lines to buffer */ if (key.item_size > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, data); buffer += strlen(data); *buffer_size -= strlen(data); strcpy(line, "\n====#$@$#====\n"); } else { std::string str = db_sprintf(data, key.item_size, 0, key.type); if (key.type == TID_STRING || key.type == TID_LINK) sprintf(line + strlen(line), "[%d] ", key.item_size); sprintf(line + strlen(line), "%s\n", str.c_str()); // FIXME: buffer overflow. K.O. Aug2024 } } else { sprintf(line, "%s = %s[%d] :\n", key.name, rpc_tid_name(key.type), key.num_values); for (j = 0; j < key.num_values; j++) { if (key.type == TID_STRING || key.type == TID_LINK) sprintf(line + strlen(line), "[%d] ", key.item_size); else sprintf(line + strlen(line), "[%d] ", j); std::string str = db_sprintf(data, key.item_size, j, key.type); sprintf(line + strlen(line), "%s\n", str.c_str()); // FIXME: buffer overflow. K.O. Aug2024 /* copy line to buffer */ if ((INT) (strlen(line) + 1) > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, line); buffer += strlen(line); *buffer_size -= strlen(line); line[0] = 0; } } } /* copy line to buffer */ if ((INT) (strlen(line) + 1) > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, line); buffer += strlen(line); *buffer_size -= strlen(line); free(data); data = NULL; } if (!hSubkey) break; status = db_get_link(hDB, hSubkey, &key); if (status != DB_SUCCESS) continue; if (strcmp(key.name, "arr2") == 0) printf("\narr2\n"); size = key.total_size; data = (char *) malloc(size); if (data == NULL) { cm_msg(MERROR, "db_copy", "cannot allocate data buffer"); return DB_NO_MEMORY; } line[0] = 0; if (key.type == TID_KEY) { char str[MAX_ODB_PATH]; /* new line */ if (bWritten) { if (*buffer_size < 2) { free(data); return DB_TRUNCATED; } strcpy(buffer, "\n"); buffer += 1; *buffer_size -= 1; } strcpy(str, full_path); if (str[0] && str[strlen(str) - 1] != '/') strcat(str, "/"); strcat(str, key.name); /* recurse */ status = db_copy(hDB, hSubkey, buffer, buffer_size, str); if (status != DB_SUCCESS) { free(data); return status; } buffer += strlen(buffer); bWritten = FALSE; } else { status = db_get_link_data(hDB, hSubkey, data, &size, key.type); if (status != DB_SUCCESS) continue; if (!bWritten) { if (path[0] == 0) sprintf(line, "[.]\n"); else sprintf(line, "[%s]\n", path); bWritten = TRUE; } if (key.num_values == 1) { sprintf(line + strlen(line), "%s = %s : ", key.name, rpc_tid_name(key.type)); if (key.type == TID_STRING && strchr(data, '\n') != NULL) { /* multiline string */ sprintf(line + strlen(line), "[====#$@$#====]\n"); /* ensure string limiter */ data[size - 1] = 0; /* copy line to buffer */ if ((INT) (strlen(line) + 1) > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, line); buffer += strlen(line); *buffer_size -= strlen(line); /* copy multiple lines to buffer */ if (key.item_size > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, data); buffer += strlen(data); *buffer_size -= strlen(data); strcpy(line, "\n====#$@$#====\n"); } else { std::string str = db_sprintf(data, key.item_size, 0, key.type); if (key.type == TID_STRING || key.type == TID_LINK) sprintf(line + strlen(line), "[%d] ", key.item_size); sprintf(line + strlen(line), "%s\n", str.c_str()); // FIXME: buffer overflow. K.O. Aug2024 } } else { sprintf(line + strlen(line), "%s = %s[%d] :\n", key.name, rpc_tid_name(key.type), key.num_values); for (j = 0; j < key.num_values; j++) { if (key.type == TID_STRING || key.type == TID_LINK) sprintf(line + strlen(line), "[%d] ", key.item_size); else sprintf(line + strlen(line), "[%d] ", j); std::string str = db_sprintf(data, key.item_size, j, key.type); sprintf(line + strlen(line), "%s\n", str.c_str()); // FIXME: buffer overflow. K.O. Aug2024 /* copy line to buffer */ if ((INT) (strlen(line) + 1) > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, line); buffer += strlen(line); *buffer_size -= strlen(line); line[0] = 0; } } /* copy line to buffer */ if ((INT) (strlen(line) + 1) > *buffer_size) { free(data); return DB_TRUNCATED; } strcpy(buffer, line); buffer += strlen(line); *buffer_size -= strlen(line); } free(data); data = NULL; } if (bWritten) { if (*buffer_size < 2) return DB_TRUNCATED; strcpy(buffer, "\n"); buffer += 1; *buffer_size -= 1; } return DB_SUCCESS; } /********************************************************************/ /** Copy an ODB subtree in ASCII format from a buffer @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKeyRoot Handle for key where search starts, zero for root. @param buffer NULL-terminated buffer @return DB_SUCCESS, DB_TRUNCATED, DB_NO_MEMORY */ INT db_paste(HNDLE hDB, HNDLE hKeyRoot, const char *buffer) { char title[MAX_STRING_LENGTH]; char *data; const char *pold; INT data_size, index; INT tid, i, j, n_data, string_length, status, size; HNDLE hKey; KEY root_key; title[0] = 0; if (hKeyRoot == 0) db_find_key(hDB, hKeyRoot, "", &hKeyRoot); db_get_key(hDB, hKeyRoot, &root_key); /* initial data size */ data_size = 1000; data = (char *) malloc(data_size); if (data == NULL) { cm_msg(MERROR, "db_paste", "cannot allocate data buffer"); return DB_NO_MEMORY; } do { char line[10*MAX_STRING_LENGTH]; if (*buffer == 0) break; for (i = 0; *buffer != '\n' && *buffer && i < 10*MAX_STRING_LENGTH; i++) line[i] = *buffer++; if (i == 10*MAX_STRING_LENGTH) { line[10*MAX_STRING_LENGTH-1] = 0; cm_msg(MERROR, "db_paste", "line too long: %s...", line); free(data); return DB_TRUNCATED; } line[i] = 0; if (*buffer == '\n') buffer++; /* check if it is a section title */ if (line[0] == '[') { /* extract title and append '/' */ mstrlcpy(title, line + 1, sizeof(title)); if (strchr(title, ']')) *strchr(title, ']') = 0; if (title[0] && title[strlen(title) - 1] != '/') mstrlcat(title, "/", sizeof(title)); } else { /* valid data line if it includes '=' and no ';' */ if (strchr(line, '=') && line[0] != ';') { char key_name[MAX_ODB_PATH]; char test_str[MAX_ODB_PATH]; char data_str[10*MAX_STRING_LENGTH]; /* copy type info and data */ char* pline = strrchr(line, '=') + 1; while (strstr(line, ": [") != NULL && strstr(line, ": [") < pline) { pline -= 2; while (*pline != '=' && pline > line) pline--; pline++; } while (*pline == ' ') pline++; mstrlcpy(data_str, pline, sizeof(data_str)); /* extract key name */ *strrchr(line, '=') = 0; while (strstr(line, ": [") && strchr(line, '=')) *strrchr(line, '=') = 0; pline = &line[strlen(line) - 1]; while (*pline == ' ') *pline-- = 0; key_name[0] = 0; if (title[0] != '.') mstrlcpy(key_name, title, sizeof(key_name)); mstrlcat(key_name, line, sizeof(key_name)); /* evaluate type info */ mstrlcpy(line, data_str, sizeof(line)); if (strchr(line, ' ')) *strchr(line, ' ') = 0; n_data = 1; if (strchr(line, '[')) { n_data = atol(strchr(line, '[') + 1); *strchr(line, '[') = 0; } for (tid = 0; tid < TID_LAST; tid++) if (strcmp(rpc_tid_name(tid), line) == 0) break; if (tid == TID_LAST) { for (tid = 0; tid < TID_LAST; tid++) if (strcmp(rpc_tid_name_old(tid), line) == 0) break; } string_length = 0; if (tid == TID_LAST) cm_msg(MERROR, "db_paste", "found unknown data type \"%s\" in ODB file", line); else { /* skip type info */ char* pc = data_str; while (*pc != ' ' && *pc) pc++; while ((*pc == ' ' || *pc == ':') && *pc) pc++; //mstrlcpy(data_str, pc, sizeof(data_str)); // MacOS 10.9 does not permit mstrlcpy() of overlapping strings assert(strlen(pc) < sizeof(data_str)); // "pc" points at a substring inside "data_str" memmove(data_str, pc, strlen(pc)+1); if (n_data > 1) { data_str[0] = 0; if (!*buffer) break; for (j = 0; *buffer != '\n' && *buffer; j++) data_str[j] = *buffer++; data_str[j] = 0; if (*buffer == '\n') buffer++; } for (i = 0; i < n_data; i++) { /* strip trailing \n */ char* pc = &data_str[strlen(data_str) - 1]; while (*pc == '\n' || *pc == '\r') *pc-- = 0; if (tid == TID_STRING || tid == TID_LINK) { if (!string_length) { if (data_str[1] == '=') string_length = -1; else string_length = atoi(data_str + 1); if (string_length > MAX_STRING_LENGTH) { string_length = MAX_STRING_LENGTH; cm_msg(MERROR, "db_paste", "found string exceeding MAX_STRING_LENGTH, odb path \"%s\"", key_name); } if (string_length == 0) { string_length = 32; cm_msg(MERROR, "db_paste", "found string length of zero, set to 32, odb path \"%s\"", key_name); } } if (string_length == -1) { /* multi-line string */ if (strstr(buffer, "\n====#$@$#====\n") != NULL) { string_length = (POINTER_T) strstr(buffer, "\n====#$@$#====\n") - (POINTER_T) buffer + 1; if (string_length >= data_size) { data_size += string_length + 100; data = (char *) realloc(data, data_size); if (data == NULL) { cm_msg(MERROR, "db_paste", "cannot allocate data buffer"); return DB_NO_MEMORY; } } memset(data, 0, data_size); strncpy(data, buffer, string_length); data[string_length - 1] = 0; buffer = strstr(buffer, "\n====#$@$#====\n") + strlen("\n====#$@$#====\n"); } else cm_msg(MERROR, "db_paste", "found multi-line string without termination sequence"); } else { char* pc = data_str + 2; while (*pc && *pc != ' ') pc++; // skip one space (needed for strings starting with spaces) if (*pc) pc++; /* limit string size */ *(pc + string_length - 1) = 0; /* increase data buffer if necessary */ if (string_length * (i + 1) >= data_size) { data_size += 1000; data = (char *) realloc(data, data_size); if (data == NULL) { cm_msg(MERROR, "db_paste", "cannot allocate data buffer"); return DB_NO_MEMORY; } } mstrlcpy(data + string_length * i, pc, string_length); } } else { char* pc = data_str; if (n_data > 1 && data_str[0] == '[') { index = atoi(data_str+1); pc = strchr(data_str, ']') + 1; while (*pc && *pc == ' ') pc++; } else index = 0; /* increase data buffer if necessary */ if (rpc_tid_size(tid) * (index + 1) >= data_size) { data_size += 1000; data = (char *) realloc(data, data_size); if (data == NULL) { cm_msg(MERROR, "db_paste", "cannot allocate data buffer"); return DB_NO_MEMORY; } } db_sscanf(pc, data, &size, index, tid); } if (i < n_data - 1) { data_str[0] = 0; if (!*buffer) break; pold = buffer; for (j = 0; *buffer != '\n' && *buffer; j++) data_str[j] = *buffer++; data_str[j] = 0; if (*buffer == '\n') buffer++; /* test if valid data */ if (tid != TID_STRING && tid != TID_LINK) { if (data_str[0] == 0 || (strchr(data_str, '=') && strchr(data_str, ':'))) buffer = pold; } } } /* skip system client entries */ mstrlcpy(test_str, key_name, sizeof(test_str)); test_str[15] = 0; if (!equal_ustring(test_str, "/System/Clients")) { if (root_key.type != TID_KEY) { /* root key is destination key */ hKey = hKeyRoot; } else { /* create key and set value */ if (key_name[0] == '/') { status = db_find_link(hDB, 0, key_name, &hKey); if (status == DB_NO_KEY) { db_create_key(hDB, 0, key_name, tid); status = db_find_link(hDB, 0, key_name, &hKey); } } else { status = db_find_link(hDB, hKeyRoot, key_name, &hKey); if (status == DB_NO_KEY) { db_create_key(hDB, hKeyRoot, key_name, tid); status = db_find_link(hDB, hKeyRoot, key_name, &hKey); } } } /* set key data if created successfully */ if (hKey) { if (tid == TID_STRING || tid == TID_LINK) db_set_link_data(hDB, hKey, data, string_length * n_data, n_data, tid); else db_set_link_data(hDB, hKey, data, rpc_tid_size(tid) * n_data, n_data, tid); } } } } } } while (TRUE); free(data); return DB_SUCCESS; } /********************************************************************/ /* Only internally used by db_paste_xml */ static int db_paste_node(HNDLE hDB, HNDLE hKeyRoot, PMXML_NODE node) { int status; if (strcmp(mxml_get_name(node), "odb") == 0) { for (int i = 0; i < mxml_get_number_of_children(node); i++) { status = db_paste_node(hDB, hKeyRoot, mxml_subnode(node, i)); if (status != DB_SUCCESS) return status; } } else if (strcmp(mxml_get_name(node), "dir") == 0) { const char* name = mxml_get_attribute(node, "name"); if (name == NULL) { cm_msg(MERROR, "db_paste_node", "found key \"%s\" with no name in XML data", mxml_get_name(node)); return DB_TYPE_MISMATCH; } HNDLE hKey; status = db_find_link(hDB, hKeyRoot, name, &hKey); if (status == DB_NO_KEY) { status = db_create_key(hDB, hKeyRoot, name, TID_KEY); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load key \"%s\": write protected", name); return DB_SUCCESS; /* key or tree is locked, just skip it */ } if (status != DB_SUCCESS && status != DB_KEY_EXIST) { cm_msg(MERROR, "db_paste_node", "cannot create key \"%s\" in ODB, status = %d", name, status); return status; } status = db_find_link(hDB, hKeyRoot, name, &hKey); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot find key \"%s\" in ODB", name); return status; } } std::string path = db_get_path(hDB, hKey); if (!equal_ustring(path.c_str(), "/System/Clients")) { for (int i = 0; i < mxml_get_number_of_children(node); i++) { status = db_paste_node(hDB, hKey, mxml_subnode(node, i)); if (status != DB_SUCCESS) return status; } } } else if (strcmp(mxml_get_name(node), "key") == 0 || strcmp(mxml_get_name(node), "keyarray") == 0) { const char* name = mxml_get_attribute(node, "name"); if (name == NULL) { cm_msg(MERROR, "db_paste_node", "found key \"%s\" with no name in XML data", mxml_get_name(node)); return DB_TYPE_MISMATCH; } int num_values; if (strcmp(mxml_get_name(node), "keyarray") == 0) num_values = atoi(mxml_get_attribute(node, "num_values")); else num_values = 0; const char* type = mxml_get_attribute(node, "type"); if (type == NULL) { cm_msg(MERROR, "db_paste_node", "found key \"%s\" with no type in XML data", mxml_get_name(node)); return DB_TYPE_MISMATCH; } int tid = rpc_name_tid(type); if (tid == 0) { cm_msg(MERROR, "db_paste_node", "found unknown data type \"%s\" in XML data", type); return DB_TYPE_MISMATCH; } HNDLE hKey; status = db_find_link(hDB, hKeyRoot, name, &hKey); if (status == DB_NO_KEY) { status = db_create_key(hDB, hKeyRoot, name, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load key \"%s\": write protected", name); return DB_SUCCESS; /* key or tree is locked, just skip it */ } if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot create key \"%s\" in ODB, status = %d", name, status); return status; } status = db_find_link(hDB, hKeyRoot, name, &hKey); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot find key \"%s\" in ODB, status = %d", name, status); return status; } } else { KEY key; status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot get key \"%s\" in ODB, status = %d", name, status); return status; } if (num_values > 0 && num_values != key.num_values && key.item_size > 0) { status = db_set_num_values(hDB, hKey, num_values); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot resize key \"%s\" in ODB, status = %d", name, status); return status; } } } int size = 0; char *buf = NULL; if (tid == TID_STRING || tid == TID_LINK) { size = atoi(mxml_get_attribute(node, "size")); buf = (char *)malloc(size); assert(buf); buf[0] = 0; } if (num_values) { /* evaluate array */ for (int i = 0; i < mxml_get_number_of_children(node); i++) { PMXML_NODE child = mxml_subnode(node, i); int idx; if (mxml_get_attribute(child, "index")) idx = atoi(mxml_get_attribute(child, "index")); else idx = i; if (tid == TID_STRING || tid == TID_LINK) { if (mxml_get_value(child) == NULL) { status = db_set_data_index(hDB, hKey, "", size, i, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load string or link \"%s\": write protected", mxml_get_attribute(node, "name")); return DB_SUCCESS; /* key or tree is locked, just skip it */ } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot load string or link \"%s\": db_set_data_index() status %d", mxml_get_attribute(node, "name"), status); return status; } } else { mstrlcpy(buf, mxml_get_value(child), size); status = db_set_data_index(hDB, hKey, buf, size, idx, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load array element \"%s\": write protected", mxml_get_attribute(node, "name")); return DB_SUCCESS; /* key or tree is locked, just skip it */ } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot load array element \"%s\": db_set_data_index() status %d", mxml_get_attribute(node, "name"), status); return status; } } } else { char data[256]; db_sscanf(mxml_get_value(child), data, &size, 0, tid); status = db_set_data_index(hDB, hKey, data, rpc_tid_size(tid), idx, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load array element \"%s\": write protected", mxml_get_attribute(node, "name")); return DB_SUCCESS; /* key or tree is locked, just skip it */ } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot load array element \"%s\": db_set_data_index() status %d", mxml_get_attribute(node, "name"), status); return status; } } } } else { /* single value */ if (tid == TID_STRING || tid == TID_LINK) { size = atoi(mxml_get_attribute(node, "size")); if (mxml_get_value(node) == NULL) { status = db_set_data(hDB, hKey, "", size, 1, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load string or link \"%s\": write protected", mxml_get_attribute(node, "name")); return DB_SUCCESS; /* key or tree is locked, just skip it */ } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot load string or link \"%s\": db_set_data() status %d", mxml_get_attribute(node, "name"), status); return status; } } else { mstrlcpy(buf, mxml_get_value(node), size); status = db_set_data(hDB, hKey, buf, size, 1, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load value \"%s\": write protected", mxml_get_attribute(node, "name")); return DB_SUCCESS; /* key or tree is locked, just skip it */ } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot load value \"%s\": db_set_data() status %d", mxml_get_attribute(node, "name"), status); return status; } } } else { char data[256]; db_sscanf(mxml_get_value(node), data, &size, 0, tid); status = db_set_data(hDB, hKey, data, rpc_tid_size(tid), 1, tid); if (status == DB_NO_ACCESS) { cm_msg(MINFO, "db_paste_node", "cannot load value \"%s\": write protected", mxml_get_attribute(node, "name")); return DB_SUCCESS; /* key or tree is locked, just skip it */ } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_paste_node", "cannot load value \"%s\": db_set_data() status %d", mxml_get_attribute(node, "name"), status); return status; } } } if (buf) { free(buf); buf = NULL; } } return DB_SUCCESS; } /********************************************************************/ /** Paste an ODB subtree in XML format from a buffer @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKeyRoot Handle for key where search starts, zero for root. @param buffer NULL-terminated buffer @return DB_SUCCESS, DB_INVALID_PARAM, DB_NO_MEMORY, DB_TYPE_MISMATCH */ INT db_paste_xml(HNDLE hDB, HNDLE hKeyRoot, const char *buffer) { char error[256]; INT status; PMXML_NODE tree, node; if (hKeyRoot == 0) db_find_key(hDB, hKeyRoot, "", &hKeyRoot); /* parse XML buffer */ tree = mxml_parse_buffer(buffer, error, sizeof(error), NULL); if (tree == NULL) { puts(error); return DB_TYPE_MISMATCH; } node = mxml_find_node(tree, "odb"); if (node == NULL) { puts("Cannot find element \"odb\" in XML data"); return DB_TYPE_MISMATCH; } status = db_paste_node(hDB, hKeyRoot, node); mxml_free_tree(tree); return status; } /********************************************************************/ /** Copy an ODB subtree in XML format to a buffer @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param buffer ASCII buffer which receives ODB contents. @param buffer_size Size of buffer, returns remaining space in buffer. @return DB_SUCCESS, DB_TRUNCATED, DB_NO_MEMORY */ INT db_copy_xml(HNDLE hDB, HNDLE hKey, char *buffer, int *buffer_size, bool header) { if (rpc_is_remote()) return rpc_call(RPC_DB_COPY_XML, hDB, hKey, buffer, buffer_size, header); #ifdef LOCAL_ROUTINES { INT len; char *p; MXML_WRITER *writer; /* open file */ writer = mxml_open_buffer(); if (writer == NULL) { cm_msg(MERROR, "db_copy_xml", "Cannot allocate buffer"); return DB_NO_MEMORY; } if (header) { std::string path = db_get_path(hDB, hKey); /* write XML header */ mxml_start_element(writer, "odb"); mxml_write_attribute(writer, "root", path.c_str()); mxml_write_attribute(writer, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); mxml_write_attribute(writer, "xsi:noNamespaceSchemaLocation", "http://midas.psi.ch/odb.xsd"); db_save_xml_key(hDB, hKey, 0, writer); mxml_end_element(writer); // "odb" } else { KEY key; int status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; db_save_xml_key(hDB, hKey, 1, writer); } p = mxml_close_buffer(writer); len = strlen(p) + 1; if (len > *buffer_size) { free(p); *buffer_size = 0; return DB_TRUNCATED; } mstrlcpy(buffer, p, len); free(p); *buffer_size = len; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ void name2c(char *str) /********************************************************************\ Routine: name2c Purpose: Convert key name to C name. Internal use only. \********************************************************************/ { if (*str >= '0' && *str <= '9') *str = '_'; while (*str) { if (!(*str >= 'a' && *str <= 'z') && !(*str >= 'A' && *str <= 'Z') && !(*str >= '0' && *str <= '9')) *str = '_'; *str = (char) tolower(*str); str++; } } /*------------------------------------------------------------------*/ static void db_save_tree_struct(HNDLE hDB, HNDLE hKey, int hfile, INT level) /********************************************************************\ Routine: db_save_tree_struct Purpose: Save database tree as a C structure. Gets called by db_save_struct(). Internal use only. \********************************************************************/ { INT i, idx; KEY key; HNDLE hSubkey; int wr; /* first enumerate this level */ for (idx = 0;; idx++) { char name[MAX_ODB_PATH]; db_enum_link(hDB, hKey, idx, &hSubkey); if (!hSubkey) break; /* first get the name of the link, than the type of the link target */ db_get_key(hDB, hSubkey, &key); mstrlcpy(name, key.name, sizeof(name)); db_enum_key(hDB, hKey, idx, &hSubkey); db_get_key(hDB, hSubkey, &key); if (key.type != TID_KEY) { char line[MAX_ODB_PATH]; char str[MAX_ODB_PATH]; for (i = 0; i <= level; i++) { wr = write(hfile, " ", 2); assert(wr == 2); } switch (key.type) { case TID_INT8: case TID_CHAR: strcpy(line, "char"); break; case TID_INT16: strcpy(line, "short"); break; case TID_FLOAT: strcpy(line, "float"); break; case TID_DOUBLE: strcpy(line, "double"); break; case TID_BITFIELD: strcpy(line, "unsigned char"); break; case TID_STRING: strcpy(line, "char"); break; case TID_LINK: strcpy(line, "char"); break; default: strcpy(line, rpc_tid_name(key.type)); break; } mstrlcat(line, " ", sizeof(line)); mstrlcpy(str, name, sizeof(str)); name2c(str); if (key.num_values > 1) sprintf(str + strlen(str), "[%d]", key.num_values); if (key.type == TID_STRING || key.type == TID_LINK) sprintf(str + strlen(str), "[%d]", key.item_size); mstrlcpy(line + 10, str, sizeof(line) - 10); mstrlcat(line, ";\n", sizeof(line)); wr = write(hfile, line, strlen(line)); assert(wr > 0); } else { char line[10+MAX_ODB_PATH]; char str[MAX_ODB_PATH]; /* recurse subtree */ for (i = 0; i <= level; i++) { wr = write(hfile, " ", 2); assert(wr == 2); } sprintf(line, "struct {\n"); wr = write(hfile, line, strlen(line)); assert(wr > 0); db_save_tree_struct(hDB, hSubkey, hfile, level + 1); for (i = 0; i <= level; i++) { wr = write(hfile, " ", 2); assert(wr == 2); } strcpy(str, name); name2c(str); sprintf(line, "} %s;\n", str); wr = write(hfile, line, strlen(line)); assert(wr > 0); } } } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Save a branch of a database to an .ODB file This function is used by the ODBEdit command save. For a description of the ASCII format, see db_copy(). Data of the whole ODB can be saved (hkey equal zero) or only a sub-tree. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param filename Filename of .ODB file. @param bRemote Flag for saving database on remote server. @return DB_SUCCESS, DB_FILE_ERROR */ INT db_save(HNDLE hDB, HNDLE hKey, const char *filename, BOOL bRemote) { if (rpc_is_remote() && bRemote) return rpc_call(RPC_DB_SAVE, hDB, hKey, filename, bRemote); #ifdef LOCAL_ROUTINES { INT hfile, size, buffer_size, n, status; char *buffer; /* open file */ hfile = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_TEXT, 0644); if (hfile == -1) { cm_msg(MERROR, "db_save", "Cannot open file \"%s\"", filename); return DB_FILE_ERROR; } std::string path = db_get_path(hDB, hKey); buffer_size = 10000; do { buffer = (char *) malloc(buffer_size); if (buffer == NULL) { cm_msg(MERROR, "db_save", "cannot allocate ODB dump buffer"); break; } size = buffer_size; status = db_copy(hDB, hKey, buffer, &size, path.c_str()); if (status != DB_TRUNCATED) { n = write(hfile, buffer, buffer_size - size); free(buffer); buffer = NULL; if (n != buffer_size - size) { cm_msg(MERROR, "db_save", "cannot save .ODB file"); close(hfile); return DB_FILE_ERROR; } break; } /* increase buffer size if truncated */ free(buffer); buffer = NULL; buffer_size *= 2; } while (1); close(hfile); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ void xml_encode(char *src, int size) { int i; char *dst, *p; dst = (char *) malloc(size); if (dst == NULL) return; *dst = 0; for (i = 0; i < (int) strlen(src); i++) { switch (src[i]) { case '<': mstrlcat(dst, "<", size); break; case '>': mstrlcat(dst, ">", size); break; case '&': mstrlcat(dst, "&", size); break; case '\"': mstrlcat(dst, """, size); break; case '\'': mstrlcat(dst, "'", size); break; default: if ((int) strlen(dst) >= size) { free(dst); return; } p = dst + strlen(dst); *p = src[i]; *(p + 1) = 0; } } mstrlcpy(src, dst, size); } /*------------------------------------------------------------------*/ INT db_save_xml_key(HNDLE hDB, HNDLE hKey, INT level, MXML_WRITER * writer) { INT i, idx, size, status; char *data; HNDLE hSubkey; KEY key; status = db_get_link(hDB, hKey, &key); if (status != DB_SUCCESS) return status; if (key.type == TID_KEY) { /* save opening tag for subtree */ if (level > 0) { mxml_start_element(writer, "dir"); mxml_write_attribute(writer, "name", key.name); mxml_write_attribute(writer, "handle", std::to_string(hKey).c_str()); } for (idx = 0;; idx++) { db_enum_link(hDB, hKey, idx, &hSubkey); if (!hSubkey) break; /* save subtree */ status = db_save_xml_key(hDB, hSubkey, level + 1, writer); if (status != DB_SUCCESS) return status; } /* save closing tag for subtree */ if (level > 0) mxml_end_element(writer); } else { /* save key value */ if (key.num_values > 1) mxml_start_element(writer, "keyarray"); else mxml_start_element(writer, "key"); mxml_write_attribute(writer, "name", key.name); mxml_write_attribute(writer, "type", rpc_tid_name(key.type)); mxml_write_attribute(writer, "handle", std::to_string(hKey).c_str()); if (key.type == TID_STRING || key.type == TID_LINK) { char str[256]; sprintf(str, "%d", key.item_size); mxml_write_attribute(writer, "size", str); } if (key.num_values > 1) { char str[256]; sprintf(str, "%d", key.num_values); mxml_write_attribute(writer, "num_values", str); } size = key.total_size; data = (char *) malloc(size+1); // an extra byte to zero-terminate strings if (data == NULL) { cm_msg(MERROR, "db_save_xml_key", "cannot allocate data buffer"); return DB_NO_MEMORY; } db_get_link_data(hDB, hKey, data, &size, key.type); if (key.num_values == 1) { if (key.type == TID_STRING) { data[size] = 0; // make sure strings are NUL-terminated mxml_write_value(writer, data); } else { std::string str = db_sprintf(data, key.item_size, 0, key.type); if (key.type == TID_STRING && strlen(data) >= MAX_STRING_LENGTH) { std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_save_xml_key", "Long odb string probably truncated, odb path \"%s\", string length %d truncated to %d", path.c_str(), (int)strlen(data), (int)str.length()); } mxml_write_value(writer, str.c_str()); } mxml_end_element(writer); } else { /* array of values */ for (i = 0; i < key.num_values; i++) { mxml_start_element(writer, "value"); { char str[256]; sprintf(str, "%d", i); mxml_write_attribute(writer, "index", str); } if (key.type == TID_STRING) { char* p = data + i * key.item_size; p[key.item_size - 1] = 0; // make sure string is NUL-terminated //cm_msg(MINFO, "db_save_xml_key", "odb string array item_size %d, index %d length %d", key.item_size, i, (int)strlen(p)); mxml_write_value(writer, p); } else { std::string str = db_sprintf(data, key.item_size, i, key.type); if ((key.type == TID_STRING) && (str.length() >= MAX_STRING_LENGTH-1)) { std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_save_xml_key", "Long odb string array probably truncated, odb path \"%s\"[%d]", path.c_str(), i); } mxml_write_value(writer, str.c_str()); } mxml_end_element(writer); } mxml_end_element(writer); /* keyarray */ } free(data); data = NULL; } return DB_SUCCESS; } /********************************************************************/ /** Save a branch of a database to an .xml file This function is used by the ODBEdit command save to write the contents of the ODB into a XML file. Data of the whole ODB can be saved (hkey equal zero) or only a sub-tree. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param filename Filename of .XML file. @return DB_SUCCESS, DB_FILE_ERROR */ INT db_save_xml(HNDLE hDB, HNDLE hKey, const char *filename) { #ifdef LOCAL_ROUTINES { INT status; MXML_WRITER *writer; /* open file */ writer = mxml_open_file(filename); if (writer == NULL) { cm_msg(MERROR, "db_save_xml", "Cannot open file \"%s\"", filename); return DB_FILE_ERROR; } std::string path = db_get_path(hDB, hKey); /* write XML header */ mxml_start_element(writer, "odb"); mxml_write_attribute(writer, "root", path.c_str()); mxml_write_attribute(writer, "filename", filename); mxml_write_attribute(writer, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); std::string xsd_path; if (getenv("MIDASSYS")) xsd_path = getenv("MIDASSYS"); else xsd_path = "."; xsd_path += DIR_SEPARATOR_STR; xsd_path += "odb.xsd"; mxml_write_attribute(writer, "xsi:noNamespaceSchemaLocation", xsd_path.c_str()); status = db_save_xml_key(hDB, hKey, 0, writer); mxml_end_element(writer); // "odb" mxml_close_file(writer); return status; } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /*------------------------------------------------------------------*/ void json_write(char **buffer, int* buffer_size, int* buffer_end, int level, const char* s, int quoted) { int len, remain, xlevel; len = strlen(s); remain = *buffer_size - *buffer_end; assert(remain >= 0); xlevel = 2*level; while (10 + xlevel + 3*len > remain) { // reallocate the buffer int new_buffer_size = 2*(*buffer_size); if (new_buffer_size < 4*1024) new_buffer_size = 4*1024; //printf("reallocate: len %d, size %d, remain %d, allocate %d\n", len, *buffer_size, remain, new_buffer_size); assert(new_buffer_size > *buffer_size); *buffer = (char *)realloc(*buffer, new_buffer_size); assert(*buffer); *buffer_size = new_buffer_size; remain = *buffer_size - *buffer_end; assert(remain >= 0); } if (xlevel) { int i; for (i=0; i 0); } static void json_ensure_decimal_dot(char *str) { // sprintf "%.2e" can result in strings like "1,23e6" if // the user's locale has a comma for the decimal spearator. // However the JSON RFC requires a dot for the decimal. // This approach of "sprintf-then-replace" is much safer than // changing the user's locale, and much faster than the C++ // approach of using a stringstream that we "imbue" with a C locale. char* comma = strchr(str, ','); if (comma != nullptr) { *comma = '.'; } } static void json_write_data(char **buffer, int* buffer_size, int* buffer_end, int level, const KEY* key, const char* p) { char str[256]; switch (key->type) { case TID_UINT8: sprintf(str, "%u", *(unsigned char*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT8: sprintf(str, "%d", *(char*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_CHAR: sprintf(str, "%c", *(char*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 1); break; case TID_UINT16: sprintf(str, "\"0x%04x\"", *(WORD*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT16: sprintf(str, "%d", *(short*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_UINT32: sprintf(str, "\"0x%08x\"", *(DWORD*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT32: sprintf(str, "%d", *(int*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_UINT64: // encode as hex string sprintf(str, "\"0x%016llx\"", *(UINT64*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT64: // encode as decimal string sprintf(str, "\"%lld\"", *(INT64*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_BOOL: if (*(int*)p) json_write(buffer, buffer_size, buffer_end, 0, "true", 0); else json_write(buffer, buffer_size, buffer_end, 0, "false", 0); break; case TID_FLOAT: { float flt = (*(float*)p); if (isnan(flt)) json_write(buffer, buffer_size, buffer_end, 0, "\"NaN\"", 0); else if (isinf(flt)) { if (flt > 0) json_write(buffer, buffer_size, buffer_end, 0, "\"Infinity\"", 0); else json_write(buffer, buffer_size, buffer_end, 0, "\"-Infinity\"", 0); } else if (flt == 0) json_write(buffer, buffer_size, buffer_end, 0, "0", 0); else if (flt == (int)flt) { sprintf(str, "%.0f", flt); json_ensure_decimal_dot(str); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } else { sprintf(str, "%.7e", flt); json_ensure_decimal_dot(str); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } break; } case TID_DOUBLE: { double dbl = (*(double*)p); if (isnan(dbl)) json_write(buffer, buffer_size, buffer_end, 0, "\"NaN\"", 0); else if (isinf(dbl)) { if (dbl > 0) json_write(buffer, buffer_size, buffer_end, 0, "\"Infinity\"", 0); else json_write(buffer, buffer_size, buffer_end, 0, "\"-Infinity\"", 0); } else if (dbl == 0) json_write(buffer, buffer_size, buffer_end, 0, "0", 0); else if (dbl == (int)dbl) { sprintf(str, "%.0f", dbl); json_ensure_decimal_dot(str); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } else { sprintf(str, "%.16e", dbl); json_ensure_decimal_dot(str); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } break; } case TID_BITFIELD: json_write(buffer, buffer_size, buffer_end, 0, "(TID_BITFIELD value)", 1); break; case TID_STRING: // data is already NUL terminated // p[key.item_size-1] = 0; // make sure string is NUL terminated! json_write(buffer, buffer_size, buffer_end, 0, p, 1); break; case TID_ARRAY: json_write(buffer, buffer_size, buffer_end, 0, "(TID_ARRAY value)", 1); break; case TID_STRUCT: json_write(buffer, buffer_size, buffer_end, 0, "(TID_STRUCT value)", 1); break; case TID_KEY: json_write(buffer, buffer_size, buffer_end, 0, "{ }", 0); break; case TID_LINK: // data is already NUL terminated // p[key.item_size-1] = 0; // make sure string is NUL terminated! json_write(buffer, buffer_size, buffer_end, 0, p, 1); break; default: json_write(buffer, buffer_size, buffer_end, 0, "(TID_UNKNOWN value)", 1); } } static void json_write_key(HNDLE hDB, HNDLE hKey, const KEY* key, const char* link_path, char **buffer, int* buffer_size, int* buffer_end) { char str[256]; // not used to store anything long, only numeric values like: "item_size: 100" json_write(buffer, buffer_size, buffer_end, 0, "{ ", 0); sprintf(str, "\"type\" : %d", key->type); json_write(buffer, buffer_size, buffer_end, 0, str, 0); if (link_path) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); json_write(buffer, buffer_size, buffer_end, 0, "link", 1); json_write(buffer, buffer_size, buffer_end, 0, ": ", 0); json_write(buffer, buffer_size, buffer_end, 0, link_path, 1); } if (key->num_values > 1) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"num_values\" : %d", key->num_values); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } if (key->type == TID_STRING) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"item_size\" : %d", key->item_size); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } if (key->notify_count > 0) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"notify_count\" : %d", key->notify_count); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"access_mode\" : %d", key->access_mode); json_write(buffer, buffer_size, buffer_end, 0, str, 0); json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"last_written\" : %d", key->last_written); json_write(buffer, buffer_size, buffer_end, 0, str, 0); json_write(buffer, buffer_size, buffer_end, 0, " ", 0); json_write(buffer, buffer_size, buffer_end, 0, "}", 0); } static int db_save_json_key_obsolete(HNDLE hDB, HNDLE hKey, INT level, char **buffer, int* buffer_size, int* buffer_end, int save_keys, int follow_links, int recurse) { INT i, size, status; char *data; KEY key; KEY link_key; char link_path[MAX_ODB_PATH]; int omit_top_level_braces = 0; //printf("db_save_json_key: key %d, level %d, save_keys %d, follow_links %d, recurse %d\n", hKey, level, save_keys, follow_links, recurse); if (level < 0) { level = 0; omit_top_level_braces = 1; } status = db_get_link(hDB, hKey, &key); if (status != DB_SUCCESS) return status; link_key = key; if (key.type == TID_LINK) { size = sizeof(link_path); status = db_get_data(hDB, hKey, link_path, &size, TID_LINK); if (status != DB_SUCCESS) return status; if (follow_links) { status = db_find_key(hDB, 0, link_path, &hKey); if (status != DB_SUCCESS) return status; status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; } } //printf("key [%s] link [%s], type %d, link %d\n", key.name, link_key.name, key.type, link_key.type); if (key.type == TID_KEY && (recurse || level<=0)) { int idx = 0; int do_close_curly_bracket = 0; if (level == 0 && !omit_top_level_braces) { json_write(buffer, buffer_size, buffer_end, 0, "{\n", 0); do_close_curly_bracket = 1; } else if (level > 0) { json_write(buffer, buffer_size, buffer_end, level, link_key.name, 1); json_write(buffer, buffer_size, buffer_end, 0, " : {\n", 0); do_close_curly_bracket = 1; } if (level > 100) { std::string path = db_get_path(hDB, hKey); json_write(buffer, buffer_size, buffer_end, 0, "/error", 1); json_write(buffer, buffer_size, buffer_end, 0, " : ", 0); json_write(buffer, buffer_size, buffer_end, 0, "max nesting level exceed", 1); cm_msg(MERROR, "db_save_json_key", "max nesting level exceeded at \"%s\", check for symlink loops in this subtree", path.c_str()); } else { HNDLE hSubkey; for (;; idx++) { db_enum_link(hDB, hKey, idx, &hSubkey); if (!hSubkey) break; if (idx != 0) { json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } /* save subtree */ status = db_save_json_key_obsolete(hDB, hSubkey, level + 1, buffer, buffer_size, buffer_end, save_keys, follow_links, recurse); if (status != DB_SUCCESS) return status; } } if (do_close_curly_bracket) { if (idx > 0) json_write(buffer, buffer_size, buffer_end, 0, "\n", 0); json_write(buffer, buffer_size, buffer_end, level, "}", 0); } } else { if (save_keys && level == 0) { json_write(buffer, buffer_size, buffer_end, 0, "{\n", 0); } /* save key value */ if (save_keys == 1) { char str[NAME_LENGTH+15]; sprintf(str, "%s/key", link_key.name); json_write(buffer, buffer_size, buffer_end, level, str, 1); json_write(buffer, buffer_size, buffer_end, 0, " : { ", 0); sprintf(str, "\"type\" : %d", key.type); json_write(buffer, buffer_size, buffer_end, 0, str, 0); if (link_key.type == TID_LINK && follow_links) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); json_write(buffer, buffer_size, buffer_end, 0, "link", 1); json_write(buffer, buffer_size, buffer_end, 0, ": ", 0); json_write(buffer, buffer_size, buffer_end, 0, link_path, 1); } if (key.num_values > 1) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"num_values\" : %d", key.num_values); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } if (key.type == TID_STRING || key.type == TID_LINK) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"item_size\" : %d", key.item_size); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } if (key.notify_count > 0) { json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"notify_count\" : %d", key.notify_count); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"access_mode\" : %d", key.access_mode); json_write(buffer, buffer_size, buffer_end, 0, str, 0); json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); sprintf(str, "\"last_written\" : %d", key.last_written); json_write(buffer, buffer_size, buffer_end, 0, str, 0); json_write(buffer, buffer_size, buffer_end, 0, " ", 0); json_write(buffer, buffer_size, buffer_end, 0, "}", 0); json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } if (save_keys == 2) { char str[NAME_LENGTH+15]; sprintf(str, "%s/last_written", link_key.name); json_write(buffer, buffer_size, buffer_end, level, str, 1); sprintf(str, " : %d", key.last_written); json_write(buffer, buffer_size, buffer_end, 0, str, 0); json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } if (save_keys) { json_write(buffer, buffer_size, buffer_end, level, link_key.name, 1); json_write(buffer, buffer_size, buffer_end, 0, " : ", 0); } if (key.num_values > 1) { json_write(buffer, buffer_size, buffer_end, 0, "[ ", 0); } size = key.total_size; data = (char *) malloc(size); if (data == NULL) { cm_msg(MERROR, "db_save_json_key", "cannot allocate data buffer for %d bytes", size); return DB_NO_MEMORY; } if (key.type != TID_KEY) { if (follow_links) status = db_get_data(hDB, hKey, data, &size, key.type); else status = db_get_link_data(hDB, hKey, data, &size, key.type); if (status != DB_SUCCESS) return status; } for (i = 0; i < key.num_values; i++) { char str[256]; char *p = data + key.item_size*i; if (i != 0) json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); switch (key.type) { case TID_UINT8: sprintf(str, "%u", *(unsigned char*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT8: sprintf(str, "%d", *(char*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_CHAR: sprintf(str, "%c", *(char*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 1); break; case TID_UINT16: sprintf(str, "\"0x%04x\"", *(WORD*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT16: sprintf(str, "%d", *(short*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_UINT32: sprintf(str, "\"0x%08x\"", *(DWORD*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT32: sprintf(str, "%d", *(int*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_UINT64: sprintf(str, "\"0x%08llx\"", *(UINT64*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_INT64: sprintf(str, "%lld", *(INT64*)p); json_write(buffer, buffer_size, buffer_end, 0, str, 0); break; case TID_BOOL: if (*(int*)p) json_write(buffer, buffer_size, buffer_end, 0, "true", 0); else json_write(buffer, buffer_size, buffer_end, 0, "false", 0); break; case TID_FLOAT: { float flt = (*(float*)p); if (isnan(flt)) json_write(buffer, buffer_size, buffer_end, 0, "\"NaN\"", 0); else if (isinf(flt)) { if (flt > 0) json_write(buffer, buffer_size, buffer_end, 0, "\"Infinity\"", 0); else json_write(buffer, buffer_size, buffer_end, 0, "\"-Infinity\"", 0); } else if (flt == 0) json_write(buffer, buffer_size, buffer_end, 0, "0", 0); else if (flt == (int)flt) { sprintf(str, "%.0f", flt); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } else { sprintf(str, "%.7e", flt); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } break; } case TID_DOUBLE: { double dbl = (*(double*)p); if (isnan(dbl)) json_write(buffer, buffer_size, buffer_end, 0, "\"NaN\"", 0); else if (isinf(dbl)) { if (dbl > 0) json_write(buffer, buffer_size, buffer_end, 0, "\"Infinity\"", 0); else json_write(buffer, buffer_size, buffer_end, 0, "\"-Infinity\"", 0); } else if (dbl == 0) json_write(buffer, buffer_size, buffer_end, 0, "0", 0); else if (dbl == (int)dbl) { sprintf(str, "%.0f", dbl); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } else { sprintf(str, "%.16e", dbl); json_write(buffer, buffer_size, buffer_end, 0, str, 0); } break; } case TID_BITFIELD: json_write(buffer, buffer_size, buffer_end, 0, "(TID_BITFIELD value)", 1); break; case TID_STRING: p[key.item_size-1] = 0; // make sure string is NUL terminated! json_write(buffer, buffer_size, buffer_end, 0, p, 1); break; case TID_ARRAY: json_write(buffer, buffer_size, buffer_end, 0, "(TID_ARRAY value)", 1); break; case TID_STRUCT: json_write(buffer, buffer_size, buffer_end, 0, "(TID_STRUCT value)", 1); break; case TID_KEY: json_write(buffer, buffer_size, buffer_end, 0, "{ }", 0); break; case TID_LINK: p[key.item_size-1] = 0; // make sure string is NUL terminated! json_write(buffer, buffer_size, buffer_end, 0, p, 1); break; default: json_write(buffer, buffer_size, buffer_end, 0, "(TID_UNKNOWN value)", 1); } } if (key.num_values > 1) { json_write(buffer, buffer_size, buffer_end, 0, " ]", 0); } else { json_write(buffer, buffer_size, buffer_end, 0, "", 0); } free(data); data = NULL; if (save_keys && level == 0) { json_write(buffer, buffer_size, buffer_end, 0, "\n}", 0); } } return DB_SUCCESS; } /********************************************************************/ /** Copy an ODB array in JSON format to a buffer @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key @param buffer returns pointer to ASCII buffer with ODB contents @param buffer_size returns size of ASCII buffer @param buffer_end returns number of bytes contained in buffer @return DB_SUCCESS, DB_NO_MEMORY */ INT db_copy_json_array(HNDLE hDB, HNDLE hKey, char **buffer, int* buffer_size, int* buffer_end) { int size, asize; int status; char* data; int i; KEY key; status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; assert(key.type != TID_KEY); if (key.num_values > 1) { json_write(buffer, buffer_size, buffer_end, 0, "[ ", 0); } size = key.total_size; asize = size; if (asize < 1024) asize = 1024; data = (char *) malloc(asize); if (data == NULL) { cm_msg(MERROR, "db_save_json_key_data", "cannot allocate data buffer for %d bytes", asize); return DB_NO_MEMORY; } data[0] = 0; // protect against TID_STRING that has key.total_size == 0. status = db_get_data(hDB, hKey, data, &size, key.type); if (status != DB_SUCCESS) { free(data); return status; } for (i = 0; i < key.num_values; i++) { char *p = data + key.item_size*i; if (i != 0) json_write(buffer, buffer_size, buffer_end, 0, ", ", 0); json_write_data(buffer, buffer_size, buffer_end, 0, &key, p); } if (key.num_values > 1) { json_write(buffer, buffer_size, buffer_end, 0, " ]", 0); } free(data); data = NULL; return DB_SUCCESS; } /********************************************************************/ /** Copy an ODB array element in JSON format to a buffer @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key @param index Array index @param buffer returns pointer to ASCII buffer with ODB contents @param buffer_size returns size of ASCII buffer @param buffer_end returns number of bytes contained in buffer @return DB_SUCCESS, DB_NO_MEMORY */ INT db_copy_json_index(HNDLE hDB, HNDLE hKey, int index, char **buffer, int* buffer_size, int* buffer_end) { int status; KEY key; status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; int size = key.item_size; char* data = (char*)malloc(size + 1); // extra byte for string NUL termination assert(data != NULL); status = db_get_data_index(hDB, hKey, data, &size, index, key.type); if (status != DB_SUCCESS) { free(data); return status; } assert(size <= key.item_size); data[key.item_size] = 0; // make sure data is NUL terminated, in case of strings. json_write_data(buffer, buffer_size, buffer_end, 0, &key, data); free(data); return DB_SUCCESS; } static int json_write_bare_key(HNDLE hDB, HNDLE hLink, const KEY& link, char **buffer, int *buffer_size, int *buffer_end, int level, int flags, time_t timestamp, bool need_comma) { int status; char link_buf[MAX_ODB_PATH]; // link_path points to link_buf char* link_path = NULL; HNDLE hLinkTarget = hLink; if (link.type == TID_LINK) { int size = sizeof(link_buf); status = db_get_link_data(hDB, hLink, link_buf, &size, TID_LINK); if (status != DB_SUCCESS) return status; //printf("link size %d, len %d, data [%s]\n", size, (int)strlen(link_buf), link_buf); if ((size > 0) && (strlen(link_buf) > 0)) { link_path = link_buf; // resolve the link, unless it is a link to an array element if ((flags & JSFLAG_FOLLOW_LINKS) && strchr(link_path, '[') == NULL) { status = db_find_key(hDB, 0, link_path, &hLinkTarget); if (status != DB_SUCCESS) { // dangling link to nowhere hLinkTarget = hLink; } } } } KEY link_target; status = db_get_key(hDB, hLinkTarget, &link_target); if (status != DB_SUCCESS) return status; if (flags & JSFLAG_OMIT_OLD) { if (link_target.last_written) { if (link_target.last_written < timestamp) { // omit old data return DB_SUCCESS; } } } if (need_comma) { json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } else { json_write(buffer, buffer_size, buffer_end, 0, "\n", 0); } char link_name[MAX_ODB_PATH]; mstrlcpy(link_name, link.name, sizeof(link_name)); if (flags & JSFLAG_LOWERCASE) { for (int i=0; (i<(int)sizeof(link.name)) && link.name[i]; i++) link_name[i] = tolower(link.name[i]); } if ((flags & JSFLAG_LOWERCASE) && !(flags & JSFLAG_OMIT_NAMES)) { char buf[MAX_ODB_PATH]; mstrlcpy(buf, link_name, sizeof(buf)); mstrlcat(buf, "/name", sizeof(buf)); json_write(buffer, buffer_size, buffer_end, level, buf, 1); json_write(buffer, buffer_size, buffer_end, 0, " : " , 0); json_write(buffer, buffer_size, buffer_end, 0, link.name, 1); json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } if (link.type != TID_KEY && (flags & JSFLAG_SAVE_KEYS)) { char buf[MAX_ODB_PATH]; mstrlcpy(buf, link_name, sizeof(buf)); mstrlcat(buf, "/key", sizeof(buf)); json_write(buffer, buffer_size, buffer_end, level, buf, 1); json_write(buffer, buffer_size, buffer_end, 0, " : " , 0); json_write_key(hDB, hLink, &link_target, link_path, buffer, buffer_size, buffer_end); json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } else if ((link_target.type != TID_KEY) && !(flags & JSFLAG_OMIT_LAST_WRITTEN)) { char buf[MAX_ODB_PATH]; mstrlcpy(buf, link_name, sizeof(buf)); mstrlcat(buf, "/last_written", sizeof(buf)); json_write(buffer, buffer_size, buffer_end, level, buf, 1); json_write(buffer, buffer_size, buffer_end, 0, " : " , 0); sprintf(buf, "%d", link_target.last_written); json_write(buffer, buffer_size, buffer_end, 0, buf, 0); json_write(buffer, buffer_size, buffer_end, 0, ",\n", 0); } json_write(buffer, buffer_size, buffer_end, level, link_name, 1); json_write(buffer, buffer_size, buffer_end, 0, " : " , 0); if (link_target.type == TID_KEY && !(flags & JSFLAG_RECURSE)) { json_write(buffer, buffer_size, buffer_end, 0, "{ }" , 0); } else { status = json_write_anything(hDB, hLinkTarget, buffer, buffer_size, buffer_end, level, 0, flags, timestamp); if (status != DB_SUCCESS) return status; } return DB_SUCCESS; } int json_write_bare_subdir(HNDLE hDB, HNDLE hKey, char **buffer, int *buffer_size, int *buffer_end, int level, int flags, time_t timestamp) { if (level > MAX_ODB_PATH/2) { // max nesting level is limited by max odb path, where each subdirectory takes // at least 2 bytes - 1 byte directory name and 1 byte for "/" cm_msg(MERROR, "json_write_bare_subdir", "Max ODB subdirectory nesting level exceeded %d", level); return DB_TRUNCATED; } for (int i=0; ; i++) { int status; HNDLE hLink; status = db_enum_link(hDB, hKey, i, &hLink); if (status != DB_SUCCESS && !hLink) break; KEY link; status = db_get_link(hDB, hLink, &link); if (status != DB_SUCCESS) return status; bool need_comma = (i!=0); status = json_write_bare_key(hDB, hLink, link, buffer, buffer_size, buffer_end, level, flags, timestamp, need_comma); if (status != DB_SUCCESS) return status; } return DB_SUCCESS; } int EXPRT json_write_anything(HNDLE hDB, HNDLE hKey, char **buffer, int *buffer_size, int *buffer_end, int level, int must_be_subdir, int flags, time_t timestamp) { int status; KEY key; status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; if (key.type == TID_KEY) { json_write(buffer, buffer_size, buffer_end, 0, "{", 0); status = json_write_bare_subdir(hDB, hKey, buffer, buffer_size, buffer_end, level+1, flags, timestamp); if (status != DB_SUCCESS) return status; json_write(buffer, buffer_size, buffer_end, 0, "\n", 0); json_write(buffer, buffer_size, buffer_end, level, "}", 0); } else if (must_be_subdir) { json_write(buffer, buffer_size, buffer_end, 0, "{", 0); status = json_write_bare_key(hDB, hKey, key, buffer, buffer_size, buffer_end, level+1, flags, timestamp, false); if (status != DB_SUCCESS) return status; json_write(buffer, buffer_size, buffer_end, 0, "\n", 0); json_write(buffer, buffer_size, buffer_end, level, "}", 0); } else { status = db_copy_json_array(hDB, hKey, buffer, buffer_size, buffer_end); if (status != DB_SUCCESS) return status; } return DB_SUCCESS; } INT db_copy_json_ls(HNDLE hDB, HNDLE hKey, char **buffer, int* buffer_size, int* buffer_end) { int status; bool unlock = false; if (!rpc_is_remote()) { status = db_lock_database(hDB); if (status != DB_SUCCESS) { return status; } unlock = true; } status = json_write_anything(hDB, hKey, buffer, buffer_size, buffer_end, JS_LEVEL_0, JS_MUST_BE_SUBDIR, JSFLAG_SAVE_KEYS|JSFLAG_FOLLOW_LINKS, 0); if (unlock) { db_unlock_database(hDB); } return status; } INT db_copy_json_values(HNDLE hDB, HNDLE hKey, char **buffer, int* buffer_size, int* buffer_end, int omit_names, int omit_last_written, time_t omit_old_timestamp, int preserve_case) { int status; int flags = JSFLAG_FOLLOW_LINKS|JSFLAG_RECURSE; if (omit_names) flags |= JSFLAG_OMIT_NAMES; if (omit_last_written) flags |= JSFLAG_OMIT_LAST_WRITTEN; if (omit_old_timestamp) flags |= JSFLAG_OMIT_OLD; if (!preserve_case) flags |= JSFLAG_LOWERCASE; bool unlock = false; if (!rpc_is_remote()) { status = db_lock_database(hDB); if (status != DB_SUCCESS) { return status; } unlock = true; } status = json_write_anything(hDB, hKey, buffer, buffer_size, buffer_end, JS_LEVEL_0, 0, flags, omit_old_timestamp); if (unlock) { db_unlock_database(hDB); } return status; } INT db_copy_json_save(HNDLE hDB, HNDLE hKey, char **buffer, int* buffer_size, int* buffer_end) { int status; bool unlock = false; if (!rpc_is_remote()) { status = db_lock_database(hDB); if (status != DB_SUCCESS) { return status; } unlock = true; } status = json_write_anything(hDB, hKey, buffer, buffer_size, buffer_end, JS_LEVEL_0, JS_MUST_BE_SUBDIR, JSFLAG_SAVE_KEYS|JSFLAG_RECURSE, 0); if (unlock) { db_unlock_database(hDB); } return status; } /********************************************************************/ /** Copy an ODB subtree in JSON format to a buffer @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param buffer returns pointer to ASCII buffer with ODB contents @param buffer_size returns size of ASCII buffer @param buffer_end returns number of bytes contained in buffer @return DB_SUCCESS, DB_NO_MEMORY */ INT db_copy_json_obsolete(HNDLE hDB, HNDLE hKey, char **buffer, int* buffer_size, int* buffer_end, int save_keys, int follow_links, int recurse) { db_save_json_key_obsolete(hDB, hKey, 0, buffer, buffer_size, buffer_end, save_keys, follow_links, recurse); json_write(buffer, buffer_size, buffer_end, 0, "\n", 0); return DB_SUCCESS; } /********************************************************************/ /** Save a branch of a database to an .json file This function is used by the ODBEdit command save to write the contents of the ODB into a JSON file. Data of the whole ODB can be saved (hkey equal zero) or only a sub-tree. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param filename Filename of .json file. @return DB_SUCCESS, DB_FILE_ERROR */ INT db_save_json(HNDLE hDB, HNDLE hKey, const char *filename, int flags) { INT status; /* open file */ FILE *fp = fopen(filename, "w"); if (fp == NULL) { cm_msg(MERROR, "db_save_json", "Cannot open file \"%s\", fopen() errno %d (%s)", filename, errno, strerror(errno)); return DB_FILE_ERROR; } bool unlock = false; if (!rpc_is_remote()) { status = db_lock_database(hDB); if (status != DB_SUCCESS) { return status; } unlock = true; } std::string path = db_get_path(hDB, hKey); bool emptySubdir = false; HNDLE hSubKey; status = db_enum_link(hDB, hKey, 0, &hSubKey); if (status == DB_NO_MORE_SUBKEYS) emptySubdir = true; char* buffer = NULL; int buffer_size = 0; int buffer_end = 0; json_write(&buffer, &buffer_size, &buffer_end, 0, "{\n", 0); json_write(&buffer, &buffer_size, &buffer_end, 1, "/MIDAS version", 1); json_write(&buffer, &buffer_size, &buffer_end, 0, " : ", 0); json_write(&buffer, &buffer_size, &buffer_end, 0, MIDAS_VERSION, 1); json_write(&buffer, &buffer_size, &buffer_end, 0, ",\n", 0); json_write(&buffer, &buffer_size, &buffer_end, 1, "/MIDAS git revision", 1); json_write(&buffer, &buffer_size, &buffer_end, 0, " : ", 0); json_write(&buffer, &buffer_size, &buffer_end, 0, GIT_REVISION, 1); json_write(&buffer, &buffer_size, &buffer_end, 0, ",\n", 0); json_write(&buffer, &buffer_size, &buffer_end, 1, "/filename", 1); json_write(&buffer, &buffer_size, &buffer_end, 0, " : ", 0); json_write(&buffer, &buffer_size, &buffer_end, 0, filename, 1); json_write(&buffer, &buffer_size, &buffer_end, 0, ",\n", 0); json_write(&buffer, &buffer_size, &buffer_end, 1, "/ODB path", 1); json_write(&buffer, &buffer_size, &buffer_end, 0, " : ", 0); json_write(&buffer, &buffer_size, &buffer_end, 0, path.c_str(), 1); if (emptySubdir) json_write(&buffer, &buffer_size, &buffer_end, 0, "", 0); else json_write(&buffer, &buffer_size, &buffer_end, 0, ",\n", 0); //status = db_save_json_key_obsolete(hDB, hKey, -1, &buffer, &buffer_size, &buffer_end, 1, 0, 1); status = json_write_bare_subdir(hDB, hKey, &buffer, &buffer_size, &buffer_end, JS_LEVEL_1, flags, 0); json_write(&buffer, &buffer_size, &buffer_end, 0, "\n}\n", 0); if (unlock) { db_unlock_database(hDB); } if (status == DB_SUCCESS) { if (buffer) { size_t wr = fwrite(buffer, 1, buffer_end, fp); if (wr != (size_t)buffer_end) { cm_msg(MERROR, "db_save_json", "Cannot write to file \"%s\", fwrite() errno %d (%s)", filename, errno, strerror(errno)); free(buffer); fclose(fp); return DB_FILE_ERROR; } } } if (buffer) free(buffer); fclose(fp); return DB_SUCCESS; } /********************************************************************/ /** Save a branch of a database to a C structure .H file @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param file_name Filename of .ODB file. @param struct_name Name of structure. If struct_name == NULL, the name of the key is used. @param append If TRUE, append to end of existing file @return DB_SUCCESS, DB_INVALID_HANDLE, DB_FILE_ERROR */ INT db_save_struct(HNDLE hDB, HNDLE hKey, const char *file_name, const char *struct_name, BOOL append) { KEY key; char str[100], line[10+100]; INT status, i, fh; int wr, size; /* open file */ fh = open(file_name, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0644); if (fh == -1) { cm_msg(MERROR, "db_save_struct", "Cannot open file\"%s\"", file_name); return DB_FILE_ERROR; } status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_save_struct", "cannot find key"); return DB_INVALID_HANDLE; } sprintf(line, "typedef struct {\n"); size = strlen(line); wr = write(fh, line, size); if (wr != size) { cm_msg(MERROR, "db_save_struct", "file \"%s\" write error: write(%d) returned %d, errno %d (%s)", file_name, size, wr, errno, strerror(errno)); close(fh); return DB_FILE_ERROR; } db_save_tree_struct(hDB, hKey, fh, 0); if (struct_name && struct_name[0]) mstrlcpy(str, struct_name, sizeof(str)); else mstrlcpy(str, key.name, sizeof(str)); name2c(str); for (i = 0; i < (int) strlen(str); i++) str[i] = (char) toupper(str[i]); sprintf(line, "} %s;\n\n", str); size = strlen(line); wr = write(fh, line, size); if (wr != size) { cm_msg(MERROR, "db_save_struct", "file \"%s\" write error: write(%d) returned %d, errno %d (%s)", file_name, size, wr, errno, strerror(errno)); close(fh); return DB_FILE_ERROR; } close(fh); return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_save_string(HNDLE hDB, HNDLE hKey, const char *file_name, const char *string_name, BOOL append) /********************************************************************\ Routine: db_save_string Purpose: Save a branch of a database as a string which can be used by db_create_record. Input: HNDLE hDB Handle to the database HNDLE hKey Handle of key to start, 0 for root int fh File handle to write to char string_name Name of string. If struct_name == NULL, the name of the key is used. Output: none Function value: DB_SUCCESS Successful completion DB_INVALID_HANDLE Database handle is invalid \********************************************************************/ { KEY key; char str[256], line[50+256]; INT status, i, size, fh, buffer_size; char *buffer = NULL, *pc; int wr; /* open file */ fh = open(file_name, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0644); if (fh == -1) { cm_msg(MERROR, "db_save_string", "Cannot open file\"%s\"", file_name); return DB_FILE_ERROR; } status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_save_string", "cannot find key"); return DB_INVALID_HANDLE; } if (string_name && string_name[0]) strcpy(str, string_name); else strcpy(str, key.name); name2c(str); for (i = 0; i < (int) strlen(str); i++) str[i] = (char) toupper(str[i]); sprintf(line, "#define %s(_name) const char *_name[] = {\\\n", str); size = strlen(line); wr = write(fh, line, size); if (wr != size) { cm_msg(MERROR, "db_save", "file \"%s\" write error: write(%d) returned %d, errno %d (%s)", file_name, size, wr, errno, strerror(errno)); close(fh); if (buffer) free(buffer); return DB_FILE_ERROR; } buffer_size = 10000; do { buffer = (char *) malloc(buffer_size); if (buffer == NULL) { cm_msg(MERROR, "db_save", "cannot allocate ODB dump buffer"); break; } size = buffer_size; status = db_copy(hDB, hKey, buffer, &size, ""); if (status != DB_TRUNCATED) break; /* increase buffer size if truncated */ free(buffer); buffer = NULL; buffer_size *= 2; } while (1); pc = buffer; do { i = 0; line[i++] = '"'; while (*pc != '\n' && *pc != 0) { if (*pc == '\"' || *pc == '\'') line[i++] = '\\'; line[i++] = *pc++; } strcpy(&line[i], "\",\\\n"); if (i > 0) { size = strlen(line); wr = write(fh, line, size); if (wr != size) { cm_msg(MERROR, "db_save", "file \"%s\" write error: write(%d) returned %d, errno %d (%s)", file_name, size, wr, errno, strerror(errno)); close(fh); if (buffer) free(buffer); return DB_FILE_ERROR; } } if (*pc == '\n') pc++; } while (*pc); sprintf(line, "NULL }\n\n"); size = strlen(line); wr = write(fh, line, size); if (wr != size) { cm_msg(MERROR, "db_save", "file \"%s\" write error: write(%d) returned %d, errno %d (%s)", file_name, size, wr, errno, strerror(errno)); close(fh); if (buffer) free(buffer); return DB_FILE_ERROR; } close(fh); free(buffer); return DB_SUCCESS; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Convert a database value to a string according to its type. This function is a convenient way to convert a binary ODB value into a string depending on its type if is not known at compile time. If it is known, the normal sprintf() function can be used. \code ... for (j=0 ; j"); else switch (type) { case TID_UINT8: sprintf(string, "%d", *(((BYTE *) data) + idx)); break; case TID_INT8: sprintf(string, "%d", *(((char *) data) + idx)); break; case TID_CHAR: sprintf(string, "%c", *(((char *) data) + idx)); break; case TID_UINT16: sprintf(string, "%u", *(((WORD *) data) + idx)); break; case TID_INT16: sprintf(string, "%d", *(((short *) data) + idx)); break; case TID_UINT32: sprintf(string, "%u", *(((DWORD *) data) + idx)); break; case TID_INT32: sprintf(string, "%d", *(((INT *) data) + idx)); break; case TID_UINT64: sprintf(string, "%llu", *(((UINT64 *) data) + idx)); break; case TID_INT64: sprintf(string, "%lld", *(((INT64 *) data) + idx)); break; case TID_BOOL: sprintf(string, "%c", *(((BOOL *) data) + idx) ? 'y' : 'n'); break; case TID_FLOAT: if (ss_isnan(*(((float *) data) + idx))) sprintf(string, "NAN"); else sprintf(string, "%.7g", *(((float *) data) + idx)); break; case TID_DOUBLE: if (ss_isnan(*(((double *) data) + idx))) sprintf(string, "NAN"); else sprintf(string, "%.16lg", *(((double *) data) + idx)); break; case TID_BITFIELD: sprintf(string, "%u", *(((DWORD *) data) + idx)); break; case TID_STRING: case TID_LINK: mstrlcpy(string, ((char *) data) + data_size * idx, MAX_STRING_LENGTH); break; default: sprintf(string, ""); break; } return DB_SUCCESS; } /********************************************************************/ /** Same as db_sprintf, but with additional format parameter @param string output ASCII string of data. @param format Format specifier passed to sprintf() @param data Value data. @param data_size Size of single data element. @param idx Index for array data. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS */ INT db_sprintff(char *string, const char *format, const void *data, INT data_size, INT idx, DWORD type) { if (data_size == 0) sprintf(string, ""); else switch (type) { case TID_UINT8: sprintf(string, format, *(((BYTE *) data) + idx)); break; case TID_INT8: sprintf(string, format, *(((char *) data) + idx)); break; case TID_CHAR: sprintf(string, format, *(((char *) data) + idx)); break; case TID_UINT16: sprintf(string, format, *(((WORD *) data) + idx)); break; case TID_INT16: sprintf(string, format, *(((short *) data) + idx)); break; case TID_UINT32: sprintf(string, format, *(((DWORD *) data) + idx)); break; case TID_INT32: sprintf(string, format, *(((INT *) data) + idx)); break; case TID_UINT64: sprintf(string, format, *(((UINT64 *) data) + idx)); break; case TID_INT64: sprintf(string, format, *(((INT64 *) data) + idx)); break; case TID_BOOL: sprintf(string, format, *(((BOOL *) data) + idx) ? 'y' : 'n'); break; case TID_FLOAT: if (ss_isnan(*(((float *) data) + idx))) sprintf(string, "NAN"); else sprintf(string, format, *(((float *) data) + idx)); break; case TID_DOUBLE: if (ss_isnan(*(((double *) data) + idx))) sprintf(string, "NAN"); else sprintf(string, format, *(((double *) data) + idx)); break; case TID_BITFIELD: sprintf(string, format, *(((DWORD *) data) + idx)); break; case TID_STRING: case TID_LINK: mstrlcpy(string, ((char *) data) + data_size * idx, MAX_STRING_LENGTH); break; default: sprintf(string, ""); break; } return DB_SUCCESS; } /*------------------------------------------------------------------*/ INT db_sprintfh(char *string, const void *data, INT data_size, INT idx, DWORD type) /********************************************************************\ Routine: db_sprintfh Purpose: Convert a database value to a string according to its type in hex format Input: void *data Value data INT idx Index for array data INT data_size Size of single data element DWORD type Valye type, one of TID_xxx Output: char *string ASCII string of data Function value: DB_SUCCESS Successful completion \********************************************************************/ { if (data_size == 0) sprintf(string, ""); else switch (type) { case TID_UINT8: sprintf(string, "0x%X", *(((BYTE *) data) + idx)); break; case TID_INT8: sprintf(string, "0x%X", *(((char *) data) + idx)); break; case TID_CHAR: sprintf(string, "%c", *(((char *) data) + idx)); break; case TID_UINT16: sprintf(string, "0x%X", *(((WORD *) data) + idx)); break; case TID_INT16: sprintf(string, "0x%hX", *(((short *) data) + idx)); break; case TID_UINT32: sprintf(string, "0x%X", *(((DWORD *) data) + idx)); break; case TID_INT32: sprintf(string, "0x%X", *(((INT *) data) + idx)); break; case TID_UINT64: sprintf(string, "0x%llX", *(((UINT64 *) data) + idx)); break; case TID_INT64: sprintf(string, "0x%llX", *(((INT64 *) data) + idx)); break; case TID_BOOL: sprintf(string, "%c", *(((BOOL *) data) + idx) ? 'y' : 'n'); break; case TID_FLOAT: if (ss_isnan(*(((float *) data) + idx))) sprintf(string, "NAN"); else sprintf(string, "%.7g", *(((float *) data) + idx)); break; case TID_DOUBLE: if (ss_isnan(*(((double *) data) + idx))) sprintf(string, "NAN"); else sprintf(string, "%.16lg", *(((double *) data) + idx)); break; case TID_BITFIELD: sprintf(string, "0x%X", *(((DWORD *) data) + idx)); break; case TID_STRING: case TID_LINK: sprintf(string, "%s", ((char *) data) + data_size * idx); break; default: sprintf(string, ""); break; } return DB_SUCCESS; } /********************************************************************/ /** Convert a database value to a string according to its type. This function is a convenient way to convert a binary ODB value into a string depending on its type if is not known at compile time. If it is known, the normal sprintf() function can be used. \code ... for (j=0 ; j"; } else { char buf[256]; switch (type) { case TID_UINT8: sprintf(buf, "%d", *(((BYTE *) data) + idx)); return buf; case TID_INT8: sprintf(buf, "%d", *(((char *) data) + idx)); return buf; case TID_CHAR: sprintf(buf, "%c", *(((char *) data) + idx)); return buf; case TID_UINT16: sprintf(buf, "%u", *(((WORD *) data) + idx)); return buf; case TID_INT16: sprintf(buf, "%d", *(((short *) data) + idx)); return buf; case TID_UINT32: sprintf(buf, "%u", *(((DWORD *) data) + idx)); return buf; case TID_INT32: sprintf(buf, "%d", *(((INT *) data) + idx)); return buf; case TID_UINT64: sprintf(buf, "%llu", *(((UINT64 *) data) + idx)); return buf; case TID_INT64: sprintf(buf, "%lld", *(((INT64 *) data) + idx)); return buf; case TID_BOOL: sprintf(buf, "%c", *(((BOOL *) data) + idx) ? 'y' : 'n'); return buf; case TID_FLOAT: if (ss_isnan(*(((float *) data) + idx))) { return "NAN"; } else { sprintf(buf, "%.7g", *(((float *) data) + idx)); return buf; } case TID_DOUBLE: if (ss_isnan(*(((double *) data) + idx))) { return "NAN"; } else { sprintf(buf, "%.16lg", *(((double *) data) + idx)); return buf; } case TID_BITFIELD: sprintf(buf, "%u", *(((DWORD *) data) + idx)); return buf; case TID_STRING: case TID_LINK: return (((char *) data) + data_size * idx); default: return ""; } } } /********************************************************************/ /** Same as db_sprintf, but with additional format parameter @param string output ASCII string of data. @param format Format specifier passed to sprintf() @param data Value data. @param data_size Size of single data element. @param idx Index for array data. @param type Type of key, one of TID_xxx (see @ref Midas_Data_Types). @return DB_SUCCESS */ std::string db_sprintff(const char *format, const void *data, INT data_size, INT idx, DWORD type) { if (data_size == 0) { return ""; } else { char buf[256]; switch (type) { case TID_UINT8: sprintf(buf, format, *(((BYTE *) data) + idx)); return buf; case TID_INT8: sprintf(buf, format, *(((char *) data) + idx)); return buf; case TID_CHAR: sprintf(buf, format, *(((char *) data) + idx)); return buf; case TID_UINT16: sprintf(buf, format, *(((WORD *) data) + idx)); return buf; case TID_INT16: sprintf(buf, format, *(((short *) data) + idx)); return buf; case TID_UINT32: sprintf(buf, format, *(((DWORD *) data) + idx)); return buf; case TID_INT32: sprintf(buf, format, *(((INT *) data) + idx)); return buf; case TID_UINT64: sprintf(buf, format, *(((UINT64 *) data) + idx)); return buf; case TID_INT64: sprintf(buf, format, *(((INT64 *) data) + idx)); return buf; case TID_BOOL: sprintf(buf, format, *(((BOOL *) data) + idx) ? 'y' : 'n'); return buf; case TID_FLOAT: if (ss_isnan(*(((float *) data) + idx))) { return "NAN"; } else { sprintf(buf, format, *(((float *) data) + idx)); return buf; } case TID_DOUBLE: if (ss_isnan(*(((double *) data) + idx))) { return "NAN"; } else { sprintf(buf, format, *(((double *) data) + idx)); return buf; } case TID_BITFIELD: sprintf(buf, format, *(((DWORD *) data) + idx)); return buf; case TID_STRING: case TID_LINK: return (((char *) data) + data_size * idx); default: return ""; } } } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ std::string db_sprintfh(const void *data, INT data_size, INT idx, DWORD type) /********************************************************************\ Routine: db_sprintfh Purpose: Convert a database value to a string according to its type in hex format Input: void *data Value data INT idx Index for array data INT data_size Size of single data element DWORD type Valye type, one of TID_xxx Output: char *string ASCII string of data Function value: DB_SUCCESS Successful completion \********************************************************************/ { if (data_size == 0) { return ""; } else { char buf[256]; switch (type) { case TID_UINT8: sprintf(buf, "0x%X", *(((BYTE *) data) + idx)); return buf; case TID_INT8: sprintf(buf, "0x%X", *(((char *) data) + idx)); return buf; case TID_CHAR: sprintf(buf, "%c", *(((char *) data) + idx)); return buf; case TID_UINT16: sprintf(buf, "0x%X", *(((WORD *) data) + idx)); return buf; case TID_INT16: sprintf(buf, "0x%hX", *(((short *) data) + idx)); return buf; case TID_UINT32: sprintf(buf, "0x%X", *(((DWORD *) data) + idx)); return buf; case TID_INT32: sprintf(buf, "0x%X", *(((INT *) data) + idx)); return buf; case TID_UINT64: sprintf(buf, "0x%llX", *(((UINT64 *) data) + idx)); return buf; case TID_INT64: sprintf(buf, "0x%llX", *(((INT64 *) data) + idx)); return buf; case TID_BOOL: sprintf(buf, "%c", *(((BOOL *) data) + idx) ? 'y' : 'n'); return buf; case TID_FLOAT: if (ss_isnan(*(((float *) data) + idx))) { return "NAN"; } else { sprintf(buf, "%.7g", *(((float *) data) + idx)); return buf; } case TID_DOUBLE: if (ss_isnan(*(((double *) data) + idx))) { return "NAN"; } else { sprintf(buf, "%.16lg", *(((double *) data) + idx)); return buf; } case TID_BITFIELD: sprintf(buf, "0x%X", *(((DWORD *) data) + idx)); return buf; case TID_STRING: case TID_LINK: return (((char *) data) + data_size * idx); default: return ""; } } } /*------------------------------------------------------------------*/ INT db_sscanf(const char *data_str, void *data, INT * data_size, INT i, DWORD tid) /********************************************************************\ Routine: db_sscanf Purpose: Convert a string to a database value according to its type Input: char *data_str ASCII string of data INT i Index for array data DWORD tid Value type, one of TID_xxx Output: void *data Value data INT *data_size Size of single data element Function value: DB_SUCCESS Successful completion \********************************************************************/ { DWORD value = 0; BOOL hex = FALSE; if (data_str == NULL) return 0; *data_size = rpc_tid_size(tid); if (strncmp(data_str, "0x", 2) == 0) { hex = TRUE; sscanf(data_str + 2, "%x", &value); } switch (tid) { case TID_UINT8: case TID_INT8: if (hex) *((char *) data + i) = (char) value; else *((char *) data + i) = (char) atoi(data_str); break; case TID_CHAR: *((char *) data + i) = data_str[0]; break; case TID_UINT16: if (hex) *((WORD *) data + i) = (WORD) value; else *((WORD *) data + i) = (WORD) atoi(data_str); break; case TID_INT16: if (hex) *((short int *) data + i) = (short int) value; else *((short int *) data + i) = (short int) atoi(data_str); break; case TID_UINT32: if (hex) value = strtoul(data_str, nullptr, 16); else value = strtoul(data_str, nullptr, 10); *((DWORD *) data + i) = value; break; case TID_INT32: if (hex) value = strtol(data_str, nullptr, 16); else value = strtol(data_str, nullptr, 10); *((INT *) data + i) = value; break; case TID_UINT64: if (hex) *((UINT64 *) data + i) = strtoull(data_str, nullptr, 16); else *((UINT64 *) data + i) = strtoull(data_str, nullptr, 10); break; case TID_INT64: if (hex) *((UINT64 *) data + i) = strtoll(data_str, nullptr, 16); else *((UINT64 *) data + i) = strtoll(data_str, nullptr, 10); break; case TID_BOOL: if (data_str[0] == 'y' || data_str[0] == 'Y' || data_str[0] == 't' || data_str[0] == 'T' || atoi(data_str) > 0) *((BOOL *) data + i) = 1; else *((BOOL *) data + i) = 0; break; case TID_FLOAT: if (data_str[0] == 'n' || data_str[0] == 'N') *((float *) data + i) = (float) ss_nan(); else *((float *) data + i) = (float) atof(data_str); break; case TID_DOUBLE: if (data_str[0] == 'n' || data_str[0] == 'N') *((double *) data + i) = ss_nan(); else *((double *) data + i) = atof(data_str); break; case TID_BITFIELD: if (hex) value = strtoul(data_str, nullptr, 16); else value = strtoul(data_str, nullptr, 10); *((DWORD *) data + i) = value; break; case TID_STRING: case TID_LINK: strcpy((char *) data, data_str); *data_size = strlen(data_str) + 1; break; } return DB_SUCCESS; } /*------------------------------------------------------------------*/ #ifdef LOCAL_ROUTINES static void db_recurse_record_tree_locked(HNDLE hDB, const DATABASE_HEADER* pheader, const KEY* pkey, void **data, INT * total_size, INT base_align, INT * max_align, BOOL bSet, INT convert_flags, db_err_msg** msg) /********************************************************************\ Routine: db_recurse_record_tree_locked Purpose: Recurse a database tree and calculate its size or copy data. Internal use only. \********************************************************************/ { const KEY *pold; INT size, align, corr, total_size_tmp; KEYLIST *pkeylist = (KEYLIST *) ((char *) pheader + pkey->data); if (!pkeylist->first_key) return; // FIXME: validate pkeylist->first_key pkey = (const KEY *) ((char *) pheader + pkeylist->first_key); /* first browse through this level */ do { pold = NULL; if (pkey->type == TID_LINK) { const KEY *plink = db_resolve_link_locked(pheader, pkey, NULL, msg); if (!plink) return; if (plink->type == TID_KEY) { db_recurse_record_tree_locked(hDB, pheader, plink, data, total_size, base_align, NULL, bSet, convert_flags, msg); } else { pold = pkey; pkey = plink; } } if (pkey->type != TID_KEY) { /* correct for alignment */ align = 1; if (rpc_tid_size(pkey->type)) align = rpc_tid_size(pkey->type) < base_align ? rpc_tid_size(pkey->type) : base_align; if (max_align && align > *max_align) *max_align = align; corr = VALIGN(*total_size, align) - *total_size; *total_size += corr; if (data) *data = (void *) ((char *) (*data) + corr); /* calculate data size */ size = pkey->item_size * pkey->num_values; if (data) { if (bSet) { KEY* wpkey = (KEY*)pkey; /* copy data if there is write access */ if (pkey->access_mode & MODE_WRITE) { memcpy((char *) pheader + pkey->data, *data, pkey->item_size * pkey->num_values); /* convert data */ if (convert_flags) { if (pkey->num_values > 1) rpc_convert_data((char *) pheader + pkey->data, pkey->type, RPC_FIXARRAY, pkey->item_size * pkey->num_values, convert_flags); else rpc_convert_single((char *) pheader + pkey->data, pkey->type, 0, convert_flags); } /* update time */ wpkey->last_written = ss_time(); /* notify clients which have key open */ db_notify_clients_locked(pheader, hDB, db_pkey_to_hkey(pheader, pkey), -1, TRUE, msg); } } else { /* copy key data if there is read access */ if (pkey->access_mode & MODE_READ) { memcpy(*data, (char *) pheader + pkey->data, pkey->item_size * pkey->num_values); /* convert data */ if (convert_flags) { if (pkey->num_values > 1) rpc_convert_data(*data, pkey->type, RPC_FIXARRAY | RPC_OUTGOING, pkey->item_size * pkey->num_values, convert_flags); else rpc_convert_single(*data, pkey->type, RPC_OUTGOING, convert_flags); } } } *data = (char *) (*data) + size; } *total_size += size; } else { /* align new substructure according to the maximum align value in this structure */ align = 1; total_size_tmp = *total_size; db_recurse_record_tree_locked(hDB, pheader, pkey, NULL, &total_size_tmp, base_align, &align, bSet, convert_flags, msg); if (max_align && align > *max_align) *max_align = align; corr = VALIGN(*total_size, align) - *total_size; *total_size += corr; if (data) *data = (void *) ((char *) (*data) + corr); /* now recurse subtree */ db_recurse_record_tree_locked(hDB, pheader, pkey, data, total_size, base_align, NULL, bSet, convert_flags, msg); corr = VALIGN(*total_size, align) - *total_size; *total_size += corr; if (data) *data = (void *) ((char *) (*data) + corr); } if (pold) { pkey = pold; pold = NULL; } if (!pkey->next_key) break; // FIXME: validate pkey->next_key pkey = (KEY *) ((char *) pheader + pkey->next_key); } while (TRUE); } static void db_recurse_record_tree_locked(HNDLE hDB, HNDLE hKey, void **data, INT * total_size, INT base_align, INT * max_align, BOOL bSet, INT convert_flags, db_err_msg** msg) /********************************************************************\ Routine: db_recurse_record_tree_locked Purpose: Recurse a database tree and calculate its size or copy data. Internal use only. \********************************************************************/ { /* get first subkey of hKey */ DATABASE_HEADER *pheader = _database[hDB - 1].database_header; const KEY* pkey = db_get_pkey(pheader, hKey, NULL, "db_recurse_record_tree", msg); if (!pkey) { return; } db_recurse_record_tree_locked(hDB, pheader, pkey, data, total_size, base_align, max_align, bSet, convert_flags, msg); } #endif /* LOCAL_ROUTINES */ /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Calculates the size of a record. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param align Byte alignment calculated by the stub and passed to the rpc side to align data according to local machine. Must be zero when called from user level @param buf_size Size of record structure @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TYPE_MISMATCH, DB_STRUCT_SIZE_MISMATCH, DB_NO_KEY */ INT db_get_record_size(HNDLE hDB, HNDLE hKey, INT align, INT * buf_size) { if (rpc_is_remote()) { align = ss_get_struct_align(); return rpc_call(RPC_DB_GET_RECORD_SIZE, hDB, hKey, align, buf_size); } #ifdef LOCAL_ROUTINES { KEY key; INT status, max_align; if (!align) align = ss_get_struct_align(); /* check if key has subkeys */ status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; if (key.type != TID_KEY) { /* just a single key */ *buf_size = key.item_size * key.num_values; return DB_SUCCESS; } db_err_msg* msg = NULL; db_lock_database(hDB); /* determine record size */ *buf_size = max_align = 0; db_recurse_record_tree_locked(hDB, hKey, NULL, buf_size, align, &max_align, 0, 0, &msg); //int tmp = *buf_size; /* correct for byte padding */ *buf_size = VALIGN(*buf_size, max_align); //if (tmp != *buf_size) { // cm_msg(MERROR, "db_get_record_size", "ODB record \"%s\" has unexpected padding from %d to %d bytes", db_get_path(hDB, hKey).c_str(), tmp, *buf_size); //} db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Copy a set of keys to local memory. An ODB sub-tree can be mapped to a C structure automatically via a hot-link using the function db_open_record() or manually with this function. Problems might occur if the ODB sub-tree contains values which don't match the C structure. Although the structure size is checked against the sub-tree size, no checking can be done if the type and order of the values in the structure are the same than those in the ODB sub-tree. Therefore it is recommended to use the function db_create_record() before db_get_record() is used which ensures that both are equivalent. \code struct { INT level1; INT level2; } trigger_settings; char *trigger_settings_str = "[Settings]\n\ level1 = INT : 0\n\ level2 = INT : 0"; main() { HNDLE hDB, hkey; INT size; ... cm_get_experiment_database(&hDB, NULL); db_create_record(hDB, 0, "/Equipment/Trigger", trigger_settings_str); db_find_key(hDB, 0, "/Equipment/Trigger/Settings", &hkey); size = sizeof(trigger_settings); db_get_record(hDB, hkey, &trigger_settings, &size, 0); ... } \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to the retrieved data. @param buf_size Size of data structure, must be obtained via sizeof(RECORD-NAME). @param align Byte alignment calculated by the stub and passed to the rpc side to align data according to local machine. Must be zero when called from user level. @return DB_SUCCESS, DB_INVALID_HANDLE, DB_STRUCT_SIZE_MISMATCH */ INT db_get_record(HNDLE hDB, HNDLE hKey, void *data, INT * buf_size, INT align) { if (rpc_is_remote()) { align = ss_get_struct_align(); return rpc_call(RPC_DB_GET_RECORD, hDB, hKey, data, buf_size, align); } #ifdef LOCAL_ROUTINES { KEY key; INT convert_flags, status; INT total_size; void *pdata; if (data && buf_size) { memset(data, 0x00, *buf_size); } convert_flags = 0; if (!align) align = ss_get_struct_align(); else { /* only convert data if called remotely, as indicated by align != 0 */ if (rpc_is_mserver()) { convert_flags = rpc_get_convert_flags(); } } /* check if key has subkeys */ status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) return status; if (key.type != TID_KEY) { /* copy single key */ if (key.item_size * key.num_values != *buf_size) { cm_msg(MERROR, "db_get_record", "struct size mismatch for \"%s\" (expected size: %d, size in ODB: %d)", db_get_path(hDB, hKey).c_str(), *buf_size, key.item_size * key.num_values); return DB_STRUCT_SIZE_MISMATCH; } db_get_data(hDB, hKey, data, buf_size, key.type); if (convert_flags) { if (key.num_values > 1) rpc_convert_data(data, key.type, RPC_OUTGOING | RPC_FIXARRAY, key.item_size * key.num_values, convert_flags); else rpc_convert_single(data, key.type, RPC_OUTGOING, convert_flags); } return DB_SUCCESS; } /* check record size */ db_get_record_size(hDB, hKey, align, &total_size); if (total_size != *buf_size) { cm_msg(MERROR, "db_get_record", "struct size mismatch for \"%s\" (expected size: %d, size in ODB: %d)", db_get_path(hDB, hKey).c_str(), *buf_size, total_size); return DB_STRUCT_SIZE_MISMATCH; } /* get subkey data */ pdata = data; total_size = 0; db_err_msg* msg = NULL; db_lock_database(hDB); db_recurse_record_tree_locked(hDB, hKey, &pdata, &total_size, align, NULL, FALSE, convert_flags, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); //if (total_size != *buf_size) { // cm_msg(MERROR, "db_get_record", "struct size mismatch for \"%s\", expected size: %d, read from ODB: %d bytes", db_get_path(hDB, hKey).c_str(), *buf_size, total_size); // //return DB_STRUCT_SIZE_MISMATCH; //} } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Same as db_get_record() but if there is a record mismatch between ODB structure and C record, it is automatically corrected by calling db_check_record() @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to the retrieved data. @param buf_size Size of data structure, must be obtained via sizeof(RECORD-NAME). @param align Byte alignment calculated by the stub and passed to the rpc side to align data according to local machine. Must be zero when called from user level. @param rec_str ASCII representation of ODB record in the format @return DB_SUCCESS, DB_INVALID_HANDLE, DB_STRUCT_SIZE_MISMATCH */ INT db_get_record1(HNDLE hDB, HNDLE hKey, void *data, INT * buf_size, INT align, const char *rec_str) { int size = *buf_size; int odb_size = 0; int status; //char path[MAX_ODB_PATH]; /* check record size first */ status = db_get_record_size(hDB, hKey, align, &odb_size); if (status != DB_SUCCESS) return status; /* if size mismatch, call repair function */ if (odb_size != size) { std::string path = db_get_path(hDB, hKey); cm_msg(MINFO, "db_get_record1", "Fixing ODB \"%s\" struct size mismatch (expected %d, odb size %d)", path.c_str(), size, odb_size); status = db_create_record(hDB, hKey, "", rec_str); if (status != DB_SUCCESS) return status; } /* run db_get_record(), if success, we are done */ status = db_get_record(hDB, hKey, data, buf_size, align); if (status == DB_SUCCESS) return status; /* try repair with db_check_record() */ status = db_check_record(hDB, hKey, "", rec_str, TRUE); if (status != DB_SUCCESS) return status; /* verify struct size again, because there can still be a mismatch if there * are extra odb entries at the end of the record as db_check_record() * seems to ignore all odb entries past the end of "rec_str". K.O. */ status = db_get_record_size(hDB, hKey, align, &odb_size); if (status != DB_SUCCESS) return status; std::string path = db_get_path(hDB, hKey); if (odb_size != size) { cm_msg(MERROR, "db_get_record1", "after db_check_record() still struct size mismatch (expected %d, odb size %d) of \"%s\", calling db_create_record()", size, odb_size, path.c_str()); status = db_create_record(hDB, hKey, "", rec_str); if (status != DB_SUCCESS) return status; } cm_msg(MERROR, "db_get_record1", "repaired struct size mismatch of \"%s\"", path.c_str()); *buf_size = size; status = db_get_record(hDB, hKey, data, buf_size, align); return status; } static int db_parse_record(const char* rec_str, const char** out_rec_str, char* title, int title_size, char* key_name, int key_name_size, int* tid, int* n_data, int* string_length) { title[0] = 0; key_name[0] = 0; *tid = 0; *n_data = 0; *string_length = 0; *out_rec_str = NULL; // // expected format of rec_str: // // title: "[.]", // numeric value: "example_int = INT : 3", // string value: "example_string = STRING : [20] /Runinfo/Run number", // array: "aaa = INT[10] : ...", // string array: "sarr = STRING[10] : [32] ", // //printf("parse_rec_str: [%s]\n", rec_str); while (*rec_str == '\n') rec_str++; /* check if it is a section title */ if (rec_str[0] == '[') { rec_str++; title[0] = 0; /* extract title and append '/' */ mstrlcpy(title, rec_str, title_size); char* p = strchr(title, ']'); if (p) *p = 0; int len = strlen(title); if (len > 0) { if (title[len - 1] != '/') mstrlcat(title, "/", title_size); } // skip to the next end-of-line const char* pend = strchr(rec_str, '\n'); if (pend) rec_str = pend; else rec_str = rec_str+strlen(rec_str); while (*rec_str == '\n') rec_str++; *out_rec_str = rec_str; return DB_SUCCESS; } if (rec_str[0] == ';') { // skip to the next end-of-line const char* pend = strchr(rec_str, '\n'); if (pend) rec_str = pend; else rec_str = rec_str+strlen(rec_str); while (*rec_str == '\n') rec_str++; *out_rec_str = rec_str; return DB_SUCCESS; } const char* peq = strchr(rec_str, '='); if (!peq) { cm_msg(MERROR, "db_parse_record", "do not see \'=\'"); return DB_INVALID_PARAM; } int key_name_len = peq - rec_str; // remove trailing equals sign and trailing spaces while (key_name_len > 1) { if (rec_str[key_name_len-1] == '=') { key_name_len--; continue; } if (rec_str[key_name_len-1] == ' ') { key_name_len--; continue; } break; } memcpy(key_name, rec_str, key_name_len); key_name[key_name_len] = 0; rec_str = peq + 1; // consume the "=" sign while (*rec_str == ' ') // consume spaces rec_str++; // extract type id char stid[256]; int i; for (i=0; i<(int)sizeof(stid)-1; i++) { char s = *rec_str; if (s == 0) break; if (s == ' ') break; if (s == '\n') break; if (s == '[') break; stid[i] = s; rec_str++; } stid[i] = 0; DWORD xtid = 0; for (xtid = 0; xtid < TID_LAST; xtid++) { if (strcmp(rpc_tid_name(xtid), stid) == 0) { *tid = xtid; break; } } //printf("tid [%s], tid %d\n", stid, *tid); if (xtid == TID_LAST) { cm_msg(MERROR, "db_parse_record", "do not see \':\'"); return DB_INVALID_PARAM; } while (*rec_str == ' ') // consume spaces rec_str++; *n_data = 1; if (*rec_str == '[') { // decode array size rec_str++; // cosume the '[' *n_data = atoi(rec_str); const char *pbr = strchr(rec_str, ']'); if (!pbr) { cm_msg(MERROR, "db_parse_record", "do not see closing bracket \']\'"); return DB_INVALID_PARAM; } rec_str = pbr + 1; // skip the closing bracket } while (*rec_str == ' ') // consume spaces rec_str++; const char* pcol = strchr(rec_str, ':'); if (!pcol) { cm_msg(MERROR, "db_parse_record", "do not see \':\'"); return DB_INVALID_PARAM; } rec_str = pcol + 1; // skip the ":" while (*rec_str == ' ') // consume spaces rec_str++; *string_length = 0; if (xtid == TID_LINK || xtid == TID_STRING) { // extract string length const char* pbr = strchr(rec_str, '['); if (pbr) { *string_length = atoi(pbr+1); } } // skip to the next end-of-line const char* pend = strchr(rec_str, '\n'); if (pend) rec_str = pend; else rec_str = rec_str+strlen(rec_str); while (*rec_str == '\n') rec_str++; *out_rec_str = rec_str; return DB_SUCCESS; } static int db_get_record2_read_element(HNDLE hDB, HNDLE hKey, const char* key_name, int tid, int n_data, int string_length, char* buf_start, char** buf_ptr, int* buf_remain, BOOL correct) { assert(tid > 0); assert(n_data > 0); int tsize = rpc_tid_size(tid); int offset = *buf_ptr - buf_start; int align = 0; if (tsize && (offset%tsize != 0)) { while (offset%tsize != 0) { align++; *(*buf_ptr) = 0xFF; // pad bytes for correct data alignement (*buf_ptr)++; (*buf_remain)--; offset++; } } printf("read element [%s] tid %d, n_data %d, string_length %d, tid_size %d, align %d, offset %d, buf_remain %d\n", key_name, tid, n_data, string_length, tsize, align, offset, *buf_remain); if (tsize > 0) { int xsize = tsize*n_data; if (xsize > *buf_remain) { cm_msg(MERROR, "db_get_record2", "buffer overrun at key \"%s\", size %d, buffer remaining %d", key_name, xsize, *buf_remain); return DB_INVALID_PARAM; } int ysize = xsize; int status = db_get_value(hDB, hKey, key_name, *buf_ptr, &ysize, tid, FALSE); //printf("status %d, xsize %d\n", status, xsize); if (status != DB_SUCCESS) { cm_msg(MERROR, "db_get_record2", "cannot read \"%s\", db_get_value() status %d", key_name, status); memset(*buf_ptr, 0, xsize); *buf_ptr += xsize; *buf_remain -= xsize; return status; } *buf_ptr += xsize; *buf_remain -= xsize; } else if (tid == TID_STRING) { int xstatus = 0; int i; for (i=0; i *buf_remain) { cm_msg(MERROR, "db_get_record2", "string buffer overrun at key \"%s\" index %d, size %d, buffer remaining %d", key_name, i, xsize, *buf_remain); return DB_INVALID_PARAM; } char xkey_name[MAX_ODB_PATH+100]; sprintf(xkey_name, "%s[%d]", key_name, i); int status = db_get_value(hDB, hKey, xkey_name, *buf_ptr, &xsize, tid, FALSE); //printf("status %d, string length %d, xsize %d, actual len %d\n", status, string_length, xsize, (int)strlen(*buf_ptr)); if (status == DB_TRUNCATED) { // make sure string is NUL terminated (*buf_ptr)[string_length-1] = 0; cm_msg(MERROR, "db_get_record2", "string key \"%s\" index %d, string value was truncated", key_name, i); } else if (status != DB_SUCCESS) { cm_msg(MERROR, "db_get_record2", "cannot read string \"%s\"[%d], db_get_value() status %d", key_name, i, status); memset(*buf_ptr, 0, string_length); xstatus = status; } *buf_ptr += string_length; *buf_remain -= string_length; } if (xstatus != 0) { return xstatus; } } else { cm_msg(MERROR, "db_get_record2", "cannot read key \"%s\" of unsupported type %d", key_name, tid); return DB_INVALID_PARAM; } return DB_SUCCESS; } /********************************************************************/ /** Copy a set of keys to local memory. An ODB sub-tree can be mapped to a C structure automatically via a hot-link using the function db_open_record1() or manually with this function. For correct operation, the description string *must* match the C data structure. If the contents of ODB sub-tree does not exactly match the description string, db_get_record2() will try to read as much as it can and return DB_TRUNCATED to inform the user that there was a mismatch somewhere. To ensure that the ODB sub-tree matches the desciption string, call db_create_record() or db_check_record() before calling db_get_record2(). Unlike db_get_record() and db_get_record1(), this function will not complain about data strucure mismatches. It will ignore all extra entries in the ODB sub-tree and it will set to zero the C-structure data fields that do not have corresponding ODB entries. \code struct { INT level1; INT level2; } trigger_settings; const char *trigger_settings_str = "[Settings]\n\ level1 = INT : 0\n\ level2 = INT : 0"; main() { HNDLE hDB, hkey; INT size; ... cm_get_experiment_database(&hDB, NULL); db_create_record(hDB, 0, "/Equipment/Trigger", trigger_settings_str); db_find_key(hDB, 0, "/Equipment/Trigger/Settings", &hkey); size = sizeof(trigger_settings); db_get_record2(hDB, hkey, &trigger_settings, &size, 0, trigger_settings_str); ... } \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer to the retrieved data. @param buf_size Size of data structure, must be obtained via sizeof(data). @param align Byte alignment calculated by the stub and passed to the rpc side to align data according to local machine. Must be zero when called from user level. @param rec_str Description of the data structure, see db_create_record() @param correct Must be set to zero @return DB_SUCCESS, DB_INVALID_HANDLE, DB_STRUCT_SIZE_MISMATCH */ INT db_get_record2(HNDLE hDB, HNDLE hKey, void *data, INT * xbuf_size, INT align, const char *rec_str, BOOL correct) { int status = DB_SUCCESS; printf("db_get_record2!\n"); assert(data != NULL); assert(xbuf_size != NULL); assert(*xbuf_size > 0); assert(correct == 0); int truncated = 0; #if 1 char* r1 = NULL; int rs = *xbuf_size; if (1) { r1 = (char*)malloc(rs); memset(data, 0xFF, *xbuf_size); memset(r1, 0xFF, rs); //status = db_get_record1(hDB, hKey, r1, &rs, 0, rec_str); status = db_get_record(hDB, hKey, r1, &rs, 0); printf("db_get_record status %d\n", status); } #endif char* buf_start = (char*)data; int buf_size = *xbuf_size; char* buf_ptr = buf_start; int buf_remain = buf_size; while (rec_str && *rec_str != 0) { char title[256]; char key_name[MAX_ODB_PATH]; int tid = 0; int n_data = 0; int string_length = 0; const char* rec_str_next = NULL; status = db_parse_record(rec_str, &rec_str_next, title, sizeof(title), key_name, sizeof(key_name), &tid, &n_data, &string_length); //printf("parse [%s], status %d, title [%s], key_name [%s], tid %d, n_data %d, string_length %d, next [%s]\n", rec_str, status, title, key_name, tid, n_data, string_length, rec_str_next); rec_str = rec_str_next; if (status != DB_SUCCESS) { return status; } if (key_name[0] == 0) { // skip title strings, comments, etc continue; } status = db_get_record2_read_element(hDB, hKey, key_name, tid, n_data, string_length, buf_start, &buf_ptr, &buf_remain, correct); if (status == DB_INVALID_PARAM) { cm_msg(MERROR, "db_get_record2", "error: cannot continue reading odb record because of previous fatal error, status %d", status); return DB_INVALID_PARAM; } if (status != DB_SUCCESS) { truncated = 1; } rec_str = rec_str_next; } if (r1) { int ok = -1; int i; for (i=0; i=0 || buf_remain>0) { printf("db_get_record2: miscompare at %d out of %d, buf_remain %d\n", ok, rs, buf_remain); } else { printf("db_get_record2: check ok\n"); } } if (buf_remain > 0) { // FIXME: we finished processing the data definition string, but unused space remains in the buffer return DB_TRUNCATED; } if (truncated) return DB_TRUNCATED; else return DB_SUCCESS; } /********************************************************************/ /** Copy a set of keys from local memory to the database. An ODB sub-tree can be mapped to a C structure automatically via a hot-link using the function db_open_record() or manually with this function. Problems might occur if the ODB sub-tree contains values which don't match the C structure. Although the structure size is checked against the sub-tree size, no checking can be done if the type and order of the values in the structure are the same than those in the ODB sub-tree. Therefore it is recommended to use the function db_create_record() before using this function. \code ... memset(&lazyst,0,size); if (db_find_key(hDB, pLch->hKey, "Statistics",&hKeyst) == DB_SUCCESS) status = db_set_record(hDB, hKeyst, &lazyst, size, 0); else cm_msg(MERROR,"task","record %s/statistics not found", pLch->name) ... \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param data Pointer where data is stored. @param buf_size Size of data structure, must be obtained via sizeof(RECORD-NAME). @param align Byte alignment calculated by the stub and passed to the rpc side to align data according to local machine. Must be zero when called from user level. @return DB_SUCCESS, DB_INVALID_HANDLE, DB_TYPE_MISMATCH, DB_STRUCT_SIZE_MISMATCH */ INT db_set_record(HNDLE hDB, HNDLE hKey, void *data, INT buf_size, INT align) { if (rpc_is_remote()) { align = ss_get_struct_align(); return rpc_call(RPC_DB_SET_RECORD, hDB, hKey, data, buf_size, align); } #ifdef LOCAL_ROUTINES { KEY key; INT convert_flags; INT total_size; void *pdata; convert_flags = 0; if (!align) align = ss_get_struct_align(); else { /* only convert data if called remotely, as indicated by align != 0 */ if (rpc_is_mserver()) { convert_flags = rpc_get_convert_flags(); } } /* check if key has subkeys */ db_get_key(hDB, hKey, &key); if (key.type != TID_KEY) { /* copy single key */ if (key.item_size * key.num_values != buf_size) { cm_msg(MERROR, "db_set_record", "struct size mismatch for \"%s\"", key.name); return DB_STRUCT_SIZE_MISMATCH; } if (convert_flags) { if (key.num_values > 1) rpc_convert_data(data, key.type, RPC_FIXARRAY, key.item_size * key.num_values, convert_flags); else rpc_convert_single(data, key.type, 0, convert_flags); } db_set_data(hDB, hKey, data, key.total_size, key.num_values, key.type); return DB_SUCCESS; } /* check record size */ db_get_record_size(hDB, hKey, align, &total_size); if (total_size != buf_size) { cm_msg(MERROR, "db_set_record", "struct size mismatch for \"%s\"", key.name); return DB_STRUCT_SIZE_MISMATCH; } /* set subkey data */ pdata = data; total_size = 0; db_lock_database(hDB); db_err_msg* msg = NULL; db_allow_write_locked(&_database[hDB-1], "db_set_record"); db_recurse_record_tree_locked(hDB, hKey, &pdata, &total_size, align, NULL, TRUE, convert_flags, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /**dox***************************************************************/ #ifndef DOXYGEN_SHOULD_SKIP_THIS /*------------------------------------------------------------------*/ INT db_add_open_record(HNDLE hDB, HNDLE hKey, WORD access_mode) /********************************************************************\ Routine: db_add_open_record Purpose: Server part of db_open_record. Internal use only. \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_ADD_OPEN_RECORD, hDB, hKey, access_mode); #ifdef LOCAL_ROUTINES { INT i; int status; if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_add_open_record", "invalid database handle"); return DB_INVALID_HANDLE; } db_err_msg* msg = NULL; /* lock database */ db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; DATABASE_CLIENT *pclient = db_get_my_client_locked(pdb); /* check if key is already open */ for (i = 0; i < pclient->max_index; i++) { if (pclient->open_record[i].handle == hKey) break; } if (i < pclient->max_index) { db_unlock_database(hDB); return DB_SUCCESS; } /* not found, search free entry */ for (i = 0; i < pclient->max_index; i++) { if (pclient->open_record[i].handle == 0) break; } /* check if maximum number reached */ if (i == MAX_OPEN_RECORDS) { db_unlock_database(hDB); return DB_NO_MEMORY; } db_allow_write_locked(pdb, "db_add_open_record"); KEY *pkey = (KEY*)db_get_pkey(pheader, hKey, &status, "db_add_open_record", &msg); if (!pkey) { db_unlock_database(hDB); if (msg) db_flush_msg(&msg); return status; } if (i == pclient->max_index) pclient->max_index++; pclient->open_record[i].handle = hKey; pclient->open_record[i].access_mode = access_mode; /* increment notify_count */ pkey->notify_count++; pclient->num_open_records++; /* set exclusive bit if requested */ if (access_mode & MODE_WRITE) db_set_mode(hDB, hKey, (WORD) (pkey->access_mode | MODE_EXCLUSIVE), 2); db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } #ifdef LOCAL_ROUTINES static int db_remove_open_record_wlocked(DATABASE* pdb, DATABASE_HEADER* pheader, HNDLE hKey) { int status = DB_SUCCESS; DATABASE_CLIENT *pclient = db_get_my_client_locked(pdb); /* search key */ int idx; for (idx = 0; idx < pclient->max_index; idx++) if (pclient->open_record[idx].handle == hKey) break; if (idx == pclient->max_index) { return DB_INVALID_HANDLE; } KEY* pkey = (KEY*)db_get_pkey(pheader, hKey, &status, "db_remove_open_record_wlocked", NULL); if (!pkey) return status; /* decrement notify_count */ if (pkey->notify_count > 0) pkey->notify_count--; pclient->num_open_records--; /* remove exclusive flag */ if (pclient->open_record[idx].access_mode & MODE_WRITE) db_set_mode_wlocked(pheader, pkey, (WORD) (pkey->access_mode & ~MODE_EXCLUSIVE), 2, NULL); memset(&pclient->open_record[idx], 0, sizeof(OPEN_RECORD)); /* calculate new max_index entry */ int i; for (i = pclient->max_index - 1; i >= 0; i--) if (pclient->open_record[i].handle != 0) break; pclient->max_index = i + 1; return DB_SUCCESS; } #endif // LOCAL_ROUTINES INT db_remove_open_record(HNDLE hDB, HNDLE hKey, BOOL lock) /********************************************************************\ Routine: db_remove_open_record Purpose: Gets called by db_close_record. Internal use only. \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_REMOVE_OPEN_RECORD, hDB, hKey, lock); int status = DB_SUCCESS; #ifdef LOCAL_ROUTINES { if (hDB > _database_entries || hDB <= 0) { cm_msg(MERROR, "db_remove_open_record", "invalid database handle %d", hDB); return DB_INVALID_HANDLE; } db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; db_allow_write_locked(pdb, "db_remove_open_record"); status = db_remove_open_record_wlocked(pdb, pheader, hKey); db_unlock_database(hDB); } #endif /* LOCAL_ROUTINES */ return status; } /*------------------------------------------------------------------*/ #ifdef LOCAL_ROUTINES static INT db_notify_clients_locked(const DATABASE_HEADER* pheader, HNDLE hDB, HNDLE hKeyMod, int index, BOOL bWalk, db_err_msg** msg) /********************************************************************\ Routine: db_notify_clients Purpose: Gets called by db_set_xxx functions. Internal use only. \********************************************************************/ { HNDLE hKey; KEYLIST *pkeylist; INT i, j; char str[80]; int status; hKey = hKeyMod; const KEY* pkey = db_get_pkey(pheader, hKey, &status, "db_notify_clients", msg); if (!pkey) { return status; } do { /* check which client has record open */ if (pkey->notify_count) for (i = 0; i < pheader->max_client_index; i++) { const DATABASE_CLIENT* pclient = &pheader->client[i]; for (j = 0; j < pclient->max_index; j++) if (pclient->open_record[j].handle == hKey) { /* send notification to remote process */ sprintf(str, "O %d %d %d %d", hDB, hKey, hKeyMod, index); ss_resume(pclient->port, str); } } if (pkey->parent_keylist == 0 || !bWalk) return DB_SUCCESS; // FIXME: validate pkey->parent_keylist pkeylist = (KEYLIST *) ((char *) pheader + pkey->parent_keylist); pkey = db_get_pkey(pheader, pkeylist->parent, &status, "db_notify_clients", msg); if (!pkey) { return status; } hKey = db_pkey_to_hkey(pheader, pkey); } while (TRUE); } #endif /* LOCAL_ROUTINES */ /*------------------------------------------------------------------*/ INT db_notify_clients(HNDLE hDB, HNDLE hKeyMod, int index, BOOL bWalk) /********************************************************************\ Routine: db_notify_clients Purpose: Gets called by db_set_xxx functions. Internal use only. \********************************************************************/ { if (rpc_is_remote()) { cm_msg(MERROR, "db_notify_clients", "db_notify_clients() does not work in remotely connected MIDAS clients"); return DB_INVALID_HANDLE; } #ifdef LOCAL_ROUTINES { db_err_msg* msg = NULL; int status = db_lock_database(hDB); if (status != DB_SUCCESS) return status; DATABASE_HEADER* pheader = _database[hDB - 1].database_header; db_notify_clients_locked(pheader, hDB, hKeyMod, index, bWalk, &msg); db_unlock_database(hDB); if (msg) db_flush_msg(&msg); } #endif return DB_SUCCESS; } INT db_notify_clients_array(HNDLE hDB, HNDLE hKeys[], INT size) /********************************************************************\ Routine: db_notify_clients_array Purpose: This function is typically called after a set of calls to db_set_data1 which omits hot-link notification to programs. After several ODB values are modified in a set, this function has to be called to trigger the hot-links of the whole set. \********************************************************************/ { if (rpc_is_remote()) return rpc_call(RPC_DB_NOTIFY_CLIENTS_ARRAY, hDB, hKeys, size); #ifdef LOCAL_ROUTINES { int status = db_lock_database(hDB); if (status != DB_SUCCESS) return status; db_err_msg* msg = NULL; DATABASE_HEADER* pheader = _database[hDB - 1].database_header; int count = size/sizeof(INT); for (int i=0 ; inotify_count) _global_open_count++; } /**dox***************************************************************/ #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /********************************************************************/ /** Create a record. If a part of the record exists alreay, merge it with the init_str (use values from the init_str only when they are not in the existing record). This functions creates a ODB sub-tree according to an ASCII representation of that tree. See db_copy() for a description. It can be used to create a sub-tree which exactly matches a C structure. The sub-tree can then later mapped to the C structure ("hot-link") via the function db_open_record(). If a sub-tree exists already which exactly matches the ASCII representation, it is not modified. If part of the tree exists, it is merged with the ASCII representation where the ODB values have priority, only values not present in the ODB are created with the default values of the ASCII representation. It is therefore recommended that before creating an ODB hot-link the function db_create_record() is called to insure that the ODB tree and the C structure contain exactly the same values in the same order. Following example creates a record under /Equipment/Trigger/Settings, opens a hot-link between that record and a local C structure trigger_settings and registers a callback function trigger_update() which gets called each time the record is changed. \code struct { INT level1; INT level2; } trigger_settings; char *trigger_settings_str = "[Settings]\n\ level1 = INT : 0\n\ level2 = INT : 0"; void trigger_update(INT hDB, INT hkey, void *info) { printf("New levels: %d %d\n", trigger_settings.level1, trigger_settings.level2); } main() { HNDLE hDB, hkey; char[128] info; ... cm_get_experiment_database(&hDB, NULL); db_create_record(hDB, 0, "/Equipment/Trigger", trigger_settings_str); db_find_key(hDB, 0,"/Equipment/Trigger/Settings", &hkey); db_open_record(hDB, hkey, &trigger_settings, sizeof(trigger_settings), MODE_READ, trigger_update, info); ... } \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param orig_key_name Name of key to search, can contain directories. @param init_str Initialization string in the format of the db_copy/db_save functions. @return DB_SUCCESS, DB_INVALID_HANDLE, DB_FULL, DB_NO_ACCESS, DB_OPEN_RECORD */ INT db_create_record(HNDLE hDB, HNDLE hKey, const char *orig_key_name, const char *init_str) { char str[256], key_name[256], *buffer; INT status, size, buffer_size; HNDLE hKeyTmp, hKeyTmpO, hKeyOrig, hSubkey; if (rpc_is_remote()) return rpc_call(RPC_DB_CREATE_RECORD, hDB, hKey, orig_key_name, init_str); /* make this function atomic */ db_lock_database(hDB); /* strip trailing '/' */ mstrlcpy(key_name, orig_key_name, sizeof(key_name)); if (strlen(key_name) > 1 && key_name[strlen(key_name) - 1] == '/') key_name[strlen(key_name) - 1] = 0; /* merge temporay record and original record */ status = db_find_key(hDB, hKey, key_name, &hKeyOrig); if (status == DB_SUCCESS) { assert(hKeyOrig != 0); #ifdef CHECK_OPEN_RECORD /* check if key or subkey is opened */ _global_open_count = 0; // FIXME: this is not thread safe db_scan_tree_link(hDB, hKeyOrig, 0, check_open_keys, NULL); if (_global_open_count) { db_unlock_database(hDB); return DB_OPEN_RECORD; } #endif /* create temporary records */ sprintf(str, "/System/Tmp/%sI", ss_tid_to_string(ss_gettid()).c_str()); //printf("db_create_record str [%s]\n", str); db_find_key(hDB, 0, str, &hKeyTmp); if (hKeyTmp) db_delete_key(hDB, hKeyTmp, FALSE); db_create_key(hDB, 0, str, TID_KEY); status = db_find_key(hDB, 0, str, &hKeyTmp); if (status != DB_SUCCESS) { db_unlock_database(hDB); return status; } sprintf(str, "/System/Tmp/%sO", ss_tid_to_string(ss_gettid()).c_str()); //printf("db_create_record str [%s]\n", str); db_find_key(hDB, 0, str, &hKeyTmpO); if (hKeyTmpO) db_delete_key(hDB, hKeyTmpO, FALSE); db_create_key(hDB, 0, str, TID_KEY); status = db_find_key(hDB, 0, str, &hKeyTmpO); if (status != DB_SUCCESS) { db_unlock_database(hDB); return status; } status = db_paste(hDB, hKeyTmp, init_str); if (status != DB_SUCCESS) { db_unlock_database(hDB); return status; } buffer_size = 10000; buffer = (char *) malloc(buffer_size); do { size = buffer_size; status = db_copy(hDB, hKeyOrig, buffer, &size, ""); if (status == DB_TRUNCATED) { buffer_size += 10000; buffer = (char *) realloc(buffer, buffer_size); continue; } if (status != DB_SUCCESS) { db_unlock_database(hDB); return status; } } while (status != DB_SUCCESS); status = db_paste(hDB, hKeyTmpO, buffer); if (status != DB_SUCCESS) { free(buffer); db_unlock_database(hDB); return status; } /* merge temporay record and original record */ db_scan_tree_link(hDB, hKeyTmpO, 0, merge_records, NULL); /* delete original record */ while (1) { db_enum_link(hDB, hKeyOrig, 0, &hSubkey); if (!hSubkey) break; status = db_delete_key(hDB, hSubkey, FALSE); if (status != DB_SUCCESS) { free(buffer); db_unlock_database(hDB); return status; } } /* copy merged record to original record */ do { size = buffer_size; status = db_copy(hDB, hKeyTmp, buffer, &size, ""); if (status == DB_TRUNCATED) { buffer_size += 10000; buffer = (char *) realloc(buffer, buffer_size); continue; } if (status != DB_SUCCESS) { free(buffer); db_unlock_database(hDB); return status; } } while (status != DB_SUCCESS); status = db_paste(hDB, hKeyOrig, buffer); if (status != DB_SUCCESS) { free(buffer); db_unlock_database(hDB); return status; } /* delete temporary records */ db_delete_key(hDB, hKeyTmp, FALSE); db_delete_key(hDB, hKeyTmpO, FALSE); free(buffer); buffer = NULL; } else if (status == DB_NO_KEY) { /* create fresh record */ db_create_key(hDB, hKey, key_name, TID_KEY); status = db_find_key(hDB, hKey, key_name, &hKeyTmp); if (status != DB_SUCCESS) { db_unlock_database(hDB); return status; } status = db_paste(hDB, hKeyTmp, init_str); if (status != DB_SUCCESS) { db_unlock_database(hDB); return status; } } else { cm_msg(MERROR, "db_create_record", "aborting on unexpected failure of db_find_key(%s), status %d", key_name, status); abort(); } db_unlock_database(hDB); return DB_SUCCESS; } /********************************************************************/ /** This function ensures that a certain ODB subtree matches a given C structure, by comparing the init_str with the current ODB structure. If the record does not exist at all, it is created with the default values in init_str. If it does exist but does not match the variables in init_str, the function returns an error if correct=FALSE or calls db_create_record() if correct=TRUE. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param keyname Name of key to search, can contain directories. @param rec_str ASCII representation of ODB record in the format @param correct If TRUE, correct ODB record if necessary @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_KEY, DB_STRUCT_MISMATCH */ INT db_check_record(HNDLE hDB, HNDLE hKey, const char *keyname, const char *rec_str, BOOL correct) { char line[MAX_STRING_LENGTH]; char title[MAX_STRING_LENGTH]; char key_name[MAX_STRING_LENGTH]; char info_str[MAX_STRING_LENGTH + 50]; char *pc; const char *pold, *rec_str_orig; DWORD tid; INT i, j, n_data, string_length, status; HNDLE hKeyRoot, hKeyTest; KEY key; int bad_string_length; if (rpc_is_remote()) return rpc_call(RPC_DB_CHECK_RECORD, hDB, hKey, keyname, rec_str, correct); /* check if record exists */ status = db_find_key(hDB, hKey, keyname, &hKeyRoot); /* create record if not */ if (status == DB_NO_KEY) { if (correct) return db_create_record(hDB, hKey, keyname, rec_str); return DB_NO_KEY; } assert(hKeyRoot); title[0] = 0; rec_str_orig = rec_str; db_get_key(hDB, hKeyRoot, &key); if (key.type == TID_KEY) /* get next subkey which is not of type TID_KEY */ db_get_next_link(hDB, hKeyRoot, &hKeyTest); else /* test root key itself */ hKeyTest = hKeyRoot; if (hKeyTest == 0 && *rec_str != 0) { if (correct) return db_create_record(hDB, hKey, keyname, rec_str_orig); return DB_STRUCT_MISMATCH; } do { if (*rec_str == 0) break; for (i = 0; *rec_str != '\n' && *rec_str && i < MAX_STRING_LENGTH; i++) line[i] = *rec_str++; if (i == MAX_STRING_LENGTH) { cm_msg(MERROR, "db_check_record", "line too long"); return DB_TRUNCATED; } line[i] = 0; if (*rec_str == '\n') rec_str++; /* check if it is a section title */ if (line[0] == '[') { /* extract title and append '/' */ strcpy(title, line + 1); if (strchr(title, ']')) *strchr(title, ']') = 0; if (title[0] && title[strlen(title) - 1] != '/') strcat(title, "/"); } else { /* valid data line if it includes '=' and no ';' */ if (strchr(line, '=') && line[0] != ';') { /* copy type info and data */ pc = strchr(line, '=') + 1; while (*pc == ' ') pc++; strcpy(info_str, pc); /* extract key name */ *strchr(line, '=') = 0; pc = &line[strlen(line) - 1]; while (*pc == ' ') *pc-- = 0; mstrlcpy(key_name, line, sizeof(key_name)); /* evaluate type info */ strcpy(line, info_str); if (strchr(line, ' ')) *strchr(line, ' ') = 0; n_data = 1; if (strchr(line, '[')) { n_data = atol(strchr(line, '[') + 1); *strchr(line, '[') = 0; } for (tid = 0; tid < TID_LAST; tid++) if (strcmp(rpc_tid_name(tid), line) == 0) break; if (tid == TID_LAST) { for (tid = 0; tid < TID_LAST; tid++) if (strcmp(rpc_tid_name_old(tid), line) == 0) break; } string_length = 0; if (tid == TID_LAST) cm_msg(MERROR, "db_check_record", "found unknown data type \"%s\" in ODB file", line); else { /* skip type info */ pc = info_str; while (*pc != ' ' && *pc) pc++; while ((*pc == ' ' || *pc == ':') && *pc) pc++; if (n_data > 1) { info_str[0] = 0; if (!*rec_str) break; for (j = 0; *rec_str != '\n' && *rec_str; j++) info_str[j] = *rec_str++; info_str[j] = 0; if (*rec_str == '\n') rec_str++; } for (i = 0; i < n_data; i++) { /* strip trailing \n */ pc = &info_str[strlen(info_str) - 1]; while (*pc == '\n' || *pc == '\r') *pc-- = 0; if (tid == TID_STRING || tid == TID_LINK) { if (!string_length) { if (info_str[1] == '=') string_length = -1; else { pc = strchr(info_str, '['); if (pc != NULL) string_length = atoi(pc + 1); else string_length = -1; } if (string_length > MAX_STRING_LENGTH) { string_length = MAX_STRING_LENGTH; cm_msg(MERROR, "db_check_record", "found string exceeding MAX_STRING_LENGTH"); } } if (string_length == -1) { /* multi-line string */ if (strstr(rec_str, "\n====#$@$#====\n") != NULL) { string_length = (POINTER_T) strstr(rec_str, "\n====#$@$#====\n") - (POINTER_T) rec_str + 1; rec_str = strstr(rec_str, "\n====#$@$#====\n") + strlen("\n====#$@$#====\n"); } else cm_msg(MERROR, "db_check_record", "found multi-line string without termination sequence"); } else { if (strchr(info_str, ']')) pc = strchr(info_str, ']') + 1; else pc = info_str + 2; while (*pc && *pc != ' ') pc++; while (*pc && *pc == ' ') pc++; /* limit string size */ *(pc + string_length - 1) = 0; } } else { pc = info_str; if (n_data > 1 && info_str[0] == '[') { pc = strchr(info_str, ']') + 1; while (*pc && *pc == ' ') pc++; } } if (i < n_data - 1) { info_str[0] = 0; if (!*rec_str) break; pold = rec_str; for (j = 0; *rec_str != '\n' && *rec_str; j++) info_str[j] = *rec_str++; info_str[j] = 0; if (*rec_str == '\n') rec_str++; /* test if valid data */ if (tid != TID_STRING && tid != TID_LINK) { if (info_str[0] == 0 || (strchr(info_str, '=') && strchr(info_str, ':'))) rec_str = pold; } } } /* if rec_str contains key, but not ODB, return mismatch */ if (!hKeyTest) { if (correct) return db_create_record(hDB, hKey, keyname, rec_str_orig); return DB_STRUCT_MISMATCH; } status = db_get_key(hDB, hKeyTest, &key); assert(status == DB_SUCCESS); bad_string_length = 0; if (key.type == TID_STRING) { //printf("key name [%s], tid %d/%d, num_values %d/%d, string length %d/%d\n", key_name, key.type, tid, key.num_values, n_data, string_length, key.item_size); if (string_length > 0 && string_length != key.item_size) { bad_string_length = 1; } } /* check rec_str vs. ODB key */ if (!equal_ustring(key.name, key_name) || key.type != tid || key.num_values != n_data || bad_string_length) { //printf("miscompare key name [%s], tid %d/%d, num_values %d/%d, string length %d/%d\n", key_name, key.type, tid, key.num_values, n_data, key.item_size, string_length); if (correct) return db_create_record(hDB, hKey, keyname, rec_str_orig); return DB_STRUCT_MISMATCH; } /* get next key in ODB */ db_get_next_link(hDB, hKeyTest, &hKeyTest); } } } } while (TRUE); return DB_SUCCESS; } /********************************************************************/ /** Open a record. Create a local copy and maintain an automatic update. This function opens a hot-link between an ODB sub-tree and a local structure. The sub-tree is copied to the structure automatically every time it is modified by someone else. Additionally, a callback function can be declared which is called after the structure has been updated. The callback function receives the database handle and the key handle as parameters. Problems might occur if the ODB sub-tree contains values which don't match the C structure. Although the structure size is checked against the sub-tree size, no checking can be done if the type and order of the values in the structure are the same than those in the ODB sub-tree. Therefore it is recommended to use the function db_create_record() before db_open_record() is used which ensures that both are equivalent. The access mode might either be MODE_READ or MODE_WRITE. In read mode, the ODB sub-tree is automatically copied to the local structure when modified by other clients. In write mode, the local structure is copied to the ODB sub-tree if it has been modified locally. This update has to be manually scheduled by calling db_send_changed_records() periodically in the main loop. The system keeps a copy of the local structure to determine if its contents has been changed. If MODE_ALLOC is or'ed with the access mode, the memory for the structure is allocated internally. The structure pointer must contain a pointer to a pointer to the structure. The internal memory is released when db_close_record() is called. - To open a record in write mode. \code struct { INT level1; INT level2; } trigger_settings; char *trigger_settings_str = "[Settings]\n\ level1 = INT : 0\n\ level2 = INT : 0"; main() { HNDLE hDB, hkey, i=0; ... cm_get_experiment_database(&hDB, NULL); db_create_record(hDB, 0, "/Equipment/Trigger", trigger_settings_str); db_find_key(hDB, 0,"/Equipment/Trigger/Settings", &hkey); db_open_record(hDB, hkey, &trigger_settings, sizeof(trigger_settings) , MODE_WRITE, NULL); do { trigger_settings.level1 = i++; db_send_changed_records() status = cm_yield(1000); } while (status != RPC_SHUTDOWN && status != SS_ABORT); ... } \endcode @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param ptr If access_mode includes MODE_ALLOC: Address of pointer which points to the record data after the call if access_mode includes not MODE_ALLOC: Address of record if ptr==NULL, only the dispatcher is called. @param rec_size Record size in bytes @param access_mode Mode for opening record, either MODE_READ or MODE_WRITE. May be or'ed with MODE_ALLOC to let db_open_record allocate the memory for the record. @param (*dispatcher) Function which gets called when record is updated.The argument list composed of: HNDLE hDB, HNDLE hKey, void *info @param info Additional info passed to the dispatcher. @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_MEMORY, DB_NO_ACCESS, DB_STRUCT_SIZE_MISMATCH */ INT db_open_record(HNDLE hDB, HNDLE hKey, void *ptr, INT rec_size, WORD access_mode, void (*dispatcher) (INT, INT, void *), void *info) { INT idx, status, size; KEY key; void *data; /* allocate new space for the local record list */ if (_record_list_entries == 0) { _record_list = (RECORD_LIST *) malloc(sizeof(RECORD_LIST)); memset(_record_list, 0, sizeof(RECORD_LIST)); if (_record_list == NULL) { cm_msg(MERROR, "db_open_record", "not enough memory"); return DB_NO_MEMORY; } _record_list_entries = 1; idx = 0; } else { /* check for a deleted entry */ for (idx = 0; idx < _record_list_entries; idx++) if (!_record_list[idx].handle) break; /* if not found, create new one */ if (idx == _record_list_entries) { _record_list = (RECORD_LIST *) realloc(_record_list, sizeof(RECORD_LIST) * (_record_list_entries + 1)); if (_record_list == NULL) { cm_msg(MERROR, "db_open_record", "not enough memory"); return DB_NO_MEMORY; } memset(&_record_list[_record_list_entries], 0, sizeof(RECORD_LIST)); _record_list_entries++; } } db_get_key(hDB, hKey, &key); /* check record size */ status = db_get_record_size(hDB, hKey, 0, &size); if (status != DB_SUCCESS) { _record_list_entries--; cm_msg(MERROR, "db_open_record", "cannot get record size, db_get_record_size() status %d", status); return DB_NO_MEMORY; } if (size != rec_size && ptr != NULL) { _record_list_entries--; std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_open_record", "struct size mismatch for \"%s\" (expected size: %d, size in ODB: %d)", path.c_str(), rec_size, size); return DB_STRUCT_SIZE_MISMATCH; } /* check for read access */ if (((key.access_mode & MODE_EXCLUSIVE) && (access_mode & MODE_WRITE)) || (!(key.access_mode & MODE_WRITE) && (access_mode & MODE_WRITE)) || (!(key.access_mode & MODE_READ) && (access_mode & MODE_READ))) { _record_list_entries--; return DB_NO_ACCESS; } if (access_mode & MODE_ALLOC) { data = malloc(size); if (data == NULL) { _record_list_entries--; cm_msg(MERROR, "db_open_record", "not enough memory, malloc(%d) returned NULL", size); return DB_NO_MEMORY; } memset(data, 0, size); *((void **) ptr) = data; } else { data = ptr; } /* copy record to local memory */ if (access_mode & MODE_READ && data != NULL) { status = db_get_record(hDB, hKey, data, &size, 0); if (status != DB_SUCCESS) { _record_list_entries--; cm_msg(MERROR, "db_open_record", "cannot get record, db_get_record() status %d", status); return DB_NO_MEMORY; } } /* copy local record to ODB */ if (access_mode & MODE_WRITE) { /* only write to ODB if not in MODE_ALLOC */ if ((access_mode & MODE_ALLOC) == 0) { status = db_set_record(hDB, hKey, data, size, 0); if (status != DB_SUCCESS) { _record_list_entries--; cm_msg(MERROR, "db_open_record", "cannot set record, db_set_record() status %d", status); return DB_NO_MEMORY; } } /* init a local copy of the record */ _record_list[idx].copy = malloc(size); if (_record_list[idx].copy == NULL) { cm_msg(MERROR, "db_open_record", "not enough memory"); return DB_NO_MEMORY; } memcpy(_record_list[idx].copy, data, size); } /* initialize record list */ _record_list[idx].handle = hKey; _record_list[idx].hDB = hDB; _record_list[idx].access_mode = access_mode; _record_list[idx].data = data; _record_list[idx].buf_size = size; _record_list[idx].dispatcher = dispatcher; _record_list[idx].info = info; /* add record entry in database structure */ return db_add_open_record(hDB, hKey, (WORD) (access_mode & ~MODE_ALLOC)); } /********************************************************************/ /** Open a record. Create a local copy and maintain an automatic update. This function is same as db_open_record(), except that it calls db_check_record(), db_get_record1() and db_create_record() to ensure that the ODB structure matches Parameters are the same as for db_open_record(): @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param ptr If access_mode includes MODE_ALLOC: Address of pointer which points to the record data after the call if access_mode includes not MODE_ALLOC: Address of record if ptr==NULL, only the dispatcher is called. @param rec_size Record size in bytes @param access_mode Mode for opening record, either MODE_READ or MODE_WRITE. May be or'ed with MODE_ALLOC to let db_open_record allocate the memory for the record. @param (*dispatcher) Function which gets called when record is updated.The argument list composed of: HNDLE hDB, HNDLE hKey, void *info @param info Additional info passed to the dispatcher. @param rec_str ASCII representation of ODB record in the format @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_MEMORY, DB_NO_ACCESS, DB_STRUCT_SIZE_MISMATCH */ INT db_open_record1(HNDLE hDB, HNDLE hKey, void *ptr, INT rec_size, WORD access_mode, void (*dispatcher) (INT, INT, void *), void *info, const char *rec_str) { if (rec_str) { int status; if (rec_size) { char* pbuf; int size = rec_size; pbuf = (char*)malloc(size); assert(pbuf != NULL); status = db_get_record1(hDB, hKey, pbuf, &size, 0, rec_str); free(pbuf); if (status != DB_SUCCESS) return status; } status = db_check_record(hDB, hKey, "", rec_str, TRUE); if (status != DB_SUCCESS) return status; } return db_open_record(hDB, hKey, ptr, rec_size, access_mode, dispatcher, info); } /********************************************************************/ /** Close a record previously opend with db_open_record. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_close_record(HNDLE hDB, HNDLE hKey) { #ifdef LOCAL_ROUTINES { INT i; for (i = 0; i < _record_list_entries; i++) if (_record_list[i].handle == hKey && _record_list[i].hDB == hDB) break; if (i == _record_list_entries) return DB_INVALID_HANDLE; /* remove record entry from database structure */ db_remove_open_record(hDB, hKey, TRUE); /* free local memory */ if (_record_list[i].access_mode & MODE_ALLOC) { free(_record_list[i].data); _record_list[i].data = NULL; } if (_record_list[i].access_mode & MODE_WRITE) { free(_record_list[i].copy); _record_list[i].copy = NULL; } memset(&_record_list[i], 0, sizeof(RECORD_LIST)); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Release local memory for open records. This routines is called by db_close_all_databases() and cm_disconnect_experiment() @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_close_all_records() { INT i; for (i = 0; i < _record_list_entries; i++) { if (_record_list[i].handle) { if (_record_list[i].access_mode & MODE_WRITE) { free(_record_list[i].copy); _record_list[i].copy = NULL; } if (_record_list[i].access_mode & MODE_ALLOC) { free(_record_list[i].data); _record_list[i].data = NULL; } memset(&_record_list[i], 0, sizeof(RECORD_LIST)); } } if (_record_list_entries > 0) { _record_list_entries = 0; free(_record_list); _record_list = NULL; } return DB_SUCCESS; } /********************************************************************/ /** db_open_record() and db_watch() event handler @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key which changed. @param index Index for array keys. @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_update_record_local(INT hDB, INT hKeyRoot, INT hKey, int index) { INT i, size, status; status = DB_INVALID_HANDLE; /* check all record entries for matching key */ for (i = 0; i < _record_list_entries; i++) if (_record_list[i].handle == hKeyRoot) { status = DB_SUCCESS; /* get updated data if record not opened in write mode */ if ((_record_list[i].access_mode & MODE_WRITE) == 0) { size = _record_list[i].buf_size; if (_record_list[i].data != NULL) { status = db_get_record(hDB, hKeyRoot, _record_list[i].data, &size, 0); // db_open_record() update //printf("db_open_record update status %d, size %d %d\n", status, _record_list[i].buf_size, size); } /* call dispatcher if requested */ if (_record_list[i].dispatcher) _record_list[i].dispatcher(hDB, hKeyRoot, _record_list[i].info); } } /* check all watch entries for matching key */ for (i = 0; i < _watch_list_entries; i++) if (_watch_list[i].handle == hKeyRoot) { status = DB_SUCCESS; /* call dispatcher if requested */ if (_watch_list[i].dispatcher) _watch_list[i].dispatcher(hDB, hKey, index, _watch_list[i].info); } return status; } /********************************************************************/ /** Relay db_open_record() and db_watch() notification to the remote client. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key which changed. @param index Index for array keys. @param s client socket. @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_update_record_mserver(INT hDB, INT hKeyRoot, INT hKey, int index, int client_socket) { char buffer[32]; int convert_flags = rpc_get_convert_flags(); NET_COMMAND* nc = (NET_COMMAND *) buffer; nc->header.routine_id = MSG_ODB; nc->header.param_size = 4 * sizeof(INT); *((INT *) nc->param) = hDB; *((INT *) nc->param + 1) = hKeyRoot; *((INT *) nc->param + 2) = hKey; *((INT *) nc->param + 3) = index; if (convert_flags) { rpc_convert_single(&nc->header.routine_id, TID_UINT32, RPC_OUTGOING, convert_flags); rpc_convert_single(&nc->header.param_size, TID_UINT32, RPC_OUTGOING, convert_flags); rpc_convert_single(&nc->param[0], TID_UINT32, RPC_OUTGOING, convert_flags); rpc_convert_single(&nc->param[4], TID_UINT32, RPC_OUTGOING, convert_flags); rpc_convert_single(&nc->param[8], TID_UINT32, RPC_OUTGOING, convert_flags); rpc_convert_single(&nc->param[12], TID_UINT32, RPC_OUTGOING, convert_flags); } /* send the update notification to the client */ send_tcp(client_socket, buffer, sizeof(NET_COMMAND_HEADER) + 4 * sizeof(INT), 0); return DB_SUCCESS; } /********************************************************************/ /** Send all records to the ODB which were changed locally since the last call to this function. This function is valid if used in conjunction with db_open_record() under the condition the record is open as MODE_WRITE access code. - Full example dbchange.c which can be compiled as follow \code gcc -DOS_LINUX -I/midas/include -o dbchange dbchange.c /midas/linux/lib/libmidas.a -lutil} \begin{verbatim} //------- dbchange.c #include "midas.h" #include "msystem.h" \endcode \code //-------- BOF dbchange.c typedef struct { INT my_number; float my_rate; } MY_STATISTICS; MY_STATISTICS myrec; #define MY_STATISTICS(_name) char *_name[] = {\ "My Number = INT : 0",\ "My Rate = FLOAT : 0",\ "",\ NULL } HNDLE hDB, hKey; // Main int main(unsigned int argc,char **argv) { char host_name[HOST_NAME_LENGTH]; char expt_name[HOST_NAME_LENGTH]; INT lastnumber, status, msg; BOOL debug=FALSE; char i, ch; DWORD update_time, mainlast_time; MY_STATISTICS (my_stat); // set default host_name[0] = 0; expt_name[0] = 0; // get default cm_get_environment(host_name, sizeof(host_name), expt_name, sizeof(expt_name)); // get parameters for (i=1 ; i= argc || argv[i+1][0] == '-') goto usage; if (strncmp(argv[i],"-e",2) == 0) strcpy(expt_name, argv[++i]); else if (strncmp(argv[i],"-h",2)==0) strcpy(host_name, argv[++i]); } else { usage: printf("usage: dbchange [-h ] [-e ]\n"); return 0; } } // connect to experiment status = cm_connect_experiment(host_name, expt_name, "dbchange", 0); if (status != CM_SUCCESS) return 1; // Connect to DB cm_get_experiment_database(&hDB, &hKey); // Create a default structure in ODB db_create_record(hDB, 0, "My statistics", strcomb1(my_stat).c_str()); // Retrieve key for that strucutre in ODB if (db_find_key(hDB, 0, "My statistics", &hKey) != DB_SUCCESS) { cm_msg(MERROR, "mychange", "cannot find My statistics"); goto error; } // Hot link this structure in Write mode status = db_open_record(hDB, hKey, &myrec, sizeof(MY_STATISTICS), MODE_WRITE, NULL, NULL); if (status != DB_SUCCESS) { cm_msg(MERROR, "mychange", "cannot open My statistics record"); goto error; } // initialize ss_getchar() ss_getchar(0); // Main loop do { // Update local structure if ((ss_millitime() - update_time) > 100) { myrec.my_number += 1; if (myrec.my_number - lastnumber) { myrec.my_rate = 1000.f * (float) (myrec.my_number - lastnumber) / (float) (ss_millitime() - update_time); } update_time = ss_millitime(); lastnumber = myrec.my_number; } // Publish local structure to ODB (db_send_changed_record) if ((ss_millitime() - mainlast_time) > 5000) { db_send_changed_records(); // <------- Call mainlast_time = ss_millitime(); } // Check for keyboard interaction ch = 0; while (ss_kbhit()) { ch = ss_getchar(0); if (ch == -1) ch = getchar(); if ((char) ch == '!') break; } msg = cm_yield(20); } while (msg != RPC_SHUTDOWN && msg != SS_ABORT && ch != '!'); error: cm_disconnect_experiment(); return 1; } //-------- EOF dbchange.c \endcode @return DB_SUCCESS */ INT db_send_changed_records() { INT i; for (i = 0; i < _record_list_entries; i++) if (_record_list[i].access_mode & MODE_WRITE) { if (memcmp(_record_list[i].copy, _record_list[i].data, _record_list[i].buf_size) != 0) { if (rpc_is_remote()) { int align = ss_get_struct_align(); rpc_call(RPC_DB_SET_RECORD|RPC_NO_REPLY, _record_list[i].hDB, _record_list[i].handle, _record_list[i].data, _record_list[i].buf_size, align); } else { db_set_record(_record_list[i].hDB, _record_list[i].handle, _record_list[i].data, _record_list[i].buf_size, 0); } memcpy(_record_list[i].copy, _record_list[i].data, _record_list[i].buf_size); } } return DB_SUCCESS; } /*------------------------------------------------------------------*/ /********************************************************************/ /** Watch an ODB subtree. The callback function gets called whenever a key in the watched subtree changes. The callback function receives the database handle and the key handle as parameters. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key at top of subtree to watch, zero for root. @param (*dispatcher) Function which gets called when record is updated.The argument list composed of: HNDLE hDB, HNDLE hKey @return DB_SUCCESS, DB_INVALID_HANDLE, DB_NO_MEMORY, DB_NO_ACCESS, DB_STRUCT_SIZE_MISMATCH */ INT db_watch(HNDLE hDB, HNDLE hKey, void (*dispatcher) (INT, INT, INT, void*), void* info) { INT idx, status; KEY key; /* check for valid key */ assert(hKey); /* allocate new space for the local record list */ if (_watch_list_entries == 0) { _watch_list = (WATCH_LIST *) malloc(sizeof(WATCH_LIST)); memset(_watch_list, 0, sizeof(WATCH_LIST)); if (_watch_list == NULL) { cm_msg(MERROR, "db_watch", "not enough memory"); return DB_NO_MEMORY; } _watch_list_entries = 1; idx = 0; } else { /* check for a deleted entry */ for (idx = 0; idx < _watch_list_entries; idx++) if (!_watch_list[idx].handle) break; /* if not found, create new one */ if (idx == _watch_list_entries) { _watch_list = (WATCH_LIST *) realloc(_watch_list, sizeof(WATCH_LIST) * (_watch_list_entries + 1)); if (_watch_list == NULL) { cm_msg(MERROR, "db_watch", "not enough memory"); return DB_NO_MEMORY; } memset(&_watch_list[_watch_list_entries], 0, sizeof(WATCH_LIST)); _watch_list_entries++; } } /* check key */ status = db_get_key(hDB, hKey, &key); if (status != DB_SUCCESS) { _watch_list_entries--; std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_watch", "cannot get key \"%s\"", path.c_str()); return DB_NO_MEMORY; } /* check for read access */ if (!(key.access_mode & MODE_READ)) { _watch_list_entries--; std::string path = db_get_path(hDB, hKey); cm_msg(MERROR, "db_watch", "cannot get key \"%s\"", path.c_str()); return DB_NO_ACCESS; } /* initialize record list */ _watch_list[idx].handle = hKey; _watch_list[idx].hDB = hDB; _watch_list[idx].dispatcher = dispatcher; _watch_list[idx].info = info; /* add record entry in database structure */ return db_add_open_record(hDB, hKey, MODE_WATCH); } /********************************************************************/ /** Remove watch callback from a key previously watched with db_watch. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key, zero for root. @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_unwatch(HNDLE hDB, HNDLE hKey) { #ifdef LOCAL_ROUTINES { INT i; for (i = 0; i < _watch_list_entries; i++) if (_watch_list[i].handle == hKey && _watch_list[i].hDB == hDB) break; if (i == _watch_list_entries) return DB_INVALID_HANDLE; /* remove record entry from database structure */ db_remove_open_record(hDB, hKey, TRUE); memset(&_watch_list[i], 0, sizeof(WATCH_LIST)); } #endif /* LOCAL_ROUTINES */ return DB_SUCCESS; } /********************************************************************/ /** Closes all watched variables. This routines is called by db_close_all_databases() and cm_disconnect_experiment() @return DB_SUCCESS, DB_INVALID_HANDLE */ INT db_unwatch_all() { for (int i = _watch_list_entries-1; i >= 0 ; i--) { if ((_watch_list[i].hDB == 0) && (_watch_list[i].handle == 0)) { // empty or deleted watch list entry continue; } db_unwatch(_watch_list[i].hDB, _watch_list[i].handle); } return DB_SUCCESS; } /*------------------------------------------------------------------*/ /* C++ wrapper for db_get_value */ INT EXPRT db_get_value_string(HNDLE hdb, HNDLE hKeyRoot, const char *key_name, int index, std::string* s, BOOL create, int create_string_length) { int status; int hkey; //printf("db_get_value_string: key_name [%s], index %d, string [%s], create %d, create_string_length %d\n", key_name, index, s->c_str(), create, create_string_length); if (index > 0 && create) { cm_msg(MERROR, "db_get_value_string", "cannot resize odb string arrays, please use db_resize_string() instead"); return DB_OUT_OF_RANGE; } status = db_find_key(hdb, hKeyRoot, key_name, &hkey); if (status == DB_SUCCESS) { KEY key; status = db_get_key(hdb, hkey, &key); if (status != DB_SUCCESS) return status; if (index < 0 || index >= key.num_values) return DB_OUT_OF_RANGE; int size = key.item_size; if (size == 0) { if (s) *s = ""; //printf("db_get_value_string: return empty string, item_size %d\n", key.item_size); return DB_SUCCESS; } char* buf = (char*)malloc(size); assert(buf != NULL); status = db_get_data_index(hdb, hkey, buf, &size, index, TID_STRING); if (status != DB_SUCCESS) { free(buf); return status; } if (s) *s = buf; free(buf); //printf("db_get_value_string: return [%s], len %d, item_size %d, size %d\n", s->c_str(), s->length(), key.item_size, size); return DB_SUCCESS; } else if (!create) { // does not exist and not asked to create it return status; } else { //printf("db_get_value_string: creating [%s]\n", key_name); status = db_create_key(hdb, hKeyRoot, key_name, TID_STRING); if (status != DB_SUCCESS) return status; status = db_find_key(hdb, hKeyRoot, key_name, &hkey); if (status != DB_SUCCESS) return status; assert(s != NULL); if (create_string_length == 0) { int size = s->length() + 1; // 1 byte for final \0 status = db_set_data_index(hdb, hkey, s->c_str(), size, index, TID_STRING); } else { char* buf = (char*)malloc(create_string_length); assert(buf); mstrlcpy(buf, s->c_str(), create_string_length); status = db_set_data_index(hdb, hkey, buf, create_string_length, index, TID_STRING); free(buf); } if (status != DB_SUCCESS) return status; //printf("db_get_value_string: created with value [%s]\n", s->c_str()); return DB_SUCCESS; } // NOT REACHED } /* C++ wrapper for db_set_value */ INT EXPRT db_set_value_string(HNDLE hDB, HNDLE hKeyRoot, const char *key_name, const std::string* s) { assert(s != NULL); int size = s->length() + 1; // 1 byte for final \0 //printf("db_set_value_string: key_name [%s], string [%s], size %d\n", key_name, s->c_str(), size); return db_set_value(hDB, hKeyRoot, key_name, s->c_str(), size, 1, TID_STRING); } /********************************************************************/ /** Change size of string arrays. This function can change the number of elements and the string element length of an array of strings. @param hDB ODB handle obtained via cm_get_experiment_database(). @param hKey Handle for key where search starts, zero for root. @param key_name Odb key name, if NULL, will resize ODB entry pointed to by hKey @param num_values New number of array elements, if 0, remains unchanged @param max_string_length New max string length for array elements, if 0, remains unchanged @return DB_SUCCESS, or error from db_find_key, db_create_key, db_get_data(), db_set_data() */ INT EXPRT db_resize_string(HNDLE hdb, HNDLE hKeyRoot, const char *key_name, int num_values, int max_string_length) { int status; int hkey; //printf("db_resize_string: key_name [%s], num_values %d, max_string_length %d\n", key_name, num_values, max_string_length); int old_num_values = 0; int old_item_size = 0; int old_size = 0; char* old_data = NULL; if (key_name) { status = db_find_key(hdb, hKeyRoot, key_name, &hkey); } else { hkey = hKeyRoot; status = DB_SUCCESS; } if (status == DB_SUCCESS) { KEY key; status = db_get_key(hdb, hkey, &key); if (status != DB_SUCCESS) return status; old_num_values = key.num_values; old_item_size = key.item_size; old_size = old_num_values * old_item_size; if (old_size > 0) { old_data = (char*)malloc(old_size); assert(old_data != NULL); int size = old_size; status = db_get_data(hdb, hkey, old_data, &size, TID_STRING); if (status != DB_SUCCESS) { free(old_data); return status; } assert(size == old_size); } } else { status = db_create_key(hdb, hKeyRoot, key_name, TID_STRING); if (status != DB_SUCCESS) return status; status = db_find_key(hdb, hKeyRoot, key_name, &hkey); if (status != DB_SUCCESS) return status; } //printf("old_num_values %d, old_item_size %d, old_size %d\n", old_num_values, old_item_size, old_size); int item_size = max_string_length; if (item_size < 1) item_size = old_item_size; if (num_values < 1) num_values = old_num_values; int new_size = num_values * item_size; char* new_data = (char*)malloc(new_size); assert(new_data); memset(new_data, 0, new_size); if (old_data) { int num = old_num_values; if (num > num_values) num = num_values; //printf("new num_values %d, item_size %d, new_size %d, old_size %d, to copy %d values\n", num_values, item_size, new_size, old_size, num); for (int i=0; iAddToObject("now", MJsonNode::MakeNumber(ss_time_sec())); scl->AddToObject("clients", clients); /* lock database */ db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; DWORD now = ss_millitime(); /* list clients */ for (int i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT *pclient = &pheader->client[i]; if (pclient->pid) { MJsonNode* c = MJsonNode::MakeObject(); c->AddToObject("slot", MJsonNode::MakeNumber(i)); c->AddToObject("pid", MJsonNode::MakeNumber(pclient->pid)); c->AddToObject("name", MJsonNode::MakeString(pclient->name)); std::string path = msprintf("/System/Clients/%d/Host", pclient->pid); const KEY* pkey = db_find_pkey_locked(pheader, NULL, path.c_str(), NULL, NULL); if (pkey) { int host_size = pkey->total_size; char* host = (char*)malloc(host_size); assert(host != NULL); db_get_data_locked(pheader, pkey, 0, host, &host_size, TID_STRING, NULL); c->AddToObject("host", MJsonNode::MakeString(host)); free(host); } c->AddToObject("watchdog_timeout_millisec", MJsonNode::MakeNumber(pclient->watchdog_timeout)); // "now" and "last_activity" is millisecond time wrapped around at 32 bits, must do unsigned 32-bit math here, not in javascript c->AddToObject("last_activity_millisec", MJsonNode::MakeNumber(now - pclient->last_activity)); clients->AddToArray(c); } } db_unlock_database(hDB); return scl; #else return MJsonNode::MakeNull(); #endif } MJsonNode* db_sor(HNDLE hDB, const char* root_path) { //printf("db_sor(%s)\n", root_path); assert(root_path != NULL); size_t root_path_len = strlen(root_path); #ifdef LOCAL_ROUTINES MJsonNode* sor = MJsonNode::MakeArray(); /* lock database */ db_lock_database(hDB); DATABASE *pdb = &_database[hDB - 1]; DATABASE_HEADER *pheader = pdb->database_header; /* list clients */ for (int i = 0; i < pheader->max_client_index; i++) { DATABASE_CLIENT *pclient = &pheader->client[i]; if (pclient->pid) { for (int j = 0; j < pclient->max_index; j++) { std::string path = db_get_path_locked(pheader, pclient->open_record[j].handle); if (path.length() < root_path_len) continue; if (strncmp(root_path, path.c_str(), root_path_len) != 0) continue; MJsonNode* c = MJsonNode::MakeObject(); c->AddToObject("name", MJsonNode::MakeString(pclient->name)); //c->AddToObject("handle", MJsonNode::MakeNumber(pclient->open_record[j].handle)); c->AddToObject("access_mode", MJsonNode::MakeNumber(pclient->open_record[j].access_mode)); c->AddToObject("flags", MJsonNode::MakeNumber(pclient->open_record[j].flags)); c->AddToObject("path", MJsonNode::MakeString(path.c_str())); sor->AddToArray(c); } } } db_unlock_database(hDB); return sor; #else return MJsonNode::MakeNull(); #endif } /** @} *//* end of odbfunctionc */ /* emacs * Local Variables: * tab-width: 8 * c-basic-offset: 3 * indent-tabs-mode: nil * End: */