/* starts a new thread in detached state, which in turn calls start_new_process_wait() */ void process_manager_start_new_process_detached(int num, const char *projname, int do_exchange_processes) { debug(1, "start new %d process%s for project %s, exchange=%d", num, (num>1?"es":""), projname, do_exchange_processes); /* Start the process creation thread in detached state because * we do not want to wait for it. Different from the handling * during the program startup there is no join() waiting for * the end of the thread and collecting its resources. */ /* NOTE: aside from the general rule * "malloc() and free() within the same function" * we transfer the responsibility for this memory * to the thread itself. */ struct thread_start_process_detached_args *targs = malloc(sizeof(*targs)); assert(targs); if ( !targs ) { logerror("ERROR: could not allocate memory"); qexit(EXIT_FAILURE); } targs->num = num; targs->project_name = strdup(projname); targs->do_exchange_processes = do_exchange_processes; pthread_t thread; int retval = pthread_create(&thread, NULL, process_manager_thread_start_process_detached, targs); if (retval) { errno = retval; logerror("ERROR: pthread_create"); qexit(EXIT_FAILURE); } }
static void db_statement_finalize(sqlite3_stmt *ppstmt) { int retval = sqlite3_finalize(ppstmt); if (SQLITE_OK != retval) { printlog("ERROR: finalizing sql statement: %s", sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } }
static void db_global_unlock(void) { int retval = pthread_mutex_unlock(&db_mutex_lock); if (retval) { errno = retval; logerror("ERROR: unlock db mutex lock"); qexit(EXIT_FAILURE); } }
//----------------------------------------------------------------------- local void fatal(const char *format,...) { if ( infile[0] != '\0' && modname[0] != '\0' ) fprintf(stderr,"Fatal [%s] (%s): ",infile,modname); va_list va; va_start(va,format); vfprintf(stderr,format,va); va_end(va); fprintf(stderr,"\n"); fclose(outfp); unlink(outfile); // fprintf(stderr,"press enter to exit.\n"); // getchar(); qexit(1); }
/* a child process died. * this may happen because we cancelled its operation or * the process died because of a bug or low memory or something else. * Get the project this process was tasked for. If there is no project then the * process was already scheduled to shut down or the process id did not belong * to us. Either way in this case we don't need to take action. * Else look for the number of idle processes and restart a new process if * needed. * * This function is called from the signal handler. * If the signal handler receives a SIGCHLD there may be more than one child * process having send a signal. We have to test all programs if they still * exist in RAM. */ void process_manager_process_died(void) { int retval; pid_t *pidlist; int listlen; int i; retval = db_get_complete_list_process(&pidlist, &listlen); for(i=0; i<listlen; i++) { /* check if we are during shutdown sequence. if not then restart the * process. * check if the process died during the initialisation sequence * if it did, remark the process being instable. * then start a new one. * Refrain from restarting if too much processes have died during the * initialization. */ const pid_t pid = pidlist[i]; retval = kill(pid, 0); if (-1 == retval) { if (ESRCH == errno) { /* child process died. */ process_manager_restart_process(pid); } else { logerror("ERROR: kill(%d,0) returned", pid); qexit(EXIT_FAILURE); } } else { // process still exists, do not act on it } } db_free_list_process(pidlist, listlen); }
static void *process_manager_thread_start_process_detached(void *arg) { assert(arg); struct thread_start_process_detached_args *tinfo = arg; pthread_t thread_id = pthread_self(); /* detach myself from the main thread. Doing this to collect resources after * this thread ends. Because there is no join() waiting for this thread. */ int retval = pthread_detach(thread_id); if (retval) { errno = retval; logerror("ERROR: detaching thread"); qexit(EXIT_FAILURE); } process_manager_start_new_process_wait(tinfo->num, tinfo->project_name, tinfo->do_exchange_processes); free(tinfo->project_name); free(arg); return NULL; }
//---------------------------------------------------------------------- uchar loadDialog(bool manual) { static const char fmt[] = "HELP\n" "Java-VM class file loading options Ü\n" " ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß\n" "\n" "Do not create 'import' segment with external-references\n" "\n" " Prohibits the creation of a segment with external names, classes, and\n" " methods.\n" "\n" "\n" "Create 'import' segment with with references from commands\n" "\n" " Only regular references to the import segment will be collected.\n" " If this option is off, then all references (including simple text\n" " references) will be collected.\n" "\n" "\n" "Field/variable declarations are included in references\n" "\n" " Cross references from field declarations are collected in the import\n" " segment. For example, a field declaration\n" "\n" " Field borland.ui.AboutDialog about\n" "\n" " creates a reference to class borland.ui.AboutDialog\n" "\n" "\n" "Method return types are included in references\n" "\n" " Cross references from the return type and arguments of method\n" " declarations are collected in the import segment.\n" " NOTE: The import segment does not contain classes that appear only in\n" " the arguments of the method declarations.\n" "\n" "Store unknown attributes to external files\n" "\n" " This option allows to extract non-standard attributes (if present)\n" " and store them to a set of files in the current directory.\n" " Filenames will have <classname>.<tagname>.<attributename> form\n" "\n" "\n" "Rename local (slot) variables (if information is available)\n" "\n" " This option allows to use names from variable declarations to\n" " rename local variables if the input file has this information.\n" "\n" "\n" "Create visible representation of stripped names\n" "\n" " Some java classes have their local\n" " names stripped. This option allows IDA to recreate such local names.\n" " NOTE: If this option is selected then all one-character names with\n" " the character code >= 0x80 will be ignored.\n" "\n" "\n" "Continue loading after errors in additional attributes\n" "\n" " Normally all errors in the classfile structure are fatal.\n" " If this option is on, errors in the additional attributes\n" " produce only warnings and the loading process continues.\n" "\n" "ENDHELP\n" "Java loading options\n" "\n" " Class File version %D.%D (JDK1.%D%A)\n" "\n" "\n" "<~D~o not create 'import' segment with external-references :R>\n" "<~C~reate 'import' segment with references from commands :R>\n" "<~F~ield/variable declarations are included in references :R>\n" "<~M~ethod return types are included in references :R>>\n" "\n" "<~S~tore unknown attributes to external files :C>\n" "<~R~ename local (slot) variables (if info is available) :C>\n" "<Create ~v~isible representation of stripped names :C>\n" "<Continue ~l~oading after errors in additional attributes :C>>\n" "\n" "\n"; #if ((MLD__DEFAULT & MLD_METHREF) && !(MLD__DEFAULT & MLD_VARREF)) || \ ((MLD__DEFAULT & MLD_VARREF) && !(MLD__DEFAULT & MLD_EXTREF)) #error #endif if(!manual) return(MLD__DEFAULT); short rtyp = #if (MLD__DEFAULT & MLD_METHREF) 3, #elif (MLD__DEFAULT & MLD_VARREF) 2, #elif (MLD__DEFAULT & MLD_EXTREF) 1, #else 0, #endif mod = (MLD__DEFAULT & (MLD_EXTATR | MLD_LOCVAR | MLD_STRIP | MLD_FORCE)) >> 3; #if (MLD_EXTATR >> 3) != 1 || (MLD_LOCVAR >> 3) != 2 || \ (MLD_STRIP >> 3) != 4 || (MLD_FORCE >> 3) != 8 #error #endif { uval_t maxv = curClass.MajVers, minv = curClass.MinVers, jdk = curClass.JDKsubver; if(!AskUsingForm_c(fmt, &maxv, &minv, &jdk, jdk == 3 ? "/CLDC" : "", &rtyp, &mod)) qexit(1); } idpflags &= ~IDM_REQUNK; // do not use 'request mode' when 'manual options' uchar ans = 0; #if MLD_EXTATR != (1<<3) || MLD_LOCVAR != (2<<3) || MLD_STRIP != (4<<3) || \ MLD_FORCE != (8<<3) #error #endif ans |= ((uchar)mod) << 3; #if MLD_EXTREF != 1 || MLD_VARREF != 2 || MLD_METHREF != 4 #error #endif ans |= (uchar)((1 << rtyp) - 1); return(ans); }
/* starts "num" new child processes synchronously. * param num: number of processes to start (num>=0) * param project: project to manage them * param do_exchange_processes: if true removes all active processes and replaces them with the new created ones. * else integrate them in the list of active processes. */ void process_manager_start_new_process_wait(int num, const char *projname, int do_exchange_processes) { assert(projname); assert(num > 0); pthread_t threads[num]; int i; int retval; retval = db_get_startup_failures(projname); if ( 0 > retval ) { // too much dying processes during init phase, do not start new processes printlog("ERROR: can not get number of startup failures, function call failed for project %s", projname); qexit(EXIT_FAILURE); } else if (max_nr_process_crashes > retval+1) { printlog("Starting %d process%s for project '%s'", num, (num>1)?"es":"", projname); /* start all thread in parallel */ for (i=0; i<num; i++) { /* NOTE: aside from the general rule * "malloc() and free() within the same function" * we transfer the responsibility for this memory * to the thread itself. */ struct thread_start_new_child_args *targs = malloc(sizeof(*targs)); assert(targs); if ( !targs ) { logerror("ERROR: could not allocate memory"); qexit(EXIT_FAILURE); } targs->project_name = strdup(projname); retval = pthread_create(&threads[i], NULL, process_manager_thread_start_new_child, targs); if (retval) { errno = retval; logerror("ERROR: creating thread"); qexit(EXIT_FAILURE); } debug(1, "[%lu] started thread %lu", pthread_self(), threads[i]); } /* wait for those threads */ for (i=0; i<num; i++) { debug(1, "[%lu] join thread %lu", pthread_self(), threads[i]); retval = pthread_join(threads[i], NULL); if (retval) { errno = retval; logerror("ERROR: joining thread"); qexit(EXIT_FAILURE); } } /* move the processes from the initialization list to the active process * list. * If we got the option to exchange the processes then first move all * existing processes from the active list to the shutdown queue. * * Note: The option to exchange the processes is usually set if a new * configuration file has been copied to the processes. * If a new configuration file arrives the number of crashed processes is * reset. But if we do this during a crashing process, the number becomes * invalid. So we can not reset the number in * qgis_project_check_inotify_config_changed(), because it is not * protected. We have the reset the number over here. */ if (do_exchange_processes) { // TODO: move only those processes which have been started above db_move_all_process_from_active_to_shutdown_list(projname); db_reset_startup_failures(projname); // TODO: move this line to the config change manager } db_move_all_idle_process_from_init_to_active_list(projname); statistic_add_process_start(num); } else { printlog("WARNING: max number (%d) of startup failures in project %s reached." " Stoppped creating new processes until the configuration for this project has changed", max_nr_process_crashes, projname); } }
/* return the child process id if successful, 0 otherwise */ static int process_manager_thread_function_start_new_child(struct thread_start_new_child_args *tinfo) { assert(tinfo); const char *project_name = tinfo->project_name; const char *command = config_get_process( project_name ); debug(1, "project '%s' start new child process '%s'", project_name, command); if (NULL == command || 0 == strlen(command)) { printlog("ERROR: no process path specified. Not starting any process for project '%s'", project_name); return 0; } /* prepare the socket to connect to this child process only */ /* NOTE: Linux allows abstract socket names which have no representation * in the filesystem namespace. */ int retval = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (-1 == retval) { logerror("ERROR: can not create socket for fcgi program"); qexit(EXIT_FAILURE); } const int childsocket = retval; /* Create a unique socket name without a file system inode. * The name is "\0qgis-schedulerd-socket0", "..1", etc. * We just count upwards until integer overflow and then start * over at 0. * If one name is already given to a socket, bind() returns EADDRINUSE. * In this case we try again. */ /* security rope for the really unlikely case * that we got no more numbers free. * To prevent infinite loop */ retval = pthread_mutex_lock (&socket_id_mutex); if (retval) { errno = retval; logerror("ERROR: acquire mutex"); qexit(EXIT_FAILURE); } unsigned int socket_suffix_start = socket_id-1; retval = pthread_mutex_unlock (&socket_id_mutex); if (retval) { errno = retval; logerror("ERROR: unlock mutex"); qexit(EXIT_FAILURE); } for (;;) { retval = pthread_mutex_lock (&socket_id_mutex); if (retval) { errno = retval; logerror("ERROR: acquire mutex"); qexit(EXIT_FAILURE); } unsigned int socket_suffix = socket_id++; retval = pthread_mutex_unlock (&socket_id_mutex); if (retval) { errno = retval; logerror("ERROR: unlock mutex"); qexit(EXIT_FAILURE); } if (socket_suffix == socket_suffix_start) { /* we tested UINT_MAX numbers without success. * exit here, because we can not get any more numbers. * Or we have a programmers error here.. */ debug(1, "ERROR: out of numbers to create socket name. exit"); qexit(EXIT_FAILURE); } struct sockaddr_un childsockaddr; childsockaddr.sun_family = AF_UNIX; retval = snprintf( childsockaddr.sun_path, sizeof(childsockaddr.sun_path), "%c%s%u", '\0', base_socket_desc, socket_suffix ); if (-1 == retval) { logerror("ERROR: calling string format function snprintf"); qexit(EXIT_FAILURE); } retval = bind(childsocket, (struct sockaddr *)&childsockaddr, sizeof(childsockaddr)); if (-1 == retval) { if (EADDRINUSE==errno) { continue; // reiterate with next number } else { logerror("ERROR: calling bind"); qexit(EXIT_FAILURE); } } debug(1, "start project '%s', bound socket to '\\0%s'", project_name, childsockaddr.sun_path+1); break; } /* the child process listens for connections, one at a time */ retval = listen(childsocket, 1); if (-1 == retval) { logerror("ERROR: can not listen to socket connecting fast cgi application"); qexit(EXIT_FAILURE); } /* preparation of data before fork() so we don't need to call functions with * mutexes after fork() (see below for further reading). */ static const int maxenv = 128; // to prevent infinite loop support 128 environment variables at max int lenkey = 0; int numkey = 0; const char **keys = NULL; const char **values = NULL; int i; for (i=0; i<maxenv; i++) { const char *key = config_get_env_key(project_name, i); if ( !key ) break; const char *value = config_get_env_value(project_name, i); if ( !value ) break; int lenvalue = lenkey; int numvalue = numkey; arraycat(&values, &numvalue, &lenvalue, &value, sizeof(*values) ); arraycat(&keys, &numkey, &lenkey, &key, sizeof(*keys) ); debug(1, "project %s: add %s = %s to environment", project_name, key, value); } const char *working_directory = config_get_working_directory(project_name); pid_t pid = fork(); if (0 == pid) { /* child */ /* according to * http://www.linuxprogrammingblog.com/threads-and-fork-think-twice-before-using-them * we may only call async-safe functions in multithreaded programs (this!) * after calling fork() (here!). * * So we are not allowed to call ANY function with in turn calls locks * or mutexes. We should only call functions which are listed as async-safe * in signal(7). * NOTE: setenv() is not listed as async-safe. Unfortunately I don't see * any other safe way to prepare the environment for the child only? * Maybe kill all threads (except this) with a combination of * pthread_atfork() and pthread_cancel() ? */ /* Add the configured environment to the existing environment */ /* Note: shall we clean up before? */ for (i=0; i<lenkey; i++) { // debug(1, "project %s: add %s = %s to environment", project_name, key, value); # no debug message allowed because of locking retval = setenv(keys[i], values[i], 1); if (retval) { // logerror("ERROR: can not set environment with key='%s' and value='%s'", key[i], value[i]); # no log message allowed because of locking qexit(EXIT_FAILURE); } } // no need to free() memory, is freed by exec() /* change working dir * close file descriptor stdin = 0 * assign socket file descriptor to fd 0 * fork * exec */ retval = chdir(working_directory); if (-1 == retval) { // logerror("ERROR: calling chdir"); # no log message allowed because of locking } retval = dup2(childsocket, FCGI_LISTENSOCK_FILENO); if (-1 == retval) { // logerror("ERROR: calling dup2"); # no log message allowed because of locking qexit(EXIT_FAILURE); } /* close all file descriptors different from 0. The fd different from * 1 and 2 are opened during open() and socket() calls with FD_CLOEXEC * flag enabled. This way, all fds are closed during exec() call. * TODO: assign an error log file to fd 1 and 2 */ close(STDOUT_FILENO); close(STDERR_FILENO); execl(command, command, NULL); // logerror("ERROR: could not execute '%s': ", command); # no log message allowed because of locking qexit(EXIT_FAILURE); } else if (0 < pid) { /* parent */ free(keys); free(values); debug(1, "project '%s' started new child process '%s', pid %d", project_name, command, pid); db_add_process( project_name, pid, childsocket); return pid; } else { /* error */ logerror("ERROR: can not fork"); qexit(EXIT_FAILURE); } return 0; }
static void process_manager_thread_function_init_new_child(struct thread_init_new_child_args *tinfo) { assert(tinfo); const pid_t pid = tinfo->pid; assert(pid > 0); const char *projname = tinfo->project_name; assert(projname); const pthread_t thread_id = pthread_self(); char *buffer = NULL; int retval; int has_child_crash = 0; int len; struct fcgi_message_s *message = NULL; db_process_set_state_init(pid, thread_id); debug(1, "init new spawned child process for project '%s'", projname); // char debugfile[256]; // sprintf(debugfile, "/tmp/threadinit.%lu.dump", thread_id); // int debugfd = open(debugfile, (O_WRONLY|O_CREAT|O_TRUNC), (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)); // if (-1 == debugfd) // { // debug(1, "ERROR: can not open file '%s': ", debugfile); // logerror(NULL); // qexit(EXIT_FAILURE); // } /* open the socket to the child process */ struct sockaddr_un sockaddr; socklen_t sockaddrlen = sizeof(sockaddr); int childunixsocketfd = db_get_process_socket(pid); retval = getsockname(childunixsocketfd, (struct sockaddr *)&sockaddr, &sockaddrlen); if (-1 == retval) { logerror("ERROR: retrieving the name of child process socket %d", childunixsocketfd); has_child_crash = 1; } /* leave the original child socket and create a new one on the opposite * side. * create the socket in blocking mode (non SOCK_NONBLOCK) because we need the * read() and write() calls waiting on it. */ if (!has_child_crash) { retval = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (-1 == retval) { logerror("ERROR: can not create socket to child process"); has_child_crash = 1; } } if (!has_child_crash) { childunixsocketfd = retval; // refers to the socket this program connects to the child process retval = connect(childunixsocketfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); if (-1 == retval) { logerror("ERROR: init can not connect to child process"); has_child_crash = 1; } } // debug(1, "init project '%s', connected to child via socket '\\0%s'", projname, sockaddr.sun_path+1); static const int maxbufferlen = 4096; static const int requestid = 1; if (!has_child_crash) { /* create the fcgi data and * send the fcgi data to the child process */ buffer = malloc(maxbufferlen); assert(buffer); if ( !buffer ) { logerror("ERROR: could not allocate memory"); qexit(EXIT_FAILURE); } message = fcgi_message_new_begin(requestid, FCGI_RESPONDER, 0); len = fcgi_message_write(buffer, maxbufferlen, message); if (-1 == len) // TODO: be more flexible if buffer too small { debug(1, "fcgi message buffer too small (%d)", maxbufferlen); qexit(EXIT_FAILURE); } // retval = write(debugfd, buffer, len); retval = write(childunixsocketfd, buffer, len); if (-1 == retval) { logerror("ERROR: can not write to child process"); has_child_crash = 1; } //printf(stderr, "write to child prog (%d): %.*s\n", retval, buffer, retval); fcgi_message_delete(message); } if (!has_child_crash) { char *parambuffer = (char *)buffer; int remain_len = maxbufferlen; int i; for (i=0; i<128; i++) { const char *key = config_get_init_key(projname, i); if (!key) break; const char *value = config_get_init_value(projname, i); if (!value) break; debug(1, "Param %s=%s", key, value); retval = fcgi_param_list_write(parambuffer, remain_len, key, value); if (-1 == retval) { // TODO: be more flexible if buffer too small debug(1, "fcgi parameter buffer too small (%d)", maxbufferlen); qexit(EXIT_FAILURE); } parambuffer += retval; remain_len -= retval; } len = maxbufferlen - remain_len; if (i>=128) { debug(1, "fcgi parameter too many key/value pairs"); has_child_crash = 1; } } if (!has_child_crash) { /* send parameter list */ message = fcgi_message_new_parameter(requestid, buffer, len); len = fcgi_message_write(buffer, maxbufferlen, message); if (-1 == len) // TODO: be more flexible if buffer too small { debug(1, "fcgi message buffer too small (%d)", maxbufferlen); qexit(EXIT_FAILURE); } // retval = write(debugfd, buffer, len); retval = write(childunixsocketfd, buffer, len); if (-1 == retval) { logerror("ERROR: can not write to child process"); has_child_crash = 1; } fcgi_message_delete(message); } if (!has_child_crash) { /* send empty parameter list to signal EOP */ message = fcgi_message_new_parameter(requestid, "", 0); len = fcgi_message_write(buffer, maxbufferlen, message); if (-1 == len) // TODO: be more flexible if buffer too small { debug(1, "fcgi message buffer too small (%d)", maxbufferlen); qexit(EXIT_FAILURE); } // retval = write(debugfd, buffer, len); retval = write(childunixsocketfd, buffer, len); if (-1 == retval) { logerror("ERROR: can not write to child process"); has_child_crash = 1; } fcgi_message_delete(message); } if (!has_child_crash) { message = fcgi_message_new_stdin(requestid, "", 0); len = fcgi_message_write(buffer, maxbufferlen, message); if (-1 == len) { debug(1, "fcgi message buffer too small (%d)", maxbufferlen); qexit(EXIT_FAILURE); } // retval = write(debugfd, buffer, len); retval = write(childunixsocketfd, buffer, len); if (-1 == retval) { logerror("ERROR: can not write to child process"); has_child_crash = 1; } // write stdin = "" twice if (!has_child_crash) { // retval = write(debugfd, buffer, len); retval = write(childunixsocketfd, buffer, len); if (-1 == retval) { logerror("ERROR: can not write to child process"); has_child_crash = 1; } } fcgi_message_delete(message); } if (!has_child_crash) { /* now read from socket into void until no more data * we do it to make sure that the child process has completed the request * and filled up its cache. * * Set a timeout of N seconds in case the program crashed during start. If * the timeout catches move the process to the shutdown module and in the * database mark the process as crashed. */ const int init_read_timeout = config_get_read_timeout(projname); retval = 1; while (retval>0) { retval = read_timeout(childunixsocketfd, buffer, maxbufferlen, init_read_timeout*1000); // debug(1, "init project '%s' received:\n%.*s", projname, retval, buffer); if (-1 == retval) { logerror("ERROR: read() from child process during init phase"); if (ETIMEDOUT == errno) { has_child_crash = 1; } } } } /* if the child process died during the initialization we need to figure * this out. * there may be a race condition between the signal handler and this thread * so we test the existence of the child process after the read. */ if (has_child_crash) { printlog("starting new process for project %s", projname); process_manager_restart_process(pid); process_manager_process_died_during_init(pid, projname); } else { retval = kill(pid, 0); if (-1 == retval) { if (ESRCH == errno) { /* child process died during initialization. * start a new one if possible */ process_manager_process_died_during_init(pid, projname); } else { logerror("ERROR: kill(%d,0) returned", pid); qexit(EXIT_FAILURE); } } else { db_process_set_state_idle(pid); } } /* ok, we did read each and every byte from child process. * now close this and set idle * Try to close the file secriptor even if error occurred previously */ retval = close(childunixsocketfd); debug(1, "closed child socket fd %d, retval %d, errno %d", childunixsocketfd, retval, errno); // close(debugfd); debug(1, "init child process for project '%s' done. waiting for input..", projname); free(buffer); }
static int db_select_parameter_callback(enum db_select_statement_id sid, db_callback callback, void *callback_arg, ...) { /* The life-cycle of a prepared statement object usually goes like this: * * 1. Create the prepared statement object using sqlite3_prepare_v2(). * 2. Bind values to parameters using the sqlite3_bind_*() interfaces. * 3. Run the SQL by calling sqlite3_step() one or more times. * 4. Reset the prepared statement using sqlite3_reset() then go back to step 2. Do this zero or more times. * 5. Destroy the object using sqlite3_finalize(). */ int retval; const int loglevel = config_get_debuglevel(); #define BUFFERSIZE 64 char debug_buffer[BUFFERSIZE] = {0,}; int debug_loglen = BUFFERSIZE; char *debug_log = NULL; if (1 <= loglevel) { debug_log = malloc(BUFFERSIZE); if (NULL == debug_log) { logerror("ERROR: malloc"); qexit(EXIT_FAILURE); } *debug_log = '\0'; } assert(dbhandler); assert(sid < DB_SELECT_ID_MAX); const char *sql = db_select_statement[sid]; sqlite3_stmt *ppstmt; if ( !db_prepared_stmt[sid] ) db_prepared_stmt[sid] = ppstmt = db_statement_prepare(sid); else ppstmt = db_prepared_stmt[sid]; /* evaluate the remaining arguments */ int col = 1; // column position index va_list args; va_start(args, callback_arg); while (*sql) { if ('%' == *sql++) { switch(*sql++) { case 'p': /* found pointer value "%p". The next argument is the * type "void *" */ { assert(0); // TODO need to extend "%p" to "%NNNp" with NNN being decimal number describing the size of 'p' const void *v = va_arg(args, void *); retval = sqlite3_bind_blob(ppstmt, col++, v, -1, SQLITE_STATIC); if ( SQLITE_OK != retval ) { printlog("ERROR: in sql '%s' bind column %d returned: %s", sql, col, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } if (1 <= loglevel) { snprintf(debug_buffer, BUFFERSIZE-1, ", %p", v); strnbcat(&debug_log, &debug_loglen, debug_buffer); } break; } case 'f': /* found double value "%f". The next argument is the * type "double" */ { double d = va_arg(args, double); retval = sqlite3_bind_double(ppstmt, col++, d); if ( SQLITE_OK != retval ) { printlog("ERROR: in sql '%s' bind column %d returned: %s", sql, col, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } if (1 <= loglevel) { snprintf(debug_buffer, BUFFERSIZE-1, ", %f", d); strnbcat(&debug_log, &debug_loglen, debug_buffer); } break; } case 's': /* found string value "%s". The next argument is the * type "const char *" */ { const char *s = va_arg(args, char *); retval = sqlite3_bind_text(ppstmt, col++, s, -1, SQLITE_STATIC); if ( SQLITE_OK != retval ) { printlog("ERROR: in sql '%s' bind column %d returned: %s", sql, col, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } if (1 <= loglevel) { snprintf(debug_buffer, BUFFERSIZE-1, ", %s", s); strnbcat(&debug_log, &debug_loglen, debug_buffer); } break; } case 'd': // fall through case 'i': /* found integer value "%i". The next argument is the * type "int" */ { int i = va_arg(args, int); retval = sqlite3_bind_int(ppstmt, col++, i); if ( SQLITE_OK != retval ) { printlog("ERROR: in sql '%s' bind column %d returned: %s", sql, col, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } if (1 <= loglevel) { snprintf(debug_buffer, BUFFERSIZE-1, ", %d", i); strnbcat(&debug_log, &debug_loglen, debug_buffer); } break; } case 'l': /* found 64bit integer value "%l". The next argument is the * type "long long int" */ { long long int l = va_arg(args, long long int); retval = sqlite3_bind_int64(ppstmt, col++, l); if ( SQLITE_OK != retval ) { printlog("ERROR: in sql '%s' bind column %d returned: %s", sql, col, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } if (1 <= loglevel) { snprintf(debug_buffer, BUFFERSIZE-1, ", %lld", l); strnbcat(&debug_log, &debug_loglen, debug_buffer); } break; } case '%': /* found double percent sign "%%". just go on */ break; case '\0': /* percentage sign has been the last character in the string * rewind the string, so the outer while() catches the end of * the string. */ sql--; break; default: /* unknown character found. exit */ printlog("ERROR: unknown character found in sql string '%s', position %ld: %c", db_select_statement[sid], (long int)(sql - db_select_statement[sid]), *sql); qexit(EXIT_FAILURE); } } } va_end(args); debug(1, "db selected %d: '%s%s'", sid, db_select_statement[sid], debug_log); free(debug_log); int try_num = 0; do { retval = sqlite3_step(ppstmt); if (SQLITE_BUSY == retval) try_num++; else if (SQLITE_ROW == retval) { /* there is data available, fetch data and recall step() */ debug(1, "data available: sql '%s'", db_select_statement[sid]); assert(callback); if ( !callback ) { printlog("ERROR: data available but no callback function defined for sql '%s'", db_select_statement[sid]); /* go on with the loop until no more data is available */ } else { /* prepare the callback data */ const int ncol_result = sqlite3_column_count(ppstmt); int *type = calloc(ncol_result, sizeof(*type)); if ( !type ) { printlog("ERROR: not enough memory"); qexit(EXIT_FAILURE); } union callback_result_t *results = calloc(ncol_result, sizeof(*results)); if ( !results ) { printlog("ERROR: not enough memory"); qexit(EXIT_FAILURE); } const char **cols = calloc(ncol_result, sizeof(*cols)); if ( !cols ) { printlog("ERROR: not enough memory"); qexit(EXIT_FAILURE); } int i; for (i = 0; i<ncol_result; i++) { int mytype = type[i] = sqlite3_column_type(ppstmt, i); switch(mytype) { case SQLITE_INTEGER: results[i].integer = sqlite3_column_int64(ppstmt, i); break; case SQLITE_FLOAT: results[i].floating = sqlite3_column_double(ppstmt, i); break; case SQLITE_TEXT: results[i].text = sqlite3_column_text(ppstmt, i); break; case SQLITE_BLOB: results[i].blob = sqlite3_column_blob(ppstmt, i); break; case SQLITE_NULL: break; default: printlog("ERROR: unknown type %d", mytype); qexit(EXIT_FAILURE); } cols[i] = sqlite3_column_name(ppstmt, i); } retval = callback(callback_arg, ncol_result, type, results, cols); //TODO: reuse arrays for the next row free(type); free(results); free(cols); if (retval) { retval = SQLITE_ABORT; break; } } } else break; } while (try_num < DB_MAX_RETRIES); switch(retval) { case SQLITE_BUSY: printlog("ERROR: db busy! Exceeded max calls (%d) to fetch data", try_num); break; case SQLITE_ROW: /* error: there has been data available, * but the program broke out of the loop? */ assert(0); break; case SQLITE_ERROR: /* there has been a data error. Print out and reset() the statement */ { const char *sql = db_select_statement[sid]; printlog("ERROR: stepping sql statement '%s': %s", sql, sqlite3_errstr(retval)); if (db_exit_on_error) { printlog("exiting.."); qexit(EXIT_FAILURE); } else { retval = sqlite3_reset(ppstmt); if (SQLITE_OK != retval) { printlog("ERROR: resetting sql statement '%s': %s", sql, sqlite3_errstr(retval)); } } } break; case SQLITE_MISUSE: /* the statement has been incorrect */ printlog("ERROR: misuse of prepared sql statement '%s'", db_select_statement[sid]); if (db_exit_on_error) { printlog("exiting.."); qexit(EXIT_FAILURE); } break; case SQLITE_ABORT: printlog("ERROR: abort in callback function during steps of sql '%s'", db_select_statement[sid]); qexit(EXIT_FAILURE); break; case SQLITE_OK: case SQLITE_DONE: /* the statement has finished successfully */ retval = sqlite3_reset(ppstmt); if (SQLITE_OK != retval) { const char *sql = db_select_statement[sid]; printlog("ERROR: resetting sql statement '%s': %s", sql, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } break; } // if ( !db_prepared_stmt[sid] ) // db_statement_finalize(ppstmt); return 0; }
static sqlite3_stmt *db_statement_prepare(enum db_select_statement_id sid) { assert(sid < DB_SELECT_ID_MAX); sqlite3_stmt *ppstmt; const char *const sql = db_select_statement[sid]; const char *srcsql = sql; /* exchange "%N" markers with '?' */ char * const copysql = strdup(sql); char *destsql = copysql; char c; do { c = *srcsql++; if ('%' == c) { switch(*srcsql++) { case 'p': /* found pointer value "%p". */ // fall through case 'f': /* found double value "%f". */ // fall through case 's': /* found string value "%s". */ // fall through case 'd': /* found integer value "%i". */ // fall through case 'i': /* found integer value "%i". */ // fall through case 'l': /* found long long integer value "%l". */ /* exchange "%N" with '?' */ c = '?'; break; case '%': /* found double percent sign "%%". just go on */ /* exchange "%%" with '%' */ break; case '\0': /* percentage sign has been the last character in the string * rewind the string, so the outer while() catches the end of * the string. */ debug(1, "Huh? found '%%' at the end of the sql '%s'", sql); srcsql--; break; default: /* unknown character found. exit */ srcsql--; printlog("ERROR: unknown character found in sql string '%s', position %ld: '%c'", db_select_statement[sid], (long int)(srcsql - db_select_statement[sid]), *srcsql); qexit(EXIT_FAILURE); } } *destsql++ = c; } while ( c ); // moved while() down here to copy terminating '\0' to "destsql" debug(1, "transferred sql from '%s' to '%s'", sql, copysql); int retval = sqlite3_prepare(dbhandler, copysql, -1, &ppstmt, NULL); if (SQLITE_OK != retval) { printlog("ERROR: preparing sql statement '%s': %s", sql, sqlite3_errstr(retval)); qexit(EXIT_FAILURE); } free(copysql); return ppstmt; }
//---------------------------------------------------------------------- int upgrade_db_format(int ver, netnode constnode) { if(askyn_c(1, "AUTOHIDE REGISTRY\nHIDECANCEL\n" "The database has an old java data format.\n" "Do you want to upgrade it?") <= 0) qexit(1); switch ( ver ) { default: INTERNAL("upgrade::ver"); case IDP_JDK12: break; } // change format: jdk-version if ( curClass.MinVers > 0x8000u ) { badbase: return(0); } curClass.MajVers = JDK_MIN_MAJOR; if ( curClass.MinVers >= 0x8000 ) { curClass.MinVers &= ~0; ++curClass.MajVers; curClass.JDKsubver = 2; } else if ( curClass.MinVers >= JDK_1_1_MINOR ) ++curClass.JDKsubver; // change format: This #ifdef __BORLANDC__ #if offsetof(ClassInfo, This.Ref) != offsetof(ClassInfo, This.Name) || \ offsetof(ClassInfo, This.Dscr) != offsetof(ClassInfo, This.Name) + 2 #error #endif #endif curClass.This.Ref = (curClass.This.Ref << 16) | curClass.This.Dscr; if ( !curClass.This.Name ) goto badbase; // change format: Super #ifdef __BORLANDC__ #if offsetof(ClassInfo, super.Ref) != offsetof(ClassInfo, super.Name) || \ offsetof(ClassInfo, super.Dscr) != offsetof(ClassInfo, super.Name) + 2 #error #endif #endif switch ( curClass.super.Name ) { case 0: // absent curClass.super.Ref &= 0; break; case 0xFFFF: // bad index ++curClass.super.Name; break; default: // reverse order curClass.super.Ref = (curClass.super.Ref << 16) | curClass.super.Dscr; break; } // validate: impNode if ( curClass.impNode && !netnode(curClass.impNode).altval(0) ) goto badbase; // change variable 'errload' in previous version if ( curClass.maxSMsize ) { curClass.extflg |= XFL_C_ERRLOAD; curClass.maxSMsize &= 0; } // set segments type type for special segments segment_t *S; if ( (S = getseg(curClass.startEA)) == NULL ) goto badbase; S->set_hidden_segtype(true); S->update(); if ( curClass.xtrnCnt ) { if ( (S = getseg(curClass.xtrnEA)) == NULL ) goto badbase; S->set_hidden_segtype(true); S->update(); } curClass.extflg |= XFL_C_DONE; // do not repeat datalabel destroyer :) // change: method/fields format #define SGEXPSZ (sizeof(SegInfo) - offsetof(SegInfo, varNode)) #define FMEXPSZ (sizeof(_FMid_) - offsetof(_FMid_, _UNUSED_ALING)) #define FLEXPSZ (sizeof(FieldInfo) - offsetof(FieldInfo, annNodes)) uval_t oldsize = sizeof(SegInfo) - FMEXPSZ - SGEXPSZ; for(int pos=-(int)curClass.MethodCnt; pos<=(int)curClass.FieldCnt; pos++) { union { SegInfo s; FieldInfo f; _FMid_ id; uchar _space[qmax(sizeof(SegInfo), sizeof(FieldInfo)) + 1]; }u; if ( !pos ) { // class node oldsize += (sizeof(FieldInfo) - FLEXPSZ) - (sizeof(SegInfo) - SGEXPSZ); continue; } if ( ClassNode.supval(pos, &u, sizeof(u)) != oldsize ) goto badbase; memmove((uchar *)&u.id + sizeof(u.id), &u.id._UNUSED_ALING, (size_t)oldsize - offsetof(_FMid_, _UNUSED_ALING)); u.id._UNUSED_ALING = 0; u.id.utsign = 0; if ( u.id.extflg & ~EFL__MASK ) goto badbase; u.id.extflg &= (EFL_NAMETYPE); if ( pos > 0 ) { // fields memset(&u.f.annNodes, 0, sizeof(u.f)-offsetof(FieldInfo, annNodes)); ClassNode.supset(pos, &u.f, sizeof(u.f)); continue; } // segments memset(&u.s.varNode, 0, sizeof(u.s) - offsetof(SegInfo, varNode)); if ( u.s.thrNode && !netnode(u.s.thrNode).altval(0) ) { netnode(u.s.thrNode).kill(); // empty node (old format) u.s.thrNode = 0; } // have locvars? if ( u.s.DataSize ) { if ( (S = getseg(u.s.DataBase)) == NULL ) goto badbase; S->type = SEG_BSS; S->set_hidden_segtype(true); S->update(); } // change: Exception format if ( u.s.excNode ) { register ushort i, j; netnode enode(u.s.excNode); if ( (j = (ushort)enode.altval(0)) == 0 ) goto badbase; ea_t ea = u.s.startEA + u.s.CodeSize; i = 1; do { Exception exc; if ( enode.supval(i, &exc, sizeof(exc)) != sizeof(exc) ) goto badbase; #ifdef __BORLANDC__ #if offsetof(Exception, filter.Ref) != offsetof(Exception, filter.Name) || \ offsetof(Exception, filter.Dscr) != offsetof(Exception, filter.Name) + 2 #error #endif #endif if ( !exc.filter.Name != !exc.filter.Dscr ) goto badbase; exc.filter.Ref = (exc.filter.Ref << 16) | exc.filter.Dscr; // was reverse order if ( exc.filter.Name == 0xFFFF ) ++exc.filter.Name; enode.supset(i, &exc, sizeof(exc)); set_exception_xref(&u.s, exc, ea); }while ( ++i <= j ); } ClassNode.supset(pos, &u.s, sizeof(u.s)); //rename local variables (for references) if ( u.s.DataSize ) { int i = u.s.DataSize; ea_t ea = u.s.DataBase + i; do { char str[MAXNAMELEN]; qsnprintf(str, sizeof(str), "met%03u_slot%03u", u.s.id.Number, --i); --ea; if ( do_name_anyway(ea, str)) make_name_auto(ea ); else hide_name(ea); }while ( i ); coagulate_unused_data(&u.s); } } // for //change format of string presentation in constant pool for(int pos = 0; (ushort)pos <= curClass.maxCPindex; pos++) { ConstOpis co; if ( constnode.supval(pos, &co, sizeof(co)) != sizeof(co) ) goto badbase; switch ( co.type ) { default: continue; case CONSTANT_Unicode: error("Base contain CONSTANT_Unicode, but it is removed from " "the standard in 1996 year and never normal loaded in IDA"); case CONSTANT_Utf8: break; } uint32 v, n, i = pos << 16; if( ((n = (uint32)constnode.altval(i)) & UPG12_BADMASK) || (v = n & ~UPG12_CLRMASK) == 0) goto badbase; if ( n & UPG12_EXTMASK ) v |= UPG12_EXTSET; if ( (n = (ushort)v) != 0 ) { register uchar *po = (uchar*)append_tmp_buffer(v); n *= sizeof(ushort); uint32 pos = 0; do { uint32 sz = n - pos; if ( sz > MAXSPECSIZE ) sz = MAXSPECSIZE; if ( constnode.supval(++i, &po[pos], sz) != sz ) goto badbase; constnode.supdel(i); pos += sz; }while ( pos < n ); constnode.setblob(po, n, i & ~0xFFFF, BLOB_TAG); if ( !(v & UPG12_EXTSET) ) do { #ifdef __BORLANDC__ #if ( sizeof(ushort) % 2) || (MAXSPECSIZE % 2 ) #error #endif #endif ushort cw = *(ushort *)&po[pos]; if ( cw >= CHP_MAX ) { if ( !javaIdent(cw) ) goto extchar; } else if ( (uchar)cw <= CHP_MIN ) { extchar: v |= UPG12_EXTSET; break; } }while ( (pos -= sizeof(ushort)) != 0 ); v = upgrade_ResW(v); } constnode.altset(i, v); co._Sopstr = v; // my be not needed? (next also) constnode.supset(pos, &co, sizeof(co)); } // rename 'import' variables for refernces for(unsigned ip = 1; (ushort)ip <= curClass.xtrnCnt; ip++) { ConstOpis co; { register unsigned j; if( (j = (unsigned)XtrnNode.altval(ip)) == 0 || !LoadOpis((ushort)j, 0, &co)) goto badbase; } switch ( co.type ) { default: goto badbase; case CONSTANT_Class: if ( !(co.flag & HAS_CLSNAME) ) continue; break; case CONSTANT_InterfaceMethodref: case CONSTANT_Methodref: if ( (co.flag & NORM_METOD) != NORM_METOD ) continue; break; case CONSTANT_Fieldref: if ( (co.flag & NORM_FIELD) != NORM_FIELD ) continue; break; } make_new_name(co._name, co._subnam, co.type != CONSTANT_Class, ip); } if ( curClass.This.Dscr ) make_new_name(curClass.This.Name, 0, (uchar)-1, (unsigned)curClass.startEA); return(_TO_VERSION); }