/* * $Id$ * * scanbd - KMUX scanner button daemon * * Copyright (C) 2008 Wilhelm Meier (wilhelm.meier@fh-kl.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "scanbd.h" #include "scanbd_dbus.h" // this is non-portable (should have an initializer function here) #ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP #error "PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP not available" #endif // all programm-global sane functions use this mutex to avoid races pthread_mutex_t sane_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; struct sane_opt_value { unsigned long num_value; // before-value or after-value or actual-value (BOOL|INT|FIXED) struct { // (SRING) char* str; // actual-value regex_t* reg; // before-regex or after-regex } str_value; }; typedef struct sane_opt_value sane_opt_value_t; struct sane_dev_option { int number; // the option-number of the device-option sane_opt_value_t from_value; // the before-value of the option sane_opt_value_t to_value; // the after-value of the option (to // fire the trigger) sane_opt_value_t value; // the option value (from the last // polling cycle) const char* script; // the found (matched) script to be called if // the option-valued changes const char* action_name; // the name of this action as // specified in the config file }; typedef struct sane_dev_option sane_dev_option_t; struct sane_dev_function { int number; // the option-number of the // device-option const char* env; // the name of the environment-var to // pass to option value in }; typedef struct sane_dev_function sane_dev_function_t; // each polling thread is represented by struct sane_thread // there is no locking, since this is "thread private data" struct sane_thread { pthread_t tid; // the thread-id of the polling // thread pthread_mutex_t mutex; // mutex for this data-structure pthread_cond_t cv; // cv for this data-structure bool triggered; // a rule for this device has fired (triggered == true) int triggered_option; // the option_number which triggered const SANE_Device* dev; // the device int num_of_options; // the number of all options for // this device SANE_Handle h; // the handle of the opened device sane_dev_option_t *opts; // the list of matched actions // for this device int num_of_options_with_scripts; // the number of elements in the // above list sane_dev_function_t *functions; // the list of matched functions // for this device int num_of_options_with_functions;// the number of elements in the // above list }; typedef struct sane_thread sane_thread_t; // the list of all polling threads static sane_thread_t* sane_poll_threads = NULL; // the list of all devices locally connected to our system static const SANE_Device** sane_device_list = NULL; // the number of devices = the number of polling threads static int num_devices = 0; void get_sane_devices(void) { // detect all the scanners we have slog(SLOG_INFO, "Scanning for local-only devices" ); if (pthread_mutex_lock(&sane_mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return; } SANE_Status sane_status = 0; sane_device_list = NULL; if ((sane_status = sane_get_devices(&sane_device_list, SANE_TRUE)) != SANE_STATUS_GOOD) { slog(SLOG_WARN, "Can't get the sane device list"); } const SANE_Device** dev = sane_device_list; if (dev == NULL) { slog(SLOG_DEBUG, "device list null"); goto cleanup; } num_devices = 0; while(*dev != NULL) { slog(SLOG_DEBUG, "found device: %s %s %s %s", (*dev)->name, (*dev)->vendor, (*dev)->model, (*dev)->type); num_devices += 1; dev++; } cleanup: if (pthread_mutex_unlock(&sane_mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return; } } // simple hash function for C-strings static unsigned long hash(const char *str) { unsigned long hash = 5381; int c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } return hash; } static void sane_option_value_init(sane_opt_value_t* v) { v->num_value = 0; v->str_value.str = NULL; v->str_value.reg = NULL; } static void sane_option_value_free(sane_opt_value_t* v) { if (v->str_value.str != NULL) { free((void*)v->str_value.str); v->str_value.str = NULL; } if (v->str_value.reg != NULL) { regfree(v->str_value.reg); v->str_value.reg = NULL; } } static sane_opt_value_t get_sane_option_value(SANE_Handle* h, int index) { slog(SLOG_DEBUG, "get_sane_option_value"); // get the value of option with index of the device (opened) with // handle h // if option can't be found or other catastrophy happens, the // value 0 gets returned sane_opt_value_t res; sane_option_value_init(&res); const SANE_Option_Descriptor* odesc = NULL; if ((odesc = sane_get_option_descriptor(h, index)) == NULL) { return res; } if ((odesc->type == SANE_TYPE_BOOL) || (odesc->type == SANE_TYPE_INT) || (odesc->type == SANE_TYPE_FIXED)) { unsigned long int value = 0; if ((unsigned int)odesc->size <= sizeof(long int)) { //if we can store it in an long int SANE_Status status; if ((status = sane_control_option(h, index, SANE_ACTION_GET_VALUE, &value, NULL)) != SANE_STATUS_GOOD) { slog(SLOG_WARN, "Can't read value of %s: %s", odesc->name, sane_strstatus(status)); return res; } res.num_value = value; return res; } else { // shouldn't happen slog(SLOG_WARN, "Value of %s, sane-type %d too big", odesc->name, odesc->type); return res; } } else if (odesc->type == SANE_TYPE_STRING) { res.str_value.str = calloc(odesc->size + 1, sizeof(char)); assert(res.str_value.str != NULL); SANE_Status status; if ((status = sane_control_option(h, index, SANE_ACTION_GET_VALUE, res.str_value.str, NULL)) != SANE_STATUS_GOOD) { slog(SLOG_WARN, "Can't read value of %s: %s", odesc->name, sane_strstatus(status)); return res; } res.str_value.str[odesc->size] = '\0'; size_t slen = strlen(res.str_value.str); res.num_value = hash(res.str_value.str); slog(SLOG_INFO, "Value of %s as string (len %d, hash %d): %s", odesc->name, slen, res.num_value, res.str_value.str); return res; } else { slog(SLOG_WARN, "Can't read option %s of type %d", odesc->name, odesc->type); } return res; } // cleanup handler for sane_poll static void sane_thread_cleanup_mutex(void* arg) { assert(arg != NULL); slog(SLOG_DEBUG, "sane_thread_cleanup_mutex"); pthread_mutex_t* mutex = (pthread_mutex_t*)arg; if (pthread_mutex_unlock(mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); } } // cleanup handler for sane_poll static void sane_thread_cleanup_value(void* arg) { assert(arg != NULL); slog(SLOG_DEBUG, "sane_thread_cleanup_value"); sane_opt_value_t* v = (sane_opt_value_t*)arg; sane_option_value_free(v); } // this function can only be used in the critical region of *st static void sane_find_matching_functions(sane_thread_t* st, cfg_t* sec) { // TODO: use of recursive mutex??? slog(SLOG_DEBUG, "sane_find_matching_functions"); const char* title = cfg_title(sec); if (title == NULL) { title = "(null)"; } int functions = cfg_size(sec, C_FUNCTION); if (functions <= 0) { slog(SLOG_INFO, "no matching functions in section %s", title); return; } slog(SLOG_INFO, "found %d functions in section %s", functions, title); // iterate over all global functions for(int i = 0; i < functions; i += 1) { // get the function from the config file cfg_t* function_i = cfg_getnsec(sec, C_FUNCTION, i); assert(function_i != NULL); // get the filter-regex from the config-file const char* opt_regex = cfg_getstr(function_i, C_FILTER); assert(opt_regex != NULL); const char* title = cfg_title(function_i); if (title == NULL) { title = "(none)"; } // compile the filter-regex slog(SLOG_DEBUG, "checking function %s with filter: %s", title, opt_regex); regex_t creg; int ret = regcomp(&creg, opt_regex, REG_EXTENDED | REG_NOSUB); if (ret < 0) { char err_text[1024]; regerror(ret, &creg, err_text, 1024); slog(SLOG_WARN, "Can't compile regex: %s : %s", opt_regex, err_text); continue; } // look for matching option-names for(int opt = 1; opt < st->num_of_options; opt += 1) { const SANE_Option_Descriptor* odesc = NULL; if ((odesc = sane_get_option_descriptor(st->h, opt)) == NULL) { // no valid option-descriptor available // skip it continue; } assert(odesc); if (!SANE_OPTION_IS_ACTIVE(odesc->cap)) { continue; } // option is active // only use active (user controllable) options if (odesc->name == NULL) { // we need a valid option-name continue; } assert(odesc->name); if (!((odesc->type == SANE_TYPE_BOOL) || (odesc->type == SANE_TYPE_INT) || (odesc->type == SANE_TYPE_FIXED)|| (odesc->type == SANE_TYPE_STRING))) { slog(SLOG_WARN, "option[%d] %s for device %s not of " "type BOOL|INT|FIXED|STRING. Skipping", opt, odesc->name, st->dev->name); continue; } slog(SLOG_INFO, "found active option[%d] %s (type: %d) for device %s", opt, odesc->name, odesc->type, st->dev->name); // regex compare with the filter if (regexec(&creg, odesc->name, 0, NULL, 0) != 0) { // no match continue; } // match // now get the script const char* env = cfg_getstr(function_i, C_ENV); assert(env != NULL); slog(SLOG_INFO, "installing function %s for %s, option[%d]: %s as env: %s", title, st->dev->name, opt, odesc->name, env); // looking for option already present in the // array int n = 0; for(n = 0; n < st->num_of_options_with_functions; n += 1) { if (st->functions[n].number == opt) { slog(SLOG_WARN, "function %s overrides function of option[%d]", title, n); // break out with n == index_of_found_option break; } } // 0 <= n < st->num_of_options_with_scripts: // we found it // n == st->num_of_options_with_scripts: // not found => new st->functions[n].number = opt; st->functions[n].env = env; if (n == st->num_of_options_with_functions) { // not found in the list // we have a new option to be polled st->num_of_options_with_functions += 1; } } // foreach option // this compiled regex isn't used anymore regfree(&creg); } // foreach action } // this function can only be used in the critical region of *st static void sane_find_matching_options(sane_thread_t* st, cfg_t* sec) { slog(SLOG_DEBUG, "sane_find_matching_options"); const char* title = cfg_title(sec); if (title == NULL) { title = "(null)"; } // TODO: use of recursive mutex??? int actions = cfg_size(sec, C_ACTION); if (actions <= 0) { slog(SLOG_INFO, "no matching actions in section %s", title); return; } slog(SLOG_INFO, "found %d actions in section %s", actions, title); // iterate over all global actions for(int i = 0; i < actions; i += 1) { // get the action from the config file cfg_t* action_i = cfg_getnsec(sec, C_ACTION, i); assert(action_i != NULL); // get the filter-regex from the config-file const char* opt_regex = cfg_getstr(action_i, C_FILTER); assert(opt_regex != NULL); const char* title = cfg_title(action_i); if (title == NULL) { title = "(none)"; } // compile the filter-regex slog(SLOG_DEBUG, "checking action %s with filter: %s", title, opt_regex); regex_t creg; int ret = regcomp(&creg, opt_regex, REG_EXTENDED | REG_NOSUB); if (ret < 0) { char err_text[1024]; regerror(ret, &creg, err_text, 1024); slog(SLOG_WARN, "Can't compile regex: %s : %s", opt_regex, err_text); continue; } // look for matching option-names for(int opt = 1; opt < st->num_of_options; opt += 1) { const SANE_Option_Descriptor* odesc = NULL; if ((odesc = sane_get_option_descriptor(st->h, opt)) == NULL) { // no valid option-descriptor available // skip it continue; } assert(odesc); if (!SANE_OPTION_IS_ACTIVE(odesc->cap)) { continue; } // option is active // only use active (user controllable) options if (odesc->name == NULL) { // we need a valid option-name continue; } assert(odesc->name); if (!((odesc->type == SANE_TYPE_BOOL) || (odesc->type == SANE_TYPE_INT) || (odesc->type == SANE_TYPE_FIXED)|| (odesc->type == SANE_TYPE_STRING))) { slog(SLOG_WARN, "option[%d] %s for device %s not of " "type BOOL|INT|FIXED|STRING. Skipping", opt, odesc->name, st->dev->name); continue; } slog(SLOG_INFO, "found active option[%d] %s (type: %d) for device %s", opt, odesc->name, odesc->type, st->dev->name); // regex compare with the filter if (regexec(&creg, odesc->name, 0, NULL, 0) != 0) { // no match continue; } // match // now get the script const char* script = cfg_getstr(action_i, C_SCRIPT); assert(script != NULL); slog(SLOG_INFO, "installing action %s for %s, option[%d]: %s as: %s", title, st->dev->name, opt, odesc->name, script); // looking for option already present in the // array int n = 0; for(n = 0; n < st->num_of_options_with_scripts; n += 1) { if (st->opts[n].number == opt) { slog(SLOG_WARN, "action %s overrides script %s of option[%d] with %s", title, st->opts[n].script, n, script); // break out with n == index_of_found_option break; } } // 0 <= n < st->num_of_options_with_scripts: // we found it // n == st->num_of_options_with_scripts: // not found => new st->opts[n].number = opt; st->opts[n].action_name = title; st->opts[n].script = script; sane_option_value_free(&st->opts[n].from_value); sane_option_value_free(&st->opts[n].to_value); sane_option_value_free(&st->opts[n].value); if ((odesc->type == SANE_TYPE_BOOL) || (odesc->type == SANE_TYPE_INT) || (odesc->type == SANE_TYPE_FIXED)) { // numerical option cfg_t* num_trigger = cfg_getsec(action_i, C_NUMERICAL_TRIGGER); assert(num_trigger); st->opts[n].from_value.num_value = cfg_getint(num_trigger, C_FROM_VALUE); st->opts[n].to_value.num_value = cfg_getint(num_trigger, C_TO_VALUE); st->opts[n].value = get_sane_option_value(st->h, opt); slog(SLOG_INFO, "Initial value of option %s is %d", odesc->name, st->opts[n].value); } // type BOOL | INT || FIXED else if (odesc->type == SANE_TYPE_STRING) { bool valid = true; // string option cfg_t* str_trigger = cfg_getsec(action_i, C_STRING_TRIGGER); assert(str_trigger); st->opts[n].from_value.str_value.str = strdup(cfg_getstr(str_trigger, C_FROM_VALUE)); st->opts[n].from_value.str_value.reg = malloc(sizeof(regex_t)); int ret = 0; ret = regcomp(st->opts[n].from_value.str_value.reg, st->opts[n].from_value.str_value.str, REG_EXTENDED | REG_NOSUB); if (ret < 0) { char err_text[1024]; regerror(ret, &creg, err_text, 1024); slog(SLOG_WARN, "Can't compile regex: %s : %s", st->opts[n].from_value.str_value.str, err_text); valid = false;; } st->opts[n].to_value.str_value.str = strdup(cfg_getstr(str_trigger, C_TO_VALUE)); st->opts[n].to_value.str_value.reg = malloc(sizeof(regex_t)); ret = regcomp(st->opts[n].to_value.str_value.reg, st->opts[n].to_value.str_value.str, REG_EXTENDED | REG_NOSUB); if (ret < 0) { char err_text[1024]; regerror(ret, &creg, err_text, 1024); slog(SLOG_WARN, "Can't compile regex: %s : %s", st->opts[n].to_value.str_value.str, err_text); valid = false;; } st->opts[n].value = get_sane_option_value(st->h, opt); if (!valid) { sane_option_value_free(&st->opts[n].from_value); sane_option_value_free(&st->opts[n].to_value); sane_option_value_free(&st->opts[n].value); continue; } } // type STRING else { assert(false); // should not happen } if (n == st->num_of_options_with_scripts) { // not found in the list // we have a new option to be polled st->num_of_options_with_scripts += 1; } } // foreach option // this compiled regex isn't used anymore regfree(&creg); } // foreach action } // thread start funktion // TODO: refactor, this is awfull long! static void* sane_poll(void* arg) { sane_thread_t* st = (sane_thread_t*)arg; assert(st != NULL); slog(SLOG_DEBUG, "sane_poll"); // we only expect the main thread to handle signals sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_BLOCK, &mask, NULL); // this thread uses the device and the san_thread_t datastructure // lock it pthread_cleanup_push(sane_thread_cleanup_mutex, ((void*)&st->mutex)); if (pthread_mutex_lock(&st->mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return NULL; } // open the device this thread should poll SANE_Status status = 0; if ((status = sane_open(st->dev->name, &st->h)) != SANE_STATUS_GOOD) { slog(SLOG_ERROR, "Can't open device %s: %s", st->dev->name, sane_strstatus(status)); slog(SLOG_WARN, "abandon polling of %s", st->dev->name); return NULL; } // figure out the number of options this device has // option 0 (zero) is guaranteed to exist with the total number of // options of that device (including option 0) st->num_of_options = 0; if ((status = sane_control_option(st->h, 0, SANE_ACTION_GET_VALUE, &st->num_of_options, 0)) != SANE_STATUS_GOOD) { slog(SLOG_ERROR, "Can't get the number of scanner options"); return NULL; } if (st->num_of_options == 0) { // no options -> nothing to poll slog(SLOG_INFO, "No options for device %s", st->dev->name); return NULL; } slog(SLOG_INFO, "found %d options for device %s", st->num_of_options, st->dev->name); // allocate an array of options for the matching actions // // only one script is possible per option, later matching // actions overwrite previous ones // initialize the list of matching options if (st->opts != NULL) { slog(SLOG_ERROR, "possible memory leak: %s, %d", __FILE__, __LINE__); } st->opts = NULL; st->opts = (sane_dev_option_t*) calloc(st->num_of_options, sizeof(sane_dev_option_t)); assert(st->opts != NULL); for(int i = 0; i < st->num_of_options; i += 1) { sane_option_value_init(&st->opts[i].from_value); sane_option_value_init(&st->opts[i].to_value); sane_option_value_init(&st->opts[i].value); } // the number of valid entries in the above list st->num_of_options_with_scripts = 0; // initialize the list of matching functions if (st->functions != NULL) { slog(SLOG_ERROR, "possible memory leak: %s, %d", __FILE__, __LINE__); } st->functions = NULL; st->functions = (sane_dev_function_t*) calloc(st->num_of_options, sizeof(sane_dev_function_t)); assert(st->functions != NULL); for(int i = 0; i < st->num_of_options; i += 1) { st->functions[i].number = 0; st->functions[i].env = NULL; } // the number of valid entries in the above list st->num_of_options_with_functions = 0; // find out the functions and actions // get the global sconfig section cfg_t* cfg_sec_global = NULL; assert((cfg_sec_global = cfg_getsec(cfg, C_GLOBAL)) != NULL); // find the global actions sane_find_matching_options(st, cfg_sec_global); // find the global functions sane_find_matching_functions(st, cfg_sec_global); // find (if any) device specifc sections // these override global definitions, if any int local_sections = cfg_size(cfg, C_DEVICE); slog(SLOG_DEBUG, "found %d local device sections", local_sections); for(int loc = 0; loc < local_sections; loc += 1) { cfg_t* loc_i = cfg_getnsec(cfg, C_DEVICE, loc); assert(loc_i != NULL); // get the filter-regex from the config-file const char* loc_regex = cfg_getstr(loc_i, C_FILTER); assert(loc_regex != NULL); const char* title = cfg_title(loc_i); if (title == NULL) { title = "(none)"; } // compile the filter-regex slog(SLOG_INFO, "checking device section %s with filter: %s", title, loc_regex); regex_t creg; int ret = regcomp(&creg, loc_regex, REG_EXTENDED | REG_NOSUB); if (ret < 0) { char err_text[1024]; regerror(ret, &creg, err_text, 1024); slog(SLOG_WARN, "Can't compile regex: %s : %s", loc_regex, err_text); continue; } // compare the regex against the device name if (regexec(&creg, st->dev->name, 0, NULL, 0) == 0) { // match int loc_actions = cfg_size(loc_i, C_ACTION); slog(SLOG_INFO, "found %d local action for device %s [%s]", loc_actions, st->dev->name, title); // get the local actions for this device sane_find_matching_options(st, loc_i); continue; } } // foreach local section int timeout = cfg_getint(cfg_sec_global, C_TIMEOUT); if (timeout <= 0) { timeout = C_TIMEOUT_DEF; } slog(SLOG_DEBUG, "timeout: %d ms", timeout); slog(SLOG_DEBUG, "Start the polling for device %s", st->dev->name); while(true) { slog(SLOG_DEBUG, "polling thread for %s cancellation point", st->dev->name); // special cancellation point pthread_testcancel(); slog(SLOG_DEBUG, "polling device %s", st->dev->name); for(int i = 0; i < st->num_of_options_with_scripts; i += 1) { const SANE_Option_Descriptor* odesc = NULL; assert((odesc = sane_get_option_descriptor(st->h, st->opts[i].number)) != NULL); if (st->opts[i].script != NULL) { if (strlen(st->opts[i].script) <= 0) { slog(SLOG_WARN, "No script for option %s for device %s", odesc->name, st->dev->name); continue; } } else { slog(SLOG_WARN, "No script for option %s for device %s", odesc->name, st->dev->name); continue; } assert(st->opts[i].script != NULL); assert(strlen(st->opts[i].script) > 0); sane_opt_value_t value; sane_option_value_init(&value); // push the cleanup-handle to free the value storage pthread_cleanup_push(sane_thread_cleanup_value, &value); // get the actual value value = get_sane_option_value(st->h, st->opts[i].number); slog(SLOG_INFO, "checking option %s for device %s: value: %d", odesc->name, st->dev->name, value); if ((odesc->type == SANE_TYPE_BOOL) || (odesc->type == SANE_TYPE_INT) || (odesc->type == SANE_TYPE_FIXED)) { if ((st->opts[i].from_value.num_value == st->opts[i].value.num_value) && (st->opts[i].to_value.num_value == value.num_value)) { slog(SLOG_DEBUG, "value trigger: numerial"); st->triggered = true; st->triggered_option = i; // we need to trigger all waiting threads if (pthread_cond_broadcast(&st->cv) < 0) { slog(SLOG_ERROR, "pthread_cond_broadcats: this shouln't happen"); } } } else if (odesc->type == SANE_TYPE_STRING) { if ((regexec(st->opts[i].from_value.str_value.reg, st->opts[i].value.str_value.str, 0, NULL, 0) == 0) && (regexec(st->opts[i].to_value.str_value.reg, value.str_value.str, 0, NULL, 0) == 0)) { slog(SLOG_DEBUG, "value trigger: string"); st->triggered = true; st->triggered_option = i; // we need to trigger all waiting threads if (pthread_cond_broadcast(&st->cv) < 0) { slog(SLOG_ERROR, "pthread_cond_broadcats: this shouln't happen"); } } } else { assert(false); } // free the previous allocated value sane_option_value_free(&st->opts[i].value); // pass the responsibility to free the value to the main // thread, if this thread gets canceled if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL) < 0) { slog(SLOG_ERROR, "pthread_setcancelstate: %s", strerror(errno)); } st->opts[i].value = value; pthread_cleanup_pop(0); if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) < 0) { slog(SLOG_ERROR, "pthread_setcancelstate: %s", strerror(errno)); } // was there a value change? if (st->triggered) { assert(st->triggered_option >= 0); // index into the opts-array assert(st->triggered_option < st->num_of_options_with_scripts); slog(SLOG_ERROR, "trigger action for %s for device %s with script %s", odesc->name, st->dev->name, st->opts[st->triggered_option].script); // prepare the environment for the script to be called // number of env-vars = // number of found function-options // plus the values in the environment-section (2): // device, action // plus those 4: // PATH, PWD, USER, HOME // plus the sentinel cfg_t* global_envs = cfg_getsec(cfg_sec_global, C_ENVIRONMENT); int number_of_envs = st->num_of_options_with_functions + 4 + 2 + 1; char** env = calloc(number_of_envs, sizeof(char*)); for(int e = 0; e < number_of_envs; e += 1) { env[e] = calloc(NAME_MAX + 1, sizeof(char)); } int e = 0; for(e = 0; e < st->num_of_options_with_functions; e += 1) { const SANE_Option_Descriptor* fdesc = NULL; assert((fdesc = sane_get_option_descriptor(st->h, st->functions[e].number)) != NULL); // check if the function-option is the same // as a action-option. If so, use the // action-option value instead of re-get the same // option value, because it is (may be) reset // after the query by the backend sane_opt_value_t v; sane_option_value_init(&v); int o = 0; for(o = 0; o < st->num_of_options_with_scripts; o += 1) { if (st->opts[o].number == st->functions[e].number) { break; } } if (o == st->num_of_options_with_scripts) { // not found: query the value v = get_sane_option_value(st->h, st->functions[e].number); } else { // found: copy the value slog(SLOG_DEBUG, "copy the value of option %d", st->opts[o].number); v.num_value = st->opts[o].value.num_value; if (st->opts[o].value.str_value.str != NULL) { v.str_value.str = strdup(st->opts[o].value.str_value.str); assert(v.str_value.str != NULL); } } if ((fdesc->type == SANE_TYPE_BOOL) || (fdesc->type == SANE_TYPE_INT) || (fdesc->type == SANE_TYPE_FIXED)) { snprintf(env[e], NAME_MAX, "%s=%lud", st->functions[e].env, v.num_value); slog(SLOG_DEBUG, "setting env: %s", env[e]); } else if (fdesc->type == SANE_TYPE_STRING) { snprintf(env[e], NAME_MAX, "%s=%s", st->functions[e].env, v.str_value.str); slog(SLOG_DEBUG, "setting env: %s", env[e]); } else { assert(false); } sane_option_value_free(&v); } const char* ev = "PATH"; if (getenv(ev) != NULL) { snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev)); slog(SLOG_DEBUG, "setting env: %s", env[e]); e += 1; } ev = "PWD"; if (getenv(ev) != NULL) { snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev)); slog(SLOG_DEBUG, "setting env: %s", env[e]); e += 1; } ev = "USER"; if (getenv(ev) != NULL) { snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev)); slog(SLOG_DEBUG, "setting env: %s", env[e]); e += 1; } ev = "HOME"; if (getenv(ev) != NULL) { snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev)); slog(SLOG_DEBUG, "setting env: %s", env[e]); e += 1; } ev = cfg_getstr(global_envs, C_DEVICE); if (ev != NULL) { snprintf(env[e], NAME_MAX, "%s=%s", ev, st->dev->name); slog(SLOG_DEBUG, "setting env: %s", env[e]); e += 1; } ev = cfg_getstr(global_envs, C_ACTION); if (ev != NULL) { snprintf(env[e], NAME_MAX, "%s=%s", ev, st->opts[st->triggered_option].action_name); slog(SLOG_DEBUG, "setting env: %s", env[e]); e += 1; } env[e] = NULL; assert(e == number_of_envs-1); // sendout an dbus-signal with all the values as // arguments dbus_send_signal_argv(SCANBD_DBUS_SIGNAL_TRIGGER, env); // the action-script will use the device, // so we have to release the device sane_close(st->h); st->h = NULL; assert(st->triggered_option >= 0); assert(st->opts[st->triggered_option].script); assert(strlen(st->opts[st->triggered_option].script) > 0); // need to copy the values because we leave the // crirical section // int triggered_option = st->triggered_option; char* script = strdup(st->opts[st->triggered_option].script); assert(script != NULL); // leave the critical section if (pthread_mutex_unlock(&st->mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return NULL; } assert(timeout > 0); usleep(timeout * 1000); //ms pid_t cpid; if ((cpid = fork()) < 0) { slog(SLOG_ERROR, "Can't fork: %s", strerror(errno)); } else if (cpid > 0) { // parent slog(SLOG_INFO, "waiting for child: %s", script); int status; if (waitpid(cpid, &status, 0) < 0) { slog(SLOG_ERROR, "waitpid: %s", strerror(errno)); } if (WIFEXITED(status)) { slog(SLOG_INFO, "child %s exited with status: %d", script, WEXITSTATUS(status)); } if (WIFSIGNALED(status)) { slog(SLOG_INFO, "child % signaled with signal: %d", script, WTERMSIG(status)); } } else { // child slog(SLOG_DEBUG, "exec for %s", script); if (execle(script, script, NULL, env) < 0) { slog(SLOG_ERROR, "execlp: %s", strerror(errno)); } exit(EXIT_FAILURE); // not reached } assert(script != NULL); free(script); // free (last element is the sentinel!) assert(env != NULL); for(int e = 0; e < number_of_envs - 1; e += 1) { assert(env[e] != NULL); free(env[e]); } free(env); // enter the critical section if (pthread_mutex_lock(&st->mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return NULL; } st->triggered = false; st->triggered_option = 0; // invalid // we need to trigger all waiting threads if (pthread_cond_broadcast(&st->cv) < 0) { slog(SLOG_ERROR, "pthread_cond_broadcats: this shouln't happen"); } // leave the critical section if (pthread_mutex_unlock(&st->mutex) < 0) { // if we can't release the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return NULL; } // sleep the timeout to settle devices, necessary? usleep(timeout * 1000); //ms // enter the critical section if (pthread_mutex_lock(&st->mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return NULL; } slog(SLOG_DEBUG, "reopen device %s", st->dev->name); if ((status = sane_open(st->dev->name, &st->h)) != SANE_STATUS_GOOD) { slog(SLOG_ERROR, "Can't open device %s, %s", st->dev->name, sane_strstatus(status)); if (status == SANE_STATUS_ACCESS_DENIED) { slog(SLOG_WARN, "abandon polling of %s", st->dev->name); return NULL; } } } // if triggered } // foreach option // release the mutex // because pthread_cleanup_pop is a macro we can't use it here // pthread_cleanup_pop(1); if (pthread_mutex_unlock(&st->mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return NULL; } // sleep the polling timeout usleep(timeout * 1000); //ms // regain the mutex // because pthread_cleanup_push is a macro we can't use it here // pthread_cleanup_push(sane_thread_cleanup_mutex, ((void*)&st->mutex)); if (pthread_mutex_lock(&st->mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return NULL; } } pthread_cleanup_pop(1); // release the mutex return NULL; } // helper to trigger a specified action from another thread // (e.g. dbus) via an action number void sane_trigger_action(int number_of_dev, int action) { assert(number_of_dev >= 0); assert(action >= 0); slog(SLOG_DEBUG, "sane_trigger_action device=%d, action=%d", number_of_dev, action); if (pthread_mutex_lock(&sane_mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return; } if (number_of_dev >= num_devices) { slog(SLOG_WARN, "No such device number %d", number_of_dev); goto cleanup_sane; } sane_thread_t* st = &sane_poll_threads[number_of_dev]; assert(st != NULL); // this thread uses the device and the sane_thread_t datastructure // lock it if (pthread_mutex_lock(&st->mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); goto cleanup_sane; } if (action >= st->num_of_options_with_scripts) { slog(SLOG_WARN, "No such action %d for device number %d", action, number_of_dev); goto cleanup_dev; } st->triggered = true; st->triggered_option = action; // we need to trigger all waiting threads if (pthread_cond_broadcast(&st->cv) < 0) { slog(SLOG_ERROR, "pthread_cond_broadcats: this shouln't happen"); } cleanup_dev: if (pthread_mutex_unlock(&st->mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); goto cleanup_sane; } cleanup_sane: if (pthread_mutex_unlock(&sane_mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return; } return; } void start_sane_threads(void) { slog(SLOG_DEBUG, "start_sane_threads"); if (pthread_mutex_lock(&sane_mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return; } if (sane_poll_threads != NULL) { // if there are active threads kill them stop_sane_threads(); } // allocate the thread list assert(sane_poll_threads == NULL); sane_poll_threads = (sane_thread_t*) calloc(num_devices, sizeof(sane_thread_t)); if (sane_poll_threads == NULL) { slog(SLOG_ERROR, "Can't allocate memory for polling threads"); goto cleanup; } // starting for each device a seperate thread for(int i = 0; i < num_devices; i += 1) { slog(SLOG_DEBUG, "Starting poll thread for %s", sane_device_list[i]->name); sane_poll_threads[i].tid = 0; sane_poll_threads[i].dev = sane_device_list[i]; sane_poll_threads[i].h = 0; sane_poll_threads[i].opts = NULL; sane_poll_threads[i].functions = NULL; sane_poll_threads[i].num_of_options = 0; sane_poll_threads[i].triggered = false; sane_poll_threads[i].triggered_option = 0; sane_poll_threads[i].num_of_options_with_scripts = 0; sane_poll_threads[i].num_of_options_with_functions = 0; if (pthread_mutex_init(&sane_poll_threads[i].mutex, NULL) < 0) { slog(SLOG_ERROR, "pthread_mutex_init: should not happen"); } if (pthread_cond_init(&sane_poll_threads[i].cv, NULL) < 0) { slog(SLOG_ERROR, "pthread_cond_init: should not happen"); } if (pthread_create(&sane_poll_threads[i].tid, NULL, sane_poll, (void*)&sane_poll_threads[i]) < 0) { slog(SLOG_ERROR, "Can't start sane_poll_thread: %s", strerror(errno)); exit(EXIT_FAILURE); } slog(SLOG_DEBUG, "Thread started for device %s", sane_device_list[i]->name); } cleanup: if (pthread_mutex_unlock(&sane_mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return; } } // stops all sane polling threads void stop_sane_threads(void) { slog(SLOG_DEBUG, "stop_sane_threads"); if (pthread_mutex_lock(&sane_mutex) < 0) { // if we can't get the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_lock: %s", strerror(errno)); return; } if (sane_poll_threads == NULL) { // we don't have any active threads slog(SLOG_DEBUG, "stop_sane_threads: nothing to stop"); goto cleanup; } // sending cancel request to all threads for(int i = 0; i < num_devices; i += 1) { slog(SLOG_DEBUG, "stopping poll thread for device %s", (*(sane_device_list + i))->name); if (pthread_cancel(sane_poll_threads[i].tid) < 0) { if (errno == ESRCH) { slog(SLOG_ERROR, "poll thread for device %s was already cancelled"); } else { slog(SLOG_ERROR, "unknown error from pthread_cancel: %s", strerror(errno)); } } } // waiting for all threads to vanish for(int i = 0; i < num_devices; i += 1) { slog(SLOG_DEBUG, "waiting for poll thread for device %s", (*(sane_device_list + i))->name); if (pthread_join(sane_poll_threads[i].tid, NULL) < 0) { slog(SLOG_ERROR, "pthread_join: %s", strerror(errno)); } sane_poll_threads[i].tid = 0; // close the associated device of the thread slog(SLOG_DEBUG, "closing device %s", sane_poll_threads[i].dev->name); if (sane_poll_threads[i].h != NULL) { sane_close(sane_poll_threads[i].h); sane_poll_threads[i].h = NULL; } if (sane_poll_threads[i].opts) { slog(SLOG_DEBUG, "freeing opt ressources for device %s thread", sane_poll_threads[i].dev->name); // free the matching options list of that device / threads for (int k = 0; k < sane_poll_threads[i].num_of_options; k += 1) { sane_option_value_free(&sane_poll_threads[i].opts[k].from_value); sane_option_value_free(&sane_poll_threads[i].opts[k].to_value); sane_option_value_free(&sane_poll_threads[i].opts[k].value); } free(sane_poll_threads[i].opts); sane_poll_threads[i].opts = NULL; } if (sane_poll_threads[i].functions) { slog(SLOG_DEBUG, "freeing funtion ressources for device %s thread", sane_poll_threads[i].dev->name); free(sane_poll_threads[i].functions); sane_poll_threads[i].functions = NULL; } if (pthread_mutex_destroy(&sane_poll_threads[i].mutex) < 0) { slog(SLOG_ERROR, "pthread_mutex_destroy: %s", strerror(errno)); } if (pthread_cond_destroy(&sane_poll_threads[i].cv) < 0) { slog(SLOG_ERROR, "pthread_cond_destroy: %s", strerror(errno)); } } // free the thread list free(sane_poll_threads); sane_poll_threads = NULL; // no threads active anymore cleanup: if (pthread_mutex_unlock(&sane_mutex) < 0) { // if we can't unlock the mutex, something is heavily wrong! slog(SLOG_ERROR, "pthread_mutex_unlock: %s", strerror(errno)); return; } }